review changes
This commit is contained in:
parent
21fe79e7c2
commit
2dd22ed92b
9 changed files with 140 additions and 100 deletions
|
@ -250,7 +250,7 @@ export const showView: (state: EndpointState) => 'policy_response' | 'details' =
|
|||
* Returns the selected endpoint's elastic agent Id
|
||||
* used for fetching endpoint actions log
|
||||
*/
|
||||
export const selectedAgent = (state: Immutable<EndpointState>) => {
|
||||
export const selectedAgent = (state: Immutable<EndpointState>): string => {
|
||||
const hostList = state.hosts;
|
||||
const { selected_endpoint: selectedEndpoint } = uiQueryParams(state);
|
||||
return (
|
||||
|
|
|
@ -25,24 +25,19 @@ interface EndpointDetailsTabs {
|
|||
}
|
||||
|
||||
const StyledEuiTabbedContent = styled(EuiTabbedContent)`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
> [role='tabpanel'] {
|
||||
padding: 12px 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding-right: 12px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 7px;
|
||||
width: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
border-radius: 2px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
-webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
|
|
@ -5,41 +5,37 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import React, { memo } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import { EuiComment, EuiText } from '@elastic/eui';
|
||||
import { Immutable, EndpointAction } from '../../../../../../../common/endpoint/types';
|
||||
|
||||
const TimelineItem = styled(EuiComment)``;
|
||||
|
||||
export const TimelineEntry = ({
|
||||
endpointAction,
|
||||
}: {
|
||||
endpointAction: Immutable<EndpointAction>;
|
||||
}) => {
|
||||
const isIsolated = endpointAction?.data.command === 'isolate';
|
||||
const timelineIcon = isIsolated ? 'lock' : 'lockOpen';
|
||||
const event = `${isIsolated ? 'isolated' : 'unisolated'} host`;
|
||||
const hasComment = !!endpointAction.data.comment;
|
||||
// do this better when we can distinguish between endpoint events vs user events
|
||||
const commentType = endpointAction.user_id === 'sys' ? 'update' : 'regular';
|
||||
return (
|
||||
<TimelineItem
|
||||
type={commentType}
|
||||
username={endpointAction.user_id}
|
||||
timestamp={endpointAction['@timestamp']}
|
||||
event={event}
|
||||
timelineIcon={timelineIcon}
|
||||
data-test-subj="timelineEntry"
|
||||
>
|
||||
{hasComment ? (
|
||||
<EuiText size="s">
|
||||
<p>{endpointAction.data.comment}</p>
|
||||
</EuiText>
|
||||
) : undefined}
|
||||
</TimelineItem>
|
||||
);
|
||||
};
|
||||
export const TimelineEntry = memo(
|
||||
({ endpointAction }: { endpointAction: Immutable<EndpointAction> }) => {
|
||||
const isIsolated = endpointAction?.data.command === 'isolate';
|
||||
const timelineIcon = isIsolated ? 'lock' : 'lockOpen';
|
||||
const event = `${isIsolated ? 'isolated' : 'unisolated'} host`;
|
||||
const hasComment = !!endpointAction.data.comment;
|
||||
// do this better when we can distinguish between endpoint events vs user events
|
||||
const commentType = endpointAction.user_id === 'sys' ? 'update' : 'regular';
|
||||
return (
|
||||
<EuiComment
|
||||
type={commentType}
|
||||
username={endpointAction.user_id}
|
||||
timestamp={moment(endpointAction['@timestamp']).fromNow().toString()}
|
||||
event={event}
|
||||
timelineIcon={timelineIcon}
|
||||
data-test-subj="timelineEntry"
|
||||
>
|
||||
{hasComment ? (
|
||||
<EuiText size="s">
|
||||
<p>{endpointAction.data.comment}</p>
|
||||
</EuiText>
|
||||
) : undefined}
|
||||
</EuiComment>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
TimelineEntry.displayName = 'TimelineEntry';
|
||||
|
|
|
@ -5,26 +5,35 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { EuiFieldSearch, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiFieldSearch, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui';
|
||||
import { TimelineEntry } from './components/timeline_entry';
|
||||
import * as i18 from '../translations';
|
||||
import { Immutable, EndpointAction } from '../../../../../../common/endpoint/types';
|
||||
|
||||
export const EndpointActivityLog = ({
|
||||
endpointActions,
|
||||
}: {
|
||||
endpointActions: Immutable<EndpointAction[]>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<EuiFieldSearch compressed fullWidth placeholder="Search activity log" />
|
||||
<EuiSpacer size="l" />
|
||||
{endpointActions.map((endpointAction) => (
|
||||
<TimelineEntry key={endpointAction['@timestamp']} endpointAction={endpointAction} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const EndpointActivityLog = memo(
|
||||
({ endpointActions }: { endpointActions: Immutable<EndpointAction[]> }) => {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFieldSearch compressed fullWidth placeholder={i18.SEARCH_ACTIVITY_LOG} />
|
||||
<EuiSpacer size="l" />
|
||||
{!endpointActions.length ? (
|
||||
<EuiEmptyPrompt
|
||||
iconType="alert"
|
||||
titleSize="s"
|
||||
title={<h2>{'No activity logged for the endpoint'}</h2>}
|
||||
body={<p>{'Perform some actions on the endpoint to see actions log here.'}</p>}
|
||||
/>
|
||||
) : (
|
||||
endpointActions.map((endpointAction) => (
|
||||
<TimelineEntry key={endpointAction.action_id} endpointAction={endpointAction} />
|
||||
))
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EndpointActivityLog.displayName = 'EndpointActivityLog';
|
||||
|
|
|
@ -258,6 +258,7 @@ export const EndpointDetails = memo(
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
listItems={detailsResultsUpper}
|
||||
|
|
|
@ -65,6 +65,27 @@ import { SecurityPageName } from '../../../../../app/types';
|
|||
import { useFormatUrl } from '../../../../../common/components/link_to';
|
||||
import { PreferenceFormattedDateFromPrimitive } from '../../../../../common/components/formatted_date';
|
||||
|
||||
const contentLoadingMarkup = (
|
||||
<>
|
||||
<EuiLoadingContent lines={3} /> <EuiSpacer size="l" /> <EuiLoadingContent lines={3} />
|
||||
</>
|
||||
);
|
||||
|
||||
const DetailsFlyoutBody = styled(EuiFlyoutBody)`
|
||||
overflow-y: hidden;
|
||||
flex: 1;
|
||||
|
||||
.euiFlyoutBody__overflow {
|
||||
overflow: hidden;
|
||||
mask-image: none;
|
||||
}
|
||||
|
||||
.euiFlyoutBody__overflowContent {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
export const EndpointDetailsFlyout = memo(() => {
|
||||
const history = useHistory();
|
||||
const toasts = useToasts();
|
||||
|
@ -102,7 +123,7 @@ export const EndpointDetailsFlyout = memo(() => {
|
|||
}, [history, queryParamsWithoutSelectedEndpoint]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hostDetailsError !== undefined || activityError !== undefined) {
|
||||
if (hostDetailsError !== undefined) {
|
||||
toasts.addDanger({
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.details.errorTitle', {
|
||||
defaultMessage: 'Could not find host',
|
||||
|
@ -112,6 +133,16 @@ export const EndpointDetailsFlyout = memo(() => {
|
|||
}),
|
||||
});
|
||||
}
|
||||
if (activityError !== undefined) {
|
||||
toasts.addDanger({
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.activityLog.errorTitle', {
|
||||
defaultMessage: 'Could not find activity log for host',
|
||||
}),
|
||||
text: i18n.translate('xpack.securitySolution.endpoint.activityLog.errorBody', {
|
||||
defaultMessage: 'Please exit the flyout and select another host with actions.',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}, [hostDetailsError, activityError, toasts]);
|
||||
|
||||
return (
|
||||
|
@ -138,47 +169,48 @@ export const EndpointDetailsFlyout = memo(() => {
|
|||
</EuiToolTip>
|
||||
)}
|
||||
</EuiFlyoutHeader>
|
||||
{hostDetails === undefined || activityLog === undefined ? (
|
||||
{show === 'details' && (
|
||||
<>
|
||||
<EuiFlyoutBody>
|
||||
<EuiLoadingContent lines={3} /> <EuiSpacer size="l" /> <EuiLoadingContent lines={3} />
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{show === 'details' && (
|
||||
<>
|
||||
<EuiFlyoutBody data-test-subj="endpointDetailsFlyoutBody">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EndpointDetailsFlyoutTabs
|
||||
tabs={[
|
||||
{
|
||||
id: EndpointDetailsTabsTypes.overview,
|
||||
name: i18.OVERVIEW,
|
||||
content: (
|
||||
<EndpointDetails
|
||||
details={hostDetails}
|
||||
policyInfo={policyInfo}
|
||||
hostStatus={hostStatus}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: EndpointDetailsTabsTypes.activityLog,
|
||||
name: i18.ACTIVITY_LOG,
|
||||
content: <EndpointActivityLog endpointActions={activityLog} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
)}
|
||||
{show === 'policy_response' && <PolicyResponseFlyoutPanel hostMeta={hostDetails} />}
|
||||
<DetailsFlyoutBody data-test-subj="endpointDetailsFlyoutBody">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EndpointDetailsFlyoutTabs
|
||||
tabs={[
|
||||
{
|
||||
id: EndpointDetailsTabsTypes.overview,
|
||||
name: i18.OVERVIEW,
|
||||
content:
|
||||
hostDetails === undefined ? (
|
||||
contentLoadingMarkup
|
||||
) : (
|
||||
<EndpointDetails
|
||||
details={hostDetails}
|
||||
policyInfo={policyInfo}
|
||||
hostStatus={hostStatus}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: EndpointDetailsTabsTypes.activityLog,
|
||||
name: i18.ACTIVITY_LOG,
|
||||
content:
|
||||
activityLog === undefined ? (
|
||||
contentLoadingMarkup
|
||||
) : (
|
||||
<EndpointActivityLog endpointActions={activityLog} />
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</DetailsFlyoutBody>
|
||||
</>
|
||||
)}
|
||||
{show === 'policy_response' && !!hostDetails && (
|
||||
<PolicyResponseFlyoutPanel hostMeta={hostDetails} />
|
||||
)}
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
<EuiPagination
|
||||
pageCount={pageCount}
|
||||
|
|
|
@ -14,3 +14,10 @@ export const OVERVIEW = i18n.translate('xpack.securitySolution.endpointDetails.o
|
|||
export const ACTIVITY_LOG = i18n.translate('xpack.securitySolution.endpointDetails.activityLog', {
|
||||
defaultMessage: 'Activity Log',
|
||||
});
|
||||
|
||||
export const SEARCH_ACTIVITY_LOG = i18n.translate(
|
||||
'xpack.securitySolution.endpointDetails.activityLog.search',
|
||||
{
|
||||
defaultMessage: 'Search activity log',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -13,7 +13,7 @@ import { SecuritySolutionPluginRouter } from '../../../types';
|
|||
import { EndpointAppContext } from '../../types';
|
||||
|
||||
/**
|
||||
* Registers the Host-(un-)isolation routes
|
||||
* Registers the endpoint activity-log route
|
||||
*/
|
||||
export function registerActionAuditLogRoutes(
|
||||
router: SecuritySolutionPluginRouter,
|
||||
|
|
|
@ -47,7 +47,7 @@ export const actionsLogRequestHandler = (
|
|||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: {
|
||||
message: `Error fetching Actions Log for: ${req.params.agent_id}`,
|
||||
message: `Error fetching actions log for agent_id ${req.params.agent_id}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue