mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.8`: - [Kubernetes dashboard fixes/improvements (#158605)](https://github.com/elastic/kibana/pull/158605) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Karl Godard","email":"karl.godard@elastic.co"},"sourceCommit":{"committedDate":"2023-06-01T20:53:52Z","message":"Kubernetes dashboard fixes/improvements (#158605)\n\n## Summary\r\n\r\nFixes a number of issues on the Kubernetes dashboard in Security.\r\n\r\n**Bug fixes:**\r\n- Fixed an issue where a default query was being added in a useMemo\r\nwhich would override the global query causing all charts to never be\r\nfiltered by the search bar above.\r\n- The empty state would sometimes prevent new search requests from\r\nrunning which would force the user to do a full page refresh\r\n- i18n formatting fix ups\r\n- Includes an update to the cloud_defend regex validation patterns for\r\ncontainerImageName and containerImageFullName\r\n\r\n**Enhancements:**\r\n- Tree view selection remembered (via useLocalStorage)\r\n\r\n**Deprecations**\r\n- Removed \"responder console\" code from k8s dashboard. The k8smd service\r\nis slated to be deprecated from Endpoint.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [x] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [x] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [x] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n---------\r\n\r\nCo-authored-by: sec_cloudnative_integrations <sec-cloudnative-integrations@elastic.co>","sha":"432a5d7734f127c321f75679be913a87bc3c71a6","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:prev-minor","Team: Cloud Native Integrations","v8.9.0","v8.8.1"],"number":158605,"url":"https://github.com/elastic/kibana/pull/158605","mergeCommit":{"message":"Kubernetes dashboard fixes/improvements (#158605)\n\n## Summary\r\n\r\nFixes a number of issues on the Kubernetes dashboard in Security.\r\n\r\n**Bug fixes:**\r\n- Fixed an issue where a default query was being added in a useMemo\r\nwhich would override the global query causing all charts to never be\r\nfiltered by the search bar above.\r\n- The empty state would sometimes prevent new search requests from\r\nrunning which would force the user to do a full page refresh\r\n- i18n formatting fix ups\r\n- Includes an update to the cloud_defend regex validation patterns for\r\ncontainerImageName and containerImageFullName\r\n\r\n**Enhancements:**\r\n- Tree view selection remembered (via useLocalStorage)\r\n\r\n**Deprecations**\r\n- Removed \"responder console\" code from k8s dashboard. The k8smd service\r\nis slated to be deprecated from Endpoint.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [x] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [x] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [x] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n---------\r\n\r\nCo-authored-by: sec_cloudnative_integrations <sec-cloudnative-integrations@elastic.co>","sha":"432a5d7734f127c321f75679be913a87bc3c71a6"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/158605","number":158605,"mergeCommit":{"message":"Kubernetes dashboard fixes/improvements (#158605)\n\n## Summary\r\n\r\nFixes a number of issues on the Kubernetes dashboard in Security.\r\n\r\n**Bug fixes:**\r\n- Fixed an issue where a default query was being added in a useMemo\r\nwhich would override the global query causing all charts to never be\r\nfiltered by the search bar above.\r\n- The empty state would sometimes prevent new search requests from\r\nrunning which would force the user to do a full page refresh\r\n- i18n formatting fix ups\r\n- Includes an update to the cloud_defend regex validation patterns for\r\ncontainerImageName and containerImageFullName\r\n\r\n**Enhancements:**\r\n- Tree view selection remembered (via useLocalStorage)\r\n\r\n**Deprecations**\r\n- Removed \"responder console\" code from k8s dashboard. The k8smd service\r\nis slated to be deprecated from Endpoint.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [x] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [x] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [x] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n---------\r\n\r\nCo-authored-by: sec_cloudnative_integrations <sec-cloudnative-integrations@elastic.co>","sha":"432a5d7734f127c321f75679be913a87bc3c71a6"}},{"branch":"8.8","label":"v8.8.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
958823469a
commit
d2aab8fc2a
29 changed files with 128 additions and 519 deletions
|
@ -399,6 +399,9 @@ describe('<ControlGeneralViewSelector />', () => {
|
|||
|
||||
if (el) {
|
||||
userEvent.type(el, 'docker.io/nginx{enter}');
|
||||
userEvent.type(el, 'docker.io/nginx-dev{enter}');
|
||||
userEvent.type(el, 'docker.io/nginx.dev{enter}');
|
||||
userEvent.type(el, '127.0.0.1:8080/nginx_dev{enter}');
|
||||
} else {
|
||||
throw new Error("Can't find input");
|
||||
}
|
||||
|
@ -409,7 +412,7 @@ describe('<ControlGeneralViewSelector />', () => {
|
|||
expect(findByText(regexError)).toMatchObject({});
|
||||
|
||||
userEvent.type(el, 'nginx{enter}');
|
||||
updatedSelector = onChange.mock.calls[2][0];
|
||||
updatedSelector = onChange.mock.calls[5][0];
|
||||
rerender(<WrappedComponent selector={updatedSelector} />);
|
||||
|
||||
expect(getByText(regexError)).toBeTruthy();
|
||||
|
@ -506,7 +509,8 @@ describe('<ControlGeneralViewSelector />', () => {
|
|||
throw new Error("Can't find input");
|
||||
}
|
||||
|
||||
const expectedError = '"containerImageName" values must match the pattern: /^[a-z0-9]+$/';
|
||||
const expectedError =
|
||||
'"containerImageName" values must match the pattern: /^([a-z0-9]+(?:[._-][a-z0-9]+)*)$/';
|
||||
|
||||
expect(getByText(expectedError)).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]+$"
|
||||
"pattern": "^([a-z0-9]+(?:[._-][a-z0-9]+)*)$"
|
||||
}
|
||||
},
|
||||
"containerImageTag": {
|
||||
|
@ -133,7 +133,7 @@
|
|||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^(?:\\[[a-fA-F0-9:]+\\]|(?:[a-zA-Z0-9-](?:\\.[a-z0-9]+)*)+)(?::[0-9]+)?(?:\\/[a-z0-9]+)+$"
|
||||
"pattern": "^(?:\\[[a-fA-F0-9:]+\\]|(?:[a-zA-Z0-9-](?:\\.[a-z0-9]+)*)+)(?::[0-9]+)?(?:\\/[a-z0-9]+(?:[._-][a-z0-9]+)*)+$"
|
||||
}
|
||||
},
|
||||
"kubernetesClusterId": {
|
||||
|
@ -261,7 +261,7 @@
|
|||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]+$"
|
||||
"pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*$"
|
||||
}
|
||||
},
|
||||
"containerImageTag": {
|
||||
|
@ -276,7 +276,7 @@
|
|||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^(?:\\[[a-fA-F0-9:]+\\]|(?:[a-zA-Z0-9-](?:\\.[a-z0-9]+)*)+)(?::[0-9]+)?(?:\\/[a-z0-9]+)+$"
|
||||
"pattern": "^(?:\\[[a-fA-F0-9:]+\\]|(?:[a-zA-Z0-9-](?:\\.[a-z0-9]+)*)+)(?::[0-9]+)?(?:\\/[a-z0-9]+(?:[._-][a-z0-9]+)*)+$"
|
||||
}
|
||||
},
|
||||
"kubernetesClusterId": {
|
||||
|
|
|
@ -77,7 +77,7 @@ describe('<Policies />', () => {
|
|||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the benchmarks table', () => {
|
||||
it('renders the policies table', () => {
|
||||
renderPolicies(
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
|
|
|
@ -104,13 +104,13 @@ export const SelectorConditionsMap: SelectorConditionsMapProps = {
|
|||
containerImageFullName: {
|
||||
type: 'stringArray',
|
||||
pattern:
|
||||
'^(?:\\[[a-fA-F0-9:]+\\]|(?:[a-zA-Z0-9-](?:\\.[a-z0-9]+)*)+)(?::[0-9]+)?(?:\\/[a-z0-9]+)+$',
|
||||
'^(?:\\[[a-fA-F0-9:]+\\]|(?:[a-zA-Z0-9-](?:\\.[a-z0-9]+)*)+)(?::[0-9]+)?(?:\\/[a-z0-9]+(?:[._-][a-z0-9]+)*)+$',
|
||||
patternError: i18n.errorInvalidFullContainerImageName,
|
||||
not: ['containerImageName'],
|
||||
},
|
||||
containerImageName: {
|
||||
type: 'stringArray',
|
||||
pattern: '^[a-z0-9]+$',
|
||||
pattern: '^([a-z0-9]+(?:[._-][a-z0-9]+)*)$',
|
||||
not: ['containerImageFullName'],
|
||||
},
|
||||
containerImageTag: { type: 'stringArray' },
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export const KUBERNETES_PATH = '/kubernetes' as const;
|
||||
export const KUBERNETES_TITLE = 'Kubernetes';
|
||||
export const LOCAL_STORAGE_HIDE_WIDGETS_KEY = 'kubernetesSecurity:shouldHideWidgets';
|
||||
export const LOCAL_STORAGE_TREE_NAV_KEY = 'kubernetesSecurity:treeNavSelection';
|
||||
|
||||
export const AGGREGATE_ROUTE = '/internal/kubernetes_security/aggregate';
|
||||
export const COUNT_ROUTE = '/internal/kubernetes_security/count';
|
||||
|
@ -44,20 +45,24 @@ export const COUNT_WIDGET_KEY_NODES = 'CountNodesWidgets';
|
|||
export const COUNT_WIDGET_KEY_PODS = 'CountPodsWidgets';
|
||||
export const COUNT_WIDGET_KEY_CONTAINER_IMAGES = 'CountContainerImagesWidgets';
|
||||
|
||||
export const DEFAULT_QUERY = '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}';
|
||||
export const DEFAULT_KUBERNETES_FILTER_QUERY =
|
||||
'{"bool":{"must":[],"filter":[{"bool": {"should": [{"exists": {"field": "orchestrator.cluster.id"}}]}}],"should":[],"must_not":[]}}';
|
||||
export const DEFAULT_FILTER_QUERY =
|
||||
'{"bool":{"must":[],"filter":[{"bool": {"should": [{"exists": {"field": "process.entry_leader.entity_id"}}]}}],"should":[],"must_not":[]}}';
|
||||
export const DEFAULT_FILTER = {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
exists: {
|
||||
field: ENTRY_LEADER_ENTITY_ID,
|
||||
field: ORCHESTRATOR_CLUSTER_ID,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_FILTER_QUERY = JSON.stringify({
|
||||
bool: {
|
||||
must: [],
|
||||
filter: [DEFAULT_FILTER],
|
||||
should: [],
|
||||
must_not: [],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -62,14 +62,14 @@ export const TREE_NAVIGATION_SHOW_MORE = (name: string) =>
|
|||
export const TREE_NAVIGATION_COLLAPSE = i18n.translate(
|
||||
'xpack.kubernetesSecurity.treeNavigation.collapse',
|
||||
{
|
||||
defaultMessage: 'Collapse Tree Navigation',
|
||||
defaultMessage: 'Collapse tree navigation',
|
||||
}
|
||||
);
|
||||
|
||||
export const TREE_NAVIGATION_EXPAND = i18n.translate(
|
||||
'xpack.kubernetesSecurity.treeNavigation.expand',
|
||||
{
|
||||
defaultMessage: 'Expand Tree Navigation',
|
||||
defaultMessage: 'Expand tree navigation',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -106,14 +106,14 @@ export const COUNT_WIDGET_PODS = i18n.translate('xpack.kubernetesSecurity.countW
|
|||
export const COUNT_WIDGET_CONTAINER_IMAGES = i18n.translate(
|
||||
'xpack.kubernetesSecurity.countWidget.containerImages',
|
||||
{
|
||||
defaultMessage: 'Container Images',
|
||||
defaultMessage: 'Container images',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONTAINER_NAME_SESSION = i18n.translate(
|
||||
'xpack.kubernetesSecurity.containerNameWidget.containerImage',
|
||||
{
|
||||
defaultMessage: 'Container images',
|
||||
defaultMessage: 'Container image',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -127,6 +127,6 @@ export const CONTAINER_NAME_SESSION_COUNT_COLUMN = i18n.translate(
|
|||
export const CONTAINER_NAME_SESSION_ARIA_LABEL = i18n.translate(
|
||||
'xpack.kubernetesSecurity.containerNameWidget.containerImageAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Container Name Session Widget',
|
||||
defaultMessage: 'Container name session widget',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -21,7 +21,7 @@ import { ROW_TEST_ID } from './container_name_row';
|
|||
|
||||
const TABLE_SORT_BUTTON_ID = 'tableHeaderSortButton';
|
||||
|
||||
const TITLE = 'Container images';
|
||||
const TITLE = 'Container image';
|
||||
const GLOBAL_FILTER: GlobalFilter = {
|
||||
endDate: '2022-06-15T14:15:25.777Z',
|
||||
filterQuery: '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}',
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DEFAULT_QUERY } from '../../../common/constants';
|
||||
import { DEFAULT_FILTER_QUERY } from '../../../common/constants';
|
||||
import { QueryDslQueryContainerBool } from '../../types';
|
||||
|
||||
export const addResourceTypeToFilterQuery = (
|
||||
filterQuery: string | undefined,
|
||||
resourceType: 'node' | 'pod'
|
||||
) => {
|
||||
let validFilterQuery = DEFAULT_QUERY;
|
||||
let validFilterQuery = DEFAULT_FILTER_QUERY;
|
||||
|
||||
try {
|
||||
const parsedFilterQuery: QueryDslQueryContainerBool = JSON.parse(filterQuery || '{}');
|
||||
|
@ -32,7 +32,7 @@ export const addResourceTypeToFilterQuery = (
|
|||
});
|
||||
validFilterQuery = JSON.stringify(parsedFilterQuery);
|
||||
} catch {
|
||||
// no-op since validFilterQuery is initialized to be DEFAULT_QUERY
|
||||
// no-op since validFilterQuery is initialized to be DEFAULT_FILTER_QUERY
|
||||
}
|
||||
|
||||
return validFilterQuery;
|
||||
|
|
|
@ -53,11 +53,6 @@ const renderWithRouter = (
|
|||
},
|
||||
};
|
||||
});
|
||||
const responseActionButtonProps = {
|
||||
tooltip: { content: 'test' },
|
||||
isDisabled: false,
|
||||
canAccessResponseConsole: true,
|
||||
};
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
return mockedContext.render(
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
|
@ -68,10 +63,7 @@ const renderWithRouter = (
|
|||
startDate: '2022-03-08T18:52:15.532Z',
|
||||
endDate: '2022-06-09T17:52:15.532Z',
|
||||
}}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
responseActionClick={jest.fn()}
|
||||
renderSessionsView={jest.fn()}
|
||||
handleTreeNavSelection={jest.fn()}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Switch } from 'react-router-dom';
|
||||
import { Route } from '@kbn/shared-ux-router';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
|
@ -36,11 +36,10 @@ import {
|
|||
COUNT_WIDGET_KEY_NAMESPACE,
|
||||
COUNT_WIDGET_KEY_NODES,
|
||||
COUNT_WIDGET_KEY_CONTAINER_IMAGES,
|
||||
DEFAULT_KUBERNETES_FILTER_QUERY,
|
||||
} from '../../../common/constants';
|
||||
import { PercentWidget } from '../percent_widget';
|
||||
import { CountWidget } from '../count_widget';
|
||||
import { GlobalFilter, KubernetesSecurityDeps } from '../../types';
|
||||
import { KubernetesSecurityDeps } from '../../types';
|
||||
import { AggregateResult } from '../../../common/types/aggregate';
|
||||
import { useLastUpdated } from '../../hooks';
|
||||
import { useStyles } from './styles';
|
||||
|
@ -61,9 +60,6 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
indexPattern,
|
||||
globalFilter,
|
||||
renderSessionsView,
|
||||
responseActionClick,
|
||||
handleTreeNavSelection,
|
||||
responseActionButtonProps,
|
||||
}: KubernetesSecurityDeps) => {
|
||||
const [shouldHideCharts, setShouldHideCharts] = useLocalStorage(
|
||||
LOCAL_STORAGE_HIDE_WIDGETS_KEY,
|
||||
|
@ -71,17 +67,6 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
);
|
||||
const styles = useStyles();
|
||||
const lastUpdated = useLastUpdated(globalFilter);
|
||||
|
||||
const globalFilterForKubernetes: GlobalFilter = useMemo(() => {
|
||||
return {
|
||||
...globalFilter,
|
||||
filterQuery: JSON.stringify({
|
||||
...JSON.parse(globalFilter?.filterQuery ?? ''),
|
||||
...JSON.parse(DEFAULT_KUBERNETES_FILTER_QUERY),
|
||||
}),
|
||||
};
|
||||
}, [globalFilter]);
|
||||
|
||||
const onReduceInteractiveAggs = useCallback(
|
||||
(result: AggregateResult): Record<string, number> =>
|
||||
result.buckets.reduce((groupedByKeyValue, aggregate) => {
|
||||
|
@ -140,7 +125,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<CountWidget
|
||||
title={COUNT_WIDGET_CLUSTERS}
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
widgetKey={COUNT_WIDGET_KEY_CLUSTERS}
|
||||
groupedBy={ORCHESTRATOR_CLUSTER_ID}
|
||||
/>
|
||||
|
@ -149,7 +134,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<CountWidget
|
||||
title={COUNT_WIDGET_NAMESPACE}
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
widgetKey={COUNT_WIDGET_KEY_NAMESPACE}
|
||||
groupedBy={ORCHESTRATOR_NAMESPACE}
|
||||
/>
|
||||
|
@ -158,7 +143,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<CountWidget
|
||||
title={COUNT_WIDGET_NODES}
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
widgetKey={COUNT_WIDGET_KEY_NODES}
|
||||
groupedBy={CLOUD_INSTANCE_NAME}
|
||||
/>
|
||||
|
@ -167,7 +152,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<CountWidget
|
||||
title={COUNT_WIDGET_PODS}
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
widgetKey={COUNT_WIDGET_KEY_NODES}
|
||||
groupedBy={ORCHESTRATOR_RESOURCE_ID}
|
||||
/>
|
||||
|
@ -176,7 +161,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<CountWidget
|
||||
title={COUNT_WIDGET_CONTAINER_IMAGES}
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
widgetKey={COUNT_WIDGET_KEY_CONTAINER_IMAGES}
|
||||
groupedBy={CONTAINER_IMAGE_NAME}
|
||||
/>
|
||||
|
@ -190,7 +175,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<EuiText size="xs" css={styles.percentageChartTitle}>
|
||||
<FormattedMessage
|
||||
id="xpack.kubernetesSecurity.sessionChart.title"
|
||||
defaultMessage="Session Interactivity"
|
||||
defaultMessage="Session interactivity"
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiIconTip
|
||||
|
@ -206,7 +191,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
}
|
||||
widgetKey="sessionsPercentage"
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
dataValueMap={{
|
||||
true: {
|
||||
name: i18n.translate(
|
||||
|
@ -242,14 +227,14 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<EuiText size="xs" css={styles.percentageChartTitle}>
|
||||
<FormattedMessage
|
||||
id="xpack.kubernetesSecurity.entryUserChart.title"
|
||||
defaultMessage="Session Entry Users"
|
||||
defaultMessage="Entry session users"
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.kubernetesSecurity.entryUserChart.tooltip"
|
||||
defaultMessage="The session user is the initial Linux user associated
|
||||
defaultMessage="The entry session user is the initial Linux user associated
|
||||
with the session. This user may be set from authentication of a remote
|
||||
login or automatically for service sessions started by init."
|
||||
/>
|
||||
|
@ -259,7 +244,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
}
|
||||
widgetKey="rootLoginPercentage"
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
dataValueMap={{
|
||||
'0': {
|
||||
name: i18n.translate('xpack.kubernetesSecurity.entryUserChart.root', {
|
||||
|
@ -288,7 +273,7 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
<ContainerNameWidget
|
||||
widgetKey="containerNameSessions"
|
||||
indexPattern={indexPattern}
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
groupedBy={CONTAINER_IMAGE_NAME}
|
||||
countBy={ENTRY_LEADER_ENTITY_ID}
|
||||
/>
|
||||
|
@ -297,12 +282,9 @@ const KubernetesSecurityRoutesComponent = ({
|
|||
</>
|
||||
)}
|
||||
<TreeViewContainer
|
||||
globalFilter={globalFilterForKubernetes}
|
||||
globalFilter={globalFilter}
|
||||
renderSessionsView={renderSessionsView}
|
||||
indexPattern={indexPattern}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
responseActionClick={responseActionClick}
|
||||
handleTreeNavSelection={handleTreeNavSelection}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
|
|
|
@ -113,29 +113,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-9t7nyf-empty-primary"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -249,29 +226,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-9t7nyf-empty-primary"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
@ -442,29 +396,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-9t7nyf-empty-primary"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -578,29 +509,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-9t7nyf-empty-primary"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
@ -792,29 +700,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-9t7nyf-empty-primary"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -949,29 +834,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-9t7nyf-empty-primary"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
@ -1121,30 +983,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-1at5a38-empty-disabled"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1237,30 +1075,6 @@ Object {
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem emotion-euiFlexItem-growZero"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
|
||||
>
|
||||
<button
|
||||
class="euiButtonEmpty css-1at5a38-empty-disabled"
|
||||
data-test-subj="kubernetesSecurity:kubernetesSecurityResponseAction"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Respond
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
|
|
|
@ -24,27 +24,17 @@ describe('Tree view Breadcrumb component', () => {
|
|||
let renderResult: ReturnType<typeof render>;
|
||||
let mockedContext: AppContextTestRender;
|
||||
let onSelect: jest.Mock;
|
||||
let onResponseActionButtonClick: jest.Mock;
|
||||
const responseActionButtonProps = {
|
||||
tooltip: { content: 'test' },
|
||||
isDisabled: false,
|
||||
canAccessResponseConsole: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
onSelect = jest.fn();
|
||||
onResponseActionButtonClick = jest.fn();
|
||||
});
|
||||
|
||||
describe('When Breadcrumb is mounted', () => {
|
||||
it('renders Breadcrumb button content correctly', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<Breadcrumb
|
||||
treeNavResponseActionDisabled={false}
|
||||
treeNavSelection={{ ...MOCK_TREE_SELECTION, node: undefined }}
|
||||
responseActionClick={onResponseActionButtonClick}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
@ -59,13 +49,7 @@ describe('Tree view Breadcrumb component', () => {
|
|||
|
||||
it('should render breadcrumb icons', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<Breadcrumb
|
||||
responseActionClick={onResponseActionButtonClick}
|
||||
treeNavResponseActionDisabled={false}
|
||||
treeNavSelection={MOCK_TREE_SELECTION}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
<Breadcrumb treeNavSelection={MOCK_TREE_SELECTION} onSelect={onSelect} />
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -79,29 +63,18 @@ describe('Tree view Breadcrumb component', () => {
|
|||
expect(renderResult).toMatchSnapshot();
|
||||
});
|
||||
it('returns null when no selected collection', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<Breadcrumb
|
||||
responseActionClick={onResponseActionButtonClick}
|
||||
treeNavResponseActionDisabled={false}
|
||||
treeNavSelection={{}}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
renderResult = mockedContext.render(<Breadcrumb treeNavSelection={{}} onSelect={onSelect} />);
|
||||
expect(renderResult.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should display cluster icon button when no cluster name is provided', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<Breadcrumb
|
||||
treeNavResponseActionDisabled={false}
|
||||
responseActionClick={onResponseActionButtonClick}
|
||||
treeNavSelection={{
|
||||
...MOCK_TREE_SELECTION,
|
||||
clusterName: undefined,
|
||||
node: undefined,
|
||||
}}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
@ -117,10 +90,7 @@ describe('Tree view Breadcrumb component', () => {
|
|||
it('should return null when no cluster in selection', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<Breadcrumb
|
||||
treeNavResponseActionDisabled={false}
|
||||
responseActionClick={onResponseActionButtonClick}
|
||||
treeNavSelection={{ ...MOCK_TREE_SELECTION, clusterId: undefined }}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
@ -137,13 +107,7 @@ describe('Tree view Breadcrumb component', () => {
|
|||
pod: 'selected pod',
|
||||
};
|
||||
renderResult = mockedContext.render(
|
||||
<Breadcrumb
|
||||
responseActionClick={onResponseActionButtonClick}
|
||||
treeNavResponseActionDisabled={false}
|
||||
treeNavSelection={mockPodNavSelection}
|
||||
onSelect={onSelect}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
/>
|
||||
<Breadcrumb treeNavSelection={mockPodNavSelection} onSelect={onSelect} />
|
||||
);
|
||||
expect(renderResult.queryByText(mockPodNavSelection.pod)).toBeVisible();
|
||||
renderResult.getByText(mockPodNavSelection.pod).click();
|
||||
|
@ -153,8 +117,6 @@ describe('Tree view Breadcrumb component', () => {
|
|||
it('should render last breadcrumb content only', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<Breadcrumb
|
||||
treeNavResponseActionDisabled={true}
|
||||
responseActionClick={onResponseActionButtonClick}
|
||||
treeNavSelection={{
|
||||
clusterId: MOCK_TREE_SELECTION.clusterId,
|
||||
clusterName: MOCK_TREE_SELECTION.clusterName,
|
||||
|
@ -162,7 +124,6 @@ describe('Tree view Breadcrumb component', () => {
|
|||
containerImage: MOCK_TREE_SELECTION.containerImage,
|
||||
}}
|
||||
onSelect={onSelect}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -20,28 +20,17 @@ import {
|
|||
KubernetesCollection,
|
||||
TreeViewIconProps,
|
||||
KubernetesTreeViewLevels,
|
||||
ResponseActionButtonProps,
|
||||
} from '../../../types';
|
||||
import { useStyles } from './styles';
|
||||
import { KUBERNETES_COLLECTION_ICONS_PROPS } from '../helpers';
|
||||
import { RESPONSE_ACTION_BUTTON } from './translations';
|
||||
import { showBreadcrumbDisplayText } from './helper';
|
||||
import { BREADCRUMBS_CLUSTER_TREE_VIEW_LEVELS } from '../translations';
|
||||
interface BreadcrumbDeps {
|
||||
treeNavSelection: Partial<KubernetesCollectionMap>;
|
||||
onSelect: (selection: Partial<KubernetesCollectionMap>) => void;
|
||||
responseActionClick: () => void;
|
||||
treeNavResponseActionDisabled: boolean;
|
||||
responseActionButtonProps: ResponseActionButtonProps;
|
||||
}
|
||||
|
||||
export const Breadcrumb = ({
|
||||
treeNavSelection,
|
||||
onSelect,
|
||||
treeNavResponseActionDisabled,
|
||||
responseActionButtonProps,
|
||||
responseActionClick,
|
||||
}: BreadcrumbDeps) => {
|
||||
export const Breadcrumb = ({ treeNavSelection, onSelect }: BreadcrumbDeps) => {
|
||||
const styles = useStyles();
|
||||
const { euiVars } = useEuiTheme();
|
||||
const onBreadCrumbClick = useCallback(
|
||||
|
@ -73,8 +62,6 @@ export const Breadcrumb = ({
|
|||
},
|
||||
[onSelect, treeNavSelection]
|
||||
);
|
||||
const isResponseActionDisabled =
|
||||
(responseActionButtonProps?.isDisabled ?? false) || treeNavResponseActionDisabled;
|
||||
|
||||
const renderBreadcrumbLink = useCallback(
|
||||
(
|
||||
|
@ -161,19 +148,6 @@ export const Breadcrumb = ({
|
|||
true
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{responseActionButtonProps?.canAccessResponseConsole && (
|
||||
<EuiToolTip content={responseActionButtonProps?.tooltip ?? null}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj={`kubernetesSecurity:kubernetesSecurityResponseAction`}
|
||||
isDisabled={isResponseActionDisabled}
|
||||
onClick={responseActionClick}
|
||||
>
|
||||
{RESPONSE_ACTION_BUTTON}
|
||||
</EuiButtonEmpty>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,14 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const RESPONSE_ACTION_BUTTON = i18n.translate(
|
||||
'xpack.kubernetesSecurity.breadcrumb.responseActionButton',
|
||||
{
|
||||
defaultMessage: 'Respond',
|
||||
}
|
||||
);
|
|
@ -38,11 +38,13 @@ const focusPreviousButton = (event: KeyboardEvent) => {
|
|||
};
|
||||
|
||||
const DynamicTreeViewExpander = ({
|
||||
defaultExpanded = false,
|
||||
children,
|
||||
}: {
|
||||
defaultExpanded: boolean;
|
||||
children: (childrenProps: { isExpanded: boolean; onToggleExpand: () => void }) => JSX.Element;
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
||||
|
||||
const onToggleExpand = () => {
|
||||
setIsExpanded((e) => !e);
|
||||
|
@ -66,7 +68,7 @@ export const DynamicTreeView = ({
|
|||
}: DynamicTreeViewProps) => {
|
||||
const styles = useStyles(depth);
|
||||
|
||||
const { indexPattern, setNoResults } = useTreeViewContext();
|
||||
const { indexPattern, setNoResults, setTreeNavSelection } = useTreeViewContext();
|
||||
|
||||
const { data, fetchNextPage, isFetchingNextPage, hasNextPage, isLoading } =
|
||||
useFetchDynamicTreeView(query, tree[depth].key, indexPattern, expanded);
|
||||
|
@ -91,10 +93,15 @@ export const DynamicTreeView = ({
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (depth === 0 && data && data.pages?.[0].buckets.length === 0) {
|
||||
setNoResults(true);
|
||||
if (depth === 0 && data) {
|
||||
const noData = data.pages?.[0].buckets.length === 0;
|
||||
setNoResults(noData);
|
||||
|
||||
if (noData) {
|
||||
setTreeNavSelection({});
|
||||
}
|
||||
}
|
||||
}, [data, depth, setNoResults]);
|
||||
}, [data, depth, setNoResults, setTreeNavSelection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded) {
|
||||
|
@ -165,8 +172,10 @@ export const DynamicTreeView = ({
|
|||
},
|
||||
};
|
||||
|
||||
const defaultExpanded = selected.indexOf('' + aggData.key) > 0;
|
||||
|
||||
return (
|
||||
<DynamicTreeViewExpander key={aggData.key}>
|
||||
<DynamicTreeViewExpander key={aggData.key} defaultExpanded={defaultExpanded}>
|
||||
{({ isExpanded, onToggleExpand }) => (
|
||||
<DynamicTreeViewItem
|
||||
aggData={aggData}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import {
|
||||
CLOUD_INSTANCE_NAME,
|
||||
CONTAINER_IMAGE_NAME,
|
||||
DEFAULT_QUERY,
|
||||
DEFAULT_FILTER_QUERY,
|
||||
ORCHESTRATOR_CLUSTER_ID,
|
||||
ORCHESTRATOR_CLUSTER_NAME,
|
||||
ORCHESTRATOR_NAMESPACE,
|
||||
|
@ -43,7 +43,7 @@ export const addTreeNavSelectionToFilterQuery = (
|
|||
filterQuery: string | undefined,
|
||||
treeNavSelection: Partial<KubernetesCollectionMap>
|
||||
) => {
|
||||
let validFilterQuery = DEFAULT_QUERY;
|
||||
let validFilterQuery = DEFAULT_FILTER_QUERY;
|
||||
|
||||
try {
|
||||
const parsedFilterQuery: QueryDslQueryContainerBool = JSON.parse(filterQuery || '{}');
|
||||
|
@ -73,7 +73,7 @@ export const addTreeNavSelectionToFilterQuery = (
|
|||
|
||||
validFilterQuery = JSON.stringify(parsedFilterQuery);
|
||||
} catch {
|
||||
// no-op since validFilterQuery is initialized to be DEFAULT_QUERY
|
||||
// no-op since validFilterQuery is initialized to be DEFAULT_FILTER_QUERY
|
||||
}
|
||||
|
||||
return validFilterQuery;
|
||||
|
|
|
@ -4,17 +4,13 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import type { KubernetesCollectionMap, QueryDslQueryContainerBool } from '../../types';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import type { KubernetesCollectionMap } from '../../types';
|
||||
import { LOCAL_STORAGE_TREE_NAV_KEY } from '../../../common/constants';
|
||||
import { addTimerangeAndDefaultFilterToQuery } from '../../utils/add_timerange_and_default_filter_to_query';
|
||||
import { addTreeNavSelectionToFilterQuery } from './helpers';
|
||||
import { IndexPattern, GlobalFilter } from '../../types';
|
||||
import { QUERY_KEY_AGENT_ID, AGENT_ID_ROUTE } from '../../../common/constants';
|
||||
import { AgentIdResult } from './tree_nav/types';
|
||||
|
||||
export type UseTreeViewProps = {
|
||||
globalFilter: GlobalFilter;
|
||||
|
@ -23,9 +19,9 @@ export type UseTreeViewProps = {
|
|||
|
||||
export const useTreeView = ({ globalFilter, indexPattern }: UseTreeViewProps) => {
|
||||
const [noResults, setNoResults] = useState(false);
|
||||
const [treeNavSelection, setTreeNavSelection] = useState<Partial<KubernetesCollectionMap>>({});
|
||||
const [hasSelection, setHasSelection] = useState(false);
|
||||
const [treeNavResponseActionDisabled, setTreeNavResponseActionDisabled] = useState(false);
|
||||
const [treeNavSelection = {}, setTreeNavSelection] = useLocalStorage<
|
||||
Partial<KubernetesCollectionMap>
|
||||
>(LOCAL_STORAGE_TREE_NAV_KEY, {});
|
||||
const filterQueryWithTimeRange = useMemo(() => {
|
||||
return JSON.parse(
|
||||
addTimerangeAndDefaultFilterToQuery(
|
||||
|
@ -36,61 +32,26 @@ export const useTreeView = ({ globalFilter, indexPattern }: UseTreeViewProps) =>
|
|||
);
|
||||
}, [globalFilter.filterQuery, globalFilter.startDate, globalFilter.endDate]);
|
||||
|
||||
const onTreeNavSelect = useCallback((selection: Partial<KubernetesCollectionMap>) => {
|
||||
const isResponseActionEnabled =
|
||||
!!selection?.node || !!selection?.pod || !!selection?.containerImage;
|
||||
setTreeNavResponseActionDisabled(isResponseActionEnabled ? false : true);
|
||||
setHasSelection(false);
|
||||
setTreeNavSelection(selection);
|
||||
}, []);
|
||||
const onTreeNavSelect = useCallback(
|
||||
(selection: Partial<KubernetesCollectionMap>) => {
|
||||
setTreeNavSelection(selection);
|
||||
},
|
||||
[setTreeNavSelection]
|
||||
);
|
||||
|
||||
const sessionViewFilter = useMemo(
|
||||
() => addTreeNavSelectionToFilterQuery(globalFilter.filterQuery, treeNavSelection),
|
||||
[globalFilter.filterQuery, treeNavSelection]
|
||||
);
|
||||
|
||||
// Resetting defaults whenever filter changes
|
||||
useEffect(() => {
|
||||
setNoResults(false);
|
||||
setTreeNavSelection({});
|
||||
}, [filterQueryWithTimeRange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!treeNavSelection.clusterId) {
|
||||
setHasSelection(true);
|
||||
setTreeNavSelection(treeNavSelection);
|
||||
}
|
||||
}, [treeNavSelection]);
|
||||
|
||||
return {
|
||||
noResults,
|
||||
setNoResults,
|
||||
filterQueryWithTimeRange,
|
||||
indexPattern: indexPattern?.title || '',
|
||||
onTreeNavSelect,
|
||||
hasSelection,
|
||||
treeNavSelection,
|
||||
treeNavResponseActionDisabled,
|
||||
setTreeNavSelection,
|
||||
sessionViewFilter,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchAgentIdForResponder = (
|
||||
filterQuery: QueryDslQueryContainerBool,
|
||||
index?: string
|
||||
) => {
|
||||
const { http } = useKibana<CoreStart>().services;
|
||||
const cachingKeys = [QUERY_KEY_AGENT_ID, filterQuery, index];
|
||||
const query = useQuery(cachingKeys, async (): Promise<AgentIdResult> => {
|
||||
const res = await http.get<AgentIdResult>(AGENT_ID_ROUTE, {
|
||||
query: {
|
||||
query: JSON.stringify(filterQuery),
|
||||
index,
|
||||
},
|
||||
});
|
||||
|
||||
return res;
|
||||
});
|
||||
|
||||
return query;
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { TreeViewContainer } from '.';
|
||||
import { DEFAULT_FILTER_QUERY } from '../../../common/constants';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import * as context from './contexts';
|
||||
|
||||
|
@ -18,10 +19,11 @@ describe('TreeNav component', () => {
|
|||
|
||||
const defaultProps = {
|
||||
globalFilter: {
|
||||
filterQuery: DEFAULT_FILTER_QUERY,
|
||||
startDate: Date.now().toString(),
|
||||
endDate: (Date.now() + 1).toString(),
|
||||
},
|
||||
renderSessionsView: <div>Session View</div>,
|
||||
renderSessionsView: () => <div>Session View</div>,
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -31,13 +33,14 @@ describe('TreeNav component', () => {
|
|||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('shows empty message when there is no results', async () => {
|
||||
it('shows empty message when there is no results', () => {
|
||||
spy.mockImplementation(() => ({
|
||||
...jest.requireActual('./contexts').useTreeViewContext,
|
||||
noResults: true,
|
||||
treeNavSelection: {},
|
||||
}));
|
||||
|
||||
renderResult = mockedContext.render(<TreeViewContainer {...defaultProps} />);
|
||||
expect(await renderResult.getByText(/no results/i)).toBeInTheDocument();
|
||||
expect(renderResult.getByText(/no results/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,18 +8,13 @@
|
|||
import React from 'react';
|
||||
import { EuiSplitPanel, EuiText } from '@elastic/eui';
|
||||
import { useStyles } from './styles';
|
||||
import { IndexPattern, GlobalFilter, ResponseActionButtonProps } from '../../types';
|
||||
import { IndexPattern, GlobalFilter } from '../../types';
|
||||
import { TreeNav } from './tree_nav';
|
||||
import { Breadcrumb } from './breadcrumb';
|
||||
import { TreeViewContextProvider, useTreeViewContext } from './contexts';
|
||||
import { EmptyState } from './empty_state';
|
||||
import { addTreeNavSelectionToFilterQuery } from './helpers';
|
||||
import { useFetchAgentIdForResponder } from './hooks';
|
||||
|
||||
export interface TreeViewContainerComponentDeps {
|
||||
responseActionButtonProps: ResponseActionButtonProps;
|
||||
responseActionClick: () => void;
|
||||
handleTreeNavSelection: (agentId: string) => void;
|
||||
renderSessionsView: (sessionsFilterQuery: string | undefined) => JSX.Element;
|
||||
}
|
||||
export interface TreeViewContainerDeps extends TreeViewContainerComponentDeps {
|
||||
|
@ -31,69 +26,31 @@ export const TreeViewContainer = ({
|
|||
globalFilter,
|
||||
renderSessionsView,
|
||||
indexPattern,
|
||||
responseActionClick,
|
||||
handleTreeNavSelection,
|
||||
responseActionButtonProps,
|
||||
}: TreeViewContainerDeps) => {
|
||||
return (
|
||||
<TreeViewContextProvider indexPattern={indexPattern} globalFilter={globalFilter}>
|
||||
<TreeViewContainerComponent
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
renderSessionsView={renderSessionsView}
|
||||
responseActionClick={responseActionClick}
|
||||
handleTreeNavSelection={handleTreeNavSelection}
|
||||
/>
|
||||
<TreeViewContainerComponent renderSessionsView={renderSessionsView} />
|
||||
</TreeViewContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const TreeViewContainerComponent = ({
|
||||
renderSessionsView,
|
||||
responseActionButtonProps,
|
||||
responseActionClick,
|
||||
handleTreeNavSelection,
|
||||
}: TreeViewContainerComponentDeps) => {
|
||||
const TreeViewContainerComponent = ({ renderSessionsView }: TreeViewContainerComponentDeps) => {
|
||||
const styles = useStyles();
|
||||
|
||||
const {
|
||||
hasSelection,
|
||||
treeNavSelection,
|
||||
sessionViewFilter,
|
||||
indexPattern,
|
||||
onTreeNavSelect,
|
||||
noResults,
|
||||
treeNavResponseActionDisabled,
|
||||
} = useTreeViewContext();
|
||||
const query = JSON.parse(addTreeNavSelectionToFilterQuery(sessionViewFilter, treeNavSelection));
|
||||
const { data } = useFetchAgentIdForResponder(query, indexPattern);
|
||||
|
||||
if (data?.agentId) {
|
||||
handleTreeNavSelection(data.agentId);
|
||||
}
|
||||
const { treeNavSelection, sessionViewFilter, onTreeNavSelect, noResults } = useTreeViewContext();
|
||||
|
||||
return (
|
||||
<EuiSplitPanel.Outer direction="row" hasBorder borderRadius="m" css={styles.outerPanel}>
|
||||
{noResults ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<>
|
||||
<EuiSplitPanel.Inner color="subdued" grow={false} css={styles.navPanel}>
|
||||
<EuiText>
|
||||
<TreeNav />
|
||||
</EuiText>
|
||||
</EuiSplitPanel.Inner>
|
||||
<EuiSplitPanel.Inner css={styles.sessionsPanel}>
|
||||
<Breadcrumb
|
||||
treeNavSelection={treeNavSelection}
|
||||
treeNavResponseActionDisabled={treeNavResponseActionDisabled}
|
||||
responseActionClick={responseActionClick}
|
||||
responseActionButtonProps={responseActionButtonProps}
|
||||
onSelect={onTreeNavSelect}
|
||||
/>
|
||||
{hasSelection && renderSessionsView(sessionViewFilter)}
|
||||
</EuiSplitPanel.Inner>
|
||||
</>
|
||||
)}
|
||||
{noResults && <EmptyState />}
|
||||
<EuiSplitPanel.Inner hidden={noResults} color="subdued" grow={false} css={styles.navPanel}>
|
||||
<EuiText>
|
||||
<TreeNav />
|
||||
</EuiText>
|
||||
</EuiSplitPanel.Inner>
|
||||
<EuiSplitPanel.Inner hidden={noResults} css={styles.sessionsPanel}>
|
||||
<Breadcrumb treeNavSelection={treeNavSelection} onSelect={onTreeNavSelect} />
|
||||
{renderSessionsView(sessionViewFilter)}
|
||||
</EuiSplitPanel.Inner>
|
||||
</EuiSplitPanel.Outer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
EuiButtonGroup,
|
||||
useGeneratedHtmlId,
|
||||
|
@ -31,7 +31,8 @@ import { useTreeViewContext } from '../contexts';
|
|||
export const TreeNav = () => {
|
||||
const styles = useStyles();
|
||||
const [tree, setTree] = useState(TREE_VIEW.logical);
|
||||
const [selected, setSelected] = useState('');
|
||||
const { filterQueryWithTimeRange, onTreeNavSelect, treeNavSelection, setTreeNavSelection } =
|
||||
useTreeViewContext();
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const treeNavTypePrefix = useGeneratedHtmlId({
|
||||
prefix: 'treeNavType',
|
||||
|
@ -39,7 +40,11 @@ export const TreeNav = () => {
|
|||
const logicalTreeViewPrefix = `${treeNavTypePrefix}${LOGICAL}`;
|
||||
const [toggleIdSelected, setToggleIdSelected] = useState(logicalTreeViewPrefix);
|
||||
|
||||
const { filterQueryWithTimeRange, onTreeNavSelect } = useTreeViewContext();
|
||||
const selected = useMemo(() => {
|
||||
return Object.entries(treeNavSelection)
|
||||
.map(([k, v]) => `${k}.${v}`)
|
||||
.join();
|
||||
}, [treeNavSelection]);
|
||||
|
||||
const handleToggleCollapse = () => {
|
||||
setIsCollapsed(!isCollapsed);
|
||||
|
@ -65,10 +70,14 @@ export const TreeNav = () => {
|
|||
return options.find((opt) => opt.id === toggleIdSelected)!.label;
|
||||
}, [options, toggleIdSelected]);
|
||||
|
||||
const handleTreeViewSwitch = (id: string, value: TreeViewKind) => {
|
||||
setToggleIdSelected(id);
|
||||
setTree(TREE_VIEW[value]);
|
||||
};
|
||||
const handleTreeViewSwitch = useCallback(
|
||||
(id: string, value: TreeViewKind) => {
|
||||
setToggleIdSelected(id);
|
||||
setTree(TREE_VIEW[value]);
|
||||
setTreeNavSelection({});
|
||||
},
|
||||
[setTreeNavSelection]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -123,11 +132,6 @@ export const TreeNav = () => {
|
|||
[type]: key,
|
||||
...(clusterName && { clusterName }),
|
||||
};
|
||||
setSelected(
|
||||
Object.entries(newSelectionDepth)
|
||||
.map(([k, v]) => `${k}.${v}`)
|
||||
.join()
|
||||
);
|
||||
onTreeNavSelect(newSelectionDepth);
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -12,7 +12,3 @@ export interface TreeViewOptionsGroup {
|
|||
label: string;
|
||||
value: TreeViewKind;
|
||||
}
|
||||
|
||||
export interface AgentIdResult {
|
||||
agentId: string | null;
|
||||
}
|
||||
|
|
|
@ -33,19 +33,11 @@ export interface GlobalFilter {
|
|||
endDate: string;
|
||||
}
|
||||
|
||||
export interface ResponseActionButtonProps {
|
||||
tooltip: React.ReactNode;
|
||||
canAccessResponseConsole: boolean;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
export interface KubernetesSecurityDeps {
|
||||
filter: React.ReactNode;
|
||||
renderSessionsView: (sessionsFilterQuery: string | undefined) => JSX.Element;
|
||||
indexPattern?: IndexPattern;
|
||||
globalFilter: GlobalFilter;
|
||||
responseActionClick: () => void;
|
||||
handleTreeNavSelection: (agentId: string) => void;
|
||||
responseActionButtonProps: ResponseActionButtonProps;
|
||||
}
|
||||
|
||||
export interface KubernetesSecurityStart {
|
||||
|
|
|
@ -14,7 +14,7 @@ const TEST_INVALID_QUERY = '{"bool":{"must":[';
|
|||
const TEST_EMPTY_STRING = '';
|
||||
const TEST_DATE = '2022-06-09T22:36:46.628Z';
|
||||
const VALID_RESULT =
|
||||
'{"bool":{"must":[],"filter":[{"bool":{"should":[{"exists":{"field":"process.entry_leader.entity_id"}}],"minimum_should_match":1}},{"bool":{"should":[{"match":{"process.entry_leader.same_as_process":true}}],"minimum_should_match":1}},{"range":{"@timestamp":{"gte":"2022-06-09T22:36:46.628Z","lte":"2022-06-09T22:36:46.628Z"}}}],"should":[],"must_not":[]}}';
|
||||
'{"bool":{"must":[],"filter":[{"bool":{"should":[{"exists":{"field":"orchestrator.cluster.id"}}],"minimum_should_match":1}},{"bool":{"should":[{"match":{"process.entry_leader.same_as_process":true}}],"minimum_should_match":1}},{"range":{"@timestamp":{"gte":"2022-06-09T22:36:46.628Z","lte":"2022-06-09T22:36:46.628Z"}}}],"should":[],"must_not":[]}}';
|
||||
|
||||
describe('addTimerangeAndDefaultFilterToQuery(query, startDate, endDate)', () => {
|
||||
it('works for valid query, startDate, and endDate', () => {
|
||||
|
|
|
@ -67,6 +67,11 @@ export const doSearch = async (
|
|||
field: countBy,
|
||||
},
|
||||
},
|
||||
count_alerts: {
|
||||
cardinality: {
|
||||
field: countBy,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { getEsQueryConfig } from '@kbn/data-plugin/common';
|
||||
import type { ResponseActionButtonProps } from '@kbn/kubernetes-security-plugin/public/types';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
import { InputsModelId } from '../../common/store/inputs/constants';
|
||||
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
|
||||
|
@ -26,18 +25,12 @@ import { convertToBuildEsQuery } from '../../common/lib/kuery';
|
|||
import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query';
|
||||
import { SessionsView } from '../../common/components/sessions_viewer';
|
||||
import { kubernetesSessionsHeaders } from './constants';
|
||||
import { useResponderActionData } from '../../detections/components/endpoint_responder/use_responder_action_data';
|
||||
import { useUserPrivileges } from '../../common/components/user_privileges';
|
||||
|
||||
export const KubernetesContainer = React.memo(() => {
|
||||
const { kubernetesSecurity, uiSettings } = useKibana().services;
|
||||
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
const {
|
||||
indexPattern,
|
||||
// runtimeMappings,
|
||||
// loading: isLoadingIndexPattern,
|
||||
} = useSourcererDataView();
|
||||
const { indexPattern } = useSourcererDataView();
|
||||
const { from, to } = useGlobalTime();
|
||||
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
|
@ -47,9 +40,7 @@ export const KubernetesContainer = React.memo(() => {
|
|||
const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []);
|
||||
const query = useDeepEqualSelector(getGlobalQuerySelector);
|
||||
const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector);
|
||||
const canAccessResponseConsole = useUserPrivileges().endpointPrivileges.canAccessResponseConsole;
|
||||
|
||||
const [agentIdForResponder, setAgentIdForResponder] = useState<string>('');
|
||||
const [filterQuery, kqlError] = useMemo(
|
||||
() =>
|
||||
convertToBuildEsQuery({
|
||||
|
@ -85,24 +76,6 @@ export const KubernetesContainer = React.memo(() => {
|
|||
[from, to]
|
||||
);
|
||||
|
||||
const { handleResponseActionsClick, isDisabled, tooltip } = useResponderActionData({
|
||||
endpointId: agentIdForResponder,
|
||||
});
|
||||
|
||||
const handleTreeNavSelection = useCallback((agentId: string) => {
|
||||
setAgentIdForResponder(agentId);
|
||||
}, []);
|
||||
|
||||
const responseActionClick = useCallback(() => {
|
||||
handleResponseActionsClick();
|
||||
}, [handleResponseActionsClick]);
|
||||
|
||||
const responseActionButtonProps: ResponseActionButtonProps = {
|
||||
isDisabled,
|
||||
canAccessResponseConsole,
|
||||
tooltip,
|
||||
};
|
||||
|
||||
return (
|
||||
<SecuritySolutionPageWrapper noPadding>
|
||||
{kubernetesSecurity.getKubernetesPage({
|
||||
|
@ -118,9 +91,6 @@ export const KubernetesContainer = React.memo(() => {
|
|||
endDate: to,
|
||||
},
|
||||
renderSessionsView,
|
||||
responseActionClick,
|
||||
handleTreeNavSelection,
|
||||
responseActionButtonProps,
|
||||
})}
|
||||
<SpyRoute pageName={SecurityPageName.kubernetes} />
|
||||
</SecuritySolutionPageWrapper>
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { BETA } from '@kbn/kubernetes-security-plugin/common/translations';
|
||||
import { SecurityPageName } from '../../app/types';
|
||||
import type { NavLinkItem } from '../../common/components/navigation/types';
|
||||
import { TestProviders } from '../../common/mock';
|
||||
import { LandingLinksImages, LandingImageCards } from './landing_links_images';
|
||||
import * as telemetry from '../../common/lib/telemetry';
|
||||
|
||||
const BETA = 'Beta';
|
||||
const DEFAULT_NAV_ITEM: NavLinkItem = {
|
||||
id: SecurityPageName.overview,
|
||||
title: 'TEST LABEL',
|
||||
|
|
|
@ -20257,8 +20257,6 @@
|
|||
"xpack.ingestPipelines.testPipelineFlyout.successNotificationText": "Pipeline exécuté",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.title": "Pipeline de test",
|
||||
"xpack.kubernetesSecurity.treeNavigation.loadMore": "Afficher plus {name}",
|
||||
"xpack.kubernetesSecurity.beta": "Bêta",
|
||||
"xpack.kubernetesSecurity.breadcrumb.responseActionButton": "Répondre",
|
||||
"xpack.kubernetesSecurity.chartsToggle.hide": "Masquer les graphiques",
|
||||
"xpack.kubernetesSecurity.chartsToggle.show": "Afficher les graphiques",
|
||||
"xpack.kubernetesSecurity.containerNameWidget.containerImage": "Images de conteneurs",
|
||||
|
|
|
@ -20256,8 +20256,6 @@
|
|||
"xpack.ingestPipelines.testPipelineFlyout.successNotificationText": "パイプラインが実行されました",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.title": "パイプラインをテスト",
|
||||
"xpack.kubernetesSecurity.treeNavigation.loadMore": "{name}詳細表示",
|
||||
"xpack.kubernetesSecurity.beta": "ベータ",
|
||||
"xpack.kubernetesSecurity.breadcrumb.responseActionButton": "対応",
|
||||
"xpack.kubernetesSecurity.chartsToggle.hide": "グラフを非表示",
|
||||
"xpack.kubernetesSecurity.chartsToggle.show": "チャートを表示",
|
||||
"xpack.kubernetesSecurity.containerNameWidget.containerImage": "コンテナーイメージ",
|
||||
|
|
|
@ -20256,8 +20256,6 @@
|
|||
"xpack.ingestPipelines.testPipelineFlyout.successNotificationText": "管道已执行",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.title": "测试管道",
|
||||
"xpack.kubernetesSecurity.treeNavigation.loadMore": "显示更多 {name}",
|
||||
"xpack.kubernetesSecurity.beta": "公测版",
|
||||
"xpack.kubernetesSecurity.breadcrumb.responseActionButton": "响应",
|
||||
"xpack.kubernetesSecurity.chartsToggle.hide": "隐藏图表",
|
||||
"xpack.kubernetesSecurity.chartsToggle.show": "显示图表",
|
||||
"xpack.kubernetesSecurity.containerNameWidget.containerImage": "容器映像",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue