diff --git a/x-pack/plugins/uptime/common/translations.ts b/x-pack/plugins/uptime/common/translations.ts new file mode 100644 index 0000000000000..678fe7cb1f984 --- /dev/null +++ b/x-pack/plugins/uptime/common/translations.ts @@ -0,0 +1,14 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const VALUE_MUST_BE_GREATER_THEN_ZEO = i18n.translate( + 'xpack.uptime.settings.invalid.error', + { + defaultMessage: 'Value must be greater than 0.', + } +); diff --git a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx index 5bfd26f7c6401..8200e8292cd98 100644 --- a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx @@ -59,7 +59,7 @@ export const CertificateExpirationForm: React.FC = ({ > = ({ }} /> } - isInvalid={!!fieldErrors?.certificatesThresholds?.expirationThresholdError} + isInvalid={!!fieldErrors?.expirationThresholdError} label={ = ({ = ({ = ({ }} /> } - isInvalid={!!fieldErrors?.certificatesThresholds?.ageThresholdError} + isInvalid={!!fieldErrors?.ageThresholdError} label={ = ({ + onChange={({ currentTarget: { value } }) => onChange({ - certAgeThreshold: Number(e.currentTarget.value), + certAgeThreshold: Number(value), }) } /> diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 7ed9cf4444343..d018567ae1104 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -32,13 +32,12 @@ import { OnFieldChangeType, } from '../components/settings/certificate_form'; import * as Translations from './translations'; +import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; interface SettingsPageFieldErrors { - heartbeatIndices: 'May not be blank' | ''; - certificatesThresholds: { - expirationThresholdError: string | null; - ageThresholdError: string | null; - } | null; + heartbeatIndices: string | ''; + expirationThresholdError?: string; + ageThresholdError?: string; } export interface SettingsFormProps { @@ -49,22 +48,28 @@ export interface SettingsFormProps { isDisabled: boolean; } +const isValidCertVal = (val: string | number) => { + if (val === '') { + return Translations.BLANK_STR; + } + if (val === 0) { + return VALUE_MUST_BE_GREATER_THEN_ZEO; + } +}; + const getFieldErrors = (formFields: DynamicSettings | null): SettingsPageFieldErrors | null => { if (formFields) { - const blankStr = 'May not be blank'; const { certAgeThreshold, certExpirationThreshold, heartbeatIndices } = formFields; - const heartbeatIndErr = heartbeatIndices.match(/^\S+$/) ? '' : blankStr; - const expirationThresholdError = certExpirationThreshold ? null : blankStr; - const ageThresholdError = certAgeThreshold ? null : blankStr; + + const indError = heartbeatIndices.match(/^\S+$/) ? '' : Translations.BLANK_STR; + + const expError = isValidCertVal(certExpirationThreshold); + const ageError = isValidCertVal(certAgeThreshold); + return { - heartbeatIndices: heartbeatIndErr, - certificatesThresholds: - expirationThresholdError || ageThresholdError - ? { - expirationThresholdError, - ageThresholdError, - } - : null, + heartbeatIndices: indError, + expirationThresholdError: expError, + ageThresholdError: ageError, }; } return null; diff --git a/x-pack/plugins/uptime/public/pages/translations.ts b/x-pack/plugins/uptime/public/pages/translations.ts index 74fb2eeb1416b..8ed5503235884 100644 --- a/x-pack/plugins/uptime/public/pages/translations.ts +++ b/x-pack/plugins/uptime/public/pages/translations.ts @@ -36,3 +36,7 @@ export const settings = { defaultMessage: 'Return to overview', }), }; + +export const BLANK_STR = i18n.translate('xpack.uptime.settings.blank.error', { + defaultMessage: 'May not be blank.', +}); diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts index 31833a25ee8ac..c7d532d932aa6 100644 --- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts @@ -11,6 +11,7 @@ import { UMServerLibs } from '../lib/lib'; import { DynamicSettings, DynamicSettingsType } from '../../common/runtime_types'; import { UMRestApiRouteFactory } from '.'; import { savedObjectsAdapter } from '../lib/saved_objects'; +import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', @@ -23,19 +24,35 @@ export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSer }, }); +const validateCertsValues = (settings: DynamicSettings) => { + const errors: any = {}; + if (settings.certAgeThreshold <= 0) { + errors.certAgeThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + } + if (settings.certExpirationThreshold <= 0) { + errors.certExpirationThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + } + if (errors.certAgeThreshold || errors.certExpirationThreshold) { + return errors; + } +}; + export const createPostDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'POST', path: '/api/uptime/dynamic_settings', validate: { - body: schema.object({}, { unknowns: 'allow' }), + body: schema.object({ + heartbeatIndices: schema.string(), + certAgeThreshold: schema.number(), + certExpirationThreshold: schema.number(), + }), }, writeAccess: true, - options: { - tags: ['access:uptime-write'], - }, handler: async ({ savedObjectsClient }, _context, request, response): Promise => { const decoded = DynamicSettingsType.decode(request.body); - if (isRight(decoded)) { + const certThresholdErrors = validateCertsValues(request.body as DynamicSettings); + + if (isRight(decoded) && !certThresholdErrors) { const newSettings: DynamicSettings = decoded.right; await savedObjectsAdapter.setUptimeDynamicSettings(savedObjectsClient, newSettings); @@ -47,7 +64,7 @@ export const createPostDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSe } else { const error = PathReporter.report(decoded).join(', '); return response.badRequest({ - body: error, + body: JSON.stringify(certThresholdErrors) || error, }); } },