[Endpoint] Move the Endpoint Host page over to the Management Endpoints sub-tab (#68002)
* move code dir. to management/pages * Make hosts appear on endpoints tab * Add support for `className` to `<FormattedDate>` component * add FormattedDate to Host list to display last seen date
This commit is contained in:
parent
c3269fa117
commit
cdb6b4dd33
39 changed files with 327 additions and 233 deletions
|
@ -149,7 +149,9 @@ export const PageView = memo<PageViewProps>(
|
|||
)}
|
||||
</EuiPageHeader>
|
||||
)}
|
||||
{tabs && <EuiTabs className="endpoint-navTabs">{tabComponents}</EuiTabs>}
|
||||
{tabComponents.length > 0 && (
|
||||
<EuiTabs className="endpoint-navTabs">{tabComponents}</EuiTabs>
|
||||
)}
|
||||
<EuiPageContent className="endpoint-page-content">
|
||||
{bodyHeader && (
|
||||
<EuiPageContentHeader>
|
||||
|
|
|
@ -75,14 +75,15 @@ PreferenceFormattedP1DTDate.displayName = 'PreferenceFormattedP1DTDate';
|
|||
export const FormattedDate = React.memo<{
|
||||
fieldName: string;
|
||||
value?: string | number | null;
|
||||
className?: string;
|
||||
}>(
|
||||
({ value, fieldName }): JSX.Element => {
|
||||
({ value, fieldName, className = '' }): JSX.Element => {
|
||||
if (value == null) {
|
||||
return getOrEmptyTagFromValue(value);
|
||||
}
|
||||
const maybeDate = getMaybeDate(value);
|
||||
return maybeDate.isValid() ? (
|
||||
<LocalizedDateTooltip date={maybeDate.toDate()} fieldName={fieldName}>
|
||||
<LocalizedDateTooltip date={maybeDate.toDate()} fieldName={fieldName} className={className}>
|
||||
<PreferenceFormattedDate value={maybeDate.toDate()} />
|
||||
</LocalizedDateTooltip>
|
||||
) : (
|
||||
|
|
|
@ -13,9 +13,11 @@ export const LocalizedDateTooltip = React.memo<{
|
|||
children: React.ReactNode;
|
||||
date: Date;
|
||||
fieldName?: string;
|
||||
}>(({ children, date, fieldName }) => (
|
||||
className?: string;
|
||||
}>(({ children, date, fieldName, className = '' }) => (
|
||||
<EuiToolTip
|
||||
data-test-subj="localized-date-tool-tip"
|
||||
anchorClassName={className}
|
||||
content={
|
||||
<EuiFlexGroup data-test-subj="dates-container" direction="column" gutterSize="none">
|
||||
{fieldName != null ? (
|
||||
|
|
|
@ -18,7 +18,7 @@ import { createStore, State, substateMiddlewareFactory } from '../../store';
|
|||
import { alertMiddlewareFactory } from '../../../endpoint_alerts/store/middleware';
|
||||
import { AppRootProvider } from './app_root_provider';
|
||||
import { managementMiddlewareFactory } from '../../../management/store/middleware';
|
||||
import { hostMiddlewareFactory } from '../../../endpoint_hosts/store/middleware';
|
||||
import { createKibanaContextProviderMock } from '../kibana_react';
|
||||
import { SUB_PLUGINS_REDUCER, mockGlobalState } from '..';
|
||||
|
||||
type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult;
|
||||
|
@ -57,10 +57,6 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
const depsStart = depsStartMock();
|
||||
const middlewareSpy = createSpyMiddleware();
|
||||
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, [
|
||||
substateMiddlewareFactory(
|
||||
(globalState) => globalState.hostList,
|
||||
hostMiddlewareFactory(coreStart, depsStart)
|
||||
),
|
||||
substateMiddlewareFactory(
|
||||
(globalState) => globalState.alertList,
|
||||
alertMiddlewareFactory(coreStart, depsStart)
|
||||
|
@ -68,11 +64,14 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
...managementMiddlewareFactory(coreStart, depsStart),
|
||||
middlewareSpy.actionSpyMiddleware,
|
||||
]);
|
||||
const MockKibanaContextProvider = createKibanaContextProviderMock();
|
||||
|
||||
const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => (
|
||||
<AppRootProvider store={store} history={history} coreStart={coreStart} depsStart={depsStart}>
|
||||
{children}
|
||||
</AppRootProvider>
|
||||
<MockKibanaContextProvider>
|
||||
<AppRootProvider store={store} history={history} coreStart={coreStart} depsStart={depsStart}>
|
||||
{children}
|
||||
</AppRootProvider>
|
||||
</MockKibanaContextProvider>
|
||||
);
|
||||
const render: UiRender = (ui, options) => {
|
||||
return reactRender(ui, {
|
||||
|
|
|
@ -26,10 +26,8 @@ import {
|
|||
import { networkModel } from '../../network/store';
|
||||
import { TimelineType, TimelineStatus } from '../../../common/types/timeline';
|
||||
import { initialAlertListState } from '../../endpoint_alerts/store/reducer';
|
||||
import { initialHostListState } from '../../endpoint_hosts/store/reducer';
|
||||
import { mockManagementState } from '../../management/store/reducer';
|
||||
import { AlertListState } from '../../../common/endpoint_alerts/types';
|
||||
import { HostState } from '../../endpoint_hosts/types';
|
||||
import { ManagementState } from '../../management/types';
|
||||
|
||||
export const mockGlobalState: State = {
|
||||
|
@ -237,6 +235,5 @@ export const mockGlobalState: State = {
|
|||
* they are cast to mutable versions here.
|
||||
*/
|
||||
alertList: initialAlertListState as AlertListState,
|
||||
hostList: initialHostListState as HostState,
|
||||
management: mockManagementState as ManagementState,
|
||||
};
|
||||
|
|
|
@ -11,9 +11,7 @@ import { managementReducer } from '../../management/store/reducer';
|
|||
import { ManagementPluginReducer } from '../../management';
|
||||
import { SubPluginsInitReducer } from '../store';
|
||||
import { EndpointAlertsPluginReducer } from '../../endpoint_alerts';
|
||||
import { EndpointHostsPluginReducer } from '../../endpoint_hosts';
|
||||
import { alertListReducer } from '../../endpoint_alerts/store/reducer';
|
||||
import { hostListReducer } from '../../endpoint_hosts/store/reducer';
|
||||
|
||||
interface Global extends NodeJS.Global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -30,7 +28,6 @@ export const SUB_PLUGINS_REDUCER: SubPluginsInitReducer = {
|
|||
* These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture,
|
||||
* they are cast to mutable versions here.
|
||||
*/
|
||||
hostList: hostListReducer as EndpointHostsPluginReducer['hostList'],
|
||||
alertList: alertListReducer as EndpointAlertsPluginReducer['alertList'],
|
||||
management: managementReducer as ManagementPluginReducer['management'],
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostAction } from '../../endpoint_hosts/store/action';
|
||||
import { HostAction } from '../../management/pages/endpoint_hosts/store/action';
|
||||
import { AlertAction } from '../../endpoint_alerts/store/action';
|
||||
import { PolicyListAction } from '../../management/pages/policy/store/policy_list';
|
||||
import { PolicyDetailsAction } from '../../management/pages/policy/store/policy_details';
|
||||
|
|
|
@ -17,7 +17,6 @@ import { TimelinePluginReducer } from '../../timelines/store/timeline';
|
|||
import { SecuritySubPlugins } from '../../app/types';
|
||||
import { ManagementPluginReducer } from '../../management';
|
||||
import { EndpointAlertsPluginReducer } from '../../endpoint_alerts';
|
||||
import { EndpointHostsPluginReducer } from '../../endpoint_hosts';
|
||||
import { State } from './types';
|
||||
import { AppAction } from './actions';
|
||||
|
||||
|
@ -25,7 +24,6 @@ export type SubPluginsInitReducer = HostsPluginReducer &
|
|||
NetworkPluginReducer &
|
||||
TimelinePluginReducer &
|
||||
EndpointAlertsPluginReducer &
|
||||
EndpointHostsPluginReducer &
|
||||
ManagementPluginReducer;
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,7 +17,6 @@ import { DragAndDropState } from './drag_and_drop/reducer';
|
|||
import { TimelinePluginState } from '../../timelines/store/timeline';
|
||||
import { NetworkPluginState } from '../../network/store';
|
||||
import { EndpointAlertsPluginState } from '../../endpoint_alerts';
|
||||
import { EndpointHostsPluginState } from '../../endpoint_hosts';
|
||||
import { ManagementPluginState } from '../../management';
|
||||
|
||||
/**
|
||||
|
@ -31,7 +30,6 @@ export type State = CombinedState<
|
|||
NetworkPluginState &
|
||||
TimelinePluginState &
|
||||
EndpointAlertsPluginState &
|
||||
EndpointHostsPluginState &
|
||||
ManagementPluginState & {
|
||||
app: AppState;
|
||||
dragAndDrop: DragAndDropState;
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Reducer } from 'redux';
|
||||
import { SecuritySubPluginWithStore } from '../app/types';
|
||||
import { endpointHostsRoutes } from './routes';
|
||||
import { hostListReducer } from './store/reducer';
|
||||
import { HostState } from './types';
|
||||
import { hostMiddlewareFactory } from './store/middleware';
|
||||
import { CoreStart } from '../../../../../src/core/public';
|
||||
import { StartPlugins } from '../types';
|
||||
import { substateMiddlewareFactory } from '../common/store';
|
||||
import { AppAction } from '../common/store/actions';
|
||||
|
||||
/**
|
||||
* Internally, our state is sometimes immutable, ignore that in our external
|
||||
* interface.
|
||||
*/
|
||||
export interface EndpointHostsPluginState {
|
||||
hostList: HostState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally, we use `ImmutableReducer`, but we present a regular reducer
|
||||
* externally for compatibility w/ regular redux.
|
||||
*/
|
||||
export interface EndpointHostsPluginReducer {
|
||||
hostList: Reducer<HostState, AppAction>;
|
||||
}
|
||||
|
||||
export class EndpointHosts {
|
||||
public setup() {}
|
||||
|
||||
public start(
|
||||
core: CoreStart,
|
||||
plugins: StartPlugins
|
||||
): SecuritySubPluginWithStore<'hostList', HostState> {
|
||||
const { data, ingestManager } = plugins;
|
||||
const middleware = [
|
||||
substateMiddlewareFactory(
|
||||
(globalState) => globalState.hostList,
|
||||
hostMiddlewareFactory(core, { data, ingestManager })
|
||||
),
|
||||
];
|
||||
return {
|
||||
routes: endpointHostsRoutes(),
|
||||
store: {
|
||||
initialState: { hostList: undefined },
|
||||
/**
|
||||
* Cast the ImmutableReducer to a regular reducer for compatibility with
|
||||
* the subplugin architecture (which expects plain redux reducers.)
|
||||
*/
|
||||
reducer: { hostList: hostListReducer } as EndpointHostsPluginReducer,
|
||||
middleware,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -19,3 +19,5 @@ export const MANAGEMENT_STORE_GLOBAL_NAMESPACE: ManagementStoreGlobalNamespace =
|
|||
export const MANAGEMENT_STORE_POLICY_LIST_NAMESPACE = 'policyList';
|
||||
/** Namespace within the Management state where policy details state is maintained */
|
||||
export const MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE = 'policyDetails';
|
||||
/** Namespace within the Management state where endpoints state is maintained */
|
||||
export const MANAGEMENT_STORE_ENDPOINTS_NAMESPACE = 'endpoints';
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
import { generatePath } from 'react-router-dom';
|
||||
// eslint-disable-next-line import/no-nodejs-modules
|
||||
import querystring from 'querystring';
|
||||
import {
|
||||
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICIES_PATH,
|
||||
|
@ -13,7 +15,28 @@ import {
|
|||
} from './constants';
|
||||
import { ManagementSubTab } from '../types';
|
||||
import { SiemPageName } from '../../app/types';
|
||||
import { HostIndexUIQueryParams } from '../pages/endpoint_hosts/types';
|
||||
|
||||
// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
|
||||
type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never ? T1 : never;
|
||||
type Exact<T, Shape> = T extends Shape ? ExactKeys<T, Shape> : never;
|
||||
|
||||
/**
|
||||
* Returns a string to be used in the URL as search query params.
|
||||
* Ensures that when creating a URL query param string, that the given input strictly
|
||||
* matches the expected interface (guards against possibly leaking internal state)
|
||||
*/
|
||||
const querystringStringify: <ExpectedType extends object, ArgType>(
|
||||
params: Exact<ExpectedType, ArgType>
|
||||
) => string = querystring.stringify;
|
||||
|
||||
/** Make `selected_host` required */
|
||||
type EndpointDetailsUrlProps = Omit<HostIndexUIQueryParams, 'selected_host'> &
|
||||
Required<Pick<HostIndexUIQueryParams, 'selected_host'>>;
|
||||
|
||||
/**
|
||||
* Input props for the `getManagementUrl()` method
|
||||
*/
|
||||
export type GetManagementUrlProps = {
|
||||
/**
|
||||
* Exclude the URL prefix (everything to the left of where the router was mounted.
|
||||
|
@ -22,8 +45,8 @@ export type GetManagementUrlProps = {
|
|||
*/
|
||||
excludePrefix?: boolean;
|
||||
} & (
|
||||
| { name: 'default' }
|
||||
| { name: 'endpointList' }
|
||||
| ({ name: 'default' | 'endpointList' } & HostIndexUIQueryParams)
|
||||
| ({ name: 'endpointDetails' | 'endpointPolicyResponse' } & EndpointDetailsUrlProps)
|
||||
| { name: 'policyList' }
|
||||
| { name: 'policyDetails'; policyId: string }
|
||||
);
|
||||
|
@ -39,31 +62,47 @@ const URL_PREFIX = '#';
|
|||
export const getManagementUrl = (props: GetManagementUrlProps): string => {
|
||||
let url = props.excludePrefix ? '' : URL_PREFIX;
|
||||
|
||||
switch (props.name) {
|
||||
case 'default':
|
||||
url += generatePath(MANAGEMENT_ROUTING_ROOT_PATH, {
|
||||
pageName: SiemPageName.management,
|
||||
});
|
||||
break;
|
||||
case 'endpointList':
|
||||
if (props.name === 'default' || props.name === 'endpointList') {
|
||||
const { name, excludePrefix, ...queryParams } = props;
|
||||
const urlQueryParams = querystringStringify<HostIndexUIQueryParams, typeof queryParams>(
|
||||
queryParams
|
||||
);
|
||||
|
||||
if (name === 'endpointList') {
|
||||
url += generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
|
||||
pageName: SiemPageName.management,
|
||||
tabName: ManagementSubTab.endpoints,
|
||||
});
|
||||
break;
|
||||
case 'policyList':
|
||||
url += generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
|
||||
} else {
|
||||
url += generatePath(MANAGEMENT_ROUTING_ROOT_PATH, {
|
||||
pageName: SiemPageName.management,
|
||||
tabName: ManagementSubTab.policies,
|
||||
});
|
||||
break;
|
||||
case 'policyDetails':
|
||||
url += generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
|
||||
pageName: SiemPageName.management,
|
||||
tabName: ManagementSubTab.policies,
|
||||
policyId: props.policyId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (urlQueryParams) {
|
||||
url += `?${urlQueryParams}`;
|
||||
}
|
||||
} else if (props.name === 'endpointDetails' || props.name === 'endpointPolicyResponse') {
|
||||
const { name, excludePrefix, ...queryParams } = props;
|
||||
queryParams.show = (props.name === 'endpointPolicyResponse'
|
||||
? 'policy_response'
|
||||
: '') as HostIndexUIQueryParams['show'];
|
||||
|
||||
url += `${generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, {
|
||||
pageName: SiemPageName.management,
|
||||
tabName: ManagementSubTab.endpoints,
|
||||
})}?${querystringStringify<EndpointDetailsUrlProps, typeof queryParams>(queryParams)}`;
|
||||
} else if (props.name === 'policyList') {
|
||||
url += generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
|
||||
pageName: SiemPageName.management,
|
||||
tabName: ManagementSubTab.policies,
|
||||
});
|
||||
} else if (props.name === 'policyDetails') {
|
||||
url += generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
|
||||
pageName: SiemPageName.management,
|
||||
tabName: ManagementSubTab.policies,
|
||||
policyId: props.policyId,
|
||||
});
|
||||
}
|
||||
|
||||
return url;
|
||||
|
|
|
@ -13,7 +13,10 @@ import { getManagementUrl } from '..';
|
|||
|
||||
export const ManagementPageView = memo<Omit<PageViewProps, 'tabs'>>((options) => {
|
||||
const { tabName } = useParams<{ tabName: ManagementSubTab }>();
|
||||
const tabs = useMemo((): PageViewProps['tabs'] => {
|
||||
const tabs = useMemo((): PageViewProps['tabs'] | undefined => {
|
||||
if (options.viewType === 'details') {
|
||||
return undefined;
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: i18n.translate('xpack.securitySolution.managementTabs.endpoints', {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import React, { memo } from 'react';
|
||||
import { HostList } from './view';
|
||||
import { MANAGEMENT_ROUTING_ENDPOINTS_PATH } from '../../common/constants';
|
||||
import { NotFoundPage } from '../../../app/404';
|
||||
|
||||
/**
|
||||
* Provides the routing container for the endpoints related views
|
||||
*/
|
||||
export const EndpointsContainer = memo(() => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={MANAGEMENT_ROUTING_ENDPOINTS_PATH} exact component={HostList} />
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
);
|
||||
});
|
||||
|
||||
EndpointsContainer.displayName = 'EndpointsContainer';
|
|
@ -4,8 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostResultList, HostInfo, GetHostPolicyResponse } from '../../../common/endpoint/types';
|
||||
import { ServerApiError } from '../../common/types';
|
||||
import {
|
||||
HostResultList,
|
||||
HostInfo,
|
||||
GetHostPolicyResponse,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { ServerApiError } from '../../../../common/types';
|
||||
|
||||
interface ServerReturnedHostList {
|
||||
type: 'serverReturnedHostList';
|
|
@ -8,10 +8,10 @@ import { CoreStart, HttpSetup } from 'kibana/public';
|
|||
import { History, createBrowserHistory } from 'history';
|
||||
import { applyMiddleware, Store, createStore } from 'redux';
|
||||
|
||||
import { coreMock } from '../../../../../../src/core/public/mocks';
|
||||
import { coreMock } from '../../../../../../../../src/core/public/mocks';
|
||||
|
||||
import { HostResultList, AppLocation } from '../../../common/endpoint/types';
|
||||
import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint';
|
||||
import { HostResultList, AppLocation } from '../../../../../common/endpoint/types';
|
||||
import { DepsStartMock, depsStartMock } from '../../../../common/mock/endpoint';
|
||||
|
||||
import { hostMiddlewareFactory } from './middleware';
|
||||
|
||||
|
@ -20,8 +20,11 @@ import { hostListReducer } from './reducer';
|
|||
import { uiQueryParams } from './selectors';
|
||||
import { mockHostResultList } from './mock_host_result_list';
|
||||
import { HostState, HostIndexUIQueryParams } from '../types';
|
||||
import { MiddlewareActionSpyHelper, createSpyMiddleware } from '../../common/store/test_utils';
|
||||
import { urlFromQueryParams } from '../view/url_from_query_params';
|
||||
import {
|
||||
MiddlewareActionSpyHelper,
|
||||
createSpyMiddleware,
|
||||
} from '../../../../common/store/test_utils';
|
||||
import { getManagementUrl } from '../../..';
|
||||
|
||||
describe('host list pagination: ', () => {
|
||||
let fakeCoreStart: jest.Mocked<CoreStart>;
|
||||
|
@ -53,7 +56,9 @@ describe('host list pagination: ', () => {
|
|||
queryParams = () => uiQueryParams(store.getState());
|
||||
|
||||
historyPush = (nextQueryParams: HostIndexUIQueryParams): void => {
|
||||
return history.push(urlFromQueryParams(nextQueryParams));
|
||||
return history.push(
|
||||
getManagementUrl({ name: 'endpointList', excludePrefix: true, ...nextQueryParams })
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -67,7 +72,7 @@ describe('host list pagination: ', () => {
|
|||
type: 'userChangedUrl',
|
||||
payload: {
|
||||
...history.location,
|
||||
pathname: '/endpoint-hosts',
|
||||
pathname: getManagementUrl({ name: 'endpointList', excludePrefix: true }),
|
||||
},
|
||||
});
|
||||
await waitForAction('serverReturnedHostList');
|
|
@ -5,19 +5,23 @@
|
|||
*/
|
||||
import { CoreStart, HttpSetup } from 'kibana/public';
|
||||
import { applyMiddleware, createStore, Store } from 'redux';
|
||||
import { coreMock } from '../../../../../../src/core/public/mocks';
|
||||
import { coreMock } from '../../../../../../../../src/core/public/mocks';
|
||||
import { History, createBrowserHistory } from 'history';
|
||||
|
||||
import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint';
|
||||
import { DepsStartMock, depsStartMock } from '../../../../common/mock/endpoint';
|
||||
|
||||
import { createSpyMiddleware, MiddlewareActionSpyHelper } from '../../common/store/test_utils';
|
||||
import { Immutable, HostResultList } from '../../../common/endpoint/types';
|
||||
import { AppAction } from '../../common/store/actions';
|
||||
import {
|
||||
createSpyMiddleware,
|
||||
MiddlewareActionSpyHelper,
|
||||
} from '../../../../common/store/test_utils';
|
||||
import { Immutable, HostResultList } from '../../../../../common/endpoint/types';
|
||||
import { AppAction } from '../../../../common/store/actions';
|
||||
import { mockHostResultList } from './mock_host_result_list';
|
||||
import { listData } from './selectors';
|
||||
import { HostState } from '../types';
|
||||
import { hostListReducer } from './reducer';
|
||||
import { hostMiddlewareFactory } from './middleware';
|
||||
import { getManagementUrl } from '../../..';
|
||||
|
||||
describe('host list middleware', () => {
|
||||
let fakeCoreStart: jest.Mocked<CoreStart>;
|
||||
|
@ -56,7 +60,7 @@ describe('host list middleware', () => {
|
|||
type: 'userChangedUrl',
|
||||
payload: {
|
||||
...history.location,
|
||||
pathname: '/endpoint-hosts',
|
||||
pathname: getManagementUrl({ name: 'endpointList', excludePrefix: true }),
|
||||
},
|
||||
});
|
||||
await waitForAction('serverReturnedHostList');
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostResultList } from '../../../common/endpoint/types';
|
||||
import { ImmutableMiddlewareFactory } from '../../common/store';
|
||||
import { HostResultList } from '../../../../../common/endpoint/types';
|
||||
import { ImmutableMiddlewareFactory } from '../../../../common/store';
|
||||
import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors';
|
||||
import { HostState } from '../types';
|
||||
|
||||
|
@ -37,7 +37,7 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory<HostState> = (cor
|
|||
});
|
||||
}
|
||||
}
|
||||
if (action.type === 'userChangedUrl' && hasSelectedHost(state) !== false) {
|
||||
if (action.type === 'userChangedUrl' && hasSelectedHost(state) === true) {
|
||||
// If user navigated directly to a host details page, load the host list
|
||||
if (listData(state).length === 0) {
|
||||
const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state);
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostInfo, HostResultList, HostStatus } from '../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../common/endpoint/generate_data';
|
||||
import { HostInfo, HostResultList, HostStatus } from '../../../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
|
||||
|
||||
export const mockHostResultList: (options?: {
|
||||
total?: number;
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import { isOnHostPage, hasSelectedHost } from './selectors';
|
||||
import { HostState } from '../types';
|
||||
import { AppAction } from '../../common/store/actions';
|
||||
import { ImmutableReducer } from '../../common/store';
|
||||
import { Immutable } from '../../../common/endpoint/types';
|
||||
import { AppAction } from '../../../../common/store/actions';
|
||||
import { ImmutableReducer } from '../../../../common/store';
|
||||
import { Immutable } from '../../../../../common/endpoint/types';
|
||||
|
||||
export const initialHostListState: Immutable<HostState> = {
|
||||
hosts: [],
|
|
@ -7,13 +7,15 @@
|
|||
// eslint-disable-next-line import/no-nodejs-modules
|
||||
import querystring from 'querystring';
|
||||
import { createSelector } from 'reselect';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import {
|
||||
Immutable,
|
||||
HostPolicyResponseAppliedAction,
|
||||
HostPolicyResponseConfiguration,
|
||||
HostPolicyResponseActionStatus,
|
||||
} from '../../../common/endpoint/types';
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { HostState, HostIndexUIQueryParams } from '../types';
|
||||
import { MANAGEMENT_ROUTING_ENDPOINTS_PATH } from '../../../common/constants';
|
||||
|
||||
const PAGE_SIZES = Object.freeze([10, 20, 50]);
|
||||
|
||||
|
@ -96,8 +98,14 @@ export const policyResponseLoading = (state: Immutable<HostState>): boolean =>
|
|||
|
||||
export const policyResponseError = (state: Immutable<HostState>) => state.policyResponseError;
|
||||
|
||||
export const isOnHostPage = (state: Immutable<HostState>) =>
|
||||
state.location ? state.location.pathname === '/endpoint-hosts' : false;
|
||||
export const isOnHostPage = (state: Immutable<HostState>) => {
|
||||
return (
|
||||
matchPath(state.location?.pathname ?? '', {
|
||||
path: MANAGEMENT_ROUTING_ENDPOINTS_PATH,
|
||||
exact: true,
|
||||
}) !== null
|
||||
);
|
||||
};
|
||||
|
||||
export const uiQueryParams: (
|
||||
state: Immutable<HostState>
|
||||
|
@ -117,11 +125,21 @@ export const uiQueryParams: (
|
|||
];
|
||||
|
||||
for (const key of keys) {
|
||||
const value = query[key];
|
||||
if (typeof value === 'string') {
|
||||
data[key] = value;
|
||||
} else if (Array.isArray(value)) {
|
||||
data[key] = value[value.length - 1];
|
||||
const value: string | undefined =
|
||||
typeof query[key] === 'string'
|
||||
? (query[key] as string)
|
||||
: Array.isArray(query[key])
|
||||
? (query[key][query[key].length - 1] as string)
|
||||
: undefined;
|
||||
|
||||
if (value !== undefined) {
|
||||
if (key === 'show') {
|
||||
if (value === 'policy_response' || value === 'details') {
|
||||
data[key] = value;
|
||||
}
|
||||
} else {
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,8 @@ import {
|
|||
HostMetadata,
|
||||
HostPolicyResponse,
|
||||
AppLocation,
|
||||
} from '../../common/endpoint/types';
|
||||
import { ServerApiError } from '../common/types';
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { ServerApiError } from '../../../common/types';
|
||||
|
||||
export interface HostState {
|
||||
/** list of host **/
|
||||
|
@ -53,5 +53,5 @@ export interface HostIndexUIQueryParams {
|
|||
/** Which page to show */
|
||||
page_index?: string;
|
||||
/** show the policy response or host details */
|
||||
show?: string;
|
||||
show?: 'policy_response' | 'details';
|
||||
}
|
|
@ -16,14 +16,14 @@ import {
|
|||
import React, { memo, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HostMetadata } from '../../../../common/endpoint/types';
|
||||
import { HostMetadata } from '../../../../../../common/endpoint/types';
|
||||
import { useHostSelector, useHostLogsUrl } from '../hooks';
|
||||
import { urlFromQueryParams } from '../url_from_query_params';
|
||||
import { policyResponseStatus, uiQueryParams } from '../../store/selectors';
|
||||
import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants';
|
||||
import { FormattedDateAndTime } from '../../../common/components/endpoint/formatted_date_time';
|
||||
import { useNavigateByRouterEventHandler } from '../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { LinkToApp } from '../../../common/components/endpoint/link_to_app';
|
||||
import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time';
|
||||
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app';
|
||||
import { getManagementUrl } from '../../../..';
|
||||
|
||||
const HostIds = styled(EuiListGroupItem)`
|
||||
margin-top: 0;
|
||||
|
@ -61,14 +61,24 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
|
|||
];
|
||||
}, [details]);
|
||||
|
||||
const policyResponseUri = useMemo(() => {
|
||||
return urlFromQueryParams({
|
||||
...queryParams,
|
||||
selected_host: details.host.id,
|
||||
show: 'policy_response',
|
||||
});
|
||||
const [policyResponseUri, policyResponseRoutePath] = useMemo(() => {
|
||||
const { selected_host, show, ...currentUrlParams } = queryParams;
|
||||
return [
|
||||
getManagementUrl({
|
||||
name: 'endpointPolicyResponse',
|
||||
...currentUrlParams,
|
||||
selected_host: details.host.id,
|
||||
}),
|
||||
getManagementUrl({
|
||||
name: 'endpointPolicyResponse',
|
||||
excludePrefix: true,
|
||||
...currentUrlParams,
|
||||
selected_host: details.host.id,
|
||||
}),
|
||||
];
|
||||
}, [details.host.id, queryParams]);
|
||||
const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseUri);
|
||||
|
||||
const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath);
|
||||
|
||||
const detailsResultsLower = useMemo(() => {
|
||||
return [
|
||||
|
@ -90,7 +100,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
|
|||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiLink
|
||||
data-test-subj="policyStatusValue"
|
||||
href={`?${policyResponseUri.search}`}
|
||||
href={policyResponseUri}
|
||||
onClick={policyStatusClickHandler}
|
||||
>
|
||||
<FormattedMessage
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { useHistory } from 'react-router-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { useHostSelector } from '../hooks';
|
||||
import { urlFromQueryParams } from '../url_from_query_params';
|
||||
import {
|
||||
|
@ -35,9 +35,10 @@ import {
|
|||
} from '../../store/selectors';
|
||||
import { HostDetails } from './host_details';
|
||||
import { PolicyResponse } from './policy_response';
|
||||
import { HostMetadata } from '../../../../common/endpoint/types';
|
||||
import { HostMetadata } from '../../../../../../common/endpoint/types';
|
||||
import { FlyoutSubHeader, FlyoutSubHeaderProps } from './components/flyout_sub_header';
|
||||
import { useNavigateByRouterEventHandler } from '../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { getManagementUrl } from '../../../..';
|
||||
|
||||
export const HostDetailsFlyout = memo(() => {
|
||||
const history = useHistory();
|
||||
|
@ -115,24 +116,32 @@ const PolicyResponseFlyoutPanel = memo<{
|
|||
const responseAttentionCount = useHostSelector(policyResponseFailedOrWarningActionCount);
|
||||
const loading = useHostSelector(policyResponseLoading);
|
||||
const error = useHostSelector(policyResponseError);
|
||||
const detailsUri = useMemo(
|
||||
() =>
|
||||
urlFromQueryParams({
|
||||
const [detailsUri, detailsRoutePath] = useMemo(
|
||||
() => [
|
||||
getManagementUrl({
|
||||
name: 'endpointList',
|
||||
...queryParams,
|
||||
selected_host: hostMeta.host.id,
|
||||
}),
|
||||
getManagementUrl({
|
||||
name: 'endpointList',
|
||||
excludePrefix: true,
|
||||
...queryParams,
|
||||
selected_host: hostMeta.host.id,
|
||||
}),
|
||||
],
|
||||
[hostMeta.host.id, queryParams]
|
||||
);
|
||||
const backToDetailsClickHandler = useNavigateByRouterEventHandler(detailsUri);
|
||||
const backToDetailsClickHandler = useNavigateByRouterEventHandler(detailsRoutePath);
|
||||
const backButtonProp = useMemo((): FlyoutSubHeaderProps['backButton'] => {
|
||||
return {
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.host.policyResponse.backLinkTitle', {
|
||||
defaultMessage: 'Endpoint Details',
|
||||
}),
|
||||
href: `?${detailsUri.search}`,
|
||||
href: detailsUri,
|
||||
onClick: backToDetailsClickHandler,
|
||||
};
|
||||
}, [backToDetailsClickHandler, detailsUri.search]);
|
||||
}, [backToDetailsClickHandler, detailsUri]);
|
||||
|
||||
return (
|
||||
<>
|
|
@ -19,7 +19,7 @@ import {
|
|||
Immutable,
|
||||
HostPolicyResponseAppliedAction,
|
||||
HostPolicyResponseConfiguration,
|
||||
} from '../../../../common/endpoint/types';
|
||||
} from '../../../../../../common/endpoint/types';
|
||||
|
||||
/**
|
||||
* Nested accordion in the policy response detailing any concerned
|
|
@ -7,12 +7,18 @@
|
|||
import { useSelector } from 'react-redux';
|
||||
import { useMemo } from 'react';
|
||||
import { HostState } from '../types';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { State } from '../../common/store/types';
|
||||
import {
|
||||
MANAGEMENT_STORE_ENDPOINTS_NAMESPACE,
|
||||
MANAGEMENT_STORE_GLOBAL_NAMESPACE,
|
||||
} from '../../../common/constants';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { State } from '../../../../common/store';
|
||||
|
||||
export function useHostSelector<TSelected>(selector: (state: HostState) => TSelected) {
|
||||
return useSelector(function (state: State) {
|
||||
return selector(state.hostList as HostState);
|
||||
return selector(
|
||||
state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_ENDPOINTS_NAMESPACE] as HostState
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostStatus, HostPolicyResponseActionStatus } from '../../../common/endpoint/types';
|
||||
import { HostStatus, HostPolicyResponseActionStatus } from '../../../../../common/endpoint/types';
|
||||
|
||||
export const HOST_STATUS_TO_HEALTH_COLOR = Object.freeze<
|
||||
{
|
|
@ -9,14 +9,14 @@ import * as reactTestingLibrary from '@testing-library/react';
|
|||
|
||||
import { HostList } from './index';
|
||||
import { mockHostDetailsApiResult, mockHostResultList } from '../store/mock_host_result_list';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../common/mock/endpoint';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint';
|
||||
import {
|
||||
HostInfo,
|
||||
HostStatus,
|
||||
HostPolicyResponseActionStatus,
|
||||
} from '../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../common/endpoint/generate_data';
|
||||
import { AppAction } from '../../common/store/actions';
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
|
||||
import { AppAction } from '../../../../common/store/actions';
|
||||
|
||||
describe('when on the hosts page', () => {
|
||||
const docGenerator = new EndpointDocGenerator();
|
||||
|
@ -202,7 +202,7 @@ describe('when on the hosts page', () => {
|
|||
const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
|
||||
expect(policyStatusLink).not.toBeNull();
|
||||
expect(policyStatusLink.getAttribute('href')).toEqual(
|
||||
'?page_index=0&page_size=10&selected_host=1&show=policy_response'
|
||||
'#/management/endpoints?page_index=0&page_size=10&selected_host=1&show=policy_response'
|
||||
);
|
||||
});
|
||||
it('should update the URL when policy status link is clicked', async () => {
|
||||
|
@ -381,7 +381,7 @@ describe('when on the hosts page', () => {
|
|||
const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton');
|
||||
expect(subHeaderBackLink.textContent).toBe('Endpoint Details');
|
||||
expect(subHeaderBackLink.getAttribute('href')).toBe(
|
||||
'?page_index=0&page_size=10&selected_host=1'
|
||||
'#/management/endpoints?page_index=0&page_size=10&selected_host=1'
|
||||
);
|
||||
});
|
||||
it('should update URL when back to details link is clicked', async () => {
|
|
@ -22,17 +22,19 @@ import { createStructuredSelector } from 'reselect';
|
|||
import { HostDetailsFlyout } from './details';
|
||||
import * as selectors from '../store/selectors';
|
||||
import { useHostSelector } from './hooks';
|
||||
import { urlFromQueryParams } from './url_from_query_params';
|
||||
import { HOST_STATUS_TO_HEALTH_COLOR } from './host_constants';
|
||||
import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { CreateStructuredSelector } from '../../common/store';
|
||||
import { Immutable, HostInfo } from '../../../common/endpoint/types';
|
||||
import { PageView } from '../../common/components/endpoint/page_view';
|
||||
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { CreateStructuredSelector } from '../../../../common/store';
|
||||
import { Immutable, HostInfo } from '../../../../../common/endpoint/types';
|
||||
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
|
||||
import { ManagementPageView } from '../../../components/management_page_view';
|
||||
import { getManagementUrl } from '../../..';
|
||||
import { FormattedDate } from '../../../../common/components/formatted_date';
|
||||
|
||||
const HostLink = memo<{
|
||||
name: string;
|
||||
href: string;
|
||||
route: ReturnType<typeof urlFromQueryParams>;
|
||||
route: string;
|
||||
}>(({ name, href, route }) => {
|
||||
const clickHandler = useNavigateByRouterEventHandler(route);
|
||||
|
||||
|
@ -77,8 +79,11 @@ export const HostList = () => {
|
|||
const onTableChange = useCallback(
|
||||
({ page }: { page: { index: number; size: number } }) => {
|
||||
const { index, size } = page;
|
||||
// FIXME: PT: if host details is open, table is not displaying correct number of rows
|
||||
history.push(
|
||||
urlFromQueryParams({
|
||||
getManagementUrl({
|
||||
name: 'endpointList',
|
||||
excludePrefix: true,
|
||||
...queryParams,
|
||||
page_index: JSON.stringify(index),
|
||||
page_size: JSON.stringify(size),
|
||||
|
@ -89,22 +94,34 @@ export const HostList = () => {
|
|||
);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<Immutable<HostInfo>>> = useMemo(() => {
|
||||
const lastActiveColumnName = i18n.translate('xpack.securitySolution.endpointList.lastActive', {
|
||||
defaultMessage: 'Last Active',
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
field: 'metadata.host',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.hostname', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.hostname', {
|
||||
defaultMessage: 'Hostname',
|
||||
}),
|
||||
render: ({ hostname, id }: HostInfo['metadata']['host']) => {
|
||||
const newQueryParams = urlFromQueryParams({ ...queryParams, selected_host: id });
|
||||
return (
|
||||
<HostLink name={hostname} href={`?${newQueryParams.search}`} route={newQueryParams} />
|
||||
);
|
||||
const toRoutePath = getManagementUrl({
|
||||
...queryParams,
|
||||
name: 'endpointDetails',
|
||||
selected_host: id,
|
||||
excludePrefix: true,
|
||||
});
|
||||
const toRouteUrl = getManagementUrl({
|
||||
...queryParams,
|
||||
name: 'endpointDetails',
|
||||
selected_host: id,
|
||||
});
|
||||
return <HostLink name={hostname} href={toRouteUrl} route={toRoutePath} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'host_status',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.hostStatus', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.hostStatus', {
|
||||
defaultMessage: 'Host Status',
|
||||
}),
|
||||
// eslint-disable-next-line react/display-name
|
||||
|
@ -116,7 +133,7 @@ export const HostList = () => {
|
|||
className="eui-textTruncate"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.host.list.hostStatusValue"
|
||||
id="xpack.securitySolution.endpointList.hostStatusValue"
|
||||
defaultMessage="{hostStatus, select, online {Online} error {Error} other {Offline}}"
|
||||
values={{ hostStatus }}
|
||||
/>
|
||||
|
@ -126,7 +143,7 @@ export const HostList = () => {
|
|||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.policy', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.policy', {
|
||||
defaultMessage: 'Policy',
|
||||
}),
|
||||
truncateText: true,
|
||||
|
@ -137,7 +154,7 @@ export const HostList = () => {
|
|||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.policyStatus', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.policyStatus', {
|
||||
defaultMessage: 'Policy Status',
|
||||
}),
|
||||
// eslint-disable-next-line react/display-name
|
||||
|
@ -145,7 +162,7 @@ export const HostList = () => {
|
|||
return (
|
||||
<EuiHealth color="success" className="eui-textTruncate">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.host.list.policyStatus"
|
||||
id="xpack.securitySolution.endpointList.policyStatus"
|
||||
defaultMessage="Policy Status"
|
||||
/>
|
||||
</EuiHealth>
|
||||
|
@ -154,7 +171,7 @@ export const HostList = () => {
|
|||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.alerts', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
dataType: 'number',
|
||||
|
@ -164,14 +181,14 @@ export const HostList = () => {
|
|||
},
|
||||
{
|
||||
field: 'metadata.host.os.name',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.os', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.os', {
|
||||
defaultMessage: 'Operating System',
|
||||
}),
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'metadata.host.ip',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.ip', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.ip', {
|
||||
defaultMessage: 'IP Address',
|
||||
}),
|
||||
// eslint-disable-next-line react/display-name
|
||||
|
@ -189,35 +206,38 @@ export const HostList = () => {
|
|||
},
|
||||
{
|
||||
field: 'metadata.agent.version',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.endpointVersion', {
|
||||
name: i18n.translate('xpack.securitySolution.endpointList.endpointVersion', {
|
||||
defaultMessage: 'Version',
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: '',
|
||||
name: i18n.translate('xpack.securitySolution.endpoint.host.list.lastActive', {
|
||||
defaultMessage: 'Last Active',
|
||||
}),
|
||||
dataType: 'date',
|
||||
render: () => {
|
||||
return 'xxxx';
|
||||
field: 'metadata.@timestamp',
|
||||
name: lastActiveColumnName,
|
||||
render(dateValue: HostInfo['metadata']['@timestamp']) {
|
||||
return (
|
||||
<FormattedDate
|
||||
fieldName={lastActiveColumnName}
|
||||
value={dateValue}
|
||||
className="eui-textTruncate"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [queryParams]);
|
||||
|
||||
return (
|
||||
<PageView
|
||||
<ManagementPageView
|
||||
viewType="list"
|
||||
data-test-subj="hostPage"
|
||||
headerLeft={i18n.translate('xpack.securitySolution.endpoint.host.hosts', {
|
||||
defaultMessage: 'Hosts',
|
||||
headerLeft={i18n.translate('xpack.securitySolution.endpointLis.pageTitle', {
|
||||
defaultMessage: 'Endpoints',
|
||||
})}
|
||||
>
|
||||
{hasSelectedHost && <HostDetailsFlyout />}
|
||||
<EuiText color="subdued" size="xs" data-test-subj="hostListTableTotal">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.host.list.totalCount"
|
||||
id="xpack.securitySolution.endpointList.totalCount"
|
||||
defaultMessage="{totalItemCount, plural, one {# Host} other {# Hosts}}"
|
||||
values={{ totalItemCount }}
|
||||
/>
|
||||
|
@ -232,6 +252,7 @@ export const HostList = () => {
|
|||
pagination={paginationSetup}
|
||||
onChange={onTableChange}
|
||||
/>
|
||||
</PageView>
|
||||
<SpyRoute />
|
||||
</ManagementPageView>
|
||||
);
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
import querystring from 'querystring';
|
||||
|
||||
import { HostIndexUIQueryParams } from '../types';
|
||||
import { AppLocation } from '../../../common/endpoint/types';
|
||||
import { AppLocation } from '../../../../../common/endpoint/types';
|
||||
|
||||
export function urlFromQueryParams(queryParams: HostIndexUIQueryParams): Partial<AppLocation> {
|
||||
const search = querystring.stringify(queryParams);
|
|
@ -6,34 +6,27 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { SpyRoute } from '../../common/utils/route/spy_routes';
|
||||
import { PolicyContainer } from './policy';
|
||||
import {
|
||||
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICIES_PATH,
|
||||
MANAGEMENT_ROUTING_ROOT_PATH,
|
||||
} from '../common/constants';
|
||||
import { ManagementPageView } from '../components/management_page_view';
|
||||
import { NotFoundPage } from '../../app/404';
|
||||
|
||||
const TmpEndpoints = () => {
|
||||
return (
|
||||
<ManagementPageView viewType="list" headerLeft="Test">
|
||||
<h1>{'Endpoints will go here'}</h1>
|
||||
<SpyRoute />
|
||||
</ManagementPageView>
|
||||
);
|
||||
};
|
||||
import { EndpointsContainer } from './endpoint_hosts';
|
||||
import { getManagementUrl } from '..';
|
||||
|
||||
export const ManagementContainer = memo(() => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={MANAGEMENT_ROUTING_ENDPOINTS_PATH} exact component={TmpEndpoints} />
|
||||
<Route path={MANAGEMENT_ROUTING_ENDPOINTS_PATH} component={EndpointsContainer} />
|
||||
<Route path={MANAGEMENT_ROUTING_POLICIES_PATH} component={PolicyContainer} />
|
||||
<Route
|
||||
path={MANAGEMENT_ROUTING_ROOT_PATH}
|
||||
exact
|
||||
render={() => <Redirect to="/management/endpoints" />}
|
||||
render={() => (
|
||||
<Redirect to={getManagementUrl({ name: 'endpointList', excludePrefix: true })} />
|
||||
)}
|
||||
/>
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
|
|
|
@ -11,16 +11,34 @@ import {
|
|||
} from '../../common/store';
|
||||
import { policyListMiddlewareFactory } from '../pages/policy/store/policy_list';
|
||||
import { policyDetailsMiddlewareFactory } from '../pages/policy/store/policy_details';
|
||||
import {
|
||||
MANAGEMENT_STORE_ENDPOINTS_NAMESPACE,
|
||||
MANAGEMENT_STORE_GLOBAL_NAMESPACE,
|
||||
MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE,
|
||||
MANAGEMENT_STORE_POLICY_LIST_NAMESPACE,
|
||||
} from '../common/constants';
|
||||
import { hostMiddlewareFactory } from '../pages/endpoint_hosts/store/middleware';
|
||||
|
||||
const policyListSelector = (state: State) =>
|
||||
state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_POLICY_LIST_NAMESPACE];
|
||||
const policyDetailsSelector = (state: State) =>
|
||||
state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE];
|
||||
const endpointsSelector = (state: State) =>
|
||||
state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_ENDPOINTS_NAMESPACE];
|
||||
|
||||
export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = (
|
||||
coreStart,
|
||||
depsStart
|
||||
) => {
|
||||
const listSelector = (state: State) => state.management.policyList;
|
||||
const detailSelector = (state: State) => state.management.policyDetails;
|
||||
|
||||
return [
|
||||
substateMiddlewareFactory(listSelector, policyListMiddlewareFactory(coreStart, depsStart)),
|
||||
substateMiddlewareFactory(detailSelector, policyDetailsMiddlewareFactory(coreStart, depsStart)),
|
||||
substateMiddlewareFactory(
|
||||
policyListSelector,
|
||||
policyListMiddlewareFactory(coreStart, depsStart)
|
||||
),
|
||||
substateMiddlewareFactory(
|
||||
policyDetailsSelector,
|
||||
policyDetailsMiddlewareFactory(coreStart, depsStart)
|
||||
),
|
||||
substateMiddlewareFactory(endpointsSelector, hostMiddlewareFactory(coreStart, depsStart)),
|
||||
];
|
||||
};
|
||||
|
|
|
@ -14,18 +14,24 @@ import {
|
|||
initialPolicyListState,
|
||||
} from '../pages/policy/store/policy_list/reducer';
|
||||
import {
|
||||
MANAGEMENT_STORE_ENDPOINTS_NAMESPACE,
|
||||
MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE,
|
||||
MANAGEMENT_STORE_POLICY_LIST_NAMESPACE,
|
||||
} from '../common/constants';
|
||||
import { ImmutableCombineReducers } from '../../common/store';
|
||||
import { Immutable } from '../../../common/endpoint/types';
|
||||
import { ManagementState } from '../types';
|
||||
import { hostListReducer, initialHostListState } from '../pages/endpoint_hosts/store/reducer';
|
||||
|
||||
const immutableCombineReducers: ImmutableCombineReducers = combineReducers;
|
||||
|
||||
/**
|
||||
* Returns the initial state of the store for the SIEM Management section
|
||||
*/
|
||||
export const mockManagementState: Immutable<ManagementState> = {
|
||||
policyList: initialPolicyListState(),
|
||||
policyDetails: initialPolicyDetailsState(),
|
||||
[MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: initialPolicyListState(),
|
||||
[MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(),
|
||||
[MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialHostListState,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -34,4 +40,6 @@ export const mockManagementState: Immutable<ManagementState> = {
|
|||
export const managementReducer = immutableCombineReducers({
|
||||
[MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: policyListReducer,
|
||||
[MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer,
|
||||
// @ts-ignore
|
||||
[MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: hostListReducer,
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { CombinedState } from 'redux';
|
||||
import { SiemPageName } from '../app/types';
|
||||
import { PolicyListState, PolicyDetailsState } from './pages/policy/types';
|
||||
import { HostState } from './pages/endpoint_hosts/types';
|
||||
|
||||
/**
|
||||
* The type for the management store global namespace. Used mostly internally to reference
|
||||
|
@ -17,6 +18,7 @@ export type ManagementStoreGlobalNamespace = 'management';
|
|||
export type ManagementState = CombinedState<{
|
||||
policyList: PolicyListState;
|
||||
policyDetails: PolicyDetailsState;
|
||||
endpoints: HostState;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,7 +65,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
const overviewSubPlugin = new (await import('./overview')).Overview();
|
||||
const timelinesSubPlugin = new (await import('./timelines')).Timelines();
|
||||
const endpointAlertsSubPlugin = new (await import('./endpoint_alerts')).EndpointAlerts();
|
||||
const endpointHostsSubPlugin = new (await import('./endpoint_hosts')).EndpointHosts();
|
||||
const managementSubPlugin = new (await import('./management')).Management();
|
||||
|
||||
const alertsStart = alertsSubPlugin.start();
|
||||
|
@ -75,7 +74,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
const overviewStart = overviewSubPlugin.start();
|
||||
const timelinesStart = timelinesSubPlugin.start();
|
||||
const endpointAlertsStart = endpointAlertsSubPlugin.start(coreStart, startPlugins);
|
||||
const endpointHostsStart = endpointHostsSubPlugin.start(coreStart, startPlugins);
|
||||
const managementSubPluginStart = managementSubPlugin.start(coreStart, startPlugins);
|
||||
|
||||
return renderApp(services, params, {
|
||||
|
@ -87,7 +85,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
...overviewStart.routes,
|
||||
...timelinesStart.routes,
|
||||
...endpointAlertsStart.routes,
|
||||
...endpointHostsStart.routes,
|
||||
...managementSubPluginStart.routes,
|
||||
],
|
||||
store: {
|
||||
|
@ -96,7 +93,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
...networkStart.store.initialState,
|
||||
...timelinesStart.store.initialState,
|
||||
...endpointAlertsStart.store.initialState,
|
||||
...endpointHostsStart.store.initialState,
|
||||
...managementSubPluginStart.store.initialState,
|
||||
},
|
||||
reducer: {
|
||||
|
@ -104,12 +100,10 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
...networkStart.store.reducer,
|
||||
...timelinesStart.store.reducer,
|
||||
...endpointAlertsStart.store.reducer,
|
||||
...endpointHostsStart.store.reducer,
|
||||
...managementSubPluginStart.store.reducer,
|
||||
},
|
||||
middlewares: [
|
||||
...(endpointAlertsStart.store.middleware ?? []),
|
||||
...(endpointHostsStart.store.middleware ?? []),
|
||||
...(managementSubPluginStart.store.middleware ?? []),
|
||||
],
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue