[Monitoring] Some progress on making alerts better in the UI (#81569)
* Some progress on making alerts better in the UI * Handle edge case * Updates * More updates * Show kibana instances alerts better * Stop showing missing nodes and improve the detail alert UI * WIP * Fix the badge display * Okay I think this is finally working * Fix type issues * Fix tests * Fix tests * Fix alert counts * Fix setup mode listing * Better detail page view of alerts * Feedback * Sorting * Fix a couple small issues * Start of unit tests * I don't think we need this Mock type * Fix types * More tests * Improve tests and fix sorting * Make this test more resilient * Updates after merging master * Fix tests * Fix types, and improve tests * PR comments * Remove nextStep logic * PR feedback * PR feedback * Removing unnecessary changes * Fixing bad merge issues * Remove unused imports * Add tooltip to alerts grouped by node * Fix up stateFilter usage * Code clean up * PR feedback * Fix state filtering in the category list * Fix types * Fix test * Fix types * Update snapshots Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
95beef7637
commit
1e8f2f66eb
|
@ -453,6 +453,42 @@ export const ALERT_DETAILS = {
|
|||
},
|
||||
};
|
||||
|
||||
export const ALERT_PANEL_MENU = [
|
||||
{
|
||||
label: i18n.translate('xpack.monitoring.alerts.badge.panelCategory.clusterHealth', {
|
||||
defaultMessage: 'Cluster health',
|
||||
}),
|
||||
alerts: [
|
||||
{ alertName: ALERT_NODES_CHANGED },
|
||||
{ alertName: ALERT_CLUSTER_HEALTH },
|
||||
{ alertName: ALERT_ELASTICSEARCH_VERSION_MISMATCH },
|
||||
{ alertName: ALERT_KIBANA_VERSION_MISMATCH },
|
||||
{ alertName: ALERT_LOGSTASH_VERSION_MISMATCH },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.monitoring.alerts.badge.panelCategory.resourceUtilization', {
|
||||
defaultMessage: 'Resource utilization',
|
||||
}),
|
||||
alerts: [
|
||||
{ alertName: ALERT_CPU_USAGE },
|
||||
{ alertName: ALERT_DISK_USAGE },
|
||||
{ alertName: ALERT_MEMORY_USAGE },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.monitoring.alerts.badge.panelCategory.errors', {
|
||||
defaultMessage: 'Errors and exceptions',
|
||||
}),
|
||||
alerts: [
|
||||
{ alertName: ALERT_MISSING_MONITORING_DATA },
|
||||
{ alertName: ALERT_LICENSE_EXPIRATION },
|
||||
{ alertName: ALERT_THREAD_POOL_SEARCH_REJECTIONS },
|
||||
{ alertName: ALERT_THREAD_POOL_WRITE_REJECTIONS },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* A listing of all alert types
|
||||
*/
|
||||
|
|
|
@ -11,13 +11,14 @@ export const SMALL_FLOAT = '0.[00]';
|
|||
export const LARGE_BYTES = '0,0.0 b';
|
||||
export const SMALL_BYTES = '0.0 b';
|
||||
export const LARGE_ABBREVIATED = '0,0.[0]a';
|
||||
export const ROUNDED_FLOAT = '00.[00]';
|
||||
|
||||
/**
|
||||
* Format the {@code date} in the user's expected date/time format using their <em>guessed</em> local time zone.
|
||||
* @param date Either a numeric Unix timestamp or a {@code Date} object
|
||||
* @returns The date formatted using 'LL LTS'
|
||||
*/
|
||||
export function formatDateTimeLocal(date, useUTC = false, timezone = null) {
|
||||
export function formatDateTimeLocal(date: number | Date, useUTC = false, timezone = null) {
|
||||
return useUTC
|
||||
? moment.utc(date).format('LL LTS')
|
||||
: moment.tz(date, timezone || moment.tz.guess()).format('LL LTS');
|
||||
|
@ -28,6 +29,18 @@ export function formatDateTimeLocal(date, useUTC = false, timezone = null) {
|
|||
* @param {string} hash The complete hash
|
||||
* @return {string} The shortened hash
|
||||
*/
|
||||
export function shortenPipelineHash(hash) {
|
||||
export function shortenPipelineHash(hash: string) {
|
||||
return hash.substr(0, 6);
|
||||
}
|
||||
|
||||
export function getDateFromNow(timestamp: string | number | Date, tz: string) {
|
||||
return moment(timestamp)
|
||||
.tz(tz === 'Browser' ? moment.tz.guess() : tz)
|
||||
.fromNow();
|
||||
}
|
||||
|
||||
export function getCalendar(timestamp: string | number | Date, tz: string) {
|
||||
return moment(timestamp)
|
||||
.tz(tz === 'Browser' ? moment.tz.guess() : tz)
|
||||
.calendar();
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
import { Alert, SanitizedAlert } from '../../../alerts/common';
|
||||
import { AlertParamType, AlertMessageTokenType, AlertSeverity } from '../enums';
|
||||
|
||||
export type CommonAlert = Alert | SanitizedAlert;
|
||||
|
||||
export interface CommonAlertStatus {
|
||||
states: CommonAlertState[];
|
||||
rawAlert: Alert | SanitizedAlert;
|
||||
|
@ -179,6 +181,7 @@ export interface LegacyAlert {
|
|||
message: string;
|
||||
resolved_timestamp: string;
|
||||
metadata: LegacyAlertMetadata;
|
||||
nodeName: string;
|
||||
nodes?: LegacyAlertNodesChangedList;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,187 +4,115 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiContextMenu,
|
||||
EuiPopover,
|
||||
EuiBadge,
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { CommonAlertStatus, CommonAlertState } from '../../common/types/alerts';
|
||||
import { EuiContextMenu, EuiPopover, EuiBadge, EuiSwitch } from '@elastic/eui';
|
||||
import { AlertState, CommonAlertStatus } from '../../common/types/alerts';
|
||||
import { AlertSeverity } from '../../common/enums';
|
||||
// @ts-ignore
|
||||
import { formatDateTimeLocal } from '../../common/formatting';
|
||||
import { AlertState } from '../../common/types/alerts';
|
||||
import { AlertPanel } from './panel';
|
||||
import { Legacy } from '../legacy_shims';
|
||||
import { isInSetupMode } from '../lib/setup_mode';
|
||||
import { SetupModeContext } from '../components/setup_mode/setup_mode_context';
|
||||
|
||||
function getDateFromState(state: CommonAlertState) {
|
||||
const timestamp = state.state.ui.triggeredMS;
|
||||
const tz = Legacy.shims.uiSettings.get('dateFormat:tz');
|
||||
return formatDateTimeLocal(timestamp, false, tz === 'Browser' ? null : tz);
|
||||
}
|
||||
import { AlertsContext } from './context';
|
||||
import { getAlertPanelsByCategory } from './lib/get_alert_panels_by_category';
|
||||
import { getAlertPanelsByNode } from './lib/get_alert_panels_by_node';
|
||||
|
||||
export const numberOfAlertsLabel = (count: number) => `${count} alert${count > 1 ? 's' : ''}`;
|
||||
|
||||
interface AlertInPanel {
|
||||
alert: CommonAlertStatus;
|
||||
alertState: CommonAlertState;
|
||||
}
|
||||
const MAX_TO_SHOW_BY_CATEGORY = 8;
|
||||
|
||||
const PANEL_TITLE = i18n.translate('xpack.monitoring.alerts.badge.panelTitle', {
|
||||
defaultMessage: 'Alerts',
|
||||
});
|
||||
|
||||
const GROUP_BY_NODE = i18n.translate('xpack.monitoring.alerts.badge.groupByNode', {
|
||||
defaultMessage: 'Group by node',
|
||||
});
|
||||
|
||||
const GROUP_BY_TYPE = i18n.translate('xpack.monitoring.alerts.badge.groupByType', {
|
||||
defaultMessage: 'Group by alert type',
|
||||
});
|
||||
|
||||
interface Props {
|
||||
alerts: { [alertTypeId: string]: CommonAlertStatus };
|
||||
stateFilter: (state: AlertState) => boolean;
|
||||
}
|
||||
export const AlertsBadge: React.FC<Props> = (props: Props) => {
|
||||
// We do not always have the alerts that each consumer wants due to licensing
|
||||
const { stateFilter = () => true } = props;
|
||||
const alerts = Object.values(props.alerts).filter((alertItem) => Boolean(alertItem?.rawAlert));
|
||||
const [showPopover, setShowPopover] = React.useState<AlertSeverity | boolean | null>(null);
|
||||
const inSetupMode = isInSetupMode(React.useContext(SetupModeContext));
|
||||
const alerts = Object.values(props.alerts).filter((alertItem) => Boolean(alertItem?.rawAlert));
|
||||
const alertsContext = React.useContext(AlertsContext);
|
||||
const alertCount = inSetupMode
|
||||
? alerts.length
|
||||
: alerts.reduce(
|
||||
(sum, { states }) => sum + states.filter(({ state }) => stateFilter(state)).length,
|
||||
0
|
||||
);
|
||||
const [showByNode, setShowByNode] = React.useState(
|
||||
!inSetupMode && alertCount > MAX_TO_SHOW_BY_CATEGORY
|
||||
);
|
||||
|
||||
if (alerts.length === 0) {
|
||||
React.useEffect(() => {
|
||||
if (inSetupMode && showByNode) {
|
||||
setShowByNode(false);
|
||||
}
|
||||
}, [inSetupMode, showByNode]);
|
||||
|
||||
if (alertCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const badges = [];
|
||||
const groupByType = GROUP_BY_NODE;
|
||||
const panels = showByNode
|
||||
? getAlertPanelsByNode(PANEL_TITLE, alerts, stateFilter)
|
||||
: getAlertPanelsByCategory(PANEL_TITLE, inSetupMode, alerts, alertsContext, stateFilter);
|
||||
|
||||
if (inSetupMode) {
|
||||
const button = (
|
||||
<EuiBadge
|
||||
iconType="bell"
|
||||
onClickAriaLabel={numberOfAlertsLabel(alerts.length)}
|
||||
onClick={() => setShowPopover(true)}
|
||||
>
|
||||
{numberOfAlertsLabel(alerts.length)}
|
||||
</EuiBadge>
|
||||
);
|
||||
const panels = [
|
||||
{
|
||||
id: 0,
|
||||
title: i18n.translate('xpack.monitoring.alerts.badge.panelTitle', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
items: alerts.map(({ rawAlert }, index) => {
|
||||
return {
|
||||
name: <EuiText>{rawAlert.name}</EuiText>,
|
||||
panel: index + 1,
|
||||
};
|
||||
}),
|
||||
},
|
||||
...alerts.map((alertStatus, index) => {
|
||||
return {
|
||||
id: index + 1,
|
||||
title: alertStatus.rawAlert.name,
|
||||
width: 400,
|
||||
content: <AlertPanel alert={alertStatus} />,
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
||||
badges.push(
|
||||
<EuiPopover
|
||||
id="monitoringAlertMenu"
|
||||
button={button}
|
||||
isOpen={showPopover === true}
|
||||
closePopover={() => setShowPopover(null)}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
} else {
|
||||
const byType = {
|
||||
[AlertSeverity.Danger]: [] as AlertInPanel[],
|
||||
[AlertSeverity.Warning]: [] as AlertInPanel[],
|
||||
[AlertSeverity.Success]: [] as AlertInPanel[],
|
||||
};
|
||||
|
||||
for (const alert of alerts) {
|
||||
for (const alertState of alert.states) {
|
||||
if (alertState.firing && stateFilter(alertState.state)) {
|
||||
const state = alertState.state as AlertState;
|
||||
byType[state.ui.severity].push({
|
||||
alertState,
|
||||
alert,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const typesToShow = [AlertSeverity.Danger, AlertSeverity.Warning];
|
||||
for (const type of typesToShow) {
|
||||
const list = byType[type];
|
||||
if (list.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const button = (
|
||||
<EuiBadge
|
||||
iconType="bell"
|
||||
color={type}
|
||||
onClickAriaLabel={numberOfAlertsLabel(list.length)}
|
||||
onClick={() => setShowPopover(type)}
|
||||
>
|
||||
{numberOfAlertsLabel(list.length)}
|
||||
</EuiBadge>
|
||||
);
|
||||
|
||||
const panels = [
|
||||
if (panels.length && !inSetupMode && panels[0].items) {
|
||||
panels[0].items.push(
|
||||
...[
|
||||
{
|
||||
id: 0,
|
||||
title: `Alerts`,
|
||||
items: list.map(({ alert, alertState }, index) => {
|
||||
return {
|
||||
name: (
|
||||
<Fragment>
|
||||
<EuiText size="s">
|
||||
<h4>{getDateFromState(alertState)}</h4>
|
||||
</EuiText>
|
||||
<EuiText>{alert.rawAlert.name}</EuiText>
|
||||
</Fragment>
|
||||
),
|
||||
panel: index + 1,
|
||||
};
|
||||
}),
|
||||
isSeparator: true as const,
|
||||
},
|
||||
...list.map((alertStatus, index) => {
|
||||
return {
|
||||
id: index + 1,
|
||||
title: getDateFromState(alertStatus.alertState),
|
||||
width: 400,
|
||||
content: <AlertPanel alert={alertStatus.alert} alertState={alertStatus.alertState} />,
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
||||
badges.push(
|
||||
<EuiPopover
|
||||
id="monitoringAlertMenu"
|
||||
button={button}
|
||||
isOpen={showPopover === type}
|
||||
closePopover={() => setShowPopover(null)}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
{
|
||||
name: (
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
onChange={() => setShowByNode(!showByNode)}
|
||||
label={showByNode ? GROUP_BY_TYPE : groupByType}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
const button = (
|
||||
<EuiBadge
|
||||
iconType="bell"
|
||||
color={inSetupMode ? 'default' : 'danger'}
|
||||
onClickAriaLabel={numberOfAlertsLabel(alertCount)}
|
||||
onClick={() => setShowPopover(true)}
|
||||
>
|
||||
{numberOfAlertsLabel(alertCount)}
|
||||
</EuiBadge>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGrid data-test-subj="monitoringSetupModeAlertBadges">
|
||||
{badges.map((badge, index) => (
|
||||
<EuiFlexItem key={index} grow={false}>
|
||||
{badge}
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
<EuiPopover
|
||||
id="monitoringAlertMenu"
|
||||
button={button}
|
||||
isOpen={showPopover === true}
|
||||
closePopover={() => setShowPopover(null)}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu
|
||||
key={`${showByNode ? 'byNode' : 'byType'}_${panels.length}`}
|
||||
initialPanelId={0}
|
||||
panels={panels}
|
||||
/>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,78 +5,108 @@
|
|||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { CommonAlertStatus } from '../../common/types/alerts';
|
||||
import { AlertSeverity } from '../../common/enums';
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiAccordion,
|
||||
EuiListGroup,
|
||||
EuiListGroupItem,
|
||||
EuiTextColor,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { replaceTokens } from './lib/replace_tokens';
|
||||
import { AlertMessage, AlertState } from '../../common/types/alerts';
|
||||
|
||||
const TYPES = [
|
||||
{
|
||||
severity: AlertSeverity.Warning,
|
||||
color: 'warning',
|
||||
label: i18n.translate('xpack.monitoring.alerts.callout.warningLabel', {
|
||||
defaultMessage: 'Warning alert(s)',
|
||||
}),
|
||||
},
|
||||
{
|
||||
severity: AlertSeverity.Danger,
|
||||
color: 'danger',
|
||||
label: i18n.translate('xpack.monitoring.alerts.callout.dangerLabel', {
|
||||
defaultMessage: 'Danger alert(s)',
|
||||
}),
|
||||
},
|
||||
];
|
||||
import { AlertMessage } from '../../common/types/alerts';
|
||||
import { AlertsByName } from './types';
|
||||
import { isInSetupMode } from '../lib/setup_mode';
|
||||
import { SetupModeContext } from '../components/setup_mode/setup_mode_context';
|
||||
import { AlertConfiguration } from './configuration';
|
||||
|
||||
interface Props {
|
||||
alerts: { [alertTypeId: string]: CommonAlertStatus };
|
||||
stateFilter: (state: AlertState) => boolean;
|
||||
alerts: AlertsByName;
|
||||
}
|
||||
export const AlertsCallout: React.FC<Props> = (props: Props) => {
|
||||
const { alerts, stateFilter = () => true } = props;
|
||||
const { alerts } = props;
|
||||
const inSetupMode = isInSetupMode(React.useContext(SetupModeContext));
|
||||
|
||||
const callouts = TYPES.map((type) => {
|
||||
const list = [];
|
||||
for (const alertTypeId of Object.keys(alerts)) {
|
||||
const alertInstance = alerts[alertTypeId];
|
||||
for (const { firing, state } of alertInstance.states) {
|
||||
if (firing && stateFilter(state) && state.ui.severity === type.severity) {
|
||||
list.push(state);
|
||||
}
|
||||
}
|
||||
if (inSetupMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const list = [];
|
||||
for (const alertTypeId of Object.keys(alerts)) {
|
||||
const alertInstance = alerts[alertTypeId];
|
||||
for (const state of alertInstance.states) {
|
||||
list.push({
|
||||
alert: alertInstance,
|
||||
state,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (list.length) {
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiCallOut title={type.label} color={type.severity} iconType="bell">
|
||||
<ul>
|
||||
{list.map((state, index) => {
|
||||
const nextStepsUi =
|
||||
state.ui.message.nextSteps && state.ui.message.nextSteps.length ? (
|
||||
<ul>
|
||||
{state.ui.message.nextSteps.map(
|
||||
(step: AlertMessage, nextStepIndex: number) => (
|
||||
<li key={nextStepIndex}>{replaceTokens(step)}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
) : null;
|
||||
if (list.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={index}>
|
||||
{replaceTokens(state.ui.message)}
|
||||
{nextStepsUi}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
const accordions = list.map((status, index) => {
|
||||
const buttonContent = (
|
||||
<div>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="alert" size="m" color="danger" />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiTextColor color="danger">
|
||||
{replaceTokens(status.state.state.ui.message)}
|
||||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
|
||||
const accordion = (
|
||||
<EuiAccordion
|
||||
id={`monitoringAlertCallout_${index}`}
|
||||
buttonContent={buttonContent}
|
||||
paddingSize="s"
|
||||
>
|
||||
<EuiListGroup
|
||||
flush={true}
|
||||
bordered={true}
|
||||
gutterSize="m"
|
||||
size="xs"
|
||||
style={{
|
||||
marginTop: '0.5rem',
|
||||
paddingTop: '0.5rem',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingLeft: `0.5rem`,
|
||||
}}
|
||||
>
|
||||
{(status.state.state.ui.message.nextSteps || []).map((step: AlertMessage) => {
|
||||
return <EuiListGroupItem onClick={() => {}} label={replaceTokens(step)} />;
|
||||
})}
|
||||
<EuiListGroupItem
|
||||
label={<AlertConfiguration alert={status.alert.rawAlert} compressed />}
|
||||
/>
|
||||
</EuiListGroup>
|
||||
</EuiAccordion>
|
||||
);
|
||||
|
||||
const spacer = index !== list.length - 1 ? <EuiSpacer /> : null;
|
||||
return (
|
||||
<div key={index}>
|
||||
{accordion}
|
||||
{spacer}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return <Fragment>{callouts}</Fragment>;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiPanel>{accordions}</EuiPanel>
|
||||
<EuiSpacer />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
|
166
x-pack/plugins/monitoring/public/alerts/configuration.tsx
Normal file
166
x-pack/plugins/monitoring/public/alerts/configuration.tsx
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { Fragment, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui';
|
||||
import { CommonAlert } from '../../common/types/alerts';
|
||||
import { Legacy } from '../legacy_shims';
|
||||
import { hideBottomBar, showBottomBar } from '../lib/setup_mode';
|
||||
import { BASE_ALERT_API_PATH } from '../../../alerts/common';
|
||||
|
||||
interface Props {
|
||||
alert: CommonAlert;
|
||||
compressed?: boolean;
|
||||
}
|
||||
export const AlertConfiguration: React.FC<Props> = (props: Props) => {
|
||||
const { alert, compressed } = props;
|
||||
const [showFlyout, setShowFlyout] = React.useState(false);
|
||||
const [isEnabled, setIsEnabled] = React.useState(alert.enabled);
|
||||
const [isMuted, setIsMuted] = React.useState(alert.muteAll);
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
|
||||
async function disableAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_disable`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', {
|
||||
defaultMessage: `Unable to disable alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
async function enableAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_enable`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', {
|
||||
defaultMessage: `Unable to enable alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
async function muteAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_mute_all`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', {
|
||||
defaultMessage: `Unable to mute alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
async function unmuteAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_unmute_all`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', {
|
||||
defaultMessage: `Unable to unmute alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
||||
const flyoutUi = useMemo(
|
||||
() =>
|
||||
showFlyout &&
|
||||
Legacy.shims.triggersActionsUi.getEditAlertFlyout({
|
||||
initialAlert: alert,
|
||||
onClose: () => {
|
||||
setShowFlyout(false);
|
||||
showBottomBar();
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[showFlyout]
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup
|
||||
justifyContent={compressed ? 'flexStart' : 'spaceBetween'}
|
||||
gutterSize={compressed ? 'm' : 'xs'}
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size={compressed ? 's' : 'm'}
|
||||
onClick={() => {
|
||||
setShowFlyout(true);
|
||||
hideBottomBar();
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.monitoring.alerts.panel.editAlert', {
|
||||
defaultMessage: `Edit alert`,
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
name="disable"
|
||||
disabled={isSaving}
|
||||
checked={!isEnabled}
|
||||
onChange={async () => {
|
||||
if (isEnabled) {
|
||||
setIsEnabled(false);
|
||||
await disableAlert();
|
||||
} else {
|
||||
setIsEnabled(true);
|
||||
await enableAlert();
|
||||
}
|
||||
}}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.alerts.panel.disableTitle"
|
||||
defaultMessage="Disable"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
name="mute"
|
||||
disabled={isSaving}
|
||||
checked={isMuted}
|
||||
data-test-subj="muteSwitch"
|
||||
onChange={async () => {
|
||||
if (isMuted) {
|
||||
setIsMuted(false);
|
||||
await unmuteAlert();
|
||||
} else {
|
||||
setIsMuted(true);
|
||||
await muteAlert();
|
||||
}
|
||||
}}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.alerts.panel.muteTitle"
|
||||
defaultMessage="Mute"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{flyoutUi}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
16
x-pack/plugins/monitoring/public/alerts/context.ts
Normal file
16
x-pack/plugins/monitoring/public/alerts/context.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { AlertsByName } from './types';
|
||||
|
||||
export interface IAlertsContext {
|
||||
allAlerts: AlertsByName;
|
||||
}
|
||||
|
||||
export const AlertsContext = React.createContext({
|
||||
allAlerts: {} as AlertsByName,
|
||||
});
|
1287
x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap
generated
Normal file
1287
x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap
generated
Normal file
File diff suppressed because it is too large
Load diff
660
x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap
generated
Normal file
660
x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,660 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getAlertPanelsByNode should not show any alert if none are firing 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"id": 0,
|
||||
"items": Array [],
|
||||
"title": "Alerts",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`getAlertPanelsByNode should properly group for alerts in a single category 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"id": 0,
|
||||
"items": Array [
|
||||
Object {
|
||||
"name": <EuiText>
|
||||
es_name_0
|
||||
(
|
||||
1
|
||||
)
|
||||
</EuiText>,
|
||||
"panel": 1,
|
||||
},
|
||||
Object {
|
||||
"name": <EuiText>
|
||||
es_name_1
|
||||
(
|
||||
1
|
||||
)
|
||||
</EuiText>,
|
||||
"panel": 2,
|
||||
},
|
||||
],
|
||||
"title": "Alerts",
|
||||
},
|
||||
Object {
|
||||
"id": 1,
|
||||
"items": Array [
|
||||
Object {
|
||||
"name": <React.Fragment>
|
||||
<EuiToolTip
|
||||
content="triggered:0"
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
triggered:0
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
monitoring_alert_jvm_memory_usage_label
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"panel": 3,
|
||||
},
|
||||
],
|
||||
"title": "es_name_0",
|
||||
},
|
||||
Object {
|
||||
"id": 2,
|
||||
"items": Array [
|
||||
Object {
|
||||
"name": <React.Fragment>
|
||||
<EuiToolTip
|
||||
content="triggered:0"
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
triggered:0
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
monitoring_alert_jvm_memory_usage_label
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"panel": 4,
|
||||
},
|
||||
],
|
||||
"title": "es_name_1",
|
||||
},
|
||||
Object {
|
||||
"content": <AlertPanel
|
||||
alert={
|
||||
Object {
|
||||
"actions": Array [],
|
||||
"alertTypeId": "monitoring_alert_jvm_memory_usage",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "",
|
||||
"createdAt": 2020-12-08T00:00:00.000Z,
|
||||
"createdBy": null,
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": 2020-12-08T00:00:00.000Z,
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "monitoring_alert_jvm_memory_usage_label",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": 2020-12-08T00:00:00.000Z,
|
||||
"updatedBy": null,
|
||||
}
|
||||
}
|
||||
alertState={
|
||||
Object {
|
||||
"firing": true,
|
||||
"meta": Object {},
|
||||
"state": Object {
|
||||
"cluster": Object {
|
||||
"clusterName": "one",
|
||||
"clusterUuid": "1",
|
||||
},
|
||||
"nodeId": "es0",
|
||||
"nodeName": "es_name_0",
|
||||
"ui": Object {
|
||||
"isFiring": false,
|
||||
"lastCheckedMS": 0,
|
||||
"message": Object {
|
||||
"text": "",
|
||||
},
|
||||
"resolvedMS": 0,
|
||||
"severity": "danger",
|
||||
"triggeredMS": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"id": 3,
|
||||
"title": "monitoring_alert_jvm_memory_usage_label",
|
||||
"width": 400,
|
||||
},
|
||||
Object {
|
||||
"content": <AlertPanel
|
||||
alert={
|
||||
Object {
|
||||
"actions": Array [],
|
||||
"alertTypeId": "monitoring_alert_jvm_memory_usage",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "",
|
||||
"createdAt": 2020-12-08T00:00:00.000Z,
|
||||
"createdBy": null,
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": 2020-12-08T00:00:00.000Z,
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "monitoring_alert_jvm_memory_usage_label",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": 2020-12-08T00:00:00.000Z,
|
||||
"updatedBy": null,
|
||||
}
|
||||
}
|
||||
alertState={
|
||||
Object {
|
||||
"firing": true,
|
||||
"meta": Object {},
|
||||
"state": Object {
|
||||
"cluster": Object {
|
||||
"clusterName": "one",
|
||||
"clusterUuid": "1",
|
||||
},
|
||||
"nodeId": "es1",
|
||||
"nodeName": "es_name_1",
|
||||
"ui": Object {
|
||||
"isFiring": false,
|
||||
"lastCheckedMS": 0,
|
||||
"message": Object {
|
||||
"text": "",
|
||||
},
|
||||
"resolvedMS": 0,
|
||||
"severity": "danger",
|
||||
"triggeredMS": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"id": 4,
|
||||
"title": "monitoring_alert_jvm_memory_usage_label",
|
||||
"width": 400,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`getAlertPanelsByNode should properly group for alerts in each category 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"id": 0,
|
||||
"items": Array [
|
||||
Object {
|
||||
"name": <EuiText>
|
||||
es_name_0
|
||||
(
|
||||
3
|
||||
)
|
||||
</EuiText>,
|
||||
"panel": 1,
|
||||
},
|
||||
Object {
|
||||
"name": <EuiText>
|
||||
es_name_1
|
||||
(
|
||||
2
|
||||
)
|
||||
</EuiText>,
|
||||
"panel": 2,
|
||||
},
|
||||
],
|
||||
"title": "Alerts",
|
||||
},
|
||||
Object {
|
||||
"id": 1,
|
||||
"items": Array [
|
||||
Object {
|
||||
"name": <React.Fragment>
|
||||
<EuiToolTip
|
||||
content="triggered:0"
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
triggered:0
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
monitoring_alert_nodes_changed_label
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"panel": 3,
|
||||
},
|
||||
Object {
|
||||
"name": <React.Fragment>
|
||||
<EuiToolTip
|
||||
content="triggered:0"
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
triggered:0
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
monitoring_alert_disk_usage_label
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"panel": 4,
|
||||
},
|
||||
Object {
|
||||
"name": <React.Fragment>
|
||||
<EuiToolTip
|
||||
content="triggered:0"
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
triggered:0
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
monitoring_alert_license_expiration_label
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"panel": 5,
|
||||
},
|
||||
],
|
||||
"title": "es_name_0",
|
||||
},
|
||||
Object {
|
||||
"id": 2,
|
||||
"items": Array [
|
||||
Object {
|
||||
"name": <React.Fragment>
|
||||
<EuiToolTip
|
||||
content="triggered:0"
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
triggered:0
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
monitoring_alert_nodes_changed_label
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"panel": 6,
|
||||
},
|
||||
Object {
|
||||
"name": <React.Fragment>
|
||||
<EuiToolTip
|
||||
content="triggered:0"
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
triggered:0
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
monitoring_alert_license_expiration_label
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"panel": 7,
|
||||
},
|
||||
],
|
||||
"title": "es_name_1",
|
||||
},
|
||||
Object {
|
||||
"content": <AlertPanel
|
||||
alert={
|
||||
Object {
|
||||
"actions": Array [],
|
||||
"alertTypeId": "monitoring_alert_nodes_changed",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "",
|
||||
"createdAt": 2020-12-08T00:00:00.000Z,
|
||||
"createdBy": null,
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": 2020-12-08T00:00:00.000Z,
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "monitoring_alert_nodes_changed_label",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": 2020-12-08T00:00:00.000Z,
|
||||
"updatedBy": null,
|
||||
}
|
||||
}
|
||||
alertState={
|
||||
Object {
|
||||
"firing": true,
|
||||
"meta": Object {},
|
||||
"state": Object {
|
||||
"cluster": Object {
|
||||
"clusterName": "one",
|
||||
"clusterUuid": "1",
|
||||
},
|
||||
"nodeId": "es0",
|
||||
"nodeName": "es_name_0",
|
||||
"ui": Object {
|
||||
"isFiring": false,
|
||||
"lastCheckedMS": 0,
|
||||
"message": Object {
|
||||
"text": "",
|
||||
},
|
||||
"resolvedMS": 0,
|
||||
"severity": "danger",
|
||||
"triggeredMS": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"id": 3,
|
||||
"title": "monitoring_alert_nodes_changed_label",
|
||||
"width": 400,
|
||||
},
|
||||
Object {
|
||||
"content": <AlertPanel
|
||||
alert={
|
||||
Object {
|
||||
"actions": Array [],
|
||||
"alertTypeId": "monitoring_alert_disk_usage",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "",
|
||||
"createdAt": 2020-12-08T00:00:00.000Z,
|
||||
"createdBy": null,
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": 2020-12-08T00:00:00.000Z,
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "monitoring_alert_disk_usage_label",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": 2020-12-08T00:00:00.000Z,
|
||||
"updatedBy": null,
|
||||
}
|
||||
}
|
||||
alertState={
|
||||
Object {
|
||||
"firing": true,
|
||||
"meta": Object {},
|
||||
"state": Object {
|
||||
"cluster": Object {
|
||||
"clusterName": "one",
|
||||
"clusterUuid": "1",
|
||||
},
|
||||
"nodeId": "es0",
|
||||
"nodeName": "es_name_0",
|
||||
"ui": Object {
|
||||
"isFiring": false,
|
||||
"lastCheckedMS": 0,
|
||||
"message": Object {
|
||||
"text": "",
|
||||
},
|
||||
"resolvedMS": 0,
|
||||
"severity": "danger",
|
||||
"triggeredMS": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"id": 4,
|
||||
"title": "monitoring_alert_disk_usage_label",
|
||||
"width": 400,
|
||||
},
|
||||
Object {
|
||||
"content": <AlertPanel
|
||||
alert={
|
||||
Object {
|
||||
"actions": Array [],
|
||||
"alertTypeId": "monitoring_alert_license_expiration",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "",
|
||||
"createdAt": 2020-12-08T00:00:00.000Z,
|
||||
"createdBy": null,
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": 2020-12-08T00:00:00.000Z,
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "monitoring_alert_license_expiration_label",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": 2020-12-08T00:00:00.000Z,
|
||||
"updatedBy": null,
|
||||
}
|
||||
}
|
||||
alertState={
|
||||
Object {
|
||||
"firing": true,
|
||||
"meta": Object {},
|
||||
"state": Object {
|
||||
"cluster": Object {
|
||||
"clusterName": "one",
|
||||
"clusterUuid": "1",
|
||||
},
|
||||
"nodeId": "es0",
|
||||
"nodeName": "es_name_0",
|
||||
"ui": Object {
|
||||
"isFiring": false,
|
||||
"lastCheckedMS": 0,
|
||||
"message": Object {
|
||||
"text": "",
|
||||
},
|
||||
"resolvedMS": 0,
|
||||
"severity": "danger",
|
||||
"triggeredMS": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"id": 5,
|
||||
"title": "monitoring_alert_license_expiration_label",
|
||||
"width": 400,
|
||||
},
|
||||
Object {
|
||||
"content": <AlertPanel
|
||||
alert={
|
||||
Object {
|
||||
"actions": Array [],
|
||||
"alertTypeId": "monitoring_alert_nodes_changed",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "",
|
||||
"createdAt": 2020-12-08T00:00:00.000Z,
|
||||
"createdBy": null,
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": 2020-12-08T00:00:00.000Z,
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "monitoring_alert_nodes_changed_label",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": 2020-12-08T00:00:00.000Z,
|
||||
"updatedBy": null,
|
||||
}
|
||||
}
|
||||
alertState={
|
||||
Object {
|
||||
"firing": true,
|
||||
"meta": Object {},
|
||||
"state": Object {
|
||||
"cluster": Object {
|
||||
"clusterName": "one",
|
||||
"clusterUuid": "1",
|
||||
},
|
||||
"nodeId": "es1",
|
||||
"nodeName": "es_name_1",
|
||||
"ui": Object {
|
||||
"isFiring": false,
|
||||
"lastCheckedMS": 0,
|
||||
"message": Object {
|
||||
"text": "",
|
||||
},
|
||||
"resolvedMS": 0,
|
||||
"severity": "danger",
|
||||
"triggeredMS": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"id": 6,
|
||||
"title": "monitoring_alert_nodes_changed_label",
|
||||
"width": 400,
|
||||
},
|
||||
Object {
|
||||
"content": <AlertPanel
|
||||
alert={
|
||||
Object {
|
||||
"actions": Array [],
|
||||
"alertTypeId": "monitoring_alert_license_expiration",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "",
|
||||
"createdAt": 2020-12-08T00:00:00.000Z,
|
||||
"createdBy": null,
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": 2020-12-08T00:00:00.000Z,
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "monitoring_alert_license_expiration_label",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": 2020-12-08T00:00:00.000Z,
|
||||
"updatedBy": null,
|
||||
}
|
||||
}
|
||||
alertState={
|
||||
Object {
|
||||
"firing": true,
|
||||
"meta": Object {},
|
||||
"state": Object {
|
||||
"cluster": Object {
|
||||
"clusterName": "one",
|
||||
"clusterUuid": "1",
|
||||
},
|
||||
"nodeId": "es1",
|
||||
"nodeName": "es_name_1",
|
||||
"ui": Object {
|
||||
"isFiring": false,
|
||||
"lastCheckedMS": 0,
|
||||
"message": Object {
|
||||
"text": "",
|
||||
},
|
||||
"resolvedMS": 0,
|
||||
"severity": "danger",
|
||||
"triggeredMS": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"id": 7,
|
||||
"title": "monitoring_alert_license_expiration_label",
|
||||
"width": 400,
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ALERTS,
|
||||
ALERT_CPU_USAGE,
|
||||
ALERT_LOGSTASH_VERSION_MISMATCH,
|
||||
ALERT_THREAD_POOL_WRITE_REJECTIONS,
|
||||
} from '../../../common/constants';
|
||||
import { AlertSeverity } from '../../../common/enums';
|
||||
import { getAlertPanelsByCategory } from './get_alert_panels_by_category';
|
||||
import {
|
||||
ALERT_LICENSE_EXPIRATION,
|
||||
ALERT_NODES_CHANGED,
|
||||
ALERT_DISK_USAGE,
|
||||
ALERT_MEMORY_USAGE,
|
||||
} from '../../../common/constants';
|
||||
import { AlertsByName } from '../types';
|
||||
import { AlertExecutionStatusValues } from '../../../../alerts/common';
|
||||
import { AlertState } from '../../../common/types/alerts';
|
||||
|
||||
jest.mock('../../legacy_shims', () => ({
|
||||
Legacy: {
|
||||
shims: {
|
||||
uiSettings: {
|
||||
get: () => '',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../common/formatting', () => ({
|
||||
getDateFromNow: (timestamp: number) => `triggered:${timestamp}`,
|
||||
getCalendar: (timestamp: number) => `triggered:${timestamp}`,
|
||||
}));
|
||||
|
||||
const mockAlert = {
|
||||
id: '',
|
||||
enabled: true,
|
||||
tags: [],
|
||||
consumer: '',
|
||||
schedule: { interval: '1m' },
|
||||
actions: [],
|
||||
params: {},
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
createdAt: new Date('2020-12-08'),
|
||||
updatedAt: new Date('2020-12-08'),
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: null,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: AlertExecutionStatusValues[0],
|
||||
lastExecutionDate: new Date('2020-12-08'),
|
||||
},
|
||||
notifyWhen: null,
|
||||
};
|
||||
|
||||
function getAllAlerts() {
|
||||
return ALERTS.reduce((accum: AlertsByName, alertType) => {
|
||||
accum[alertType] = {
|
||||
states: [],
|
||||
rawAlert: {
|
||||
alertTypeId: alertType,
|
||||
name: `${alertType}_label`,
|
||||
...mockAlert,
|
||||
},
|
||||
};
|
||||
return accum;
|
||||
}, {});
|
||||
}
|
||||
|
||||
describe('getAlertPanelsByCategory', () => {
|
||||
const ui = {
|
||||
isFiring: false,
|
||||
severity: AlertSeverity.Danger,
|
||||
message: { text: '' },
|
||||
resolvedMS: 0,
|
||||
lastCheckedMS: 0,
|
||||
triggeredMS: 0,
|
||||
};
|
||||
|
||||
const cluster = { clusterUuid: '1', clusterName: 'one' };
|
||||
|
||||
function getAlert(type: string, firingCount: number) {
|
||||
const states = [];
|
||||
|
||||
for (let fi = 0; fi < firingCount; fi++) {
|
||||
states.push({
|
||||
firing: true,
|
||||
meta: {},
|
||||
state: {
|
||||
cluster,
|
||||
ui: {
|
||||
...ui,
|
||||
triggeredMS: fi,
|
||||
},
|
||||
nodeId: `es${fi}`,
|
||||
nodeName: `es_name_${fi}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
states,
|
||||
rawAlert: {
|
||||
alertTypeId: type,
|
||||
name: `${type}_label`,
|
||||
...mockAlert,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const alertsContext = {
|
||||
allAlerts: getAllAlerts(),
|
||||
};
|
||||
|
||||
const stateFilter = (state: AlertState) => true;
|
||||
const panelTitle = 'Alerts';
|
||||
|
||||
describe('non setup mode', () => {
|
||||
it('should properly group for alerts in each category', () => {
|
||||
const alerts = [
|
||||
getAlert(ALERT_NODES_CHANGED, 2),
|
||||
getAlert(ALERT_DISK_USAGE, 1),
|
||||
getAlert(ALERT_LICENSE_EXPIRATION, 2),
|
||||
];
|
||||
const result = getAlertPanelsByCategory(
|
||||
panelTitle,
|
||||
false,
|
||||
alerts,
|
||||
alertsContext,
|
||||
stateFilter
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly group for alerts in a single category', () => {
|
||||
const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)];
|
||||
const result = getAlertPanelsByCategory(
|
||||
panelTitle,
|
||||
false,
|
||||
alerts,
|
||||
alertsContext,
|
||||
stateFilter
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not show any alert if none are firing', () => {
|
||||
const alerts = [
|
||||
getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0),
|
||||
getAlert(ALERT_CPU_USAGE, 0),
|
||||
getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0),
|
||||
];
|
||||
const result = getAlertPanelsByCategory(
|
||||
panelTitle,
|
||||
false,
|
||||
alerts,
|
||||
alertsContext,
|
||||
stateFilter
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow for state filtering', () => {
|
||||
const alerts = [getAlert(ALERT_CPU_USAGE, 2)];
|
||||
const customStateFilter = (state: AlertState) => state.nodeName === 'es_name_0';
|
||||
const result = getAlertPanelsByCategory(
|
||||
panelTitle,
|
||||
false,
|
||||
alerts,
|
||||
alertsContext,
|
||||
customStateFilter
|
||||
);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup mode', () => {
|
||||
it('should properly group for alerts in each category', () => {
|
||||
const alerts = [
|
||||
getAlert(ALERT_NODES_CHANGED, 2),
|
||||
getAlert(ALERT_DISK_USAGE, 1),
|
||||
getAlert(ALERT_LICENSE_EXPIRATION, 2),
|
||||
];
|
||||
const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly group for alerts in a single category', () => {
|
||||
const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)];
|
||||
const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should still show alerts if none are firing', () => {
|
||||
const alerts = [
|
||||
getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0),
|
||||
getAlert(ALERT_CPU_USAGE, 0),
|
||||
getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0),
|
||||
];
|
||||
const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { Fragment } from 'react';
|
||||
import { EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import { AlertPanel } from '../panel';
|
||||
import { ALERT_PANEL_MENU } from '../../../common/constants';
|
||||
import { getDateFromNow, getCalendar } from '../../../common/formatting';
|
||||
import { IAlertsContext } from '../context';
|
||||
import { AlertState, CommonAlertStatus } from '../../../common/types/alerts';
|
||||
import { PanelItem } from '../types';
|
||||
import { sortByNewestAlert } from './sort_by_newest_alert';
|
||||
import { Legacy } from '../../legacy_shims';
|
||||
|
||||
export function getAlertPanelsByCategory(
|
||||
panelTitle: string,
|
||||
inSetupMode: boolean,
|
||||
alerts: CommonAlertStatus[],
|
||||
alertsContext: IAlertsContext,
|
||||
stateFilter: (state: AlertState) => boolean
|
||||
) {
|
||||
const menu = [];
|
||||
for (const category of ALERT_PANEL_MENU) {
|
||||
let categoryFiringAlertCount = 0;
|
||||
if (inSetupMode) {
|
||||
const alertsInCategory = [];
|
||||
for (const categoryAlert of category.alerts) {
|
||||
if (
|
||||
Boolean(alerts.find(({ rawAlert }) => rawAlert.alertTypeId === categoryAlert.alertName))
|
||||
) {
|
||||
alertsInCategory.push(categoryAlert);
|
||||
}
|
||||
}
|
||||
if (alertsInCategory.length > 0) {
|
||||
menu.push({
|
||||
...category,
|
||||
alerts: alertsInCategory.map(({ alertName }) => {
|
||||
const alertStatus = alertsContext.allAlerts[alertName];
|
||||
return {
|
||||
alert: alertStatus.rawAlert,
|
||||
states: [],
|
||||
alertName,
|
||||
};
|
||||
}),
|
||||
alertCount: 0,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const firingAlertsInCategory = [];
|
||||
for (const { alertName } of category.alerts) {
|
||||
const foundAlert = alerts.find(
|
||||
({ rawAlert: { alertTypeId } }) => alertName === alertTypeId
|
||||
);
|
||||
if (foundAlert && foundAlert.states.length > 0) {
|
||||
const states = foundAlert.states.filter(({ state }) => stateFilter(state));
|
||||
if (states.length > 0) {
|
||||
firingAlertsInCategory.push({
|
||||
alert: foundAlert.rawAlert,
|
||||
states: foundAlert.states,
|
||||
alertName,
|
||||
});
|
||||
categoryFiringAlertCount += states.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firingAlertsInCategory.length > 0) {
|
||||
menu.push({
|
||||
...category,
|
||||
alertCount: categoryFiringAlertCount,
|
||||
alerts: firingAlertsInCategory,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of menu) {
|
||||
for (const alert of item.alerts) {
|
||||
alert.states.sort(sortByNewestAlert);
|
||||
}
|
||||
}
|
||||
|
||||
const panels: PanelItem[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: panelTitle,
|
||||
items: [
|
||||
...menu.map((category, index) => {
|
||||
const name = inSetupMode ? (
|
||||
<EuiText>{category.label}</EuiText>
|
||||
) : (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
{category.label} ({category.alertCount})
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
return {
|
||||
name,
|
||||
panel: index + 1,
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (inSetupMode) {
|
||||
let secondaryPanelIndex = menu.length;
|
||||
let tertiaryPanelIndex = menu.length;
|
||||
let nodeIndex = 0;
|
||||
for (const category of menu) {
|
||||
panels.push({
|
||||
id: nodeIndex + 1,
|
||||
title: `${category.label}`,
|
||||
items: category.alerts.map(({ alertName }) => {
|
||||
const alertStatus = alertsContext.allAlerts[alertName];
|
||||
return {
|
||||
name: <EuiText>{alertStatus.rawAlert.name}</EuiText>,
|
||||
panel: ++secondaryPanelIndex,
|
||||
};
|
||||
}),
|
||||
});
|
||||
nodeIndex++;
|
||||
}
|
||||
|
||||
for (const category of menu) {
|
||||
for (const { alert, alertName } of category.alerts) {
|
||||
const alertStatus = alertsContext.allAlerts[alertName];
|
||||
panels.push({
|
||||
id: ++tertiaryPanelIndex,
|
||||
title: `${alert.name}`,
|
||||
width: 400,
|
||||
content: <AlertPanel alert={alertStatus.rawAlert} />,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let primaryPanelIndex = menu.length;
|
||||
let nodeIndex = 0;
|
||||
for (const category of menu) {
|
||||
panels.push({
|
||||
id: nodeIndex + 1,
|
||||
title: `${category.label}`,
|
||||
items: category.alerts.map(({ alertName, states }) => {
|
||||
const filteredStates = states.filter(({ state }) => stateFilter(state));
|
||||
const alertStatus = alertsContext.allAlerts[alertName];
|
||||
const name = inSetupMode ? (
|
||||
<EuiText>{alertStatus.rawAlert.name}</EuiText>
|
||||
) : (
|
||||
<EuiText>
|
||||
{alertStatus.rawAlert.name} ({filteredStates.length})
|
||||
</EuiText>
|
||||
);
|
||||
return {
|
||||
name,
|
||||
panel: ++primaryPanelIndex,
|
||||
};
|
||||
}),
|
||||
});
|
||||
nodeIndex++;
|
||||
}
|
||||
|
||||
let secondaryPanelIndex = menu.length;
|
||||
let tertiaryPanelIndex = menu.reduce((count, category) => {
|
||||
count += category.alerts.length;
|
||||
return count;
|
||||
}, menu.length);
|
||||
for (const category of menu) {
|
||||
for (const { alert, states } of category.alerts) {
|
||||
const items = [];
|
||||
for (const alertState of states.filter(({ state }) => stateFilter(state))) {
|
||||
items.push({
|
||||
name: (
|
||||
<Fragment>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={getCalendar(
|
||||
alertState.state.ui.triggeredMS,
|
||||
Legacy.shims.uiSettings.get('dateFormat:tz')
|
||||
)}
|
||||
>
|
||||
<EuiText size="s">
|
||||
{getDateFromNow(
|
||||
alertState.state.ui.triggeredMS,
|
||||
Legacy.shims.uiSettings.get('dateFormat:tz')
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText size="s">{alertState.state.nodeName}</EuiText>
|
||||
</Fragment>
|
||||
),
|
||||
panel: ++tertiaryPanelIndex,
|
||||
});
|
||||
items.push({
|
||||
isSeparator: true as const,
|
||||
});
|
||||
}
|
||||
|
||||
panels.push({
|
||||
id: ++secondaryPanelIndex,
|
||||
title: `${alert.name}`,
|
||||
items,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let tertiaryPanelIndex2 = menu.reduce((count, category) => {
|
||||
count += category.alerts.length;
|
||||
return count;
|
||||
}, menu.length);
|
||||
for (const category of menu) {
|
||||
for (const { alert, states } of category.alerts) {
|
||||
for (const state of states.filter(({ state: _state }) => stateFilter(_state))) {
|
||||
panels.push({
|
||||
id: ++tertiaryPanelIndex2,
|
||||
title: `${alert.name}`,
|
||||
width: 400,
|
||||
content: <AlertPanel alert={alert} alertState={state} />,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return panels;
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ALERT_CPU_USAGE,
|
||||
ALERT_LOGSTASH_VERSION_MISMATCH,
|
||||
ALERT_THREAD_POOL_WRITE_REJECTIONS,
|
||||
} from '../../../common/constants';
|
||||
import { AlertSeverity } from '../../../common/enums';
|
||||
import { getAlertPanelsByNode } from './get_alert_panels_by_node';
|
||||
import {
|
||||
ALERT_LICENSE_EXPIRATION,
|
||||
ALERT_NODES_CHANGED,
|
||||
ALERT_DISK_USAGE,
|
||||
ALERT_MEMORY_USAGE,
|
||||
} from '../../../common/constants';
|
||||
import { AlertExecutionStatusValues } from '../../../../alerts/common';
|
||||
import { AlertState } from '../../../common/types/alerts';
|
||||
|
||||
jest.mock('../../legacy_shims', () => ({
|
||||
Legacy: {
|
||||
shims: {
|
||||
uiSettings: {
|
||||
get: () => '',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../common/formatting', () => ({
|
||||
getDateFromNow: (timestamp: number) => `triggered:${timestamp}`,
|
||||
getCalendar: (timestamp: number) => `triggered:${timestamp}`,
|
||||
}));
|
||||
|
||||
const mockAlert = {
|
||||
id: '',
|
||||
enabled: true,
|
||||
tags: [],
|
||||
consumer: '',
|
||||
schedule: { interval: '1m' },
|
||||
actions: [],
|
||||
params: {},
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
createdAt: new Date('2020-12-08'),
|
||||
updatedAt: new Date('2020-12-08'),
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: null,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: AlertExecutionStatusValues[0],
|
||||
lastExecutionDate: new Date('2020-12-08'),
|
||||
},
|
||||
notifyWhen: null,
|
||||
};
|
||||
|
||||
describe('getAlertPanelsByNode', () => {
|
||||
const ui = {
|
||||
isFiring: false,
|
||||
severity: AlertSeverity.Danger,
|
||||
message: { text: '' },
|
||||
resolvedMS: 0,
|
||||
lastCheckedMS: 0,
|
||||
triggeredMS: 0,
|
||||
};
|
||||
|
||||
const cluster = { clusterUuid: '1', clusterName: 'one' };
|
||||
|
||||
function getAlert(type: string, firingCount: number) {
|
||||
const states = [];
|
||||
|
||||
for (let fi = 0; fi < firingCount; fi++) {
|
||||
states.push({
|
||||
firing: true,
|
||||
meta: {},
|
||||
state: {
|
||||
cluster,
|
||||
ui,
|
||||
nodeId: `es${fi}`,
|
||||
nodeName: `es_name_${fi}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
rawAlert: {
|
||||
alertTypeId: type,
|
||||
name: `${type}_label`,
|
||||
...mockAlert,
|
||||
},
|
||||
states,
|
||||
};
|
||||
}
|
||||
|
||||
const panelTitle = 'Alerts';
|
||||
const stateFilter = (state: AlertState) => true;
|
||||
|
||||
it('should properly group for alerts in each category', () => {
|
||||
const alerts = [
|
||||
getAlert(ALERT_NODES_CHANGED, 2),
|
||||
getAlert(ALERT_DISK_USAGE, 1),
|
||||
getAlert(ALERT_LICENSE_EXPIRATION, 2),
|
||||
];
|
||||
const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly group for alerts in a single category', () => {
|
||||
const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)];
|
||||
const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not show any alert if none are firing', () => {
|
||||
const alerts = [
|
||||
getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0),
|
||||
getAlert(ALERT_CPU_USAGE, 0),
|
||||
getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0),
|
||||
];
|
||||
const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { Fragment } from 'react';
|
||||
import { EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import { AlertPanel } from '../panel';
|
||||
import {
|
||||
CommonAlertStatus,
|
||||
CommonAlertState,
|
||||
CommonAlert,
|
||||
AlertState,
|
||||
} from '../../../common/types/alerts';
|
||||
import { getDateFromNow, getCalendar } from '../../../common/formatting';
|
||||
import { PanelItem } from '../types';
|
||||
import { sortByNewestAlert } from './sort_by_newest_alert';
|
||||
import { Legacy } from '../../legacy_shims';
|
||||
|
||||
export function getAlertPanelsByNode(
|
||||
panelTitle: string,
|
||||
alerts: CommonAlertStatus[],
|
||||
stateFilter: (state: AlertState) => boolean
|
||||
) {
|
||||
const alertsByNodes: {
|
||||
[uuid: string]: {
|
||||
[alertName: string]: {
|
||||
alert: CommonAlert;
|
||||
states: CommonAlertState[];
|
||||
count: number;
|
||||
};
|
||||
};
|
||||
} = {};
|
||||
const statesByNodes: {
|
||||
[uuid: string]: CommonAlertState[];
|
||||
} = {};
|
||||
|
||||
for (const { states, rawAlert } of alerts) {
|
||||
const { alertTypeId } = rawAlert;
|
||||
for (const alertState of states.filter(({ state: _state }) => stateFilter(_state))) {
|
||||
const { state } = alertState;
|
||||
statesByNodes[state.nodeId] = statesByNodes[state.nodeId] || [];
|
||||
statesByNodes[state.nodeId].push(alertState);
|
||||
|
||||
alertsByNodes[state.nodeId] = alertsByNodes[state.nodeId] || {};
|
||||
alertsByNodes[state.nodeId][alertTypeId] = alertsByNodes[alertState.state.nodeId][
|
||||
alertTypeId
|
||||
] || { alert: rawAlert, states: [], count: 0 };
|
||||
alertsByNodes[state.nodeId][alertTypeId].count++;
|
||||
alertsByNodes[state.nodeId][alertTypeId].states.push(alertState);
|
||||
}
|
||||
}
|
||||
|
||||
for (const types of Object.values(alertsByNodes)) {
|
||||
for (const { states } of Object.values(types)) {
|
||||
states.sort(sortByNewestAlert);
|
||||
}
|
||||
}
|
||||
|
||||
const nodeCount = Object.keys(statesByNodes).length;
|
||||
let secondaryPanelIndex = nodeCount;
|
||||
let tertiaryPanelIndex = nodeCount;
|
||||
const panels: PanelItem[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: panelTitle,
|
||||
items: [
|
||||
...Object.keys(statesByNodes).map((nodeUuid, index) => {
|
||||
const states = (statesByNodes[nodeUuid] as CommonAlertState[]).filter(({ state }) =>
|
||||
stateFilter(state)
|
||||
);
|
||||
return {
|
||||
name: (
|
||||
<EuiText>
|
||||
{states[0].state.nodeName} ({states.length})
|
||||
</EuiText>
|
||||
),
|
||||
panel: index + 1,
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
...Object.keys(statesByNodes).reduce((accum: PanelItem[], nodeUuid, nodeIndex) => {
|
||||
const alertsForNode = Object.values(alertsByNodes[nodeUuid]);
|
||||
const panelItems = [];
|
||||
let title = '';
|
||||
for (const { alert, states } of alertsForNode) {
|
||||
for (const alertState of states) {
|
||||
title = alertState.state.nodeName;
|
||||
panelItems.push({
|
||||
name: (
|
||||
<Fragment>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={getCalendar(
|
||||
alertState.state.ui.triggeredMS,
|
||||
Legacy.shims.uiSettings.get('dateFormat:tz')
|
||||
)}
|
||||
>
|
||||
<EuiText size="s">
|
||||
{getDateFromNow(
|
||||
alertState.state.ui.triggeredMS,
|
||||
Legacy.shims.uiSettings.get('dateFormat:tz')
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
<EuiText size="s">{alert.name}</EuiText>
|
||||
</Fragment>
|
||||
),
|
||||
panel: ++secondaryPanelIndex,
|
||||
});
|
||||
}
|
||||
}
|
||||
accum.push({
|
||||
id: nodeIndex + 1,
|
||||
title,
|
||||
items: panelItems,
|
||||
});
|
||||
return accum;
|
||||
}, []),
|
||||
...Object.keys(statesByNodes).reduce((accum: PanelItem[], nodeUuid, nodeIndex) => {
|
||||
const alertsForNode = Object.values(alertsByNodes[nodeUuid]);
|
||||
for (const { alert, states } of alertsForNode) {
|
||||
for (const alertState of states) {
|
||||
accum.push({
|
||||
id: ++tertiaryPanelIndex,
|
||||
title: alert.name,
|
||||
width: 400,
|
||||
content: <AlertPanel alert={alert} alertState={alertState} />,
|
||||
});
|
||||
}
|
||||
}
|
||||
return accum;
|
||||
}, []),
|
||||
];
|
||||
|
||||
return panels;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertSeverity } from '../../../common/enums';
|
||||
import { sortByNewestAlert } from './sort_by_newest_alert';
|
||||
|
||||
describe('sortByNewestAlert', () => {
|
||||
const ui = {
|
||||
isFiring: false,
|
||||
severity: AlertSeverity.Danger,
|
||||
message: { text: '' },
|
||||
resolvedMS: 0,
|
||||
lastCheckedMS: 0,
|
||||
triggeredMS: 0,
|
||||
};
|
||||
|
||||
const cluster = { clusterUuid: '1', clusterName: 'one' };
|
||||
|
||||
it('should sort properly', () => {
|
||||
const a = {
|
||||
firing: false,
|
||||
meta: {},
|
||||
state: {
|
||||
cluster,
|
||||
ui: {
|
||||
...ui,
|
||||
triggeredMS: 2,
|
||||
},
|
||||
nodeId: `es1`,
|
||||
nodeName: `es_name_1`,
|
||||
},
|
||||
};
|
||||
const b = {
|
||||
firing: false,
|
||||
meta: {},
|
||||
state: {
|
||||
cluster,
|
||||
ui: {
|
||||
...ui,
|
||||
triggeredMS: 1,
|
||||
},
|
||||
nodeId: `es1`,
|
||||
nodeName: `es_name_1`,
|
||||
},
|
||||
};
|
||||
expect(sortByNewestAlert(a, b)).toBe(-1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CommonAlertState } from '../../../common/types/alerts';
|
||||
|
||||
export function sortByNewestAlert(a: CommonAlertState, b: CommonAlertState) {
|
||||
if (a.state.ui.triggeredMS === b.state.ui.triggeredMS) {
|
||||
return 0;
|
||||
}
|
||||
return a.state.ui.triggeredMS < b.state.ui.triggeredMS ? 1 : -1;
|
||||
}
|
|
@ -3,186 +3,39 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { Fragment, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { Fragment } from 'react';
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSwitch,
|
||||
EuiTitle,
|
||||
EuiHorizontalRule,
|
||||
EuiListGroup,
|
||||
EuiListGroupItem,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { CommonAlertStatus, CommonAlertState, AlertMessage } from '../../common/types/alerts';
|
||||
import { Legacy } from '../legacy_shims';
|
||||
import { CommonAlert, CommonAlertState, AlertMessage } from '../../common/types/alerts';
|
||||
import { replaceTokens } from './lib/replace_tokens';
|
||||
import { isInSetupMode, hideBottomBar, showBottomBar } from '../lib/setup_mode';
|
||||
import { BASE_ALERT_API_PATH } from '../../../alerts/common';
|
||||
import { isInSetupMode } from '../lib/setup_mode';
|
||||
import { SetupModeContext } from '../components/setup_mode/setup_mode_context';
|
||||
import { AlertConfiguration } from './configuration';
|
||||
|
||||
interface Props {
|
||||
alert: CommonAlertStatus;
|
||||
alert: CommonAlert;
|
||||
alertState?: CommonAlertState;
|
||||
}
|
||||
export const AlertPanel: React.FC<Props> = (props: Props) => {
|
||||
const {
|
||||
alert: { rawAlert },
|
||||
alertState,
|
||||
} = props;
|
||||
|
||||
const [showFlyout, setShowFlyout] = React.useState(false);
|
||||
const [isEnabled, setIsEnabled] = React.useState(rawAlert?.enabled);
|
||||
const [isMuted, setIsMuted] = React.useState(rawAlert?.muteAll);
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const { alert, alertState } = props;
|
||||
const inSetupMode = isInSetupMode(React.useContext(SetupModeContext));
|
||||
|
||||
const flyoutUi = useMemo(
|
||||
() =>
|
||||
showFlyout &&
|
||||
Legacy.shims.triggersActionsUi.getEditAlertFlyout({
|
||||
initialAlert: rawAlert,
|
||||
onClose: () => {
|
||||
setShowFlyout(false);
|
||||
showBottomBar();
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[showFlyout]
|
||||
);
|
||||
|
||||
if (!rawAlert) {
|
||||
if (!alert) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function disableAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_disable`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', {
|
||||
defaultMessage: `Unable to disable alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
async function enableAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_enable`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', {
|
||||
defaultMessage: `Unable to enable alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
async function muteAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_mute_all`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', {
|
||||
defaultMessage: `Unable to mute alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
async function unmuteAlert() {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_unmute_all`);
|
||||
} catch (err) {
|
||||
Legacy.shims.toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', {
|
||||
defaultMessage: `Unable to unmute alert`,
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
||||
const configurationUi = (
|
||||
<Fragment>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => {
|
||||
setShowFlyout(true);
|
||||
hideBottomBar();
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.monitoring.alerts.panel.editAlert', {
|
||||
defaultMessage: `Edit alert`,
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
name="disable"
|
||||
disabled={isSaving}
|
||||
checked={!isEnabled}
|
||||
onChange={async () => {
|
||||
if (isEnabled) {
|
||||
setIsEnabled(false);
|
||||
await disableAlert();
|
||||
} else {
|
||||
setIsEnabled(true);
|
||||
await enableAlert();
|
||||
}
|
||||
}}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.alerts.panel.disableTitle"
|
||||
defaultMessage="Disable"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
name="mute"
|
||||
disabled={isSaving}
|
||||
checked={isMuted}
|
||||
data-test-subj="muteSwitch"
|
||||
onChange={async () => {
|
||||
if (isMuted) {
|
||||
setIsMuted(false);
|
||||
await unmuteAlert();
|
||||
} else {
|
||||
setIsMuted(true);
|
||||
await muteAlert();
|
||||
}
|
||||
}}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.alerts.panel.muteTitle"
|
||||
defaultMessage="Mute"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{flyoutUi}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
if (inSetupMode || !alertState) {
|
||||
return <div style={{ padding: '1rem' }}>{configurationUi}</div>;
|
||||
return (
|
||||
<div style={{ padding: '1rem' }}>
|
||||
<AlertConfiguration alert={alert} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const nextStepsUi =
|
||||
|
@ -204,7 +57,9 @@ export const AlertPanel: React.FC<Props> = (props: Props) => {
|
|||
{nextStepsUi}
|
||||
</div>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<div style={{ padding: '0 1rem 1rem 1rem' }}>{configurationUi}</div>
|
||||
<div style={{ padding: '0 1rem 1rem 1rem' }}>
|
||||
<AlertConfiguration alert={alert} />
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
|
29
x-pack/plugins/monitoring/public/alerts/types.ts
Normal file
29
x-pack/plugins/monitoring/public/alerts/types.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { CommonAlertStatus } from '../../common/types/alerts';
|
||||
|
||||
export interface AlertsByName {
|
||||
[name: string]: CommonAlertStatus;
|
||||
}
|
||||
|
||||
export interface PanelItem {
|
||||
id: number;
|
||||
title: string;
|
||||
width?: number;
|
||||
content?: React.ReactElement;
|
||||
items?: Array<ContextMenuItem | ContextMenuItemSeparator>;
|
||||
}
|
||||
|
||||
export interface ContextMenuItem {
|
||||
name: React.ReactElement;
|
||||
panel?: number;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface ContextMenuItemSeparator {
|
||||
isSeparator: true;
|
||||
}
|
|
@ -20,7 +20,7 @@ import { MonitoringTimeseriesContainer } from '../../chart';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AlertsCallout } from '../../../alerts/callout';
|
||||
|
||||
export const AdvancedNode = ({ nodeSummary, metrics, alerts, nodeId, ...props }) => {
|
||||
export const AdvancedNode = ({ nodeSummary, metrics, alerts, ...props }) => {
|
||||
const metricsToShow = [
|
||||
metrics.node_gc,
|
||||
metrics.node_gc_time,
|
||||
|
@ -54,7 +54,7 @@ export const AdvancedNode = ({ nodeSummary, metrics, alerts, nodeId, ...props })
|
|||
<NodeDetailStatus stats={nodeSummary} alerts={alerts} />
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
<AlertsCallout alerts={alerts} stateFilter={(state) => state.nodeId === nodeId} />
|
||||
<AlertsCallout alerts={alerts} />
|
||||
<EuiPageContent>
|
||||
<EuiFlexGrid columns={2} gutterSize="s">
|
||||
{metricsToShow.map((metric, index) => (
|
||||
|
|
|
@ -130,7 +130,6 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler
|
|||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
field: 'alerts',
|
||||
// width: '175px',
|
||||
sortable: true,
|
||||
render: (_field, node) => {
|
||||
return (
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Legacy } from '../legacy_shims';
|
|||
import { PromiseWithCancel } from '../../common/cancel_promise';
|
||||
import { SetupModeFeature } from '../../common/enums';
|
||||
import { updateSetupModeData, isSetupModeFeatureEnabled } from '../lib/setup_mode';
|
||||
import { AlertsContext } from '../alerts/context';
|
||||
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
/**
|
||||
|
@ -243,11 +244,13 @@ export class MonitoringViewBaseController {
|
|||
const wrappedComponent = (
|
||||
<KibanaContextProvider services={Legacy.shims.kibanaServices}>
|
||||
<I18nContext>
|
||||
{!this._isDataInitialized ? (
|
||||
<PageLoading pageViewTitle={trackPageView ? this.telemetryPageViewTitle : null} />
|
||||
) : (
|
||||
component
|
||||
)}
|
||||
<AlertsContext.Provider value={{ allAlerts: this.alerts }}>
|
||||
{!this._isDataInitialized ? (
|
||||
<PageLoading pageViewTitle={trackPageView ? this.telemetryPageViewTitle : null} />
|
||||
) : (
|
||||
component
|
||||
)}
|
||||
</AlertsContext.Provider>
|
||||
</I18nContext>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import { uiRoutes } from '../../../angular/helpers/routes';
|
|||
import { routeInitProvider } from '../../../lib/route_init';
|
||||
import { getPageData } from './get_page_data';
|
||||
import template from './index.html';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
import { Node } from '../../../components/elasticsearch/node/node';
|
||||
import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
|
||||
import { nodesByIndices } from '../../../components/elasticsearch/shard_allocation/transformers/nodes_by_indices';
|
||||
|
@ -26,7 +27,9 @@ import {
|
|||
ALERT_MISSING_MONITORING_DATA,
|
||||
ALERT_DISK_USAGE,
|
||||
ALERT_MEMORY_USAGE,
|
||||
ELASTICSEARCH_SYSTEM_ID,
|
||||
} from '../../../../common/constants';
|
||||
import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
|
||||
|
||||
uiRoutes.when('/elasticsearch/nodes/:node', {
|
||||
template,
|
||||
|
@ -122,14 +125,26 @@ uiRoutes.when('/elasticsearch/nodes/:node', {
|
|||
$scope.labels = labels.node;
|
||||
|
||||
this.renderReact(
|
||||
<Node
|
||||
<SetupModeRenderer
|
||||
scope={$scope}
|
||||
alerts={this.alerts}
|
||||
nodeId={this.nodeName}
|
||||
clusterUuid={$scope.cluster.cluster_uuid}
|
||||
onBrush={this.onBrush}
|
||||
zoomInfo={this.zoomInfo}
|
||||
{...data}
|
||||
injector={$injector}
|
||||
productName={ELASTICSEARCH_SYSTEM_ID}
|
||||
render={({ setupMode, flyoutComponent, bottomBarComponent }) => (
|
||||
<SetupModeContext.Provider value={{ setupModeSupported: true }}>
|
||||
{flyoutComponent}
|
||||
<Node
|
||||
scope={$scope}
|
||||
setupMode={setupMode}
|
||||
alerts={this.alerts}
|
||||
nodeId={this.nodeName}
|
||||
clusterUuid={$scope.cluster.cluster_uuid}
|
||||
onBrush={this.onBrush}
|
||||
zoomInfo={this.zoomInfo}
|
||||
{...data}
|
||||
/>
|
||||
{bottomBarComponent}
|
||||
</SetupModeContext.Provider>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ import { mapLegacySeverity } from '../lib/alerts/map_legacy_severity';
|
|||
|
||||
interface LegacyOptions {
|
||||
watchName: string;
|
||||
nodeNameLabel: string;
|
||||
changeDataValues?: Partial<AlertData>;
|
||||
}
|
||||
|
||||
|
@ -322,6 +323,7 @@ export class BaseAlert {
|
|||
shouldFire: !legacyAlert.resolved_timestamp,
|
||||
severity: mapLegacySeverity(legacyAlert.metadata.severity),
|
||||
meta: legacyAlert,
|
||||
nodeName: this.alertOptions.legacy!.nodeNameLabel,
|
||||
...this.alertOptions.legacy!.changeDataValues,
|
||||
};
|
||||
});
|
||||
|
@ -394,6 +396,7 @@ export class BaseAlert {
|
|||
}
|
||||
const cluster = clusters.find((c: AlertCluster) => c.clusterUuid === item.clusterUuid);
|
||||
const alertState: AlertState = this.getDefaultAlertState(cluster!, item);
|
||||
alertState.nodeName = item.nodeName;
|
||||
alertState.ui.triggeredMS = currentUTC;
|
||||
alertState.ui.isFiring = true;
|
||||
alertState.ui.severity = item.severity;
|
||||
|
|
|
@ -119,6 +119,7 @@ describe('ClusterHealthAlert', () => {
|
|||
{
|
||||
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
|
||||
ccs: undefined,
|
||||
nodeName: 'Elasticsearch cluster alert',
|
||||
ui: {
|
||||
isFiring: true,
|
||||
message: {
|
||||
|
|
|
@ -38,6 +38,9 @@ export class ClusterHealthAlert extends BaseAlert {
|
|||
name: LEGACY_ALERT_DETAILS[ALERT_CLUSTER_HEALTH].label,
|
||||
legacy: {
|
||||
watchName: 'elasticsearch_cluster_status',
|
||||
nodeNameLabel: i18n.translate('xpack.monitoring.alerts.clusterHealth.nodeNameLabel', {
|
||||
defaultMessage: 'Elasticsearch cluster alert',
|
||||
}),
|
||||
},
|
||||
actionVariables: [
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { BaseAlert } from './base_alert';
|
||||
import {
|
||||
AlertData,
|
||||
|
@ -16,8 +17,8 @@ import {
|
|||
AlertMessageTimeToken,
|
||||
AlertMessageLinkToken,
|
||||
AlertInstanceState,
|
||||
CommonAlertFilter,
|
||||
CommonAlertParams,
|
||||
CommonAlertFilter,
|
||||
} from '../../common/types/alerts';
|
||||
import { AlertInstance } from '../../../alerts/server';
|
||||
import {
|
||||
|
@ -25,6 +26,8 @@ import {
|
|||
ALERT_CPU_USAGE,
|
||||
ALERT_DETAILS,
|
||||
} from '../../common/constants';
|
||||
// @ts-ignore
|
||||
import { ROUNDED_FLOAT } from '../../common/formatting';
|
||||
import { fetchCpuUsageNodeStats } from '../lib/alerts/fetch_cpu_usage_node_stats';
|
||||
import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
|
||||
import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
|
||||
|
@ -121,7 +124,7 @@ export class CpuUsageAlert extends BaseAlert {
|
|||
defaultMessage: `Node #start_link{nodeName}#end_link is reporting cpu usage of {cpuUsage}% at #absolute`,
|
||||
values: {
|
||||
nodeName: stat.nodeName,
|
||||
cpuUsage: stat.cpuUsage,
|
||||
cpuUsage: numeral(stat.cpuUsage).format(ROUNDED_FLOAT),
|
||||
},
|
||||
}),
|
||||
nextSteps: [
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { BaseAlert } from './base_alert';
|
||||
import {
|
||||
AlertData,
|
||||
|
@ -15,8 +16,9 @@ import {
|
|||
AlertMessageTimeToken,
|
||||
AlertMessageLinkToken,
|
||||
AlertInstanceState,
|
||||
CommonAlertFilter,
|
||||
CommonAlertParams,
|
||||
AlertDiskUsageNodeStats,
|
||||
CommonAlertFilter,
|
||||
} from '../../common/types/alerts';
|
||||
import { AlertInstance } from '../../../alerts/server';
|
||||
import {
|
||||
|
@ -24,6 +26,8 @@ import {
|
|||
ALERT_DISK_USAGE,
|
||||
ALERT_DETAILS,
|
||||
} from '../../common/constants';
|
||||
// @ts-ignore
|
||||
import { ROUNDED_FLOAT } from '../../common/formatting';
|
||||
import { fetchDiskUsageNodeStats } from '../lib/alerts/fetch_disk_usage_node_stats';
|
||||
import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
|
||||
import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
|
||||
|
@ -102,13 +106,13 @@ export class DiskUsageAlert extends BaseAlert {
|
|||
}
|
||||
|
||||
protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage {
|
||||
const stat = item.meta as AlertDiskUsageState;
|
||||
const stat = item.meta as AlertDiskUsageNodeStats;
|
||||
return {
|
||||
text: i18n.translate('xpack.monitoring.alerts.diskUsage.ui.firingMessage', {
|
||||
defaultMessage: `Node #start_link{nodeName}#end_link is reporting disk usage of {diskUsage}% at #absolute`,
|
||||
values: {
|
||||
nodeName: stat.nodeName,
|
||||
diskUsage: stat.diskUsage,
|
||||
diskUsage: numeral(stat.diskUsage).format(ROUNDED_FLOAT),
|
||||
},
|
||||
}),
|
||||
nextSteps: [
|
||||
|
|
|
@ -124,6 +124,7 @@ describe('ElasticsearchVersionMismatchAlert', () => {
|
|||
{
|
||||
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
|
||||
ccs: undefined,
|
||||
nodeName: 'Elasticsearch node alert',
|
||||
ui: {
|
||||
isFiring: true,
|
||||
message: {
|
||||
|
|
|
@ -26,6 +26,12 @@ export class ElasticsearchVersionMismatchAlert extends BaseAlert {
|
|||
name: LEGACY_ALERT_DETAILS[ALERT_ELASTICSEARCH_VERSION_MISMATCH].label,
|
||||
legacy: {
|
||||
watchName: 'elasticsearch_version_mismatch',
|
||||
nodeNameLabel: i18n.translate(
|
||||
'xpack.monitoring.alerts.elasticsearchVersionMismatch.nodeNameLabel',
|
||||
{
|
||||
defaultMessage: 'Elasticsearch node alert',
|
||||
}
|
||||
),
|
||||
changeDataValues: { severity: AlertSeverity.Warning },
|
||||
},
|
||||
interval: '1d',
|
||||
|
|
|
@ -126,6 +126,7 @@ describe('KibanaVersionMismatchAlert', () => {
|
|||
{
|
||||
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
|
||||
ccs: undefined,
|
||||
nodeName: 'Kibana instance alert',
|
||||
ui: {
|
||||
isFiring: true,
|
||||
message: {
|
||||
|
|
|
@ -26,6 +26,12 @@ export class KibanaVersionMismatchAlert extends BaseAlert {
|
|||
name: LEGACY_ALERT_DETAILS[ALERT_KIBANA_VERSION_MISMATCH].label,
|
||||
legacy: {
|
||||
watchName: 'kibana_version_mismatch',
|
||||
nodeNameLabel: i18n.translate(
|
||||
'xpack.monitoring.alerts.kibanaVersionMismatch.nodeNameLabel',
|
||||
{
|
||||
defaultMessage: 'Kibana instance alert',
|
||||
}
|
||||
),
|
||||
changeDataValues: { severity: AlertSeverity.Warning },
|
||||
},
|
||||
interval: '1d',
|
||||
|
|
|
@ -130,6 +130,7 @@ describe('LicenseExpirationAlert', () => {
|
|||
{
|
||||
cluster: { clusterUuid, clusterName },
|
||||
ccs: undefined,
|
||||
nodeName: 'Elasticsearch cluster alert',
|
||||
ui: {
|
||||
isFiring: true,
|
||||
message: {
|
||||
|
|
|
@ -34,6 +34,9 @@ export class LicenseExpirationAlert extends BaseAlert {
|
|||
name: LEGACY_ALERT_DETAILS[ALERT_LICENSE_EXPIRATION].label,
|
||||
legacy: {
|
||||
watchName: 'xpack_license_expiration',
|
||||
nodeNameLabel: i18n.translate('xpack.monitoring.alerts.licenseExpiration.nodeNameLabel', {
|
||||
defaultMessage: 'Elasticsearch cluster alert',
|
||||
}),
|
||||
},
|
||||
interval: '1d',
|
||||
actionVariables: [
|
||||
|
|
|
@ -125,6 +125,7 @@ describe('LogstashVersionMismatchAlert', () => {
|
|||
{
|
||||
cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' },
|
||||
ccs: undefined,
|
||||
nodeName: 'Logstash node alert',
|
||||
ui: {
|
||||
isFiring: true,
|
||||
message: {
|
||||
|
|
|
@ -26,6 +26,12 @@ export class LogstashVersionMismatchAlert extends BaseAlert {
|
|||
name: LEGACY_ALERT_DETAILS[ALERT_LOGSTASH_VERSION_MISMATCH].label,
|
||||
legacy: {
|
||||
watchName: 'logstash_version_mismatch',
|
||||
nodeNameLabel: i18n.translate(
|
||||
'xpack.monitoring.alerts.logstashVersionMismatch.nodeNameLabel',
|
||||
{
|
||||
defaultMessage: 'Logstash node alert',
|
||||
}
|
||||
),
|
||||
changeDataValues: { severity: AlertSeverity.Warning },
|
||||
},
|
||||
interval: '1d',
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { BaseAlert } from './base_alert';
|
||||
import {
|
||||
AlertData,
|
||||
|
@ -15,8 +16,9 @@ import {
|
|||
AlertMessageTimeToken,
|
||||
AlertMessageLinkToken,
|
||||
AlertInstanceState,
|
||||
CommonAlertFilter,
|
||||
CommonAlertParams,
|
||||
AlertMemoryUsageNodeStats,
|
||||
CommonAlertFilter,
|
||||
} from '../../common/types/alerts';
|
||||
import { AlertInstance } from '../../../alerts/server';
|
||||
import {
|
||||
|
@ -24,6 +26,8 @@ import {
|
|||
ALERT_MEMORY_USAGE,
|
||||
ALERT_DETAILS,
|
||||
} from '../../common/constants';
|
||||
// @ts-ignore
|
||||
import { ROUNDED_FLOAT } from '../../common/formatting';
|
||||
import { fetchMemoryUsageNodeStats } from '../lib/alerts/fetch_memory_usage_node_stats';
|
||||
import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
|
||||
import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
|
||||
|
@ -108,13 +112,13 @@ export class MemoryUsageAlert extends BaseAlert {
|
|||
}
|
||||
|
||||
protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage {
|
||||
const stat = item.meta as AlertMemoryUsageState;
|
||||
const stat = item.meta as AlertMemoryUsageNodeStats;
|
||||
return {
|
||||
text: i18n.translate('xpack.monitoring.alerts.memoryUsage.ui.firingMessage', {
|
||||
defaultMessage: `Node #start_link{nodeName}#end_link is reporting JVM memory usage of {memoryUsage}% at #absolute`,
|
||||
values: {
|
||||
nodeName: stat.nodeName,
|
||||
memoryUsage: stat.memoryUsage,
|
||||
memoryUsage: numeral(stat.memoryUsage).format(ROUNDED_FLOAT),
|
||||
},
|
||||
}),
|
||||
nextSteps: [
|
||||
|
|
|
@ -128,9 +128,9 @@ describe('MissingMonitoringDataAlert', () => {
|
|||
{
|
||||
ccs: undefined,
|
||||
cluster: { clusterUuid, clusterName },
|
||||
gapDuration,
|
||||
nodeName,
|
||||
nodeId,
|
||||
nodeName,
|
||||
gapDuration,
|
||||
ui: {
|
||||
isFiring: true,
|
||||
message: {
|
||||
|
|
|
@ -12,10 +12,9 @@ import {
|
|||
AlertCluster,
|
||||
AlertState,
|
||||
AlertMessage,
|
||||
AlertNodeState,
|
||||
AlertMessageTimeToken,
|
||||
CommonAlertFilter,
|
||||
CommonAlertParams,
|
||||
CommonAlertFilter,
|
||||
} from '../../common/types/alerts';
|
||||
import { AlertInstance } from '../../../alerts/server';
|
||||
import {
|
||||
|
@ -40,12 +39,12 @@ export class MissingMonitoringDataAlert extends BaseAlert {
|
|||
super(rawAlert, {
|
||||
id: ALERT_MISSING_MONITORING_DATA,
|
||||
name: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label,
|
||||
accessorKey: 'gapDuration',
|
||||
defaultParams: {
|
||||
duration: '15m',
|
||||
limit: '1d',
|
||||
},
|
||||
throttle: '6h',
|
||||
accessorKey: 'gapDuration',
|
||||
actionVariables: [
|
||||
{
|
||||
name: 'nodes',
|
||||
|
@ -153,7 +152,7 @@ export class MissingMonitoringDataAlert extends BaseAlert {
|
|||
|
||||
protected executeActions(
|
||||
instance: AlertInstance,
|
||||
{ alertStates }: { alertStates: AlertNodeState[] },
|
||||
{ alertStates }: { alertStates: AlertState[] },
|
||||
item: AlertData | null,
|
||||
cluster: AlertCluster
|
||||
) {
|
||||
|
|
|
@ -137,6 +137,7 @@ describe('NodesChangedAlert', () => {
|
|||
{
|
||||
cluster: { clusterUuid, clusterName },
|
||||
ccs: undefined,
|
||||
nodeName: 'Elasticsearch nodes alert',
|
||||
ui: {
|
||||
isFiring: true,
|
||||
message: {
|
||||
|
|
|
@ -26,6 +26,9 @@ export class NodesChangedAlert extends BaseAlert {
|
|||
name: LEGACY_ALERT_DETAILS[ALERT_NODES_CHANGED].label,
|
||||
legacy: {
|
||||
watchName: 'elasticsearch_nodes',
|
||||
nodeNameLabel: i18n.translate('xpack.monitoring.alerts.nodesChanged.nodeNameLabel', {
|
||||
defaultMessage: 'Elasticsearch nodes alert',
|
||||
}),
|
||||
changeDataValues: { shouldFire: true },
|
||||
},
|
||||
actionVariables: [
|
||||
|
|
|
@ -13,8 +13,8 @@ import {
|
|||
AlertThreadPoolRejectionsState,
|
||||
AlertMessageTimeToken,
|
||||
AlertMessageLinkToken,
|
||||
CommonAlertFilter,
|
||||
ThreadPoolRejectionsAlertParams,
|
||||
CommonAlertFilter,
|
||||
} from '../../common/types/alerts';
|
||||
import { AlertInstance } from '../../../alerts/server';
|
||||
import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants';
|
||||
|
|
|
@ -47,6 +47,7 @@ describe('fetchLegacyAlerts', () => {
|
|||
message,
|
||||
metadata,
|
||||
nodes,
|
||||
nodeName: '',
|
||||
prefix,
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -86,6 +86,7 @@ export async function fetchLegacyAlerts(
|
|||
message: get(hit, '_source.message'),
|
||||
resolved_timestamp: get(hit, '_source.resolved_timestamp'),
|
||||
nodes: get(hit, '_source.nodes'),
|
||||
nodeName: '', // This is set by BaseAlert
|
||||
metadata: get(hit, '_source.metadata') as LegacyAlertMetadata,
|
||||
};
|
||||
return legacyAlert;
|
||||
|
|
|
@ -11,6 +11,7 @@ Array [
|
|||
"shardCount": 6,
|
||||
"transport_address": "127.0.0.1:9300",
|
||||
"type": "node",
|
||||
"uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
|
||||
},
|
||||
Object {
|
||||
"isOnline": false,
|
||||
|
@ -21,6 +22,7 @@ Array [
|
|||
"shardCount": 6,
|
||||
"transport_address": "127.0.0.1:9301",
|
||||
"type": "node",
|
||||
"uuid": "DAiX7fFjS3Wii7g2HYKrOg",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
@ -156,6 +158,7 @@ Array [
|
|||
"shardCount": 0,
|
||||
"transport_address": "127.0.0.1:9300",
|
||||
"type": "master",
|
||||
"uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
|
||||
},
|
||||
Object {
|
||||
"isOnline": true,
|
||||
|
@ -265,6 +268,7 @@ Array [
|
|||
"shardCount": 0,
|
||||
"transport_address": "127.0.0.1:9301",
|
||||
"type": "node",
|
||||
"uuid": "DAiX7fFjS3Wii7g2HYKrOg",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
@ -286,6 +290,7 @@ Array [
|
|||
"shardCount": 6,
|
||||
"transport_address": "127.0.0.1:9300",
|
||||
"type": "master",
|
||||
"uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
|
||||
},
|
||||
Object {
|
||||
"isOnline": true,
|
||||
|
@ -302,6 +307,7 @@ Array [
|
|||
"shardCount": 6,
|
||||
"transport_address": "127.0.0.1:9301",
|
||||
"type": "node",
|
||||
"uuid": "DAiX7fFjS3Wii7g2HYKrOg",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
@ -435,6 +441,7 @@ Array [
|
|||
"shardCount": 6,
|
||||
"transport_address": "127.0.0.1:9300",
|
||||
"type": "master",
|
||||
"uuid": "_x_V2YzPQU-a9KRRBxUxZQ",
|
||||
},
|
||||
Object {
|
||||
"isOnline": true,
|
||||
|
@ -544,6 +551,7 @@ Array [
|
|||
"shardCount": 6,
|
||||
"transport_address": "127.0.0.1:9301",
|
||||
"type": "node",
|
||||
"uuid": "DAiX7fFjS3Wii7g2HYKrOg",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
@ -25,8 +25,9 @@ import { LISTING_METRICS_NAMES, LISTING_METRICS_PATHS } from './nodes_listing_me
|
|||
*
|
||||
* @param {Object} req: server request object
|
||||
* @param {String} esIndexPattern: index pattern for elasticsearch data in monitoring indices
|
||||
* @param {Object} pageOfNodes: server-side paginated current page of ES nodes
|
||||
* @param {Object} clusterStats: cluster stats from cluster state document
|
||||
* @param {Object} shardStats: per-node information about shards
|
||||
* @param {Object} nodesShardCount: per-node information about shards
|
||||
* @return {Array} node info combined with metrics for each node from handle_response
|
||||
*/
|
||||
export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, nodesShardCount) {
|
||||
|
|
|
@ -47,6 +47,7 @@ export function handleResponse(
|
|||
|
||||
// nodesInfo is the source of truth for the nodeIds, where nodesMetrics will lack metrics for offline nodes
|
||||
const nodes = pageOfNodes.map((node) => ({
|
||||
...node,
|
||||
...nodesInfo[node.uuid],
|
||||
...nodesMetrics[node.uuid],
|
||||
resolver: node.uuid,
|
||||
|
|
|
@ -15,7 +15,7 @@ function defaultFilterFn(value, query) {
|
|||
export function filter(data, queryText, fields, filterFn = defaultFilterFn) {
|
||||
return data.filter((item) => {
|
||||
for (const field of fields) {
|
||||
if (filterFn(get(item, field), queryText)) {
|
||||
if (filterFn(get(item, field, ''), queryText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13564,8 +13564,6 @@
|
|||
"xpack.monitoring.alerts.actionVariables.internalShortMessage": "内部メッセージ(省略あり)はElasticで生成されました。",
|
||||
"xpack.monitoring.alerts.actionVariables.state": "現在のアラートの状態。",
|
||||
"xpack.monitoring.alerts.badge.panelTitle": "アラート",
|
||||
"xpack.monitoring.alerts.callout.dangerLabel": "危険アラート",
|
||||
"xpack.monitoring.alerts.callout.warningLabel": "警告アラート",
|
||||
"xpack.monitoring.alerts.clusterHealth.action.danger": "見つからないプライマリおよびレプリカシャードを割り当てます。",
|
||||
"xpack.monitoring.alerts.clusterHealth.action.warning": "見つからないレプリカシャードを割り当てます。",
|
||||
"xpack.monitoring.alerts.clusterHealth.actionVariables.clusterHealth": "クラスターの正常性。",
|
||||
|
|
|
@ -13581,8 +13581,6 @@
|
|||
"xpack.monitoring.alerts.actionVariables.internalShortMessage": "Elastic 生成的简短内部消息。",
|
||||
"xpack.monitoring.alerts.actionVariables.state": "告警的当前状态。",
|
||||
"xpack.monitoring.alerts.badge.panelTitle": "告警",
|
||||
"xpack.monitoring.alerts.callout.dangerLabel": "危险告警",
|
||||
"xpack.monitoring.alerts.callout.warningLabel": "警告告警",
|
||||
"xpack.monitoring.alerts.clusterHealth.action.danger": "分配缺失的主分片和副本分片。",
|
||||
"xpack.monitoring.alerts.clusterHealth.action.warning": "分配缺失的副本分片。",
|
||||
"xpack.monitoring.alerts.clusterHealth.actionVariables.clusterHealth": "集群的运行状况。",
|
||||
|
|
|
@ -139,7 +139,8 @@
|
|||
"slope": -1
|
||||
}
|
||||
},
|
||||
"resolver": "_x_V2YzPQU-a9KRRBxUxZQ"
|
||||
"resolver": "_x_V2YzPQU-a9KRRBxUxZQ",
|
||||
"uuid": "_x_V2YzPQU-a9KRRBxUxZQ"
|
||||
},
|
||||
{
|
||||
"name": "hello02",
|
||||
|
@ -247,7 +248,8 @@
|
|||
"slope": -1
|
||||
}
|
||||
},
|
||||
"resolver": "DAiX7fFjS3Wii7g2HYKrOg"
|
||||
"resolver": "DAiX7fFjS3Wii7g2HYKrOg",
|
||||
"uuid": "DAiX7fFjS3Wii7g2HYKrOg"
|
||||
}
|
||||
],
|
||||
"totalNodeCount": 2
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
}
|
||||
},
|
||||
"resolver": "ENVgDIKRSdCVJo-YqY4kUQ",
|
||||
"uuid": "ENVgDIKRSdCVJo-YqY4kUQ",
|
||||
"shardCount": 54,
|
||||
"transport_address": "127.0.0.1:9300",
|
||||
"type": "master"
|
||||
|
@ -185,6 +186,7 @@
|
|||
}
|
||||
},
|
||||
"resolver": "t9J9jvHpQ2yDw9c1LJ0tHA",
|
||||
"uuid": "t9J9jvHpQ2yDw9c1LJ0tHA",
|
||||
"shardCount": 54,
|
||||
"transport_address": "127.0.0.1:9301",
|
||||
"type": "node"
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
}
|
||||
},
|
||||
"resolver": "_WmX0plYQwm2z6tfCPyCQw",
|
||||
"uuid": "_WmX0plYQwm2z6tfCPyCQw",
|
||||
"shardCount": 23,
|
||||
"transport_address": "127.0.0.1:9300",
|
||||
"type": "master"
|
||||
|
@ -107,6 +108,7 @@
|
|||
"nodeTypeClass": "storage",
|
||||
"nodeTypeLabel": "Node",
|
||||
"resolver": "1jxg5T33TWub-jJL4qP0Wg",
|
||||
"uuid": "1jxg5T33TWub-jJL4qP0Wg",
|
||||
"shardCount": 0,
|
||||
"transport_address": "127.0.0.1:9302",
|
||||
"type": "node"
|
||||
|
|
Loading…
Reference in a new issue