mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Endpoint] Bug fixes to the Endpoint List (#186223)
## Summary Fixes a display bug on the Endpoint List where the Policy name column value was wrapping and causing the row to be miss-aligned. Changes include: - Moved components pieces that display the Policy Revision and the "Out-of-date" message to the `<EndpointPolicyLink>` component - this component now handles on displaying all of this information in one place via input Props - The component also dues Authz checks and ensures that if the user does not have Authz to read the Endpoint Policy Management section, the component will display the policy name as plain text (no link) - It will truncate the Policy name if not enough width is available to display its full value - Replaced the Policy List column component for Policy Name with the use of `<EndpointPolicyLink>` - Replaced the Policy Details flyout component to also use `<EndpointPolicyLink>` to display the policy name > [!NOTE] > Its still possible for the Policy Name column on the Endpoint list to display across two lines - when the Policy that the Endpoint host last reported is not longer available in Kibana. In this case/flow, the second line will display a message indicating that. See screen captures below.
This commit is contained in:
parent
b632f0011d
commit
108e1fadea
14 changed files with 397 additions and 326 deletions
|
@ -23,6 +23,9 @@ import type {
|
|||
} from '@testing-library/react-hooks/src/types/react';
|
||||
import type { UseBaseQueryResult } from '@tanstack/react-query';
|
||||
import ReactDOM from 'react-dom';
|
||||
import type { DeepReadonly } from 'utility-types';
|
||||
import type { UserPrivilegesState } from '../../components/user_privileges/user_privileges_context';
|
||||
import { getUserPrivilegesMockDefaultValue } from '../../components/user_privileges/__mocks__';
|
||||
import type { AppLinkItems } from '../../links/types';
|
||||
import { ExperimentalFeaturesService } from '../../experimental_features_service';
|
||||
import { applyIntersectionObserverMock } from '../intersection_observer_mock';
|
||||
|
@ -41,6 +44,7 @@ import { KibanaServices } from '../../lib/kibana';
|
|||
import { appLinks } from '../../../app_links';
|
||||
import { fleetGetPackageHttpMock } from '../../../management/mocks';
|
||||
import { allowedExperimentalValues } from '../../../../common/experimental_features';
|
||||
import type { EndpointPrivileges } from '../../../../common/endpoint/types';
|
||||
|
||||
const REAL_REACT_DOM_CREATE_PORTAL = ReactDOM.createPortal;
|
||||
|
||||
|
@ -116,6 +120,11 @@ export type ReactQueryHookRenderer<
|
|||
options?: RenderHookOptions<TProps>
|
||||
) => Promise<TResult>;
|
||||
|
||||
export interface UserPrivilegesMockSetter {
|
||||
set: (privileges: Partial<EndpointPrivileges>) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocked app root context renderer
|
||||
*/
|
||||
|
@ -155,6 +164,42 @@ export interface AppContextTestRender {
|
|||
*/
|
||||
setExperimentalFlag: (flags: Partial<ExperimentalFeatures>) => void;
|
||||
|
||||
/**
|
||||
* A helper method that will return an interface to more easily manipulate Endpoint related user authz.
|
||||
* Works in conjunction with `jest.mock()` at the test level.
|
||||
* @param useUserPrivilegesHookMock
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // in your test
|
||||
* import { useUserPrivileges as _useUserPrivileges } from 'path/to/user_privileges'
|
||||
*
|
||||
* jest.mock('path/to/user_privileges');
|
||||
*
|
||||
* const useUserPrivilegesMock = _useUserPrivileges as jest.Mock;
|
||||
*
|
||||
* // If you test - or more likely, in the `beforeEach` and `afterEach`
|
||||
* let authMockSetter: UserPrivilegesMockSetter;
|
||||
*
|
||||
* beforeEach(() => {
|
||||
* const appTestSetup = createAppRootMockRenderer();
|
||||
*
|
||||
* authMockSetter = appTestSetup.getUserPrivilegesMockSetter(useUserPrivilegesMock);
|
||||
* })
|
||||
*
|
||||
* afterEach(() => {
|
||||
* authMockSetter.reset();
|
||||
* }
|
||||
*
|
||||
* // Manipulate the authz in your test
|
||||
* it('does something', () => {
|
||||
* authMockSetter({ canReadPolicyManagement: false });
|
||||
* });
|
||||
*/
|
||||
getUserPrivilegesMockSetter: (
|
||||
useUserPrivilegesHookMock: jest.MockedFn<() => DeepReadonly<UserPrivilegesState>>
|
||||
) => UserPrivilegesMockSetter;
|
||||
|
||||
/**
|
||||
* The React Query client (setup to support jest testing)
|
||||
*/
|
||||
|
@ -305,6 +350,23 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
});
|
||||
};
|
||||
|
||||
const getUserPrivilegesMockSetter: AppContextTestRender['getUserPrivilegesMockSetter'] = (
|
||||
useUserPrivilegesHookMock
|
||||
) => {
|
||||
return {
|
||||
set: (authOverrides) => {
|
||||
const newAuthz = getUserPrivilegesMockDefaultValue();
|
||||
|
||||
Object.assign(newAuthz.endpointPrivileges, authOverrides);
|
||||
useUserPrivilegesHookMock.mockReturnValue(newAuthz);
|
||||
},
|
||||
reset: () => {
|
||||
useUserPrivilegesHookMock.mockReset();
|
||||
useUserPrivilegesHookMock.mockReturnValue(getUserPrivilegesMockDefaultValue());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Initialize the singleton `KibanaServices` with global services created for this test instance.
|
||||
// The module (`../../lib/kibana`) could have been mocked at the test level via `jest.mock()`,
|
||||
// and if so, then we set the return value of `KibanaServices.get` instead of calling `KibanaServices.init()`
|
||||
|
@ -336,6 +398,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
renderHook,
|
||||
renderReactQueryHook,
|
||||
setExperimentalFlag,
|
||||
getUserPrivilegesMockSetter,
|
||||
queryClient,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,49 +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 type { AppContextTestRender } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import type { EndpointAppliedPolicyStatusProps } from './endpoint_applied_policy_status';
|
||||
import { EndpointAppliedPolicyStatus } from './endpoint_applied_policy_status';
|
||||
import React from 'react';
|
||||
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
|
||||
import { POLICY_STATUS_TO_TEXT } from '../../pages/endpoint_hosts/view/host_constants';
|
||||
|
||||
describe('when using EndpointPolicyStatus component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let renderProps: EndpointAppliedPolicyStatusProps;
|
||||
|
||||
beforeEach(() => {
|
||||
const appTestContext = createAppRootMockRenderer();
|
||||
|
||||
renderProps = {
|
||||
policyApplied: new EndpointDocGenerator('seed').generateHostMetadata().Endpoint.policy
|
||||
.applied,
|
||||
};
|
||||
|
||||
render = () => {
|
||||
renderResult = appTestContext.render(<EndpointAppliedPolicyStatus {...renderProps} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should display status from metadata `policy.applied` value', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('policyStatus').textContent).toEqual(
|
||||
POLICY_STATUS_TO_TEXT[renderProps.policyApplied.status]
|
||||
);
|
||||
});
|
||||
|
||||
it('should display status passed as `children`', () => {
|
||||
renderProps.children = 'status goes here';
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('policyStatus').textContent).toEqual('status goes here');
|
||||
});
|
||||
});
|
|
@ -1,86 +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 type { PropsWithChildren } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { EuiHealth, EuiToolTip, EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
POLICY_STATUS_TO_HEALTH_COLOR,
|
||||
POLICY_STATUS_TO_TEXT,
|
||||
} from '../../pages/endpoint_hosts/view/host_constants';
|
||||
import type { HostMetadata } from '../../../../common/endpoint/types';
|
||||
|
||||
/**
|
||||
* Displays the status of an applied policy on the Endpoint (using the information provided
|
||||
* by the endpoint in the Metadata document `Endpoint.policy.applied`.
|
||||
* By default, the policy status is displayed as plain text, however, that can be overridden
|
||||
* by defining the `children` prop or passing a child component to this one.
|
||||
*/
|
||||
export type EndpointAppliedPolicyStatusProps = PropsWithChildren<{
|
||||
policyApplied: HostMetadata['Endpoint']['policy']['applied'];
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Display the status of the Policy applied on an endpoint
|
||||
*/
|
||||
export const EndpointAppliedPolicyStatus = memo<EndpointAppliedPolicyStatusProps>(
|
||||
({ policyApplied, children }) => {
|
||||
return (
|
||||
<EuiToolTip
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel"
|
||||
defaultMessage="Policy applied"
|
||||
/>
|
||||
}
|
||||
anchorClassName="eui-textTruncate"
|
||||
content={
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
data-test-subj="endpointAppliedPolicyTooltipInfo"
|
||||
>
|
||||
<EuiFlexItem className="eui-textTruncate" grow>
|
||||
<EuiText size="s" className="eui-textTruncate">
|
||||
{policyApplied.name}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{policyApplied.endpoint_policy_version && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
style={{ whiteSpace: 'nowrap', paddingLeft: '6px' }}
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="policyRevision"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpointPolicyStatus.revisionNumber"
|
||||
defaultMessage="rev. {revNumber}"
|
||||
values={{ revNumber: policyApplied.endpoint_policy_version }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<EuiHealth
|
||||
color={POLICY_STATUS_TO_HEALTH_COLOR[policyApplied.status]}
|
||||
className="eui-textTruncate eui-fullWidth"
|
||||
data-test-subj="policyStatus"
|
||||
>
|
||||
{children !== undefined ? children : POLICY_STATUS_TO_TEXT[policyApplied.status]}
|
||||
</EuiHealth>
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
);
|
||||
EndpointAppliedPolicyStatus.displayName = 'EndpointAppliedPolicyStatus';
|
|
@ -1,9 +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.
|
||||
*/
|
||||
|
||||
export { EndpointAppliedPolicyStatus } from './endpoint_applied_policy_status';
|
||||
export type { EndpointAppliedPolicyStatusProps } from './endpoint_applied_policy_status';
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 type { AppContextTestRender, UserPrivilegesMockSetter } from '../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../common/mock/endpoint';
|
||||
import React from 'react';
|
||||
import type { EndpointPolicyLinkProps } from './endpoint_policy_link';
|
||||
import { EndpointPolicyLink, POLICY_NOT_FOUND_MESSAGE } from './endpoint_policy_link';
|
||||
import { useUserPrivileges as _useUserPrivileges } from '../../common/components/user_privileges';
|
||||
|
||||
jest.mock('../../common/components/user_privileges');
|
||||
|
||||
const useUserPrivilegesMock = _useUserPrivileges as jest.Mock;
|
||||
|
||||
describe('EndpointPolicyLink component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let props: EndpointPolicyLinkProps;
|
||||
let authzSettter: UserPrivilegesMockSetter;
|
||||
|
||||
beforeEach(() => {
|
||||
const appTestContext = createAppRootMockRenderer();
|
||||
|
||||
props = {
|
||||
policyId: 'abc-123',
|
||||
'data-test-subj': 'test',
|
||||
children: 'policy name here',
|
||||
};
|
||||
|
||||
authzSettter = appTestContext.getUserPrivilegesMockSetter(useUserPrivilegesMock);
|
||||
|
||||
render = () => {
|
||||
renderResult = appTestContext.render(<EndpointPolicyLink {...props} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
authzSettter.reset();
|
||||
});
|
||||
|
||||
it('should display policy as a link to policy details page', () => {
|
||||
const { getByTestId, queryByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-displayContent')).toHaveTextContent(props.children as string);
|
||||
expect(getByTestId('test-link')).toBeTruthy();
|
||||
expect(queryByTestId('test-revision')).toBeNull();
|
||||
expect(queryByTestId('test-outdatedMsg')).toBeNull();
|
||||
expect(queryByTestId('test-policyNotFoundMsg')).toBeNull();
|
||||
});
|
||||
|
||||
it('should display regular text (no link) if user has no authz to read policy details', () => {
|
||||
authzSettter.set({ canReadPolicyManagement: false });
|
||||
const { getByTestId, queryByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-displayContent')).toHaveTextContent(props.children as string);
|
||||
expect(queryByTestId('test-link')).toBeNull();
|
||||
});
|
||||
|
||||
it('should display regular text (no link) if policy does not exist', () => {
|
||||
props.policyExists = false;
|
||||
const { getByTestId, queryByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-displayContent')).toHaveTextContent(props.children as string);
|
||||
expect(queryByTestId('test-link')).toBeNull();
|
||||
});
|
||||
|
||||
it('should display regular text (no link) if policy id is empty string', () => {
|
||||
props.policyId = '';
|
||||
const { getByTestId, queryByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-displayContent')).toHaveTextContent(props.children as string);
|
||||
expect(queryByTestId('test-link')).toBeNull();
|
||||
});
|
||||
|
||||
it('should display revision', () => {
|
||||
props.revision = 10;
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-revision')).toHaveTextContent('rev. 10');
|
||||
});
|
||||
|
||||
it('should display out-of-date message', () => {
|
||||
props.isOutdated = true;
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-outdatedMsg')).toHaveTextContent('Out-of-date');
|
||||
});
|
||||
|
||||
it('should display policy no longer available', () => {
|
||||
props.policyExists = false;
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-policyNotFoundMsg')).toHaveTextContent(POLICY_NOT_FOUND_MESSAGE);
|
||||
});
|
||||
|
||||
it('should display all info. when policy is missing and out of date', () => {
|
||||
props.revision = 10;
|
||||
props.isOutdated = true;
|
||||
props.policyExists = false;
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test').textContent).toEqual('policy name hererev. 10Out-of-date');
|
||||
});
|
||||
});
|
|
@ -6,60 +6,200 @@
|
|||
*/
|
||||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import type { EuiLinkAnchorProps } from '@elastic/eui';
|
||||
import { EuiLink, EuiText, EuiIcon } from '@elastic/eui';
|
||||
import type { EuiLinkAnchorProps, EuiTextProps } from '@elastic/eui';
|
||||
import { EuiLink, EuiText, EuiIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
import { getPolicyDetailPath } from '../common/routing';
|
||||
import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { useAppUrl } from '../../common/lib/kibana/hooks';
|
||||
import type { PolicyDetailsRouteState } from '../../../common/endpoint/types';
|
||||
import { useUserPrivileges } from '../../common/components/user_privileges';
|
||||
|
||||
export const POLICY_NOT_FOUND_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.endpointPolicyLink.policyNotFound',
|
||||
{ defaultMessage: 'Policy no longer available!' }
|
||||
);
|
||||
|
||||
export type EndpointPolicyLinkProps = Omit<EuiLinkAnchorProps, 'href'> & {
|
||||
policyId: string;
|
||||
/**
|
||||
* If defined, then a tooltip will also be shown when a user hovers over the dispplayed value (`children`).
|
||||
* When set to `true`, the tooltip content will be the same as the value that is
|
||||
* displayed (`children`). The tooltip can also be customized by passing in the content to be shown.
|
||||
*/
|
||||
tooltip?: boolean | React.ReactNode;
|
||||
/**
|
||||
* The revision of the policy that the Endpoint is running with (normally obtained from the host's metadata.
|
||||
*/
|
||||
revision?: number;
|
||||
/**
|
||||
* Will display an "out of date" message.
|
||||
*/
|
||||
isOutdated?: boolean;
|
||||
/** Text size to be applied to the display content (`children`) */
|
||||
textSize?: EuiTextProps['size'];
|
||||
/**
|
||||
* If policy still exists. In some cases, we could be displaying the policy name for a policy
|
||||
* that no longer exists (ex. it was deleted, but we still have data in ES that references that deleted policy)
|
||||
* When set to `true`, a link to the policy wil not be shown and the display value (`children`)
|
||||
* will have a message appended to it indicating policy no longer available.
|
||||
*/
|
||||
policyExists?: boolean;
|
||||
backLink?: PolicyDetailsRouteState['backLink'];
|
||||
};
|
||||
|
||||
/**
|
||||
* A policy link (to details) that first checks to see if the policy id exists against
|
||||
* the `nonExistingPolicies` value in the store. If it does not exist, then regular
|
||||
* text is returned.
|
||||
* Will display the provided content (`children`) as a link that takes the user to the Endpoint
|
||||
* Policy Details page. A link is only displayed if the user has Authz to that page, otherwise the
|
||||
* provided display content will just be shown as is.
|
||||
*/
|
||||
export const EndpointPolicyLink = memo<
|
||||
Omit<EuiLinkAnchorProps, 'href'> & {
|
||||
policyId: string;
|
||||
missingPolicies?: Record<string, boolean>;
|
||||
backLink?: PolicyDetailsRouteState['backLink'];
|
||||
}
|
||||
>(({ policyId, backLink, children, missingPolicies = {}, ...otherProps }) => {
|
||||
const { getAppUrl } = useAppUrl();
|
||||
const { toRoutePath, toRouteUrl } = useMemo(() => {
|
||||
const path = policyId ? getPolicyDetailPath(policyId) : '';
|
||||
return {
|
||||
toRoutePath: backLink ? { pathname: path, state: { backLink } } : path,
|
||||
toRouteUrl: getAppUrl({ path }),
|
||||
};
|
||||
}, [policyId, getAppUrl, backLink]);
|
||||
const clickHandler = useNavigateByRouterEventHandler(toRoutePath);
|
||||
export const EndpointPolicyLink = memo<EndpointPolicyLinkProps>(
|
||||
({
|
||||
policyId,
|
||||
backLink,
|
||||
children,
|
||||
policyExists = true,
|
||||
isOutdated = false,
|
||||
tooltip = true,
|
||||
revision,
|
||||
textSize = 's',
|
||||
...euiLinkProps
|
||||
}) => {
|
||||
const { getAppUrl } = useAppUrl();
|
||||
const { canReadPolicyManagement } = useUserPrivileges().endpointPrivileges;
|
||||
const testId = useTestIdGenerator(euiLinkProps['data-test-subj']);
|
||||
|
||||
if (!policyId || missingPolicies[policyId]) {
|
||||
return (
|
||||
<span className={otherProps.className} data-test-subj={otherProps['data-test-subj']}>
|
||||
{children}
|
||||
{
|
||||
<EuiText color="subdued" size="xs" className="eui-textNoWrap">
|
||||
const { toRoutePath, toRouteUrl } = useMemo(() => {
|
||||
const path = policyId ? getPolicyDetailPath(policyId) : '';
|
||||
return {
|
||||
toRoutePath: backLink ? { pathname: path, state: { backLink } } : path,
|
||||
toRouteUrl: getAppUrl({ path }),
|
||||
};
|
||||
}, [policyId, getAppUrl, backLink]);
|
||||
|
||||
const clickHandler = useNavigateByRouterEventHandler(toRoutePath);
|
||||
|
||||
const displayAsLink = useMemo(() => {
|
||||
return Boolean(canReadPolicyManagement && policyId && policyExists);
|
||||
}, [canReadPolicyManagement, policyExists, policyId]);
|
||||
|
||||
const displayValue = useMemo(() => {
|
||||
const content = (
|
||||
<EuiText
|
||||
className="eui-displayInline eui-textTruncate"
|
||||
size={textSize}
|
||||
data-test-subj={testId('displayContent')}
|
||||
>
|
||||
{children}
|
||||
</EuiText>
|
||||
);
|
||||
|
||||
return displayAsLink ? (
|
||||
// eslint-disable-next-line @elastic/eui/href-or-on-click
|
||||
<EuiLink
|
||||
href={toRouteUrl}
|
||||
onClick={clickHandler}
|
||||
{...euiLinkProps}
|
||||
data-test-subj={testId('link')}
|
||||
>
|
||||
{content}
|
||||
</EuiLink>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
}, [children, clickHandler, displayAsLink, euiLinkProps, testId, textSize, toRouteUrl]);
|
||||
|
||||
const policyNoLongerAvailableMessage = useMemo(() => {
|
||||
return (
|
||||
((!policyId || !policyExists) && (
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
className="eui-textNoWrap"
|
||||
data-test-subj={testId('policyNotFoundMsg')}
|
||||
>
|
||||
<EuiIcon size="m" type="warning" color="warning" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyNotFound"
|
||||
defaultMessage="Policy not found!"
|
||||
/>
|
||||
{POLICY_NOT_FOUND_MESSAGE}
|
||||
</EuiText>
|
||||
}
|
||||
</span>
|
||||
)) ||
|
||||
null
|
||||
);
|
||||
}, [policyExists, policyId, testId]);
|
||||
|
||||
const tooltipContent: React.ReactNode | undefined = useMemo(() => {
|
||||
const content = tooltip === true ? children : tooltip || undefined;
|
||||
return content ? (
|
||||
<div className="eui-textBreakAll" style={{ width: '100%' }}>
|
||||
{content}
|
||||
{policyNoLongerAvailableMessage && <> {`(${POLICY_NOT_FOUND_MESSAGE})`}</>}
|
||||
</div>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
}, [children, policyNoLongerAvailableMessage, tooltip]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiFlexGroup
|
||||
wrap={false}
|
||||
responsive={false}
|
||||
gutterSize="xs"
|
||||
alignItems="center"
|
||||
data-test-subj={testId()}
|
||||
>
|
||||
<EuiFlexItem
|
||||
data-test-subj={testId('policyName')}
|
||||
className="eui-textTruncate"
|
||||
grow={false}
|
||||
style={{ minWidth: '40px' }}
|
||||
>
|
||||
{tooltipContent ? (
|
||||
<EuiToolTip content={tooltipContent} anchorClassName="eui-textTruncate">
|
||||
{displayValue}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
displayValue
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
||||
{revision && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
className="eui-textTruncate"
|
||||
data-test-subj={testId('revision')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpointPolicyLink.policyVersion"
|
||||
defaultMessage="rev. {revision}"
|
||||
values={{ revision }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{isOutdated && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="xs" className="eui-textTruncate">
|
||||
<EuiIcon size="m" type="warning" color="warning" className="eui-alignTop" />
|
||||
<span className="eui-displayInlineBlock" data-test-subj={testId('outdatedMsg')}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpointPolicyLink.outdatedMessage"
|
||||
defaultMessage="Out-of-date"
|
||||
/>
|
||||
</span>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
{policyNoLongerAvailableMessage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line @elastic/eui/href-or-on-click
|
||||
<EuiLink href={toRouteUrl} onClick={clickHandler} {...otherProps}>
|
||||
{children}
|
||||
</EuiLink>
|
||||
);
|
||||
});
|
||||
|
||||
);
|
||||
EndpointPolicyLink.displayName = 'EndpointPolicyLink';
|
||||
|
|
|
@ -125,7 +125,7 @@ describe('Endpoints page', { tags: ['@ess', '@serverless'] }, () => {
|
|||
|
||||
cy.get<number>('@originalPolicyRevision').then((originalRevision: number) => {
|
||||
const revisionRegex = new RegExp(`^rev\\. ${originalRevision + 1}$`);
|
||||
cy.get('@endpointRow').findByTestSubj('policyListRevNo').contains(revisionRegex);
|
||||
cy.get('@endpointRow').findByTestSubj('policyNameCellLink-revision').contains(revisionRegex);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,27 +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 React from 'react';
|
||||
import { EuiText, EuiIcon } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export const OutOfDate = React.memo<{ style?: React.CSSProperties }>(({ style, ...otherProps }) => {
|
||||
return (
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
className="eui-textNoWrap eui-displayInlineBlock"
|
||||
style={style}
|
||||
{...otherProps}
|
||||
>
|
||||
<EuiIcon className={'eui-alignTop'} size="m" type="warning" color="warning" />
|
||||
<FormattedMessage id="xpack.securitySolution.outOfDateLabel" defaultMessage="Out-of-date" />
|
||||
</EuiText>
|
||||
);
|
||||
});
|
||||
|
||||
OutOfDate.displayName = 'OutOfDate';
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
|
@ -17,12 +16,12 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { isPolicyOutOfDate } from '../../utils';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
import {
|
||||
AgentStatus,
|
||||
EndpointAgentStatus,
|
||||
} from '../../../../../common/components/endpoint/agents/agent_status';
|
||||
import { isPolicyOutOfDate } from '../../utils';
|
||||
import type { HostInfo } from '../../../../../../common/endpoint/types';
|
||||
import { useEndpointSelector } from '../hooks';
|
||||
import {
|
||||
|
@ -35,13 +34,6 @@ import { FormattedDate } from '../../../../../common/components/formatted_date';
|
|||
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { getEndpointDetailsPath } from '../../../../common/routing';
|
||||
import { EndpointPolicyLink } from '../../../../components/endpoint_policy_link';
|
||||
import { OutOfDate } from '../components/out_of_date';
|
||||
|
||||
const EndpointDetailsContentStyled = styled.div`
|
||||
.policyLineText {
|
||||
padding-right: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ColumnTitle = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
|
@ -138,35 +130,15 @@ export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
|||
</ColumnTitle>
|
||||
),
|
||||
description: (
|
||||
<EuiText size="xs" className={'eui-textBreakWord'}>
|
||||
<EndpointPolicyLink
|
||||
policyId={hostInfo.metadata.Endpoint.policy.applied.id}
|
||||
data-test-subj="policyDetailsValue"
|
||||
className={'policyLineText'}
|
||||
missingPolicies={missingPolicies}
|
||||
>
|
||||
{hostInfo.metadata.Endpoint.policy.applied.name}
|
||||
</EndpointPolicyLink>
|
||||
{hostInfo.metadata.Endpoint.policy.applied.endpoint_policy_version && (
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
className={'eui-displayInlineBlock eui-textNoWrap policyLineText'}
|
||||
data-test-subj="policyDetailsRevNo"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.details.policy.revisionNumber"
|
||||
defaultMessage="rev. {revNumber}"
|
||||
values={{
|
||||
revNumber: hostInfo.metadata.Endpoint.policy.applied.endpoint_policy_version,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
{isPolicyOutOfDate(hostInfo.metadata.Endpoint.policy.applied, policyInfo) && (
|
||||
<OutOfDate />
|
||||
)}
|
||||
</EuiText>
|
||||
<EndpointPolicyLink
|
||||
policyId={hostInfo.metadata.Endpoint.policy.applied.id}
|
||||
revision={hostInfo.metadata.Endpoint.policy.applied.endpoint_policy_version}
|
||||
isOutdated={isPolicyOutOfDate(hostInfo.metadata.Endpoint.policy.applied, policyInfo)}
|
||||
policyExists={!missingPolicies[hostInfo.metadata.Endpoint.policy.applied.id]}
|
||||
data-test-subj="policyDetailsValue"
|
||||
>
|
||||
{hostInfo.metadata.Endpoint.policy.applied.name}
|
||||
</EndpointPolicyLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -227,17 +199,17 @@ export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
|||
},
|
||||
];
|
||||
}, [
|
||||
agentStatusClientEnabled,
|
||||
hostInfo,
|
||||
agentStatusClientEnabled,
|
||||
getHostPendingActions,
|
||||
missingPolicies,
|
||||
policyInfo,
|
||||
missingPolicies,
|
||||
policyStatus,
|
||||
policyStatusClickHandler,
|
||||
]);
|
||||
|
||||
return (
|
||||
<EndpointDetailsContentStyled>
|
||||
<div>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiDescriptionList
|
||||
columnWidths={[1, 3]}
|
||||
|
@ -247,7 +219,7 @@ export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
|||
listItems={detailsResults}
|
||||
data-test-subj="endpointDetailsList"
|
||||
/>
|
||||
</EndpointDetailsContentStyled>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -385,12 +385,11 @@ describe('when on the endpoint list page', () => {
|
|||
await reactTestingLibrary.act(async () => {
|
||||
await middlewareSpy.waitForAction('serverReturnedEndpointList');
|
||||
});
|
||||
const outOfDates = await renderResult.findAllByTestId('rowPolicyOutOfDate');
|
||||
const outOfDates = await renderResult.findAllByTestId('policyNameCellLink-outdatedMsg');
|
||||
expect(outOfDates).toHaveLength(4);
|
||||
|
||||
outOfDates.forEach((item) => {
|
||||
expect(item.textContent).toEqual('Out-of-date');
|
||||
expect(item.querySelector(`[data-euiicon-type][color=warning]`)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -399,7 +398,7 @@ describe('when on the endpoint list page', () => {
|
|||
await reactTestingLibrary.act(async () => {
|
||||
await middlewareSpy.waitForAction('serverReturnedEndpointList');
|
||||
});
|
||||
const firstPolicyName = (await renderResult.findAllByTestId('policyNameCellLink'))[0];
|
||||
const firstPolicyName = (await renderResult.findAllByTestId('policyNameCellLink-link'))[0];
|
||||
expect(firstPolicyName).not.toBeNull();
|
||||
expect(firstPolicyName.getAttribute('href')).toEqual(
|
||||
`${APP_PATH}${MANAGEMENT_PATH}/policy/${firstPolicyID}/settings`
|
||||
|
@ -452,7 +451,9 @@ describe('when on the endpoint list page', () => {
|
|||
await reactTestingLibrary.act(async () => {
|
||||
await middlewareSpy.waitForAction('serverReturnedEndpointList');
|
||||
});
|
||||
const firstPolicyRevElement = (await renderResult.findAllByTestId('policyListRevNo'))[0];
|
||||
const firstPolicyRevElement = (
|
||||
await renderResult.findAllByTestId('policyNameCellLink-revision')
|
||||
)[0];
|
||||
expect(firstPolicyRevElement).not.toBeNull();
|
||||
expect(firstPolicyRevElement.textContent).toEqual(`rev. ${firstPolicyRev}`);
|
||||
});
|
||||
|
@ -589,7 +590,7 @@ describe('when on the endpoint list page', () => {
|
|||
|
||||
it('should display policy name value as a link', async () => {
|
||||
const renderResult = render();
|
||||
const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue');
|
||||
const policyDetailsLink = await renderResult.findByTestId('policyNameCellLink-link');
|
||||
expect(policyDetailsLink).not.toBeNull();
|
||||
expect(policyDetailsLink.getAttribute('href')).toEqual(
|
||||
`${APP_PATH}${MANAGEMENT_PATH}/policy/${hostInfo.metadata.Endpoint.policy.applied.id}/settings`
|
||||
|
@ -598,7 +599,9 @@ describe('when on the endpoint list page', () => {
|
|||
|
||||
it('should display policy revision number', async () => {
|
||||
const renderResult = render();
|
||||
const policyDetailsRevElement = await renderResult.findByTestId('policyDetailsRevNo');
|
||||
const policyDetailsRevElement = await renderResult.findByTestId(
|
||||
'policyNameCellLink-revision'
|
||||
);
|
||||
expect(policyDetailsRevElement).not.toBeNull();
|
||||
expect(policyDetailsRevElement.textContent).toEqual(
|
||||
`rev. ${hostInfo.metadata.Endpoint.policy.applied.endpoint_policy_version}`
|
||||
|
@ -607,7 +610,7 @@ describe('when on the endpoint list page', () => {
|
|||
|
||||
it('should update the URL when policy name link is clicked', async () => {
|
||||
const renderResult = render();
|
||||
const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue');
|
||||
const policyDetailsLink = await renderResult.findByTestId('policyNameCellLink-link');
|
||||
const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl');
|
||||
reactTestingLibrary.act(() => {
|
||||
reactTestingLibrary.fireEvent.click(policyDetailsLink);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { type CSSProperties, useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { CriteriaWithPagination } from '@elastic/eui';
|
||||
import {
|
||||
|
@ -32,6 +32,7 @@ import type {
|
|||
AgentPolicyDetailsDeployAgentAction,
|
||||
CreatePackagePolicyRouteState,
|
||||
} from '@kbn/fleet-plugin/public';
|
||||
import { isPolicyOutOfDate } from '../utils';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import { TransformFailedCallout } from './components/transform_failed_callout';
|
||||
import type { EndpointIndexUIQueryParams } from '../types';
|
||||
|
@ -42,9 +43,8 @@ import {
|
|||
} from '../../../../common/components/endpoint/agents/agent_status';
|
||||
import { EndpointDetailsFlyout } from './details';
|
||||
import * as selectors from '../store/selectors';
|
||||
import { getEndpointPendingActionsCallback } from '../store/selectors';
|
||||
import { getEndpointPendingActionsCallback, nonExistingPolicies } from '../store/selectors';
|
||||
import { useEndpointSelector } from './hooks';
|
||||
import { isPolicyOutOfDate } from '../utils';
|
||||
import { POLICY_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_TEXT } from './host_constants';
|
||||
import type { CreateStructuredSelector } from '../../../../common/store';
|
||||
import type {
|
||||
|
@ -64,7 +64,6 @@ import { getEndpointDetailsPath, getEndpointListPath } from '../../../common/rou
|
|||
import { useFormatUrl } from '../../../../common/components/link_to';
|
||||
import { useAppUrl } from '../../../../common/lib/kibana/hooks';
|
||||
import type { EndpointAction } from '../store/action';
|
||||
import { OutOfDate } from './components/out_of_date';
|
||||
import { AdminSearchBar } from './components/search_bar';
|
||||
import { AdministrationListPage } from '../../../components/administration_list_page';
|
||||
import { TableRowActions } from './components/table_row_actions';
|
||||
|
@ -83,7 +82,7 @@ const StyledDatePicker = styled.div`
|
|||
|
||||
interface GetEndpointListColumnsProps {
|
||||
agentStatusClientEnabled: boolean;
|
||||
canReadPolicyManagement: boolean;
|
||||
missingPolicies: ReturnType<typeof nonExistingPolicies>;
|
||||
backToEndpointList: PolicyDetailsRouteState['backLink'];
|
||||
getHostPendingActions: ReturnType<typeof getEndpointPendingActionsCallback>;
|
||||
queryParams: Immutable<EndpointIndexUIQueryParams>;
|
||||
|
@ -108,7 +107,7 @@ const columnWidths: Record<
|
|||
|
||||
const getEndpointListColumns = ({
|
||||
agentStatusClientEnabled,
|
||||
canReadPolicyManagement,
|
||||
missingPolicies,
|
||||
backToEndpointList,
|
||||
getHostPendingActions,
|
||||
queryParams,
|
||||
|
@ -118,7 +117,6 @@ const getEndpointListColumns = ({
|
|||
const lastActiveColumnName = i18n.translate('xpack.securitySolution.endpoint.list.lastActive', {
|
||||
defaultMessage: 'Last active',
|
||||
});
|
||||
const padLeft: CSSProperties = { paddingLeft: '6px' };
|
||||
|
||||
return [
|
||||
{
|
||||
|
@ -183,42 +181,17 @@ const getEndpointListColumns = ({
|
|||
item: HostInfo
|
||||
) => {
|
||||
const policy = item.metadata.Endpoint.policy.applied;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiToolTip content={policyName} anchorClassName="eui-textTruncate">
|
||||
{canReadPolicyManagement ? (
|
||||
<EndpointPolicyLink
|
||||
policyId={policy.id}
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="policyNameCellLink"
|
||||
backLink={backToEndpointList}
|
||||
>
|
||||
{policyName}
|
||||
</EndpointPolicyLink>
|
||||
) : (
|
||||
<>{policyName}</>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
{policy.endpoint_policy_version && (
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
style={{ whiteSpace: 'nowrap', ...padLeft }}
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="policyListRevNo"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.list.policy.revisionNumber"
|
||||
defaultMessage="rev. {revNumber}"
|
||||
values={{ revNumber: policy.endpoint_policy_version }}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
{isPolicyOutOfDate(policy, item.policy_info) && (
|
||||
<OutOfDate style={padLeft} data-test-subj="rowPolicyOutOfDate" />
|
||||
)}
|
||||
</>
|
||||
<EndpointPolicyLink
|
||||
policyId={policy.id}
|
||||
revision={policy.endpoint_policy_version}
|
||||
isOutdated={isPolicyOutOfDate(policy, item.policy_info)}
|
||||
policyExists={!missingPolicies[policy.id]}
|
||||
data-test-subj="policyNameCellLink"
|
||||
backLink={backToEndpointList}
|
||||
>
|
||||
{policyName}
|
||||
</EndpointPolicyLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -375,11 +348,11 @@ export const EndpointList = () => {
|
|||
metadataTransformStats,
|
||||
isInitialized,
|
||||
} = useEndpointSelector(selector);
|
||||
const missingPolicies = useEndpointSelector(nonExistingPolicies);
|
||||
const getHostPendingActions = useEndpointSelector(getEndpointPendingActionsCallback);
|
||||
const {
|
||||
canReadEndpointList,
|
||||
canAccessFleet,
|
||||
canReadPolicyManagement,
|
||||
loading: endpointPrivilegesLoading,
|
||||
} = useUserPrivileges().endpointPrivileges;
|
||||
const { search } = useFormatUrl(SecurityPageName.administration);
|
||||
|
@ -540,9 +513,9 @@ export const EndpointList = () => {
|
|||
() =>
|
||||
getEndpointListColumns({
|
||||
agentStatusClientEnabled,
|
||||
canReadPolicyManagement,
|
||||
backToEndpointList,
|
||||
getAppUrl,
|
||||
missingPolicies,
|
||||
getHostPendingActions,
|
||||
queryParams,
|
||||
search,
|
||||
|
@ -550,9 +523,9 @@ export const EndpointList = () => {
|
|||
[
|
||||
agentStatusClientEnabled,
|
||||
backToEndpointList,
|
||||
canReadPolicyManagement,
|
||||
getAppUrl,
|
||||
getHostPendingActions,
|
||||
missingPolicies,
|
||||
queryParams,
|
||||
search,
|
||||
]
|
||||
|
|
|
@ -32643,7 +32643,6 @@
|
|||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "Une fois que vous avez activé cette fonctionnalité, vous pouvez obtenir un accès rapide aux scores de risque de {riskEntity} dans cette section. Les données pourront prendre jusqu'à une heure pour être générées après l'activation du module.",
|
||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "Mettre à niveau le score de risque de {riskEntity}",
|
||||
"xpack.securitySolution.endpoint.actions.unsupported.message": "La version actuelle de l'agent {agentType} ne prend pas en charge {command}. Mettez à niveau votre Elastic Agent via Fleet vers la dernière version pour activer cette action de réponse.",
|
||||
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "rév. {revNumber}",
|
||||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {Succès} warning {Avertissement} failure {Échec} other {Inconnu}}",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "Une erreur s'est produite lors de la tentative de récupération des statistiques d'artefacts : \"{error}\"",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.blocklistsSummary.error": "Une erreur s'est produite lors de la tentative de récupération des statistiques de liste noire : \"{error}\"",
|
||||
|
@ -32663,7 +32662,6 @@
|
|||
"xpack.securitySolution.endpoint.hostIsolation.unisolate.successfulMessage": "La libération de l'hôte {hostName} a été soumise",
|
||||
"xpack.securitySolution.endpoint.hostIsolation.unIsolateThisHost": "{hostName} est actuellement {isolated}. Voulez-vous vraiment {unisolate} cet hôte ?",
|
||||
"xpack.securitySolution.endpoint.list.hostStatusValue": "{hostStatus, select, healthy {Sain} unhealthy {En mauvais état} updating {En cours de mise à jour} offline {Hors ligne} inactive {Inactif} unenrolled {Désinscrit} other {En mauvais état}}",
|
||||
"xpack.securitySolution.endpoint.list.policy.revisionNumber": "rév. {revNumber}",
|
||||
"xpack.securitySolution.endpoint.list.totalCount": "Affichage de {totalItemCount, plural, one {# point de terminaison} other {# points de terminaison}}",
|
||||
"xpack.securitySolution.endpoint.list.totalCount.limited": "Affichage de {limit} de {totalItemCount, plural, one {# point de terminaison} other {# points de terminaison}}",
|
||||
"xpack.securitySolution.endpoint.list.transformFailed.message": "Une transformation requise, {transformId}, est actuellement en échec. La plupart du temps, ce problème peut être corrigé grâce aux {transformsPage}. Pour une assistance supplémentaire, veuillez visitez la {docsPage}",
|
||||
|
@ -32734,7 +32732,6 @@
|
|||
"xpack.securitySolution.endpoint.resolver.panel.relatedEventList.countByCategory": "{count} {category}",
|
||||
"xpack.securitySolution.endpoint.resolver.panel.relatedEventList.numberOfEvents": "{totalCount} événements",
|
||||
"xpack.securitySolution.endpoint.resolver.relatedEventLimitTitle": "Cette liste inclut {numberOfEntries} événements de processus.",
|
||||
"xpack.securitySolution.endpointPolicyStatus.revisionNumber": "rév. {revNumber}",
|
||||
"xpack.securitySolution.endpointResponseActions.actionError.errorMessage": "{ errorCount, plural, =1 {Erreur rencontrée} other {Erreurs rencontrées}} :",
|
||||
"xpack.securitySolution.enrichment.noInvestigationEnrichment": "Aucun renseignement supplémentaire sur les menaces n'a été détecté sur la période sélectionnée. Sélectionnez une autre plage temporelle ou {link} afin de collecter des renseignements sur les menaces pour les détecter et les comparer.",
|
||||
"xpack.securitySolution.entityAnalytics.anomalies.moduleNotCompatibleTitle": "{incompatibleJobCount} {incompatibleJobCount, plural, =1 {tâche est actuellement indisponible} other {tâches sont actuellement indisponibles}}",
|
||||
|
@ -35613,7 +35610,6 @@
|
|||
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromEndpointPage": "À partir de cette page, vous pourrez afficher et gérer les hôtes dans votre environnement exécutant Elastic Defend.",
|
||||
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromPolicyPage": "À partir de cette page, vous pourrez afficher et gérer les politiques d'intégration Elastic Defend dans votre environnement exécutant Elastic Defend.",
|
||||
"xpack.securitySolution.endpoint.policyList.onboardingTitle": "Lancez-vous avec Elastic Defend",
|
||||
"xpack.securitySolution.endpoint.policyNotFound": "Politique introuvable !",
|
||||
"xpack.securitySolution.endpoint.policyResponse.backLinkTitle": "Détails de point de terminaison",
|
||||
"xpack.securitySolution.endpoint.policyResponse.title": "Réponse de politique",
|
||||
"xpack.securitySolution.endpoint.protectionUpdates.automaticUpdates.enabled.toggleName": "Activer les mises à jour automatique",
|
||||
|
@ -35750,7 +35746,6 @@
|
|||
"xpack.securitySolution.endpointDetails.activityLog.logEntry.response.unisolationSuccessful": "Requête de libération de l'hôte reçue par Endpoint",
|
||||
"xpack.securitySolution.endpointDetails.overview": "Aperçu",
|
||||
"xpack.securitySolution.endpointDetails.responseActionsHistory": "Historique des actions de réponse",
|
||||
"xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "Politique appliquée",
|
||||
"xpack.securitySolution.endpointResponseActions.actionSubmitter.apiErrorDetails": "L'erreur suivante a été rencontrée :",
|
||||
"xpack.securitySolution.endpointResponseActions.executeAction.successTitle": "L'exécution de la commande a réussi.",
|
||||
"xpack.securitySolution.endpointResponseActions.getFileAction.successTitle": "Fichier récupéré à partir de l'hôte.",
|
||||
|
@ -36831,7 +36826,6 @@
|
|||
"xpack.securitySolution.osquery.action.permissionDenied": "Autorisation refusée",
|
||||
"xpack.securitySolution.osquery.action.shortEmptyTitle": "Osquery n’est pas disponible.",
|
||||
"xpack.securitySolution.osquery.action.unavailable": "L’intégration Osquery Manager n'a pas été ajoutée à la politique d'agent. Pour exécuter des requêtes sur l'hôte, ajoutez l'intégration Osquery Manager à la politique d'agent dans Fleet.",
|
||||
"xpack.securitySolution.outOfDateLabel": "Obsolète",
|
||||
"xpack.securitySolution.overview.auditBeatAuditTitle": "Audit",
|
||||
"xpack.securitySolution.overview.auditBeatFimTitle": "File Integrity Module",
|
||||
"xpack.securitySolution.overview.auditBeatLoginTitle": "Connexion",
|
||||
|
|
|
@ -32617,7 +32617,6 @@
|
|||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "この機能を有効化すると、このセクションで{riskEntity}リスクスコアにすばやくアクセスできます。モジュールを有効化した後、データの生成までに1時間かかる場合があります。",
|
||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "{riskEntity}リスクスコアをアップグレード",
|
||||
"xpack.securitySolution.endpoint.actions.unsupported.message": "現在のバージョンの{agentType}エージェントは、{command}をサポートしていません。この応答アクションを有効化するには、Fleet経由でElasticエージェントを最新バージョンにアップグレードしてください。",
|
||||
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "rev. {revNumber}",
|
||||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失敗} other {不明}}",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "アーティファクト統計情報の取得中にエラーが発生しました:\"{error}\"",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.blocklistsSummary.error": "ブロックリスト統計情報の取得中にエラーが発生しました:\"{error}\"",
|
||||
|
@ -32637,7 +32636,6 @@
|
|||
"xpack.securitySolution.endpoint.hostIsolation.unisolate.successfulMessage": "ホスト{hostName}でのリリースは正常に送信されました",
|
||||
"xpack.securitySolution.endpoint.hostIsolation.unIsolateThisHost": "現在{hostName}は{isolated}です。このホストを{unisolate}しますか?",
|
||||
"xpack.securitySolution.endpoint.list.hostStatusValue": "{hostStatus, select, healthy {正常} unhealthy {異常} updating {更新中} offline {オフライン} inactive {無効} unenrolled {登録解除済み} other {異常}}",
|
||||
"xpack.securitySolution.endpoint.list.policy.revisionNumber": "rev. {revNumber}",
|
||||
"xpack.securitySolution.endpoint.list.totalCount": "{totalItemCount, plural, other {# 個のエンドポイント}}を表示しています",
|
||||
"xpack.securitySolution.endpoint.list.totalCount.limited": "{totalItemCount, plural, other {# 個のエンドポイント}}の{limit}を表示しています",
|
||||
"xpack.securitySolution.endpoint.list.transformFailed.message": "現在、必須の変換{transformId}が失敗しています。通常、これは{transformsPage}で修正できます。ヘルプについては、{docsPage}をご覧ください",
|
||||
|
@ -32707,7 +32705,6 @@
|
|||
"xpack.securitySolution.endpoint.resolver.panel.relatedEventList.countByCategory": "{count} {category}",
|
||||
"xpack.securitySolution.endpoint.resolver.panel.relatedEventList.numberOfEvents": "{totalCount}件のイベント",
|
||||
"xpack.securitySolution.endpoint.resolver.relatedEventLimitTitle": "このリストには、{numberOfEntries} 件のプロセスイベントが含まれています。",
|
||||
"xpack.securitySolution.endpointPolicyStatus.revisionNumber": "rev. {revNumber}",
|
||||
"xpack.securitySolution.endpointResponseActions.actionError.errorMessage": "次の{ errorCount, plural, other {件のエラー}}が発生しました:",
|
||||
"xpack.securitySolution.enrichment.noInvestigationEnrichment": "選択した期間内に追加の脅威情報が見つかりませんでした。別の時間枠、または{link}を試して、脅威の検出と照合のための脅威インテリジェンスを収集します。",
|
||||
"xpack.securitySolution.entityAnalytics.anomalies.moduleNotCompatibleTitle": "{incompatibleJobCount} {incompatibleJobCount, plural, other {件のジョブ}}が現在使用できません",
|
||||
|
@ -35588,7 +35585,6 @@
|
|||
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromEndpointPage": "このページでは、Elastic Defendを実行している環境でホストを表示して管理できます。",
|
||||
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromPolicyPage": "このページでは、Elastic Defendを実行している環境で、Elastic Defend統合ポリシーを表示して管理できます。",
|
||||
"xpack.securitySolution.endpoint.policyList.onboardingTitle": "Elastic Defendをはじめよう",
|
||||
"xpack.securitySolution.endpoint.policyNotFound": "ポリシーが見つかりません。",
|
||||
"xpack.securitySolution.endpoint.policyResponse.backLinkTitle": "エンドポイント詳細",
|
||||
"xpack.securitySolution.endpoint.policyResponse.title": "ポリシー応答",
|
||||
"xpack.securitySolution.endpoint.protectionUpdates.automaticUpdates.enabled.toggleName": "自動更新を有効化",
|
||||
|
@ -35725,7 +35721,6 @@
|
|||
"xpack.securitySolution.endpointDetails.activityLog.logEntry.response.unisolationSuccessful": "エンドポイントが受信したホストリリースリクエスト",
|
||||
"xpack.securitySolution.endpointDetails.overview": "概要",
|
||||
"xpack.securitySolution.endpointDetails.responseActionsHistory": "対応アクション履歴",
|
||||
"xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "ポリシーが適用されました",
|
||||
"xpack.securitySolution.endpointResponseActions.actionSubmitter.apiErrorDetails": "次のエラーが発生しました:",
|
||||
"xpack.securitySolution.endpointResponseActions.executeAction.successTitle": "コマンド実行が成功しました。",
|
||||
"xpack.securitySolution.endpointResponseActions.getFileAction.successTitle": "ファイルがホストから取得されました。",
|
||||
|
@ -36806,7 +36801,6 @@
|
|||
"xpack.securitySolution.osquery.action.permissionDenied": "パーミッションが拒否されました",
|
||||
"xpack.securitySolution.osquery.action.shortEmptyTitle": "Osqueryが使用できません",
|
||||
"xpack.securitySolution.osquery.action.unavailable": "Osqueryマネージャー統合がエージェントポリシーに追加されていません。ホストでクエリを実行するには、FleetでOsqueryマネージャー統合をエージェントポリシーに追加してください。",
|
||||
"xpack.securitySolution.outOfDateLabel": "最新ではありません",
|
||||
"xpack.securitySolution.overview.auditBeatAuditTitle": "監査",
|
||||
"xpack.securitySolution.overview.auditBeatFimTitle": "File Integrityモジュール",
|
||||
"xpack.securitySolution.overview.auditBeatLoginTitle": "ログイン",
|
||||
|
|
|
@ -32660,7 +32660,6 @@
|
|||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "一旦启用此功能,您将可以在此部分快速访问{riskEntity}风险分数。启用此模板后,可能需要一小时才能生成数据。",
|
||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "升级{riskEntity}风险分数",
|
||||
"xpack.securitySolution.endpoint.actions.unsupported.message": "当前版本的 {agentType} 代理不支持 {command}。通过 Fleet 将您的 Elastic 代理升级到最新版本以启用此响应操作。",
|
||||
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "修订版 {revNumber}",
|
||||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失败} other {未知}}",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "尝试提取项目统计时出错:“{error}”",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.blocklistsSummary.error": "尝试提取阻止列表统计时出错:“{error}”",
|
||||
|
@ -32680,7 +32679,6 @@
|
|||
"xpack.securitySolution.endpoint.hostIsolation.unisolate.successfulMessage": "已成功提交主机 {hostName} 的释放",
|
||||
"xpack.securitySolution.endpoint.hostIsolation.unIsolateThisHost": "{hostName} 当前{isolated}。是否确定要{unisolate}此主机?",
|
||||
"xpack.securitySolution.endpoint.list.hostStatusValue": "{hostStatus, select, healthy {运行正常} unhealthy {运行不正常} updating {正在更新} offline {脱机} inactive {非活动} unenrolled {未注册} other {运行不正常}}",
|
||||
"xpack.securitySolution.endpoint.list.policy.revisionNumber": "修订版 {revNumber}",
|
||||
"xpack.securitySolution.endpoint.list.totalCount": "正在显示 {totalItemCount, plural, other {# 个终端}}",
|
||||
"xpack.securitySolution.endpoint.list.totalCount.limited": "正在显示 {totalItemCount, plural, other {# 个终端}}中的 {limit} 个",
|
||||
"xpack.securitySolution.endpoint.list.transformFailed.message": "所需的转换 {transformId} 当前失败。多数时候,这可以通过 {transformsPage} 解决。要获取更多帮助,请访问{docsPage}",
|
||||
|
@ -32751,7 +32749,6 @@
|
|||
"xpack.securitySolution.endpoint.resolver.panel.relatedEventList.countByCategory": "{count} 个{category}",
|
||||
"xpack.securitySolution.endpoint.resolver.panel.relatedEventList.numberOfEvents": "{totalCount} 个事件",
|
||||
"xpack.securitySolution.endpoint.resolver.relatedEventLimitTitle": "此列表包括 {numberOfEntries} 个进程事件。",
|
||||
"xpack.securitySolution.endpointPolicyStatus.revisionNumber": "修订版 {revNumber}",
|
||||
"xpack.securitySolution.endpointResponseActions.actionError.errorMessage": "遇到以下{ errorCount, plural, other {错误}}:",
|
||||
"xpack.securitySolution.enrichment.noInvestigationEnrichment": "在选定时间范围内未发现其他威胁情报。请尝试不同时间范围,或 {link} 以收集威胁情报用于威胁检测和匹配。",
|
||||
"xpack.securitySolution.entityAnalytics.anomalies.moduleNotCompatibleTitle": "{incompatibleJobCount} 个{incompatibleJobCount, plural, other {作业}}当前不可用",
|
||||
|
@ -35631,7 +35628,6 @@
|
|||
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromEndpointPage": "从此页面,您将能够查看和管理环境中运行 Elastic Defend 的主机。",
|
||||
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromPolicyPage": "从此页面,您将能够查看和管理运行 Elastic Defend 的环境中的 Elastic Defend 集成策略。",
|
||||
"xpack.securitySolution.endpoint.policyList.onboardingTitle": "开始使用 Elastic Defend",
|
||||
"xpack.securitySolution.endpoint.policyNotFound": "未找到策略!",
|
||||
"xpack.securitySolution.endpoint.policyResponse.backLinkTitle": "终端详情",
|
||||
"xpack.securitySolution.endpoint.policyResponse.title": "策略响应",
|
||||
"xpack.securitySolution.endpoint.protectionUpdates.automaticUpdates.enabled.toggleName": "启用自动更新",
|
||||
|
@ -35768,7 +35764,6 @@
|
|||
"xpack.securitySolution.endpointDetails.activityLog.logEntry.response.unisolationSuccessful": "终端收到释放主机请求",
|
||||
"xpack.securitySolution.endpointDetails.overview": "概览",
|
||||
"xpack.securitySolution.endpointDetails.responseActionsHistory": "响应操作历史记录",
|
||||
"xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "已应用策略",
|
||||
"xpack.securitySolution.endpointResponseActions.actionSubmitter.apiErrorDetails": "遇到以下错误:",
|
||||
"xpack.securitySolution.endpointResponseActions.executeAction.successTitle": "命令执行成功。",
|
||||
"xpack.securitySolution.endpointResponseActions.getFileAction.successTitle": "已从主机检索文件。",
|
||||
|
@ -36849,7 +36844,6 @@
|
|||
"xpack.securitySolution.osquery.action.permissionDenied": "权限被拒绝",
|
||||
"xpack.securitySolution.osquery.action.shortEmptyTitle": "Osquery 不可用",
|
||||
"xpack.securitySolution.osquery.action.unavailable": "Osquery 管理器集成未添加到代理策略。要在此主机上运行查询,请在 Fleet 中将 Osquery 管理器集成添加到代理策略。",
|
||||
"xpack.securitySolution.outOfDateLabel": "过时",
|
||||
"xpack.securitySolution.overview.auditBeatAuditTitle": "审计",
|
||||
"xpack.securitySolution.overview.auditBeatFimTitle": "文件完整性模块",
|
||||
"xpack.securitySolution.overview.auditBeatLoginTitle": "登录",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue