[Synthetics] Filters tls certs by saved objects (#160113)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2023-06-21 22:04:13 +02:00 committed by GitHub
parent b20ccb25a8
commit 79d8349eaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 250 additions and 63 deletions

View file

@ -39,6 +39,8 @@ export enum SYNTHETICS_API_URLS {
DELETE_PACKAGE_POLICY = `/internal/synthetics/monitor/policy/{packagePolicyId}`,
FILTERS = '/internal/synthetics/monitor/filters',
CERTS = '/internal/synthetics/certs',
// Project monitor public endpoint
SYNTHETICS_MONITORS_PROJECT = '/api/synthetics/project/{projectName}/monitors',
SYNTHETICS_MONITORS_PROJECT_UPDATE = '/api/synthetics/project/{projectName}/monitors/_bulk_update',

View file

@ -47,7 +47,7 @@ export const getCertsRequestBody = ({
const searchRequest = createEsQuery({
body: {
from: pageIndex * size,
from: (pageIndex ?? 0) * size,
size,
sort: asMutableArray([
{

View file

@ -7,23 +7,19 @@
import * as t from 'io-ts';
export const GetCertsParamsType = t.intersection([
t.type({
pageIndex: t.number,
}),
t.partial({
search: t.string,
notValidBefore: t.string,
notValidAfter: t.string,
from: t.string,
to: t.string,
sortBy: t.string,
direction: t.string,
size: t.number,
filters: t.unknown,
monitorIds: t.array(t.string),
}),
]);
export const GetCertsParamsType = t.partial({
pageIndex: t.number,
search: t.string,
notValidBefore: t.string,
notValidAfter: t.string,
from: t.string,
to: t.string,
sortBy: t.string,
direction: t.string,
size: t.number,
filters: t.unknown,
monitorIds: t.array(t.string),
});
export type GetCertsParams = t.TypeOf<typeof GetCertsParamsType>;

View file

@ -26,8 +26,8 @@ const getPageSizeValue = () => {
};
export const CertificatesPage: React.FC = () => {
useTrackPageview({ app: 'uptime', path: 'certificates' });
useTrackPageview({ app: 'uptime', path: 'certificates', delay: 15000 });
useTrackPageview({ app: 'synthetics', path: 'certificates' });
useTrackPageview({ app: 'synthetics', path: 'certificates', delay: 15000 });
useBreadcrumbs([{ text: 'Certificates' }]);

View file

@ -25,7 +25,7 @@ describe('CertificateList', () => {
page={page}
sort={sort}
onChange={jest.fn()}
certificates={{ loading: false, total: 0, certs: [] }}
certificates={{ isLoading: false, total: 0, certs: [] }}
/>
);
@ -48,7 +48,7 @@ describe('CertificateList', () => {
sort={sort}
onChange={jest.fn()}
certificates={{
loading: false,
isLoading: false,
total: 1,
certs: [
{

View file

@ -42,7 +42,7 @@ interface Props {
page: Page;
sort: CertSort;
onChange: (page: Page, sort: CertSort) => void;
certificates: CertResult & { loading?: boolean };
certificates: CertResult & { isLoading?: boolean };
}
export const CertificateList: React.FC<Props> = ({ page, certificates, sort, onChange }) => {
@ -101,7 +101,7 @@ export const CertificateList: React.FC<Props> = ({ page, certificates, sort, onC
return (
<EuiBasicTable
loading={certificates.loading}
loading={certificates.isLoading}
columns={columns}
items={certificates?.certs ?? []}
pagination={pagination}
@ -113,7 +113,7 @@ export const CertificateList: React.FC<Props> = ({ page, certificates, sort, onC
},
}}
noItemsMessage={
certificates.loading ? (
certificates.isLoading ? (
LOADING_CERTIFICATES
) : (
<span data-test-subj="uptimeCertsEmptyMessage">{NO_CERTS_AVAILABLE}</span>

View file

@ -5,20 +5,16 @@
* 2.0.
*/
import { useContext } from 'react';
import { createEsParams, useEsSearch } from '@kbn/observability-shared-plugin/public';
import { useContext, useEffect } from 'react';
import { SYNTHETICS_INDEX_PATTERN } from '../../../../../common/constants';
import { useDispatch, useSelector } from 'react-redux';
import { getCertsListAction, selectCertsListState } from '../../state/certs';
import {
DEFAULT_DIRECTION,
DEFAULT_FROM,
DEFAULT_SIZE,
DEFAULT_SORT,
DEFAULT_TO,
getCertsRequestBody,
processCertsResult,
} from '../../../../../common/requests/get_certs_request_body';
import { CertResult, GetCertsParams, Ping } from '../../../../../common/runtime_types';
import { CertResult, GetCertsParams } from '../../../../../common/runtime_types';
import { SyntheticsRefreshContext } from '../../contexts';
export const useCertSearch = ({
@ -27,31 +23,24 @@ export const useCertSearch = ({
search,
sortBy = DEFAULT_SORT,
direction = DEFAULT_DIRECTION,
}: GetCertsParams): CertResult & { loading?: boolean } => {
}: GetCertsParams): CertResult & { isLoading?: boolean } => {
const { lastRefresh } = useContext(SyntheticsRefreshContext);
const searchBody = getCertsRequestBody({
pageIndex,
size,
search,
sortBy,
direction,
to: DEFAULT_TO,
from: DEFAULT_FROM,
});
const dispatch = useDispatch();
const esParams = createEsParams({
index: SYNTHETICS_INDEX_PATTERN,
body: searchBody,
});
useEffect(() => {
dispatch(
getCertsListAction.get({
pageIndex,
size,
search,
sortBy,
direction,
})
);
}, [direction, dispatch, lastRefresh, pageIndex, search, size, sortBy]);
const { data: result, loading } = useEsSearch<Ping, typeof esParams>(
esParams,
[size, pageIndex, lastRefresh, search, sortBy, direction],
{
name: 'getTLSCertificates',
}
);
const { data, isLoading } = useSelector(selectCertsListState);
return result ? { ...processCertsResult(result), loading } : { certs: [], total: 0, loading };
return { ...(data ?? { certs: [], total: 0 }), isLoading };
};

View file

@ -0,0 +1,11 @@
/*
* 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 { CertResult, GetCertsParams } from '../../../../../common/runtime_types';
import { createAsyncAction } from '../utils/actions';
export const getCertsListAction = createAsyncAction<GetCertsParams, CertResult>('GET CERTS LIST');

View file

@ -0,0 +1,24 @@
/*
* 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 { SYNTHETICS_API_URLS } from '../../../../../common/constants';
import { CertResult, GetCertsParams } from '../../../../../common/runtime_types';
import { apiService } from '../../../../utils/api_service/api_service';
export const getCertsList = async (queryParams: GetCertsParams): Promise<CertResult> => {
const { pageIndex, size, search, sortBy, direction } = queryParams;
const result = (await apiService.get(SYNTHETICS_API_URLS.CERTS, {
pageIndex,
size,
search,
sortBy,
direction,
})) as {
data: CertResult;
};
return result.data;
};

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { takeLeading } from 'redux-saga/effects';
import { i18n } from '@kbn/i18n';
import { fetchEffectFactory } from '../utils/fetch_effect';
import { getCertsList } from './api';
import { getCertsListAction } from './actions';
export function* getCertsListEffect() {
yield takeLeading(
getCertsListAction.get,
fetchEffectFactory(
getCertsList,
getCertsListAction.success,
getCertsListAction.fail,
undefined,
getFailMessage
)
);
}
const getFailMessage = i18n.translate('xpack.synthetics.getCerts.failed', {
defaultMessage: 'Failed to get TLS certificates.',
});

View file

@ -0,0 +1,45 @@
/*
* 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 { createReducer } from '@reduxjs/toolkit';
import { CertResult, SyntheticsParamSO } from '../../../../../common/runtime_types';
import { IHttpSerializedFetchError } from '..';
import { getCertsListAction } from './actions';
export interface CertsListState {
isLoading?: boolean;
data?: CertResult;
error: IHttpSerializedFetchError | null;
isSaving?: boolean;
savedData?: SyntheticsParamSO;
}
const initialState: CertsListState = {
isLoading: false,
error: null,
data: { certs: [], total: 0 },
};
export const certsListReducer = createReducer(initialState, (builder) => {
builder
.addCase(getCertsListAction.get, (state) => {
state.isLoading = true;
})
.addCase(getCertsListAction.success, (state, action) => {
state.isLoading = false;
state.data = action.payload;
})
.addCase(getCertsListAction.fail, (state, action) => {
state.isLoading = false;
state.error = action.payload;
});
});
export * from './actions';
export * from './effects';
export * from './selectors';
export * from './api';

View file

@ -0,0 +1,10 @@
/*
* 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 { AppState } from '..';
export const selectCertsListState = (state: AppState) => state.certsList;

View file

@ -6,6 +6,7 @@
*/
import { all, fork } from 'redux-saga/effects';
import { getCertsListEffect } from './certs';
import { addGlobalParamEffect, editGlobalParamEffect, getGlobalParamEffect } from './global_params';
import { fetchManualTestRunsEffect } from './manual_test_runs/effects';
import { enableDefaultAlertingEffect, updateDefaultAlertingEffect } from './alert_rules/effects';
@ -61,5 +62,6 @@ export const rootEffect = function* root(): Generator {
fork(addGlobalParamEffect),
fork(editGlobalParamEffect),
fork(getGlobalParamEffect),
fork(getCertsListEffect),
]);
};

View file

@ -7,6 +7,7 @@
import { combineReducers } from '@reduxjs/toolkit';
import { certsListReducer, CertsListState } from './certs';
import { certificatesReducer, CertificatesState } from './certificates/certificates';
import { globalParamsReducer, GlobalParamsState } from './global_params';
import { overviewStatusReducer, OverviewStatusStateReducer } from './overview_status';
@ -45,6 +46,7 @@ export interface SyntheticsAppState {
manualTestRuns: ManualTestRunsState;
monitorDetails: MonitorDetailsState;
browserJourney: BrowserJourneyState;
certsList: CertsListState;
defaultAlerting: DefaultAlertingState;
dynamicSettings: DynamicSettingsState;
serviceLocations: ServiceLocationsState;
@ -71,4 +73,5 @@ export const rootReducer = combineReducers<SyntheticsAppState>({
serviceLocations: serviceLocationsReducer,
syntheticsEnablement: syntheticsEnablementReducer,
certificates: certificatesReducer,
certsList: certsListReducer,
});

View file

@ -156,6 +156,13 @@ export const mockState: SyntheticsAppState = {
certificates: {
total: 0,
},
certsList: {
error: null,
data: {
total: 0,
certs: [],
},
},
};
function getBrowserJourneyMockSlice() {

View file

@ -71,9 +71,10 @@ export type UptimeRoute<ClientContract = unknown> = UMRouteDefinition<
export type UMRestApiRouteFactory<ClientContract = unknown> = (
libs: UMServerLibs
) => UptimeRoute<ClientContract>;
export type SyntheticsRestApiRouteFactory<ClientContract = any> = (
libs: UMServerLibs
) => SyntheticsRoute<ClientContract>;
export type SyntheticsRestApiRouteFactory<
ClientContract = any,
QueryParams = Record<string, any>
> = (libs: UMServerLibs) => SyntheticsRoute<ClientContract, QueryParams>;
export type SyntheticsStreamingRouteFactory = (libs: UMServerLibs) => SyntheticsStreamingRoute;
/**
@ -85,9 +86,10 @@ export type UMKibanaRouteWrapper = (
server: UptimeServerSetup
) => UMKibanaRoute;
export type SyntheticsRoute<ClientContract = unknown> = UMRouteDefinition<
SyntheticsRouteHandler<ClientContract>
>;
export type SyntheticsRoute<
ClientContract = unknown,
QueryParams = Record<string, any>
> = UMRouteDefinition<SyntheticsRouteHandler<ClientContract, QueryParams>>;
export type SyntheticsStreamingRoute = UMRouteDefinition<SyntheticsStreamingRouteHandler>;
export type SyntheticsRouteWrapper = (
@ -131,7 +133,7 @@ export interface RouteContext<Query = Record<string, any>> {
spaceId: string;
}
export type SyntheticsRouteHandler<ClientContract = unknown> = ({
export type SyntheticsRouteHandler<ClientContract = unknown, Query = Record<string, any>> = ({
uptimeEsClient,
context,
request,
@ -139,7 +141,7 @@ export type SyntheticsRouteHandler<ClientContract = unknown> = ({
server,
savedObjectsClient,
subject: Subject,
}: RouteContext) => Promise<IKibanaResponse<ClientContract> | ClientContract>;
}: RouteContext<Query>) => Promise<IKibanaResponse<ClientContract> | ClientContract>;
export type SyntheticsStreamingRouteHandler = ({
uptimeEsClient,

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { schema } from '@kbn/config-schema';
import {
getAllMonitors,
processMonitors,
} from '../../saved_objects/synthetics_monitor/get_all_monitors';
import { monitorAttributes } from '../../../common/types/saved_objects';
import { getCerts } from '../../legacy_uptime/lib/requests/get_certs';
import { SYNTHETICS_API_URLS } from '../../../common/constants';
import { CertResult, GetCertsParams } from '../../../common/runtime_types';
import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes';
import { AlertConfigKey } from '../../../common/constants/monitor_management';
export const getSyntheticsCertsRoute: SyntheticsRestApiRouteFactory<
{ data: CertResult },
GetCertsParams
> = () => ({
method: 'GET',
path: SYNTHETICS_API_URLS.CERTS,
validate: {
query: schema.object({
pageIndex: schema.maybe(schema.number()),
size: schema.maybe(schema.number()),
sortBy: schema.maybe(schema.string()),
direction: schema.maybe(schema.string()),
search: schema.maybe(schema.string()),
from: schema.maybe(schema.string()),
to: schema.maybe(schema.string()),
}),
},
handler: async ({
request,
uptimeEsClient,
savedObjectsClient,
server,
syntheticsMonitorClient,
}) => {
const queryParams = request.query;
const monitors = await getAllMonitors({
soClient: savedObjectsClient,
filter: `${monitorAttributes}.${AlertConfigKey.STATUS_ENABLED}: true`,
});
const { enabledMonitorQueryIds } = await processMonitors(
monitors,
server,
savedObjectsClient,
syntheticsMonitorClient
);
const data = await getCerts({
...queryParams,
uptimeEsClient,
monitorIds: enabledMonitorQueryIds,
});
return { data };
},
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { getSyntheticsCertsRoute } from './certs/get_certificates';
import { getAgentPoliciesRoute } from './settings/private_locations/get_agent_policies';
import { inspectSyntheticsMonitorRoute } from './monitor_cruds/inspect_monitor';
import { deletePackagePolicyRoute } from './monitor_cruds/delete_integration';
@ -102,6 +103,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
getSyntheticsFilters,
inspectSyntheticsMonitorRoute,
getAgentPoliciesRoute,
getSyntheticsCertsRoute,
];
export const syntheticsAppStreamingApiRoutes: SyntheticsStreamingRouteFactory[] = [