mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Convert isolate host to standalone flyout (#211853)
## Summary Ref: https://github.com/elastic/kibana/issues/207596 This PR fixed a bug where when user has a alert open in preview, then clicks isolate/release host, the panel opens in the background. This is because the isolate host panel was called via `openRightPanel`, which only replaces the panel and not opens a new flyout. To make the isolate host flyout consistent with other actions, this PR converts the panel into a normal EUI flyout. https://github.com/user-attachments/assets/7da4baa0-61ee-4166-9ff1-57c1078a1547 **How to test** - Apply license (platinum+) - To enable the isolate/release host button, you can use this command `yarn test:generate --fleet --withNewUser=test:changeme` ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
6d6db2fe66
commit
74e1320276
7 changed files with 220 additions and 39 deletions
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { AppContextTestRender } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer, endpointAlertDataMock } from '../../../common/mock/endpoint';
|
||||
import { FLYOUT_HOST_ISOLATION_PANEL_TEST_ID } from './test_ids';
|
||||
import { IsolateHostPanelContent } from './content';
|
||||
|
||||
describe('<IsolateHostPanelContent />', () => {
|
||||
let appContextMock: AppContextTestRender;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
appContextMock = createAppRootMockRenderer();
|
||||
|
||||
appContextMock.setExperimentalFlag({
|
||||
responseActionsSentinelOneV1Enabled: true,
|
||||
responseActionsCrowdstrikeManualHostIsolationEnabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should display content with success banner when action is isolateHost', () => {
|
||||
const { getByTestId } = appContextMock.render(
|
||||
<IsolateHostPanelContent
|
||||
isIsolateActionSuccessBannerVisible={true}
|
||||
hostName="some-host-name"
|
||||
alertId="some-alert-id"
|
||||
isolateAction="isolateHost"
|
||||
dataFormattedForFieldBrowser={endpointAlertDataMock.generateEndpointAlertDetailsItemData()}
|
||||
showAlertDetails={() => {}}
|
||||
handleIsolationActionSuccess={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId('hostIsolateSuccessMessage')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display content without success banner when action is isolateHost and success banner is not visible', () => {
|
||||
const { getByTestId, queryByTestId } = appContextMock.render(
|
||||
<IsolateHostPanelContent
|
||||
isIsolateActionSuccessBannerVisible={false}
|
||||
hostName="some-host-name"
|
||||
alertId="some-alert-id"
|
||||
isolateAction="isolateHost"
|
||||
dataFormattedForFieldBrowser={endpointAlertDataMock.generateEndpointAlertDetailsItemData()}
|
||||
showAlertDetails={() => {}}
|
||||
handleIsolationActionSuccess={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
|
||||
expect(queryByTestId('hostIsolateSuccessMessage')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display content with success banner when action is unisolateHost', () => {
|
||||
const { getByTestId } = appContextMock.render(
|
||||
<IsolateHostPanelContent
|
||||
isIsolateActionSuccessBannerVisible={true}
|
||||
hostName="some-host-name"
|
||||
alertId="some-alert-id"
|
||||
isolateAction="unisolateHost"
|
||||
dataFormattedForFieldBrowser={endpointAlertDataMock.generateEndpointAlertDetailsItemData()}
|
||||
showAlertDetails={() => {}}
|
||||
handleIsolationActionSuccess={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId('hostUnisolateSuccessMessage')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display content without success banner when action is unisolateHost', () => {
|
||||
const { getByTestId, queryByTestId } = appContextMock.render(
|
||||
<IsolateHostPanelContent
|
||||
isIsolateActionSuccessBannerVisible={false}
|
||||
hostName="some-host-name"
|
||||
alertId="some-alert-id"
|
||||
isolateAction="unisolateHost"
|
||||
dataFormattedForFieldBrowser={endpointAlertDataMock.generateEndpointAlertDetailsItemData()}
|
||||
showAlertDetails={() => {}}
|
||||
handleIsolationActionSuccess={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId(FLYOUT_HOST_ISOLATION_PANEL_TEST_ID)).toBeInTheDocument();
|
||||
expect(queryByTestId('hostUnisolateSuccessMessage')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -8,6 +8,7 @@
|
|||
import type { FC } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys';
|
||||
import { useBasicDataFromDetailsData } from '../shared/hooks/use_basic_data_from_details_data';
|
||||
import {
|
||||
|
@ -17,6 +18,7 @@ import {
|
|||
import { useHostIsolation } from '../shared/hooks/use_host_isolation';
|
||||
import { useIsolateHostPanelContext } from './context';
|
||||
import { FlyoutBody } from '../../shared/components/flyout_body';
|
||||
import { FLYOUT_HOST_ISOLATION_PANEL_TEST_ID } from './test_ids';
|
||||
|
||||
/**
|
||||
* Document details expandable flyout section content for the isolate host component, displaying the form or the success banner
|
||||
|
@ -44,7 +46,36 @@ export const PanelContent: FC = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<FlyoutBody>
|
||||
<IsolateHostPanelContent
|
||||
isIsolateActionSuccessBannerVisible={isIsolateActionSuccessBannerVisible}
|
||||
hostName={hostName}
|
||||
alertId={alertId}
|
||||
isolateAction={isolateAction}
|
||||
dataFormattedForFieldBrowser={dataFormattedForFieldBrowser}
|
||||
showAlertDetails={showAlertDetails}
|
||||
handleIsolationActionSuccess={handleIsolationActionSuccess}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const IsolateHostPanelContent: FC<{
|
||||
isIsolateActionSuccessBannerVisible: boolean;
|
||||
hostName: string;
|
||||
alertId: string;
|
||||
isolateAction: 'isolateHost' | 'unisolateHost';
|
||||
dataFormattedForFieldBrowser: TimelineEventsDetailsItem[];
|
||||
showAlertDetails: () => void;
|
||||
handleIsolationActionSuccess: () => void;
|
||||
}> = ({
|
||||
isIsolateActionSuccessBannerVisible,
|
||||
hostName,
|
||||
alertId,
|
||||
isolateAction,
|
||||
dataFormattedForFieldBrowser,
|
||||
showAlertDetails,
|
||||
handleIsolationActionSuccess,
|
||||
}) => {
|
||||
return (
|
||||
<FlyoutBody data-test-subj={FLYOUT_HOST_ISOLATION_PANEL_TEST_ID}>
|
||||
{isIsolateActionSuccessBannerVisible && (
|
||||
<EndpointIsolateSuccess
|
||||
hostName={hostName}
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import type { IsolateHostPanelContext } from './context';
|
||||
import { useIsolateHostPanelContext } from './context';
|
||||
import { PanelHeader } from './header';
|
||||
import { PanelHeader, IsolateHostPanelHeader } from './header';
|
||||
import type { AppContextTestRender } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer, endpointAlertDataMock } from '../../../common/mock/endpoint';
|
||||
import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
|
@ -18,11 +19,11 @@ import { ISOLATE_HOST, UNISOLATE_HOST } from '../../../common/components/endpoin
|
|||
jest.mock('./context');
|
||||
|
||||
describe('Isolation Flyout PanelHeader', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderComponent: () => ReturnType<AppContextTestRender['render']>;
|
||||
|
||||
const setUseIsolateHostPanelContext = (data: Partial<IsolateHostPanelContext> = {}) => {
|
||||
const panelContextMock: IsolateHostPanelContext = {
|
||||
eventId: 'some-even-1',
|
||||
eventId: 'some-event-1',
|
||||
indexName: 'some-index-name',
|
||||
scopeId: 'some-scope-id',
|
||||
dataFormattedForFieldBrowser: endpointAlertDataMock.generateEndpointAlertDetailsItemData(),
|
||||
|
@ -41,7 +42,7 @@ describe('Isolation Flyout PanelHeader', () => {
|
|||
responseActionsCrowdstrikeManualHostIsolationEnabled: true,
|
||||
});
|
||||
|
||||
render = () => appContextMock.render(<PanelHeader />);
|
||||
renderComponent = () => appContextMock.render(<PanelHeader />);
|
||||
|
||||
setUseIsolateHostPanelContext({
|
||||
isolateAction: 'isolateHost',
|
||||
|
@ -79,10 +80,23 @@ describe('Isolation Flyout PanelHeader', () => {
|
|||
dataFormattedForFieldBrowser:
|
||||
endpointAlertDataMock.generateAlertDetailsItemDataForAgentType(agentType),
|
||||
});
|
||||
const { getByTestId } = render();
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
expect(getByTestId('flyoutHostIsolationHeaderTitle')).toHaveTextContent(title);
|
||||
expect(getByTestId('flyoutHostIsolationHeaderIntegration'));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('<IsolateHostPanelHeader />', () => {
|
||||
it('should display correct flyout header title for isolateHost', () => {
|
||||
const { getByTestId } = render(
|
||||
<IsolateHostPanelHeader
|
||||
isolateAction="isolateHost"
|
||||
data={endpointAlertDataMock.generateEndpointAlertDetailsItemData()}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('flyoutHostIsolationHeaderTitle')).toHaveTextContent(ISOLATE_HOST);
|
||||
expect(getByTestId('flyoutHostIsolationHeaderIntegration')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import type { FC } from 'react';
|
||||
import React from 'react';
|
||||
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import { AgentTypeIntegration } from '../../../common/components/endpoint/agents/agent_type_integration';
|
||||
import { useAlertResponseActionsSupport } from '../../../common/hooks/endpoint/use_alert_response_actions_support';
|
||||
import { useIsolateHostPanelContext } from './context';
|
||||
|
@ -20,6 +21,13 @@ import { ISOLATE_HOST, UNISOLATE_HOST } from '../../../common/components/endpoin
|
|||
*/
|
||||
export const PanelHeader: FC = () => {
|
||||
const { isolateAction, dataFormattedForFieldBrowser: data } = useIsolateHostPanelContext();
|
||||
return <IsolateHostPanelHeader isolateAction={isolateAction} data={data} />;
|
||||
};
|
||||
|
||||
export const IsolateHostPanelHeader: FC<{
|
||||
isolateAction: string;
|
||||
data: TimelineEventsDetailsItem[];
|
||||
}> = ({ isolateAction, data }) => {
|
||||
const {
|
||||
details: { agentType },
|
||||
} = useAlertResponseActionsSupport(data);
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
import { PREFIX } from '../../shared/test_ids';
|
||||
|
||||
export const FLYOUT_HEADER_TITLE_TEST_ID = `${PREFIX}HeaderTitle` as const;
|
||||
export const FLYOUT_HOST_ISOLATION_PANEL_TEST_ID = `${PREFIX}HostIsolationPanel` as const;
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
import type { FC } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import { useEuiTheme, EuiFlyout } from '@elastic/eui';
|
||||
import { find } from 'lodash/fp';
|
||||
import { useBasicDataFromDetailsData } from '../hooks/use_basic_data_from_details_data';
|
||||
import type { Status } from '../../../../../common/api/detection_engine';
|
||||
import { getAlertDetailsFieldValue } from '../../../../common/lib/endpoint/utils/get_event_details_field_values';
|
||||
import { TakeActionDropdown } from './take_action_dropdown';
|
||||
|
@ -18,12 +19,12 @@ import { EventFiltersFlyout } from '../../../../management/pages/event_filters/v
|
|||
import { OsqueryFlyout } from '../../../../detections/components/osquery/osquery_flyout';
|
||||
import { useDocumentDetailsContext } from '../context';
|
||||
import { useHostIsolation } from '../hooks/use_host_isolation';
|
||||
import { DocumentDetailsIsolateHostPanelKey } from '../constants/panel_keys';
|
||||
import { useRefetchByScope } from '../../right/hooks/use_refetch_by_scope';
|
||||
import { useExceptionFlyout } from '../../../../detections/components/alerts_table/timeline_actions/use_add_exception_flyout';
|
||||
import { isActiveTimeline } from '../../../../helpers';
|
||||
import { useEventFilterModal } from '../../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal';
|
||||
|
||||
import { IsolateHostPanelHeader } from '../../isolate_host/header';
|
||||
import { IsolateHostPanelContent } from '../../isolate_host/content';
|
||||
interface AlertSummaryData {
|
||||
/**
|
||||
* Status of the alert (open, closed...)
|
||||
|
@ -58,33 +59,21 @@ export const TakeActionButton: FC = () => {
|
|||
[euiTheme]
|
||||
);
|
||||
|
||||
const { closeFlyout, openRightPanel } = useExpandableFlyoutApi();
|
||||
const {
|
||||
eventId,
|
||||
indexName,
|
||||
dataFormattedForFieldBrowser,
|
||||
dataAsNestedObject,
|
||||
refetchFlyoutData,
|
||||
scopeId,
|
||||
} = useDocumentDetailsContext();
|
||||
const { closeFlyout } = useExpandableFlyoutApi();
|
||||
const { dataFormattedForFieldBrowser, dataAsNestedObject, refetchFlyoutData, scopeId } =
|
||||
useDocumentDetailsContext();
|
||||
|
||||
// host isolation interaction
|
||||
const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolation();
|
||||
const showHostIsolationPanelCallback = useCallback(
|
||||
(action: 'isolateHost' | 'unisolateHost' | undefined) => {
|
||||
showHostIsolationPanel(action);
|
||||
openRightPanel({
|
||||
id: DocumentDetailsIsolateHostPanelKey,
|
||||
params: {
|
||||
id: eventId,
|
||||
indexName,
|
||||
scopeId,
|
||||
isolateAction: action,
|
||||
},
|
||||
});
|
||||
},
|
||||
[eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel]
|
||||
);
|
||||
const {
|
||||
isolateAction,
|
||||
isHostIsolationPanelOpen,
|
||||
showHostIsolationPanel,
|
||||
isIsolateActionSuccessBannerVisible,
|
||||
handleIsolationActionSuccess,
|
||||
showAlertDetails,
|
||||
} = useHostIsolation();
|
||||
|
||||
const { hostName } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
|
||||
|
||||
const { refetch: refetchAll } = useRefetchByScope({ scopeId });
|
||||
|
||||
|
@ -174,7 +163,7 @@ export const TakeActionButton: FC = () => {
|
|||
isHostIsolationPanelOpen={isHostIsolationPanelOpen}
|
||||
onAddEventFilterClick={onAddEventFilterClick}
|
||||
onAddExceptionTypeClick={onAddExceptionTypeClick}
|
||||
onAddIsolationStatusClick={showHostIsolationPanelCallback}
|
||||
onAddIsolationStatusClick={showHostIsolationPanel}
|
||||
refetchFlyoutData={refetchFlyoutData}
|
||||
refetch={refetchAll}
|
||||
scopeId={scopeId}
|
||||
|
@ -213,6 +202,25 @@ export const TakeActionButton: FC = () => {
|
|||
ecsData={dataAsNestedObject}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isHostIsolationPanelOpen && alertId && (
|
||||
// EUI TODO: This z-index override of EuiOverlayMask is a workaround, and ideally should be resolved with a cleaner UI/UX flow long-term
|
||||
<EuiFlyout onClose={showAlertDetails} size="m" maskProps={flyoutZIndex}>
|
||||
<IsolateHostPanelHeader
|
||||
isolateAction={isolateAction}
|
||||
data={dataFormattedForFieldBrowser}
|
||||
/>
|
||||
<IsolateHostPanelContent
|
||||
isIsolateActionSuccessBannerVisible={isIsolateActionSuccessBannerVisible}
|
||||
hostName={hostName}
|
||||
alertId={alertId}
|
||||
isolateAction={isolateAction}
|
||||
dataFormattedForFieldBrowser={dataFormattedForFieldBrowser}
|
||||
showAlertDetails={showAlertDetails}
|
||||
handleIsolationActionSuccess={handleIsolationActionSuccess}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,11 +9,13 @@ import { useCallback, useMemo, useReducer } from 'react';
|
|||
import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint';
|
||||
|
||||
interface State {
|
||||
isolateAction: 'isolateHost' | 'unisolateHost';
|
||||
isHostIsolationPanelOpen: boolean;
|
||||
isIsolateActionSuccessBannerVisible: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
isolateAction: 'isolateHost',
|
||||
isHostIsolationPanelOpen: false,
|
||||
isIsolateActionSuccessBannerVisible: false,
|
||||
};
|
||||
|
@ -23,6 +25,10 @@ type HostIsolationActions =
|
|||
type: 'setIsHostIsolationPanel';
|
||||
isHostIsolationPanelOpen: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'setIsolateAction';
|
||||
isolateAction: 'isolateHost' | 'unisolateHost';
|
||||
}
|
||||
| {
|
||||
type: 'setIsIsolateActionSuccessBannerVisible';
|
||||
isIsolateActionSuccessBannerVisible: boolean;
|
||||
|
@ -30,6 +36,8 @@ type HostIsolationActions =
|
|||
|
||||
function reducer(state: State, action: HostIsolationActions) {
|
||||
switch (action.type) {
|
||||
case 'setIsolateAction':
|
||||
return { ...state, isolateAction: action.isolateAction };
|
||||
case 'setIsHostIsolationPanel':
|
||||
return { ...state, isHostIsolationPanelOpen: action.isHostIsolationPanelOpen };
|
||||
case 'setIsIsolateActionSuccessBannerVisible':
|
||||
|
@ -43,6 +51,10 @@ function reducer(state: State, action: HostIsolationActions) {
|
|||
}
|
||||
|
||||
export interface UseHostIsolationResult {
|
||||
/**
|
||||
* The action to take on the host
|
||||
*/
|
||||
isolateAction: 'isolateHost' | 'unisolateHost';
|
||||
/**
|
||||
* True if the host isolation panel is open in the flyout
|
||||
*/
|
||||
|
@ -59,21 +71,34 @@ export interface UseHostIsolationResult {
|
|||
* Callback to show the host isolation panel in the flyout
|
||||
*/
|
||||
showHostIsolationPanel: (action: 'isolateHost' | 'unisolateHost' | undefined) => void;
|
||||
/**
|
||||
* Callback to show the alert details in the flyout
|
||||
*/
|
||||
showAlertDetails: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that returns the information for a parent to render the host isolation panel in the flyout
|
||||
*/
|
||||
export const useHostIsolation = (): UseHostIsolationResult => {
|
||||
const [{ isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible }, dispatch] = useReducer(
|
||||
reducer,
|
||||
initialState
|
||||
);
|
||||
const [
|
||||
{ isolateAction, isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible },
|
||||
dispatch,
|
||||
] = useReducer(reducer, initialState);
|
||||
|
||||
const showAlertDetails = useCallback(() => {
|
||||
dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: false });
|
||||
dispatch({
|
||||
type: 'setIsIsolateActionSuccessBannerVisible',
|
||||
isIsolateActionSuccessBannerVisible: false,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const showHostIsolationPanel = useCallback(
|
||||
(action: 'isolateHost' | 'unisolateHost' | undefined) => {
|
||||
if (action === 'isolateHost' || action === 'unisolateHost') {
|
||||
dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: true });
|
||||
dispatch({ type: 'setIsolateAction', isolateAction: action });
|
||||
}
|
||||
},
|
||||
[]
|
||||
|
@ -95,15 +120,19 @@ export const useHostIsolation = (): UseHostIsolationResult => {
|
|||
|
||||
return useMemo(
|
||||
() => ({
|
||||
isolateAction,
|
||||
isHostIsolationPanelOpen,
|
||||
isIsolateActionSuccessBannerVisible,
|
||||
handleIsolationActionSuccess,
|
||||
showAlertDetails,
|
||||
showHostIsolationPanel,
|
||||
}),
|
||||
[
|
||||
isolateAction,
|
||||
isHostIsolationPanelOpen,
|
||||
isIsolateActionSuccessBannerVisible,
|
||||
handleIsolationActionSuccess,
|
||||
showAlertDetails,
|
||||
showHostIsolationPanel,
|
||||
]
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue