mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Uptime] allow an administrator to enable and disable monitor management (#128223)
* uptime - allow an administrator to enable monitor management Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: shahzad31 <shahzad.muhammad@elastic.co>
This commit is contained in:
parent
490cd51abc
commit
038f091311
49 changed files with 1633 additions and 250 deletions
|
@ -40,6 +40,7 @@ export enum API_URLS {
|
|||
INDEX_TEMPLATES = '/internal/uptime/service/index_templates',
|
||||
SERVICE_LOCATIONS = '/internal/uptime/service/locations',
|
||||
SYNTHETICS_MONITORS = '/internal/uptime/service/monitors',
|
||||
SYNTHETICS_ENABLEMENT = '/internal/uptime/service/enablement',
|
||||
RUN_ONCE_MONITOR = '/internal/uptime/service/monitors/run_once',
|
||||
TRIGGER_MONITOR = '/internal/uptime/service/monitors/trigger',
|
||||
SERVICE_ALLOWED = '/internal/uptime/service/allowed',
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const FetchMonitorManagementListQueryArgsType = t.partial({
|
||||
export const FetchMonitorManagementListQueryArgsCodec = t.partial({
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
sortField: t.string,
|
||||
|
@ -17,5 +17,15 @@ export const FetchMonitorManagementListQueryArgsType = t.partial({
|
|||
});
|
||||
|
||||
export type FetchMonitorManagementListQueryArgs = t.TypeOf<
|
||||
typeof FetchMonitorManagementListQueryArgsType
|
||||
typeof FetchMonitorManagementListQueryArgsCodec
|
||||
>;
|
||||
|
||||
export const MonitorManagementEnablementResultCodec = t.type({
|
||||
isEnabled: t.boolean,
|
||||
canEnable: t.boolean,
|
||||
areApiKeysEnabled: t.boolean,
|
||||
});
|
||||
|
||||
export type MonitorManagementEnablementResult = t.TypeOf<
|
||||
typeof MonitorManagementEnablementResultCodec
|
||||
>;
|
||||
|
|
|
@ -13,4 +13,5 @@ export * from './read_only_user';
|
|||
export * from './monitor_details.journey';
|
||||
export * from './monitor_name.journey';
|
||||
export * from './monitor_management.journey';
|
||||
export * from './monitor_management_enablement.journey';
|
||||
export * from './monitor_details';
|
||||
|
|
|
@ -96,18 +96,14 @@ const createMonitorJourney = ({
|
|||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
const isRemote = process.env.SYNTHETICS_REMOTE_ENABLED;
|
||||
const deleteMonitor = async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const isSuccessful = await uptime.deleteMonitor();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await uptime.waitForLoadingToFinish();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteMonitor();
|
||||
await uptime.navigateToMonitorManagement();
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
|
@ -123,13 +119,14 @@ const createMonitorJourney = ({
|
|||
});
|
||||
|
||||
step(`create ${monitorType} monitor`, async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
await uptime.createMonitor({ monitorConfig, monitorType });
|
||||
const isSuccessful = await uptime.confirmAndSave();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
|
||||
step(`view ${monitorType} details in monitor management UI`, async () => {
|
||||
step(`view ${monitorType} details in Monitor Management UI`, async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const hasFailure = await uptime.findMonitorConfiguration(monitorDetails);
|
||||
expect(hasFailure).toBeFalsy();
|
||||
|
@ -141,6 +138,12 @@ const createMonitorJourney = ({
|
|||
await page.waitForSelector(`text=${monitorName}`, { timeout: 160 * 1000 });
|
||||
});
|
||||
}
|
||||
|
||||
step('delete monitor', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const isSuccessful = await uptime.deleteMonitors();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -167,6 +170,10 @@ journey('Monitor Management breadcrumbs', async ({ page, params }: { page: Page;
|
|||
await uptime.waitForLoadingToFinish();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
});
|
||||
|
@ -177,13 +184,14 @@ journey('Monitor Management breadcrumbs', async ({ page, params }: { page: Page;
|
|||
|
||||
step('Check breadcrumb', async () => {
|
||||
const lastBreadcrumb = await (await uptime.findByTestSubj('"breadcrumb last"')).textContent();
|
||||
expect(lastBreadcrumb).toEqual('Monitor management');
|
||||
expect(lastBreadcrumb).toEqual('Monitor Management');
|
||||
});
|
||||
|
||||
step('check breadcrumbs', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
const breadcrumbs = await page.$$('[data-test-subj="breadcrumb"]');
|
||||
expect(await breadcrumbs[1].textContent()).toEqual('Monitor management');
|
||||
expect(await breadcrumbs[1].textContent()).toEqual('Monitor Management');
|
||||
const lastBreadcrumb = await (await uptime.findByTestSubj('"breadcrumb last"')).textContent();
|
||||
expect(lastBreadcrumb).toEqual('Add monitor');
|
||||
});
|
||||
|
@ -204,14 +212,14 @@ journey('Monitor Management breadcrumbs', async ({ page, params }: { page: Page;
|
|||
// breadcrumb is available before edit page is loaded, make sure its edit view
|
||||
await page.waitForSelector(byTestId('monitorManagementMonitorName'), { timeout: 60 * 1000 });
|
||||
const breadcrumbs = await page.$$('[data-test-subj=breadcrumb]');
|
||||
expect(await breadcrumbs[1].textContent()).toEqual('Monitor management');
|
||||
expect(await breadcrumbs[1].textContent()).toEqual('Monitor Management');
|
||||
const lastBreadcrumb = await (await uptime.findByTestSubj('"breadcrumb last"')).textContent();
|
||||
expect(lastBreadcrumb).toEqual('Edit monitor');
|
||||
});
|
||||
|
||||
step('delete monitor', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
const isSuccessful = await uptime.deleteMonitor();
|
||||
const isSuccessful = await uptime.deleteMonitors();
|
||||
expect(isSuccessful).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 { journey, step, expect, before, after, Page } from '@elastic/synthetics';
|
||||
import { monitorManagementPageProvider } from '../page_objects/monitor_management';
|
||||
|
||||
journey(
|
||||
'Monitor Management-enablement-superuser',
|
||||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
|
||||
before(async () => {
|
||||
await uptime.waitForLoadingToFinish();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
});
|
||||
|
||||
step('login to Kibana', async () => {
|
||||
await uptime.loginToKibana();
|
||||
const invalid = await page.locator(
|
||||
`text=Username or password is incorrect. Please try again.`
|
||||
);
|
||||
expect(await invalid.isVisible()).toBeFalsy();
|
||||
});
|
||||
|
||||
step('check add monitor button', async () => {
|
||||
expect(await uptime.checkIsEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
step('enable Monitor Management', async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
expect(await uptime.checkIsEnabled()).toBe(true);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
journey(
|
||||
'MonitorManagement-enablement-obs-admin',
|
||||
async ({ page, params }: { page: Page; params: any }) => {
|
||||
const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl });
|
||||
|
||||
before(async () => {
|
||||
await uptime.waitForLoadingToFinish();
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
});
|
||||
|
||||
step('login to Kibana', async () => {
|
||||
await uptime.loginToKibana('obs_admin_user', 'changeme');
|
||||
const invalid = await page.locator(
|
||||
`text=Username or password is incorrect. Please try again.`
|
||||
);
|
||||
expect(await invalid.isVisible()).toBeFalsy();
|
||||
});
|
||||
|
||||
step('check add monitor button', async () => {
|
||||
expect(await uptime.checkIsEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
step('check that enabled toggle does not appear', async () => {
|
||||
expect(await page.$(`[data-test-subj=syntheticsEnableSwitch]`)).toBeFalsy();
|
||||
});
|
||||
}
|
||||
);
|
|
@ -12,7 +12,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { journey, step, expect, after, before, Page } from '@elastic/synthetics';
|
||||
import { journey, step, expect, before, Page } from '@elastic/synthetics';
|
||||
import { monitorManagementPageProvider } from '../page_objects/monitor_management';
|
||||
import { byTestId } from './utils';
|
||||
|
||||
|
@ -23,11 +23,6 @@ journey(`MonitorName`, async ({ page, params }: { page: Page; params: any }) =>
|
|||
await uptime.waitForLoadingToFinish();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
await uptime.deleteMonitor();
|
||||
});
|
||||
|
||||
step('Go to monitor-management', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
});
|
||||
|
@ -39,6 +34,7 @@ journey(`MonitorName`, async ({ page, params }: { page: Page; params: any }) =>
|
|||
});
|
||||
|
||||
step(`shows error if name already exists`, async () => {
|
||||
await uptime.enableMonitorManagement();
|
||||
await uptime.clickAddMonitor();
|
||||
await uptime.createBasicMonitorDetails({
|
||||
name: 'Test monitor',
|
||||
|
@ -61,4 +57,10 @@ journey(`MonitorName`, async ({ page, params }: { page: Page; params: any }) =>
|
|||
|
||||
expect(await page.isEnabled(byTestId('monitorTestNowRunBtn'))).toBeTruthy();
|
||||
});
|
||||
|
||||
step('delete monitor', async () => {
|
||||
await uptime.navigateToMonitorManagement();
|
||||
await uptime.deleteMonitors();
|
||||
await uptime.enableMonitorManagement(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ journey(
|
|||
});
|
||||
|
||||
step('Adding monitor is disabled', async () => {
|
||||
expect(await page.isEnabled(byTestId('addMonitorBtn'))).toBeFalsy();
|
||||
expect(await page.isEnabled(byTestId('syntheticsAddMonitorBtn'))).toBeFalsy();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Page } from '@elastic/synthetics';
|
||||
import { DataStream } from '../../common/runtime_types/monitor_management';
|
||||
import { getQuerystring } from '../journeys/utils';
|
||||
|
@ -39,6 +38,60 @@ export function monitorManagementPageProvider({
|
|||
await page.goto(monitorManagement, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
await this.waitForMonitorManagementLoadingToFinish();
|
||||
},
|
||||
|
||||
async waitForMonitorManagementLoadingToFinish() {
|
||||
while (true) {
|
||||
if ((await page.$(this.byTestId('uptimeLoader'))) === null) break;
|
||||
await page.waitForTimeout(5 * 1000);
|
||||
}
|
||||
},
|
||||
|
||||
async enableMonitorManagement(shouldEnable: boolean = true) {
|
||||
const isEnabled = await this.checkIsEnabled();
|
||||
if (isEnabled === shouldEnable) {
|
||||
return;
|
||||
}
|
||||
const [toggle, button] = await Promise.all([
|
||||
page.$(this.byTestId('syntheticsEnableSwitch')),
|
||||
page.$(this.byTestId('syntheticsEnableButton')),
|
||||
]);
|
||||
|
||||
if (toggle === null && button === null) {
|
||||
return null;
|
||||
}
|
||||
if (toggle) {
|
||||
if (isEnabled !== shouldEnable) {
|
||||
await toggle.click();
|
||||
}
|
||||
} else {
|
||||
await button?.click();
|
||||
}
|
||||
if (shouldEnable) {
|
||||
await this.findByText('Monitor Management enabled successfully.');
|
||||
} else {
|
||||
await this.findByText('Monitor Management disabled successfully.');
|
||||
}
|
||||
},
|
||||
|
||||
async getEnableToggle() {
|
||||
return await this.findByTestSubj('syntheticsEnableSwitch');
|
||||
},
|
||||
|
||||
async getEnableButton() {
|
||||
return await this.findByTestSubj('syntheticsEnableSwitch');
|
||||
},
|
||||
|
||||
async getAddMonitorButton() {
|
||||
return await this.findByTestSubj('syntheticsAddMonitorBtn');
|
||||
},
|
||||
|
||||
async checkIsEnabled() {
|
||||
await page.waitForTimeout(5 * 1000);
|
||||
const addMonitorBtn = await this.getAddMonitorButton();
|
||||
const isDisabled = await addMonitorBtn.isDisabled();
|
||||
return !isDisabled;
|
||||
},
|
||||
|
||||
async navigateToAddMonitor() {
|
||||
|
@ -57,10 +110,18 @@ export function monitorManagementPageProvider({
|
|||
await page.click('text=Add monitor');
|
||||
},
|
||||
|
||||
async deleteMonitor() {
|
||||
await this.clickByTestSubj('monitorManagementDeleteMonitor');
|
||||
await this.clickByTestSubj('confirmModalConfirmButton');
|
||||
return await this.findByTestSubj('uptimeDeleteMonitorSuccess');
|
||||
async deleteMonitors() {
|
||||
let isSuccessful: boolean = false;
|
||||
await page.waitForSelector('[data-test-subj="monitorManagementDeleteMonitor"]');
|
||||
while (true) {
|
||||
if ((await page.$(this.byTestId('monitorManagementDeleteMonitor'))) === null) break;
|
||||
await page.click(this.byTestId('monitorManagementDeleteMonitor'), { delay: 800 });
|
||||
await page.waitForSelector('[data-test-subj="confirmModalTitleText"]');
|
||||
await this.clickByTestSubj('confirmModalConfirmButton');
|
||||
isSuccessful = Boolean(await this.findByTestSubj('uptimeDeleteMonitorSuccess'));
|
||||
await page.waitForTimeout(5 * 1000);
|
||||
}
|
||||
return isSuccessful;
|
||||
},
|
||||
|
||||
async editMonitor() {
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
{"attributes":{"__ui":{"is_tls_enabled":false,"is_zip_url_tls_enabled":false},"check.request.method":"GET","check.response.status":[],"enabled":true,"locations":[{"geo":{"lat":41.25,"lon":-95.86},"id":"us_central","label":"US Central","url":"https://us-central.synthetics.elastic.dev"}],"max_redirects":"0","name":"Test Monitor","proxy_url":"","response.include_body":"on_error","response.include_headers":true,"schedule":{"number":"3","unit":"m"},"service.name":"","tags":[],"timeout":"16","type":"http","urls":"https://www.google.com", "secrets": "{}"},"coreMigrationVersion":"8.1.0","id":"832b9980-7fba-11ec-b360-25a79ce3f496","references":[],"sort":[1643319958480,20371],"type":"synthetics-monitor","updated_at":"2022-01-27T21:45:58.480Z","version":"WzExOTg3ODYsMl0="}
|
||||
{"attributes":{"__ui":{"is_tls_enabled":false,"is_zip_url_tls_enabled":false},"check.request.method":"GET","check.response.status":[],"enabled":true,"locations":[{"geo":{"lat":41.25,"lon":-95.86},"id":"us_central","label":"US Central","url":"https://us-central.synthetics.elastic.dev"}],"max_redirects":"0","name":"Test Monito","proxy_url":"","response.include_body":"on_error","response.include_headers":true,"schedule":{"number":"3","unit":"m"},"service.name":"","tags":[],"timeout":"16","type":"http","urls":"https://www.google.com", "secrets": "{}"},"coreMigrationVersion":"8.1.0","id":"b28380d0-7fba-11ec-b360-25a79ce3f496","references":[],"sort":[1643320037906,20374],"type":"synthetics-monitor","updated_at":"2022-01-27T21:47:17.906Z","version":"WzExOTg4MDAsMl0="}
|
||||
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":2,"missingRefCount":0,"missingReferences":[]}
|
||||
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":2,"missingRefCount":0,"missingReferences":[]}
|
||||
|
|
|
@ -78,7 +78,7 @@ export function ActionMenuContent(): React.ReactElement {
|
|||
<EuiHeaderLinks gutterSize="xs">
|
||||
<EuiHeaderLink
|
||||
aria-label={i18n.translate('xpack.uptime.page_header.manageLink.label', {
|
||||
defaultMessage: 'Navigate to the Uptime monitor management page',
|
||||
defaultMessage: 'Navigate to the Uptime Monitor Management page',
|
||||
})}
|
||||
color="text"
|
||||
data-test-subj="management-page-link"
|
||||
|
@ -88,7 +88,7 @@ export function ActionMenuContent(): React.ReactElement {
|
|||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.page_header.manageLink"
|
||||
defaultMessage="Monitor management"
|
||||
defaultMessage="Monitor Management"
|
||||
/>
|
||||
</EuiHeaderLink>
|
||||
|
||||
|
|
|
@ -5,39 +5,203 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButton, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexItem, EuiFlexGroup, EuiToolTip, EuiSwitch } from '@elastic/eui';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { kibanaService } from '../../state/kibana_service';
|
||||
import { MONITOR_ADD_ROUTE } from '../../../common/constants';
|
||||
import { useEnablement } from './hooks/use_enablement';
|
||||
import { useSyntheticsServiceAllowed } from './hooks/use_service_allowed';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
export const AddMonitorBtn = () => {
|
||||
const history = useHistory();
|
||||
const [isEnabling, setIsEnabling] = useState(false);
|
||||
const [isDisabling, setIsDisabling] = useState(false);
|
||||
const {
|
||||
error,
|
||||
loading: enablementLoading,
|
||||
enablement,
|
||||
disableSynthetics,
|
||||
enableSynthetics,
|
||||
totalMonitors,
|
||||
} = useEnablement();
|
||||
const { isEnabled, canEnable, areApiKeysEnabled } = enablement || {};
|
||||
|
||||
const { isAllowed, loading } = useSyntheticsServiceAllowed();
|
||||
useEffect(() => {
|
||||
if (isEnabling && isEnabled) {
|
||||
setIsEnabling(false);
|
||||
kibanaService.toasts.addSuccess({
|
||||
title: SYNTHETICS_ENABLE_SUCCESS,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
} else if (isDisabling && !isEnabled) {
|
||||
setIsDisabling(false);
|
||||
kibanaService.toasts.addSuccess({
|
||||
title: SYNTHETICS_DISABLE_SUCCESS,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
} else if (isEnabling && error) {
|
||||
setIsEnabling(false);
|
||||
kibanaService.toasts.addDanger({
|
||||
title: SYNTHETICS_ENABLE_FAILURE,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
} else if (isDisabling && error) {
|
||||
kibanaService.toasts.addDanger({
|
||||
title: SYNTHETICS_DISABLE_FAILURE,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
}
|
||||
}, [isEnabled, isEnabling, isDisabling, error]);
|
||||
|
||||
const handleSwitch = () => {
|
||||
if (isEnabled) {
|
||||
setIsDisabling(true);
|
||||
disableSynthetics();
|
||||
} else {
|
||||
setIsEnabling(true);
|
||||
enableSynthetics();
|
||||
}
|
||||
};
|
||||
|
||||
const getShowSwitch = () => {
|
||||
if (isEnabled) {
|
||||
return canEnable;
|
||||
} else if (!isEnabled) {
|
||||
return canEnable && (totalMonitors || 0) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
const getSwitchToolTipContent = () => {
|
||||
if (!isEnabled) {
|
||||
return SYNTHETICS_ENABLE_TOOL_TIP_MESSAGE;
|
||||
} else if (isEnabled) {
|
||||
return SYNTHETICS_DISABLE_TOOL_TIP_MESSAGE;
|
||||
} else if (!areApiKeysEnabled) {
|
||||
return API_KEYS_DISABLED_TOOL_TIP_MESSAGE;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const { isAllowed, loading: allowedLoading } = useSyntheticsServiceAllowed();
|
||||
|
||||
const loading = allowedLoading || enablementLoading;
|
||||
|
||||
const canSave: boolean = !!useKibana().services?.application?.capabilities.uptime.save;
|
||||
|
||||
return (
|
||||
<EuiFlexItem style={{ alignItems: 'flex-end' }} grow={false} data-test-subj="addMonitorButton">
|
||||
<EuiButton
|
||||
fill
|
||||
isLoading={loading}
|
||||
isDisabled={!canSave || !isAllowed}
|
||||
iconType="plus"
|
||||
data-test-subj="addMonitorBtn"
|
||||
href={history.createHref({
|
||||
pathname: MONITOR_ADD_ROUTE,
|
||||
})}
|
||||
>
|
||||
{ADD_MONITOR_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem style={{ alignItems: 'flex-end' }} grow={false}>
|
||||
{getShowSwitch() && !loading && (
|
||||
<EuiToolTip content={getSwitchToolTipContent()}>
|
||||
<EuiSwitch
|
||||
checked={Boolean(isEnabled)}
|
||||
label={SYNTHETICS_ENABLE_LABEL}
|
||||
disabled={loading || !areApiKeysEnabled}
|
||||
onChange={() => handleSwitch()}
|
||||
data-test-subj="syntheticsEnableSwitch"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
{getShowSwitch() && loading && (
|
||||
<EuiSwitch
|
||||
checked={Boolean(isEnabled)}
|
||||
label={SYNTHETICS_ENABLE_LABEL}
|
||||
disabled={true}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ alignItems: 'flex-end' }} grow={false}>
|
||||
<EuiToolTip content={!isEnabled && !canEnable ? SYNTHETICS_DISABLED_MESSAGE : ''}>
|
||||
<EuiButton
|
||||
isLoading={loading}
|
||||
fill
|
||||
isDisabled={!canSave || !isEnabled || !isAllowed}
|
||||
iconType="plus"
|
||||
data-test-subj="syntheticsAddMonitorBtn"
|
||||
href={history.createHref({
|
||||
pathname: MONITOR_ADD_ROUTE,
|
||||
})}
|
||||
>
|
||||
{ADD_MONITOR_LABEL}
|
||||
</EuiButton>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const ADD_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.addMonitorLabel', {
|
||||
defaultMessage: 'Add monitor',
|
||||
});
|
||||
|
||||
const SYNTHETICS_ENABLE_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsEnableLabel',
|
||||
{
|
||||
defaultMessage: 'Enable',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_ENABLE_FAILURE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsEnabledFailure',
|
||||
{
|
||||
defaultMessage: 'Monitor Management was not able to be enabled. Please contact support.',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_DISABLE_FAILURE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsDisabledFailure',
|
||||
{
|
||||
defaultMessage: 'Monitor Management was not able to be disabled. Please contact support.',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_ENABLE_SUCCESS = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsEnableSuccess',
|
||||
{
|
||||
defaultMessage: 'Monitor Management enabled successfully.',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_DISABLE_SUCCESS = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsDisabledSuccess',
|
||||
{
|
||||
defaultMessage: 'Monitor Management disabled successfully.',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_DISABLED_MESSAGE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsDisabled',
|
||||
{
|
||||
defaultMessage:
|
||||
'Monitor Management is currently disabled. Please contact an administrator to enable Monitor Management.',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_ENABLE_TOOL_TIP_MESSAGE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsEnableToolTip',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enable Monitor Management to create lightweight and real-browser monitors from locations around the world.',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_DISABLE_TOOL_TIP_MESSAGE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsDisableToolTip',
|
||||
{
|
||||
defaultMessage:
|
||||
'Disabling Monitor Management with immediately stop the execution of monitors in all test locations and prevent the creation of new monitors.',
|
||||
}
|
||||
);
|
||||
|
||||
const API_KEYS_DISABLED_TOOL_TIP_MESSAGE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.apiKeysDisabledToolTip',
|
||||
{
|
||||
defaultMessage:
|
||||
'API Keys are disabled for this cluster. Monitor Management requires the use of API keys to write back to your Elasticsearch cluster. To enable API keys, please contact an administrator.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const SYNTHETICS_ENABLE_FAILURE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsEnabledFailure',
|
||||
{
|
||||
defaultMessage: 'Monitor Management was not able to be enabled. Please contact support.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYNTHETICS_DISABLE_FAILURE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsDisabledFailure',
|
||||
{
|
||||
defaultMessage: 'Monitor Management was not able to be disabled. Please contact support.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYNTHETICS_ENABLE_SUCCESS = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsEnableSuccess',
|
||||
{
|
||||
defaultMessage: 'Monitor Management enabled successfully.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYNTHETICS_DISABLE_SUCCESS = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsDisabledSuccess',
|
||||
{
|
||||
defaultMessage: 'Monitor Management disabled successfully.',
|
||||
}
|
||||
);
|
|
@ -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 { useEffect, useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { monitorManagementListSelector } from '../../../state/selectors';
|
||||
import {
|
||||
getSyntheticsEnablement,
|
||||
disableSynthetics,
|
||||
enableSynthetics,
|
||||
} from '../../../state/actions';
|
||||
|
||||
export function useEnablement() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
loading: { enablement: loading },
|
||||
error: { enablement: error },
|
||||
enablement,
|
||||
list: { total },
|
||||
} = useSelector(monitorManagementListSelector);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enablement) {
|
||||
dispatch(getSyntheticsEnablement());
|
||||
}
|
||||
}, [dispatch, enablement]);
|
||||
|
||||
return {
|
||||
enablement: {
|
||||
areApiKeysEnabled: enablement?.areApiKeysEnabled,
|
||||
canEnable: enablement?.canEnable,
|
||||
isEnabled: enablement?.isEnabled,
|
||||
},
|
||||
error,
|
||||
loading,
|
||||
totalMonitors: total,
|
||||
enableSynthetics: useCallback(() => dispatch(enableSynthetics()), [dispatch]),
|
||||
disableSynthetics: useCallback(() => dispatch(disableSynthetics()), [dispatch]),
|
||||
};
|
||||
}
|
|
@ -68,9 +68,10 @@ describe('useInlineErrors', function () {
|
|||
[
|
||||
'heartbeat-8*,heartbeat-7*,synthetics-*',
|
||||
{
|
||||
error: { monitorList: null, serviceLocations: null },
|
||||
error: { monitorList: null, serviceLocations: null, enablement: null },
|
||||
enablement: null,
|
||||
list: { monitors: [], page: 1, perPage: 10, total: null },
|
||||
loading: { monitorList: false, serviceLocations: false },
|
||||
loading: { monitorList: false, serviceLocations: false, enablement: false },
|
||||
locations: [],
|
||||
syntheticsService: {
|
||||
loading: false,
|
||||
|
|
|
@ -67,9 +67,10 @@ describe('useInlineErrorsCount', function () {
|
|||
[
|
||||
'heartbeat-8*,heartbeat-7*,synthetics-*',
|
||||
{
|
||||
error: { monitorList: null, serviceLocations: null },
|
||||
error: { monitorList: null, serviceLocations: null, enablement: null },
|
||||
list: { monitors: [], page: 1, perPage: 10, total: null },
|
||||
loading: { monitorList: false, serviceLocations: false },
|
||||
enablement: null,
|
||||
loading: { monitorList: false, serviceLocations: false, enablement: false },
|
||||
locations: [],
|
||||
syntheticsService: {
|
||||
loading: false,
|
||||
|
|
|
@ -42,13 +42,16 @@ describe('useExpViewTimeRange', function () {
|
|||
monitors: [],
|
||||
},
|
||||
locations: [],
|
||||
enablement: null,
|
||||
error: {
|
||||
serviceLocations: error,
|
||||
monitorList: null,
|
||||
enablement: null,
|
||||
},
|
||||
loading: {
|
||||
monitorList: false,
|
||||
serviceLocations: loading,
|
||||
enablement: false,
|
||||
},
|
||||
syntheticsService: {
|
||||
loading: false,
|
||||
|
|
|
@ -44,6 +44,7 @@ export const Loader = ({
|
|||
color="subdued"
|
||||
icon={<EuiLoadingLogo logo="logoKibana" size="xl" />}
|
||||
title={<h2>{loadingTitle}</h2>}
|
||||
data-test-subj="uptimeLoader"
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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, { useState, useEffect, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiEmptyPrompt, EuiButton, EuiTitle, EuiLink } from '@elastic/eui';
|
||||
import { useEnablement } from '..//hooks/use_enablement';
|
||||
import { kibanaService } from '../../../state/kibana_service';
|
||||
import { SYNTHETICS_ENABLE_SUCCESS, SYNTHETICS_DISABLE_SUCCESS } from '../content';
|
||||
|
||||
export const EnablementEmptyState = ({ focusButton }: { focusButton: boolean }) => {
|
||||
const { error, enablement, enableSynthetics, loading } = useEnablement();
|
||||
const [isEnabling, setIsEnabling] = useState(false);
|
||||
const { isEnabled, canEnable } = enablement;
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEnabling && isEnabled) {
|
||||
setIsEnabling(false);
|
||||
kibanaService.toasts.addSuccess({
|
||||
title: SYNTHETICS_ENABLE_SUCCESS,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
} else if (isEnabling && error) {
|
||||
setIsEnabling(false);
|
||||
kibanaService.toasts.addSuccess({
|
||||
title: SYNTHETICS_DISABLE_SUCCESS,
|
||||
toastLifeTimeMs: 3000,
|
||||
});
|
||||
}
|
||||
}, [isEnabled, isEnabling, error]);
|
||||
|
||||
const handleEnableSynthetics = () => {
|
||||
enableSynthetics();
|
||||
setIsEnabling(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (focusButton) {
|
||||
buttonRef.current?.focus();
|
||||
}
|
||||
}, [focusButton]);
|
||||
|
||||
return !isEnabled && !loading ? (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<h2>
|
||||
{canEnable ? MONITOR_MANAGEMENT_ENABLEMENT_LABEL : MONITOR_MANAGEMENT_DISABLED_LABEL}
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
{canEnable ? MONITOR_MANAGEMENT_ENABLEMENT_MESSAGE : MONITOR_MANAGEMENT_DISABLED_MESSAGE}
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
canEnable ? (
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
onClick={handleEnableSynthetics}
|
||||
data-test-subj="syntheticsEnableButton"
|
||||
buttonRef={buttonRef}
|
||||
>
|
||||
{MONITOR_MANAGEMENT_ENABLEMENT_BTN_LABEL}
|
||||
</EuiButton>
|
||||
) : null
|
||||
}
|
||||
footer={
|
||||
<>
|
||||
<EuiTitle size="xxs">
|
||||
<h3>{LEARN_MORE_LABEL}</h3>
|
||||
</EuiTitle>
|
||||
<EuiLink href="#" target="_blank">
|
||||
{DOCS_LABEL}
|
||||
</EuiLink>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const MONITOR_MANAGEMENT_ENABLEMENT_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.emptyState.enablement.enabled.title',
|
||||
{
|
||||
defaultMessage: 'Enable Monitor Management',
|
||||
}
|
||||
);
|
||||
|
||||
const MONITOR_MANAGEMENT_DISABLED_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.emptyState.enablement.disabled.title',
|
||||
{
|
||||
defaultMessage: 'Monitor Management is disabled',
|
||||
}
|
||||
);
|
||||
|
||||
const MONITOR_MANAGEMENT_ENABLEMENT_MESSAGE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.emptyState.enablement',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enable Monitor Management to run lightweight checks and real-browser monitors from hosted testing locations around the world. Enabling Monitor Management will generate an API key to allow the Synthetics Service to write back to your Elasticsearch cluster.',
|
||||
}
|
||||
);
|
||||
|
||||
const MONITOR_MANAGEMENT_DISABLED_MESSAGE = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.emptyState.enablement.disabledDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Monitor Management is currently disabled. Monitor Management allows you to run lightweight checks and real-browser monitors from hosted testing locations around the world. To enable Monitor Management, please contact an administrator.',
|
||||
}
|
||||
);
|
||||
|
||||
const MONITOR_MANAGEMENT_ENABLEMENT_BTN_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.emptyState.enablement.title',
|
||||
{
|
||||
defaultMessage: 'Enable',
|
||||
}
|
||||
);
|
||||
|
||||
const DOCS_LABEL = i18n.translate('xpack.uptime.monitorManagement.emptyState.enablement.doc', {
|
||||
defaultMessage: 'Read the docs',
|
||||
});
|
||||
|
||||
const LEARN_MORE_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.emptyState.enablement.learnMore',
|
||||
{
|
||||
defaultMessage: 'Want to learn more?',
|
||||
}
|
||||
);
|
|
@ -49,8 +49,9 @@ export const InvalidMonitors = ({
|
|||
perPage: pageState.pageSize,
|
||||
total: invalidTotal ?? 0,
|
||||
},
|
||||
error: { monitorList: null, serviceLocations: null },
|
||||
loading: { monitorList: summariesLoading, serviceLocations: false },
|
||||
enablement: null,
|
||||
error: { monitorList: null, serviceLocations: null, enablement: null },
|
||||
loading: { monitorList: summariesLoading, serviceLocations: false, enablement: false },
|
||||
locations: monitorList.locations,
|
||||
syntheticsService: monitorList.syntheticsService,
|
||||
throttling: DEFAULT_THROTTLING,
|
||||
|
|
|
@ -50,13 +50,16 @@ describe('<MonitorManagementList />', () => {
|
|||
monitors,
|
||||
},
|
||||
locations: [],
|
||||
enablement: null,
|
||||
error: {
|
||||
serviceLocations: null,
|
||||
monitorList: null,
|
||||
enablement: null,
|
||||
},
|
||||
loading: {
|
||||
monitorList: true,
|
||||
serviceLocations: false,
|
||||
enablement: false,
|
||||
},
|
||||
syntheticsService: {
|
||||
loading: false,
|
||||
|
|
|
@ -207,7 +207,7 @@ export const MonitorManagementList = ({
|
|||
<EuiSpacer size="m" />
|
||||
<EuiBasicTable
|
||||
aria-label={i18n.translate('xpack.uptime.monitorManagement.monitorList.title', {
|
||||
defaultMessage: 'Monitor management list',
|
||||
defaultMessage: 'Monitor Management list',
|
||||
})}
|
||||
error={error?.message}
|
||||
loading={loading}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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, { useEffect, useReducer, useCallback, Reducer } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { ConfigKey } from '../../../../common/runtime_types';
|
||||
import { getMonitors } from '../../../state/actions';
|
||||
import { monitorManagementListSelector } from '../../../state/selectors';
|
||||
import { MonitorManagementListPageState } from './monitor_list';
|
||||
import { useInlineErrors } from '../hooks/use_inline_errors';
|
||||
import { MonitorListTabs } from './list_tabs';
|
||||
import { AllMonitors } from './all_monitors';
|
||||
import { InvalidMonitors } from './invalid_monitors';
|
||||
import { useInvalidMonitors } from '../hooks/use_invalid_monitors';
|
||||
|
||||
export const MonitorListContainer: React.FC = () => {
|
||||
const [pageState, dispatchPageAction] = useReducer<typeof monitorManagementPageReducer>(
|
||||
monitorManagementPageReducer,
|
||||
{
|
||||
pageIndex: 1, // saved objects page index is base 1
|
||||
pageSize: 10,
|
||||
sortOrder: 'asc',
|
||||
sortField: ConfigKey.NAME,
|
||||
}
|
||||
);
|
||||
|
||||
const onPageStateChange = useCallback(
|
||||
(state) => {
|
||||
dispatchPageAction({ type: 'update', payload: state });
|
||||
},
|
||||
[dispatchPageAction]
|
||||
);
|
||||
|
||||
const onUpdate = useCallback(() => {
|
||||
dispatchPageAction({ type: 'refresh' });
|
||||
}, [dispatchPageAction]);
|
||||
|
||||
useTrackPageview({ app: 'uptime', path: 'manage-monitors' });
|
||||
useTrackPageview({ app: 'uptime', path: 'manage-monitors', delay: 15000 });
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const monitorList = useSelector(monitorManagementListSelector);
|
||||
|
||||
const { pageIndex, pageSize, sortField, sortOrder } = pageState as MonitorManagementListPageState;
|
||||
|
||||
const { type: viewType } = useParams<{ type: 'all' | 'invalid' }>();
|
||||
const { errorSummaries, loading, count } = useInlineErrors({
|
||||
onlyInvalidMonitors: viewType === 'invalid',
|
||||
sortField: pageState.sortField,
|
||||
sortOrder: pageState.sortOrder,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (viewType === 'all') {
|
||||
dispatch(getMonitors({ page: pageIndex, perPage: pageSize, sortField, sortOrder }));
|
||||
}
|
||||
}, [dispatch, pageState, pageIndex, pageSize, sortField, sortOrder, viewType]);
|
||||
|
||||
const { data: monitorSavedObjects, loading: objectsLoading } = useInvalidMonitors(errorSummaries);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MonitorListTabs
|
||||
invalidTotal={monitorSavedObjects?.length ?? 0}
|
||||
onUpdate={onUpdate}
|
||||
onPageStateChange={onPageStateChange}
|
||||
/>
|
||||
{viewType === 'all' ? (
|
||||
<AllMonitors
|
||||
pageState={pageState}
|
||||
monitorList={monitorList}
|
||||
onPageStateChange={onPageStateChange}
|
||||
onUpdate={onUpdate}
|
||||
errorSummaries={errorSummaries}
|
||||
/>
|
||||
) : (
|
||||
<InvalidMonitors
|
||||
pageState={pageState}
|
||||
monitorSavedObjects={monitorSavedObjects}
|
||||
onPageStateChange={onPageStateChange}
|
||||
onUpdate={onUpdate}
|
||||
errorSummaries={errorSummaries}
|
||||
invalidTotal={count ?? 0}
|
||||
loading={Boolean(loading) || Boolean(objectsLoading)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type MonitorManagementPageAction =
|
||||
| {
|
||||
type: 'update';
|
||||
payload: MonitorManagementListPageState;
|
||||
}
|
||||
| { type: 'refresh' };
|
||||
|
||||
const monitorManagementPageReducer: Reducer<
|
||||
MonitorManagementListPageState,
|
||||
MonitorManagementPageAction
|
||||
> = (state: MonitorManagementListPageState, action: MonitorManagementPageAction) => {
|
||||
switch (action.type) {
|
||||
case 'update':
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
case 'refresh':
|
||||
return { ...state };
|
||||
default:
|
||||
throw new Error(`Action "${(action as MonitorManagementPageAction)?.type}" not recognizable`);
|
||||
}
|
||||
};
|
|
@ -58,7 +58,7 @@ export const TEST_NOW_ARIA_LABEL = i18n.translate('xpack.uptime.monitorList.test
|
|||
export const TEST_NOW_AVAILABLE_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorList.testNow.available',
|
||||
{
|
||||
defaultMessage: 'Test now is only available for monitors added via Monitor management.',
|
||||
defaultMessage: 'Test now is only available for monitors added via Monitor Management.',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -74,11 +74,14 @@ export const mockState: AppState = {
|
|||
loading: {
|
||||
monitorList: false,
|
||||
serviceLocations: false,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
monitorList: null,
|
||||
serviceLocations: null,
|
||||
enablement: null,
|
||||
},
|
||||
enablement: null,
|
||||
syntheticsService: {
|
||||
loading: false,
|
||||
},
|
||||
|
|
|
@ -52,7 +52,7 @@ const LOADING_LABEL = i18n.translate('xpack.uptime.monitorManagement.addMonitorL
|
|||
const ERROR_HEADING_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.addMonitorLoadingError',
|
||||
{
|
||||
defaultMessage: 'Error loading monitor management',
|
||||
defaultMessage: 'Error loading Monitor Management',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -49,8 +49,8 @@ const LOADING_LABEL = i18n.translate('xpack.uptime.monitorManagement.editMonitor
|
|||
defaultMessage: 'Loading monitor',
|
||||
});
|
||||
|
||||
const ERROR_HEADING_LABEL = i18n.translate('xpack.uptime.monitorManagement.editMonitorError', {
|
||||
defaultMessage: 'Error loading monitor management',
|
||||
const ERROR_HEADING_LABEL = i18n.translate('xpack.uptime.monitorManagement.manageMonitorError', {
|
||||
defaultMessage: 'Error loading Monitor Management',
|
||||
});
|
||||
|
||||
const SERVICE_LOCATIONS_ERROR_LABEL = i18n.translate(
|
||||
|
|
|
@ -5,116 +5,151 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useReducer, useCallback, Reducer } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { EuiCallOut, EuiButton, EuiSpacer, EuiLink } from '@elastic/eui';
|
||||
import { useTrackPageview } from '../../../../observability/public';
|
||||
import { ConfigKey } from '../../../common/runtime_types';
|
||||
import { getMonitors } from '../../state/actions';
|
||||
import { monitorManagementListSelector } from '../../state/selectors';
|
||||
import { MonitorManagementListPageState } from '../../components/monitor_management/monitor_list/monitor_list';
|
||||
import { useMonitorManagementBreadcrumbs } from './use_monitor_management_breadcrumbs';
|
||||
import { useInlineErrors } from '../../components/monitor_management/hooks/use_inline_errors';
|
||||
import { MonitorListTabs } from '../../components/monitor_management/monitor_list/list_tabs';
|
||||
import { AllMonitors } from '../../components/monitor_management/monitor_list/all_monitors';
|
||||
import { InvalidMonitors } from '../../components/monitor_management/monitor_list/invalid_monitors';
|
||||
import { useInvalidMonitors } from '../../components/monitor_management/hooks/use_invalid_monitors';
|
||||
import { MonitorListContainer } from '../../components/monitor_management/monitor_list/monitor_list_container';
|
||||
import { EnablementEmptyState } from '../../components/monitor_management/monitor_list/enablement_empty_state';
|
||||
import { useEnablement } from '../../components/monitor_management/hooks/use_enablement';
|
||||
import { Loader } from '../../components/monitor_management/loader/loader';
|
||||
|
||||
export const MonitorManagementPage: React.FC = () => {
|
||||
const [pageState, dispatchPageAction] = useReducer<typeof monitorManagementPageReducer>(
|
||||
monitorManagementPageReducer,
|
||||
{
|
||||
pageIndex: 1, // saved objects page index is base 1
|
||||
pageSize: 10,
|
||||
sortOrder: 'asc',
|
||||
sortField: ConfigKey.NAME,
|
||||
}
|
||||
);
|
||||
|
||||
const onPageStateChange = useCallback(
|
||||
(state) => {
|
||||
dispatchPageAction({ type: 'update', payload: state });
|
||||
},
|
||||
[dispatchPageAction]
|
||||
);
|
||||
|
||||
const onUpdate = useCallback(() => {
|
||||
dispatchPageAction({ type: 'refresh' });
|
||||
}, [dispatchPageAction]);
|
||||
|
||||
useTrackPageview({ app: 'uptime', path: 'manage-monitors' });
|
||||
useTrackPageview({ app: 'uptime', path: 'manage-monitors', delay: 15000 });
|
||||
useMonitorManagementBreadcrumbs();
|
||||
const dispatch = useDispatch();
|
||||
const monitorList = useSelector(monitorManagementListSelector);
|
||||
const [shouldFocusEnablementButton, setShouldFocusEnablementButton] = useState(false);
|
||||
|
||||
const { pageIndex, pageSize, sortField, sortOrder } = pageState as MonitorManagementListPageState;
|
||||
const {
|
||||
error: enablementError,
|
||||
enablement,
|
||||
loading: enablementLoading,
|
||||
enableSynthetics,
|
||||
} = useEnablement();
|
||||
const { list: monitorList } = useSelector(monitorManagementListSelector);
|
||||
const { isEnabled } = enablement;
|
||||
|
||||
const { type: viewType } = useParams<{ type: 'all' | 'invalid' }>();
|
||||
const { errorSummaries, loading, count } = useInlineErrors({
|
||||
onlyInvalidMonitors: viewType === 'invalid',
|
||||
sortField: pageState.sortField,
|
||||
sortOrder: pageState.sortOrder,
|
||||
});
|
||||
const isEnabledRef = useRef(isEnabled);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewType === 'all') {
|
||||
dispatch(getMonitors({ page: pageIndex, perPage: pageSize, sortField, sortOrder }));
|
||||
if (monitorList.total === null) {
|
||||
dispatch(
|
||||
getMonitors({
|
||||
page: 1, // saved objects page index is base 1
|
||||
perPage: 10,
|
||||
sortOrder: 'asc',
|
||||
sortField: ConfigKey.NAME,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [dispatch, pageState, pageIndex, pageSize, sortField, sortOrder, viewType]);
|
||||
}, [dispatch, monitorList.total]);
|
||||
|
||||
const { data: monitorSavedObjects, loading: objectsLoading } = useInvalidMonitors(errorSummaries);
|
||||
useEffect(() => {
|
||||
if (!isEnabled && isEnabledRef.current === true) {
|
||||
/* shift focus to enable button when enable toggle disappears. Prevent
|
||||
* focus loss on the page */
|
||||
setShouldFocusEnablementButton(true);
|
||||
}
|
||||
isEnabledRef.current = Boolean(isEnabled);
|
||||
}, [isEnabled]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MonitorListTabs
|
||||
invalidTotal={monitorSavedObjects?.length ?? 0}
|
||||
onUpdate={onUpdate}
|
||||
onPageStateChange={onPageStateChange}
|
||||
/>
|
||||
{viewType === 'all' ? (
|
||||
<AllMonitors
|
||||
pageState={pageState}
|
||||
monitorList={monitorList}
|
||||
onPageStateChange={onPageStateChange}
|
||||
onUpdate={onUpdate}
|
||||
errorSummaries={errorSummaries}
|
||||
/>
|
||||
) : (
|
||||
<InvalidMonitors
|
||||
pageState={pageState}
|
||||
monitorSavedObjects={monitorSavedObjects}
|
||||
onPageStateChange={onPageStateChange}
|
||||
onUpdate={onUpdate}
|
||||
errorSummaries={errorSummaries}
|
||||
invalidTotal={count ?? 0}
|
||||
loading={Boolean(loading) || Boolean(objectsLoading)}
|
||||
/>
|
||||
<Loader
|
||||
loading={enablementLoading || monitorList.total === null}
|
||||
error={Boolean(enablementError)}
|
||||
loadingTitle={LOADING_LABEL}
|
||||
errorTitle={ERROR_HEADING_LABEL}
|
||||
errorBody={ERROR_HEADING_BODY}
|
||||
>
|
||||
{!isEnabled && monitorList.total && monitorList.total > 0 ? (
|
||||
<>
|
||||
<EuiCallOut title={CALLOUT_MANAGEMENT_DISABLED} color="warning" iconType="help">
|
||||
<p>{CALLOUT_MANAGEMENT_DESCRIPTION}</p>
|
||||
{enablement.canEnable ? (
|
||||
<EuiButton
|
||||
fill
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
enableSynthetics();
|
||||
}}
|
||||
>
|
||||
{SYNTHETICS_ENABLE_LABEL}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<p>
|
||||
{CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '}
|
||||
<EuiLink href="#" target="_blank">
|
||||
{LEARN_MORE_LABEL}
|
||||
</EuiLink>
|
||||
</p>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
) : null}
|
||||
{isEnabled || (!isEnabled && monitorList.total) ? <MonitorListContainer /> : null}
|
||||
</Loader>
|
||||
{isEnabled !== undefined && monitorList.total === 0 && (
|
||||
<EnablementEmptyState focusButton={shouldFocusEnablementButton} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type MonitorManagementPageAction =
|
||||
| {
|
||||
type: 'update';
|
||||
payload: MonitorManagementListPageState;
|
||||
}
|
||||
| { type: 'refresh' };
|
||||
const LOADING_LABEL = i18n.translate('xpack.uptime.monitorManagement.manageMonitorLoadingLabel', {
|
||||
defaultMessage: 'Loading Monitor Management',
|
||||
});
|
||||
|
||||
const monitorManagementPageReducer: Reducer<
|
||||
MonitorManagementListPageState,
|
||||
MonitorManagementPageAction
|
||||
> = (state: MonitorManagementListPageState, action: MonitorManagementPageAction) => {
|
||||
switch (action.type) {
|
||||
case 'update':
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
case 'refresh':
|
||||
return { ...state };
|
||||
default:
|
||||
throw new Error(`Action "${(action as MonitorManagementPageAction)?.type}" not recognizable`);
|
||||
const LEARN_MORE_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.manageMonitorLoadingLabel.callout.learnMore',
|
||||
{
|
||||
defaultMessage: 'Learn more.',
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
const CALLOUT_MANAGEMENT_DISABLED = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.callout.disabled',
|
||||
{
|
||||
defaultMessage: 'Monitor Management is disabled',
|
||||
}
|
||||
);
|
||||
|
||||
const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.callout.disabled.adminContact',
|
||||
{
|
||||
defaultMessage: 'Please contact your administrator to enable Monitor Management.',
|
||||
}
|
||||
);
|
||||
|
||||
const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.callout.description.disabled',
|
||||
{
|
||||
defaultMessage:
|
||||
'Monitor Management is currently disabled. To run your monitors on Elastic managed Synthetics service, enable Monitor Management. Your existing monitors are paused.',
|
||||
}
|
||||
);
|
||||
|
||||
const ERROR_HEADING_LABEL = i18n.translate('xpack.uptime.monitorManagement.editMonitorError', {
|
||||
defaultMessage: 'Error loading Monitor Management',
|
||||
});
|
||||
|
||||
const ERROR_HEADING_BODY = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.editMonitorError.description',
|
||||
{
|
||||
defaultMessage: 'Monitor Management settings could not be loaded. Please contact Support.',
|
||||
}
|
||||
);
|
||||
|
||||
const SYNTHETICS_ENABLE_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.syntheticsEnableLabel.management',
|
||||
{
|
||||
defaultMessage: 'Enable Monitor Management',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('ServiceAllowedWrapper', () => {
|
|||
</ServiceAllowedWrapper>
|
||||
);
|
||||
|
||||
expect(await findByText('Loading monitor management')).toBeInTheDocument();
|
||||
expect(await findByText('Loading Monitor Management')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders when enabled state is false', async () => {
|
||||
|
@ -45,7 +45,7 @@ describe('ServiceAllowedWrapper', () => {
|
|||
</ServiceAllowedWrapper>
|
||||
);
|
||||
|
||||
expect(await findByText('Monitor management')).toBeInTheDocument();
|
||||
expect(await findByText('Monitor Management')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders when enabled state is true', async () => {
|
||||
|
|
|
@ -45,13 +45,13 @@ const REQUEST_ACCESS_LABEL = i18n.translate('xpack.uptime.monitorManagement.requ
|
|||
});
|
||||
|
||||
const MONITOR_MANAGEMENT_LABEL = i18n.translate('xpack.uptime.monitorManagement.label', {
|
||||
defaultMessage: 'Monitor management',
|
||||
defaultMessage: 'Monitor Management',
|
||||
});
|
||||
|
||||
const LOADING_MONITOR_MANAGEMENT_LABEL = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.loading.label',
|
||||
{
|
||||
defaultMessage: 'Loading monitor management',
|
||||
defaultMessage: 'Loading Monitor Management',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -59,7 +59,7 @@ const PUBLIC_BETA_DESCRIPTION = i18n.translate(
|
|||
'xpack.uptime.monitorManagement.publicBetaDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Monitor management is available only for selected public beta users. With public\n' +
|
||||
'Monitor Management is available only for selected public beta users. With public\n' +
|
||||
'beta access, you will be able to add HTTP, TCP, ICMP and Browser checks which will\n' +
|
||||
"run on Elastic's managed synthetics service nodes.",
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export const useMonitorManagementBreadcrumbs = ({
|
|||
export const MONITOR_MANAGEMENT_CRUMB = i18n.translate(
|
||||
'xpack.uptime.monitorManagement.monitorManagementCrumb',
|
||||
{
|
||||
defaultMessage: 'Monitor management',
|
||||
defaultMessage: 'Monitor Management',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -30,6 +30,22 @@ export const getServiceLocationsSuccess = createAction<{
|
|||
}>('GET_SERVICE_LOCATIONS_LIST_SUCCESS');
|
||||
export const getServiceLocationsFailure = createAction<Error>('GET_SERVICE_LOCATIONS_LIST_FAILURE');
|
||||
|
||||
export const getSyntheticsEnablement = createAction('GET_SYNTHETICS_ENABLEMENT');
|
||||
export const getSyntheticsEnablementSuccess = createAction<any>(
|
||||
'GET_SYNTHETICS_ENABLEMENT_SUCCESS'
|
||||
);
|
||||
export const getSyntheticsEnablementFailure = createAction<Error>(
|
||||
'GET_SYNTHETICS_ENABLEMENT_FAILURE'
|
||||
);
|
||||
|
||||
export const disableSynthetics = createAction('DISABLE_SYNTHETICS');
|
||||
export const disableSyntheticsSuccess = createAction<any>('DISABLE_SYNTEHTICS_SUCCESS');
|
||||
export const disableSyntheticsFailure = createAction<Error>('DISABLE_SYNTHETICS_FAILURE');
|
||||
|
||||
export const enableSynthetics = createAction('ENABLE_SYNTHETICS');
|
||||
export const enableSyntheticsSuccess = createAction<any>('ENABLE_SYNTEHTICS_SUCCESS');
|
||||
export const enableSyntheticsFailure = createAction<Error>('ENABLE_SYNTHETICS_FAILURE');
|
||||
|
||||
export const getSyntheticsServiceAllowed = createAsyncAction<void, SyntheticsServiceAllowed>(
|
||||
'GET_SYNTHETICS_SERVICE_ALLOWED'
|
||||
);
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
FetchMonitorManagementListQueryArgs,
|
||||
MonitorManagementListResultCodec,
|
||||
MonitorManagementListResult,
|
||||
MonitorManagementEnablementResultCodec,
|
||||
MonitorManagementEnablementResult,
|
||||
ServiceLocations,
|
||||
SyntheticsMonitor,
|
||||
EncryptedSyntheticsMonitor,
|
||||
|
@ -91,6 +93,23 @@ export const testNowMonitor = async (configId: string): Promise<TestNowResponse
|
|||
return await apiService.get(API_URLS.TRIGGER_MONITOR + `/${configId}`);
|
||||
};
|
||||
|
||||
export const fetchGetSyntheticsEnablement =
|
||||
async (): Promise<MonitorManagementEnablementResult> => {
|
||||
return await apiService.get(
|
||||
API_URLS.SYNTHETICS_ENABLEMENT,
|
||||
undefined,
|
||||
MonitorManagementEnablementResultCodec
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchDisableSynthetics = async (): Promise<void> => {
|
||||
return await apiService.delete(API_URLS.SYNTHETICS_ENABLEMENT);
|
||||
};
|
||||
|
||||
export const fetchEnableSynthetics = async (): Promise<void> => {
|
||||
return await apiService.post(API_URLS.SYNTHETICS_ENABLEMENT);
|
||||
};
|
||||
|
||||
export const fetchServiceAllowed = async (): Promise<SyntheticsServiceAllowed> => {
|
||||
return await apiService.get(API_URLS.SERVICE_ALLOWED);
|
||||
};
|
||||
|
|
|
@ -13,9 +13,25 @@ import {
|
|||
getServiceLocations,
|
||||
getServiceLocationsSuccess,
|
||||
getServiceLocationsFailure,
|
||||
getSyntheticsEnablement,
|
||||
getSyntheticsEnablementSuccess,
|
||||
getSyntheticsEnablementFailure,
|
||||
disableSynthetics,
|
||||
disableSyntheticsSuccess,
|
||||
disableSyntheticsFailure,
|
||||
enableSynthetics,
|
||||
enableSyntheticsSuccess,
|
||||
enableSyntheticsFailure,
|
||||
getSyntheticsServiceAllowed,
|
||||
} from '../actions';
|
||||
import { fetchMonitorManagementList, fetchServiceAllowed, fetchServiceLocations } from '../api';
|
||||
import {
|
||||
fetchMonitorManagementList,
|
||||
fetchServiceLocations,
|
||||
fetchServiceAllowed,
|
||||
fetchGetSyntheticsEnablement,
|
||||
fetchDisableSynthetics,
|
||||
fetchEnableSynthetics,
|
||||
} from '../api';
|
||||
import { fetchEffectFactory } from './fetch_effect';
|
||||
|
||||
export function* fetchMonitorManagementEffect() {
|
||||
|
@ -31,6 +47,22 @@ export function* fetchMonitorManagementEffect() {
|
|||
getServiceLocationsFailure
|
||||
)
|
||||
);
|
||||
yield takeLatest(
|
||||
getSyntheticsEnablement,
|
||||
fetchEffectFactory(
|
||||
fetchGetSyntheticsEnablement,
|
||||
getSyntheticsEnablementSuccess,
|
||||
getSyntheticsEnablementFailure
|
||||
)
|
||||
);
|
||||
yield takeLatest(
|
||||
disableSynthetics,
|
||||
fetchEffectFactory(fetchDisableSynthetics, disableSyntheticsSuccess, disableSyntheticsFailure)
|
||||
);
|
||||
yield takeLatest(
|
||||
enableSynthetics,
|
||||
fetchEffectFactory(fetchEnableSynthetics, enableSyntheticsSuccess, enableSyntheticsFailure)
|
||||
);
|
||||
}
|
||||
|
||||
export function* fetchSyntheticsServiceAllowedEffect() {
|
||||
|
|
|
@ -14,23 +14,32 @@ import {
|
|||
getServiceLocations,
|
||||
getServiceLocationsSuccess,
|
||||
getServiceLocationsFailure,
|
||||
getSyntheticsEnablement,
|
||||
getSyntheticsEnablementSuccess,
|
||||
getSyntheticsEnablementFailure,
|
||||
disableSynthetics,
|
||||
disableSyntheticsSuccess,
|
||||
disableSyntheticsFailure,
|
||||
enableSynthetics,
|
||||
enableSyntheticsSuccess,
|
||||
enableSyntheticsFailure,
|
||||
getSyntheticsServiceAllowed,
|
||||
} from '../actions';
|
||||
|
||||
import { SyntheticsServiceAllowed } from '../../../common/types';
|
||||
|
||||
import {
|
||||
MonitorManagementEnablementResult,
|
||||
MonitorManagementListResult,
|
||||
ServiceLocations,
|
||||
ThrottlingOptions,
|
||||
DEFAULT_THROTTLING,
|
||||
} from '../../../common/runtime_types';
|
||||
import { SyntheticsServiceAllowed } from '../../../common/types';
|
||||
|
||||
export interface MonitorManagementList {
|
||||
error: Record<'monitorList' | 'serviceLocations', Error | null>;
|
||||
loading: Record<'monitorList' | 'serviceLocations', boolean>;
|
||||
error: Record<'monitorList' | 'serviceLocations' | 'enablement', Error | null>;
|
||||
loading: Record<'monitorList' | 'serviceLocations' | 'enablement', boolean>;
|
||||
list: MonitorManagementListResult;
|
||||
locations: ServiceLocations;
|
||||
enablement: MonitorManagementEnablementResult | null;
|
||||
syntheticsService: { isAllowed?: boolean; loading: boolean };
|
||||
throttling: ThrottlingOptions;
|
||||
}
|
||||
|
@ -43,13 +52,16 @@ export const initialState: MonitorManagementList = {
|
|||
monitors: [],
|
||||
},
|
||||
locations: [],
|
||||
enablement: null,
|
||||
loading: {
|
||||
monitorList: false,
|
||||
serviceLocations: false,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
monitorList: null,
|
||||
serviceLocations: null,
|
||||
enablement: null,
|
||||
},
|
||||
syntheticsService: {
|
||||
loading: false,
|
||||
|
@ -141,6 +153,116 @@ export const monitorManagementListReducer = createReducer(initialState, (builder
|
|||
},
|
||||
})
|
||||
)
|
||||
.addCase(getSyntheticsEnablement, (state: WritableDraft<MonitorManagementList>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: true,
|
||||
},
|
||||
}))
|
||||
.addCase(
|
||||
getSyntheticsEnablementSuccess,
|
||||
(state: WritableDraft<MonitorManagementList>, action: PayloadAction<any>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
...state.error,
|
||||
enablement: null,
|
||||
},
|
||||
enablement: action.payload,
|
||||
})
|
||||
)
|
||||
.addCase(
|
||||
getSyntheticsEnablementFailure,
|
||||
(state: WritableDraft<MonitorManagementList>, action: PayloadAction<Error>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
...state.error,
|
||||
enablement: action.payload,
|
||||
},
|
||||
})
|
||||
)
|
||||
.addCase(disableSynthetics, (state: WritableDraft<MonitorManagementList>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: true,
|
||||
},
|
||||
}))
|
||||
.addCase(disableSyntheticsSuccess, (state: WritableDraft<MonitorManagementList>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
...state.error,
|
||||
enablement: null,
|
||||
},
|
||||
enablement: {
|
||||
canEnable: state.enablement?.canEnable || false,
|
||||
areApiKeysEnabled: state.enablement?.areApiKeysEnabled || false,
|
||||
isEnabled: false,
|
||||
},
|
||||
}))
|
||||
.addCase(
|
||||
disableSyntheticsFailure,
|
||||
(state: WritableDraft<MonitorManagementList>, action: PayloadAction<Error>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
...state.error,
|
||||
enablement: action.payload,
|
||||
},
|
||||
})
|
||||
)
|
||||
.addCase(enableSynthetics, (state: WritableDraft<MonitorManagementList>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: true,
|
||||
},
|
||||
}))
|
||||
.addCase(enableSyntheticsSuccess, (state: WritableDraft<MonitorManagementList>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
...state.error,
|
||||
enablement: null,
|
||||
},
|
||||
enablement: {
|
||||
canEnable: state.enablement?.canEnable || false,
|
||||
areApiKeysEnabled: state.enablement?.areApiKeysEnabled || false,
|
||||
isEnabled: true,
|
||||
},
|
||||
}))
|
||||
.addCase(
|
||||
enableSyntheticsFailure,
|
||||
(state: WritableDraft<MonitorManagementList>, action: PayloadAction<Error>) => ({
|
||||
...state,
|
||||
loading: {
|
||||
...state.loading,
|
||||
enablement: false,
|
||||
},
|
||||
error: {
|
||||
...state.error,
|
||||
enablement: action.payload,
|
||||
},
|
||||
})
|
||||
)
|
||||
.addCase(
|
||||
String(getSyntheticsServiceAllowed.get),
|
||||
(state: WritableDraft<MonitorManagementList>) => ({
|
||||
|
|
|
@ -26,6 +26,12 @@ import { getJourneyFailedSteps } from './get_journey_failed_steps';
|
|||
import { getLastSuccessfulCheck } from './get_last_successful_check';
|
||||
import { getJourneyScreenshotBlocks } from './get_journey_screenshot_blocks';
|
||||
import { getSyntheticsMonitor } from './get_monitor';
|
||||
import {
|
||||
getSyntheticsEnablement,
|
||||
deleteServiceApiKey,
|
||||
generateAndSaveServiceAPIKey,
|
||||
getAPIKeyForSyntheticsService,
|
||||
} from '../synthetics_service/get_api_key';
|
||||
|
||||
export const requests = {
|
||||
getCerts,
|
||||
|
@ -49,6 +55,10 @@ export const requests = {
|
|||
getJourneyScreenshotBlocks,
|
||||
getJourneyDetails,
|
||||
getNetworkEvents,
|
||||
getSyntheticsEnablement,
|
||||
getAPIKeyForSyntheticsService,
|
||||
deleteServiceApiKey,
|
||||
generateAndSaveServiceAPIKey,
|
||||
};
|
||||
|
||||
export type UptimeRequests = typeof requests;
|
||||
|
|
|
@ -63,6 +63,7 @@ export const getSyntheticsServiceAPIKey = async (client: EncryptedSavedObjectsCl
|
|||
throw getErr;
|
||||
}
|
||||
};
|
||||
|
||||
export const setSyntheticsServiceApiKey = async (
|
||||
client: SavedObjectsClientContract,
|
||||
apiKey: SyntheticsServiceApiKey
|
||||
|
@ -72,3 +73,11 @@ export const setSyntheticsServiceApiKey = async (
|
|||
overwrite: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteSyntheticsServiceApiKey = async (client: SavedObjectsClientContract) => {
|
||||
try {
|
||||
return await client.delete(syntheticsServiceApiKey.name, syntheticsApiKeyID);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { coreMock } from '../../../../../../src/core/server/mocks';
|
|||
import { syntheticsServiceApiKey } from '../saved_objects/service_api_key';
|
||||
import { KibanaRequest } from 'kibana/server';
|
||||
import { UptimeServerSetup } from '../adapters';
|
||||
import { getUptimeESMockClient } from '../requests/helper';
|
||||
|
||||
describe('getAPIKeyTest', function () {
|
||||
const core = coreMock.createStart();
|
||||
|
@ -23,6 +24,7 @@ describe('getAPIKeyTest', function () {
|
|||
security,
|
||||
encryptedSavedObjects,
|
||||
savedObjectsClient: core.savedObjects.getScopedClient(request),
|
||||
uptimeEsClient: getUptimeESMockClient().uptimeEsClient,
|
||||
} as unknown as UptimeServerSetup;
|
||||
|
||||
security.authc.apiKeys.areAPIKeysEnabled = jest.fn().mockReturnValue(true);
|
||||
|
@ -33,38 +35,6 @@ describe('getAPIKeyTest', function () {
|
|||
encoded: '@#$%^&',
|
||||
});
|
||||
|
||||
it('should generate an api key and return it', async () => {
|
||||
const apiKey = await getAPIKeyForSyntheticsService({
|
||||
request,
|
||||
server,
|
||||
});
|
||||
|
||||
expect(security.authc.apiKeys.areAPIKeysEnabled).toHaveBeenCalledTimes(1);
|
||||
expect(security.authc.apiKeys.create).toHaveBeenCalledTimes(1);
|
||||
expect(security.authc.apiKeys.create).toHaveBeenCalledWith(
|
||||
{},
|
||||
{
|
||||
name: 'synthetics-api-key',
|
||||
role_descriptors: {
|
||||
synthetics_writer: {
|
||||
cluster: ['monitor', 'read_ilm', 'read_pipeline'],
|
||||
index: [
|
||||
{
|
||||
names: ['synthetics-*'],
|
||||
privileges: ['view_index_metadata', 'create_doc', 'auto_configure'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
description:
|
||||
'Created for synthetics service to be passed to the heartbeat to communicate with ES',
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(apiKey).toEqual({ apiKey: 'qwerty', id: 'test', name: 'service-api-key' });
|
||||
});
|
||||
|
||||
it('should return existing api key', async () => {
|
||||
const getObject = jest
|
||||
.fn()
|
||||
|
@ -74,7 +44,6 @@ describe('getAPIKeyTest', function () {
|
|||
getDecryptedAsInternalUser: getObject,
|
||||
});
|
||||
const apiKey = await getAPIKeyForSyntheticsService({
|
||||
request,
|
||||
server,
|
||||
});
|
||||
|
||||
|
|
|
@ -4,25 +4,42 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type {
|
||||
SecurityClusterPrivilege,
|
||||
SecurityIndexPrivilege,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import { KibanaRequest, SavedObjectsClientContract } from '../../../../../../src/core/server';
|
||||
|
||||
import { SecurityPluginStart } from '../../../../security/server';
|
||||
import {
|
||||
getSyntheticsServiceAPIKey,
|
||||
deleteSyntheticsServiceApiKey,
|
||||
setSyntheticsServiceApiKey,
|
||||
syntheticsServiceApiKey,
|
||||
} from '../saved_objects/service_api_key';
|
||||
import { SyntheticsServiceApiKey } from '../../../common/runtime_types/synthetics_service_api_key';
|
||||
import { UptimeServerSetup } from '../adapters';
|
||||
|
||||
export const serviceApiKeyPrivileges = {
|
||||
cluster: ['monitor', 'read_ilm', 'read_pipeline'] as SecurityClusterPrivilege[],
|
||||
index: [
|
||||
{
|
||||
names: ['synthetics-*'],
|
||||
privileges: [
|
||||
'view_index_metadata',
|
||||
'create_doc',
|
||||
'auto_configure',
|
||||
] as SecurityIndexPrivilege[],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const getAPIKeyForSyntheticsService = async ({
|
||||
request,
|
||||
server,
|
||||
}: {
|
||||
server: UptimeServerSetup;
|
||||
request?: KibanaRequest;
|
||||
}): Promise<SyntheticsServiceApiKey | undefined> => {
|
||||
const { security, encryptedSavedObjects, authSavedObjectsClient } = server;
|
||||
const { encryptedSavedObjects } = server;
|
||||
|
||||
const encryptedClient = encryptedSavedObjects.getClient({
|
||||
includedHiddenTypes: [syntheticsServiceApiKey.name],
|
||||
|
@ -36,19 +53,15 @@ export const getAPIKeyForSyntheticsService = async ({
|
|||
} catch (err) {
|
||||
// TODO: figure out how to handle decryption errors
|
||||
}
|
||||
|
||||
return await generateAndSaveAPIKey({
|
||||
request,
|
||||
security,
|
||||
authSavedObjectsClient,
|
||||
});
|
||||
};
|
||||
|
||||
export const generateAndSaveAPIKey = async ({
|
||||
export const generateAndSaveServiceAPIKey = async ({
|
||||
server,
|
||||
security,
|
||||
request,
|
||||
authSavedObjectsClient,
|
||||
}: {
|
||||
server: UptimeServerSetup;
|
||||
request?: KibanaRequest;
|
||||
security: SecurityPluginStart;
|
||||
// authSavedObject is needed for write operations
|
||||
|
@ -64,18 +77,15 @@ export const generateAndSaveAPIKey = async ({
|
|||
throw new Error('User authorization is needed for api key generation');
|
||||
}
|
||||
|
||||
const { canEnable } = await getSyntheticsEnablement({ request, server });
|
||||
if (!canEnable) {
|
||||
throw new SyntheticsForbiddenError();
|
||||
}
|
||||
|
||||
const apiKeyResult = await security.authc.apiKeys?.create(request, {
|
||||
name: 'synthetics-api-key',
|
||||
role_descriptors: {
|
||||
synthetics_writer: {
|
||||
cluster: ['monitor', 'read_ilm', 'read_pipeline'],
|
||||
index: [
|
||||
{
|
||||
names: ['synthetics-*'],
|
||||
privileges: ['view_index_metadata', 'create_doc', 'auto_configure'],
|
||||
},
|
||||
],
|
||||
},
|
||||
synthetics_writer: serviceApiKeyPrivileges,
|
||||
},
|
||||
metadata: {
|
||||
description:
|
||||
|
@ -93,3 +103,73 @@ export const generateAndSaveAPIKey = async ({
|
|||
return apiKeyObject;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteServiceApiKey = async ({
|
||||
request,
|
||||
server,
|
||||
savedObjectsClient,
|
||||
}: {
|
||||
server: UptimeServerSetup;
|
||||
request?: KibanaRequest;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}) => {
|
||||
await deleteSyntheticsServiceApiKey(savedObjectsClient);
|
||||
};
|
||||
|
||||
export const getSyntheticsEnablement = async ({
|
||||
request,
|
||||
server: { uptimeEsClient, security, encryptedSavedObjects },
|
||||
}: {
|
||||
server: UptimeServerSetup;
|
||||
request?: KibanaRequest;
|
||||
}) => {
|
||||
const encryptedClient = encryptedSavedObjects.getClient({
|
||||
includedHiddenTypes: [syntheticsServiceApiKey.name],
|
||||
});
|
||||
|
||||
const [apiKey, hasPrivileges, areApiKeysEnabled] = await Promise.all([
|
||||
getSyntheticsServiceAPIKey(encryptedClient),
|
||||
uptimeEsClient.baseESClient.security.hasPrivileges({
|
||||
body: {
|
||||
cluster: [
|
||||
'manage_security',
|
||||
'manage_api_key',
|
||||
'manage_own_api_key',
|
||||
...serviceApiKeyPrivileges.cluster,
|
||||
],
|
||||
index: serviceApiKeyPrivileges.index,
|
||||
},
|
||||
}),
|
||||
security.authc.apiKeys.areAPIKeysEnabled(),
|
||||
]);
|
||||
|
||||
const { cluster } = hasPrivileges;
|
||||
const {
|
||||
manage_security: manageSecurity,
|
||||
manage_api_key: manageApiKey,
|
||||
manage_own_api_key: manageOwnApiKey,
|
||||
monitor,
|
||||
read_ilm: readILM,
|
||||
read_pipeline: readPipeline,
|
||||
} = cluster || {};
|
||||
|
||||
const canManageApiKeys = manageSecurity || manageApiKey || manageOwnApiKey;
|
||||
const hasClusterPermissions = readILM && readPipeline && monitor;
|
||||
const hasIndexPermissions = !Object.values(hasPrivileges.index?.['synthetics-*'] || []).includes(
|
||||
false
|
||||
);
|
||||
|
||||
return {
|
||||
canEnable: canManageApiKeys && hasClusterPermissions && hasIndexPermissions,
|
||||
isEnabled: Boolean(apiKey),
|
||||
areApiKeysEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
export class SyntheticsForbiddenError extends Error {
|
||||
constructor() {
|
||||
super();
|
||||
this.message = 'Forbidden';
|
||||
this.name = 'SyntheticsForbiddenError';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,32 +191,25 @@ export class SyntheticsService {
|
|||
}
|
||||
}
|
||||
|
||||
async getOutput(request?: KibanaRequest) {
|
||||
if (!this.apiKey) {
|
||||
try {
|
||||
this.apiKey = await getAPIKeyForSyntheticsService({ server: this.server, request });
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
async getApiKey() {
|
||||
try {
|
||||
this.apiKey = await getAPIKeyForSyntheticsService({ server: this.server });
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!this.apiKey) {
|
||||
const error = new APIKeyMissingError();
|
||||
this.logger.error(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logger.debug('Found api key and esHosts for service.');
|
||||
return this.apiKey;
|
||||
}
|
||||
|
||||
async getOutput(apiKey: SyntheticsServiceApiKey) {
|
||||
return {
|
||||
hosts: this.esHosts,
|
||||
api_key: `${this.apiKey.id}:${this.apiKey.apiKey}`,
|
||||
api_key: `${apiKey?.id}:${apiKey?.apiKey}`,
|
||||
};
|
||||
}
|
||||
|
||||
async pushConfigs(
|
||||
request?: KibanaRequest,
|
||||
configs?: Array<
|
||||
SyntheticsMonitorWithId & {
|
||||
fields_under_root?: boolean;
|
||||
|
@ -229,9 +222,16 @@ export class SyntheticsService {
|
|||
this.logger.debug('No monitor found which can be pushed to service.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.apiKey = await this.getApiKey();
|
||||
|
||||
if (!this.apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = {
|
||||
monitors,
|
||||
output: await this.getOutput(request),
|
||||
output: await this.getOutput(this.apiKey),
|
||||
};
|
||||
|
||||
this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`);
|
||||
|
@ -245,7 +245,6 @@ export class SyntheticsService {
|
|||
}
|
||||
|
||||
async runOnceConfigs(
|
||||
request?: KibanaRequest,
|
||||
configs?: Array<
|
||||
SyntheticsMonitorWithId & {
|
||||
fields_under_root?: boolean;
|
||||
|
@ -257,9 +256,15 @@ export class SyntheticsService {
|
|||
if (monitors.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.apiKey = await this.getApiKey();
|
||||
|
||||
if (!this.apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = {
|
||||
monitors,
|
||||
output: await this.getOutput(request),
|
||||
output: await this.getOutput(this.apiKey),
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -283,9 +288,16 @@ export class SyntheticsService {
|
|||
if (monitors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.apiKey = await this.getApiKey();
|
||||
|
||||
if (!this.apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = {
|
||||
monitors,
|
||||
output: await this.getOutput(request),
|
||||
output: await this.getOutput(this.apiKey),
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -296,14 +308,25 @@ export class SyntheticsService {
|
|||
}
|
||||
}
|
||||
|
||||
async deleteConfigs(request: KibanaRequest, configs: SyntheticsMonitorWithId[]) {
|
||||
async deleteConfigs(configs: SyntheticsMonitorWithId[]) {
|
||||
this.apiKey = await this.getApiKey();
|
||||
|
||||
if (!this.apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = {
|
||||
monitors: this.formatConfigs(configs),
|
||||
output: await this.getOutput(request),
|
||||
output: await this.getOutput(this.apiKey),
|
||||
};
|
||||
return await this.apiClient.delete(data);
|
||||
}
|
||||
|
||||
async deleteAllConfigs() {
|
||||
const configs = await this.getMonitorConfigs();
|
||||
return await this.deleteConfigs(configs);
|
||||
}
|
||||
|
||||
async getMonitorConfigs() {
|
||||
const savedObjectsClient = this.server.savedObjectsClient;
|
||||
const encryptedClient = this.server.encryptedSavedObjects.getClient();
|
||||
|
@ -362,14 +385,6 @@ export class SyntheticsService {
|
|||
}
|
||||
}
|
||||
|
||||
class APIKeyMissingError extends Error {
|
||||
constructor() {
|
||||
super();
|
||||
this.message = 'API key is needed for synthetics service.';
|
||||
this.name = 'APIKeyMissingError';
|
||||
}
|
||||
}
|
||||
|
||||
class IndexTemplateInstallationError extends Error {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
@ -38,6 +38,11 @@ import { editSyntheticsMonitorRoute } from './synthetics_service/edit_monitor';
|
|||
import { deleteSyntheticsMonitorRoute } from './synthetics_service/delete_monitor';
|
||||
import { runOnceSyntheticsMonitorRoute } from './synthetics_service/run_once_monitor';
|
||||
import { testNowMonitorRoute } from './synthetics_service/test_now_monitor';
|
||||
import {
|
||||
getSyntheticsEnablementRoute,
|
||||
disableSyntheticsRoute,
|
||||
enableSyntheticsRoute,
|
||||
} from './synthetics_service/enablement';
|
||||
import { getServiceAllowedRoute } from './synthetics_service/get_service_allowed';
|
||||
|
||||
export * from './types';
|
||||
|
@ -45,6 +50,8 @@ export { createRouteWithAuth } from './create_route_with_auth';
|
|||
export { uptimeRouteWrapper } from './uptime_route_wrapper';
|
||||
|
||||
export const restApiRoutes: UMRestApiRouteFactory[] = [
|
||||
addSyntheticsMonitorRoute,
|
||||
getSyntheticsEnablementRoute,
|
||||
createGetPingsRoute,
|
||||
createGetIndexStatusRoute,
|
||||
createGetDynamicSettingsRoute,
|
||||
|
@ -63,13 +70,14 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [
|
|||
createJourneyFailedStepsRoute,
|
||||
createLastSuccessfulCheckRoute,
|
||||
createJourneyScreenshotBlocksRoute,
|
||||
installIndexTemplatesRoute,
|
||||
deleteSyntheticsMonitorRoute,
|
||||
disableSyntheticsRoute,
|
||||
editSyntheticsMonitorRoute,
|
||||
enableSyntheticsRoute,
|
||||
getServiceLocationsRoute,
|
||||
getSyntheticsMonitorRoute,
|
||||
getAllSyntheticsMonitorRoute,
|
||||
addSyntheticsMonitorRoute,
|
||||
editSyntheticsMonitorRoute,
|
||||
deleteSyntheticsMonitorRoute,
|
||||
installIndexTemplatesRoute,
|
||||
runOnceSyntheticsMonitorRoute,
|
||||
testNowMonitorRoute,
|
||||
getServiceAllowedRoute,
|
||||
|
|
|
@ -45,7 +45,7 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
|
||||
const { syntheticsService } = server;
|
||||
|
||||
const errors = await syntheticsService.pushConfigs(request, [
|
||||
const errors = await syntheticsService.pushConfigs([
|
||||
{
|
||||
...monitor,
|
||||
id: newMonitor.id,
|
||||
|
|
|
@ -59,7 +59,7 @@ export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
const normalizedMonitor = normalizeSecrets(monitor);
|
||||
|
||||
await savedObjectsClient.delete(syntheticsMonitorType, monitorId);
|
||||
const errors = await syntheticsService.deleteConfigs(request, [
|
||||
const errors = await syntheticsService.deleteConfigs([
|
||||
{ ...normalizedMonitor.attributes, id: monitorId },
|
||||
]);
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
monitor.type === 'browser' ? { ...monitorWithRevision, urls: '' } : monitorWithRevision
|
||||
);
|
||||
|
||||
const errors = await syntheticsService.pushConfigs(request, [
|
||||
const errors = await syntheticsService.pushConfigs([
|
||||
{
|
||||
...editedMonitor,
|
||||
id: editMonitor.id,
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { SyntheticsForbiddenError } from '../../lib/synthetics_service/get_api_key';
|
||||
|
||||
export const getSyntheticsEnablementRoute: UMRestApiRouteFactory = (libs) => ({
|
||||
method: 'GET',
|
||||
path: API_URLS.SYNTHETICS_ENABLEMENT,
|
||||
validate: {},
|
||||
handler: async ({ request, response, server }): Promise<any> => {
|
||||
try {
|
||||
return response.ok({
|
||||
body: await libs.requests.getSyntheticsEnablement({
|
||||
request,
|
||||
server,
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
server.logger.error(e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const disableSyntheticsRoute: UMRestApiRouteFactory = (libs) => ({
|
||||
method: 'DELETE',
|
||||
path: API_URLS.SYNTHETICS_ENABLEMENT,
|
||||
validate: {},
|
||||
handler: async ({ response, request, server, savedObjectsClient }): Promise<any> => {
|
||||
const { syntheticsService, security } = server;
|
||||
try {
|
||||
const { canEnable } = await libs.requests.getSyntheticsEnablement({ request, server });
|
||||
if (!canEnable) {
|
||||
return response.forbidden();
|
||||
}
|
||||
await syntheticsService.deleteAllConfigs();
|
||||
const apiKey = await libs.requests.getAPIKeyForSyntheticsService({
|
||||
server,
|
||||
});
|
||||
await libs.requests.deleteServiceApiKey({
|
||||
request,
|
||||
server,
|
||||
savedObjectsClient,
|
||||
});
|
||||
await security.authc.apiKeys?.invalidate(request, { ids: [apiKey?.id || ''] });
|
||||
return response.ok({});
|
||||
} catch (e) {
|
||||
server.logger.error(e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const enableSyntheticsRoute: UMRestApiRouteFactory = (libs) => ({
|
||||
method: 'POST',
|
||||
path: API_URLS.SYNTHETICS_ENABLEMENT,
|
||||
validate: {},
|
||||
handler: async ({ request, response, server }): Promise<any> => {
|
||||
const { authSavedObjectsClient, logger, security } = server;
|
||||
try {
|
||||
await libs.requests.generateAndSaveServiceAPIKey({
|
||||
request,
|
||||
authSavedObjectsClient,
|
||||
security,
|
||||
server,
|
||||
});
|
||||
return response.ok({});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
if (e instanceof SyntheticsForbiddenError) {
|
||||
return response.forbidden();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
});
|
|
@ -32,7 +32,7 @@ export const runOnceSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
|
||||
const { syntheticsService } = server;
|
||||
|
||||
const errors = await syntheticsService.runOnceConfigs(request, [
|
||||
const errors = await syntheticsService.runOnceConfigs([
|
||||
{
|
||||
...monitor,
|
||||
id: monitorId,
|
||||
|
|
|
@ -53,7 +53,7 @@ export function formatTelemetryEvent({
|
|||
lastUpdatedAt?: string;
|
||||
durationSinceLastUpdated?: number;
|
||||
deletedAt?: string;
|
||||
errors?: ServiceLocationErrors;
|
||||
errors?: ServiceLocationErrors | null;
|
||||
}) {
|
||||
const { attributes } = monitor;
|
||||
|
||||
|
@ -91,7 +91,7 @@ export function formatTelemetryUpdateEvent(
|
|||
currentMonitor: SavedObjectsUpdateResponse<EncryptedSyntheticsMonitor>,
|
||||
previousMonitor: SavedObject<EncryptedSyntheticsMonitor>,
|
||||
kibanaVersion: string,
|
||||
errors?: ServiceLocationErrors
|
||||
errors?: ServiceLocationErrors | null
|
||||
) {
|
||||
let durationSinceLastUpdated: number = 0;
|
||||
if (currentMonitor.updated_at && previousMonitor.updated_at) {
|
||||
|
@ -113,7 +113,7 @@ export function formatTelemetryDeleteEvent(
|
|||
previousMonitor: SavedObject<EncryptedSyntheticsMonitor>,
|
||||
kibanaVersion: string,
|
||||
deletedAt: string,
|
||||
errors?: ServiceLocationErrors
|
||||
errors?: ServiceLocationErrors | null
|
||||
) {
|
||||
let durationSinceLastUpdated: number = 0;
|
||||
if (deletedAt && previousMonitor.updated_at) {
|
||||
|
|
|
@ -30,7 +30,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
return res.body as SimpleSavedObject<MonitorFields>;
|
||||
};
|
||||
|
||||
before(() => {
|
||||
before(async () => {
|
||||
await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
|
||||
|
||||
_monitors = [
|
||||
getFixtureJson('icmp_monitor'),
|
||||
getFixtureJson('tcp_monitor'),
|
||||
|
|
|
@ -77,6 +77,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./add_monitor'));
|
||||
loadTestFile(require.resolve('./edit_monitor'));
|
||||
loadTestFile(require.resolve('./delete_monitor'));
|
||||
loadTestFile(require.resolve('./synthetics_enablement'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { API_URLS } from '../../../../../plugins/uptime/common/constants';
|
||||
import { serviceApiKeyPrivileges } from '../../../../../plugins/uptime/server/lib/synthetics_service/get_api_key';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
describe('/internal/uptime/service/enablement', () => {
|
||||
const supertestWithAuth = getService('supertest');
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
const security = getService('security');
|
||||
|
||||
before(async () => {
|
||||
await supertestWithAuth.delete(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true');
|
||||
});
|
||||
|
||||
describe('[GET] - /internal/uptime/service/enablement', () => {
|
||||
['manage_security', 'manage_api_key', 'manage_own_api_key'].forEach((privilege) => {
|
||||
it(`returns response for an admin with priviledge ${privilege}`, async () => {
|
||||
const username = 'admin';
|
||||
const roleName = `synthetics_admin-${privilege}`;
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
elasticsearch: {
|
||||
cluster: [privilege, ...serviceApiKeyPrivileges.cluster],
|
||||
indices: serviceApiKeyPrivileges.index,
|
||||
},
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.body).eql({
|
||||
areApiKeysEnabled: true,
|
||||
canEnable: true,
|
||||
isEnabled: false,
|
||||
});
|
||||
} finally {
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns response for an uptime all user without admin privileges', async () => {
|
||||
const username = 'uptime';
|
||||
const roleName = 'uptime_user';
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
elasticsearch: {},
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.body).eql({
|
||||
areApiKeysEnabled: true,
|
||||
canEnable: false,
|
||||
isEnabled: false,
|
||||
});
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('[POST] - /internal/uptime/service/enablement', () => {
|
||||
it('with an admin', async () => {
|
||||
const username = 'admin';
|
||||
const roleName = `synthetics_admin`;
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
elasticsearch: {
|
||||
cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster],
|
||||
indices: serviceApiKeyPrivileges.index,
|
||||
},
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
await supertest
|
||||
.post(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.body).eql({
|
||||
areApiKeysEnabled: true,
|
||||
canEnable: true,
|
||||
isEnabled: true,
|
||||
});
|
||||
} finally {
|
||||
await supertest
|
||||
.delete(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
|
||||
it('with an uptime user', async () => {
|
||||
const username = 'uptime';
|
||||
const roleName = `uptime_user`;
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
elasticsearch: {},
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
await supertest
|
||||
.post(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(403);
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
expect(apiResponse.body).eql({
|
||||
areApiKeysEnabled: true,
|
||||
canEnable: false,
|
||||
isEnabled: false,
|
||||
});
|
||||
} finally {
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('[DELETE] - /internal/uptime/service/enablement', () => {
|
||||
it('with an admin', async () => {
|
||||
const username = 'admin';
|
||||
const roleName = `synthetics_admin`;
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
elasticsearch: {
|
||||
cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster],
|
||||
indices: serviceApiKeyPrivileges.index,
|
||||
},
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
await supertest
|
||||
.post(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
await supertest
|
||||
.delete(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.body).eql({
|
||||
areApiKeysEnabled: true,
|
||||
canEnable: true,
|
||||
isEnabled: false,
|
||||
});
|
||||
} finally {
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
|
||||
it('with an uptime user', async () => {
|
||||
const username = 'uptime';
|
||||
const roleName = `uptime_user`;
|
||||
const password = `${username}-password`;
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
elasticsearch: {},
|
||||
});
|
||||
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
|
||||
await supertestWithAuth
|
||||
.post(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
await supertest
|
||||
.delete(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(403);
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
expect(apiResponse.body).eql({
|
||||
areApiKeysEnabled: true,
|
||||
canEnable: false,
|
||||
isEnabled: true,
|
||||
});
|
||||
} finally {
|
||||
await supertestWithAuth
|
||||
.delete(API_URLS.SYNTHETICS_ENABLEMENT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue