mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Stack Monitoring] Add stale status reporting for Kibana (#132613)
* [Stack Monitoring] Add stale status reporting for Kibana (#126386) * Fix stale message grammar and update stale indicator to use EuiBadge * Fix i18n ids * Remove unused i18n key * Fix Jest tests * Update exposeToBrowser test * Update API integration tests * Fix functional tests * Fix API integration tests * Update snapshots Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6b02c03869
commit
ee7d9b0f33
42 changed files with 600 additions and 159 deletions
|
@ -137,6 +137,10 @@ represent. Defaults to 10. If you modify the
|
||||||
`monitoring.ui.collection.interval` in `elasticsearch.yml`, use the same
|
`monitoring.ui.collection.interval` in `elasticsearch.yml`, use the same
|
||||||
value in this setting.
|
value in this setting.
|
||||||
|
|
||||||
|
`monitoring.ui.kibana.reporting.stale_status_threshold_seconds`::
|
||||||
|
Specifies how many seconds can pass before the Kibana status reports are considered stale.
|
||||||
|
Defaults to `120`.
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
[[monitoring-ui-cgroup-settings]]
|
[[monitoring-ui-cgroup-settings]]
|
||||||
===== Monitoring UI container settings
|
===== Monitoring UI container settings
|
||||||
|
|
|
@ -130,6 +130,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||||
'monitoring.kibana.collection.enabled (boolean)',
|
'monitoring.kibana.collection.enabled (boolean)',
|
||||||
'monitoring.kibana.collection.interval (number)',
|
'monitoring.kibana.collection.interval (number)',
|
||||||
'monitoring.ui.ccs.enabled (boolean)',
|
'monitoring.ui.ccs.enabled (boolean)',
|
||||||
|
'monitoring.ui.kibana.reporting.stale_status_threshold_seconds (number)',
|
||||||
'monitoring.ui.container.apm.enabled (boolean)',
|
'monitoring.ui.container.apm.enabled (boolean)',
|
||||||
'monitoring.ui.container.elasticsearch.enabled (boolean)',
|
'monitoring.ui.container.elasticsearch.enabled (boolean)',
|
||||||
'monitoring.ui.container.logstash.enabled (boolean)',
|
'monitoring.ui.container.logstash.enabled (boolean)',
|
||||||
|
|
|
@ -12,6 +12,7 @@ export interface ExternalConfig {
|
||||||
showCgroupMetricsElasticsearch: boolean;
|
showCgroupMetricsElasticsearch: boolean;
|
||||||
showCgroupMetricsLogstash: boolean;
|
showCgroupMetricsLogstash: boolean;
|
||||||
renderReactApp: boolean;
|
renderReactApp: boolean;
|
||||||
|
staleStatusThresholdSeconds: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalConfigContext = createContext({} as ExternalConfig);
|
export const ExternalConfigContext = createContext({} as ExternalConfig);
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function HealthStatusIndicator(props) {
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiHealth color={statusColor} data-test-subj="statusIcon">
|
<EuiHealth color={statusColor} data-test-subj="status">
|
||||||
<HealthLabel {...props} />
|
<HealthLabel {...props} />
|
||||||
</EuiHealth>
|
</EuiHealth>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
|
@ -5,37 +5,40 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { formatNumber } from '../../../lib/format_number';
|
|
||||||
import {
|
|
||||||
ClusterItemContainer,
|
|
||||||
HealthStatusIndicator,
|
|
||||||
BytesPercentageUsage,
|
|
||||||
DisabledIfNoDataAndInSetupModeLink,
|
|
||||||
} from './helpers';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
|
EuiBadge,
|
||||||
|
EuiDescriptionList,
|
||||||
|
EuiDescriptionListDescription,
|
||||||
|
EuiDescriptionListTitle,
|
||||||
EuiFlexGrid,
|
EuiFlexGrid,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiLink,
|
|
||||||
EuiTitle,
|
|
||||||
EuiPanel,
|
|
||||||
EuiDescriptionList,
|
|
||||||
EuiDescriptionListTitle,
|
|
||||||
EuiDescriptionListDescription,
|
|
||||||
EuiHorizontalRule,
|
EuiHorizontalRule,
|
||||||
|
EuiLink,
|
||||||
|
EuiPanel,
|
||||||
|
EuiTitle,
|
||||||
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { SetupModeTooltip } from '../../setup_mode/tooltip';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
import { KIBANA_SYSTEM_ID, RULE_KIBANA_VERSION_MISMATCH } from '../../../../common/constants';
|
import { KIBANA_SYSTEM_ID, RULE_KIBANA_VERSION_MISMATCH } from '../../../../common/constants';
|
||||||
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
|
import { SetupModeFeature } from '../../../../common/enums';
|
||||||
import { AlertsBadge } from '../../../alerts/badge';
|
import { AlertsBadge } from '../../../alerts/badge';
|
||||||
import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge';
|
import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge';
|
||||||
|
import { ExternalConfigContext } from '../../../application/contexts/external_config_context';
|
||||||
|
import { formatNumber } from '../../../lib/format_number';
|
||||||
|
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
|
||||||
import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode';
|
import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode';
|
||||||
import { SetupModeFeature } from '../../../../common/enums';
|
|
||||||
import { SetupModeContext } from '../../setup_mode/setup_mode_context';
|
import { SetupModeContext } from '../../setup_mode/setup_mode_context';
|
||||||
|
import { SetupModeTooltip } from '../../setup_mode/tooltip';
|
||||||
|
import {
|
||||||
|
BytesPercentageUsage,
|
||||||
|
ClusterItemContainer,
|
||||||
|
DisabledIfNoDataAndInSetupModeLink,
|
||||||
|
HealthStatusIndicator,
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
const INSTANCES_PANEL_ALERTS = [RULE_KIBANA_VERSION_MISMATCH];
|
const INSTANCES_PANEL_ALERTS = [RULE_KIBANA_VERSION_MISMATCH];
|
||||||
|
|
||||||
|
@ -43,14 +46,13 @@ export function KibanaPanel(props) {
|
||||||
const setupMode = props.setupMode;
|
const setupMode = props.setupMode;
|
||||||
const alerts = props.alerts;
|
const alerts = props.alerts;
|
||||||
const setupModeContext = React.useContext(SetupModeContext);
|
const setupModeContext = React.useContext(SetupModeContext);
|
||||||
|
const { staleStatusThresholdSeconds } = React.useContext(ExternalConfigContext);
|
||||||
const showDetectedKibanas =
|
const showDetectedKibanas =
|
||||||
setupMode.enabled && get(setupMode.data, 'kibana.detected.doesExist', false);
|
setupMode.enabled && get(setupMode.data, 'kibana.detected.doesExist', false);
|
||||||
if (!props.count && !showDetectedKibanas) {
|
if (!props.count && !showDetectedKibanas) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusIndicator = <HealthStatusIndicator status={props.status} product={'kb'} />;
|
|
||||||
|
|
||||||
const goToKibana = () => getSafeForExternalLink('#/kibana');
|
const goToKibana = () => getSafeForExternalLink('#/kibana');
|
||||||
const goToInstances = () => getSafeForExternalLink('#/kibana/instances');
|
const goToInstances = () => getSafeForExternalLink('#/kibana/instances');
|
||||||
|
|
||||||
|
@ -78,7 +80,12 @@ export function KibanaPanel(props) {
|
||||||
return (
|
return (
|
||||||
<ClusterItemContainer
|
<ClusterItemContainer
|
||||||
{...props}
|
{...props}
|
||||||
statusIndicator={statusIndicator}
|
statusIndicator={statusIndicator(
|
||||||
|
props.status,
|
||||||
|
props.some_status_is_stale,
|
||||||
|
goToInstances(),
|
||||||
|
staleStatusThresholdSeconds
|
||||||
|
)}
|
||||||
url="kibana"
|
url="kibana"
|
||||||
title={i18n.translate('xpack.monitoring.cluster.overview.kibanaPanel.kibanaTitle', {
|
title={i18n.translate('xpack.monitoring.cluster.overview.kibanaPanel.kibanaTitle', {
|
||||||
defaultMessage: 'Kibana',
|
defaultMessage: 'Kibana',
|
||||||
|
@ -203,3 +210,42 @@ export function KibanaPanel(props) {
|
||||||
</ClusterItemContainer>
|
</ClusterItemContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function statusIndicator(status, someStatusIsStale, instancesHref, staleStatusThresholdSeconds) {
|
||||||
|
if (!someStatusIsStale) {
|
||||||
|
return <HealthStatusIndicator status={status} product={'kb'} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const staleMessage = i18n.translate(
|
||||||
|
'xpack.monitoring.cluster.overview.kibanaPanel.staleStatusTooltip',
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"It's been more than {staleStatusThresholdSeconds} seconds since we have heard from some instances.",
|
||||||
|
values: {
|
||||||
|
staleStatusThresholdSeconds,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<EuiToolTip position="top" content={staleMessage}>
|
||||||
|
<EuiBadge iconType="alert" color="warning" data-test-subj="status">
|
||||||
|
{i18n.translate('xpack.monitoring.cluster.overview.kibanaPanel.staleStatusLabel', {
|
||||||
|
defaultMessage: 'Stale',
|
||||||
|
})}
|
||||||
|
</EuiBadge>
|
||||||
|
</EuiToolTip>
|
||||||
|
</div>
|
||||||
|
<EuiLink href={instancesHref}>
|
||||||
|
{i18n.translate(
|
||||||
|
'xpack.monitoring.cluster.overview.kibanaPanel.staleStatusLinkToInstancesLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'View all instances',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</EuiLink>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -5,11 +5,15 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import { EuiBadge, EuiLink, EuiStat, EuiToolTip } from '@elastic/eui';
|
||||||
import { SummaryStatus } from '../../summary_status';
|
|
||||||
import { KibanaStatusIcon } from '../status_icon';
|
|
||||||
import { formatMetric } from '../../../lib/format_number';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import React from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { ExternalConfigContext } from '../../../application/contexts/external_config_context';
|
||||||
|
import { formatMetric } from '../../../lib/format_number';
|
||||||
|
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
|
||||||
|
import { DefaultStatusIndicator, SummaryStatus } from '../../summary_status';
|
||||||
|
import { KibanaStatusIcon } from '../status_icon';
|
||||||
|
|
||||||
export function ClusterStatus({ stats, alerts }) {
|
export function ClusterStatus({ stats, alerts }) {
|
||||||
const {
|
const {
|
||||||
|
@ -20,8 +24,12 @@ export function ClusterStatus({ stats, alerts }) {
|
||||||
requests_total: requests,
|
requests_total: requests,
|
||||||
response_time_max: maxResponseTime,
|
response_time_max: maxResponseTime,
|
||||||
status,
|
status,
|
||||||
|
some_status_is_stale: someStatusIsStale,
|
||||||
} = stats;
|
} = stats;
|
||||||
|
|
||||||
|
const { staleStatusThresholdSeconds } = React.useContext(ExternalConfigContext);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const metrics = [
|
const metrics = [
|
||||||
{
|
{
|
||||||
label: i18n.translate('xpack.monitoring.kibana.clusterStatus.instancesLabel', {
|
label: i18n.translate('xpack.monitoring.kibana.clusterStatus.instancesLabel', {
|
||||||
|
@ -60,15 +68,107 @@ export function ClusterStatus({ stats, alerts }) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const IconComponent = ({ status }) => <KibanaStatusIcon status={status} />;
|
const StatusIndicator = () => {
|
||||||
|
if (!someStatusIsStale) {
|
||||||
|
return (
|
||||||
|
<DefaultStatusIndicator status={status} isOnline={true} IconComponent={KibanaStatusIcon} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const staleMessage = i18n.translate(
|
||||||
|
'xpack.monitoring.kibana.clusterStatus.staleStatusTooltip',
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"It's been more than {staleStatusThresholdSeconds} seconds since we have heard from some instances.",
|
||||||
|
values: {
|
||||||
|
staleStatusThresholdSeconds,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (location.pathname === '/kibana') {
|
||||||
|
return <OverviewPageStatusIndicator staleMessage={staleMessage} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <InstancesPageStatusIndicator staleMessage={staleMessage} />;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SummaryStatus
|
<SummaryStatus
|
||||||
metrics={metrics}
|
StatusIndicator={StatusIndicator}
|
||||||
status={status}
|
|
||||||
alerts={alerts}
|
alerts={alerts}
|
||||||
IconComponent={IconComponent}
|
metrics={metrics}
|
||||||
data-test-subj="kibanaClusterStatus"
|
data-test-subj="kibanaClusterStatus"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OverviewPageStatusIndicator({ staleMessage }) {
|
||||||
|
const instancesHref = getSafeForExternalLink('#/kibana/instances');
|
||||||
|
|
||||||
|
const title = (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<EuiToolTip position="top" content={staleMessage}>
|
||||||
|
<EuiBadge iconType="alert" color="warning">
|
||||||
|
{i18n.translate(
|
||||||
|
'xpack.monitoring.kibana.clusterStatus.overview.staleStatusInstancesLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Stale',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</EuiBadge>
|
||||||
|
</EuiToolTip>
|
||||||
|
</div>
|
||||||
|
<EuiLink href={instancesHref}>
|
||||||
|
{i18n.translate(
|
||||||
|
'xpack.monitoring.kibana.clusterStatus.overview.staleStatusLinkToInstancesLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'View all instances',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</EuiLink>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiStat
|
||||||
|
data-test-subj="status"
|
||||||
|
description={i18n.translate('xpack.monitoring.kibana.clusterStatus.overview.statusLabel', {
|
||||||
|
defaultMessage: 'Status',
|
||||||
|
})}
|
||||||
|
title={title}
|
||||||
|
titleSize="xxxs"
|
||||||
|
textAlign="left"
|
||||||
|
className="monSummaryStatusNoWrap__stat"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function InstancesPageStatusIndicator({ staleMessage }) {
|
||||||
|
const title = (
|
||||||
|
<EuiToolTip position="top" content={staleMessage}>
|
||||||
|
<EuiBadge iconType="alert" color="warning">
|
||||||
|
{i18n.translate(
|
||||||
|
'xpack.monitoring.kibana.clusterStatus.instances.staleStatusInstancesLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Stale',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</EuiBadge>
|
||||||
|
</EuiToolTip>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiStat
|
||||||
|
data-test-subj="status"
|
||||||
|
description={i18n.translate('xpack.monitoring.kibana.clusterStatus.instances.statusLabel', {
|
||||||
|
defaultMessage: 'Status',
|
||||||
|
})}
|
||||||
|
title={title}
|
||||||
|
titleSize="xxxs"
|
||||||
|
textAlign="left"
|
||||||
|
className="monSummaryStatusNoWrap__stat"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -5,11 +5,16 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import { EuiBadge, EuiStat, EuiToolTip } from '@elastic/eui';
|
||||||
import { SummaryStatus } from '../../summary_status';
|
|
||||||
import { KibanaStatusIcon } from '../status_icon';
|
|
||||||
import { formatMetric } from '../../../lib/format_number';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { useUiSetting } from '@kbn/kibana-react-plugin/public';
|
||||||
|
import { capitalize } from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import { ExternalConfigContext } from '../../../application/contexts/external_config_context';
|
||||||
|
import { formatMetric } from '../../../lib/format_number';
|
||||||
|
import { DefaultStatusIndicator, SummaryStatus } from '../../summary_status';
|
||||||
|
import { formatLastSeenTimestamp } from '../format_last_seen_timestamp';
|
||||||
|
import { KibanaStatusIcon } from '../status_icon';
|
||||||
|
|
||||||
export function DetailStatus({ stats }) {
|
export function DetailStatus({ stats }) {
|
||||||
const {
|
const {
|
||||||
|
@ -18,8 +23,13 @@ export function DetailStatus({ stats }) {
|
||||||
version,
|
version,
|
||||||
uptime,
|
uptime,
|
||||||
status,
|
status,
|
||||||
|
statusIsStale,
|
||||||
|
lastSeenTimestamp,
|
||||||
} = stats;
|
} = stats;
|
||||||
|
|
||||||
|
const { staleStatusThresholdSeconds } = React.useContext(ExternalConfigContext);
|
||||||
|
const dateFormat = useUiSetting('dateFormat');
|
||||||
|
|
||||||
const metrics = [
|
const metrics = [
|
||||||
{
|
{
|
||||||
label: i18n.translate('xpack.monitoring.kibana.detailStatus.transportAddressLabel', {
|
label: i18n.translate('xpack.monitoring.kibana.detailStatus.transportAddressLabel', {
|
||||||
|
@ -51,14 +61,82 @@ export function DetailStatus({ stats }) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const IconComponent = ({ status }) => <KibanaStatusIcon status={status} />;
|
const StatusIndicator = () => {
|
||||||
|
if (!statusIsStale) {
|
||||||
|
return (
|
||||||
|
<DefaultStatusIndicator status={status} isOnline={true} IconComponent={KibanaStatusIcon} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { description, title } = prepareStaleMessage(
|
||||||
|
status,
|
||||||
|
lastSeenTimestamp,
|
||||||
|
staleStatusThresholdSeconds,
|
||||||
|
dateFormat
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiStat
|
||||||
|
data-test-subj="status"
|
||||||
|
description={description}
|
||||||
|
title={title}
|
||||||
|
titleSize="xxxs"
|
||||||
|
textAlign="left"
|
||||||
|
className="monSummaryStatusNoWrap__stat"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SummaryStatus
|
<SummaryStatus
|
||||||
|
StatusIndicator={StatusIndicator}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
status={status}
|
|
||||||
IconComponent={IconComponent}
|
|
||||||
data-test-subj="kibanaDetailStatus"
|
data-test-subj="kibanaDetailStatus"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareStaleMessage(status, lastSeenTimestamp, staleStatusThresholdSeconds, dateFormat) {
|
||||||
|
const { shouldShowRelativeTime, relativeTime, formattedTimestamp } = formatLastSeenTimestamp(
|
||||||
|
lastSeenTimestamp,
|
||||||
|
dateFormat
|
||||||
|
);
|
||||||
|
|
||||||
|
const staleMessage = i18n.translate('xpack.monitoring.kibana.detailStatus.staleStatusTooltip', {
|
||||||
|
defaultMessage:
|
||||||
|
"It's been more than {staleStatusThresholdSeconds} seconds since we have heard from this instance. Last seen: {lastSeenTimestamp}",
|
||||||
|
values: {
|
||||||
|
staleStatusThresholdSeconds,
|
||||||
|
lastSeenTimestamp: shouldShowRelativeTime ? relativeTime : formattedTimestamp,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const description = i18n.translate(
|
||||||
|
'xpack.monitoring.kibana.detailStatus.staleStatusMetricDescription',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Last Reported Status',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const title = (
|
||||||
|
<>
|
||||||
|
<KibanaStatusIcon status={status} />
|
||||||
|
|
||||||
|
{capitalize(status)}
|
||||||
|
<span style={{ marginLeft: '8px' }}>
|
||||||
|
<EuiToolTip position="top" content={staleMessage}>
|
||||||
|
<EuiBadge iconType="alert" color="warning">
|
||||||
|
{i18n.translate('xpack.monitoring.kibana.detailStatus.staleStatusLabel', {
|
||||||
|
defaultMessage: 'Stale',
|
||||||
|
})}
|
||||||
|
</EuiBadge>
|
||||||
|
</EuiToolTip>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
description,
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { capitalize } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
export function formatLastSeenTimestamp(lastSeenTimestampRaw: string, dateFormat: string) {
|
||||||
|
const lastSeenTimestamp = moment(lastSeenTimestampRaw);
|
||||||
|
|
||||||
|
const formattedTimestamp = lastSeenTimestamp.format(dateFormat);
|
||||||
|
const relativeTime = capitalize(lastSeenTimestamp.fromNow());
|
||||||
|
|
||||||
|
const sixHoursAgo = moment().subtract(6, 'hours');
|
||||||
|
const shouldShowRelativeTime = !sixHoursAgo.isAfter(lastSeenTimestamp);
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldShowRelativeTime,
|
||||||
|
formattedTimestamp,
|
||||||
|
relativeTime,
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,42 +5,51 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment } from 'react';
|
|
||||||
import {
|
import {
|
||||||
|
EuiCallOut,
|
||||||
|
EuiHealth,
|
||||||
|
EuiIconTip,
|
||||||
|
EuiLink,
|
||||||
EuiPage,
|
EuiPage,
|
||||||
EuiPageBody,
|
EuiPageBody,
|
||||||
EuiPageContent,
|
EuiPageContent,
|
||||||
EuiPanel,
|
EuiPanel,
|
||||||
EuiSpacer,
|
|
||||||
EuiLink,
|
|
||||||
EuiCallOut,
|
|
||||||
EuiScreenReaderOnly,
|
EuiScreenReaderOnly,
|
||||||
|
EuiSpacer,
|
||||||
EuiToolTip,
|
EuiToolTip,
|
||||||
EuiHealth,
|
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { useUiSetting } from '@kbn/kibana-react-plugin/public';
|
||||||
import { capitalize, get } from 'lodash';
|
import { capitalize, get } from 'lodash';
|
||||||
// @ts-ignore
|
import React, { Fragment } from 'react';
|
||||||
import { ClusterStatus } from '../cluster_status';
|
import { KIBANA_SYSTEM_ID } from '../../../../common/constants';
|
||||||
// @ts-ignore
|
import { SetupModeFeature } from '../../../../common/enums';
|
||||||
import { EuiMonitoringTable } from '../../table';
|
import { CommonAlertStatus } from '../../../../common/types/alerts';
|
||||||
import { STATUS_ICON_TYPES } from '../../status_icon';
|
import { ElasticsearchSourceKibanaStats } from '../../../../common/types/es';
|
||||||
|
import { AlertsStatus } from '../../../alerts/status';
|
||||||
|
import { ExternalConfigContext } from '../../../application/contexts/external_config_context';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { formatMetric, formatNumber } from '../../../lib/format_number';
|
import { formatMetric, formatNumber } from '../../../lib/format_number';
|
||||||
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
|
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
|
||||||
|
import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { SetupModeBadge } from '../../setup_mode/badge';
|
import { SetupModeBadge } from '../../setup_mode/badge';
|
||||||
import { KIBANA_SYSTEM_ID } from '../../../../common/constants';
|
|
||||||
import { CommonAlertStatus } from '../../../../common/types/alerts';
|
|
||||||
import { ElasticsearchSourceKibanaStats } from '../../../../common/types/es';
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { ListingCallOut } from '../../setup_mode/listing_callout';
|
import { ListingCallOut } from '../../setup_mode/listing_callout';
|
||||||
import { AlertsStatus } from '../../../alerts/status';
|
import { STATUS_ICON_TYPES } from '../../status_icon';
|
||||||
import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode';
|
// @ts-ignore
|
||||||
import { SetupModeFeature } from '../../../../common/enums';
|
import { EuiMonitoringTable } from '../../table';
|
||||||
|
// @ts-ignore
|
||||||
|
import { ClusterStatus } from '../cluster_status';
|
||||||
|
import { formatLastSeenTimestamp } from '../format_last_seen_timestamp';
|
||||||
|
|
||||||
const getColumns = (setupMode: any, alerts: { [alertTypeId: string]: CommonAlertStatus[] }) => {
|
const getColumns = (
|
||||||
|
setupMode: any,
|
||||||
|
alerts: { [alertTypeId: string]: CommonAlertStatus[] },
|
||||||
|
dateFormat: string,
|
||||||
|
staleStatusThresholdSeconds: number
|
||||||
|
) => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', {
|
name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', {
|
||||||
|
@ -95,29 +104,57 @@ const getColumns = (setupMode: any, alerts: { [alertTypeId: string]: CommonAlert
|
||||||
name: i18n.translate('xpack.monitoring.kibana.listing.alertsColumnTitle', {
|
name: i18n.translate('xpack.monitoring.kibana.listing.alertsColumnTitle', {
|
||||||
defaultMessage: 'Alerts',
|
defaultMessage: 'Alerts',
|
||||||
}),
|
}),
|
||||||
field: 'isOnline',
|
field: 'alerts_column',
|
||||||
width: '175px',
|
width: '175px',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: () => <AlertsStatus showBadge={true} alerts={alerts} />,
|
render: () => <AlertsStatus showBadge={true} alerts={alerts} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: i18n.translate('xpack.monitoring.kibana.listing.statusColumnTitle', {
|
name: i18n.translate('xpack.monitoring.kibana.listing.lastReportedStatusColumnTitle', {
|
||||||
defaultMessage: 'Status',
|
defaultMessage: 'Last Reported Status',
|
||||||
}),
|
}),
|
||||||
field: 'status',
|
field: 'status',
|
||||||
render: (
|
render: (status: string) => {
|
||||||
status: string,
|
|
||||||
kibana: Pick<ElasticsearchSourceKibanaStats, 'kibana'> & { availability: boolean }
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
<EuiToolTip content={status} position="bottom">
|
<EuiHealth color={statusIconColor(status)} data-test-subj="status">
|
||||||
<EuiHealth
|
{capitalize(status)}
|
||||||
color={kibana.availability ? 'success' : 'subdued'}
|
</EuiHealth>
|
||||||
data-test-subj="statusIcon"
|
);
|
||||||
>
|
},
|
||||||
{capitalize(status)}
|
},
|
||||||
</EuiHealth>
|
{
|
||||||
</EuiToolTip>
|
name: i18n.translate('xpack.monitoring.kibana.listing.lastSeenColumnTitle', {
|
||||||
|
defaultMessage: 'Last Seen',
|
||||||
|
}),
|
||||||
|
field: 'lastSeenTimestamp',
|
||||||
|
render: (
|
||||||
|
lastSeenTimestampRaw: string,
|
||||||
|
kibana: Pick<ElasticsearchSourceKibanaStats, 'kibana'> & { statusIsStale: boolean }
|
||||||
|
) => {
|
||||||
|
const lastSeenTimestamp = prepareLastSeenTimestamp(lastSeenTimestampRaw, dateFormat);
|
||||||
|
const staleMessage = i18n.translate('xpack.monitoring.kibana.listing.staleStatusTooltip', {
|
||||||
|
defaultMessage:
|
||||||
|
"It's been more than {staleStatusThresholdSeconds} seconds since we have heard from this instance.",
|
||||||
|
values: {
|
||||||
|
staleStatusThresholdSeconds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<span data-test-subj="lastSeen">
|
||||||
|
{lastSeenTimestamp}
|
||||||
|
{kibana.statusIsStale && (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<EuiIconTip
|
||||||
|
aria-label={staleMessage}
|
||||||
|
content={staleMessage}
|
||||||
|
size="l"
|
||||||
|
type="alert"
|
||||||
|
color="warning"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -183,6 +220,9 @@ interface Props {
|
||||||
export const KibanaInstances: React.FC<Props> = (props: Props) => {
|
export const KibanaInstances: React.FC<Props> = (props: Props) => {
|
||||||
const { clusterStatus, alerts, setupMode, sorting, pagination, onTableChange } = props;
|
const { clusterStatus, alerts, setupMode, sorting, pagination, onTableChange } = props;
|
||||||
|
|
||||||
|
const { staleStatusThresholdSeconds } = React.useContext(ExternalConfigContext);
|
||||||
|
const dateFormat = useUiSetting<string>('dateFormat');
|
||||||
|
|
||||||
let setupModeCallOut = null;
|
let setupModeCallOut = null;
|
||||||
// Merge the instances data with the setup data if enabled
|
// Merge the instances data with the setup data if enabled
|
||||||
const instances = props.instances || [];
|
const instances = props.instances || [];
|
||||||
|
@ -286,7 +326,7 @@ export const KibanaInstances: React.FC<Props> = (props: Props) => {
|
||||||
<EuiMonitoringTable
|
<EuiMonitoringTable
|
||||||
className="kibanaInstancesTable"
|
className="kibanaInstancesTable"
|
||||||
rows={dataFlattened}
|
rows={dataFlattened}
|
||||||
columns={getColumns(setupMode, alerts)}
|
columns={getColumns(setupMode, alerts, dateFormat, staleStatusThresholdSeconds)}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
setupMode={setupMode}
|
setupMode={setupMode}
|
||||||
|
@ -312,3 +352,33 @@ export const KibanaInstances: React.FC<Props> = (props: Props) => {
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function statusIconColor(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case 'red':
|
||||||
|
return 'danger';
|
||||||
|
case 'yellow':
|
||||||
|
return 'warning';
|
||||||
|
case 'green':
|
||||||
|
return 'success';
|
||||||
|
default:
|
||||||
|
return 'subdued';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareLastSeenTimestamp(lastSeenTimestampRaw: string, dateFormat: string) {
|
||||||
|
const { shouldShowRelativeTime, formattedTimestamp, relativeTime } = formatLastSeenTimestamp(
|
||||||
|
lastSeenTimestampRaw,
|
||||||
|
dateFormat
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldShowRelativeTime) {
|
||||||
|
return (
|
||||||
|
<EuiToolTip position="top" content={formattedTimestamp}>
|
||||||
|
<span>{relativeTime}</span>
|
||||||
|
</EuiToolTip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedTimestamp;
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ exports[`Summary Status Component should allow label to be optional 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="euiStat euiStat--leftAligned monSummaryStatusNoWrap__stat"
|
class="euiStat euiStat--leftAligned monSummaryStatusNoWrap__stat"
|
||||||
|
data-test-subj="status"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="euiText euiText--small euiStat__description"
|
class="euiText euiText--small euiStat__description"
|
||||||
|
@ -109,6 +110,10 @@ exports[`Summary Status Component should allow status to be optional 1`] = `
|
||||||
<div
|
<div
|
||||||
class="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
|
class="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="euiFlexItem euiFlexItem--flexGrowZero eui-textTruncate"
|
||||||
|
style="max-width:200px"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||||
data-test-subj="freeDiskSpace"
|
data-test-subj="freeDiskSpace"
|
||||||
|
@ -187,6 +192,7 @@ exports[`Summary Status Component should render metrics in a summary bar 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="euiStat euiStat--leftAligned monSummaryStatusNoWrap__stat"
|
class="euiStat euiStat--leftAligned monSummaryStatusNoWrap__stat"
|
||||||
|
data-test-subj="status"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="euiText euiText--small euiStat__description"
|
class="euiText euiText--small euiStat__description"
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { SummaryStatus } from './summary_status';
|
export { SummaryStatus, DefaultStatusIndicator } from './summary_status';
|
||||||
|
|
|
@ -54,49 +54,51 @@ const DefaultIconComponent = ({ status }) => (
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
const StatusIndicator = ({ status, isOnline, IconComponent }) => {
|
export const DefaultStatusIndicator = ({ status, isOnline, IconComponent }) => {
|
||||||
if (isEmpty(status)) {
|
if (isEmpty(status)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexItem
|
<EuiStat
|
||||||
className="eui-textTruncate"
|
data-test-subj="status"
|
||||||
style={{ maxWidth: 200 }}
|
title={
|
||||||
key={`summary-status-item-status`}
|
<>
|
||||||
grow={false}
|
<IconComponent status={status} isOnline={isOnline} />
|
||||||
>
|
|
||||||
<EuiStat
|
{capitalize(status)}
|
||||||
title={
|
</>
|
||||||
<Fragment>
|
}
|
||||||
<IconComponent status={status} isOnline={isOnline} />
|
titleSize="xxxs"
|
||||||
|
textAlign="left"
|
||||||
{capitalize(status)}
|
className="monSummaryStatusNoWrap__stat"
|
||||||
</Fragment>
|
description={i18n.translate('xpack.monitoring.summaryStatus.statusDescription', {
|
||||||
}
|
defaultMessage: 'Status',
|
||||||
titleSize="xxxs"
|
})}
|
||||||
textAlign="left"
|
/>
|
||||||
className="monSummaryStatusNoWrap__stat"
|
|
||||||
description={i18n.translate('xpack.monitoring.summaryStatus.statusDescription', {
|
|
||||||
defaultMessage: 'Status',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SummaryStatus({
|
export function SummaryStatus({
|
||||||
metrics,
|
StatusIndicator = DefaultStatusIndicator,
|
||||||
status,
|
status,
|
||||||
alerts,
|
|
||||||
isOnline,
|
isOnline,
|
||||||
IconComponent = DefaultIconComponent,
|
IconComponent = DefaultIconComponent,
|
||||||
|
alerts,
|
||||||
|
metrics,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div {...props} className="monSummaryStatusNoWrap">
|
<div {...props} className="monSummaryStatusNoWrap">
|
||||||
<EuiFlexGroup gutterSize="m" alignItems="center" justifyContent="spaceBetween">
|
<EuiFlexGroup gutterSize="m" alignItems="center" justifyContent="spaceBetween">
|
||||||
<StatusIndicator status={status} IconComponent={IconComponent} isOnline={isOnline} />
|
<EuiFlexItem
|
||||||
|
className="eui-textTruncate"
|
||||||
|
style={{ maxWidth: 200 }}
|
||||||
|
key={`summary-status-item-status`}
|
||||||
|
grow={false}
|
||||||
|
>
|
||||||
|
<StatusIndicator status={status} isOnline={isOnline} IconComponent={IconComponent} />
|
||||||
|
</EuiFlexItem>
|
||||||
{alerts ? (
|
{alerts ? (
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiStat
|
<EuiStat
|
||||||
|
|
|
@ -145,6 +145,10 @@ export class MonitoringPlugin
|
||||||
['showLicenseExpiration', monitoring.ui.show_license_expiration],
|
['showLicenseExpiration', monitoring.ui.show_license_expiration],
|
||||||
['showCgroupMetricsElasticsearch', monitoring.ui.container.elasticsearch.enabled],
|
['showCgroupMetricsElasticsearch', monitoring.ui.container.elasticsearch.enabled],
|
||||||
['showCgroupMetricsLogstash', monitoring.ui.container.logstash.enabled],
|
['showCgroupMetricsLogstash', monitoring.ui.container.logstash.enabled],
|
||||||
|
[
|
||||||
|
'staleStatusThresholdSeconds',
|
||||||
|
monitoring.ui.kibana.reporting.stale_status_threshold_seconds,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,23 +8,6 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { configSchema, createConfig } from './config';
|
import { configSchema, createConfig } from './config';
|
||||||
|
|
||||||
const MOCKED_PATHS = [
|
|
||||||
'/proc/self/cgroup',
|
|
||||||
'packages/kbn-dev-utils/certs/ca.crt',
|
|
||||||
'packages/kbn-dev-utils/certs/elasticsearch.crt',
|
|
||||||
'packages/kbn-dev-utils/certs/elasticsearch.key',
|
|
||||||
];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.spyOn(fs, 'readFileSync').mockImplementation((path, enc) => {
|
|
||||||
if (typeof path === 'string' && MOCKED_PATHS.includes(path) && enc === 'utf8') {
|
|
||||||
return `contents-of-${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`unpexpected arguments to fs.readFileSync: ${path}, ${enc}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('config schema', () => {
|
describe('config schema', () => {
|
||||||
it('generates proper defaults', () => {
|
it('generates proper defaults', () => {
|
||||||
expect(configSchema.validate({})).toMatchInlineSnapshot(`
|
expect(configSchema.validate({})).toMatchInlineSnapshot(`
|
||||||
|
@ -99,6 +82,11 @@ describe('config schema', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"kibana": Object {
|
||||||
|
"reporting": Object {
|
||||||
|
"stale_status_threshold_seconds": 120,
|
||||||
|
},
|
||||||
|
},
|
||||||
"logs": Object {
|
"logs": Object {
|
||||||
"index": "filebeat-*",
|
"index": "filebeat-*",
|
||||||
},
|
},
|
||||||
|
@ -115,6 +103,22 @@ describe('config schema', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createConfig()', () => {
|
describe('createConfig()', () => {
|
||||||
|
const MOCKED_PATHS = [
|
||||||
|
'packages/kbn-dev-utils/certs/ca.crt',
|
||||||
|
'packages/kbn-dev-utils/certs/elasticsearch.crt',
|
||||||
|
'packages/kbn-dev-utils/certs/elasticsearch.key',
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(fs, 'readFileSync').mockImplementation((path, enc) => {
|
||||||
|
if (typeof path === 'string' && MOCKED_PATHS.includes(path) && enc === 'utf8') {
|
||||||
|
return `contents-of-${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`unpexpected arguments to fs.readFileSync: ${path}, ${enc}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should wrap in Elasticsearch config', async () => {
|
it('should wrap in Elasticsearch config', async () => {
|
||||||
const config = createConfig(
|
const config = createConfig(
|
||||||
configSchema.validate({
|
configSchema.validate({
|
||||||
|
|
|
@ -35,6 +35,11 @@ export const configSchema = schema.object({
|
||||||
}),
|
}),
|
||||||
max_bucket_size: schema.number({ defaultValue: 10000 }),
|
max_bucket_size: schema.number({ defaultValue: 10000 }),
|
||||||
elasticsearch: monitoringElasticsearchConfigSchema,
|
elasticsearch: monitoringElasticsearchConfigSchema,
|
||||||
|
kibana: schema.object({
|
||||||
|
reporting: schema.object({
|
||||||
|
stale_status_threshold_seconds: schema.number({ defaultValue: 120 }),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
container: schema.object({
|
container: schema.object({
|
||||||
elasticsearch: schema.object({
|
elasticsearch: schema.object({
|
||||||
enabled: schema.boolean({ defaultValue: false }),
|
enabled: schema.boolean({ defaultValue: false }),
|
||||||
|
|
|
@ -28,6 +28,11 @@ export const config: PluginConfigDescriptor<TypeOf<typeof configSchema>> = {
|
||||||
ccs: {
|
ccs: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
kibana: {
|
||||||
|
reporting: {
|
||||||
|
stale_status_threshold_seconds: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
kibana: true,
|
kibana: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,22 @@
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { handleResponse } from './get_kibana_info';
|
import { handleResponse } from './get_kibana_info';
|
||||||
|
|
||||||
|
jest.mock('../../static_globals', () => ({
|
||||||
|
Globals: {
|
||||||
|
app: {
|
||||||
|
config: {
|
||||||
|
ui: {
|
||||||
|
kibana: {
|
||||||
|
reporting: {
|
||||||
|
stale_status_threshold_seconds: 120,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('get_kibana_info', () => {
|
describe('get_kibana_info', () => {
|
||||||
// TODO: test was not running before and is not up to date
|
// TODO: test was not running before and is not up to date
|
||||||
it.skip('return undefined for empty response', () => {
|
it.skip('return undefined for empty response', () => {
|
||||||
|
@ -16,13 +32,14 @@ describe('get_kibana_info', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('return mapped data for result with hits, availability = true', () => {
|
it('return mapped data for result with hits, availability = true', () => {
|
||||||
|
const timestamp = moment().format();
|
||||||
const result = handleResponse({
|
const result = handleResponse({
|
||||||
hits: {
|
hits: {
|
||||||
hits: [
|
hits: [
|
||||||
{
|
{
|
||||||
_source: {
|
_source: {
|
||||||
kibana_stats: {
|
kibana_stats: {
|
||||||
timestamp: moment().format(),
|
timestamp,
|
||||||
kibana: {
|
kibana: {
|
||||||
data: 123,
|
data: 123,
|
||||||
},
|
},
|
||||||
|
@ -31,6 +48,9 @@ describe('get_kibana_info', () => {
|
||||||
free_in_bytes: 123000,
|
free_in_bytes: 123000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
process: {
|
||||||
|
uptime_in_millis: 3000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -38,20 +58,23 @@ describe('get_kibana_info', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
availability: true,
|
lastSeenTimestamp: timestamp,
|
||||||
|
statusIsStale: false,
|
||||||
data: 123,
|
data: 123,
|
||||||
os_memory_free: 123000,
|
os_memory_free: 123000,
|
||||||
|
uptime: 3000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('return mapped data for result with hits, availability = false', () => {
|
it('return mapped data for result with hits, availability = false', () => {
|
||||||
|
const timestamp = moment().subtract(11, 'minutes').format();
|
||||||
const result = handleResponse({
|
const result = handleResponse({
|
||||||
hits: {
|
hits: {
|
||||||
hits: [
|
hits: [
|
||||||
{
|
{
|
||||||
_source: {
|
_source: {
|
||||||
kibana_stats: {
|
kibana_stats: {
|
||||||
timestamp: moment().subtract(11, 'minutes').format(),
|
timestamp,
|
||||||
kibana: {
|
kibana: {
|
||||||
data: 123,
|
data: 123,
|
||||||
},
|
},
|
||||||
|
@ -60,6 +83,9 @@ describe('get_kibana_info', () => {
|
||||||
free_in_bytes: 123000,
|
free_in_bytes: 123000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
process: {
|
||||||
|
uptime_in_millis: 3000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -67,9 +93,11 @@ describe('get_kibana_info', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
availability: false,
|
lastSeenTimestamp: timestamp,
|
||||||
|
statusIsStale: true,
|
||||||
data: 123,
|
data: 123,
|
||||||
os_memory_free: 123000,
|
os_memory_free: 123000,
|
||||||
|
uptime: 3000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,27 +6,26 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { merge } from 'lodash';
|
import { merge } from 'lodash';
|
||||||
// @ts-ignore
|
|
||||||
import { MissingRequiredError } from '../error_missing_required';
|
|
||||||
// @ts-ignore
|
|
||||||
import { calculateAvailability } from '../calculate_availability';
|
|
||||||
import { LegacyRequest } from '../../types';
|
|
||||||
import { ElasticsearchResponse } from '../../../common/types/es';
|
import { ElasticsearchResponse } from '../../../common/types/es';
|
||||||
import { getNewIndexPatterns } from '../cluster/get_index_patterns';
|
|
||||||
import { Globals } from '../../static_globals';
|
import { Globals } from '../../static_globals';
|
||||||
|
import { LegacyRequest } from '../../types';
|
||||||
|
import { getNewIndexPatterns } from '../cluster/get_index_patterns';
|
||||||
|
import { MissingRequiredError } from '../error_missing_required';
|
||||||
import { buildKibanaInfo } from './build_kibana_info';
|
import { buildKibanaInfo } from './build_kibana_info';
|
||||||
|
import { isKibanaStatusStale } from './is_kibana_status_stale';
|
||||||
|
|
||||||
export function handleResponse(resp: ElasticsearchResponse) {
|
export function handleResponse(resp: ElasticsearchResponse) {
|
||||||
const hit = resp.hits?.hits[0];
|
const hit = resp.hits?.hits[0];
|
||||||
const legacySource = hit?._source.kibana_stats;
|
const legacySource = hit?._source.kibana_stats;
|
||||||
const mbSource = hit?._source.kibana?.stats;
|
const mbSource = hit?._source.kibana?.stats;
|
||||||
const availabilityTimestamp = hit?._source['@timestamp'] ?? legacySource?.timestamp;
|
const lastSeenTimestamp = hit?._source['@timestamp'] ?? legacySource?.timestamp;
|
||||||
if (!availabilityTimestamp) {
|
if (!lastSeenTimestamp) {
|
||||||
throw new MissingRequiredError('timestamp');
|
throw new MissingRequiredError('timestamp');
|
||||||
}
|
}
|
||||||
|
|
||||||
return merge(buildKibanaInfo(hit!), {
|
return merge(buildKibanaInfo(hit!), {
|
||||||
availability: calculateAvailability(availabilityTimestamp),
|
statusIsStale: isKibanaStatusStale(lastSeenTimestamp),
|
||||||
|
lastSeenTimestamp,
|
||||||
os_memory_free: mbSource?.os?.memory?.free_in_bytes ?? legacySource?.os?.memory?.free_in_bytes,
|
os_memory_free: mbSource?.os?.memory?.free_in_bytes ?? legacySource?.os?.memory?.free_in_bytes,
|
||||||
uptime: mbSource?.process?.uptime?.ms ?? legacySource?.process?.uptime_in_millis,
|
uptime: mbSource?.process?.uptime?.ms ?? legacySource?.process?.uptime_in_millis,
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,17 +6,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
// @ts-ignore
|
import { ElasticsearchResponse, ElasticsearchResponseHit } from '../../../common/types/es';
|
||||||
import { createQuery } from '../create_query';
|
import { Globals } from '../../static_globals';
|
||||||
// @ts-ignore
|
|
||||||
import { calculateAvailability } from '../calculate_availability';
|
|
||||||
// @ts-ignore
|
|
||||||
import { KibanaMetric } from '../metrics';
|
|
||||||
import { LegacyRequest } from '../../types';
|
import { LegacyRequest } from '../../types';
|
||||||
import { getNewIndexPatterns } from '../cluster/get_index_patterns';
|
import { getNewIndexPatterns } from '../cluster/get_index_patterns';
|
||||||
import { Globals } from '../../static_globals';
|
import { createQuery } from '../create_query';
|
||||||
import { ElasticsearchResponse, ElasticsearchResponseHit } from '../../../common/types/es';
|
import { KibanaMetric } from '../metrics';
|
||||||
import { KibanaInfo, buildKibanaInfo } from './build_kibana_info';
|
import { buildKibanaInfo, KibanaInfo } from './build_kibana_info';
|
||||||
|
import { isKibanaStatusStale } from './is_kibana_status_stale';
|
||||||
|
|
||||||
interface Kibana {
|
interface Kibana {
|
||||||
process?: {
|
process?: {
|
||||||
|
@ -38,7 +35,8 @@ interface Kibana {
|
||||||
};
|
};
|
||||||
concurrent_connections?: number;
|
concurrent_connections?: number;
|
||||||
kibana?: KibanaInfo;
|
kibana?: KibanaInfo;
|
||||||
availability: boolean;
|
statusIsStale: boolean;
|
||||||
|
lastSeenTimestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -120,6 +118,8 @@ export async function getKibanas(req: LegacyRequest, { clusterUuid }: { clusterU
|
||||||
const legacyStats = hit._source.kibana_stats;
|
const legacyStats = hit._source.kibana_stats;
|
||||||
const mbStats = hit._source.kibana?.stats;
|
const mbStats = hit._source.kibana?.stats;
|
||||||
|
|
||||||
|
const lastSeenTimestamp = hit._source['@timestamp'] ?? hit._source.timestamp;
|
||||||
|
|
||||||
const kibana: Kibana = {
|
const kibana: Kibana = {
|
||||||
kibana: buildKibanaInfo(hit),
|
kibana: buildKibanaInfo(hit),
|
||||||
concurrent_connections:
|
concurrent_connections:
|
||||||
|
@ -143,7 +143,8 @@ export async function getKibanas(req: LegacyRequest, { clusterUuid }: { clusterU
|
||||||
requests: {
|
requests: {
|
||||||
total: mbStats?.request?.total ?? legacyStats?.requests?.total,
|
total: mbStats?.request?.total ?? legacyStats?.requests?.total,
|
||||||
},
|
},
|
||||||
availability: calculateAvailability(hit._source['@timestamp'] ?? hit._source.timestamp),
|
statusIsStale: isKibanaStatusStale(lastSeenTimestamp),
|
||||||
|
lastSeenTimestamp,
|
||||||
};
|
};
|
||||||
return kibana;
|
return kibana;
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { chain, find } from 'lodash';
|
import { chain, find } from 'lodash';
|
||||||
import { LegacyRequest, Cluster, Bucket } from '../../types';
|
import { Globals } from '../../static_globals';
|
||||||
|
import { Bucket, Cluster, LegacyRequest } from '../../types';
|
||||||
|
import { getNewIndexPatterns } from '../cluster/get_index_patterns';
|
||||||
import { createQuery } from '../create_query';
|
import { createQuery } from '../create_query';
|
||||||
import { KibanaClusterMetric } from '../metrics';
|
import { KibanaClusterMetric } from '../metrics';
|
||||||
import { getNewIndexPatterns } from '../cluster/get_index_patterns';
|
import { isKibanaStatusStale } from './is_kibana_status_stale';
|
||||||
import { Globals } from '../../static_globals';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get high-level info for Kibanas in a set of clusters
|
* Get high-level info for Kibanas in a set of clusters
|
||||||
|
@ -74,6 +75,11 @@ export function getKibanasForClusters(req: LegacyRequest, clusters: Cluster[], c
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aggs: {
|
aggs: {
|
||||||
|
last_seen: {
|
||||||
|
max: {
|
||||||
|
field: 'kibana_stats.timestamp',
|
||||||
|
},
|
||||||
|
},
|
||||||
response_time_max: {
|
response_time_max: {
|
||||||
max: {
|
max: {
|
||||||
field: 'kibana_stats.response_times.max',
|
field: 'kibana_stats.response_times.max',
|
||||||
|
@ -185,6 +191,7 @@ export function getKibanasForClusters(req: LegacyRequest, clusters: Cluster[], c
|
||||||
let responseTime = 0;
|
let responseTime = 0;
|
||||||
let memorySize = 0;
|
let memorySize = 0;
|
||||||
let memoryLimit = 0;
|
let memoryLimit = 0;
|
||||||
|
let someStatusIsStale = true;
|
||||||
|
|
||||||
// if the cluster has kibana instances at all
|
// if the cluster has kibana instances at all
|
||||||
if (kibanaUuids.length) {
|
if (kibanaUuids.length) {
|
||||||
|
@ -204,6 +211,8 @@ export function getKibanasForClusters(req: LegacyRequest, clusters: Cluster[], c
|
||||||
responseTime = aggregations.response_time_max?.value;
|
responseTime = aggregations.response_time_max?.value;
|
||||||
memorySize = aggregations.memory_rss?.value;
|
memorySize = aggregations.memory_rss?.value;
|
||||||
memoryLimit = aggregations.memory_heap_size_limit?.value;
|
memoryLimit = aggregations.memory_heap_size_limit?.value;
|
||||||
|
|
||||||
|
someStatusIsStale = kibanaUuids.some(hasStaleStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -211,6 +220,7 @@ export function getKibanasForClusters(req: LegacyRequest, clusters: Cluster[], c
|
||||||
stats: {
|
stats: {
|
||||||
uuids: kibanaUuids.map(({ key }: Bucket) => key),
|
uuids: kibanaUuids.map(({ key }: Bucket) => key),
|
||||||
status,
|
status,
|
||||||
|
some_status_is_stale: someStatusIsStale,
|
||||||
requests_total: requestsTotal,
|
requests_total: requestsTotal,
|
||||||
concurrent_connections: connections,
|
concurrent_connections: connections,
|
||||||
response_time_max: responseTime,
|
response_time_max: responseTime,
|
||||||
|
@ -223,3 +233,19 @@ export function getKibanasForClusters(req: LegacyRequest, clusters: Cluster[], c
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasStaleStatus(kibana: any) {
|
||||||
|
const buckets: any[] = kibana?.latest_report?.buckets ?? [];
|
||||||
|
|
||||||
|
if (buckets.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastSeenTimestamp: string | null = buckets[0]?.last_seen?.value_as_string ?? null;
|
||||||
|
|
||||||
|
if (lastSeenTimestamp === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isKibanaStatusStale(lastSeenTimestamp);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Globals } from '../../static_globals';
|
||||||
|
|
||||||
|
export function isKibanaStatusStale(lastSeenTimestamp: string) {
|
||||||
|
const lastSeen = moment(lastSeenTimestamp);
|
||||||
|
const staleThreshold = moment().subtract(
|
||||||
|
Globals.app.config.ui.kibana.reporting.stale_status_threshold_seconds,
|
||||||
|
'seconds'
|
||||||
|
);
|
||||||
|
return staleThreshold.isAfter(lastSeen);
|
||||||
|
}
|
|
@ -20508,7 +20508,6 @@
|
||||||
"xpack.monitoring.kibana.listing.nameColumnTitle": "Nom",
|
"xpack.monitoring.kibana.listing.nameColumnTitle": "Nom",
|
||||||
"xpack.monitoring.kibana.listing.requestsColumnTitle": "Demandes",
|
"xpack.monitoring.kibana.listing.requestsColumnTitle": "Demandes",
|
||||||
"xpack.monitoring.kibana.listing.responseTimeColumnTitle": "Temps de réponse",
|
"xpack.monitoring.kibana.listing.responseTimeColumnTitle": "Temps de réponse",
|
||||||
"xpack.monitoring.kibana.listing.statusColumnTitle": "Statut",
|
|
||||||
"xpack.monitoring.kibana.overview.pageTitle": "Aperçu Kibana",
|
"xpack.monitoring.kibana.overview.pageTitle": "Aperçu Kibana",
|
||||||
"xpack.monitoring.kibana.overview.title": "Kibana",
|
"xpack.monitoring.kibana.overview.title": "Kibana",
|
||||||
"xpack.monitoring.kibana.shardActivity.bytesTitle": "Octets",
|
"xpack.monitoring.kibana.shardActivity.bytesTitle": "Octets",
|
||||||
|
|
|
@ -20648,7 +20648,6 @@
|
||||||
"xpack.monitoring.kibana.listing.nameColumnTitle": "名前",
|
"xpack.monitoring.kibana.listing.nameColumnTitle": "名前",
|
||||||
"xpack.monitoring.kibana.listing.requestsColumnTitle": "リクエスト",
|
"xpack.monitoring.kibana.listing.requestsColumnTitle": "リクエスト",
|
||||||
"xpack.monitoring.kibana.listing.responseTimeColumnTitle": "応答時間",
|
"xpack.monitoring.kibana.listing.responseTimeColumnTitle": "応答時間",
|
||||||
"xpack.monitoring.kibana.listing.statusColumnTitle": "ステータス",
|
|
||||||
"xpack.monitoring.kibana.overview.pageTitle": "Kibanaの概要",
|
"xpack.monitoring.kibana.overview.pageTitle": "Kibanaの概要",
|
||||||
"xpack.monitoring.kibana.overview.title": "Kibana",
|
"xpack.monitoring.kibana.overview.title": "Kibana",
|
||||||
"xpack.monitoring.kibana.shardActivity.bytesTitle": "バイト",
|
"xpack.monitoring.kibana.shardActivity.bytesTitle": "バイト",
|
||||||
|
|
|
@ -20679,7 +20679,6 @@
|
||||||
"xpack.monitoring.kibana.listing.nameColumnTitle": "名称",
|
"xpack.monitoring.kibana.listing.nameColumnTitle": "名称",
|
||||||
"xpack.monitoring.kibana.listing.requestsColumnTitle": "请求",
|
"xpack.monitoring.kibana.listing.requestsColumnTitle": "请求",
|
||||||
"xpack.monitoring.kibana.listing.responseTimeColumnTitle": "响应时间",
|
"xpack.monitoring.kibana.listing.responseTimeColumnTitle": "响应时间",
|
||||||
"xpack.monitoring.kibana.listing.statusColumnTitle": "状态",
|
|
||||||
"xpack.monitoring.kibana.overview.pageTitle": "Kibana 概览",
|
"xpack.monitoring.kibana.overview.pageTitle": "Kibana 概览",
|
||||||
"xpack.monitoring.kibana.overview.title": "Kibana",
|
"xpack.monitoring.kibana.overview.title": "Kibana",
|
||||||
"xpack.monitoring.kibana.shardActivity.bytesTitle": "字节",
|
"xpack.monitoring.kibana.shardActivity.bytesTitle": "字节",
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
},
|
},
|
||||||
"kibana": {
|
"kibana": {
|
||||||
"status": null,
|
"status": null,
|
||||||
|
"some_status_is_stale": true,
|
||||||
"requests_total": 0,
|
"requests_total": 0,
|
||||||
"concurrent_connections": 0,
|
"concurrent_connections": 0,
|
||||||
"response_time_max": 0,
|
"response_time_max": 0,
|
||||||
|
@ -170,6 +171,7 @@
|
||||||
},
|
},
|
||||||
"kibana": {
|
"kibana": {
|
||||||
"status": null,
|
"status": null,
|
||||||
|
"some_status_is_stale": true,
|
||||||
"requests_total": 0,
|
"requests_total": 0,
|
||||||
"concurrent_connections": 0,
|
"concurrent_connections": 0,
|
||||||
"response_time_max": 0,
|
"response_time_max": 0,
|
||||||
|
@ -283,6 +285,7 @@
|
||||||
},
|
},
|
||||||
"kibana": {
|
"kibana": {
|
||||||
"status": "green",
|
"status": "green",
|
||||||
|
"some_status_is_stale": true,
|
||||||
"requests_total": 571,
|
"requests_total": 571,
|
||||||
"concurrent_connections": 307,
|
"concurrent_connections": 307,
|
||||||
"response_time_max": 1930,
|
"response_time_max": 1930,
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
},
|
},
|
||||||
"kibana": {
|
"kibana": {
|
||||||
"status": "green",
|
"status": "green",
|
||||||
|
"some_status_is_stale": true,
|
||||||
"requests_total": 914,
|
"requests_total": 914,
|
||||||
"concurrent_connections": 646,
|
"concurrent_connections": 646,
|
||||||
"response_time_max": 2873,
|
"response_time_max": 2873,
|
||||||
|
|
|
@ -645,7 +645,8 @@
|
||||||
"transport_address": "tsullivan.local:5601",
|
"transport_address": "tsullivan.local:5601",
|
||||||
"uuid": "de3b8f2a-7bb9-4931-9bf3-997ba7824cf9",
|
"uuid": "de3b8f2a-7bb9-4931-9bf3-997ba7824cf9",
|
||||||
"version": "7.0.0-alpha1",
|
"version": "7.0.0-alpha1",
|
||||||
"availability": false,
|
"lastSeenTimestamp": "2017-08-29T17:25:43.192Z",
|
||||||
|
"statusIsStale": true,
|
||||||
"os_memory_free": 1645989888,
|
"os_memory_free": 1645989888,
|
||||||
"uptime": 200102
|
"uptime": 200102
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"de3b8f2a-7bb9-4931-9bf3-997ba7824cf9"
|
"de3b8f2a-7bb9-4931-9bf3-997ba7824cf9"
|
||||||
],
|
],
|
||||||
"status": "green",
|
"status": "green",
|
||||||
|
"some_status_is_stale": true,
|
||||||
"requests_total": 174,
|
"requests_total": 174,
|
||||||
"concurrent_connections": 174,
|
"concurrent_connections": 174,
|
||||||
"response_time_max": 2203,
|
"response_time_max": 2203,
|
||||||
|
@ -38,7 +39,7 @@
|
||||||
"uuid": "de3b8f2a-7bb9-4931-9bf3-997ba7824cf9",
|
"uuid": "de3b8f2a-7bb9-4931-9bf3-997ba7824cf9",
|
||||||
"status": "green"
|
"status": "green"
|
||||||
},
|
},
|
||||||
"availability": false
|
"statusIsStale": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"requests_total": 174,
|
"requests_total": 174,
|
||||||
"response_time_max": 2203,
|
"response_time_max": 2203,
|
||||||
"status": "green",
|
"status": "green",
|
||||||
|
"some_status_is_stale": true,
|
||||||
"uuids": [
|
"uuids": [
|
||||||
"de3b8f2a-7bb9-4931-9bf3-997ba7824cf9"
|
"de3b8f2a-7bb9-4931-9bf3-997ba7824cf9"
|
||||||
]
|
]
|
||||||
|
|
|
@ -34,7 +34,13 @@ export default function ({ getService }) {
|
||||||
.send({ timeRange })
|
.send({ timeRange })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
|
// Fixture is shared between internal and Metricbeat collection tests
|
||||||
|
// But timestamps of documents differ by a few miliseconds
|
||||||
|
const lastSeenTimestamp = body.kibanas[0].lastSeenTimestamp;
|
||||||
|
delete body.kibanas[0].lastSeenTimestamp;
|
||||||
|
|
||||||
expect(body).to.eql(listingFixture);
|
expect(body).to.eql(listingFixture);
|
||||||
|
expect(lastSeenTimestamp).to.eql('2017-08-29T17:25:47.825Z');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,13 @@ export default function ({ getService }) {
|
||||||
.send({ timeRange })
|
.send({ timeRange })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
|
// Fixture is shared between internal and Metricbeat collection tests
|
||||||
|
// But timestamps of documents differ by a few miliseconds
|
||||||
|
const lastSeenTimestamp = body.kibanas[0].lastSeenTimestamp;
|
||||||
|
delete body.kibanas[0].lastSeenTimestamp;
|
||||||
|
|
||||||
expect(body).to.eql(listingFixture);
|
expect(body).to.eql(listingFixture);
|
||||||
|
expect(lastSeenTimestamp).to.eql('2017-08-29T17:25:43.192Z');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
},
|
},
|
||||||
"kibana": {
|
"kibana": {
|
||||||
"status": "green",
|
"status": "green",
|
||||||
|
"some_status_is_stale": true,
|
||||||
"requests_total": 42,
|
"requests_total": 42,
|
||||||
"concurrent_connections": 0,
|
"concurrent_connections": 0,
|
||||||
"response_time_max": 864,
|
"response_time_max": 864,
|
||||||
|
@ -148,6 +149,7 @@
|
||||||
},
|
},
|
||||||
"kibana": {
|
"kibana": {
|
||||||
"status": null,
|
"status": null,
|
||||||
|
"some_status_is_stale": true,
|
||||||
"requests_total": 0,
|
"requests_total": 0,
|
||||||
"concurrent_connections": 0,
|
"concurrent_connections": 0,
|
||||||
"response_time_max": 0,
|
"response_time_max": 0,
|
||||||
|
|
|
@ -105,7 +105,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows kibana panel', async () => {
|
it('shows kibana panel', async () => {
|
||||||
expect(await overview.getKbnStatus()).to.be('Healthy');
|
expect(await overview.getKbnStatus()).to.be('Stale');
|
||||||
expect(await overview.getKbnRequests()).to.be('174');
|
expect(await overview.getKbnRequests()).to.be('174');
|
||||||
expect(await overview.getKbnMaxResponseTime()).to.be('2203 ms');
|
expect(await overview.getKbnMaxResponseTime()).to.be('2203 ms');
|
||||||
expect(await overview.getKbnInstances()).to.be('Instances: 1');
|
expect(await overview.getKbnInstances()).to.be('Instances: 1');
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
osFreeMemory: 'OS Free Memory\n1.5 GB',
|
osFreeMemory: 'OS Free Memory\n1.5 GB',
|
||||||
version: 'Version\n7.0.0-alpha1',
|
version: 'Version\n7.0.0-alpha1',
|
||||||
uptime: 'Uptime\n3 minutes',
|
uptime: 'Uptime\n3 minutes',
|
||||||
health: 'Health: green',
|
health: 'Last Reported Status\n Green\nStale',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
osFreeMemory: 'OS Free Memory\n1.5 GB',
|
osFreeMemory: 'OS Free Memory\n1.5 GB',
|
||||||
version: 'Version\n7.0.0-alpha1',
|
version: 'Version\n7.0.0-alpha1',
|
||||||
uptime: 'Uptime\n3 minutes',
|
uptime: 'Uptime\n3 minutes',
|
||||||
health: 'Health: green',
|
health: 'Last Reported Status\n Green\nStale',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
requests: 'Requests\n174',
|
requests: 'Requests\n174',
|
||||||
connections: 'Connections\n174',
|
connections: 'Connections\n174',
|
||||||
maxResponseTime: 'Max. Response Time\n2203 ms',
|
maxResponseTime: 'Max. Response Time\n2203 ms',
|
||||||
health: 'Health: green',
|
health: 'Status\nStale',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
requests: 'Requests\n174',
|
requests: 'Requests\n174',
|
||||||
connections: 'Connections\n174',
|
connections: 'Connections\n174',
|
||||||
maxResponseTime: 'Max. Response Time\n2203 ms',
|
maxResponseTime: 'Max. Response Time\n2203 ms',
|
||||||
health: 'Health: green',
|
health: 'Status\nStale',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
requests: 'Requests\n174',
|
requests: 'Requests\n174',
|
||||||
connections: 'Connections\n174',
|
connections: 'Connections\n174',
|
||||||
maxResponseTime: 'Max. Response Time\n2203 ms',
|
maxResponseTime: 'Max. Response Time\n2203 ms',
|
||||||
health: 'Health: green',
|
health: 'Status\nStale\nView all instances',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,7 +44,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
requests: 'Requests\n174',
|
requests: 'Requests\n174',
|
||||||
connections: 'Connections\n174',
|
connections: 'Connections\n174',
|
||||||
maxResponseTime: 'Max. Response Time\n2203 ms',
|
maxResponseTime: 'Max. Response Time\n2203 ms',
|
||||||
health: 'Health: green',
|
health: 'Status\nStale\nView all instances',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,7 @@ export function MonitoringClusterOverviewProvider({ getService }) {
|
||||||
const SUBJ_ES_ML_JOBS = `${SUBJ_ES_PANEL} > esMlJobs`;
|
const SUBJ_ES_ML_JOBS = `${SUBJ_ES_PANEL} > esMlJobs`;
|
||||||
|
|
||||||
const SUBJ_KBN_PANEL = `${SUBJ_CLUSTER_ITEM_CONTAINER_PREFIX}Kibana`;
|
const SUBJ_KBN_PANEL = `${SUBJ_CLUSTER_ITEM_CONTAINER_PREFIX}Kibana`;
|
||||||
const SUBJ_KBN_STATUS = `${SUBJ_KBN_PANEL} > statusIcon`;
|
const SUBJ_KBN_STATUS = `${SUBJ_KBN_PANEL} > status`;
|
||||||
const SUBJ_KBN_REQUESTS = `${SUBJ_KBN_PANEL} > kbnRequests`;
|
const SUBJ_KBN_REQUESTS = `${SUBJ_KBN_PANEL} > kbnRequests`;
|
||||||
const SUBJ_KBN_MAX_RESPONSE_TIME = `${SUBJ_KBN_PANEL} > kbnMaxResponseTime`;
|
const SUBJ_KBN_MAX_RESPONSE_TIME = `${SUBJ_KBN_PANEL} > kbnMaxResponseTime`;
|
||||||
const SUBJ_KBN_CONNECTIONS = `${SUBJ_KBN_PANEL} > kbnConnections`;
|
const SUBJ_KBN_CONNECTIONS = `${SUBJ_KBN_PANEL} > kbnConnections`;
|
||||||
|
|
|
@ -16,7 +16,7 @@ export function MonitoringKibanaInstanceProvider({ getService }) {
|
||||||
const SUBJ_SUMMARY_OS_FREE_MEMORY = `${SUBJ_SUMMARY} > osFreeMemory`;
|
const SUBJ_SUMMARY_OS_FREE_MEMORY = `${SUBJ_SUMMARY} > osFreeMemory`;
|
||||||
const SUBJ_SUMMARY_VERSION = `${SUBJ_SUMMARY} > version`;
|
const SUBJ_SUMMARY_VERSION = `${SUBJ_SUMMARY} > version`;
|
||||||
const SUBJ_SUMMARY_UPTIME = `${SUBJ_SUMMARY} > uptime`;
|
const SUBJ_SUMMARY_UPTIME = `${SUBJ_SUMMARY} > uptime`;
|
||||||
const SUBJ_SUMMARY_HEALTH = `${SUBJ_SUMMARY} > statusIcon`;
|
const SUBJ_SUMMARY_HEALTH = `${SUBJ_SUMMARY} > status`;
|
||||||
|
|
||||||
return new (class KibanaInstance {
|
return new (class KibanaInstance {
|
||||||
async isOnInstance() {
|
async isOnInstance() {
|
||||||
|
@ -30,7 +30,7 @@ export function MonitoringKibanaInstanceProvider({ getService }) {
|
||||||
osFreeMemory: await testSubjects.getVisibleText(SUBJ_SUMMARY_OS_FREE_MEMORY),
|
osFreeMemory: await testSubjects.getVisibleText(SUBJ_SUMMARY_OS_FREE_MEMORY),
|
||||||
version: await testSubjects.getVisibleText(SUBJ_SUMMARY_VERSION),
|
version: await testSubjects.getVisibleText(SUBJ_SUMMARY_VERSION),
|
||||||
uptime: await testSubjects.getVisibleText(SUBJ_SUMMARY_UPTIME),
|
uptime: await testSubjects.getVisibleText(SUBJ_SUMMARY_UPTIME),
|
||||||
health: await testSubjects.getAttribute(SUBJ_SUMMARY_HEALTH, 'alt'),
|
health: await testSubjects.getVisibleText(SUBJ_SUMMARY_HEALTH),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function MonitoringKibanaSummaryStatusProvider({ getService }) {
|
||||||
const SUBJ_SUMMARY_REQUESTS = `${SUBJ_SUMMARY} > requests`;
|
const SUBJ_SUMMARY_REQUESTS = `${SUBJ_SUMMARY} > requests`;
|
||||||
const SUBJ_SUMMARY_CONNECTIONS = `${SUBJ_SUMMARY} > connections`;
|
const SUBJ_SUMMARY_CONNECTIONS = `${SUBJ_SUMMARY} > connections`;
|
||||||
const SUBJ_SUMMARY_MAX_RESPONSE_TIME = `${SUBJ_SUMMARY} > maxResponseTime`;
|
const SUBJ_SUMMARY_MAX_RESPONSE_TIME = `${SUBJ_SUMMARY} > maxResponseTime`;
|
||||||
const SUBJ_SUMMARY_HEALTH = `${SUBJ_SUMMARY} > statusIcon`;
|
const SUBJ_SUMMARY_HEALTH = `${SUBJ_SUMMARY} > status`;
|
||||||
|
|
||||||
return new (class KibanaSummaryStatus {
|
return new (class KibanaSummaryStatus {
|
||||||
async getContent() {
|
async getContent() {
|
||||||
|
@ -24,7 +24,7 @@ export function MonitoringKibanaSummaryStatusProvider({ getService }) {
|
||||||
requests: await testSubjects.getVisibleText(SUBJ_SUMMARY_REQUESTS),
|
requests: await testSubjects.getVisibleText(SUBJ_SUMMARY_REQUESTS),
|
||||||
connections: await testSubjects.getVisibleText(SUBJ_SUMMARY_CONNECTIONS),
|
connections: await testSubjects.getVisibleText(SUBJ_SUMMARY_CONNECTIONS),
|
||||||
maxResponseTime: await testSubjects.getVisibleText(SUBJ_SUMMARY_MAX_RESPONSE_TIME),
|
maxResponseTime: await testSubjects.getVisibleText(SUBJ_SUMMARY_MAX_RESPONSE_TIME),
|
||||||
health: await testSubjects.getAttribute(SUBJ_SUMMARY_HEALTH, 'alt'),
|
health: await testSubjects.getVisibleText(SUBJ_SUMMARY_HEALTH),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue