mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][RBAC][Endpoint List] RBAC permission guards reflect correct UI for endpoint list onboarding screen (#144958)
## Summary - Removes endpoint list link from Manage Navigation if user does not have at least endpoint list READ privileges - Shows a No Privileges page if user 1. has no endpoint list access but has fleet access, or 2. has neither endpoint list access nor fleet access - Shows a modified onboarding screen if the user has endpoint list access but no fleet access - General endpoint list onboarding flow is shown otherwise - [x] Unit tests for links and for endpoint list access
This commit is contained in:
parent
5e925f7860
commit
8e684bea77
6 changed files with 254 additions and 98 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { MouseEvent, CSSProperties } from 'react';
|
||||
import type { MouseEvent, CSSProperties, ReactNode } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import type { EuiSelectableProps } from '@elastic/eui';
|
||||
import {
|
||||
|
@ -43,90 +43,107 @@ interface ManagementStep {
|
|||
|
||||
const PolicyEmptyState = React.memo<{
|
||||
loading: boolean;
|
||||
onActionClick: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void;
|
||||
onActionClick?: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void;
|
||||
actionDisabled?: boolean;
|
||||
actionHidden?: boolean;
|
||||
additionalInfo?: ReactNode;
|
||||
policyEntryPoint?: boolean;
|
||||
}>(({ loading, onActionClick, actionDisabled, policyEntryPoint = false }) => {
|
||||
const docLinks = useKibana().services.docLinks;
|
||||
return (
|
||||
<div data-test-subj="emptyPolicyTable">
|
||||
{loading ? (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" className="essentialAnimation" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup data-test-subj="policyOnboardingInstructions" alignItems="center">
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiText>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingTitle"
|
||||
defaultMessage="Get started with Elastic Defend"
|
||||
/>
|
||||
</h1>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionOne"
|
||||
defaultMessage="Protect your hosts with threat prevention, detection, and deep security data visibility."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s" color="subdued">
|
||||
{policyEntryPoint ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromPolicyPage"
|
||||
defaultMessage="From this page, you’ll be able to view and manage the Elastic Defend Integration policies in your environment running Elastic Defend."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromEndpointPage"
|
||||
defaultMessage="From this page, you’ll be able to view and manage the hosts in your environment running Elastic Defend."
|
||||
/>
|
||||
)}
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionThree"
|
||||
defaultMessage="To get started, add the Elastic Defend integration to your Agents. For more information, "
|
||||
/>
|
||||
<EuiLink external href={`${docLinks.links.siem.guide}`}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingDocsLink"
|
||||
defaultMessage="view the Elastic Security documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
onClick={onActionClick}
|
||||
isDisabled={actionDisabled}
|
||||
data-test-subj="onboardingStartButton"
|
||||
>
|
||||
}>(
|
||||
({
|
||||
loading,
|
||||
onActionClick,
|
||||
actionDisabled,
|
||||
actionHidden,
|
||||
additionalInfo,
|
||||
policyEntryPoint = false,
|
||||
}) => {
|
||||
const docLinks = useKibana().services.docLinks;
|
||||
return (
|
||||
<div data-test-subj="emptyPolicyTable">
|
||||
{loading ? (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" className="essentialAnimation" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup data-test-subj="policyOnboardingInstructions" alignItems="center">
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiText>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.actionButtonText"
|
||||
defaultMessage="Add Elastic Defend"
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingTitle"
|
||||
defaultMessage="Get started with Elastic Defend"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiIcon type={onboardingLogo} size="original" style={MAX_SIZE_ONBOARDING_LOGO} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
</h1>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionOne"
|
||||
defaultMessage="Protect your hosts with threat prevention, detection, and deep security data visibility."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s" color="subdued">
|
||||
{policyEntryPoint ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromPolicyPage"
|
||||
defaultMessage="From this page, you’ll be able to view and manage the Elastic Defend Integration policies in your environment running Elastic Defend."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionTwo.fromEndpointPage"
|
||||
defaultMessage="From this page, you’ll be able to view and manage the hosts in your environment running Elastic Defend."
|
||||
/>
|
||||
)}
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingSectionThree"
|
||||
defaultMessage="To get started, add the Elastic Defend integration to your Agents. For more information, "
|
||||
/>
|
||||
<EuiLink external href={`${docLinks.links.siem.guide}`}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingDocsLink"
|
||||
defaultMessage="view the Elastic Security documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
{additionalInfo}
|
||||
{!actionHidden && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
onClick={onActionClick}
|
||||
isDisabled={actionDisabled}
|
||||
data-test-subj="onboardingStartButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.actionButtonText"
|
||||
defaultMessage="Add Elastic Defend"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiIcon type={onboardingLogo} size="original" style={MAX_SIZE_ONBOARDING_LOGO} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const EndpointsEmptyState = React.memo<{
|
||||
loading: boolean;
|
||||
|
|
|
@ -96,6 +96,7 @@ describe('links', () => {
|
|||
canUnIsolateHost: false,
|
||||
canAccessEndpointManagement: true,
|
||||
canReadActionsLogManagement: true,
|
||||
canReadEndpointList: true,
|
||||
});
|
||||
|
||||
const filteredLinks = await getManagementFilteredLinks(
|
||||
|
@ -114,6 +115,7 @@ describe('links', () => {
|
|||
canUnIsolateHost: true,
|
||||
canAccessEndpointManagement: true,
|
||||
canReadActionsLogManagement: true,
|
||||
canReadEndpointList: true,
|
||||
});
|
||||
fakeHttpServices.get.mockResolvedValue({ total: 0 });
|
||||
|
||||
|
@ -133,6 +135,7 @@ describe('links', () => {
|
|||
canUnIsolateHost: true,
|
||||
canAccessEndpointManagement: false,
|
||||
canReadActionsLogManagement: true,
|
||||
canReadEndpointList: true,
|
||||
});
|
||||
fakeHttpServices.get.mockResolvedValue({ total: 1 });
|
||||
|
||||
|
@ -166,6 +169,7 @@ describe('links', () => {
|
|||
canIsolateHost: false,
|
||||
canUnIsolateHost: true,
|
||||
canReadActionsLogManagement: true,
|
||||
canReadEndpointList: true,
|
||||
});
|
||||
fakeHttpServices.get.mockRejectedValue(new Error());
|
||||
|
||||
|
@ -184,6 +188,7 @@ describe('links', () => {
|
|||
canIsolateHost: false,
|
||||
canUnIsolateHost: true,
|
||||
canReadActionsLogManagement: false,
|
||||
canReadEndpointList: true,
|
||||
});
|
||||
fakeHttpServices.get.mockRejectedValue(new Error());
|
||||
|
||||
|
@ -201,4 +206,21 @@ describe('links', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('Endpoint List', () => {
|
||||
it('should return all but endpoints link when no Endpoint List READ access', async () => {
|
||||
(calculateEndpointAuthz as jest.Mock).mockReturnValue(
|
||||
getEndpointAuthzInitialStateMock({
|
||||
canReadEndpointList: false,
|
||||
})
|
||||
);
|
||||
const filteredLinks = await getManagementFilteredLinks(
|
||||
coreMockStarted,
|
||||
getPlugins(['superuser'])
|
||||
);
|
||||
expect(filteredLinks).toEqual({
|
||||
...links,
|
||||
links: links.links?.filter((link) => link.id !== SecurityPageName.endpoints),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -273,16 +273,21 @@ export const getManagementFilteredLinks = async (
|
|||
);
|
||||
}
|
||||
|
||||
const { canReadActionsLogManagement, canReadHostIsolationExceptions } = fleetAuthz
|
||||
? calculateEndpointAuthz(
|
||||
licenseService,
|
||||
fleetAuthz,
|
||||
currentUser.roles,
|
||||
isEndpointRbacEnabled,
|
||||
endpointPermissions,
|
||||
hasHostIsolationExceptions
|
||||
)
|
||||
: getEndpointAuthzInitialState();
|
||||
const { canReadActionsLogManagement, canReadHostIsolationExceptions, canReadEndpointList } =
|
||||
fleetAuthz
|
||||
? calculateEndpointAuthz(
|
||||
licenseService,
|
||||
fleetAuthz,
|
||||
currentUser.roles,
|
||||
isEndpointRbacEnabled,
|
||||
endpointPermissions,
|
||||
hasHostIsolationExceptions
|
||||
)
|
||||
: getEndpointAuthzInitialState();
|
||||
|
||||
if (!canReadEndpointList) {
|
||||
linksToExclude.push(SecurityPageName.endpoints);
|
||||
}
|
||||
|
||||
if (!canReadActionsLogManagement) {
|
||||
linksToExclude.push(SecurityPageName.responseActionsHistory);
|
||||
|
|
|
@ -52,9 +52,13 @@ import {
|
|||
METADATA_UNITED_TRANSFORM,
|
||||
} from '../../../../../common/endpoint/constants';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
import { initialUserPrivilegesState as mockInitialUserPrivilegesState } from '../../../../common/components/user_privileges/user_privileges_context';
|
||||
import {
|
||||
initialUserPrivilegesState,
|
||||
initialUserPrivilegesState as mockInitialUserPrivilegesState,
|
||||
} from '../../../../common/components/user_privileges/user_privileges_context';
|
||||
import { getUserPrivilegesMockDefaultValue } from '../../../../common/components/user_privileges/__mocks__';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { getEndpointPrivilegesInitialStateMock } from '../../../../common/components/user_privileges/endpoint/mocks';
|
||||
|
||||
const mockUserPrivileges = useUserPrivileges as jest.Mock;
|
||||
// not sure why this can't be imported from '../../../../common/mock/formatted_relative';
|
||||
|
@ -820,6 +824,8 @@ describe('when on the endpoint list page', () => {
|
|||
endpointPrivileges: {
|
||||
...mockInitialUserPrivilegesState().endpointPrivileges,
|
||||
canReadActionsLogManagement: false,
|
||||
canReadEndpointList: true,
|
||||
canAccessFleet: true,
|
||||
},
|
||||
});
|
||||
const renderResult = await renderAndWaitForData();
|
||||
|
@ -839,6 +845,8 @@ describe('when on the endpoint list page', () => {
|
|||
endpointPrivileges: {
|
||||
...mockInitialUserPrivilegesState().endpointPrivileges,
|
||||
canReadActionsLogManagement: false,
|
||||
canReadEndpointList: true,
|
||||
canAccessFleet: true,
|
||||
},
|
||||
});
|
||||
reactTestingLibrary.act(() => {
|
||||
|
@ -1231,6 +1239,14 @@ describe('when on the endpoint list page', () => {
|
|||
});
|
||||
|
||||
describe('required transform failed banner', () => {
|
||||
beforeEach(() => {
|
||||
mockUserPrivileges.mockReturnValue(getUserPrivilegesMockDefaultValue());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUserPrivileges.mockReset();
|
||||
});
|
||||
it('is not displayed when transform state is not failed', () => {
|
||||
const transforms: TransformStats[] = [
|
||||
{
|
||||
|
@ -1315,4 +1331,64 @@ describe('when on the endpoint list page', () => {
|
|||
expect(banner).toHaveTextContent(transforms[1].id);
|
||||
});
|
||||
});
|
||||
describe('endpoint list onboarding screens with RBAC', () => {
|
||||
beforeEach(() => {
|
||||
setEndpointListApiMockImplementation(coreStart.http, {
|
||||
endpointsResults: [],
|
||||
endpointPackagePolicies: mockPolicyResultList({ total: 3 }).items,
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
mockUserPrivileges.mockReset();
|
||||
});
|
||||
it('user has endpoint list ALL and fleet All and can view entire onboarding screen', async () => {
|
||||
mockUserPrivileges.mockReturnValue({
|
||||
...initialUserPrivilegesState(),
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock({
|
||||
canWriteEndpointList: true,
|
||||
canAccessFleet: true,
|
||||
}),
|
||||
});
|
||||
const renderResult = render();
|
||||
await reactTestingLibrary.act(async () => {
|
||||
await middlewareSpy.waitForAction('serverReturnedPoliciesForOnboarding');
|
||||
});
|
||||
const onboardingSteps = await renderResult.findByTestId('onboardingSteps');
|
||||
expect(onboardingSteps).not.toBeNull();
|
||||
});
|
||||
it('user has endpoint list READ and fleet All and can view entire onboarding screen', async () => {
|
||||
mockUserPrivileges.mockReturnValue({
|
||||
...initialUserPrivilegesState(),
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock({
|
||||
canReadEndpointList: true,
|
||||
canAccessFleet: true,
|
||||
}),
|
||||
});
|
||||
const renderResult = render();
|
||||
await reactTestingLibrary.act(async () => {
|
||||
await middlewareSpy.waitForAction('serverReturnedPoliciesForOnboarding');
|
||||
});
|
||||
const onboardingSteps = await renderResult.findByTestId('onboardingSteps');
|
||||
expect(onboardingSteps).not.toBeNull();
|
||||
});
|
||||
it('user has endpoint list ALL/READ and fleet NONE and can view a modified onboarding screen with no actions link to fleet', async () => {
|
||||
mockUserPrivileges.mockReturnValue({
|
||||
...initialUserPrivilegesState(),
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock({
|
||||
canReadEndpointList: true,
|
||||
canAccessFleet: false,
|
||||
}),
|
||||
});
|
||||
const renderResult = render();
|
||||
await reactTestingLibrary.act(async () => {
|
||||
await middlewareSpy.waitForAction('serverReturnedPoliciesForOnboarding');
|
||||
});
|
||||
const onboardingSteps = await renderResult.findByTestId('policyOnboardingInstructions');
|
||||
expect(onboardingSteps).not.toBeNull();
|
||||
const noPrivilegesPage = await renderResult.findByTestId('noFleetAccess');
|
||||
expect(noPrivilegesPage).not.toBeNull();
|
||||
const startButton = renderResult.queryByTestId('onboardingStartButton');
|
||||
expect(startButton).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,7 +31,6 @@ import type {
|
|||
AgentPolicyDetailsDeployAgentAction,
|
||||
} from '@kbn/fleet-plugin/public';
|
||||
import { pagePathGetters } from '@kbn/fleet-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { EndpointDetailsFlyout } from './details';
|
||||
import * as selectors from '../store/selectors';
|
||||
import { useEndpointSelector } from './hooks';
|
||||
|
@ -70,7 +69,8 @@ import { WARNING_TRANSFORM_STATES, APP_UI_ID } from '../../../../../common/const
|
|||
import type { BackToExternalAppButtonProps } from '../../../components/back_to_external_app_button/back_to_external_app_button';
|
||||
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button/back_to_external_app_button';
|
||||
import { ManagementEmptyStateWrapper } from '../../../components/management_empty_state_wrapper';
|
||||
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
const MAX_PAGINATED_ITEM = 9999;
|
||||
const TRANSFORM_URL = '/data/transform';
|
||||
|
||||
|
@ -132,6 +132,11 @@ export const EndpointList = () => {
|
|||
endpointsTotalError,
|
||||
metadataTransformStats,
|
||||
} = useEndpointSelector(selector);
|
||||
const {
|
||||
canReadEndpointList,
|
||||
canAccessFleet,
|
||||
loading: endpointPrivilegesLoading,
|
||||
} = useUserPrivileges().endpointPrivileges;
|
||||
const { search } = useFormatUrl(SecurityPageName.administration);
|
||||
const { search: searchParams } = useLocation();
|
||||
const { getAppUrl } = useAppUrl();
|
||||
|
@ -173,6 +178,23 @@ export const EndpointList = () => {
|
|||
<BackToExternalAppButton {...backLinkOptions} data-test-subj="endpointListBackLink" />
|
||||
);
|
||||
|
||||
const missingFleetAccessInfo = useMemo(() => {
|
||||
return (
|
||||
<EuiText size="s" color="subdued" data-test-subj="noFleetAccess">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.onboarding.enableFleetAccess"
|
||||
defaultMessage="Deploying Agents for the first time requires Fleet access. For more information, "
|
||||
/>
|
||||
<EuiLink external href={`${services.docLinks.links.securitySolution.privileges}`}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyList.onboardingDocsLink"
|
||||
defaultMessage="view the Elastic Security documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
);
|
||||
}, [services.docLinks.links.securitySolution.privileges]);
|
||||
|
||||
useEffect(() => {
|
||||
// if no endpoint policy, skip transform check
|
||||
if (!shouldCheckTransforms || !policyItems || !policyItems.length) {
|
||||
|
@ -547,6 +569,16 @@ export const EndpointList = () => {
|
|||
rowProps={setTableRowProps}
|
||||
/>
|
||||
);
|
||||
} else if (canReadEndpointList && !canAccessFleet) {
|
||||
return (
|
||||
<ManagementEmptyStateWrapper>
|
||||
<PolicyEmptyState
|
||||
loading={endpointPrivilegesLoading}
|
||||
actionHidden
|
||||
additionalInfo={missingFleetAccessInfo}
|
||||
/>
|
||||
</ManagementEmptyStateWrapper>
|
||||
);
|
||||
} else if (!policyItemsLoading && policyItems && policyItems.length > 0) {
|
||||
return (
|
||||
<HostsEmptyState
|
||||
|
@ -581,6 +613,10 @@ export const EndpointList = () => {
|
|||
handleSelectableOnChange,
|
||||
selectionOptions,
|
||||
handleCreatePolicyClick,
|
||||
canAccessFleet,
|
||||
canReadEndpointList,
|
||||
endpointPrivilegesLoading,
|
||||
missingFleetAccessInfo,
|
||||
]);
|
||||
|
||||
const hasListData = listData && listData.length > 0;
|
||||
|
@ -633,7 +669,7 @@ export const EndpointList = () => {
|
|||
docsPage: (
|
||||
<EuiLink
|
||||
data-test-subj="failed-transform-docs-link"
|
||||
href={services?.docLinks?.links.endpoints.troubleshooting}
|
||||
href={services.docLinks.links.endpoints.troubleshooting}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -647,7 +683,7 @@ export const EndpointList = () => {
|
|||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
}, [metadataTransformStats, services?.docLinks?.links.endpoints.troubleshooting]);
|
||||
}, [metadataTransformStats, services.docLinks.links.endpoints.troubleshooting]);
|
||||
|
||||
const transformFailedCallout = useMemo(() => {
|
||||
if (!showTransformFailedCallout) {
|
||||
|
@ -712,7 +748,7 @@ export const EndpointList = () => {
|
|||
appPath={`#${pagePathGetters.agent_list({
|
||||
kuery: 'packages : "endpoint"',
|
||||
})}`}
|
||||
href={`${services?.application?.getUrlForApp(
|
||||
href={`${services.application.getUrlForApp(
|
||||
'fleet'
|
||||
)}#${pagePathGetters.agent_list({
|
||||
kuery: 'packages : "endpoint"',
|
||||
|
|
|
@ -101,7 +101,7 @@ describe('when in the Administration tab', () => {
|
|||
describe.skip('when the user has permissions', () => {
|
||||
it('should display the Management view if user has privileges', async () => {
|
||||
useUserPrivilegesMock.mockReturnValue({
|
||||
endpointPrivileges: { loading: false, canReadEndpointList: true },
|
||||
endpointPrivileges: { loading: false, canReadEndpointList: true, canAccessFleet: true },
|
||||
});
|
||||
|
||||
expect(await render().findByTestId('endpointPage')).toBeTruthy();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue