[7.x] [Uptime] Support location name in UI (#36317) (#36730)

Adds support for a user-configured location name, so users can more easily differentiate their monitors if they're watching hosts across multiple locations.
This commit is contained in:
Andrew Cholakian 2019-05-20 21:01:37 -05:00 committed by GitHub
parent 680f52af83
commit 09102cd17e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 907 additions and 246 deletions

View file

@ -15,31 +15,31 @@
"args": [
{
"name": "sort",
"description": "",
"description": "Optional: the direction to sort by. Accepts 'asc' and 'desc'. Defaults to 'desc'.",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
},
{
"name": "size",
"description": "",
"description": "Optional: the number of results to return.",
"type": { "kind": "SCALAR", "name": "Int", "ofType": null },
"defaultValue": null
},
{
"name": "monitorId",
"description": "",
"description": "Optional: the monitor ID filter.",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
},
{
"name": "status",
"description": "",
"description": "Optional: the check status to filter by.",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
},
{
"name": "dateRangeStart",
"description": "",
"description": "The lower limit of the date range.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -49,13 +49,19 @@
},
{
"name": "dateRangeEnd",
"description": "",
"description": "The upper limit of the date range.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"defaultValue": null
},
{
"name": "location",
"description": "Optional: agent location to filter by.",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
}
],
"type": {
@ -181,6 +187,12 @@
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"defaultValue": null
},
{
"name": "location",
"description": "",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
}
],
"type": { "kind": "OBJECT", "name": "MonitorChart", "ofType": null },
@ -189,11 +201,11 @@
},
{
"name": "getLatestMonitors",
"description": "",
"description": "Fetch the most recent event data for a monitor ID, date range, location.",
"args": [
{
"name": "dateRangeStart",
"description": "",
"description": "The lower limit of the date range.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -203,7 +215,7 @@
},
{
"name": "dateRangeEnd",
"description": "",
"description": "The upper limit of the date range.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -213,7 +225,13 @@
},
{
"name": "monitorId",
"description": "",
"description": "Optional: a specific monitor ID filter.",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
},
{
"name": "location",
"description": "Optional: a specific instance location filter.",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
}
@ -517,6 +535,14 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "observer",
"description": "",
"args": [],
"type": { "kind": "OBJECT", "name": "Observer", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "resolve",
"description": "",
@ -1351,6 +1377,100 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Observer",
"description": "Metadata added by a proccessor, which is specified in its configuration.",
"fields": [
{
"name": "geo",
"description": "Geolocation data for the agent.",
"args": [],
"type": { "kind": "OBJECT", "name": "Geo", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Geo",
"description": "Geolocation data added via processors to enrich events.",
"fields": [
{
"name": "city_name",
"description": "Name of the city in which the agent is running.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "continent_name",
"description": "The name of the continent on which the agent is running.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "country_iso_code",
"description": "ISO designation for the agent's country.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "country_name",
"description": "The name of the agent's country.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "location",
"description": "The lat/long of the agent.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "A name for the host's location, e.g. 'us-east-1' or 'LAX'.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "region_iso_code",
"description": "ISO designation of the agent's region.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "region_name",
"description": "Name of the region hosting the agent.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Resolve",
@ -2102,11 +2222,11 @@
{
"kind": "OBJECT",
"name": "FilterBar",
"description": "",
"description": "The data used to enrich the filter bar.",
"fields": [
{
"name": "ids",
"description": "",
"description": "A series of monitor IDs in the heartbeat indices.",
"args": [],
"type": {
"kind": "LIST",
@ -2120,9 +2240,25 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "locations",
"description": "The location values users have configured for the agents.",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "names",
"description": "",
"description": "The names users have configured for the monitors.",
"args": [],
"type": {
"kind": "LIST",
@ -2138,7 +2274,7 @@
},
{
"name": "ports",
"description": "",
"description": "The ports of the monitored endpoints.",
"args": [],
"type": {
"kind": "LIST",
@ -2154,7 +2290,7 @@
},
{
"name": "schemes",
"description": "",
"description": "The schemes used by the monitors.",
"args": [],
"type": {
"kind": "LIST",
@ -2170,7 +2306,7 @@
},
{
"name": "statuses",
"description": "",
"description": "The possible status values contained in the indices.",
"args": [],
"type": {
"kind": "LIST",
@ -2193,11 +2329,31 @@
{
"kind": "OBJECT",
"name": "ErrorListItem",
"description": "",
"description": "A representation of an error state for a monitor.",
"fields": [
{
"name": "count",
"description": "The number of times this error has occurred.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "Int", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "latestMessage",
"description": "",
"description": "The most recent message associated with this error type.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "location",
"description": "The location assigned to the agent reporting this error.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
@ -2205,43 +2361,7 @@
},
{
"name": "monitorId",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "count",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "Int", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "statusCode",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "timestamp",
"description": "",
"description": "The ID of the monitor reporting the error.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
@ -2249,11 +2369,39 @@
},
{
"name": "name",
"description": "",
"description": "The name configured for the monitor by the user.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "statusCode",
"description": "The status code, if available, of the error request.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "timestamp",
"description": "When the most recent error state occurred.",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "What kind of error the monitor reported.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,

View file

@ -30,7 +30,7 @@ export interface Query {
getSnapshot?: Snapshot | null;
getMonitorChartsData?: MonitorChart | null;
/** Fetch the most recent event data for a monitor ID, date range, location. */
getLatestMonitors: Ping[];
getFilterBar?: FilterBar | null;
@ -74,6 +74,8 @@ export interface Ping {
monitor?: Monitor | null;
observer?: Observer | null;
resolve?: Resolve | null;
socks5?: Socks5 | null;
@ -266,6 +268,30 @@ export interface Monitor {
check_group?: string | null;
}
/** Metadata added by a proccessor, which is specified in its configuration. */
export interface Observer {
/** Geolocation data for the agent. */
geo?: Geo | null;
}
/** Geolocation data added via processors to enrich events. */
export interface Geo {
/** Name of the city in which the agent is running. */
city_name?: string | null;
/** The name of the continent on which the agent is running. */
continent_name?: string | null;
/** ISO designation for the agent's country. */
country_iso_code?: string | null;
/** The name of the agent's country. */
country_name?: string | null;
/** The lat/long of the agent. */
location?: string | null;
/** A name for the host's location, e.g. 'us-east-1' or 'LAX'. */
name?: string | null;
/** ISO designation of the agent's region. */
region_iso_code?: string | null;
/** Name of the region hosting the agent. */
region_name?: string | null;
}
export interface Resolve {
host?: string | null;
@ -415,33 +441,39 @@ export interface StatusData {
/** The total down counts for this point. */
total?: number | null;
}
/** The data used to enrich the filter bar. */
export interface FilterBar {
/** A series of monitor IDs in the heartbeat indices. */
ids?: MonitorKey[] | null;
/** The location values users have configured for the agents. */
locations?: string[] | null;
/** The names users have configured for the monitors. */
names?: string[] | null;
/** The ports of the monitored endpoints. */
ports?: number[] | null;
/** The schemes used by the monitors. */
schemes?: string[] | null;
/** The possible status values contained in the indices. */
statuses?: string[] | null;
}
/** A representation of an error state for a monitor. */
export interface ErrorListItem {
/** The number of times this error has occurred. */
count: number;
/** The most recent message associated with this error type. */
latestMessage?: string | null;
/** The location assigned to the agent reporting this error. */
location?: string | null;
/** The ID of the monitor reporting the error. */
monitorId?: string | null;
type: string;
count?: number | null;
statusCode?: string | null;
timestamp?: string | null;
/** The name configured for the monitor by the user. */
name?: string | null;
/** The status code, if available, of the error request. */
statusCode?: string | null;
/** When the most recent error state occurred. */
timestamp?: string | null;
/** What kind of error the monitor reported. */
type: string;
}
export interface MonitorPageTitle {
@ -463,17 +495,20 @@ export interface DataPoint {
// ====================================================
export interface AllPingsQueryArgs {
/** Optional: the direction to sort by. Accepts 'asc' and 'desc'. Defaults to 'desc'. */
sort?: string | null;
/** Optional: the number of results to return. */
size?: number | null;
/** Optional: the monitor ID filter. */
monitorId?: string | null;
/** Optional: the check status to filter by. */
status?: string | null;
/** The lower limit of the date range. */
dateRangeStart: string;
/** The upper limit of the date range. */
dateRangeEnd: string;
/** Optional: agent location to filter by. */
location?: string | null;
}
export interface GetMonitorsQueryArgs {
dateRangeStart: string;
@ -495,13 +530,18 @@ export interface GetMonitorChartsDataQueryArgs {
dateRangeStart: string;
dateRangeEnd: string;
location?: string | null;
}
export interface GetLatestMonitorsQueryArgs {
/** The lower limit of the date range. */
dateRangeStart: string;
/** The upper limit of the date range. */
dateRangeEnd: string;
/** Optional: a specific monitor ID filter. */
monitorId?: string | null;
/** Optional: a specific instance location filter. */
location?: string | null;
}
export interface GetFilterBarQueryArgs {
dateRangeStart: string;

View file

@ -34,7 +34,12 @@ exports[`ErrorList component renders the error list without errors 1`] = `
"field": "monitorId",
"name": "Monitor ID",
"render": [Function],
"width": "25%",
"width": "12.5%",
},
Object {
"field": "location",
"name": "Location",
"width": "12.5%",
},
Object {
"field": "statusCode",

View file

@ -27,6 +27,12 @@ exports[`FilterBar component renders the component without errors 1`] = `
],
"type": "field_value_toggle_group",
},
Object {
"field": "observer.geo.name",
"name": "Location",
"options": Array [],
"type": "field_value_selection",
},
Object {
"field": "monitor.id",
"multiSelect": false,

View file

@ -34,6 +34,11 @@ exports[`MonitorList component renders a monitor list without errors 1`] = `
"name": "ID",
"render": [Function],
},
Object {
"field": "ping.observer.geo.name",
"name": "Location",
"render": [Function],
},
Object {
"field": "ping.url.full",
"name": "URL",

View file

@ -8,7 +8,6 @@ import {
EuiBadge,
EuiCodeBlock,
EuiInMemoryTable,
EuiLink,
EuiPanel,
EuiText,
EuiTextColor,
@ -18,10 +17,10 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment';
import React from 'react';
import { Link } from 'react-router-dom';
import { ErrorListItem, Ping } from '../../../common/graphql/types';
import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../higher_order';
import { errorListQuery } from '../../queries';
import { MonitorPageLink } from './monitor_page_link';
interface ErrorListProps {
linkParameters?: string;
@ -72,12 +71,25 @@ export const ErrorListComponent = ({ data, linkParameters, loading }: Props) =>
name: i18n.translate('xpack.uptime.errorList.monitorIdColumnLabel', {
defaultMessage: 'Monitor ID',
}),
render: (id: string, { name }: ErrorListItem) => (
<EuiLink>
<Link to={`/monitor/${id}${linkParameters}`}>{name || id}</Link>
</EuiLink>
render: (id: string, { name, location }: ErrorListItem) => (
<MonitorPageLink
id={id}
location={location || undefined}
linkParameters={linkParameters}
>
{name || id}
</MonitorPageLink>
),
width: '25%',
width: '12.5%',
},
{
field: 'location',
name: i18n.translate('xpack.uptime.errorList.location', {
defaultMessage: 'Location',
description:
"The heading of a column that displays the location of a Heartbeat instance's host machine.",
}),
width: '12.5%',
},
{
field: 'statusCode',

View file

@ -33,7 +33,7 @@ export const FilterBarComponent = ({ currentQuery, data, updateQuery }: Props) =
return <FilterBarLoading />;
}
const {
filterBar: { ids, names, ports, schemes },
filterBar: { ids, locations, names, ports, schemes },
} = data;
// TODO: add a factory function + type for these filter options
const filters = [
@ -55,7 +55,16 @@ export const FilterBarComponent = ({ currentQuery, data, updateQuery }: Props) =
},
],
},
// TODO: add health to this select
{
type: 'field_value_selection',
field: 'observer.geo.name',
name: i18n.translate('xpack.uptime.filterBar.options.location.name', {
defaultMessage: 'Location',
description:
'A label applied to a button that lets users filter monitors by their location.',
}),
options: locations ? locations.map(location => ({ value: location, view: location })) : [],
},
{
type: 'field_value_selection',
field: 'monitor.id',

View file

@ -12,6 +12,7 @@ export { FilterBarLoading } from './filter_bar_loading';
export { IntegrationLink } from './integration_link';
export { MonitorCharts } from './monitor_charts';
export { MonitorList } from './monitor_list';
export { MonitorPageLink } from './monitor_page_link';
export { MonitorPageTitle } from './monitor_page_title';
export { MonitorStatusBar } from './monitor_status_bar';
export { PingList } from './ping_list';

View file

@ -26,12 +26,12 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { get } from 'lodash';
import moment from 'moment';
import React from 'react';
import { Link } from 'react-router-dom';
import { LatestMonitor, MonitorSeriesPoint, Ping } from '../../../common/graphql/types';
import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../higher_order';
import { monitorListQuery } from '../../queries';
import { MonitorSparkline } from './monitor_sparkline';
import { MonitorListActionsPopover } from './monitor_list_actions_popover';
import { MonitorPageLink } from './monitor_page_link';
interface MonitorListQueryResult {
// TODO: clean up this ugly result data shape, there should be no nesting
@ -111,18 +111,41 @@ export const MonitorListComponent = ({
defaultMessage: 'ID',
}),
render: (id: string, monitor: LatestMonitor) => (
<EuiLink>
<Link
data-test-subj={`monitor-page-link-${id}`}
to={`/monitor/${id}${linkParameters}`}
>
{monitor.ping && monitor.ping.monitor && monitor.ping.monitor.name
? monitor.ping.monitor.name
: id}
</Link>
</EuiLink>
<MonitorPageLink
id={id}
location={get<string | undefined>(monitor, 'ping.observer.geo.name')}
linkParameters={linkParameters}
>
{monitor.ping && monitor.ping.monitor && monitor.ping.monitor.name
? monitor.ping.monitor.name
: id}
</MonitorPageLink>
),
},
{
field: 'ping.observer.geo.name',
name: i18n.translate('xpack.uptime.monitorList.geoName', {
defaultMessage: 'Location',
description: 'Users can specify a name for a location',
}),
render: (locationName: string | null | undefined) =>
!!locationName ? (
locationName
) : (
<EuiLink
href="https://www.elastic.co/guide/en/beats/heartbeat/current/add-host-metadata.html#add-host-metadata"
target="_blank"
>
{i18n.translate('xpack.uptime.monitorList.geoName.helpLinkAnnotation', {
defaultMessage: 'Add location',
description:
'Text that instructs the user to navigate to our docs to add a geographic location to their data',
})}
&nbsp;
<EuiIcon size="s" type="popout" />
</EuiLink>
),
},
{
field: 'ping.url.full',
name: i18n.translate('xpack.uptime.monitorList.urlColumnLabel', {

View file

@ -0,0 +1,35 @@
/*
* 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 { EuiLink } from '@elastic/eui';
import { Link } from 'react-router-dom';
import React, { FunctionComponent } from 'react';
interface DetailPageLinkProps {
id: string;
location: string | undefined;
linkParameters: string | undefined;
}
export const MonitorPageLink: FunctionComponent<DetailPageLinkProps> = ({
children,
id,
location,
linkParameters,
}) => (
<EuiLink>
<Link
data-test-subj={`monitor-page-link-${id}`}
to={
location === undefined
? `/monitor/${id}${linkParameters}`
: `/monitor/${id}/${encodeURI(location)}/${linkParameters}`
}
>
{children}
</Link>
</EuiLink>
);

View file

@ -28,10 +28,11 @@ type Props = MonitorStatusBarProps & UptimeGraphQLQueryProps<MonitorStatusBarQue
export const MonitorStatusBarComponent = ({ data, monitorId }: Props) => {
if (data && data.monitorStatus && data.monitorStatus.length) {
const { monitor, timestamp } = data.monitorStatus[0];
const { monitor, observer, timestamp } = data.monitorStatus[0];
const duration = get(monitor, 'duration.us', undefined);
const status = get<'up' | 'down'>(monitor, 'status', 'down');
const full = get(data.monitorStatus[0], 'url.full');
const location = get(observer, 'geo.name');
return (
<EuiPanel>
<EuiFlexGroup gutterSize="l">
@ -97,6 +98,16 @@ export const MonitorStatusBarComponent = ({ data, monitorId }: Props) => {
>
{moment(new Date(timestamp).valueOf()).fromNow()}
</EuiFlexItem>
{!!location && (
<EuiFlexItem
aria-label={i18n.translate('xpack.uptime.monitorStatusBar.locationName', {
defaultMessage: 'Location',
})}
grow={false}
>
{location}
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiPanel>
);

View file

@ -37,7 +37,11 @@ interface MonitorPageProps {
}
export const MonitorPage = ({ history, location, query, setBreadcrumbs }: MonitorPageProps) => {
const [monitorId] = useState<string>(location.pathname.replace(/^(\/monitor\/)/, ''));
const parsedPath = location.pathname.replace(/^(\/monitor\/)/, '').split('/');
const [monitorId] = useState<string>(decodeURI(parsedPath[0]));
const [geoLocation] = useState<string | undefined>(
parsedPath[1] ? decodeURI(parsedPath[1]) : undefined
);
const { colors, refreshApp, setHeadingText } = useContext(UptimeSettingsContext);
const [params, updateUrlParams] = useUrlParams(history, location);
const { dateRangeStart, dateRangeEnd, selectedPingStatus } = params;
@ -66,16 +70,14 @@ export const MonitorPage = ({ history, location, query, setBreadcrumbs }: Monito
},
[params]
);
const sharedVariables = { dateRangeStart, dateRangeEnd, location: geoLocation, monitorId };
return (
<Fragment>
<MonitorPageTitle monitorId={monitorId} variables={{ monitorId }} />
<EuiSpacer size="s" />
<MonitorStatusBar
monitorId={monitorId}
variables={{ dateRangeStart, dateRangeEnd, monitorId }}
/>
<MonitorStatusBar monitorId={monitorId} variables={sharedVariables} />
<EuiSpacer size="s" />
<MonitorCharts {...colors} variables={{ dateRangeStart, dateRangeEnd, monitorId }} />
<MonitorCharts {...colors} variables={sharedVariables} />
<EuiSpacer size="s" />
<PingList
onSelectedStatusUpdate={(selectedStatus: string | null) =>
@ -84,9 +86,7 @@ export const MonitorPage = ({ history, location, query, setBreadcrumbs }: Monito
onUpdateApp={refreshApp}
selectedOption={selectedPingStatus}
variables={{
dateRangeStart,
dateRangeEnd,
monitorId,
...sharedVariables,
status: selectedPingStatus,
}}
/>

View file

@ -13,13 +13,14 @@ query ErrorList($dateRangeStart: String!, $dateRangeEnd: String!, $filters: Stri
dateRangeEnd: $dateRangeEnd
filters: $filters
) {
latestMessage
monitorId
type
count
latestMessage
location
monitorId
name
statusCode
timestamp
name
type
}
}
`;

View file

@ -9,15 +9,16 @@ import gql from 'graphql-tag';
export const filterBarQueryString = `
query FilterBar($dateRangeStart: String!, $dateRangeEnd: String!) {
filterBar: getFilterBar(dateRangeStart: $dateRangeStart, dateRangeEnd: $dateRangeEnd) {
ports
ids {
key
url
}
names
schemes
ids {
key
url
}
locations
names
ports
schemes
}
}
`;
export const filterBarQuery = gql`

View file

@ -7,11 +7,12 @@
import gql from 'graphql-tag';
export const monitorChartsQueryString = `
query MonitorCharts($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId: String!) {
query MonitorCharts($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId: String!, $location: String) {
monitorChartsData: getMonitorChartsData(
monitorId: $monitorId
dateRangeStart: $dateRangeStart
dateRangeEnd: $dateRangeEnd
location: $location
) {
durationArea {
x

View file

@ -37,6 +37,12 @@ export const monitorListQueryString = `
name
status
}
observer {
geo {
location
name
}
}
url {
domain
full

View file

@ -7,11 +7,12 @@
import gql from 'graphql-tag';
export const monitorStatusBarQueryString = `
query MonitorStatus($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId: String) {
query MonitorStatus($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId: String, $location: String) {
monitorStatus: getLatestMonitors(
dateRangeStart: $dateRangeStart
dateRangeEnd: $dateRangeEnd
monitorId: $monitorId
location: $location
) {
timestamp
millisFromNow
@ -21,6 +22,11 @@ query MonitorStatus($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId
us
}
}
observer {
geo {
name
}
}
url {
full
}

View file

@ -14,6 +14,7 @@ query PingList(
$status: String
$sort: String
$size: Int
$location: String
) {
allPings(
dateRangeStart: $dateRangeStart
@ -22,6 +23,7 @@ query PingList(
status: $status
sort: $sort
size: $size
location: $location
) {
total
pings {

View file

@ -162,7 +162,7 @@ const Application = (props: UptimeAppProps) => {
)}
/>
<Route
path="/monitor/:id"
path="/monitor/:id/:location?"
render={routerProps => (
<MonitorPage
query={client.query}

View file

@ -112,27 +112,39 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = (
},
async getMonitorChartsData(
resolver,
{ monitorId, dateRangeStart, dateRangeEnd },
{ monitorId, dateRangeStart, dateRangeEnd, location },
{ req }
): Promise<MonitorChart> {
return await libs.monitors.getMonitorChartsData(req, monitorId, dateRangeStart, dateRangeEnd);
return await libs.monitors.getMonitorChartsData(
req,
monitorId,
dateRangeStart,
dateRangeEnd,
location
);
},
async getLatestMonitors(
resolver,
{ dateRangeStart, dateRangeEnd, monitorId },
{ dateRangeStart, dateRangeEnd, monitorId, location },
{ req }
): Promise<Ping[]> {
return libs.pings.getLatestMonitorDocs(req, dateRangeStart, dateRangeEnd, monitorId);
return await libs.pings.getLatestMonitorDocs(
req,
dateRangeStart,
dateRangeEnd,
monitorId,
location
);
},
async getFilterBar(resolver, { dateRangeStart, dateRangeEnd }, { req }): Promise<FilterBar> {
return libs.monitors.getFilterBar(req, dateRangeStart, dateRangeEnd);
return await libs.monitors.getFilterBar(req, dateRangeStart, dateRangeEnd);
},
async getErrorsList(
resolver,
{ dateRangeStart, dateRangeEnd, filters },
{ req }
): Promise<any> {
return libs.monitors.getErrorsList(req, dateRangeStart, dateRangeEnd, filters);
return await libs.monitors.getErrorsList(req, dateRangeStart, dateRangeEnd, filters);
},
async getMonitorPageTitle(
resolver: any,

View file

@ -7,11 +7,19 @@
import gql from 'graphql-tag';
export const monitorsSchema = gql`
"The data used to enrich the filter bar."
type FilterBar {
"A series of monitor IDs in the heartbeat indices."
ids: [MonitorKey!]
"The location values users have configured for the agents."
locations: [String!]
"The names users have configured for the monitors."
names: [String!]
"The ports of the monitored endpoints."
ports: [Int!]
"The schemes used by the monitors."
schemes: [String!]
"The possible status values contained in the indices."
statuses: [String!]
}
@ -105,14 +113,24 @@ export const monitorsSchema = gql`
monitors: [LatestMonitor!]
}
"A representation of an error state for a monitor."
type ErrorListItem {
"The number of times this error has occurred."
count: Int!
"The most recent message associated with this error type."
latestMessage: String
"The location assigned to the agent reporting this error."
location: String
"The ID of the monitor reporting the error."
monitorId: String
type: String!
count: Int
statusCode: String
timestamp: String
"The name configured for the monitor by the user."
name: String
"The status code, if available, of the error request."
statusCode: String
"When the most recent error state occurred."
timestamp: String
"What kind of error the monitor reported."
type: String!
}
type MonitorPageTitle {
@ -134,9 +152,20 @@ export const monitorsSchema = gql`
monitorId: String!
dateRangeStart: String!
dateRangeEnd: String!
location: String
): MonitorChart
getLatestMonitors(dateRangeStart: String!, dateRangeEnd: String!, monitorId: String): [Ping!]!
"Fetch the most recent event data for a monitor ID, date range, location."
getLatestMonitors(
"The lower limit of the date range."
dateRangeStart: String!
"The upper limit of the date range."
dateRangeEnd: String!
"Optional: a specific monitor ID filter."
monitorId: String
"Optional: a specific instance location filter."
location: String
): [Ping!]!
getFilterBar(dateRangeStart: String!, dateRangeEnd: String!): FilterBar

View file

@ -35,10 +35,19 @@ export const createPingsResolvers: CreateUMGraphQLResolvers = (
Query: {
async allPings(
resolver,
{ monitorId, sort, size, status, dateRangeStart, dateRangeEnd },
{ monitorId, sort, size, status, dateRangeStart, dateRangeEnd, location },
{ req }
): Promise<PingResults> {
return libs.pings.getAll(req, dateRangeStart, dateRangeEnd, monitorId, status, sort, size);
return await libs.pings.getAll(
req,
dateRangeStart,
dateRangeEnd,
monitorId,
status,
sort,
size,
location
);
},
async getDocCount(resolver, args, { req }): Promise<DocCount> {
return libs.pings.getDocCount(req);

View file

@ -19,12 +19,20 @@ export const pingsSchema = gql`
type Query {
"Get a list of all recorded pings for all monitors"
allPings(
"Optional: the direction to sort by. Accepts 'asc' and 'desc'. Defaults to 'desc'."
sort: String
"Optional: the number of results to return."
size: Int
"Optional: the monitor ID filter."
monitorId: String
"Optional: the check status to filter by."
status: String
"The lower limit of the date range."
dateRangeStart: String!
"The upper limit of the date range."
dateRangeEnd: String!
"Optional: agent location to filter by."
location: String
): PingResults!
"Gets the number of documents in the target index"
@ -89,6 +97,26 @@ export const pingsSchema = gql`
build: String
}
"Geolocation data added via processors to enrich events."
type Geo {
"Name of the city in which the agent is running."
city_name: String
"The name of the continent on which the agent is running."
continent_name: String
"ISO designation for the agent's country."
country_iso_code: String
"The name of the agent's country."
country_name: String
"The lat/long of the agent."
location: String
"A name for the host's location, e.g. 'us-east-1' or 'LAX'."
name: String
"ISO designation of the agent's region."
region_iso_code: String
"Name of the region hosting the agent."
region_name: String
}
type Host {
architecture: String
id: String
@ -172,6 +200,12 @@ export const pingsSchema = gql`
check_group: String
}
"Metadata added by a proccessor, which is specified in its configuration."
type Observer {
"Geolocation data for the agent."
geo: Geo
}
type Resolve {
host: String
ip: String
@ -232,6 +266,7 @@ export const pingsSchema = gql`
kubernetes: Kubernetes
meta: Meta
monitor: Monitor
observer: Observer
resolve: Resolve
socks5: Socks5
summary: Summary

View file

@ -1,5 +1,116 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ElasticsearchMonitorsAdapter getMonitorChartsData will provide expected filters when a location is specified 1`] = `
Array [
Object {},
Object {
"body": Object {
"aggs": Object {
"timeseries": Object {
"aggs": Object {
"duration": Object {
"stats": Object {
"field": "monitor.duration.us",
},
},
"status": Object {
"terms": Object {
"field": "monitor.status",
"shard_size": 2,
"size": 2,
},
},
},
"date_histogram": Object {
"field": "@timestamp",
"interval": "36000ms",
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-15m",
"lte": "now",
},
},
},
Object {
"term": Object {
"monitor.id": "fooID",
},
},
Object {
"term": Object {
"observer.geo.name": "Philadelphia",
},
},
],
},
},
"size": 0,
},
"index": "heartbeat*",
},
]
`;
exports[`ElasticsearchMonitorsAdapter getMonitorChartsData will run expected parameters when no location is specified 1`] = `
Array [
Object {},
Object {
"body": Object {
"aggs": Object {
"timeseries": Object {
"aggs": Object {
"duration": Object {
"stats": Object {
"field": "monitor.duration.us",
},
},
"status": Object {
"terms": Object {
"field": "monitor.status",
"shard_size": 2,
"size": 2,
},
},
},
"date_histogram": Object {
"field": "@timestamp",
"interval": "36000ms",
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-15m",
"lte": "now",
},
},
},
Object {
"term": Object {
"monitor.id": "fooID",
},
},
],
},
},
"size": 0,
},
"index": "heartbeat*",
},
]
`;
exports[`ElasticsearchMonitorsAdapter will return kubernetes information if contained in hits 1`] = `
Object {
"downSeries": Array [],

View file

@ -62,4 +62,32 @@ describe('ElasticsearchMonitorsAdapter', () => {
expect(result).toHaveLength(1);
expect(result[0]).toMatchSnapshot();
});
it('getMonitorChartsData will run expected parameters when no location is specified', async () => {
expect.assertions(2);
const searchMock = jest.fn();
const search = searchMock.bind({});
const database = {
search,
count: async (request: any, params: any) => null,
};
const adapter = new ElasticsearchMonitorsAdapter(database);
await adapter.getMonitorChartsData({}, 'fooID', 'now-15m', 'now');
expect(searchMock).toHaveBeenCalledTimes(1);
expect(searchMock.mock.calls[0]).toMatchSnapshot();
});
it('getMonitorChartsData will provide expected filters when a location is specified', async () => {
expect.assertions(2);
const searchMock = jest.fn();
const search = searchMock.bind({});
const database = {
search,
count: async (request: any, params: any) => null,
};
const adapter = new ElasticsearchMonitorsAdapter(database);
await adapter.getMonitorChartsData({}, 'fooID', 'now-15m', 'now', 'Philadelphia');
expect(searchMock).toHaveBeenCalledTimes(1);
expect(searchMock.mock.calls[0]).toMatchSnapshot();
});
});

View file

@ -11,7 +11,8 @@ export interface UMMonitorsAdapter {
request: any,
monitorId: string,
dateRangeStart: string,
dateRangeEnd: string
dateRangeEnd: string,
location?: string | null
): Promise<MonitorChart>;
getMonitors(
request: any,

View file

@ -16,10 +16,14 @@ import {
MonitorSeriesPoint,
Ping,
} from '../../../../common/graphql/types';
import { dropLatestBucket, getFilteredQuery, getFilteredQueryAndStatusFilter } from '../../helper';
import {
dropLatestBucket,
getFilteredQuery,
getFilteredQueryAndStatusFilter,
getHistogramInterval,
} from '../../helper';
import { DatabaseAdapter } from '../database';
import { UMMonitorsAdapter } from './adapter_types';
import { getHistogramInterval } from '../../helper/get_histogram_interval';
const formatStatusBuckets = (time: any, buckets: any, docCount: any) => {
let up = null;
@ -57,7 +61,8 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
request: any,
monitorId: string,
dateRangeStart: string,
dateRangeEnd: string
dateRangeEnd: string,
location?: string | null
): Promise<MonitorChart> {
const params = {
index: INDEX_NAMES.HEARTBEAT,
@ -67,6 +72,8 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
filter: [
{ range: { '@timestamp': { gte: dateRangeStart, lte: dateRangeEnd } } },
{ term: { 'monitor.id': monitorId } },
// if location is truthy, add it as a filter. otherwise add nothing
...(!!location ? [{ term: { 'observer.geo.name': location } }] : []),
],
},
},
@ -166,6 +173,14 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
},
},
},
{
location: {
terms: {
field: 'observer.geo.name',
missing_bucket: true,
},
},
},
],
size: 10000,
},
@ -257,8 +272,16 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
},
},
},
{
location: {
terms: {
field: 'observer.geo.name',
missing_bucket: true,
},
},
},
],
size: 50,
size: 40,
},
aggs: {
latest: {
@ -275,6 +298,7 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
date_histogram: {
field: '@timestamp',
interval: getHistogramInterval(dateRangeStart, dateRangeEnd),
missing: 0,
},
aggs: {
status: {
@ -350,7 +374,14 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
const params = {
index: INDEX_NAMES.HEARTBEAT,
body: {
_source: ['monitor.id', 'monitor.type', 'url.full', 'url.port', 'monitor.name'],
_source: [
'monitor.id',
'monitor.type',
'url.full',
'url.port',
'monitor.name',
'observer.geo.name',
],
size: 1000,
query: {
range: {
@ -373,6 +404,7 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
const ports = new Set<number>();
const types = new Set<string>();
const names = new Set<string>();
const locations = new Set<string>();
const hits = get(result, 'hits.hits', []);
hits.forEach((hit: any) => {
@ -381,6 +413,7 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
const port: number | undefined = get(hit, '_source.url.port', undefined);
const type: string | undefined = get(hit, '_source.monitor.type', undefined);
const name: string | null = get(hit, '_source.monitor.name', null);
const location: string | null = get(hit, '_source.observer.geo.name', null);
if (key) {
ids.push({ key, url });
@ -394,13 +427,17 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
if (name) {
names.add(name);
}
if (location) {
locations.add(location);
}
});
return {
ids,
locations: Array.from(locations),
names: Array.from(names),
ports: Array.from(ports),
schemes: Array.from(types),
names: Array.from(names),
statuses: ['up', 'down'],
};
}
@ -439,65 +476,78 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter {
query,
size: 0,
aggs: {
error_type: {
terms: {
field: 'error.type',
size: 100,
},
aggs: {
by_id: {
terms: {
field: 'monitor.id',
size: 100,
},
aggs: {
latest: {
top_hits: {
sort: [{ '@timestamp': { order: 'desc' } }],
size: 1,
errors: {
composite: {
sources: [
{
id: {
terms: {
field: 'monitor.id',
},
},
},
{
error_type: {
terms: {
field: 'error.type',
},
},
},
{
location: {
terms: {
field: 'observer.geo.name',
missing_bucket: true,
},
},
},
],
size: 50,
},
aggs: {
latest: {
top_hits: {
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
size: 1,
},
},
},
},
},
},
};
const result = await this.database.search(request, params);
const buckets = get(result, 'aggregations.error_type.buckets', []);
const buckets = get(result, 'aggregations.errors.buckets', []);
const errorsList: ErrorListItem[] = [];
buckets.forEach(
({
key: errorType,
by_id: { buckets: monitorBuckets },
}: {
key: string;
by_id: { buckets: any[] };
}) => {
monitorBuckets.forEach(bucket => {
const count = get(bucket, 'doc_count', null);
const monitorId = get(bucket, 'key', null);
const source = get(bucket, 'latest.hits.hits[0]._source', null);
const errorMessage = get(source, 'error.message', null);
const statusCode = get(source, 'http.response.status_code', null);
const timestamp = get(source, '@timestamp', null);
const name = get(source, 'monitor.name', null);
errorsList.push({
latestMessage: errorMessage,
monitorId,
type: errorType,
count,
statusCode,
timestamp,
name: name === '' ? null : name,
});
});
}
);
return errorsList;
buckets.forEach((bucket: any) => {
const count = get<number>(bucket, 'doc_count', 0);
const monitorId = get<string | null>(bucket, 'key.id', null);
const errorType = get<string | null>(bucket, 'key.error_type', null);
const location = get<string | null>(bucket, 'key.location', null);
const source = get<string | null>(bucket, 'latest.hits.hits[0]._source', null);
const errorMessage = get(source, 'error.message', null);
const statusCode = get(source, 'http.response.status_code', null);
const timestamp = get(source, '@timestamp', null);
const name = get(source, 'monitor.name', null);
errorsList.push({
count,
latestMessage: errorMessage,
location,
monitorId,
name: name === '' ? null : name,
statusCode,
timestamp,
type: errorType || '',
});
});
return errorsList.sort(({ count: A }, { count: B }) => B - A);
}
/**

View file

@ -14,14 +14,16 @@ export interface UMPingsAdapter {
monitorId?: string | null,
status?: string | null,
sort?: string | null,
size?: number | null
size?: number | null,
location?: string | null
): Promise<PingResults>;
getLatestMonitorDocs(
request: any,
dateRangeStart: string,
dateRangeEnd: string,
monitorId?: string | null
monitorId?: string | null,
location?: string | null
): Promise<Ping[]>;
getPingHistogram(

View file

@ -37,7 +37,8 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter {
monitorId?: string | null,
status?: string | null,
sort: string | null = 'desc',
size?: number | null
size?: number | null,
location?: string | null
): Promise<PingResults> {
const sortParam = { sort: [{ '@timestamp': { order: sort } }] };
const sizeParam = size ? { size } : undefined;
@ -48,6 +49,9 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter {
if (status) {
filter.push({ term: { 'monitor.status': status } });
}
if (location) {
filter.push({ term: { 'observer.geo.name': location } });
}
const queryContext = { bool: { filter } };
const params = {
index: INDEX_NAMES.HEARTBEAT,
@ -87,12 +91,10 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter {
request: any,
dateRangeStart: string,
dateRangeEnd: string,
monitorId?: string | null
monitorId?: string | null,
location?: string | null
): Promise<Ping[]> {
const filter: any[] = [];
if (monitorId) {
filter.push({ term: { 'monitor.id': monitorId } });
}
// TODO: Write tests for this function
const params = {
index: INDEX_NAMES.HEARTBEAT,
body: {
@ -107,6 +109,8 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter {
},
},
},
...(monitorId ? [{ term: { 'monitor.id': monitorId } }] : []),
...(location ? [{ term: { 'observer.geo.name': location } }] : []),
],
},
},
@ -132,10 +136,6 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter {
},
};
if (filter.length) {
params.body.query.bool.filter.push(...filter);
}
const result = await this.database.search(request, params);
const buckets: any[] = get(result, 'aggregations.by_id.buckets', []);

View file

@ -16,9 +16,16 @@ export class UMMonitorsDomain {
request: any,
monitorId: string,
dateRangeStart: string,
dateRangeEnd: string
dateRangeEnd: string,
location?: string | null
): Promise<MonitorChart> {
return this.adapter.getMonitorChartsData(request, monitorId, dateRangeStart, dateRangeEnd);
return this.adapter.getMonitorChartsData(
request,
monitorId,
dateRangeStart,
dateRangeEnd,
location
);
}
public async getMonitors(

View file

@ -19,7 +19,8 @@ export class UMPingsDomain {
monitorId?: string | null,
status?: string | null,
sort?: string | null,
size?: number | null
size?: number | null,
location?: string | null
): Promise<PingResults> {
return this.adapter.getAll(
request,
@ -28,7 +29,8 @@ export class UMPingsDomain {
monitorId,
status,
sort,
size
size,
location
);
}
@ -36,9 +38,16 @@ export class UMPingsDomain {
request: any,
dateRangeStart: string,
dateRangeEnd: string,
monitorId?: string | null
monitorId?: string | null,
location?: string | null
): Promise<Ping[]> {
return this.adapter.getLatestMonitorDocs(request, dateRangeStart, dateRangeEnd, monitorId);
return this.adapter.getLatestMonitorDocs(
request,
dateRangeStart,
dateRangeEnd,
monitorId,
location
);
}
public async getPingHistogram(

View file

@ -8,12 +8,28 @@ import { getHistogramInterval } from '../get_histogram_interval';
describe('getHistogramInterval', () => {
it('specifies the interval necessary to divide a given timespan into equal buckets, rounded to the nearest integer, expressed in ms', () => {
expect.assertions(3);
const result = getHistogramInterval('now-15m', 'now', 10);
expect(result).toEqual('90000ms');
/**
* These assertions were verbatim comparisons but that introduced
* some flakiness at the ms resolution, sometimes values like "9001ms"
* are returned.
*/
expect(result.startsWith('9000')).toBeTruthy();
expect(result.endsWith('ms')).toBeTruthy();
expect(result).toHaveLength(7);
});
it('will supply a default constant value for bucketCount when none is provided', () => {
expect.assertions(3);
const result = getHistogramInterval('now-15m', 'now');
expect(result).toEqual('36000ms');
/**
* These assertions were verbatim comparisons but that introduced
* some flakiness at the ms resolution, sometimes values like "9001ms"
* are returned.
*/
expect(result.startsWith('3600')).toBeTruthy();
expect(result.endsWith('ms')).toBeTruthy();
expect(result).toHaveLength(7);
});
});

View file

@ -9,3 +9,4 @@ export { formatEsBucketsForHistogram } from './format_es_buckets_for_histogram';
export { getFilteredQuery } from './get_filtered_query';
export { getFilteredQueryAndStatusFilter } from './get_filtered_query_and_status';
export { getFilterFromMust } from './get_filter_from_must';
export { getHistogramInterval } from './get_histogram_interval';

View file

@ -1,40 +1,44 @@
{
"errorList": [
{
"latestMessage": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"monitorId": "auto-http-0X3675F89EF0612091",
"type": "io",
"count": 843,
"latestMessage": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"location": null,
"monitorId": "auto-http-0X3675F89EF0612091",
"name": null,
"statusCode": null,
"timestamp": "2019-01-28T18:43:15.077Z",
"name": null
"type": "io"
},
{
"latestMessage": "dial tcp 127.0.0.1:9200: connect: connection refused",
"monitorId": "auto-tcp-0X81440A68E839814C",
"type": "io",
"count": 748,
"latestMessage": "dial tcp 127.0.0.1:9200: connect: connection refused",
"location": null,
"monitorId": "auto-tcp-0X81440A68E839814C",
"name": null,
"statusCode": null,
"timestamp": "2019-01-28T17:59:34.075Z",
"name": null
"type": "io"
},
{
"latestMessage": "lookup www.reddit.com: no such host",
"monitorId": "auto-http-0XD9AE729FC1C1E04A",
"type": "io",
"count": 1,
"statusCode": null,
"timestamp": "2019-01-28T18:03:10.077Z",
"name": null
},
{
"latestMessage": "received status code 301 expecting 200",
"monitorId": "auto-http-0XA8096548ECEB85B7",
"type": "validate",
"count": 645,
"latestMessage": "received status code 301 expecting 200",
"location": null,
"monitorId": "auto-http-0XA8096548ECEB85B7",
"name": null,
"statusCode": "301",
"timestamp": "2019-01-28T18:43:07.078Z",
"name": null
"type": "validate"
},
{
"count": 1,
"latestMessage": "lookup www.reddit.com: no such host",
"location": null,
"monitorId": "auto-http-0XD9AE729FC1C1E04A",
"name": null,
"statusCode": null,
"timestamp": "2019-01-28T18:03:10.077Z",
"type": "io"
}
]
}

View file

@ -1,13 +1,14 @@
{
"errorList": [
{
"latestMessage": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"monitorId": "auto-http-0X3675F89EF0612091",
"type": "io",
"count": 843,
"latestMessage": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"location": null,
"monitorId": "auto-http-0X3675F89EF0612091",
"name": null,
"statusCode": null,
"timestamp": "2019-01-28T18:43:15.077Z",
"name": null
"type": "io"
}
]
}

View file

@ -1,13 +1,14 @@
{
"errorList": [
{
"latestMessage": "dial tcp 127.0.0.1:9200: connect: connection refused",
"monitorId": "auto-tcp-0X81440A68E839814C",
"type": "io",
"count": 748,
"latestMessage": "dial tcp 127.0.0.1:9200: connect: connection refused",
"location": null,
"monitorId": "auto-tcp-0X81440A68E839814C",
"name": null,
"statusCode": null,
"timestamp": "2019-01-28T17:59:34.075Z",
"name": null
"type": "io"
}
]
}

View file

@ -1,13 +1,14 @@
{
"errorList": [
{
"latestMessage": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"monitorId": "auto-http-0X3675F89EF0612091",
"type": "io",
"count": 843,
"latestMessage": "Get http://localhost:12349/: dial tcp 127.0.0.1:12349: connect: connection refused",
"location": null,
"monitorId": "auto-http-0X3675F89EF0612091",
"name": null,
"statusCode": null,
"timestamp": "2019-01-28T18:43:15.077Z",
"name": null
"type": "io"
}
]
}

View file

@ -1,6 +1,5 @@
{
"filterBar": {
"ports": [9200, 12349],
"ids": [
{ "key": "auto-tcp-0X81440A68E839814C", "url": "tcp://localhost:9200" },
{ "key": "auto-http-0X3675F89EF0612091", "url": "http://localhost:12349/" },
@ -13,7 +12,9 @@
{ "key": "auto-http-0XC9CDA429418EDC2B", "url": "https://www.wikipedia.org/" },
{ "key": "auto-http-0XE3B163481423197D", "url": "https://news.google.com/" }
],
"locations": [],
"names": [],
"ports": [9200, 12349],
"schemes": ["tcp", "http"]
}
}

View file

@ -14,6 +14,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.google.com", "full": "https://www.google.com/" }
},
"upSeries": [
@ -70,6 +71,7 @@
"name": "",
"status": "down"
},
"observer": null,
"url": { "domain": "localhost", "full": "http://localhost:12349/" }
},
"upSeries": [
@ -126,6 +128,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.google.com", "full": "http://www.google.com/" }
},
"upSeries": [
@ -182,6 +185,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.github.com", "full": "https://www.github.com/" }
},
"upSeries": [
@ -238,6 +242,7 @@
"name": "",
"status": "down"
},
"observer": null,
"url": { "domain": "www.example.com", "full": "http://www.example.com/" }
},
"upSeries": [
@ -294,6 +299,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.wikipedia.org", "full": "https://www.wikipedia.org/" }
},
"upSeries": [
@ -350,6 +356,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.reddit.com", "full": "http://www.reddit.com/" }
},
"upSeries": [
@ -406,6 +413,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.elastic.co", "full": "https://www.elastic.co" }
},
"upSeries": [
@ -462,6 +470,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "news.google.com", "full": "https://news.google.com/" }
},
"upSeries": [
@ -518,6 +527,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "localhost", "full": "tcp://localhost:9200" }
},
"upSeries": [

View file

@ -14,6 +14,7 @@
"name": "",
"status": "down"
},
"observer": null,
"url": { "domain": "localhost", "full": "http://localhost:12349/" }
},
"upSeries": [
@ -70,6 +71,7 @@
"name": "",
"status": "down"
},
"observer": null,
"url": { "domain": "www.example.com", "full": "http://www.example.com/" }
},
"upSeries": [

View file

@ -14,6 +14,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.google.com", "full": "https://www.google.com/" }
},
"upSeries": [
@ -70,6 +71,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.google.com", "full": "http://www.google.com/" }
},
"upSeries": [
@ -126,6 +128,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.github.com", "full": "https://www.github.com/" }
},
"upSeries": [
@ -182,6 +185,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.wikipedia.org", "full": "https://www.wikipedia.org/" }
},
"upSeries": [
@ -238,6 +242,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.reddit.com", "full": "http://www.reddit.com/" }
},
"upSeries": [
@ -294,6 +299,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "www.elastic.co", "full": "https://www.elastic.co" }
},
"upSeries": [
@ -350,6 +356,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "news.google.com", "full": "https://news.google.com/" }
},
"upSeries": [
@ -406,6 +413,7 @@
"name": "",
"status": "up"
},
"observer": null,
"url": { "domain": "localhost", "full": "tcp://localhost:9200" }
},
"upSeries": [

View file

@ -3,51 +3,61 @@
{
"timestamp": "2019-01-28T18:43:16.078Z",
"monitor": { "status": "up", "duration": { "us": 3328 } },
"observer": null,
"url": { "full": "tcp://localhost:9200" }
},
{
"timestamp": "2019-01-28T18:43:13.074Z",
"monitor": { "status": "up", "duration": { "us": 299586 } },
"observer": null,
"url": { "full": "http://www.reddit.com/" }
},
{
"timestamp": "2019-01-28T18:43:13.074Z",
"monitor": { "status": "up", "duration": { "us": 850870 } },
"observer": null,
"url": { "full": "https://www.elastic.co" }
},
{
"timestamp": "2019-01-28T18:43:15.077Z",
"monitor": { "status": "down", "duration": { "us": 3331 } },
"observer": null,
"url": { "full": "http://localhost:12349/" }
},
{
"timestamp": "2019-01-28T18:43:15.077Z",
"monitor": { "status": "up", "duration": { "us": 132169 } },
"observer": null,
"url": { "full": "https://www.google.com/" }
},
{
"timestamp": "2019-01-28T18:43:15.077Z",
"monitor": { "status": "up", "duration": { "us": 247244 } },
"observer": null,
"url": { "full": "https://www.github.com/" }
},
{
"timestamp": "2019-01-28T18:43:15.077Z",
"monitor": { "status": "up", "duration": { "us": 118727 } },
"observer": null,
"url": { "full": "http://www.google.com/" }
},
{
"timestamp": "2019-01-28T18:43:07.078Z",
"monitor": { "status": "down", "duration": { "us": 4751074 } },
"observer": null,
"url": { "full": "http://www.example.com/" }
},
{
"timestamp": "2019-01-28T18:42:55.074Z",
"monitor": { "status": "up", "duration": { "us": 1164812 } },
"observer": null,
"url": { "full": "https://www.wikipedia.org/" }
},
{
"timestamp": "2019-01-28T18:42:55.074Z",
"monitor": { "status": "up", "duration": { "us": 2059606 } },
"observer": null,
"url": { "full": "https://news.google.com/" }
}
]

View file

@ -3,6 +3,7 @@
{
"timestamp": "2019-01-28T18:43:16.078Z",
"monitor": { "status": "up", "duration": { "us": 3328 } },
"observer": null,
"url": { "full": "tcp://localhost:9200" }
}
]