[Security Solution][Endpoint] Show list of trusted application on the Policy Details (#112182)
* New Artifact Collapsible card and Grid generic components * Fleet setup test data loader - ignore 409 concurrent installs in data loader for fleet setup * Adds `ContextMenuWithRouterSupport` prop for `maxWidth` and `truncateText` prop for `ContextMenuItemNaByRouter` * trustedApps generator loader - use existing policies (if any) when loading TAs * `CardCompressedHeaderLayout` support for `flushTop` prop
This commit is contained in:
parent
6bfa2a4c2c
commit
36ce6bda67
57 changed files with 1694 additions and 326 deletions
|
@ -110,15 +110,20 @@ export const installOrUpgradeEndpointFleetPackage = async (
|
|||
);
|
||||
}
|
||||
|
||||
if (isFleetBulkInstallError(bulkResp[0])) {
|
||||
if (bulkResp[0].error instanceof Error) {
|
||||
const firstError = bulkResp[0];
|
||||
|
||||
if (isFleetBulkInstallError(firstError)) {
|
||||
if (firstError.error instanceof Error) {
|
||||
throw new EndpointDataLoadingError(
|
||||
`Installing the Endpoint package failed: ${bulkResp[0].error.message}, exiting`,
|
||||
`Installing the Endpoint package failed: ${firstError.error.message}, exiting`,
|
||||
bulkResp
|
||||
);
|
||||
}
|
||||
|
||||
throw new EndpointDataLoadingError(bulkResp[0].error, bulkResp);
|
||||
// Ignore `409` (conflicts due to Concurrent install or upgrades of package) errors
|
||||
if (firstError.statusCode !== 409) {
|
||||
throw new EndpointDataLoadingError(firstError.error, bulkResp);
|
||||
}
|
||||
}
|
||||
|
||||
return bulkResp[0] as BulkInstallPackageInfo;
|
||||
|
|
|
@ -32,7 +32,7 @@ export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchem
|
|||
/** API request params for retrieving summary of Trusted Apps */
|
||||
export type GetTrustedAppsSummaryRequest = TypeOf<typeof GetTrustedAppsSummaryRequestSchema.query>;
|
||||
|
||||
export interface GetTrustedListAppsResponse {
|
||||
export interface GetTrustedAppsListResponse {
|
||||
per_page: number;
|
||||
page: number;
|
||||
total: number;
|
||||
|
|
|
@ -18,7 +18,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import {
|
||||
ContextMenuItemNavByRouter,
|
||||
ContextMenuItemNavByRouterProps,
|
||||
} from '../context_menu_with_router_support/context_menu_item_nav_by_rotuer';
|
||||
} from '../context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
|
||||
export interface ActionsContextMenuProps {
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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 { TrustedAppGenerator } from '../../../../common/endpoint/data_generators/trusted_app_generator';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import React from 'react';
|
||||
import { ArtifactCardGrid, ArtifactCardGridProps } from './artifact_card_grid';
|
||||
|
||||
// FIXME:PT refactor helpers below after merge of PR https://github.com/elastic/kibana/pull/113363
|
||||
|
||||
const getCommonItemDataOverrides = () => {
|
||||
return {
|
||||
name: 'some internal app',
|
||||
description: 'this app is trusted by the company',
|
||||
created_at: new Date('2021-07-01').toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
const getTrustedAppProvider = () =>
|
||||
new TrustedAppGenerator('seed').generate(getCommonItemDataOverrides());
|
||||
|
||||
const getExceptionProvider = () => {
|
||||
// cloneDeep needed because exception mock generator uses state across instances
|
||||
return cloneDeep(
|
||||
getExceptionListItemSchemaMock({
|
||||
...getCommonItemDataOverrides(),
|
||||
os_types: ['windows'],
|
||||
updated_at: new Date().toISOString(),
|
||||
created_by: 'Justa',
|
||||
updated_by: 'Mara',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.hash.*',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '1234234659af249ddf3e40864e9fb241',
|
||||
},
|
||||
{
|
||||
field: 'process.executable.caseless',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '/one/two/three',
|
||||
},
|
||||
],
|
||||
tags: ['policy:all'],
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
describe.each([
|
||||
['trusted apps', getTrustedAppProvider],
|
||||
['exceptions/event filters', getExceptionProvider],
|
||||
])('when using the ArtifactCardGrid component %s', (_, generateItem) => {
|
||||
let appTestContext: AppContextTestRender;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let render: (
|
||||
props?: Partial<ArtifactCardGridProps>
|
||||
) => ReturnType<AppContextTestRender['render']>;
|
||||
let items: ArtifactCardGridProps['items'];
|
||||
let pageChangeHandler: jest.Mock<ArtifactCardGridProps['onPageChange']>;
|
||||
let expandCollapseHandler: jest.Mock<ArtifactCardGridProps['onExpandCollapse']>;
|
||||
let cardComponentPropsProvider: Required<ArtifactCardGridProps>['cardComponentProps'];
|
||||
|
||||
beforeEach(() => {
|
||||
items = Array.from({ length: 5 }, () => generateItem());
|
||||
pageChangeHandler = jest.fn();
|
||||
expandCollapseHandler = jest.fn();
|
||||
cardComponentPropsProvider = jest.fn().mockReturnValue({});
|
||||
|
||||
appTestContext = createAppRootMockRenderer();
|
||||
render = (props = {}) => {
|
||||
renderResult = appTestContext.render(
|
||||
<ArtifactCardGrid
|
||||
{...{
|
||||
items,
|
||||
onPageChange: pageChangeHandler!,
|
||||
onExpandCollapse: expandCollapseHandler!,
|
||||
cardComponentProps: cardComponentPropsProvider,
|
||||
'data-test-subj': 'testGrid',
|
||||
...props,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render the cards', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getAllByTestId('testGrid-card')).toHaveLength(5);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['header', 'testGrid-header'],
|
||||
['expand/collapse placeholder', 'testGrid-header-expandCollapsePlaceHolder'],
|
||||
['name column', 'testGrid-header-layout-title'],
|
||||
['description column', 'testGrid-header-layout-description'],
|
||||
['description column', 'testGrid-header-layout-cardActionsPlaceholder'],
|
||||
])('should display the Grid Header - %s', (__, selector) => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(selector)).not.toBeNull();
|
||||
});
|
||||
|
||||
it.todo('should call onPageChange callback when paginating');
|
||||
|
||||
it.todo('should use the props provided by cardComponentProps callback');
|
||||
|
||||
describe('and when cards are expanded/collapsed', () => {
|
||||
it.todo('should call onExpandCollapse callback');
|
||||
|
||||
it.todo('should provide list of cards that are expanded and collapsed');
|
||||
|
||||
it.todo('should show card expanded if card props defined it as such');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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, { ComponentType, memo, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
AnyArtifact,
|
||||
ArtifactEntryCollapsibleCard,
|
||||
ArtifactEntryCollapsibleCardProps,
|
||||
} from '../artifact_entry_card';
|
||||
import { PaginatedContent as _PaginatedContent, PaginatedContentProps } from '../paginated_content';
|
||||
import { GridHeader } from './components/grid_header';
|
||||
import { MaybeImmutable } from '../../../../common/endpoint/types';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
|
||||
const PaginatedContent: ArtifactsPaginatedComponent = _PaginatedContent;
|
||||
|
||||
type ArtifactsPaginatedContentProps = PaginatedContentProps<
|
||||
AnyArtifact,
|
||||
typeof ArtifactEntryCollapsibleCard
|
||||
>;
|
||||
|
||||
type ArtifactsPaginatedComponent = ComponentType<ArtifactsPaginatedContentProps>;
|
||||
|
||||
interface CardExpandCollapseState {
|
||||
expanded: MaybeImmutable<AnyArtifact[]>;
|
||||
collapsed: MaybeImmutable<AnyArtifact[]>;
|
||||
}
|
||||
|
||||
export type ArtifactCardGridCardComponentProps = Omit<
|
||||
ArtifactEntryCollapsibleCardProps,
|
||||
'onExpandCollapse' | 'item'
|
||||
>;
|
||||
export type ArtifactCardGridProps = Omit<
|
||||
ArtifactsPaginatedContentProps,
|
||||
'ItemComponent' | 'itemComponentProps' | 'items' | 'onChange'
|
||||
> & {
|
||||
items: MaybeImmutable<AnyArtifact[]>;
|
||||
|
||||
/** Callback to handle pagination changes */
|
||||
onPageChange: ArtifactsPaginatedContentProps['onChange'];
|
||||
|
||||
/** callback for handling changes to the card's expand/collapse state */
|
||||
onExpandCollapse: (state: CardExpandCollapseState) => void;
|
||||
|
||||
/**
|
||||
* Callback to provide additional props for the `ArtifactEntryCollapsibleCard`
|
||||
*/
|
||||
cardComponentProps?: (item: MaybeImmutable<AnyArtifact>) => ArtifactCardGridCardComponentProps;
|
||||
};
|
||||
|
||||
export const ArtifactCardGrid = memo<ArtifactCardGridProps>(
|
||||
({
|
||||
items: _items,
|
||||
cardComponentProps,
|
||||
onPageChange,
|
||||
onExpandCollapse,
|
||||
'data-test-subj': dataTestSubj,
|
||||
...paginatedContentProps
|
||||
}) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const items = _items as AnyArtifact[];
|
||||
|
||||
// The list of card props that the caller can define
|
||||
type PartialCardProps = Map<AnyArtifact, ArtifactCardGridCardComponentProps>;
|
||||
const callerDefinedCardProps = useMemo<PartialCardProps>(() => {
|
||||
const cardProps: PartialCardProps = new Map();
|
||||
|
||||
for (const artifact of items) {
|
||||
cardProps.set(artifact, cardComponentProps ? cardComponentProps(artifact) : {});
|
||||
}
|
||||
|
||||
return cardProps;
|
||||
}, [cardComponentProps, items]);
|
||||
|
||||
// Handling of what is expanded or collapsed is done by looking at the at what the caller card's
|
||||
// `expanded` prop value was and then invert it for the card that the user clicked expand/collapse
|
||||
const handleCardExpandCollapse = useCallback(
|
||||
(item: AnyArtifact) => {
|
||||
const expanded = [];
|
||||
const collapsed = [];
|
||||
|
||||
for (const [artifact, currentCardProps] of callerDefinedCardProps) {
|
||||
const currentExpandedState = Boolean(currentCardProps.expanded);
|
||||
const newExpandedState = artifact === item ? !currentExpandedState : currentExpandedState;
|
||||
|
||||
if (newExpandedState) {
|
||||
expanded.push(artifact);
|
||||
} else {
|
||||
collapsed.push(artifact);
|
||||
}
|
||||
}
|
||||
|
||||
onExpandCollapse({ expanded, collapsed });
|
||||
},
|
||||
[callerDefinedCardProps, onExpandCollapse]
|
||||
);
|
||||
|
||||
// Full list of card props that includes the actual artifact and the callbacks
|
||||
type FullCardProps = Map<AnyArtifact, ArtifactEntryCollapsibleCardProps>;
|
||||
const fullCardProps = useMemo<FullCardProps>(() => {
|
||||
const newFullCardProps: FullCardProps = new Map();
|
||||
|
||||
for (const [artifact, cardProps] of callerDefinedCardProps) {
|
||||
newFullCardProps.set(artifact, {
|
||||
...cardProps,
|
||||
item: artifact,
|
||||
onExpandCollapse: () => handleCardExpandCollapse(artifact),
|
||||
'data-test-subj': cardProps['data-test-subj'] ?? getTestId('card'),
|
||||
});
|
||||
}
|
||||
|
||||
return newFullCardProps;
|
||||
}, [callerDefinedCardProps, getTestId, handleCardExpandCollapse]);
|
||||
|
||||
const handleItemComponentProps = useCallback(
|
||||
(item: AnyArtifact): ArtifactEntryCollapsibleCardProps => {
|
||||
return fullCardProps.get(item)!;
|
||||
},
|
||||
[fullCardProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GridHeader data-test-subj={getTestId('header')} />
|
||||
|
||||
<PaginatedContent
|
||||
{...paginatedContentProps}
|
||||
data-test-subj={dataTestSubj}
|
||||
items={items}
|
||||
ItemComponent={ArtifactEntryCollapsibleCard}
|
||||
itemComponentProps={handleItemComponentProps}
|
||||
onChange={onPageChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
ArtifactCardGrid.displayName = 'ArtifactCardGrid';
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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, { memo, useMemo } from 'react';
|
||||
import { CommonProps, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import styled from 'styled-components';
|
||||
import { CardCompressedHeaderLayout, CardSectionPanel } from '../../artifact_entry_card';
|
||||
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
|
||||
|
||||
const GridHeaderContainer = styled(CardSectionPanel)`
|
||||
padding-top: 0;
|
||||
padding-bottom: ${({ theme }) => theme.eui.paddingSizes.s};
|
||||
`;
|
||||
|
||||
export type GridHeaderProps = Pick<CommonProps, 'data-test-subj'>;
|
||||
export const GridHeader = memo<GridHeaderProps>(({ 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const expandToggleElement = useMemo(
|
||||
() => <div data-test-subj={getTestId('expandCollapsePlaceHolder')} style={{ width: '24px' }} />,
|
||||
[getTestId]
|
||||
);
|
||||
|
||||
return (
|
||||
<GridHeaderContainer data-test-subj={dataTestSubj}>
|
||||
<CardCompressedHeaderLayout
|
||||
expanded={false}
|
||||
expandToggle={expandToggleElement}
|
||||
data-test-subj={getTestId('layout')}
|
||||
flushTop={true}
|
||||
name={
|
||||
<EuiText size="xs" data-test-subj={getTestId('name')}>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.artifactCardGrid.nameColumn"
|
||||
defaultMessage="Name"
|
||||
/>
|
||||
</strong>
|
||||
</EuiText>
|
||||
}
|
||||
description={
|
||||
<EuiText size="xs" data-test-subj={getTestId('description')}>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.artifactCardGrid.DescriptionColumn"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
</strong>
|
||||
</EuiText>
|
||||
}
|
||||
effectScope={
|
||||
<EuiText size="xs" data-test-subj={getTestId('assignment')}>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.artifactCardGrid.assignmentColumn"
|
||||
defaultMessage="Assignment"
|
||||
/>
|
||||
</strong>
|
||||
</EuiText>
|
||||
}
|
||||
actionMenu={false}
|
||||
/>
|
||||
</GridHeaderContainer>
|
||||
);
|
||||
});
|
||||
GridHeader.displayName = 'GridHeader';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './artifact_card_grid';
|
|
@ -10,7 +10,7 @@ import { AppContextTestRender, createAppRootMockRenderer } from '../../../common
|
|||
import { ArtifactEntryCard, ArtifactEntryCardProps } from './artifact_entry_card';
|
||||
import { act, fireEvent, getByTestId } from '@testing-library/react';
|
||||
import { AnyArtifact } from './types';
|
||||
import { isTrustedApp } from './hooks/use_normalized_artifact';
|
||||
import { isTrustedApp } from './utils';
|
||||
import { getTrustedAppProvider, getExceptionProvider } from './test_utils';
|
||||
|
||||
describe.each([
|
||||
|
|
|
@ -5,27 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { CommonProps, EuiHorizontalRule, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import React, { memo } from 'react';
|
||||
import { CommonProps, EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { CardHeader, CardHeaderProps } from './components/card_header';
|
||||
import { CardSubHeader } from './components/card_sub_header';
|
||||
import { getEmptyValue } from '../../../common/components/empty_value';
|
||||
import { CriteriaConditions, CriteriaConditionsProps } from './components/criteria_conditions';
|
||||
import { EffectScopeProps } from './components/effect_scope';
|
||||
import { ContextMenuItemNavByRouterProps } from '../context_menu_with_router_support/context_menu_item_nav_by_rotuer';
|
||||
import { AnyArtifact } from './types';
|
||||
import { AnyArtifact, MenuItemPropsByPolicyId } from './types';
|
||||
import { useNormalizedArtifact } from './hooks/use_normalized_artifact';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
|
||||
const CardContainerPanel = styled(EuiPanel)`
|
||||
&.artifactEntryCard + &.artifactEntryCard {
|
||||
margin-top: ${({ theme }) => theme.eui.spacerSizes.l};
|
||||
}
|
||||
`;
|
||||
import { CardContainerPanel } from './components/card_container_panel';
|
||||
import { CardSectionPanel } from './components/card_section_panel';
|
||||
import { usePolicyNavLinks } from './hooks/use_policy_nav_links';
|
||||
import { MaybeImmutable } from '../../../../common/endpoint/types';
|
||||
|
||||
export interface ArtifactEntryCardProps extends CommonProps {
|
||||
item: AnyArtifact;
|
||||
item: MaybeImmutable<AnyArtifact>;
|
||||
/**
|
||||
* The list of actions for the card. Will display an icon with the actions in a menu if defined.
|
||||
*/
|
||||
|
@ -33,52 +28,25 @@ export interface ArtifactEntryCardProps extends CommonProps {
|
|||
|
||||
/**
|
||||
* Information about the policies that are assigned to the `item`'s `effectScope` and that will be
|
||||
* use to create a navigation link
|
||||
* used to create the items in the popup context menu. This is a
|
||||
* `Record<policyId: string, ContextMenuItemNavByRouterProps>`.
|
||||
*/
|
||||
policies?: {
|
||||
[policyId: string]: ContextMenuItemNavByRouterProps;
|
||||
};
|
||||
policies?: MenuItemPropsByPolicyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Artifact Items (ex. Trusted App, Event Filter, etc) as a card.
|
||||
* This component is a TS Generic that allows you to set what the Item type is
|
||||
*/
|
||||
export const ArtifactEntryCard = memo(
|
||||
({
|
||||
item,
|
||||
policies,
|
||||
actions,
|
||||
'data-test-subj': dataTestSubj,
|
||||
...commonProps
|
||||
}: ArtifactEntryCardProps) => {
|
||||
const artifact = useNormalizedArtifact(item);
|
||||
export const ArtifactEntryCard = memo<ArtifactEntryCardProps>(
|
||||
({ item, policies, actions, 'data-test-subj': dataTestSubj, ...commonProps }) => {
|
||||
const artifact = useNormalizedArtifact(item as AnyArtifact);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
// create the policy links for each policy listed in the artifact record by grabbing the
|
||||
// navigation data from the `policies` prop (if any)
|
||||
const policyNavLinks = useMemo<EffectScopeProps['policies']>(() => {
|
||||
return artifact.effectScope.type === 'policy'
|
||||
? artifact?.effectScope.policies.map((id) => {
|
||||
return policies && policies[id]
|
||||
? policies[id]
|
||||
: // else, unable to build a nav link, so just show id
|
||||
{
|
||||
children: id,
|
||||
};
|
||||
})
|
||||
: undefined;
|
||||
}, [artifact.effectScope, policies]);
|
||||
const policyNavLinks = usePolicyNavLinks(artifact, policies);
|
||||
|
||||
return (
|
||||
<CardContainerPanel
|
||||
hasBorder={true}
|
||||
{...commonProps}
|
||||
data-test-subj={dataTestSubj}
|
||||
paddingSize="none"
|
||||
className="artifactEntryCard"
|
||||
>
|
||||
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
|
||||
<CardContainerPanel {...commonProps} data-test-subj={dataTestSubj}>
|
||||
<CardSectionPanel>
|
||||
<CardHeader
|
||||
name={artifact.name}
|
||||
createdDate={artifact.created_at}
|
||||
|
@ -100,17 +68,17 @@ export const ArtifactEntryCard = memo(
|
|||
{artifact.description || getEmptyValue()}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</CardSectionPanel>
|
||||
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
||||
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
|
||||
<CardSectionPanel>
|
||||
<CriteriaConditions
|
||||
os={artifact.os as CriteriaConditionsProps['os']}
|
||||
entries={artifact.entries}
|
||||
data-test-subj={getTestId('criteriaConditions')}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</CardSectionPanel>
|
||||
</CardContainerPanel>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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, { memo } from 'react';
|
||||
import { EuiHorizontalRule } from '@elastic/eui';
|
||||
import { ArtifactEntryCardProps } from './artifact_entry_card';
|
||||
import { CardContainerPanel } from './components/card_container_panel';
|
||||
import { useNormalizedArtifact } from './hooks/use_normalized_artifact';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
import { CardSectionPanel } from './components/card_section_panel';
|
||||
import { CriteriaConditions, CriteriaConditionsProps } from './components/criteria_conditions';
|
||||
import { CardCompressedHeader } from './components/card_compressed_header';
|
||||
|
||||
export interface ArtifactEntryCollapsibleCardProps extends ArtifactEntryCardProps {
|
||||
onExpandCollapse: () => void;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
export const ArtifactEntryCollapsibleCard = memo<ArtifactEntryCollapsibleCardProps>(
|
||||
({
|
||||
item,
|
||||
onExpandCollapse,
|
||||
policies,
|
||||
actions,
|
||||
expanded = false,
|
||||
'data-test-subj': dataTestSubj,
|
||||
...commonProps
|
||||
}) => {
|
||||
const artifact = useNormalizedArtifact(item);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
<CardContainerPanel {...commonProps} data-test-subj={dataTestSubj}>
|
||||
<CardSectionPanel>
|
||||
<CardCompressedHeader
|
||||
artifact={artifact}
|
||||
actions={actions}
|
||||
policies={policies}
|
||||
expanded={expanded}
|
||||
onExpandCollapse={onExpandCollapse}
|
||||
data-test-subj={getTestId('header')}
|
||||
/>
|
||||
</CardSectionPanel>
|
||||
{expanded && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
|
||||
<CardSectionPanel>
|
||||
<CriteriaConditions
|
||||
os={artifact.os as CriteriaConditionsProps['os']}
|
||||
entries={artifact.entries}
|
||||
data-test-subj={getTestId('criteriaConditions')}
|
||||
/>
|
||||
</CardSectionPanel>
|
||||
</>
|
||||
)}
|
||||
</CardContainerPanel>
|
||||
);
|
||||
}
|
||||
);
|
||||
ArtifactEntryCollapsibleCard.displayName = 'ArtifactEntryCollapsibleCard';
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { CommonProps, EuiFlexItem } from '@elastic/eui';
|
||||
import { ActionsContextMenu, ActionsContextMenuProps } from '../../actions_context_menu';
|
||||
|
||||
export interface CardActionsFlexItemProps extends Pick<CommonProps, 'data-test-subj'> {
|
||||
/** If defined, then an overflow menu will be shown with the actions provided */
|
||||
actions?: ActionsContextMenuProps['items'];
|
||||
}
|
||||
|
||||
export const CardActionsFlexItem = memo<CardActionsFlexItemProps>(
|
||||
({ actions, 'data-test-subj': dataTestSubj }) => {
|
||||
return actions && actions.length > 0 ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ActionsContextMenu items={actions} icon="boxesHorizontal" data-test-subj={dataTestSubj} />
|
||||
</EuiFlexItem>
|
||||
) : null;
|
||||
}
|
||||
);
|
||||
CardActionsFlexItem.displayName = 'CardActionsFlexItem';
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* 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, { memo, ReactNode, useCallback } from 'react';
|
||||
import { CommonProps, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { CardExpandButton } from './card_expand_button';
|
||||
import { TextValueDisplay } from './text_value_display';
|
||||
import { EffectScope } from './effect_scope';
|
||||
import { CardActionsFlexItem } from './card_actions_flex_item';
|
||||
import { ArtifactInfo } from '../types';
|
||||
import { ArtifactEntryCollapsibleCardProps } from '../artifact_entry_collapsible_card';
|
||||
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
|
||||
import { useCollapsedCssClassNames } from '../hooks/use_collapsed_css_class_names';
|
||||
import { usePolicyNavLinks } from '../hooks/use_policy_nav_links';
|
||||
import { getEmptyValue } from '../../../../common/components/empty_value';
|
||||
|
||||
export interface CardCompressedHeaderProps
|
||||
extends Pick<CommonProps, 'data-test-subj'>,
|
||||
Pick<
|
||||
ArtifactEntryCollapsibleCardProps,
|
||||
'onExpandCollapse' | 'expanded' | 'actions' | 'policies'
|
||||
> {
|
||||
artifact: ArtifactInfo;
|
||||
}
|
||||
|
||||
export const CardCompressedHeader = memo<CardCompressedHeaderProps>(
|
||||
({
|
||||
artifact,
|
||||
onExpandCollapse,
|
||||
policies,
|
||||
actions,
|
||||
expanded = false,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const cssClassNames = useCollapsedCssClassNames(expanded);
|
||||
const policyNavLinks = usePolicyNavLinks(artifact, policies);
|
||||
|
||||
const handleExpandCollapseClick = useCallback(() => {
|
||||
onExpandCollapse();
|
||||
}, [onExpandCollapse]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup responsive={false} alignItems="center" data-test-subj={dataTestSubj}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<CardExpandButton
|
||||
expanded={expanded}
|
||||
onClick={handleExpandCollapseClick}
|
||||
data-test-subj={getTestId('expandCollapse')}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className={cssClassNames}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={2} className={cssClassNames} data-test-subj={getTestId('title')}>
|
||||
<TextValueDisplay bold truncate={!expanded}>
|
||||
{artifact.name}
|
||||
</TextValueDisplay>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={3}
|
||||
className={cssClassNames}
|
||||
data-test-subj={getTestId('description')}
|
||||
>
|
||||
<TextValueDisplay truncate={!expanded}>
|
||||
{artifact.description || getEmptyValue()}
|
||||
</TextValueDisplay>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EffectScope policies={policyNavLinks} data-test-subj={getTestId('effectScope')} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<CardActionsFlexItem actions={actions} data-test-subj={getTestId('actions')} />
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
CardCompressedHeader.displayName = 'CardCompressedHeader';
|
||||
|
||||
const ButtonIconPlaceHolder = styled.div`
|
||||
display: inline-block;
|
||||
// Sizes below should match that of the Eui's Button Icon, so that it holds the same space.
|
||||
width: ${({ theme }) => theme.eui.euiIconSizes.large};
|
||||
height: ${({ theme }) => theme.eui.euiIconSizes.large};
|
||||
`;
|
||||
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
&.flushTop,
|
||||
.flushTop {
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Layout used for the compressed card header. Used also in the ArtifactGrid for creating the grid header row
|
||||
*/
|
||||
export interface CardCompressedHeaderLayoutProps extends Pick<CommonProps, 'data-test-subj'> {
|
||||
expanded: boolean;
|
||||
expandToggle: ReactNode;
|
||||
name: ReactNode;
|
||||
description: ReactNode;
|
||||
effectScope: ReactNode;
|
||||
/** If no menu is shown, but you want the space for it be preserved, set prop to `false` */
|
||||
actionMenu?: ReactNode | false;
|
||||
/**
|
||||
* When set to `true`, all padding and margin values will be set to zero for the top of the header
|
||||
* layout, so that all content is flushed to the top
|
||||
*/
|
||||
flushTop?: boolean;
|
||||
}
|
||||
|
||||
export const CardCompressedHeaderLayout = memo<CardCompressedHeaderLayoutProps>(
|
||||
({
|
||||
expanded,
|
||||
name,
|
||||
expandToggle,
|
||||
effectScope,
|
||||
actionMenu,
|
||||
description,
|
||||
'data-test-subj': dataTestSubj,
|
||||
flushTop,
|
||||
}) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const cssClassNames = useCollapsedCssClassNames(expanded);
|
||||
const flushTopCssClassname = flushTop ? ' flushTop' : '';
|
||||
|
||||
return (
|
||||
<StyledEuiFlexGroup
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
data-test-subj={dataTestSubj}
|
||||
className={flushTopCssClassname}
|
||||
>
|
||||
<EuiFlexItem grow={false} className={flushTopCssClassname}>
|
||||
{expandToggle}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className={cssClassNames + flushTopCssClassname}>
|
||||
<EuiFlexGroup alignItems="center" className={flushTopCssClassname}>
|
||||
<EuiFlexItem
|
||||
grow={2}
|
||||
className={cssClassNames + flushTopCssClassname}
|
||||
data-test-subj={getTestId('title')}
|
||||
>
|
||||
{name}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={3}
|
||||
className={cssClassNames + flushTopCssClassname}
|
||||
data-test-subj={getTestId('description')}
|
||||
>
|
||||
{description}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={1}
|
||||
data-test-subj={getTestId('effectScope')}
|
||||
className={flushTopCssClassname}
|
||||
>
|
||||
{effectScope}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{actionMenu === false ? (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
data-test-subj={getTestId('cardActionsPlaceholder')}
|
||||
className={flushTopCssClassname}
|
||||
>
|
||||
<ButtonIconPlaceHolder />
|
||||
</EuiFlexItem>
|
||||
) : (
|
||||
actionMenu
|
||||
)}
|
||||
</StyledEuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
CardCompressedHeaderLayout.displayName = 'CardCompressedHeaderLayout';
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 styled from 'styled-components';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { EuiPanelProps } from '@elastic/eui/src/components/panel/panel';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
export const EuiPanelStyled = styled(EuiPanel)`
|
||||
&.artifactEntryCard + &.artifactEntryCard {
|
||||
margin-top: ${({ theme }) => theme.eui.spacerSizes.l};
|
||||
}
|
||||
`;
|
||||
|
||||
export type CardContainerPanelProps = Exclude<EuiPanelProps, 'hasBorder' | 'paddingSize'>;
|
||||
|
||||
export const CardContainerPanel = memo<CardContainerPanelProps>(({ className, ...props }) => {
|
||||
return (
|
||||
<EuiPanelStyled
|
||||
{...props}
|
||||
hasBorder={true}
|
||||
paddingSize="none"
|
||||
className={`artifactEntryCard ${className ?? ''}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
CardContainerPanel.displayName = 'CardContainerPanel';
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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, { memo } from 'react';
|
||||
import { CommonProps, EuiButtonIcon, EuiButtonIconPropsForButton } from '@elastic/eui';
|
||||
import { COLLAPSE_ACTION, EXPAND_ACTION } from './translations';
|
||||
|
||||
export interface CardExpandButtonProps extends Pick<CommonProps, 'data-test-subj'> {
|
||||
expanded: boolean;
|
||||
onClick: EuiButtonIconPropsForButton['onClick'];
|
||||
}
|
||||
|
||||
export const CardExpandButton = memo<CardExpandButtonProps>(
|
||||
({ expanded, onClick, 'data-test-subj': dataTestSubj }) => {
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
iconType={expanded ? 'arrowUp' : 'arrowDown'}
|
||||
onClick={onClick}
|
||||
data-test-subj={dataTestSubj}
|
||||
aria-label={expanded ? COLLAPSE_ACTION : EXPAND_ACTION}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
CardExpandButton.displayName = 'CardExpandButton';
|
|
@ -8,15 +8,15 @@
|
|||
import React, { memo } from 'react';
|
||||
import { CommonProps, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { DateFieldValue } from './date_field_value';
|
||||
import { ActionsContextMenu, ActionsContextMenuProps } from '../../actions_context_menu';
|
||||
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
|
||||
import { CardActionsFlexItem, CardActionsFlexItemProps } from './card_actions_flex_item';
|
||||
|
||||
export interface CardHeaderProps extends Pick<CommonProps, 'data-test-subj'> {
|
||||
export interface CardHeaderProps
|
||||
extends CardActionsFlexItemProps,
|
||||
Pick<CommonProps, 'data-test-subj'> {
|
||||
name: string;
|
||||
createdDate: string;
|
||||
updatedDate: string;
|
||||
/** If defined, then an overflow menu will be shown with the actions provided */
|
||||
actions?: ActionsContextMenuProps['items'];
|
||||
}
|
||||
|
||||
export const CardHeader = memo<CardHeaderProps>(
|
||||
|
@ -52,15 +52,7 @@ export const CardHeader = memo<CardHeaderProps>(
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{actions && actions.length > 0 && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ActionsContextMenu
|
||||
items={actions}
|
||||
icon="boxesHorizontal"
|
||||
data-test-subj={getTestId('actions')}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<CardActionsFlexItem actions={actions} data-test-subj={getTestId('actions')} />
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiPanel, EuiPanelProps } from '@elastic/eui';
|
||||
|
||||
export type CardSectionPanelProps = Exclude<
|
||||
EuiPanelProps,
|
||||
'hasBorder' | 'hasShadow' | 'paddingSize'
|
||||
>;
|
||||
|
||||
export const CardSectionPanel = memo<CardSectionPanelProps>((props) => {
|
||||
return <EuiPanel {...props} hasBorder={false} hasShadow={false} paddingSize="l" />;
|
||||
});
|
||||
CardSectionPanel.displayName = 'CardSectionPanel';
|
|
@ -20,7 +20,7 @@ export const CardSubHeader = memo<SubHeaderProps>(
|
|||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" responsive={false} data-test-subj={dataTestSubj}>
|
||||
<EuiFlexGroup alignItems="center" responsive={true} data-test-subj={dataTestSubj}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TouchedByUsers
|
||||
createdBy={createdBy}
|
||||
|
|
|
@ -10,9 +10,15 @@ import { CommonProps, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon } from
|
|||
import { GLOBAL_EFFECT_SCOPE, POLICY_EFFECT_SCOPE } from './translations';
|
||||
import { TextValueDisplay } from './text_value_display';
|
||||
import { ContextMenuWithRouterSupport } from '../../context_menu_with_router_support';
|
||||
import { ContextMenuItemNavByRouterProps } from '../../context_menu_with_router_support/context_menu_item_nav_by_rotuer';
|
||||
import { ContextMenuItemNavByRouterProps } from '../../context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
|
||||
|
||||
// FIXME:PT support being able to show per policy label for Artifacst that have >0 policies, but no menu
|
||||
// the intent in this component was to also support to be able to display only text for artifacts
|
||||
// by policy (>0), but **NOT** show the menu.
|
||||
// So something like: `<EffectScope perPolicyCount={3} />`
|
||||
// This should dispaly it as "Applied t o 3 policies", but NOT as a menu with links
|
||||
|
||||
export interface EffectScopeProps extends Pick<CommonProps, 'data-test-subj'> {
|
||||
/** If set (even if empty), then effect scope will be policy specific. Else, it shows as global */
|
||||
policies?: ContextMenuItemNavByRouterProps[];
|
||||
|
|
|
@ -5,18 +5,30 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, PropsWithChildren } from 'react';
|
||||
import React, { memo, PropsWithChildren, useMemo } from 'react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export type TextValueDisplayProps = PropsWithChildren<{
|
||||
bold?: boolean;
|
||||
truncate?: boolean;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Common component for displaying consistent text across the card. Changes here could impact all of
|
||||
* display of values on the card
|
||||
*/
|
||||
export const TextValueDisplay = memo<TextValueDisplayProps>(({ bold, children }) => {
|
||||
return <EuiText size="s">{bold ? <strong>{children}</strong> : children}</EuiText>;
|
||||
export const TextValueDisplay = memo<TextValueDisplayProps>(({ bold, truncate, children }) => {
|
||||
const cssClassNames = useMemo(() => {
|
||||
return classNames({
|
||||
'eui-textTruncate': truncate,
|
||||
});
|
||||
}, [truncate]);
|
||||
|
||||
return (
|
||||
<EuiText size="s" className={cssClassNames}>
|
||||
{bold ? <strong>{children}</strong> : children}
|
||||
</EuiText>
|
||||
);
|
||||
});
|
||||
TextValueDisplay.displayName = 'TextValueDisplay';
|
||||
|
|
|
@ -100,3 +100,17 @@ export const OS_LINUX = i18n.translate('xpack.securitySolution.artifactCard.cond
|
|||
export const OS_MAC = i18n.translate('xpack.securitySolution.artifactCard.conditions.macos', {
|
||||
defaultMessage: 'Mac',
|
||||
});
|
||||
|
||||
export const EXPAND_ACTION = i18n.translate(
|
||||
'xpack.securitySolution.artifactExpandableCard.expand',
|
||||
{
|
||||
defaultMessage: 'Expand',
|
||||
}
|
||||
);
|
||||
|
||||
export const COLLAPSE_ACTION = i18n.translate(
|
||||
'xpack.securitySolution.artifactExpandableCard.collpase',
|
||||
{
|
||||
defaultMessage: 'Collapse',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Returns the css classnames that should be applied when the collapsible card is NOT expanded
|
||||
* @param expanded
|
||||
*/
|
||||
export const useCollapsedCssClassNames = (expanded?: boolean): string => {
|
||||
return useMemo(() => {
|
||||
return classNames({
|
||||
'eui-textTruncate': !expanded,
|
||||
});
|
||||
}, [expanded]);
|
||||
};
|
|
@ -5,53 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { AnyArtifact, ArtifactInfo } from '../types';
|
||||
import { EffectScope, TrustedApp } from '../../../../../common/endpoint/types';
|
||||
import { tagsToEffectScope } from '../../../../../common/endpoint/service/trusted_apps/mapping';
|
||||
import { mapToArtifactInfo } from '../utils';
|
||||
import { MaybeImmutable } from '../../../../../common/endpoint/types';
|
||||
|
||||
/**
|
||||
* Takes in any artifact and return back a new data structure used internally with by the card's components
|
||||
*
|
||||
* @param item
|
||||
*/
|
||||
export const useNormalizedArtifact = (item: AnyArtifact): ArtifactInfo => {
|
||||
export const useNormalizedArtifact = (item: MaybeImmutable<AnyArtifact>): ArtifactInfo => {
|
||||
return useMemo(() => {
|
||||
const {
|
||||
name,
|
||||
created_by,
|
||||
created_at,
|
||||
updated_at,
|
||||
updated_by,
|
||||
description = '',
|
||||
entries,
|
||||
} = item;
|
||||
return {
|
||||
name,
|
||||
created_by,
|
||||
created_at,
|
||||
updated_at,
|
||||
updated_by,
|
||||
description,
|
||||
entries: entries as unknown as ArtifactInfo['entries'],
|
||||
os: isTrustedApp(item) ? item.os : getOsFromExceptionItem(item),
|
||||
effectScope: isTrustedApp(item) ? item.effectScope : getEffectScopeFromExceptionItem(item),
|
||||
};
|
||||
return mapToArtifactInfo(item);
|
||||
}, [item]);
|
||||
};
|
||||
|
||||
export const isTrustedApp = (item: AnyArtifact): item is TrustedApp => {
|
||||
return 'effectScope' in item;
|
||||
};
|
||||
|
||||
const getOsFromExceptionItem = (item: ExceptionListItemSchema): string => {
|
||||
// FYI: Exceptions seem to allow for items to be assigned to more than one OS, unlike Event Filters and Trusted Apps
|
||||
return item.os_types.join(', ');
|
||||
};
|
||||
|
||||
const getEffectScopeFromExceptionItem = (item: ExceptionListItemSchema): EffectScope => {
|
||||
return tagsToEffectScope(item.tags);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { EffectScopeProps } from '../components/effect_scope';
|
||||
import { ArtifactInfo, MenuItemPropsByPolicyId } from '../types';
|
||||
import { ContextMenuItemNavByRouterProps } from '../../context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
|
||||
/**
|
||||
* creates the policy links for each policy listed in the artifact record by grabbing the
|
||||
* navigation data from the `policies` prop (if any)
|
||||
*/
|
||||
export const usePolicyNavLinks = (
|
||||
artifact: ArtifactInfo,
|
||||
policies?: MenuItemPropsByPolicyId
|
||||
): ContextMenuItemNavByRouterProps[] | undefined => {
|
||||
return useMemo<EffectScopeProps['policies']>(() => {
|
||||
return artifact.effectScope.type === 'policy'
|
||||
? artifact?.effectScope.policies.map((id) => {
|
||||
return policies && policies[id]
|
||||
? policies[id]
|
||||
: // else, unable to build a nav link, so just show id
|
||||
{
|
||||
children: id,
|
||||
};
|
||||
})
|
||||
: undefined;
|
||||
}, [artifact.effectScope, policies]);
|
||||
};
|
|
@ -7,3 +7,7 @@
|
|||
|
||||
export * from './artifact_entry_card';
|
||||
export * from './artifact_entry_card_minified';
|
||||
export * from './artifact_entry_collapsible_card';
|
||||
export * from './components/card_section_panel';
|
||||
export * from './types';
|
||||
export { CardCompressedHeaderLayout } from './components/card_compressed_header';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { EffectScope, TrustedApp } from '../../../../common/endpoint/types';
|
||||
import { ContextMenuItemNavByRouterProps } from '../context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
|
||||
export type AnyArtifact = ExceptionListItemSchema | TrustedApp;
|
||||
|
||||
|
@ -27,3 +28,7 @@ export interface ArtifactInfo
|
|||
value: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface MenuItemPropsByPolicyId {
|
||||
[policyId: string]: ContextMenuItemNavByRouterProps;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 * from './is_trusted_app';
|
||||
export * from './map_to_artifact_info';
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { AnyArtifact } from '../types';
|
||||
import { TrustedApp } from '../../../../../common/endpoint/types';
|
||||
|
||||
/**
|
||||
* Type guard for `AnyArtifact` to check if it is a trusted app entry
|
||||
* @param item
|
||||
*/
|
||||
export const isTrustedApp = (item: AnyArtifact): item is TrustedApp => {
|
||||
return 'effectScope' in item;
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { AnyArtifact, ArtifactInfo } from '../types';
|
||||
import { EffectScope, MaybeImmutable } from '../../../../../common/endpoint/types';
|
||||
import { tagsToEffectScope } from '../../../../../common/endpoint/service/trusted_apps/mapping';
|
||||
import { isTrustedApp } from './is_trusted_app';
|
||||
|
||||
export const mapToArtifactInfo = (_item: MaybeImmutable<AnyArtifact>): ArtifactInfo => {
|
||||
const item = _item as AnyArtifact;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { name, created_by, created_at, updated_at, updated_by, description = '', entries } = item;
|
||||
|
||||
return {
|
||||
name,
|
||||
created_by,
|
||||
created_at,
|
||||
updated_at,
|
||||
updated_by,
|
||||
description,
|
||||
entries: entries as unknown as ArtifactInfo['entries'],
|
||||
os: isTrustedApp(item) ? item.os : getOsFromExceptionItem(item),
|
||||
effectScope: isTrustedApp(item) ? item.effectScope : getEffectScopeFromExceptionItem(item),
|
||||
};
|
||||
};
|
||||
|
||||
const getOsFromExceptionItem = (item: ExceptionListItemSchema): string => {
|
||||
// FYI: Exceptions seem to allow for items to be assigned to more than one OS, unlike Event Filters and Trusted Apps
|
||||
return item.os_types.join(', ');
|
||||
};
|
||||
|
||||
const getEffectScopeFromExceptionItem = (item: ExceptionListItemSchema): EffectScope => {
|
||||
return tagsToEffectScope(item.tags);
|
||||
};
|
|
@ -15,6 +15,12 @@ export interface ContextMenuItemNavByRouterProps extends EuiContextMenuItemProps
|
|||
navigateAppId?: string;
|
||||
/** Additional options for the navigation action via react-router */
|
||||
navigateOptions?: NavigateToAppOptions;
|
||||
/**
|
||||
* if `true`, the `children` will be wrapped in a `div` that contains CSS Classname `eui-textTruncate`.
|
||||
* **NOTE**: When this component is used in combination with `ContextMenuWithRouterSupport` and `maxWidth`
|
||||
* is set on the menu component, this prop will be overridden
|
||||
*/
|
||||
textTruncate?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
|
@ -23,7 +29,7 @@ export interface ContextMenuItemNavByRouterProps extends EuiContextMenuItemProps
|
|||
* allow navigation to a URL path via React Router
|
||||
*/
|
||||
export const ContextMenuItemNavByRouter = memo<ContextMenuItemNavByRouterProps>(
|
||||
({ navigateAppId, navigateOptions, onClick, children, ...otherMenuItemProps }) => {
|
||||
({ navigateAppId, navigateOptions, onClick, textTruncate, children, ...otherMenuItemProps }) => {
|
||||
const handleOnClickViaNavigateToApp = useNavigateToAppEventHandler(navigateAppId ?? '', {
|
||||
...navigateOptions,
|
||||
onClick,
|
||||
|
@ -34,7 +40,19 @@ export const ContextMenuItemNavByRouter = memo<ContextMenuItemNavByRouterProps>(
|
|||
{...otherMenuItemProps}
|
||||
onClick={navigateAppId ? handleOnClickViaNavigateToApp : onClick}
|
||||
>
|
||||
{children}
|
||||
{textTruncate ? (
|
||||
<div
|
||||
className="eui-textTruncate"
|
||||
{
|
||||
/* Add the html `title` prop if children is a string */
|
||||
...('string' === typeof children ? { title: children } : {})
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
import React, { CSSProperties, HTMLAttributes, memo, useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
CommonProps,
|
||||
EuiContextMenuPanel,
|
||||
|
@ -16,13 +16,19 @@ import {
|
|||
import {
|
||||
ContextMenuItemNavByRouter,
|
||||
ContextMenuItemNavByRouterProps,
|
||||
} from './context_menu_item_nav_by_rotuer';
|
||||
} from './context_menu_item_nav_by_router';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
|
||||
export interface ContextMenuWithRouterSupportProps
|
||||
extends CommonProps,
|
||||
Pick<EuiPopoverProps, 'button' | 'anchorPosition' | 'panelPaddingSize'> {
|
||||
items: ContextMenuItemNavByRouterProps[];
|
||||
/**
|
||||
* The max width for the popup menu. Default is `32ch`.
|
||||
* **Note** that when used (default behaviour), all menu item's `truncateText` prop will be
|
||||
* overwritten to `true`. Setting this prop's value to `undefined` will suppress the default behaviour.
|
||||
*/
|
||||
maxWidth?: CSSProperties['maxWidth'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,7 +37,7 @@ export interface ContextMenuWithRouterSupportProps
|
|||
* Menu also supports automatically closing the popup when an item is clicked.
|
||||
*/
|
||||
export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportProps>(
|
||||
({ items, button, panelPaddingSize, anchorPosition, ...commonProps }) => {
|
||||
({ items, button, panelPaddingSize, anchorPosition, maxWidth = '32ch', ...commonProps }) => {
|
||||
const getTestId = useTestIdGenerator(commonProps['data-test-subj']);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
|
@ -47,6 +53,7 @@ export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportPro
|
|||
return (
|
||||
<ContextMenuItemNavByRouter
|
||||
{...itemProps}
|
||||
textTruncate={Boolean(maxWidth) || itemProps.textTruncate}
|
||||
onClick={(ev) => {
|
||||
handleCloseMenu();
|
||||
if (itemProps.onClick) {
|
||||
|
@ -56,7 +63,20 @@ export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportPro
|
|||
/>
|
||||
);
|
||||
});
|
||||
}, [handleCloseMenu, items]);
|
||||
}, [handleCloseMenu, items, maxWidth]);
|
||||
|
||||
type AdditionalPanelProps = Partial<EuiContextMenuPanelProps & HTMLAttributes<HTMLDivElement>>;
|
||||
const additionalContextMenuPanelProps = useMemo<AdditionalPanelProps>(() => {
|
||||
const newAdditionalProps: AdditionalPanelProps = {
|
||||
style: {},
|
||||
};
|
||||
|
||||
if (maxWidth) {
|
||||
newAdditionalProps.style!.maxWidth = maxWidth;
|
||||
}
|
||||
|
||||
return newAdditionalProps;
|
||||
}, [maxWidth]);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
|
@ -73,7 +93,7 @@ export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportPro
|
|||
isOpen={isOpen}
|
||||
closePopover={handleCloseMenu}
|
||||
>
|
||||
<EuiContextMenuPanel items={menuItems} />
|
||||
<EuiContextMenuPanel {...additionalContextMenuPanelProps} items={menuItems} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
EuiPopoverProps,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ContextMenuItemNavByRouter } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_rotuer';
|
||||
import { ContextMenuItemNavByRouter } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
import { HostMetadata } from '../../../../../../common/endpoint/types';
|
||||
import { useEndpointActionItems } from '../hooks';
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiContextMenuPanel, EuiButton, EuiPopover } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useEndpointActionItems, useEndpointSelector } from '../../hooks';
|
||||
import { detailsData } from '../../../store/selectors';
|
||||
import { ContextMenuItemNavByRouter } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_rotuer';
|
||||
import { ContextMenuItemNavByRouter } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
|
||||
export const ActionsMenu = React.memo<{}>(() => {
|
||||
const endpointDetails = useEndpointSelector(detailsData);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { HostMetadata, MaybeImmutable } from '../../../../../../common/endpoint/
|
|||
import { useEndpointSelector } from './hooks';
|
||||
import { agentPolicies, uiQueryParams } from '../../store/selectors';
|
||||
import { useAppUrl } from '../../../../../common/lib/kibana/hooks';
|
||||
import { ContextMenuItemNavByRouterProps } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_rotuer';
|
||||
import { ContextMenuItemNavByRouterProps } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
import { isEndpointHostIsolated } from '../../../../../common/utils/validators';
|
||||
import { useLicense } from '../../../../../common/hooks/use_license';
|
||||
import { isIsolationSupported } from '../../../../../../common/endpoint/service/host_isolation/utils';
|
||||
|
|
|
@ -5,14 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Action } from 'redux';
|
||||
import { AsyncResourceState } from '../../../../../state';
|
||||
import {
|
||||
PostTrustedAppCreateResponse,
|
||||
GetTrustedListAppsResponse,
|
||||
GetTrustedAppsListResponse,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { PolicyArtifactsState } from '../../../types';
|
||||
|
||||
export interface PolicyArtifactsAssignableListPageDataChanged {
|
||||
type: 'policyArtifactsAssignableListPageDataChanged';
|
||||
payload: AsyncResourceState<GetTrustedListAppsResponse>;
|
||||
payload: AsyncResourceState<GetTrustedAppsListResponse>;
|
||||
}
|
||||
|
||||
export interface PolicyArtifactsUpdateTrustedApps {
|
||||
|
@ -37,9 +40,28 @@ export interface PolicyArtifactsAssignableListPageDataFilter {
|
|||
payload: { filter: string };
|
||||
}
|
||||
|
||||
export interface AssignedTrustedAppsListStateChanged
|
||||
extends Action<'assignedTrustedAppsListStateChanged'> {
|
||||
payload: PolicyArtifactsState['assignedList'];
|
||||
}
|
||||
|
||||
export interface PolicyDetailsListOfAllPoliciesStateChanged
|
||||
extends Action<'policyDetailsListOfAllPoliciesStateChanged'> {
|
||||
payload: PolicyArtifactsState['policies'];
|
||||
}
|
||||
|
||||
export type PolicyDetailsTrustedAppsForceListDataRefresh =
|
||||
Action<'policyDetailsTrustedAppsForceListDataRefresh'>;
|
||||
|
||||
/**
|
||||
* All of the possible actions for Trusted Apps under the Policy Details store
|
||||
*/
|
||||
export type PolicyTrustedAppsAction =
|
||||
| PolicyArtifactsAssignableListPageDataChanged
|
||||
| PolicyArtifactsUpdateTrustedApps
|
||||
| PolicyArtifactsUpdateTrustedAppsChanged
|
||||
| PolicyArtifactsAssignableListExistDataChanged
|
||||
| PolicyArtifactsAssignableListPageDataFilter;
|
||||
| PolicyArtifactsAssignableListPageDataFilter
|
||||
| AssignedTrustedAppsListStateChanged
|
||||
| PolicyDetailsListOfAllPoliciesStateChanged
|
||||
| PolicyDetailsTrustedAppsForceListDataRefresh;
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
|
||||
import { ImmutableMiddlewareFactory } from '../../../../../../common/store';
|
||||
import { PolicyDetailsState } from '../../../types';
|
||||
import { MiddlewareRunnerContext, PolicyDetailsState } from '../../../types';
|
||||
import { policyTrustedAppsMiddlewareRunner } from './policy_trusted_apps_middleware';
|
||||
import { policySettingsMiddlewareRunner } from './policy_settings_middleware';
|
||||
import { TrustedAppsHttpService } from '../../../../trusted_apps/service';
|
||||
|
||||
export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDetailsState> = (
|
||||
coreStart
|
||||
|
@ -16,7 +17,13 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDe
|
|||
return (store) => (next) => async (action) => {
|
||||
next(action);
|
||||
|
||||
policySettingsMiddlewareRunner(coreStart, store, action);
|
||||
policyTrustedAppsMiddlewareRunner(coreStart, store, action);
|
||||
const trustedAppsService = new TrustedAppsHttpService(coreStart.http);
|
||||
const middlewareContext: MiddlewareRunnerContext = {
|
||||
coreStart,
|
||||
trustedAppsService,
|
||||
};
|
||||
|
||||
policySettingsMiddlewareRunner(middlewareContext, store, action);
|
||||
policyTrustedAppsMiddlewareRunner(middlewareContext, store, action);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ import { NewPolicyData, PolicyData } from '../../../../../../../common/endpoint/
|
|||
import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy';
|
||||
|
||||
export const policySettingsMiddlewareRunner: MiddlewareRunner = async (
|
||||
coreStart,
|
||||
{ coreStart },
|
||||
{ dispatch, getState },
|
||||
action
|
||||
) => {
|
||||
|
|
|
@ -7,30 +7,95 @@
|
|||
|
||||
import pMap from 'p-map';
|
||||
import { find, isEmpty } from 'lodash/fp';
|
||||
import { PolicyDetailsState, MiddlewareRunner } from '../../../types';
|
||||
import {
|
||||
PolicyDetailsState,
|
||||
MiddlewareRunner,
|
||||
GetPolicyListResponse,
|
||||
MiddlewareRunnerContext,
|
||||
PolicyAssignedTrustedApps,
|
||||
PolicyDetailsStore,
|
||||
} from '../../../types';
|
||||
import {
|
||||
policyIdFromParams,
|
||||
isOnPolicyTrustedAppsPage,
|
||||
getCurrentArtifactsLocation,
|
||||
getAssignableArtifactsList,
|
||||
doesPolicyTrustedAppsListNeedUpdate,
|
||||
getCurrentPolicyAssignedTrustedAppsState,
|
||||
getLatestLoadedPolicyAssignedTrustedAppsState,
|
||||
getTrustedAppsPolicyListState,
|
||||
isPolicyTrustedAppListLoading,
|
||||
getCurrentArtifactsLocation,
|
||||
isOnPolicyTrustedAppsView,
|
||||
getCurrentUrlLocationPaginationParams,
|
||||
} from '../selectors';
|
||||
import {
|
||||
ImmutableArray,
|
||||
ImmutableObject,
|
||||
PostTrustedAppCreateRequest,
|
||||
TrustedApp,
|
||||
Immutable,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { ImmutableMiddlewareAPI } from '../../../../../../common/store';
|
||||
import { TrustedAppsHttpService, TrustedAppsService } from '../../../../trusted_apps/service';
|
||||
import { TrustedAppsService } from '../../../../trusted_apps/service';
|
||||
import {
|
||||
createLoadedResourceState,
|
||||
createLoadingResourceState,
|
||||
createUninitialisedResourceState,
|
||||
createFailedResourceState,
|
||||
isLoadingResourceState,
|
||||
isUninitialisedResourceState,
|
||||
} from '../../../../../state';
|
||||
import { parseQueryFilterToKQL } from '../../../../../common/utils';
|
||||
import { SEARCHABLE_FIELDS } from '../../../../trusted_apps/constants';
|
||||
import { PolicyDetailsAction } from '../action';
|
||||
import { ServerApiError } from '../../../../../../common/types';
|
||||
|
||||
/** Runs all middleware actions associated with the Trusted Apps view in Policy Details */
|
||||
export const policyTrustedAppsMiddlewareRunner: MiddlewareRunner = async (
|
||||
context,
|
||||
store,
|
||||
action
|
||||
) => {
|
||||
const state = store.getState();
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
If not on the Trusted Apps Policy view, then just return
|
||||
----------------------------------------------------------- */
|
||||
if (!isOnPolicyTrustedAppsView(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { trustedAppsService } = context;
|
||||
|
||||
switch (action.type) {
|
||||
case 'userChangedUrl':
|
||||
fetchPolicyTrustedAppsIfNeeded(context, store);
|
||||
fetchAllPoliciesIfNeeded(context, store);
|
||||
|
||||
if (action.type === 'userChangedUrl' && getCurrentArtifactsLocation(state).show === 'list') {
|
||||
await searchTrustedApps(store, trustedAppsService);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'policyDetailsTrustedAppsForceListDataRefresh':
|
||||
fetchPolicyTrustedAppsIfNeeded(context, store, true);
|
||||
break;
|
||||
|
||||
case 'policyArtifactsUpdateTrustedApps':
|
||||
if (getCurrentArtifactsLocation(state).show === 'list') {
|
||||
await updateTrustedApps(store, trustedAppsService, action.payload.trustedAppIds);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'policyArtifactsAssignableListPageDataFilter':
|
||||
if (getCurrentArtifactsLocation(state).show === 'list') {
|
||||
await searchTrustedApps(store, trustedAppsService, action.payload.filter);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const checkIfThereAreAssignableTrustedApps = async (
|
||||
store: ImmutableMiddlewareAPI<PolicyDetailsState, PolicyDetailsAction>,
|
||||
|
@ -172,6 +237,8 @@ const updateTrustedApps = async (
|
|||
type: 'policyArtifactsUpdateTrustedAppsChanged',
|
||||
payload: createLoadedResourceState(updatedTrustedApps),
|
||||
});
|
||||
|
||||
store.dispatch({ type: 'policyDetailsTrustedAppsForceListDataRefresh' });
|
||||
} catch (err) {
|
||||
store.dispatch({
|
||||
type: 'policyArtifactsUpdateTrustedAppsChanged',
|
||||
|
@ -182,31 +249,89 @@ const updateTrustedApps = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const policyTrustedAppsMiddlewareRunner: MiddlewareRunner = async (
|
||||
coreStart,
|
||||
store,
|
||||
action
|
||||
const fetchPolicyTrustedAppsIfNeeded = async (
|
||||
{ trustedAppsService }: MiddlewareRunnerContext,
|
||||
{ getState, dispatch }: PolicyDetailsStore,
|
||||
forceFetch: boolean = false
|
||||
) => {
|
||||
const http = coreStart.http;
|
||||
const trustedAppsService = new TrustedAppsHttpService(http);
|
||||
const state = store.getState();
|
||||
if (
|
||||
action.type === 'userChangedUrl' &&
|
||||
isOnPolicyTrustedAppsPage(state) &&
|
||||
getCurrentArtifactsLocation(state).show === 'list'
|
||||
) {
|
||||
await searchTrustedApps(store, trustedAppsService);
|
||||
} else if (
|
||||
action.type === 'policyArtifactsUpdateTrustedApps' &&
|
||||
isOnPolicyTrustedAppsPage(state) &&
|
||||
getCurrentArtifactsLocation(state).show === 'list'
|
||||
) {
|
||||
await updateTrustedApps(store, trustedAppsService, action.payload.trustedAppIds);
|
||||
} else if (
|
||||
action.type === 'policyArtifactsAssignableListPageDataFilter' &&
|
||||
isOnPolicyTrustedAppsPage(state) &&
|
||||
getCurrentArtifactsLocation(state).show === 'list'
|
||||
) {
|
||||
await searchTrustedApps(store, trustedAppsService, action.payload.filter);
|
||||
const state = getState();
|
||||
|
||||
if (isPolicyTrustedAppListLoading(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forceFetch || doesPolicyTrustedAppsListNeedUpdate(state)) {
|
||||
dispatch({
|
||||
type: 'assignedTrustedAppsListStateChanged',
|
||||
// @ts-ignore will be fixed when AsyncResourceState is refactored (#830)
|
||||
payload: createLoadingResourceState(getCurrentPolicyAssignedTrustedAppsState(state)),
|
||||
});
|
||||
|
||||
try {
|
||||
const urlLocationData = getCurrentUrlLocationPaginationParams(state);
|
||||
const policyId = policyIdFromParams(state);
|
||||
const fetchResponse = await trustedAppsService.getTrustedAppsList({
|
||||
page: urlLocationData.page_index + 1,
|
||||
per_page: urlLocationData.page_size,
|
||||
kuery: `((exception-list-agnostic.attributes.tags:"policy:${policyId}") OR (exception-list-agnostic.attributes.tags:"policy:all"))${
|
||||
urlLocationData.filter ? ` AND (${urlLocationData.filter})` : ''
|
||||
}`,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'assignedTrustedAppsListStateChanged',
|
||||
payload: createLoadedResourceState<Immutable<PolicyAssignedTrustedApps>>({
|
||||
location: urlLocationData,
|
||||
artifacts: fetchResponse,
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'assignedTrustedAppsListStateChanged',
|
||||
payload: createFailedResourceState<Immutable<PolicyAssignedTrustedApps>>(
|
||||
error as ServerApiError,
|
||||
getLatestLoadedPolicyAssignedTrustedAppsState(getState())
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAllPoliciesIfNeeded = async (
|
||||
{ trustedAppsService }: MiddlewareRunnerContext,
|
||||
{ getState, dispatch }: PolicyDetailsStore
|
||||
) => {
|
||||
const state = getState();
|
||||
const currentPoliciesState = getTrustedAppsPolicyListState(state);
|
||||
const isLoading = isLoadingResourceState(currentPoliciesState);
|
||||
const hasBeenLoaded = !isUninitialisedResourceState(currentPoliciesState);
|
||||
|
||||
if (isLoading || hasBeenLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'policyDetailsListOfAllPoliciesStateChanged',
|
||||
// @ts-ignore will be fixed when AsyncResourceState is refactored (#830)
|
||||
payload: createLoadingResourceState(currentPoliciesState),
|
||||
});
|
||||
|
||||
try {
|
||||
const policyList = await trustedAppsService.getPolicyList({
|
||||
query: {
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'policyDetailsListOfAllPoliciesStateChanged',
|
||||
payload: createLoadedResourceState(policyList),
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'policyDetailsListOfAllPoliciesStateChanged',
|
||||
payload: createFailedResourceState<GetPolicyListResponse>(error.body || error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -37,5 +37,7 @@ export const initialPolicyDetailsState: () => Immutable<PolicyDetailsState> = ()
|
|||
assignableList: createUninitialisedResourceState(),
|
||||
trustedAppsToUpdate: createUninitialisedResourceState(),
|
||||
assignableListEntriesExist: createUninitialisedResourceState(),
|
||||
assignedList: createUninitialisedResourceState(),
|
||||
policies: createUninitialisedResourceState(),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { PolicyDetailsState } from '../../../types';
|
||||
import { initialPolicyDetailsState } from '../reducer/initial_policy_details_state';
|
||||
import { initialPolicyDetailsState } from './initial_policy_details_state';
|
||||
import { policyTrustedAppsReducer } from './trusted_apps_reducer';
|
||||
|
||||
import { ImmutableObject } from '../../../../../../../common/endpoint/types';
|
||||
|
@ -16,12 +16,20 @@ import {
|
|||
createFailedResourceState,
|
||||
} from '../../../../../state';
|
||||
import { getMockListResponse, getAPIError, getMockCreateResponse } from '../../../test_utils';
|
||||
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
|
||||
|
||||
describe('policy trusted apps reducer', () => {
|
||||
let initialState: ImmutableObject<PolicyDetailsState>;
|
||||
|
||||
beforeEach(() => {
|
||||
initialState = initialPolicyDetailsState();
|
||||
initialState = {
|
||||
...initialPolicyDetailsState(),
|
||||
location: {
|
||||
pathname: getPolicyDetailsArtifactsListPath('abc'),
|
||||
search: '',
|
||||
hash: '',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('PolicyTrustedApps', () => {
|
||||
|
|
|
@ -9,11 +9,28 @@ import { ImmutableReducer } from '../../../../../../common/store';
|
|||
import { PolicyDetailsState } from '../../../types';
|
||||
import { AppAction } from '../../../../../../common/store/actions';
|
||||
import { initialPolicyDetailsState } from './initial_policy_details_state';
|
||||
import { isUninitialisedResourceState } from '../../../../../state';
|
||||
import { getCurrentPolicyAssignedTrustedAppsState, isOnPolicyTrustedAppsView } from '../selectors';
|
||||
|
||||
export const policyTrustedAppsReducer: ImmutableReducer<PolicyDetailsState, AppAction> = (
|
||||
state = initialPolicyDetailsState(),
|
||||
action
|
||||
) => {
|
||||
/* ----------------------------------------------------------
|
||||
If not on the Trusted Apps Policy view, then just return
|
||||
---------------------------------------------------------- */
|
||||
if (!isOnPolicyTrustedAppsView(state)) {
|
||||
// If the artifacts state namespace needs resetting, then do it now
|
||||
if (!isUninitialisedResourceState(getCurrentPolicyAssignedTrustedAppsState(state))) {
|
||||
return {
|
||||
...state,
|
||||
artifacts: initialPolicyDetailsState().artifacts,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
if (action.type === 'policyArtifactsAssignableListPageDataChanged') {
|
||||
return {
|
||||
...state,
|
||||
|
@ -44,5 +61,25 @@ export const policyTrustedAppsReducer: ImmutableReducer<PolicyDetailsState, AppA
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === 'assignedTrustedAppsListStateChanged') {
|
||||
return {
|
||||
...state,
|
||||
artifacts: {
|
||||
...state?.artifacts,
|
||||
assignedList: action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === 'policyDetailsListOfAllPoliciesStateChanged') {
|
||||
return {
|
||||
...state,
|
||||
artifacts: {
|
||||
...state.artifacts,
|
||||
policies: action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
|
|
@ -7,3 +7,4 @@
|
|||
|
||||
export * from './policy_settings_selectors';
|
||||
export * from './trusted_apps_selectors';
|
||||
export * from './policy_common_selectors';
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { matchPath } from 'react-router-dom';
|
||||
import { createSelector } from 'reselect';
|
||||
import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types';
|
||||
import {
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
} from '../../../../../common/constants';
|
||||
|
||||
/**
|
||||
* Returns current artifacts location
|
||||
*/
|
||||
export const getCurrentArtifactsLocation: PolicyDetailsSelector<
|
||||
PolicyDetailsState['artifacts']['location']
|
||||
> = (state) => state.artifacts.location;
|
||||
|
||||
export const getUrlLocationPathname: PolicyDetailsSelector<string | undefined> = (state) =>
|
||||
state.location?.pathname;
|
||||
|
||||
/** Returns a boolean of whether the user is on the policy form page or not */
|
||||
export const isOnPolicyFormView: PolicyDetailsSelector<boolean> = createSelector(
|
||||
getUrlLocationPathname,
|
||||
(pathname) => {
|
||||
return (
|
||||
matchPath(pathname ?? '', {
|
||||
path: MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
exact: true,
|
||||
}) !== null
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/** Returns a boolean of whether the user is on the policy details page or not */
|
||||
export const isOnPolicyTrustedAppsView: PolicyDetailsSelector<boolean> = createSelector(
|
||||
getUrlLocationPathname,
|
||||
(pathname) => {
|
||||
return (
|
||||
matchPath(pathname ?? '', {
|
||||
path: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
exact: true,
|
||||
}) !== null
|
||||
);
|
||||
}
|
||||
);
|
|
@ -23,8 +23,8 @@ import {
|
|||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
} from '../../../../../common/constants';
|
||||
import { ManagementRoutePolicyDetailsParams } from '../../../../../types';
|
||||
import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy/get_policy_data_for_update';
|
||||
import { isOnPolicyTrustedAppsPage } from './trusted_apps_selectors';
|
||||
import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy';
|
||||
import { isOnPolicyTrustedAppsView, isOnPolicyFormView } from './policy_common_selectors';
|
||||
|
||||
/** Returns the policy details */
|
||||
export const policyDetails = (state: Immutable<PolicyDetailsState>) => state.policyItem;
|
||||
|
@ -81,19 +81,9 @@ export const needsToRefresh = (state: Immutable<PolicyDetailsState>): boolean =>
|
|||
return !state.policyItem && !state.apiError;
|
||||
};
|
||||
|
||||
/** Returns a boolean of whether the user is on the policy form page or not */
|
||||
export const isOnPolicyFormPage = (state: Immutable<PolicyDetailsState>) => {
|
||||
return (
|
||||
matchPath(state.location?.pathname ?? '', {
|
||||
path: MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
exact: true,
|
||||
}) !== null
|
||||
);
|
||||
};
|
||||
|
||||
/** Returns a boolean of whether the user is on some of the policy details page or not */
|
||||
export const isOnPolicyDetailsPage = (state: Immutable<PolicyDetailsState>) =>
|
||||
isOnPolicyFormPage(state) || isOnPolicyTrustedAppsPage(state);
|
||||
isOnPolicyFormView(state) || isOnPolicyTrustedAppsView(state);
|
||||
|
||||
/** Returns the license info fetched from the license service */
|
||||
export const license = (state: Immutable<PolicyDetailsState>) => {
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import { PolicyDetailsState } from '../../../types';
|
||||
import { initialPolicyDetailsState } from '../reducer/initial_policy_details_state';
|
||||
import { initialPolicyDetailsState } from '../reducer';
|
||||
import {
|
||||
getCurrentArtifactsLocation,
|
||||
getAssignableArtifactsList,
|
||||
getAssignableArtifactsListIsLoading,
|
||||
getUpdateArtifactsIsLoading,
|
||||
|
@ -17,8 +16,8 @@ import {
|
|||
getAssignableArtifactsListExist,
|
||||
getAssignableArtifactsListExistIsLoading,
|
||||
getUpdateArtifacts,
|
||||
isOnPolicyTrustedAppsPage,
|
||||
} from './trusted_apps_selectors';
|
||||
import { getCurrentArtifactsLocation, isOnPolicyTrustedAppsView } from './policy_common_selectors';
|
||||
|
||||
import { ImmutableObject } from '../../../../../../../common/endpoint/types';
|
||||
import {
|
||||
|
@ -39,7 +38,7 @@ describe('policy trusted apps selectors', () => {
|
|||
|
||||
describe('isOnPolicyTrustedAppsPage()', () => {
|
||||
it('when location is on policy trusted apps page', () => {
|
||||
const isOnPage = isOnPolicyTrustedAppsPage({
|
||||
const isOnPage = isOnPolicyTrustedAppsView({
|
||||
...initialState,
|
||||
location: {
|
||||
pathname: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
|
@ -50,7 +49,7 @@ describe('policy trusted apps selectors', () => {
|
|||
expect(isOnPage).toBeFalsy();
|
||||
});
|
||||
it('when location is not on policy trusted apps page', () => {
|
||||
const isOnPage = isOnPolicyTrustedAppsPage({
|
||||
const isOnPage = isOnPolicyTrustedAppsView({
|
||||
...initialState,
|
||||
location: { pathname: '', search: '', hash: '' },
|
||||
});
|
||||
|
|
|
@ -5,35 +5,48 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import { PolicyDetailsArtifactsPageLocation, PolicyDetailsState } from '../../../types';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Pagination } from '@elastic/eui';
|
||||
import {
|
||||
PolicyArtifactsState,
|
||||
PolicyAssignedTrustedApps,
|
||||
PolicyDetailsArtifactsPageListLocationParams,
|
||||
PolicyDetailsSelector,
|
||||
PolicyDetailsState,
|
||||
} from '../../../types';
|
||||
import {
|
||||
Immutable,
|
||||
ImmutableArray,
|
||||
PostTrustedAppCreateResponse,
|
||||
GetTrustedListAppsResponse,
|
||||
GetTrustedAppsListResponse,
|
||||
PolicyData,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH } from '../../../../../common/constants';
|
||||
import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../../../common/constants';
|
||||
import {
|
||||
getLastLoadedResourceState,
|
||||
isFailedResourceState,
|
||||
isLoadedResourceState,
|
||||
isLoadingResourceState,
|
||||
LoadedResourceState,
|
||||
} from '../../../../../state';
|
||||
import { getCurrentArtifactsLocation } from './policy_common_selectors';
|
||||
|
||||
/**
|
||||
* Returns current artifacts location
|
||||
*/
|
||||
export const getCurrentArtifactsLocation = (
|
||||
state: Immutable<PolicyDetailsState>
|
||||
): Immutable<PolicyDetailsArtifactsPageLocation> => state.artifacts.location;
|
||||
export const doesPolicyHaveTrustedApps = (
|
||||
state: PolicyDetailsState
|
||||
): { loading: boolean; hasTrustedApps: boolean } => {
|
||||
// TODO: implement empty state (task #1645)
|
||||
return {
|
||||
loading: false,
|
||||
hasTrustedApps: true,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns current assignable artifacts list
|
||||
*/
|
||||
export const getAssignableArtifactsList = (
|
||||
state: Immutable<PolicyDetailsState>
|
||||
): Immutable<GetTrustedListAppsResponse> | undefined =>
|
||||
): Immutable<GetTrustedAppsListResponse> | undefined =>
|
||||
getLastLoadedResourceState(state.artifacts.assignableList)?.data;
|
||||
|
||||
/**
|
||||
|
@ -92,12 +105,79 @@ export const getUpdateArtifacts = (
|
|||
: undefined;
|
||||
};
|
||||
|
||||
/** Returns a boolean of whether the user is on the policy details page or not */
|
||||
export const isOnPolicyTrustedAppsPage = (state: Immutable<PolicyDetailsState>) => {
|
||||
return (
|
||||
matchPath(state.location?.pathname ?? '', {
|
||||
path: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
exact: true,
|
||||
}) !== null
|
||||
);
|
||||
export const getCurrentPolicyAssignedTrustedAppsState: PolicyDetailsSelector<
|
||||
PolicyArtifactsState['assignedList']
|
||||
> = (state) => {
|
||||
return state.artifacts.assignedList;
|
||||
};
|
||||
|
||||
export const getLatestLoadedPolicyAssignedTrustedAppsState: PolicyDetailsSelector<
|
||||
undefined | LoadedResourceState<PolicyAssignedTrustedApps>
|
||||
> = createSelector(getCurrentPolicyAssignedTrustedAppsState, (currentAssignedTrustedAppsState) => {
|
||||
return getLastLoadedResourceState(currentAssignedTrustedAppsState);
|
||||
});
|
||||
|
||||
export const getCurrentUrlLocationPaginationParams: PolicyDetailsSelector<PolicyDetailsArtifactsPageListLocationParams> =
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
createSelector(getCurrentArtifactsLocation, ({ filter, page_index, page_size }) => {
|
||||
return { filter, page_index, page_size };
|
||||
});
|
||||
|
||||
export const doesPolicyTrustedAppsListNeedUpdate: PolicyDetailsSelector<boolean> = createSelector(
|
||||
getCurrentPolicyAssignedTrustedAppsState,
|
||||
getCurrentUrlLocationPaginationParams,
|
||||
(assignedListState, locationData) => {
|
||||
return (
|
||||
!isLoadedResourceState(assignedListState) ||
|
||||
(isLoadedResourceState(assignedListState) &&
|
||||
(
|
||||
Object.keys(locationData) as Array<keyof PolicyDetailsArtifactsPageListLocationParams>
|
||||
).some((key) => assignedListState.data.location[key] !== locationData[key]))
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const isPolicyTrustedAppListLoading: PolicyDetailsSelector<boolean> = createSelector(
|
||||
getCurrentPolicyAssignedTrustedAppsState,
|
||||
(assignedState) => isLoadingResourceState(assignedState)
|
||||
);
|
||||
|
||||
export const getPolicyTrustedAppList: PolicyDetailsSelector<GetTrustedAppsListResponse['data']> =
|
||||
createSelector(getLatestLoadedPolicyAssignedTrustedAppsState, (assignedState) => {
|
||||
return assignedState?.data.artifacts.data ?? [];
|
||||
});
|
||||
|
||||
export const getPolicyTrustedAppsListPagination: PolicyDetailsSelector<Pagination> = createSelector(
|
||||
getLatestLoadedPolicyAssignedTrustedAppsState,
|
||||
(currentAssignedTrustedAppsState) => {
|
||||
const trustedAppsApiResponse = currentAssignedTrustedAppsState?.data.artifacts;
|
||||
|
||||
return {
|
||||
// Trusted apps api is `1` based for page - need to subtract here for `Pagination` component
|
||||
pageIndex: trustedAppsApiResponse?.page ? trustedAppsApiResponse.page - 1 : 0,
|
||||
pageSize: trustedAppsApiResponse?.per_page ?? MANAGEMENT_PAGE_SIZE_OPTIONS[0],
|
||||
totalItemCount: trustedAppsApiResponse?.total || 0,
|
||||
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const getTrustedAppsPolicyListState: PolicyDetailsSelector<
|
||||
PolicyDetailsState['artifacts']['policies']
|
||||
> = (state) => state.artifacts.policies;
|
||||
|
||||
export const getTrustedAppsListOfAllPolicies: PolicyDetailsSelector<PolicyData[]> = createSelector(
|
||||
getTrustedAppsPolicyListState,
|
||||
(policyListState) => {
|
||||
return getLastLoadedResourceState(policyListState)?.data.items ?? [];
|
||||
}
|
||||
);
|
||||
|
||||
export const getTrustedAppsAllPoliciesById: PolicyDetailsSelector<
|
||||
Record<string, Immutable<PolicyData>>
|
||||
> = createSelector(getTrustedAppsListOfAllPolicies, (allPolicies) => {
|
||||
return allPolicies.reduce<Record<string, Immutable<PolicyData>>>((mapById, policy) => {
|
||||
mapById[policy.id] = policy;
|
||||
return mapById;
|
||||
}, {}) as Immutable<Record<string, Immutable<PolicyData>>>;
|
||||
});
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
GetTrustedListAppsResponse,
|
||||
GetTrustedAppsListResponse,
|
||||
PostTrustedAppCreateResponse,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
|
||||
import { createSampleTrustedApps, createSampleTrustedApp } from '../../trusted_apps/test_utils';
|
||||
|
||||
export const getMockListResponse: () => GetTrustedListAppsResponse = () => ({
|
||||
export const getMockListResponse: () => GetTrustedAppsListResponse = () => ({
|
||||
data: createSampleTrustedApps({}),
|
||||
per_page: 100,
|
||||
page: 1,
|
||||
|
|
|
@ -14,58 +14,41 @@ import {
|
|||
PolicyData,
|
||||
UIPolicyConfig,
|
||||
PostTrustedAppCreateResponse,
|
||||
GetTrustedListAppsResponse,
|
||||
MaybeImmutable,
|
||||
GetTrustedAppsListResponse,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { ServerApiError } from '../../../common/types';
|
||||
import {
|
||||
GetAgentStatusResponse,
|
||||
GetOnePackagePolicyResponse,
|
||||
GetPackagePoliciesResponse,
|
||||
GetPackagesResponse,
|
||||
UpdatePackagePolicyResponse,
|
||||
} from '../../../../../fleet/common';
|
||||
import { AsyncResourceState } from '../../state';
|
||||
import { ImmutableMiddlewareAPI } from '../../../common/store';
|
||||
import { AppAction } from '../../../common/store/actions';
|
||||
import { TrustedAppsService } from '../trusted_apps/service';
|
||||
|
||||
export type PolicyDetailsStore = ImmutableMiddlewareAPI<PolicyDetailsState, AppAction>;
|
||||
|
||||
/**
|
||||
* Function that runs Policy Details middleware
|
||||
*/
|
||||
export type MiddlewareRunner = (
|
||||
coreStart: CoreStart,
|
||||
store: ImmutableMiddlewareAPI<PolicyDetailsState, AppAction>,
|
||||
context: MiddlewareRunnerContext,
|
||||
store: PolicyDetailsStore,
|
||||
action: MaybeImmutable<AppAction>
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Policy list store state
|
||||
*/
|
||||
export interface PolicyListState {
|
||||
/** Array of policy items */
|
||||
policyItems: PolicyData[];
|
||||
/** Information about the latest endpoint package */
|
||||
endpointPackageInfo?: GetPackagesResponse['response'][0];
|
||||
/** API error if loading data failed */
|
||||
apiError?: ServerApiError;
|
||||
/** total number of policies */
|
||||
total: number;
|
||||
/** Number of policies per page */
|
||||
pageSize: number;
|
||||
/** page number (zero based) */
|
||||
pageIndex: number;
|
||||
/** data is being retrieved from server */
|
||||
isLoading: boolean;
|
||||
/** current location information */
|
||||
location?: Immutable<AppLocation>;
|
||||
/** policy is being deleted */
|
||||
isDeleting: boolean;
|
||||
/** Deletion status */
|
||||
deleteStatus?: boolean;
|
||||
/** A summary of stats for the agents associated with a given Fleet Agent Policy */
|
||||
agentStatusSummary?: GetAgentStatusResponse['results'];
|
||||
export interface MiddlewareRunnerContext {
|
||||
coreStart: CoreStart;
|
||||
trustedAppsService: TrustedAppsService;
|
||||
}
|
||||
|
||||
export type PolicyDetailsSelector<T = unknown> = (
|
||||
state: Immutable<PolicyDetailsState>
|
||||
) => Immutable<T>;
|
||||
|
||||
/**
|
||||
* Policy details store state
|
||||
*/
|
||||
|
@ -90,6 +73,11 @@ export interface PolicyDetailsState {
|
|||
license?: ILicense;
|
||||
}
|
||||
|
||||
export interface PolicyAssignedTrustedApps {
|
||||
location: PolicyDetailsArtifactsPageListLocationParams;
|
||||
artifacts: GetTrustedAppsListResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Policy artifacts store state
|
||||
*/
|
||||
|
@ -97,11 +85,15 @@ export interface PolicyArtifactsState {
|
|||
/** artifacts location params */
|
||||
location: PolicyDetailsArtifactsPageLocation;
|
||||
/** A list of artifacts can be linked to the policy */
|
||||
assignableList: AsyncResourceState<GetTrustedListAppsResponse>;
|
||||
/** Represents if avaialble trusted apps entries exist, regardless of whether the list is showing results */
|
||||
assignableList: AsyncResourceState<GetTrustedAppsListResponse>;
|
||||
/** Represents if available trusted apps entries exist, regardless of whether the list is showing results */
|
||||
assignableListEntriesExist: AsyncResourceState<boolean>;
|
||||
/** A list of trusted apps going to be updated */
|
||||
trustedAppsToUpdate: AsyncResourceState<PostTrustedAppCreateResponse[]>;
|
||||
/** List of artifacts currently assigned to the policy (body specific and global) */
|
||||
assignedList: AsyncResourceState<PolicyAssignedTrustedApps>;
|
||||
/** A list of all available polices */
|
||||
policies: AsyncResourceState<GetPolicyListResponse>;
|
||||
}
|
||||
|
||||
export enum OS {
|
||||
|
@ -110,13 +102,17 @@ export enum OS {
|
|||
linux = 'linux',
|
||||
}
|
||||
|
||||
export interface PolicyDetailsArtifactsPageLocation {
|
||||
export interface PolicyDetailsArtifactsPageListLocationParams {
|
||||
page_index: number;
|
||||
page_size: number;
|
||||
show?: 'list';
|
||||
filter: string;
|
||||
}
|
||||
|
||||
export interface PolicyDetailsArtifactsPageLocation
|
||||
extends PolicyDetailsArtifactsPageListLocationParams {
|
||||
show?: 'list';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keys of an object whose values meet a criteria.
|
||||
* Ex) interface largeNestedObject = {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
GetTrustedListAppsResponse,
|
||||
GetTrustedAppsListResponse,
|
||||
Immutable,
|
||||
TrustedApp,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
|
@ -16,7 +16,7 @@ import { Loader } from '../../../../../../common/components/loader';
|
|||
import { ArtifactEntryCardMinified } from '../../../../../components/artifact_entry_card';
|
||||
|
||||
export interface PolicyArtifactsAssignableListProps {
|
||||
artifacts: Immutable<GetTrustedListAppsResponse | undefined>; // Or other artifacts type like Event Filters or Endpoint Exceptions
|
||||
artifacts: Immutable<GetTrustedAppsListResponse | undefined>; // Or other artifacts type like Event Filters or Endpoint Exceptions
|
||||
selectedArtifactIds: string[];
|
||||
selectedArtifactsUpdated: (id: string, selected: boolean) => void;
|
||||
isListLoading: boolean;
|
||||
|
|
|
@ -12,8 +12,8 @@ import { EuiTabbedContent, EuiSpacer, EuiTabbedContentTab } from '@elastic/eui';
|
|||
|
||||
import { usePolicyDetailsSelector } from '../policy_hooks';
|
||||
import {
|
||||
isOnPolicyFormPage,
|
||||
isOnPolicyTrustedAppsPage,
|
||||
isOnPolicyFormView,
|
||||
isOnPolicyTrustedAppsView,
|
||||
policyIdFromParams,
|
||||
} from '../../store/policy_details/selectors';
|
||||
|
||||
|
@ -23,8 +23,8 @@ import { getPolicyDetailPath, getPolicyTrustedAppsPath } from '../../../../commo
|
|||
|
||||
export const PolicyTabs = React.memo(() => {
|
||||
const history = useHistory();
|
||||
const isInSettingsTab = usePolicyDetailsSelector(isOnPolicyFormPage);
|
||||
const isInTrustedAppsTab = usePolicyDetailsSelector(isOnPolicyTrustedAppsPage);
|
||||
const isInSettingsTab = usePolicyDetailsSelector(isOnPolicyFormView);
|
||||
const isInTrustedAppsTab = usePolicyDetailsSelector(isOnPolicyTrustedAppsView);
|
||||
const policyId = usePolicyDetailsSelector(policyIdFromParams);
|
||||
|
||||
const tabs = useMemo(
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { PolicyTrustedAppsLayout } from './layout';
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { getCurrentArtifactsLocation } from '../../../store/policy_details/selectors';
|
||||
import { usePolicyDetailsNavigateCallback, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import { PolicyTrustedAppsFlyout } from '../flyout';
|
||||
import { PolicyTrustedAppsList } from '../list/policy_trusted_apps_list';
|
||||
|
||||
export const PolicyTrustedAppsLayout = React.memo(() => {
|
||||
const location = usePolicyDetailsSelector(getCurrentArtifactsLocation);
|
||||
|
@ -67,8 +68,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
|
|||
color="transparent"
|
||||
borderRadius="none"
|
||||
>
|
||||
{/* TODO: To be implemented */}
|
||||
{'Policy trusted apps layout content'}
|
||||
<PolicyTrustedAppsList />
|
||||
</EuiPageContent>
|
||||
{showListFlyout ? <PolicyTrustedAppsFlyout /> : null}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { EuiLoadingSpinner, EuiSpacer, EuiText, Pagination } from '@elastic/eui';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
ArtifactCardGrid,
|
||||
ArtifactCardGridCardComponentProps,
|
||||
ArtifactCardGridProps,
|
||||
} from '../../../../../components/artifact_card_grid';
|
||||
import { usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import {
|
||||
doesPolicyHaveTrustedApps,
|
||||
getCurrentArtifactsLocation,
|
||||
getPolicyTrustedAppList,
|
||||
getPolicyTrustedAppsListPagination,
|
||||
getTrustedAppsAllPoliciesById,
|
||||
isPolicyTrustedAppListLoading,
|
||||
policyIdFromParams,
|
||||
} from '../../../store/policy_details/selectors';
|
||||
import {
|
||||
getPolicyDetailPath,
|
||||
getPolicyDetailsArtifactsListPath,
|
||||
getTrustedAppsListPath,
|
||||
} from '../../../../../common/routing';
|
||||
import { Immutable, TrustedApp } from '../../../../../../../common/endpoint/types';
|
||||
import { useAppUrl } from '../../../../../../common/lib/kibana';
|
||||
import { APP_ID } from '../../../../../../../common/constants';
|
||||
import { ContextMenuItemNavByRouterProps } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card';
|
||||
|
||||
export const PolicyTrustedAppsList = memo(() => {
|
||||
const history = useHistory();
|
||||
const { getAppUrl } = useAppUrl();
|
||||
const policyId = usePolicyDetailsSelector(policyIdFromParams);
|
||||
const hasTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps);
|
||||
const isLoading = usePolicyDetailsSelector(isPolicyTrustedAppListLoading);
|
||||
const trustedAppItems = usePolicyDetailsSelector(getPolicyTrustedAppList);
|
||||
const pagination = usePolicyDetailsSelector(getPolicyTrustedAppsListPagination);
|
||||
const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation);
|
||||
const allPoliciesById = usePolicyDetailsSelector(getTrustedAppsAllPoliciesById);
|
||||
|
||||
const [isCardExpanded, setCardExpanded] = useState<Record<string, boolean>>({});
|
||||
|
||||
// TODO:PT show load errors if any
|
||||
|
||||
const handlePageChange = useCallback<ArtifactCardGridProps['onPageChange']>(
|
||||
({ pageIndex, pageSize }) => {
|
||||
history.push(
|
||||
getPolicyDetailsArtifactsListPath(policyId, {
|
||||
...urlParams,
|
||||
// If user changed page size, then reset page index back to the first page
|
||||
page_index: pageSize !== pagination.pageSize ? 0 : pageIndex,
|
||||
page_size: pageSize,
|
||||
})
|
||||
);
|
||||
},
|
||||
[history, pagination.pageSize, policyId, urlParams]
|
||||
);
|
||||
|
||||
const handleExpandCollapse = useCallback<ArtifactCardGridProps['onExpandCollapse']>(
|
||||
({ expanded, collapsed }) => {
|
||||
const newCardExpandedSettings: Record<string, boolean> = {};
|
||||
|
||||
for (const trustedApp of expanded) {
|
||||
newCardExpandedSettings[trustedApp.id] = true;
|
||||
}
|
||||
|
||||
for (const trustedApp of collapsed) {
|
||||
newCardExpandedSettings[trustedApp.id] = false;
|
||||
}
|
||||
|
||||
setCardExpanded(newCardExpandedSettings);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const totalItemsCountLabel = useMemo<string>(() => {
|
||||
return i18n.translate('xpack.securitySolution.endpoint.policy.trustedApps.list.totalCount', {
|
||||
defaultMessage:
|
||||
'Showing {totalItemsCount, plural, one {# trusted application} other {# trusted applications}}',
|
||||
values: { totalItemsCount: pagination.totalItemCount },
|
||||
});
|
||||
}, [pagination.totalItemCount]);
|
||||
|
||||
const cardProps = useMemo<Map<Immutable<TrustedApp>, ArtifactCardGridCardComponentProps>>(() => {
|
||||
const newCardProps = new Map();
|
||||
|
||||
for (const trustedApp of trustedAppItems) {
|
||||
const viewUrlPath = getTrustedAppsListPath({ id: trustedApp.id, show: 'edit' });
|
||||
const assignedPoliciesMenuItems: ArtifactEntryCollapsibleCardProps['policies'] =
|
||||
trustedApp.effectScope.type === 'global'
|
||||
? undefined
|
||||
: trustedApp.effectScope.policies.reduce<
|
||||
Required<ArtifactEntryCollapsibleCardProps>['policies']
|
||||
>((byIdPolicies, trustedAppAssignedPolicyId) => {
|
||||
if (!allPoliciesById[trustedAppAssignedPolicyId]) {
|
||||
byIdPolicies[trustedAppAssignedPolicyId] = { children: trustedAppAssignedPolicyId };
|
||||
return byIdPolicies;
|
||||
}
|
||||
|
||||
const policyDetailsPath = getPolicyDetailPath(trustedAppAssignedPolicyId);
|
||||
|
||||
const thisPolicyMenuProps: ContextMenuItemNavByRouterProps = {
|
||||
navigateAppId: APP_ID,
|
||||
navigateOptions: {
|
||||
path: policyDetailsPath,
|
||||
},
|
||||
href: getAppUrl({ path: policyDetailsPath }),
|
||||
children: allPoliciesById[trustedAppAssignedPolicyId].name,
|
||||
};
|
||||
|
||||
byIdPolicies[trustedAppAssignedPolicyId] = thisPolicyMenuProps;
|
||||
|
||||
return byIdPolicies;
|
||||
}, {});
|
||||
|
||||
const thisTrustedAppCardProps: ArtifactCardGridCardComponentProps = {
|
||||
expanded: Boolean(isCardExpanded[trustedApp.id]),
|
||||
actions: [
|
||||
{
|
||||
icon: 'controlsHorizontal',
|
||||
children: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction',
|
||||
{ defaultMessage: 'View full details' }
|
||||
),
|
||||
href: getAppUrl({ appId: APP_ID, path: viewUrlPath }),
|
||||
navigateAppId: APP_ID,
|
||||
navigateOptions: { path: viewUrlPath },
|
||||
},
|
||||
],
|
||||
policies: assignedPoliciesMenuItems,
|
||||
};
|
||||
|
||||
newCardProps.set(trustedApp, thisTrustedAppCardProps);
|
||||
}
|
||||
|
||||
return newCardProps;
|
||||
}, [allPoliciesById, getAppUrl, isCardExpanded, trustedAppItems]);
|
||||
|
||||
const provideCardProps = useCallback<Required<ArtifactCardGridProps>['cardComponentProps']>(
|
||||
(item) => {
|
||||
return cardProps.get(item as Immutable<TrustedApp>)!;
|
||||
},
|
||||
[cardProps]
|
||||
);
|
||||
|
||||
// Anytime a new set of data (trusted apps) is retrieved, reset the card expand state
|
||||
useEffect(() => {
|
||||
setCardExpanded({});
|
||||
}, [trustedAppItems]);
|
||||
|
||||
if (hasTrustedApps.loading) {
|
||||
return (
|
||||
<div>
|
||||
<EuiLoadingSpinner className="essentialAnimation" size="xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasTrustedApps.hasTrustedApps) {
|
||||
// TODO: implement empty state (task #1645)
|
||||
return <div>{'No trusted application'}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText color="subdued" size="xs" data-test-subj="policyDetailsTrustedAppsCount">
|
||||
{totalItemsCountLabel}
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<ArtifactCardGrid
|
||||
items={trustedAppItems}
|
||||
onPageChange={handlePageChange}
|
||||
onExpandCollapse={handleExpandCollapse}
|
||||
cardComponentProps={provideCardProps}
|
||||
loading={isLoading}
|
||||
pagination={pagination as Pagination}
|
||||
data-test-subj="policyTrustedAppsGrid"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
PolicyTrustedAppsList.displayName = 'PolicyTrustedAppsList';
|
|
@ -18,7 +18,7 @@ import {
|
|||
|
||||
import {
|
||||
DeleteTrustedAppsRequestParams,
|
||||
GetTrustedListAppsResponse,
|
||||
GetTrustedAppsListResponse,
|
||||
GetTrustedAppsListRequest,
|
||||
PostTrustedAppCreateRequest,
|
||||
PostTrustedAppCreateResponse,
|
||||
|
@ -36,7 +36,7 @@ import { sendGetEndpointSpecificPackagePolicies } from '../../policy/store/servi
|
|||
|
||||
export interface TrustedAppsService {
|
||||
getTrustedApp(params: GetOneTrustedAppRequestParams): Promise<GetOneTrustedAppResponse>;
|
||||
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedListAppsResponse>;
|
||||
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedAppsListResponse>;
|
||||
deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise<void>;
|
||||
createTrustedApp(request: PostTrustedAppCreateRequest): Promise<PostTrustedAppCreateResponse>;
|
||||
updateTrustedApp(
|
||||
|
@ -58,7 +58,7 @@ export class TrustedAppsHttpService implements TrustedAppsService {
|
|||
}
|
||||
|
||||
async getTrustedAppsList(request: GetTrustedAppsListRequest) {
|
||||
return this.http.get<GetTrustedListAppsResponse>(TRUSTED_APPS_LIST_API, {
|
||||
return this.http.get<GetTrustedAppsListResponse>(TRUSTED_APPS_LIST_API, {
|
||||
query: request,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -407,7 +407,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
class="body-content undefined"
|
||||
>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -563,7 +563,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -790,7 +790,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -946,7 +946,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -1173,7 +1173,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -1329,7 +1329,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -1556,7 +1556,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -1712,7 +1712,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -1939,7 +1939,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -2095,7 +2095,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -2322,7 +2322,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -2478,7 +2478,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -2705,7 +2705,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -2861,7 +2861,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -3088,7 +3088,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -3244,7 +3244,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -3471,7 +3471,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -3627,7 +3627,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -3854,7 +3854,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -4010,7 +4010,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -4532,7 +4532,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
class="body-content undefined"
|
||||
>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -4688,7 +4688,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -4915,7 +4915,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -5071,7 +5071,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -5298,7 +5298,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -5454,7 +5454,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -5681,7 +5681,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -5837,7 +5837,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -6064,7 +6064,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -6220,7 +6220,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -6447,7 +6447,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -6603,7 +6603,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -6830,7 +6830,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -6986,7 +6986,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -7213,7 +7213,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -7369,7 +7369,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -7596,7 +7596,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -7752,7 +7752,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -7979,7 +7979,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -8135,7 +8135,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -8614,7 +8614,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
class="body-content undefined"
|
||||
>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -8770,7 +8770,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -8997,7 +8997,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -9153,7 +9153,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -9380,7 +9380,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -9536,7 +9536,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -9763,7 +9763,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -9919,7 +9919,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -10146,7 +10146,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -10302,7 +10302,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -10529,7 +10529,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -10685,7 +10685,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -10912,7 +10912,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -11068,7 +11068,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -11295,7 +11295,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -11451,7 +11451,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -11678,7 +11678,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -11834,7 +11834,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
@ -12061,7 +12061,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
|
||||
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
|
||||
data-test-subj="trustedAppCard"
|
||||
>
|
||||
<div
|
||||
|
@ -12217,7 +12217,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="trustedAppCard-subHeader"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -13,7 +13,7 @@ import { fireEvent } from '@testing-library/dom';
|
|||
import { MiddlewareActionSpyHelper } from '../../../../common/store/test_utils';
|
||||
import {
|
||||
ConditionEntryField,
|
||||
GetTrustedListAppsResponse,
|
||||
GetTrustedAppsListResponse,
|
||||
NewTrustedApp,
|
||||
OperatingSystem,
|
||||
PostTrustedAppCreateResponse,
|
||||
|
@ -83,7 +83,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
page: number = 1,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
per_page: number = 20
|
||||
): GetTrustedListAppsResponse => {
|
||||
): GetTrustedAppsListResponse => {
|
||||
return {
|
||||
data: [getFakeTrustedApp()],
|
||||
total: 50, // << Should be a value large enough to fulfill two pages
|
||||
|
@ -683,7 +683,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
});
|
||||
|
||||
describe('and there are no trusted apps', () => {
|
||||
const releaseExistsResponse: jest.MockedFunction<() => Promise<GetTrustedListAppsResponse>> =
|
||||
const releaseExistsResponse: jest.MockedFunction<() => Promise<GetTrustedAppsListResponse>> =
|
||||
jest.fn(async () => {
|
||||
return {
|
||||
data: [],
|
||||
|
@ -692,7 +692,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
per_page: 1,
|
||||
};
|
||||
});
|
||||
const releaseListResponse: jest.MockedFunction<() => Promise<GetTrustedListAppsResponse>> =
|
||||
const releaseListResponse: jest.MockedFunction<() => Promise<GetTrustedAppsListResponse>> =
|
||||
jest.fn(async () => {
|
||||
return {
|
||||
data: [],
|
||||
|
|
|
@ -10,11 +10,17 @@ import { ToolingLog } from '@kbn/dev-utils';
|
|||
import { KbnClient } from '@kbn/test';
|
||||
import bluebird from 'bluebird';
|
||||
import { basename } from 'path';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { TRUSTED_APPS_CREATE_API, TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants';
|
||||
import { TrustedApp } from '../../../common/endpoint/types';
|
||||
import { TrustedAppGenerator } from '../../../common/endpoint/data_generators/trusted_app_generator';
|
||||
import { indexFleetEndpointPolicy } from '../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
|
||||
import { setupFleetForEndpoint } from '../../../common/endpoint/data_loaders/setup_fleet_for_endpoint';
|
||||
import { GetPolicyListResponse } from '../../../public/management/pages/policy/types';
|
||||
import {
|
||||
PACKAGE_POLICY_API_ROUTES,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
} from '../../../../fleet/common';
|
||||
|
||||
const defaultLogger = new ToolingLog({ level: 'info', writeTo: process.stdout });
|
||||
const separator = '----------------------------------------';
|
||||
|
@ -83,21 +89,25 @@ export const run: (options?: RunOptions) => Promise<TrustedApp[]> = async ({
|
|||
}),
|
||||
]);
|
||||
|
||||
// Setup a list of read endpoint policies and return a method to randomly select one
|
||||
// Setup a list of real endpoint policies and return a method to randomly select one
|
||||
const randomPolicyId: () => string = await (async () => {
|
||||
const randomN = (max: number): number => Math.floor(Math.random() * max);
|
||||
const policyIds: string[] = [];
|
||||
const policyIds: string[] =
|
||||
(await fetchEndpointPolicies(kbnClient)).data.items.map((policy) => policy.id) || [];
|
||||
|
||||
for (let i = 0, t = 5; i < t; i++) {
|
||||
policyIds.push(
|
||||
(
|
||||
await indexFleetEndpointPolicy(
|
||||
kbnClient,
|
||||
`Policy for Trusted App assignment ${i + 1}`,
|
||||
installedEndpointPackage.version
|
||||
)
|
||||
).integrationPolicies[0].id
|
||||
);
|
||||
// If the number of existing policies is less than 5, then create some more policies
|
||||
if (policyIds.length < 5) {
|
||||
for (let i = 0, t = 5 - policyIds.length; i < t; i++) {
|
||||
policyIds.push(
|
||||
(
|
||||
await indexFleetEndpointPolicy(
|
||||
kbnClient,
|
||||
`Policy for Trusted App assignment ${i + 1}`,
|
||||
installedEndpointPackage.version
|
||||
)
|
||||
).integrationPolicies[0].id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return () => policyIds[randomN(policyIds.length)];
|
||||
|
@ -153,3 +163,16 @@ const createRunLogger = () => {
|
|||
},
|
||||
});
|
||||
};
|
||||
|
||||
const fetchEndpointPolicies = (
|
||||
kbnClient: KbnClient
|
||||
): Promise<AxiosResponse<GetPolicyListResponse>> => {
|
||||
return kbnClient.request<GetPolicyListResponse>({
|
||||
method: 'GET',
|
||||
path: PACKAGE_POLICY_API_ROUTES.LIST_PATTERN,
|
||||
query: {
|
||||
perPage: 100,
|
||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
GetOneTrustedAppResponse,
|
||||
GetTrustedAppsListRequest,
|
||||
GetTrustedAppsSummaryResponse,
|
||||
GetTrustedListAppsResponse,
|
||||
GetTrustedAppsListResponse,
|
||||
PostTrustedAppCreateRequest,
|
||||
PostTrustedAppCreateResponse,
|
||||
PutTrustedAppUpdateRequest,
|
||||
|
@ -124,7 +124,7 @@ export const getTrustedApp = async (
|
|||
export const getTrustedAppsList = async (
|
||||
exceptionsListClient: ExceptionListClient,
|
||||
{ page, per_page: perPage, kuery }: GetTrustedAppsListRequest
|
||||
): Promise<GetTrustedListAppsResponse> => {
|
||||
): Promise<GetTrustedAppsListResponse> => {
|
||||
// Ensure list is created if it does not exist
|
||||
await exceptionsListClient.createTrustedAppsList();
|
||||
|
||||
|
|
Loading…
Reference in a new issue