mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Trusted Apps Signer UI (#84628)
* Added default value for type parameter in ConditionEntry type. * Added signer field UI. Flattened a bit component structure and reused some translations. * Reverted the condition for signer option. * Fixed the import. * Removed unused translations. * Fixed the test. * Consolidated a bit the deletion and creation flows in redux.
This commit is contained in:
parent
e9ad56d1a7
commit
92db24e00c
26 changed files with 409 additions and 397 deletions
|
@ -58,7 +58,7 @@ const createNewTrustedAppForOsScheme = <O extends OperatingSystem, F extends Con
|
|||
|
||||
for (const entry of entries) {
|
||||
// unfortunately combination of generics and Type<...> for "field" causes type errors
|
||||
const { field, value } = entry as ConditionEntry<ConditionEntryField>;
|
||||
const { field, value } = entry as ConditionEntry;
|
||||
|
||||
if (usedFields.has(field)) {
|
||||
return `[${entryFieldLabels[field]}] field can only be used once`;
|
||||
|
|
|
@ -39,7 +39,7 @@ export enum ConditionEntryField {
|
|||
SIGNER = 'process.Ext.code_signature',
|
||||
}
|
||||
|
||||
export interface ConditionEntry<T extends ConditionEntryField> {
|
||||
export interface ConditionEntry<T extends ConditionEntryField = ConditionEntryField> {
|
||||
field: T;
|
||||
type: 'match';
|
||||
operator: 'included';
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ServerApiError } from '../../../../common/types';
|
||||
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
|
||||
import { AsyncResourceState } from '.';
|
||||
|
||||
|
@ -23,24 +22,6 @@ export interface TrustedAppsListData {
|
|||
totalItemsCount: number;
|
||||
}
|
||||
|
||||
/** Store State when an API request has been sent to create a new trusted app entry */
|
||||
export interface TrustedAppCreatePending {
|
||||
type: 'pending';
|
||||
data: NewTrustedApp;
|
||||
}
|
||||
|
||||
/** Store State when creation of a new Trusted APP entry was successful */
|
||||
export interface TrustedAppCreateSuccess {
|
||||
type: 'success';
|
||||
data: TrustedApp;
|
||||
}
|
||||
|
||||
/** Store State when creation of a new Trusted App Entry failed */
|
||||
export interface TrustedAppCreateFailure {
|
||||
type: 'failure';
|
||||
data: ServerApiError;
|
||||
}
|
||||
|
||||
export type ViewType = 'list' | 'grid';
|
||||
|
||||
export interface TrustedAppsListPageLocation {
|
||||
|
@ -60,11 +41,14 @@ export interface TrustedAppsListPageState {
|
|||
confirmed: boolean;
|
||||
submissionResourceState: AsyncResourceState;
|
||||
};
|
||||
createView:
|
||||
| undefined
|
||||
| TrustedAppCreatePending
|
||||
| TrustedAppCreateSuccess
|
||||
| TrustedAppCreateFailure;
|
||||
creationDialog: {
|
||||
formState?: {
|
||||
entry: NewTrustedApp;
|
||||
isValid: boolean;
|
||||
};
|
||||
confirmed: boolean;
|
||||
submissionResourceState: AsyncResourceState<TrustedApp>;
|
||||
};
|
||||
location: TrustedAppsListPageLocation;
|
||||
active: boolean;
|
||||
}
|
||||
|
|
|
@ -4,50 +4,21 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
TrustedAppCreatePending,
|
||||
TrustedAppsListPageState,
|
||||
TrustedAppCreateFailure,
|
||||
TrustedAppCreateSuccess,
|
||||
} from './trusted_apps_list_page_state';
|
||||
import {
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
Immutable,
|
||||
MacosLinuxConditionEntry,
|
||||
WindowsConditionEntry,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
|
||||
type CreateViewPossibleStates =
|
||||
| TrustedAppsListPageState['createView']
|
||||
| Immutable<TrustedAppsListPageState['createView']>;
|
||||
|
||||
export const isTrustedAppCreatePendingState = (
|
||||
data: CreateViewPossibleStates
|
||||
): data is TrustedAppCreatePending => {
|
||||
return data?.type === 'pending';
|
||||
};
|
||||
|
||||
export const isTrustedAppCreateSuccessState = (
|
||||
data: CreateViewPossibleStates
|
||||
): data is TrustedAppCreateSuccess => {
|
||||
return data?.type === 'success';
|
||||
};
|
||||
|
||||
export const isTrustedAppCreateFailureState = (
|
||||
data: CreateViewPossibleStates
|
||||
): data is TrustedAppCreateFailure => {
|
||||
return data?.type === 'failure';
|
||||
};
|
||||
|
||||
export const isWindowsTrustedAppCondition = (
|
||||
condition: ConditionEntry<ConditionEntryField>
|
||||
condition: ConditionEntry
|
||||
): condition is WindowsConditionEntry => {
|
||||
return condition.field === ConditionEntryField.SIGNER || true;
|
||||
};
|
||||
|
||||
export const isMacosLinuxTrustedAppCondition = (
|
||||
condition: ConditionEntry<ConditionEntryField>
|
||||
condition: ConditionEntry
|
||||
): condition is MacosLinuxConditionEntry => {
|
||||
return condition.field !== ConditionEntryField.SIGNER;
|
||||
};
|
||||
|
|
|
@ -6,14 +6,8 @@
|
|||
|
||||
import { Action } from 'redux';
|
||||
|
||||
import { TrustedApp } from '../../../../../common/endpoint/types';
|
||||
import {
|
||||
AsyncResourceState,
|
||||
TrustedAppCreateFailure,
|
||||
TrustedAppCreatePending,
|
||||
TrustedAppCreateSuccess,
|
||||
TrustedAppsListData,
|
||||
} from '../state';
|
||||
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types';
|
||||
import { AsyncResourceState, TrustedAppsListData } from '../state';
|
||||
|
||||
export type TrustedAppsListDataOutdated = Action<'trustedAppsListDataOutdated'>;
|
||||
|
||||
|
@ -38,20 +32,27 @@ export type TrustedAppDeletionDialogConfirmed = Action<'trustedAppDeletionDialog
|
|||
|
||||
export type TrustedAppDeletionDialogClosed = Action<'trustedAppDeletionDialogClosed'>;
|
||||
|
||||
export interface UserClickedSaveNewTrustedAppButton {
|
||||
type: 'userClickedSaveNewTrustedAppButton';
|
||||
payload: TrustedAppCreatePending;
|
||||
}
|
||||
export type TrustedAppCreationSubmissionResourceStateChanged = ResourceStateChanged<
|
||||
'trustedAppCreationSubmissionResourceStateChanged',
|
||||
TrustedApp
|
||||
>;
|
||||
|
||||
export interface ServerReturnedCreateTrustedAppSuccess {
|
||||
type: 'serverReturnedCreateTrustedAppSuccess';
|
||||
payload: TrustedAppCreateSuccess;
|
||||
}
|
||||
export type TrustedAppCreationDialogStarted = Action<'trustedAppCreationDialogStarted'> & {
|
||||
payload: {
|
||||
entry: NewTrustedApp;
|
||||
};
|
||||
};
|
||||
|
||||
export interface ServerReturnedCreateTrustedAppFailure {
|
||||
type: 'serverReturnedCreateTrustedAppFailure';
|
||||
payload: TrustedAppCreateFailure;
|
||||
}
|
||||
export type TrustedAppCreationDialogFormStateUpdated = Action<'trustedAppCreationDialogFormStateUpdated'> & {
|
||||
payload: {
|
||||
entry: NewTrustedApp;
|
||||
isValid: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type TrustedAppCreationDialogConfirmed = Action<'trustedAppCreationDialogConfirmed'>;
|
||||
|
||||
export type TrustedAppCreationDialogClosed = Action<'trustedAppCreationDialogClosed'>;
|
||||
|
||||
export type TrustedAppsPageAction =
|
||||
| TrustedAppsListDataOutdated
|
||||
|
@ -60,6 +61,8 @@ export type TrustedAppsPageAction =
|
|||
| TrustedAppDeletionDialogStarted
|
||||
| TrustedAppDeletionDialogConfirmed
|
||||
| TrustedAppDeletionDialogClosed
|
||||
| UserClickedSaveNewTrustedAppButton
|
||||
| ServerReturnedCreateTrustedAppSuccess
|
||||
| ServerReturnedCreateTrustedAppFailure;
|
||||
| TrustedAppCreationSubmissionResourceStateChanged
|
||||
| TrustedAppCreationDialogStarted
|
||||
| TrustedAppCreationDialogFormStateUpdated
|
||||
| TrustedAppCreationDialogConfirmed
|
||||
| TrustedAppCreationDialogClosed;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 {
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
NewTrustedApp,
|
||||
OperatingSystem,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
|
||||
import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../../common/constants';
|
||||
|
||||
import { TrustedAppsListPageState } from '../state';
|
||||
|
||||
export const defaultConditionEntry = (): ConditionEntry<ConditionEntryField.HASH> => ({
|
||||
field: ConditionEntryField.HASH,
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '',
|
||||
});
|
||||
|
||||
export const defaultNewTrustedApp = (): NewTrustedApp => ({
|
||||
name: '',
|
||||
os: OperatingSystem.WINDOWS,
|
||||
entries: [defaultConditionEntry()],
|
||||
description: '',
|
||||
});
|
||||
|
||||
export const initialDeletionDialogState = (): TrustedAppsListPageState['deletionDialog'] => ({
|
||||
confirmed: false,
|
||||
submissionResourceState: { type: 'UninitialisedResourceState' },
|
||||
});
|
||||
|
||||
export const initialCreationDialogState = (): TrustedAppsListPageState['creationDialog'] => ({
|
||||
confirmed: false,
|
||||
submissionResourceState: { type: 'UninitialisedResourceState' },
|
||||
});
|
||||
|
||||
export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({
|
||||
listView: {
|
||||
listResourceState: { type: 'UninitialisedResourceState' },
|
||||
freshDataTimestamp: Date.now(),
|
||||
},
|
||||
deletionDialog: initialDeletionDialogState(),
|
||||
creationDialog: initialCreationDialogState(),
|
||||
location: {
|
||||
page_index: MANAGEMENT_DEFAULT_PAGE,
|
||||
page_size: MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
show: undefined,
|
||||
view_type: 'grid',
|
||||
},
|
||||
active: false,
|
||||
});
|
|
@ -21,7 +21,8 @@ import {
|
|||
|
||||
import { TrustedAppsService } from '../service';
|
||||
import { Pagination, TrustedAppsListPageState } from '../state';
|
||||
import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer';
|
||||
import { initialTrustedAppsPageState } from './builders';
|
||||
import { trustedAppsPageReducer } from './reducer';
|
||||
import { createTrustedAppsPageMiddleware } from './middleware';
|
||||
|
||||
const initialNow = 111111;
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Immutable, PostTrustedAppCreateRequest } from '../../../../../common/endpoint/types';
|
||||
import {
|
||||
Immutable,
|
||||
PostTrustedAppCreateRequest,
|
||||
TrustedApp,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { AppAction } from '../../../../common/store/actions';
|
||||
import {
|
||||
ImmutableMiddleware,
|
||||
|
@ -23,7 +27,10 @@ import {
|
|||
TrustedAppsListPageState,
|
||||
} from '../state';
|
||||
|
||||
import { defaultNewTrustedApp } from './builders';
|
||||
|
||||
import {
|
||||
TrustedAppCreationSubmissionResourceStateChanged,
|
||||
TrustedAppDeletionSubmissionResourceStateChanged,
|
||||
TrustedAppsListResourceStateChanged,
|
||||
} from './action';
|
||||
|
@ -35,9 +42,11 @@ import {
|
|||
getLastLoadedListResourceState,
|
||||
getCurrentLocationPageIndex,
|
||||
getCurrentLocationPageSize,
|
||||
getTrustedAppCreateData,
|
||||
isCreatePending,
|
||||
needsRefreshOfListData,
|
||||
getCreationSubmissionResourceState,
|
||||
getCreationDialogFormEntry,
|
||||
isCreationDialogLocation,
|
||||
isCreationDialogFormValid,
|
||||
} from './selectors';
|
||||
|
||||
const createTrustedAppsListResourceStateChangedAction = (
|
||||
|
@ -101,6 +110,71 @@ const createTrustedAppDeletionSubmissionResourceStateChanged = (
|
|||
payload: { newState },
|
||||
});
|
||||
|
||||
const updateCreationDialogIfNeeded = (
|
||||
store: ImmutableMiddlewareAPI<TrustedAppsListPageState, AppAction>
|
||||
) => {
|
||||
const newEntry = getCreationDialogFormEntry(store.getState());
|
||||
const shouldShow = isCreationDialogLocation(store.getState());
|
||||
|
||||
if (shouldShow && !newEntry) {
|
||||
store.dispatch({
|
||||
type: 'trustedAppCreationDialogStarted',
|
||||
payload: { entry: defaultNewTrustedApp() },
|
||||
});
|
||||
} else if (!shouldShow && newEntry) {
|
||||
store.dispatch({
|
||||
type: 'trustedAppCreationDialogClosed',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const createTrustedAppCreationSubmissionResourceStateChanged = (
|
||||
newState: Immutable<AsyncResourceState<TrustedApp>>
|
||||
): Immutable<TrustedAppCreationSubmissionResourceStateChanged> => ({
|
||||
type: 'trustedAppCreationSubmissionResourceStateChanged',
|
||||
payload: { newState },
|
||||
});
|
||||
|
||||
const submitCreationIfNeeded = async (
|
||||
store: ImmutableMiddlewareAPI<TrustedAppsListPageState, AppAction>,
|
||||
trustedAppsService: TrustedAppsService
|
||||
) => {
|
||||
const submissionResourceState = getCreationSubmissionResourceState(store.getState());
|
||||
const isValid = isCreationDialogFormValid(store.getState());
|
||||
const entry = getCreationDialogFormEntry(store.getState());
|
||||
|
||||
if (isStaleResourceState(submissionResourceState) && entry !== undefined && isValid) {
|
||||
store.dispatch(
|
||||
createTrustedAppCreationSubmissionResourceStateChanged({
|
||||
type: 'LoadingResourceState',
|
||||
previousState: submissionResourceState,
|
||||
})
|
||||
);
|
||||
|
||||
try {
|
||||
store.dispatch(
|
||||
createTrustedAppCreationSubmissionResourceStateChanged({
|
||||
type: 'LoadedResourceState',
|
||||
// TODO: try to remove the cast
|
||||
data: (await trustedAppsService.createTrustedApp(entry as PostTrustedAppCreateRequest))
|
||||
.data,
|
||||
})
|
||||
);
|
||||
store.dispatch({
|
||||
type: 'trustedAppsListDataOutdated',
|
||||
});
|
||||
} catch (error) {
|
||||
store.dispatch(
|
||||
createTrustedAppCreationSubmissionResourceStateChanged({
|
||||
type: 'FailedResourceState',
|
||||
error,
|
||||
lastLoadedState: getLastLoadedResourceState(submissionResourceState),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitDeletionIfNeeded = async (
|
||||
store: ImmutableMiddlewareAPI<TrustedAppsListPageState, AppAction>,
|
||||
trustedAppsService: TrustedAppsService
|
||||
|
@ -143,40 +217,6 @@ const submitDeletionIfNeeded = async (
|
|||
}
|
||||
};
|
||||
|
||||
const createTrustedApp = async (
|
||||
store: ImmutableMiddlewareAPI<TrustedAppsListPageState, AppAction>,
|
||||
trustedAppsService: TrustedAppsService
|
||||
) => {
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
if (isCreatePending(getState())) {
|
||||
try {
|
||||
const newTrustedApp = getTrustedAppCreateData(getState());
|
||||
const createdTrustedApp = (
|
||||
await trustedAppsService.createTrustedApp(newTrustedApp as PostTrustedAppCreateRequest)
|
||||
).data;
|
||||
dispatch({
|
||||
type: 'serverReturnedCreateTrustedAppSuccess',
|
||||
payload: {
|
||||
type: 'success',
|
||||
data: createdTrustedApp,
|
||||
},
|
||||
});
|
||||
store.dispatch({
|
||||
type: 'trustedAppsListDataOutdated',
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'serverReturnedCreateTrustedAppFailure',
|
||||
payload: {
|
||||
type: 'failure',
|
||||
data: error.body || error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const createTrustedAppsPageMiddleware = (
|
||||
trustedAppsService: TrustedAppsService
|
||||
): ImmutableMiddleware<TrustedAppsListPageState, AppAction> => {
|
||||
|
@ -188,12 +228,16 @@ export const createTrustedAppsPageMiddleware = (
|
|||
await refreshListIfNeeded(store, trustedAppsService);
|
||||
}
|
||||
|
||||
if (action.type === 'trustedAppDeletionDialogConfirmed') {
|
||||
await submitDeletionIfNeeded(store, trustedAppsService);
|
||||
if (action.type === 'userChangedUrl') {
|
||||
updateCreationDialogIfNeeded(store);
|
||||
}
|
||||
|
||||
if (action.type === 'userClickedSaveNewTrustedAppButton') {
|
||||
createTrustedApp(store, trustedAppsService);
|
||||
if (action.type === 'trustedAppCreationDialogConfirmed') {
|
||||
await submitCreationIfNeeded(store, trustedAppsService);
|
||||
}
|
||||
|
||||
if (action.type === 'trustedAppDeletionDialogConfirmed') {
|
||||
await submitDeletionIfNeeded(store, trustedAppsService);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
import { AsyncResourceState } from '../state';
|
||||
import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer';
|
||||
import { initialTrustedAppsPageState } from './builders';
|
||||
import { trustedAppsPageReducer } from './reducer';
|
||||
import {
|
||||
createSampleTrustedApp,
|
||||
createListLoadedResourceState,
|
||||
|
|
|
@ -13,25 +13,28 @@ import { UserChangedUrl } from '../../../../common/store/routing/action';
|
|||
import { AppAction } from '../../../../common/store/actions';
|
||||
import { extractTrustedAppsListPageLocation } from '../../../common/routing';
|
||||
|
||||
import {
|
||||
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
|
||||
MANAGEMENT_DEFAULT_PAGE,
|
||||
MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
} from '../../../common/constants';
|
||||
import { MANAGEMENT_ROUTING_TRUSTED_APPS_PATH } from '../../../common/constants';
|
||||
|
||||
import {
|
||||
TrustedAppDeletionDialogClosed,
|
||||
TrustedAppDeletionDialogConfirmed,
|
||||
TrustedAppDeletionDialogStarted,
|
||||
TrustedAppDeletionSubmissionResourceStateChanged,
|
||||
TrustedAppCreationSubmissionResourceStateChanged,
|
||||
TrustedAppsListDataOutdated,
|
||||
TrustedAppsListResourceStateChanged,
|
||||
ServerReturnedCreateTrustedAppFailure,
|
||||
ServerReturnedCreateTrustedAppSuccess,
|
||||
UserClickedSaveNewTrustedAppButton,
|
||||
TrustedAppCreationDialogStarted,
|
||||
TrustedAppCreationDialogFormStateUpdated,
|
||||
TrustedAppCreationDialogConfirmed,
|
||||
TrustedAppCreationDialogClosed,
|
||||
} from './action';
|
||||
|
||||
import { TrustedAppsListPageState } from '../state';
|
||||
import {
|
||||
initialCreationDialogState,
|
||||
initialDeletionDialogState,
|
||||
initialTrustedAppsPageState,
|
||||
} from './builders';
|
||||
|
||||
type StateReducer = ImmutableReducer<TrustedAppsListPageState, AppAction>;
|
||||
type CaseReducer<T extends AppAction> = (
|
||||
|
@ -49,26 +52,14 @@ const isTrustedAppsPageLocation = (location: Immutable<AppLocation>) => {
|
|||
};
|
||||
|
||||
const trustedAppsListDataOutdated: CaseReducer<TrustedAppsListDataOutdated> = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
listView: {
|
||||
...state.listView,
|
||||
freshDataTimestamp: Date.now(),
|
||||
},
|
||||
};
|
||||
return { ...state, listView: { ...state.listView, freshDataTimestamp: Date.now() } };
|
||||
};
|
||||
|
||||
const trustedAppsListResourceStateChanged: CaseReducer<TrustedAppsListResourceStateChanged> = (
|
||||
state,
|
||||
action
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
listView: {
|
||||
...state.listView,
|
||||
listResourceState: action.payload.newState,
|
||||
},
|
||||
};
|
||||
return { ...state, listView: { ...state.listView, listResourceState: action.payload.newState } };
|
||||
};
|
||||
|
||||
const trustedAppDeletionSubmissionResourceStateChanged: CaseReducer<TrustedAppDeletionSubmissionResourceStateChanged> = (
|
||||
|
@ -85,78 +76,72 @@ const trustedAppDeletionDialogStarted: CaseReducer<TrustedAppDeletionDialogStart
|
|||
state,
|
||||
action
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
deletionDialog: {
|
||||
entry: action.payload.entry,
|
||||
confirmed: false,
|
||||
submissionResourceState: { type: 'UninitialisedResourceState' },
|
||||
},
|
||||
};
|
||||
return { ...state, deletionDialog: { ...initialDeletionDialogState(), ...action.payload } };
|
||||
};
|
||||
|
||||
const trustedAppDeletionDialogConfirmed: CaseReducer<TrustedAppDeletionDialogConfirmed> = (
|
||||
state,
|
||||
action
|
||||
state
|
||||
) => {
|
||||
return { ...state, deletionDialog: { ...state.deletionDialog, confirmed: true } };
|
||||
};
|
||||
|
||||
const trustedAppDeletionDialogClosed: CaseReducer<TrustedAppDeletionDialogClosed> = (
|
||||
const trustedAppDeletionDialogClosed: CaseReducer<TrustedAppDeletionDialogClosed> = (state) => {
|
||||
return { ...state, deletionDialog: initialDeletionDialogState() };
|
||||
};
|
||||
|
||||
const trustedAppCreationSubmissionResourceStateChanged: CaseReducer<TrustedAppCreationSubmissionResourceStateChanged> = (
|
||||
state,
|
||||
action
|
||||
) => {
|
||||
return { ...state, deletionDialog: initialDeletionDialogState() };
|
||||
return {
|
||||
...state,
|
||||
creationDialog: { ...state.creationDialog, submissionResourceState: action.payload.newState },
|
||||
};
|
||||
};
|
||||
|
||||
const trustedAppCreationDialogStarted: CaseReducer<TrustedAppCreationDialogStarted> = (
|
||||
state,
|
||||
action
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
creationDialog: {
|
||||
...initialCreationDialogState(),
|
||||
formState: { ...action.payload, isValid: true },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const trustedAppCreationDialogFormStateUpdated: CaseReducer<TrustedAppCreationDialogFormStateUpdated> = (
|
||||
state,
|
||||
action
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
creationDialog: { ...state.creationDialog, formState: { ...action.payload } },
|
||||
};
|
||||
};
|
||||
|
||||
const trustedAppCreationDialogConfirmed: CaseReducer<TrustedAppCreationDialogConfirmed> = (
|
||||
state
|
||||
) => {
|
||||
return { ...state, creationDialog: { ...state.creationDialog, confirmed: true } };
|
||||
};
|
||||
|
||||
const trustedAppCreationDialogClosed: CaseReducer<TrustedAppCreationDialogClosed> = (state) => {
|
||||
return { ...state, creationDialog: initialCreationDialogState() };
|
||||
};
|
||||
|
||||
const userChangedUrl: CaseReducer<UserChangedUrl> = (state, action) => {
|
||||
if (isTrustedAppsPageLocation(action.payload)) {
|
||||
const parsedUrlsParams = parse(action.payload.search.slice(1));
|
||||
const location = extractTrustedAppsListPageLocation(parsedUrlsParams);
|
||||
const location = extractTrustedAppsListPageLocation(parse(action.payload.search.slice(1)));
|
||||
|
||||
return {
|
||||
...state,
|
||||
createView: location.show ? state.createView : undefined,
|
||||
active: true,
|
||||
location,
|
||||
};
|
||||
return { ...state, active: true, location };
|
||||
} else {
|
||||
return initialTrustedAppsPageState();
|
||||
}
|
||||
};
|
||||
|
||||
const trustedAppsCreateResourceChanged: CaseReducer<
|
||||
| UserClickedSaveNewTrustedAppButton
|
||||
| ServerReturnedCreateTrustedAppFailure
|
||||
| ServerReturnedCreateTrustedAppSuccess
|
||||
> = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
createView: action.payload,
|
||||
};
|
||||
};
|
||||
|
||||
const initialDeletionDialogState = (): TrustedAppsListPageState['deletionDialog'] => ({
|
||||
confirmed: false,
|
||||
submissionResourceState: { type: 'UninitialisedResourceState' },
|
||||
});
|
||||
|
||||
export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({
|
||||
listView: {
|
||||
listResourceState: { type: 'UninitialisedResourceState' },
|
||||
freshDataTimestamp: Date.now(),
|
||||
},
|
||||
deletionDialog: initialDeletionDialogState(),
|
||||
createView: undefined,
|
||||
location: {
|
||||
page_index: MANAGEMENT_DEFAULT_PAGE,
|
||||
page_size: MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
show: undefined,
|
||||
view_type: 'grid',
|
||||
},
|
||||
active: false,
|
||||
});
|
||||
|
||||
export const trustedAppsPageReducer: StateReducer = (
|
||||
state = initialTrustedAppsPageState(),
|
||||
action
|
||||
|
@ -180,13 +165,23 @@ export const trustedAppsPageReducer: StateReducer = (
|
|||
case 'trustedAppDeletionDialogClosed':
|
||||
return trustedAppDeletionDialogClosed(state, action);
|
||||
|
||||
case 'trustedAppCreationSubmissionResourceStateChanged':
|
||||
return trustedAppCreationSubmissionResourceStateChanged(state, action);
|
||||
|
||||
case 'trustedAppCreationDialogStarted':
|
||||
return trustedAppCreationDialogStarted(state, action);
|
||||
|
||||
case 'trustedAppCreationDialogFormStateUpdated':
|
||||
return trustedAppCreationDialogFormStateUpdated(state, action);
|
||||
|
||||
case 'trustedAppCreationDialogConfirmed':
|
||||
return trustedAppCreationDialogConfirmed(state, action);
|
||||
|
||||
case 'trustedAppCreationDialogClosed':
|
||||
return trustedAppCreationDialogClosed(state, action);
|
||||
|
||||
case 'userChangedUrl':
|
||||
return userChangedUrl(state, action);
|
||||
|
||||
case 'userClickedSaveNewTrustedAppButton':
|
||||
case 'serverReturnedCreateTrustedAppSuccess':
|
||||
case 'serverReturnedCreateTrustedAppFailure':
|
||||
return trustedAppsCreateResourceChanged(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
TrustedAppsListPageLocation,
|
||||
TrustedAppsListPageState,
|
||||
} from '../state';
|
||||
import { initialTrustedAppsPageState } from './reducer';
|
||||
import { initialTrustedAppsPageState } from './builders';
|
||||
import {
|
||||
getListResourceState,
|
||||
getLastLoadedListResourceState,
|
||||
|
|
|
@ -18,16 +18,10 @@ import {
|
|||
isOutdatedResourceState,
|
||||
LoadedResourceState,
|
||||
Pagination,
|
||||
TrustedAppCreateFailure,
|
||||
TrustedAppsListData,
|
||||
TrustedAppsListPageLocation,
|
||||
TrustedAppsListPageState,
|
||||
} from '../state';
|
||||
import {
|
||||
isTrustedAppCreateFailureState,
|
||||
isTrustedAppCreatePendingState,
|
||||
isTrustedAppCreateSuccessState,
|
||||
} from '../state/type_guards';
|
||||
|
||||
export const needsRefreshOfListData = (state: Immutable<TrustedAppsListPageState>): boolean => {
|
||||
const freshDataTimestamp = state.listView.freshDataTimestamp;
|
||||
|
@ -133,26 +127,38 @@ export const getDeletionDialogEntry = (
|
|||
return state.deletionDialog.entry;
|
||||
};
|
||||
|
||||
export const isCreatePending: (state: Immutable<TrustedAppsListPageState>) => boolean = ({
|
||||
createView,
|
||||
}) => {
|
||||
return isTrustedAppCreatePendingState(createView);
|
||||
export const isCreationDialogLocation = (state: Immutable<TrustedAppsListPageState>): boolean => {
|
||||
return state.location.show === 'create';
|
||||
};
|
||||
|
||||
export const getTrustedAppCreateData: (
|
||||
export const getCreationSubmissionResourceState = (
|
||||
state: Immutable<TrustedAppsListPageState>
|
||||
) => undefined | Immutable<NewTrustedApp> = ({ createView }) => {
|
||||
return (isTrustedAppCreatePendingState(createView) && createView.data) || undefined;
|
||||
): Immutable<AsyncResourceState<TrustedApp>> => {
|
||||
return state.creationDialog.submissionResourceState;
|
||||
};
|
||||
|
||||
export const getApiCreateErrors: (
|
||||
export const getCreationDialogFormEntry = (
|
||||
state: Immutable<TrustedAppsListPageState>
|
||||
) => undefined | TrustedAppCreateFailure['data'] = ({ createView }) => {
|
||||
return (isTrustedAppCreateFailureState(createView) && createView.data) || undefined;
|
||||
): Immutable<NewTrustedApp> | undefined => {
|
||||
return state.creationDialog.formState?.entry;
|
||||
};
|
||||
|
||||
export const wasCreateSuccessful: (state: Immutable<TrustedAppsListPageState>) => boolean = ({
|
||||
createView,
|
||||
}) => {
|
||||
return isTrustedAppCreateSuccessState(createView);
|
||||
export const isCreationDialogFormValid = (state: Immutable<TrustedAppsListPageState>): boolean => {
|
||||
return state.creationDialog.formState?.isValid || false;
|
||||
};
|
||||
|
||||
export const isCreationInProgress = (state: Immutable<TrustedAppsListPageState>): boolean => {
|
||||
return isLoadingResourceState(state.creationDialog.submissionResourceState);
|
||||
};
|
||||
|
||||
export const isCreationSuccessful = (state: Immutable<TrustedAppsListPageState>): boolean => {
|
||||
return isLoadedResourceState(state.creationDialog.submissionResourceState);
|
||||
};
|
||||
|
||||
export const getCreationError = (
|
||||
state: Immutable<TrustedAppsListPageState>
|
||||
): Immutable<ServerApiError> | undefined => {
|
||||
const submissionResourceState = state.creationDialog.submissionResourceState;
|
||||
|
||||
return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined;
|
||||
};
|
||||
|
|
|
@ -7,17 +7,22 @@
|
|||
import React, { ChangeEventHandler, memo, useCallback, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiSuperSelect,
|
||||
EuiFieldText,
|
||||
EuiButtonIcon,
|
||||
EuiSuperSelectOption,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ConditionEntryField, TrustedApp } from '../../../../../../../../common/endpoint/types';
|
||||
import { CONDITION_FIELD_TITLE } from '../../../translations';
|
||||
import {
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
OperatingSystem,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
|
||||
import { CONDITION_FIELD_TITLE, ENTRY_PROPERTY_TITLES, OPERATOR_TITLE } from '../../translations';
|
||||
|
||||
const ConditionEntryCell = memo<{
|
||||
showLabel: boolean;
|
||||
|
@ -35,25 +40,27 @@ const ConditionEntryCell = memo<{
|
|||
|
||||
ConditionEntryCell.displayName = 'ConditionEntryCell';
|
||||
|
||||
export interface ConditionEntryProps {
|
||||
os: TrustedApp['os'];
|
||||
entry: TrustedApp['entries'][0];
|
||||
export interface ConditionEntryInputProps {
|
||||
os: OperatingSystem;
|
||||
entry: ConditionEntry;
|
||||
/** controls if remove button is enabled/disabled */
|
||||
isRemoveDisabled?: boolean;
|
||||
/** If the labels for each Column in the input row should be shown. Normally set on the first row entry */
|
||||
showLabels: boolean;
|
||||
onRemove: (entry: TrustedApp['entries'][0]) => void;
|
||||
onChange: (newEntry: TrustedApp['entries'][0], oldEntry: TrustedApp['entries'][0]) => void;
|
||||
onRemove: (entry: ConditionEntry) => void;
|
||||
onChange: (newEntry: ConditionEntry, oldEntry: ConditionEntry) => void;
|
||||
/**
|
||||
* invoked when at least one field in the entry was visited (triggered when `onBlur` DOM event is dispatched)
|
||||
* For this component, that will be triggered only when the `value` field is visited, since that is the
|
||||
* only one needs user input.
|
||||
*/
|
||||
onVisited?: (entry: TrustedApp['entries'][0]) => void;
|
||||
onVisited?: (entry: ConditionEntry) => void;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
export const ConditionEntry = memo<ConditionEntryProps>(
|
||||
|
||||
export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
||||
({
|
||||
os,
|
||||
entry,
|
||||
showLabels = false,
|
||||
onRemove,
|
||||
|
@ -62,14 +69,9 @@ export const ConditionEntry = memo<ConditionEntryProps>(
|
|||
onVisited,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const getTestId = useCallback(
|
||||
(suffix: string): string | undefined => {
|
||||
if (dataTestSubj) {
|
||||
return `${dataTestSubj}-${suffix}`;
|
||||
}
|
||||
},
|
||||
[dataTestSubj]
|
||||
);
|
||||
const getTestId = useCallback((suffix: string) => dataTestSubj && `${dataTestSubj}-${suffix}`, [
|
||||
dataTestSubj,
|
||||
]);
|
||||
|
||||
const fieldOptions = useMemo<Array<EuiSuperSelectOption<string>>>(() => {
|
||||
return [
|
||||
|
@ -81,38 +83,28 @@ export const ConditionEntry = memo<ConditionEntryProps>(
|
|||
inputDisplay: CONDITION_FIELD_TITLE[ConditionEntryField.PATH],
|
||||
value: ConditionEntryField.PATH,
|
||||
},
|
||||
...(os === OperatingSystem.WINDOWS
|
||||
? [
|
||||
{
|
||||
inputDisplay: CONDITION_FIELD_TITLE[ConditionEntryField.SIGNER],
|
||||
value: ConditionEntryField.SIGNER,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}, []);
|
||||
}, [os]);
|
||||
|
||||
const handleValueUpdate = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
||||
(ev) => {
|
||||
onChange(
|
||||
{
|
||||
...entry,
|
||||
value: ev.target.value,
|
||||
},
|
||||
entry
|
||||
);
|
||||
},
|
||||
(ev) => onChange({ ...entry, value: ev.target.value }, entry),
|
||||
[entry, onChange]
|
||||
);
|
||||
|
||||
const handleFieldUpdate = useCallback(
|
||||
(newField) => {
|
||||
onChange(
|
||||
{
|
||||
...entry,
|
||||
field: newField,
|
||||
},
|
||||
entry
|
||||
);
|
||||
},
|
||||
(newField) => onChange({ ...entry, field: newField }, entry),
|
||||
[entry, onChange]
|
||||
);
|
||||
|
||||
const handleRemoveClick = useCallback(() => {
|
||||
onRemove(entry);
|
||||
}, [entry, onRemove]);
|
||||
const handleRemoveClick = useCallback(() => onRemove(entry), [entry, onRemove]);
|
||||
|
||||
const handleValueOnBlur = useCallback(() => {
|
||||
if (onVisited) {
|
||||
|
@ -129,13 +121,7 @@ export const ConditionEntry = memo<ConditionEntryProps>(
|
|||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={2}>
|
||||
<ConditionEntryCell
|
||||
showLabel={showLabels}
|
||||
label={i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field',
|
||||
{ defaultMessage: 'Field' }
|
||||
)}
|
||||
>
|
||||
<ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.field}>
|
||||
<EuiSuperSelect
|
||||
options={fieldOptions}
|
||||
valueOfSelected={entry.field}
|
||||
|
@ -145,31 +131,12 @@ export const ConditionEntry = memo<ConditionEntryProps>(
|
|||
</ConditionEntryCell>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ConditionEntryCell
|
||||
showLabel={showLabels}
|
||||
label={i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.operator',
|
||||
{ defaultMessage: 'Operator' }
|
||||
)}
|
||||
>
|
||||
<EuiFieldText
|
||||
name="operator"
|
||||
value={i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.operator.is',
|
||||
{ defaultMessage: 'is' }
|
||||
)}
|
||||
readOnly
|
||||
/>
|
||||
<ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.operator}>
|
||||
<EuiFieldText name="operator" value={OPERATOR_TITLE.included} readOnly />
|
||||
</ConditionEntryCell>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<ConditionEntryCell
|
||||
showLabel={showLabels}
|
||||
label={i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.value',
|
||||
{ defaultMessage: 'Value' }
|
||||
)}
|
||||
>
|
||||
<ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.value}>
|
||||
<EuiFieldText
|
||||
name="value"
|
||||
value={entry.value}
|
||||
|
@ -202,4 +169,4 @@ export const ConditionEntry = memo<ConditionEntryProps>(
|
|||
}
|
||||
);
|
||||
|
||||
ConditionEntry.displayName = 'ConditionEntry';
|
||||
ConditionEntryInput.displayName = 'ConditionEntryInput';
|
|
@ -8,9 +8,9 @@ import React, { memo, useCallback } from 'react';
|
|||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiHideFor, EuiSpacer } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { TrustedApp, WindowsConditionEntry } from '../../../../../../../../common/endpoint/types';
|
||||
import { ConditionEntry, ConditionEntryProps } from './condition_entry';
|
||||
import { AndOrBadge } from '../../../../../../../common/components/and_or_badge';
|
||||
import { ConditionEntry, OperatingSystem } from '../../../../../../../common/endpoint/types';
|
||||
import { AndOrBadge } from '../../../../../../common/components/and_or_badge';
|
||||
import { ConditionEntryInput, ConditionEntryInputProps } from '../condition_entry_input';
|
||||
|
||||
const ConditionGroupFlexGroup = styled(EuiFlexGroup)`
|
||||
// The positioning of the 'and-badge' is done by using the EuiButton's height and adding on to it
|
||||
|
@ -41,14 +41,14 @@ const ConditionGroupFlexGroup = styled(EuiFlexGroup)`
|
|||
`;
|
||||
|
||||
export interface ConditionGroupProps {
|
||||
os: TrustedApp['os'];
|
||||
entries: TrustedApp['entries'];
|
||||
onEntryRemove: ConditionEntryProps['onRemove'];
|
||||
onEntryChange: ConditionEntryProps['onChange'];
|
||||
os: OperatingSystem;
|
||||
entries: ConditionEntry[];
|
||||
onEntryRemove: ConditionEntryInputProps['onRemove'];
|
||||
onEntryChange: ConditionEntryInputProps['onChange'];
|
||||
onAndClicked: () => void;
|
||||
isAndDisabled?: boolean;
|
||||
/** called when any of the entries is visited (triggered via `onBlur` DOM event) */
|
||||
onVisited?: ConditionEntryProps['onVisited'];
|
||||
onVisited?: ConditionEntryInputProps['onVisited'];
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
export const ConditionGroup = memo<ConditionGroupProps>(
|
||||
|
@ -85,8 +85,8 @@ export const ConditionGroup = memo<ConditionGroupProps>(
|
|||
)}
|
||||
<EuiFlexItem grow={1}>
|
||||
<div data-test-subj={getTestId('entries')} className="group-entries">
|
||||
{(entries as WindowsConditionEntry[]).map((entry, index) => (
|
||||
<ConditionEntry
|
||||
{(entries as ConditionEntry[]).map((entry, index) => (
|
||||
<ConditionEntryInput
|
||||
key={index}
|
||||
os={os}
|
||||
entry={entry}
|
|
@ -17,33 +17,31 @@ import {
|
|||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import React, { memo, useCallback, useEffect, useState } from 'react';
|
||||
import React, { memo, useCallback, useEffect } from 'react';
|
||||
import { EuiFlyoutProps } from '@elastic/eui/src/components/flyout/flyout';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CreateTrustedAppForm, CreateTrustedAppFormProps } from './create_trusted_app_form';
|
||||
import {
|
||||
CreateTrustedAppForm,
|
||||
CreateTrustedAppFormProps,
|
||||
TrustedAppFormState,
|
||||
} from './create_trusted_app_form';
|
||||
import { useTrustedAppsSelector } from '../hooks';
|
||||
import { getApiCreateErrors, isCreatePending, wasCreateSuccessful } from '../../store/selectors';
|
||||
getCreationError,
|
||||
isCreationDialogFormValid,
|
||||
isCreationInProgress,
|
||||
isCreationSuccessful,
|
||||
} from '../../store/selectors';
|
||||
import { AppAction } from '../../../../../common/store/actions';
|
||||
import { useToasts } from '../../../../../common/lib/kibana';
|
||||
import { useTrustedAppsSelector } from '../hooks';
|
||||
import { ABOUT_TRUSTED_APPS } from '../translations';
|
||||
|
||||
type CreateTrustedAppFlyoutProps = Omit<EuiFlyoutProps, 'hideCloseButton'>;
|
||||
export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
||||
({ onClose, ...flyoutProps }) => {
|
||||
const dispatch = useDispatch<(action: AppAction) => void>();
|
||||
const toasts = useToasts();
|
||||
|
||||
const pendingCreate = useTrustedAppsSelector(isCreatePending);
|
||||
const apiErrors = useTrustedAppsSelector(getApiCreateErrors);
|
||||
const wasCreated = useTrustedAppsSelector(wasCreateSuccessful);
|
||||
const creationInProgress = useTrustedAppsSelector(isCreationInProgress);
|
||||
const creationErrors = useTrustedAppsSelector(getCreationError);
|
||||
const creationSuccessful = useTrustedAppsSelector(isCreationSuccessful);
|
||||
const isFormValid = useTrustedAppsSelector(isCreationDialogFormValid);
|
||||
|
||||
const [formState, setFormState] = useState<undefined | TrustedAppFormState>();
|
||||
const dataTestSubj = flyoutProps['data-test-subj'];
|
||||
|
||||
const getTestId = useCallback(
|
||||
|
@ -55,47 +53,34 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
|||
[dataTestSubj]
|
||||
);
|
||||
const handleCancelClick = useCallback(() => {
|
||||
if (pendingCreate) {
|
||||
if (creationInProgress) {
|
||||
return;
|
||||
}
|
||||
onClose();
|
||||
}, [onClose, pendingCreate]);
|
||||
const handleSaveClick = useCallback(() => {
|
||||
if (formState) {
|
||||
dispatch({
|
||||
type: 'userClickedSaveNewTrustedAppButton',
|
||||
payload: {
|
||||
type: 'pending',
|
||||
data: formState.item,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [dispatch, formState]);
|
||||
}, [onClose, creationInProgress]);
|
||||
const handleSaveClick = useCallback(
|
||||
() => dispatch({ type: 'trustedAppCreationDialogConfirmed' }),
|
||||
[dispatch]
|
||||
);
|
||||
const handleFormOnChange = useCallback<CreateTrustedAppFormProps['onChange']>(
|
||||
(newFormState) => {
|
||||
setFormState(newFormState);
|
||||
dispatch({
|
||||
type: 'trustedAppCreationDialogFormStateUpdated',
|
||||
payload: { entry: newFormState.item, isValid: newFormState.isValid },
|
||||
});
|
||||
},
|
||||
[]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
// If it was created, then close flyout
|
||||
useEffect(() => {
|
||||
if (wasCreated) {
|
||||
toasts.addSuccess(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.createTrustedAppFlyout.successToastTitle',
|
||||
{
|
||||
defaultMessage: '"{name}" has been added to the Trusted Applications list.',
|
||||
values: { name: formState?.item.name },
|
||||
}
|
||||
)
|
||||
);
|
||||
if (creationSuccessful) {
|
||||
onClose();
|
||||
}
|
||||
}, [formState?.item?.name, onClose, toasts, wasCreated]);
|
||||
}, [onClose, creationSuccessful]);
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={handleCancelClick} {...flyoutProps} hideCloseButton={pendingCreate}>
|
||||
<EuiFlyout onClose={handleCancelClick} {...flyoutProps} hideCloseButton={creationInProgress}>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 data-test-subj={getTestId('headerTitle')}>
|
||||
|
@ -115,8 +100,8 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
|||
<CreateTrustedAppForm
|
||||
fullWidth
|
||||
onChange={handleFormOnChange}
|
||||
isInvalid={!!apiErrors}
|
||||
error={apiErrors?.message}
|
||||
isInvalid={!!creationErrors}
|
||||
error={creationErrors?.message}
|
||||
data-test-subj={getTestId('createForm')}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
|
@ -127,7 +112,7 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
|||
<EuiButtonEmpty
|
||||
onClick={handleCancelClick}
|
||||
flush="left"
|
||||
isDisabled={pendingCreate}
|
||||
isDisabled={creationInProgress}
|
||||
data-test-subj={getTestId('cancelButton')}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -140,8 +125,8 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
|||
<EuiButton
|
||||
onClick={handleSaveClick}
|
||||
fill
|
||||
isDisabled={!formState?.isValid || pendingCreate}
|
||||
isLoading={pendingCreate}
|
||||
isDisabled={!isFormValid || creationInProgress}
|
||||
isLoading={creationInProgress}
|
||||
data-test-subj={getTestId('createButton')}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -156,4 +141,5 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
CreateTrustedAppFlyout.displayName = 'NewTrustedAppFlyout';
|
||||
|
|
|
@ -164,7 +164,7 @@ describe('When showing the Trusted App Create Form', () => {
|
|||
'.euiSuperSelect__listbox button.euiSuperSelect__item'
|
||||
)
|
||||
).map((button) => button.textContent);
|
||||
expect(options).toEqual(['Hash', 'Path']);
|
||||
expect(options).toEqual(['Hash', 'Path', 'Signature']);
|
||||
});
|
||||
|
||||
it('should show the value field as required', () => {
|
||||
|
|
|
@ -15,20 +15,18 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFormProps } from '@elastic/eui/src/components/form/form';
|
||||
import { LogicalConditionBuilder } from './logical_condition';
|
||||
import {
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
MacosLinuxConditionEntry,
|
||||
NewTrustedApp,
|
||||
OperatingSystem,
|
||||
} from '../../../../../../common/endpoint/types';
|
||||
import { LogicalConditionBuilderProps } from './logical_condition/logical_condition_builder';
|
||||
import { OS_TITLES } from '../translations';
|
||||
import {
|
||||
isMacosLinuxTrustedAppCondition,
|
||||
isWindowsTrustedAppCondition,
|
||||
} from '../../state/type_guards';
|
||||
import { defaultConditionEntry, defaultNewTrustedApp } from '../../store/builders';
|
||||
import { OS_TITLES } from '../translations';
|
||||
import { LogicalConditionBuilder, LogicalConditionBuilderProps } from './logical_condition';
|
||||
|
||||
const OPERATING_SYSTEMS: readonly OperatingSystem[] = [
|
||||
OperatingSystem.MAC,
|
||||
|
@ -36,15 +34,6 @@ const OPERATING_SYSTEMS: readonly OperatingSystem[] = [
|
|||
OperatingSystem.LINUX,
|
||||
];
|
||||
|
||||
const generateNewEntry = (): ConditionEntry<ConditionEntryField.HASH> => {
|
||||
return {
|
||||
field: ConditionEntryField.HASH,
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '',
|
||||
};
|
||||
};
|
||||
|
||||
interface FieldValidationState {
|
||||
/** If this fields state is invalid. Drives display of errors on the UI */
|
||||
isInvalid: boolean;
|
||||
|
@ -170,12 +159,7 @@ export const CreateTrustedAppForm = memo<CreateTrustedAppFormProps>(
|
|||
[]
|
||||
);
|
||||
|
||||
const [formValues, setFormValues] = useState<NewTrustedApp>({
|
||||
name: '',
|
||||
os: OperatingSystem.WINDOWS,
|
||||
entries: [generateNewEntry()],
|
||||
description: '',
|
||||
});
|
||||
const [formValues, setFormValues] = useState<NewTrustedApp>(defaultNewTrustedApp());
|
||||
|
||||
const [validationResult, setValidationResult] = useState<ValidationResult>(() =>
|
||||
validateFormValues(formValues)
|
||||
|
@ -204,7 +188,7 @@ export const CreateTrustedAppForm = memo<CreateTrustedAppFormProps>(
|
|||
if (prevState.os === OperatingSystem.WINDOWS) {
|
||||
return {
|
||||
...prevState,
|
||||
entries: [...prevState.entries, generateNewEntry()].filter(
|
||||
entries: [...prevState.entries, defaultConditionEntry()].filter(
|
||||
isWindowsTrustedAppCondition
|
||||
),
|
||||
};
|
||||
|
@ -213,7 +197,7 @@ export const CreateTrustedAppForm = memo<CreateTrustedAppFormProps>(
|
|||
...prevState,
|
||||
entries: [
|
||||
...prevState.entries.filter(isMacosLinuxTrustedAppCondition),
|
||||
generateNewEntry(),
|
||||
defaultConditionEntry(),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
@ -261,7 +245,7 @@ export const CreateTrustedAppForm = memo<CreateTrustedAppFormProps>(
|
|||
) as MacosLinuxConditionEntry[])
|
||||
);
|
||||
if (updatedState.entries.length === 0) {
|
||||
updatedState.entries.push(generateNewEntry());
|
||||
updatedState.entries.push(defaultConditionEntry());
|
||||
}
|
||||
} else {
|
||||
updatedState.entries.push(...prevState.entries);
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { LogicalConditionBuilder } from './logical_condition_builder';
|
||||
export { LogicalConditionBuilder, LogicalConditionBuilderProps } from './logical_condition_builder';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { memo, useCallback } from 'react';
|
||||
import { CommonProps, EuiText, EuiPanel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ConditionGroup, ConditionGroupProps } from './components/condition_group';
|
||||
import { ConditionGroup, ConditionGroupProps } from '../condition_group';
|
||||
|
||||
export type LogicalConditionBuilderProps = CommonProps & ConditionGroupProps;
|
||||
export const LogicalConditionBuilder = memo<LogicalConditionBuilderProps>(
|
||||
|
|
|
@ -36,7 +36,7 @@ export const CONDITION_FIELD_TITLE: { [K in ConditionEntryField]: string } = {
|
|||
),
|
||||
};
|
||||
|
||||
export const OPERATOR_TITLE: { [K in ConditionEntry<ConditionEntryField>['operator']]: string } = {
|
||||
export const OPERATOR_TITLE: { [K in ConditionEntry['operator']]: string } = {
|
||||
included: i18n.translate('xpack.securitySolution.trustedapps.card.operator.includes', {
|
||||
defaultMessage: 'is',
|
||||
}),
|
||||
|
|
|
@ -7,8 +7,14 @@ import React, { memo } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ServerApiError } from '../../../../common/types';
|
||||
import { Immutable, TrustedApp } from '../../../../../common/endpoint/types';
|
||||
import { getDeletionDialogEntry, getDeletionError, isDeletionSuccessful } from '../store/selectors';
|
||||
import { Immutable, NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types';
|
||||
import {
|
||||
getCreationDialogFormEntry,
|
||||
getDeletionDialogEntry,
|
||||
getDeletionError,
|
||||
isCreationSuccessful,
|
||||
isDeletionSuccessful,
|
||||
} from '../store/selectors';
|
||||
|
||||
import { useToasts } from '../../../../common/lib/kibana';
|
||||
import { useTrustedAppsSelector } from './hooks';
|
||||
|
@ -38,10 +44,22 @@ const getDeletionSuccessMessage = (entry: Immutable<TrustedApp>) => {
|
|||
};
|
||||
};
|
||||
|
||||
const getCreationSuccessMessage = (entry: Immutable<NewTrustedApp>) => {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.createTrustedAppFlyout.successToastTitle',
|
||||
{
|
||||
defaultMessage: '"{name}" has been added to the Trusted Applications list.',
|
||||
values: { name: entry.name },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const TrustedAppsNotifications = memo(() => {
|
||||
const deletionError = useTrustedAppsSelector(getDeletionError);
|
||||
const deletionDialogEntry = useTrustedAppsSelector(getDeletionDialogEntry);
|
||||
const deletionSuccessful = useTrustedAppsSelector(isDeletionSuccessful);
|
||||
const creationDialogNewEntry = useTrustedAppsSelector(getCreationDialogFormEntry);
|
||||
const creationSuccessful = useTrustedAppsSelector(isCreationSuccessful);
|
||||
const toasts = useToasts();
|
||||
|
||||
if (deletionError && deletionDialogEntry) {
|
||||
|
@ -52,6 +70,10 @@ export const TrustedAppsNotifications = memo(() => {
|
|||
toasts.addSuccess(getDeletionSuccessMessage(deletionDialogEntry));
|
||||
}
|
||||
|
||||
if (creationSuccessful && creationDialogNewEntry) {
|
||||
toasts.addSuccess(getCreationSuccessMessage(creationDialogNewEntry));
|
||||
}
|
||||
|
||||
return <></>;
|
||||
});
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
|
||||
renderResult = await renderAndClickAddButton();
|
||||
fillInCreateForm(renderResult);
|
||||
const userClickedSaveActionWatcher = waitForAction('userClickedSaveNewTrustedAppButton');
|
||||
const userClickedSaveActionWatcher = waitForAction('trustedAppCreationDialogConfirmed');
|
||||
reactTestingLibrary.act(() => {
|
||||
fireEvent.click(renderResult.getByTestId('addTrustedAppFlyout-createButton'), {
|
||||
button: 1,
|
||||
|
@ -225,7 +225,9 @@ describe('When on the Trusted Apps Page', () => {
|
|||
},
|
||||
};
|
||||
await reactTestingLibrary.act(async () => {
|
||||
const serverResponseAction = waitForAction('serverReturnedCreateTrustedAppSuccess');
|
||||
const serverResponseAction = waitForAction(
|
||||
'trustedAppCreationSubmissionResourceStateChanged'
|
||||
);
|
||||
coreStart.http.get.mockClear();
|
||||
resolveHttpPost(successCreateApiResponse);
|
||||
await serverResponseAction;
|
||||
|
@ -256,7 +258,9 @@ describe('When on the Trusted Apps Page', () => {
|
|||
message: 'bad call',
|
||||
};
|
||||
await reactTestingLibrary.act(async () => {
|
||||
const serverResponseAction = waitForAction('serverReturnedCreateTrustedAppFailure');
|
||||
const serverResponseAction = waitForAction(
|
||||
'trustedAppCreationSubmissionResourceStateChanged'
|
||||
);
|
||||
coreStart.http.get.mockClear();
|
||||
rejectHttpPost(failedCreateApiResponse);
|
||||
await serverResponseAction;
|
||||
|
|
|
@ -26,10 +26,8 @@ import {
|
|||
endpointListReducer,
|
||||
initialEndpointListState,
|
||||
} from '../pages/endpoint_hosts/store/reducer';
|
||||
import {
|
||||
initialTrustedAppsPageState,
|
||||
trustedAppsPageReducer,
|
||||
} from '../pages/trusted_apps/store/reducer';
|
||||
import { initialTrustedAppsPageState } from '../pages/trusted_apps/store/builders';
|
||||
import { trustedAppsPageReducer } from '../pages/trusted_apps/store/reducer';
|
||||
|
||||
const immutableCombineReducers: ImmutableCombineReducers = combineReducers;
|
||||
|
||||
|
|
|
@ -140,9 +140,7 @@ export const createEntryNested = (field: string, entries: NestedEntriesArray): E
|
|||
return { field, entries, type: 'nested' };
|
||||
};
|
||||
|
||||
export const conditionEntriesToEntries = (
|
||||
conditionEntries: Array<ConditionEntry<ConditionEntryField>>
|
||||
): EntriesArray => {
|
||||
export const conditionEntriesToEntries = (conditionEntries: ConditionEntry[]): EntriesArray => {
|
||||
return conditionEntries.map((conditionEntry) => {
|
||||
if (conditionEntry.field === ConditionEntryField.HASH) {
|
||||
return createEntryMatch(
|
||||
|
|
|
@ -18134,13 +18134,9 @@
|
|||
"xpack.securitySolution.trustedapps.list.columns.actions": "アクション",
|
||||
"xpack.securitySolution.trustedapps.list.pageTitle": "信頼できるアプリケーション",
|
||||
"xpack.securitySolution.trustedapps.list.totalCount": "{totalItemCount, plural, one {#個の信頼できるアプリケーション} other {#個の信頼できるアプリケーション}}",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field": "フィールド",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.hash": "ハッシュ",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path": "パス",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.operator": "演算子",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.operator.is": "is",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.removeLabel": "エントリを削除",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.value": "値",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.group.andOperator": "AND",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.noEntries": "条件が定義されていません",
|
||||
"xpack.securitySolution.trustedapps.noResults": "項目が見つかりません",
|
||||
|
|
|
@ -18152,13 +18152,9 @@
|
|||
"xpack.securitySolution.trustedapps.list.columns.actions": "操作",
|
||||
"xpack.securitySolution.trustedapps.list.pageTitle": "受信任的应用程序",
|
||||
"xpack.securitySolution.trustedapps.list.totalCount": "{totalItemCount, plural, one {# 个受信任的应用程序} other {# 个受信任的应用程序}}",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field": "字段",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.hash": "哈希",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path": "路径",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.operator": "运算符",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.operator.is": "is",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.removeLabel": "移除条目",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.value": "值",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.group.andOperator": "AND",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.noEntries": "未定义条件",
|
||||
"xpack.securitySolution.trustedapps.noResults": "找不到项目",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue