mirror of
https://github.com/TwiN/gatus.git
synced 2026-08-27 19:36:40 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e46acb885c | ||
|
|
24da853820 | ||
|
|
4e5a86031f | ||
|
|
7f6f127f4f | ||
|
|
12c352254f | ||
|
|
2b9d986932 | ||
|
|
cdbf5f6c6f | ||
|
|
33562e97f4 | ||
|
|
c9acc83141 | ||
|
|
8c4c360472 | ||
|
|
2c8714f1fa | ||
|
|
8ec256edbf | ||
|
|
a48ec41bca | ||
|
|
541e0264ab | ||
|
|
f945e4b8a2 | ||
|
|
076b92a2b4 | ||
|
|
02e9f74a04 | ||
|
|
b37dd5e819 | ||
|
|
1775f80ffe | ||
|
|
3187db1e9a | ||
|
|
932eab00a0 | ||
|
|
c842ac2343 | ||
|
|
6320237326 | ||
|
|
8fe9d013b5 |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
gatus
|
gatus
|
||||||
db.db
|
db.db
|
||||||
|
config/config.yml
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 TwinProduction
|
Copyright (c) 2021 TwinProduction
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
@@ -20,16 +20,18 @@ core applications: https://status.twinnation.org/
|
|||||||
|
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Conditions](#conditions)
|
- [Conditions](#conditions)
|
||||||
- [Placeholders](#placeholders)
|
- [Placeholders](#placeholders)
|
||||||
- [Functions](#functions)
|
- [Functions](#functions)
|
||||||
- [Alerting](#alerting)
|
- [Alerting](#alerting)
|
||||||
- [Configuring Slack alerts](#configuring-slack-alerts)
|
- [Configuring Slack alerts](#configuring-slack-alerts)
|
||||||
|
- [Configuring Discord alerts](#configuring-discord-alerts)
|
||||||
- [Configuring PagerDuty alerts](#configuring-pagerduty-alerts)
|
- [Configuring PagerDuty alerts](#configuring-pagerduty-alerts)
|
||||||
- [Configuring Twilio alerts](#configuring-twilio-alerts)
|
- [Configuring Twilio alerts](#configuring-twilio-alerts)
|
||||||
- [Configuring Mattermost alerts](#configuring-mattermost-alerts)
|
- [Configuring Mattermost alerts](#configuring-mattermost-alerts)
|
||||||
- [Configuring Messagebird alerts](#configuring-messagebird-alerts)
|
- [Configuring Messagebird alerts](#configuring-messagebird-alerts)
|
||||||
|
- [Configuring Telegram alerts](#configuring-telegram-alerts)
|
||||||
- [Configuring custom alerts](#configuring-custom-alerts)
|
- [Configuring custom alerts](#configuring-custom-alerts)
|
||||||
- [Kubernetes (ALPHA)](#kubernetes-alpha)
|
- [Kubernetes (ALPHA)](#kubernetes-alpha)
|
||||||
- [Auto Discovery](#auto-discovery)
|
- [Auto Discovery](#auto-discovery)
|
||||||
@@ -49,6 +51,7 @@ core applications: https://status.twinnation.org/
|
|||||||
- [Service groups](#service-groups)
|
- [Service groups](#service-groups)
|
||||||
- [Exposing Gatus on a custom port](#exposing-gatus-on-a-custom-port)
|
- [Exposing Gatus on a custom port](#exposing-gatus-on-a-custom-port)
|
||||||
- [Uptime Badges (ALPHA)](#uptime-badges)
|
- [Uptime Badges (ALPHA)](#uptime-badges)
|
||||||
|
- [API](#API)
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -84,7 +87,7 @@ services:
|
|||||||
- "[RESPONSE_TIME] < 300" # Response time must be under 300ms
|
- "[RESPONSE_TIME] < 300" # Response time must be under 300ms
|
||||||
- name: example
|
- name: example
|
||||||
url: "https://example.org/"
|
url: "https://example.org/"
|
||||||
interval: 30s
|
interval: 5m
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
```
|
```
|
||||||
@@ -93,10 +96,10 @@ This example would look like this:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Note that you can also add environment variables in the configuration file (i.e. `$DOMAIN`, `${DOMAIN}`)
|
Note that you can also add environment variables in the configuration file (e.g. `$DOMAIN`, `${DOMAIN}`)
|
||||||
|
|
||||||
|
|
||||||
### Configuration
|
## Configuration
|
||||||
|
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
|:---------------------------------------- |:----------------------------------------------------------------------------- |:-------------- |
|
|:---------------------------------------- |:----------------------------------------------------------------------------- |:-------------- |
|
||||||
@@ -110,43 +113,21 @@ Note that you can also add environment variables in the configuration file (i.e.
|
|||||||
| `services[].url` | URL to send the request to | Required `""` |
|
| `services[].url` | URL to send the request to | Required `""` |
|
||||||
| `services[].method` | Request method | `GET` |
|
| `services[].method` | Request method | `GET` |
|
||||||
| `services[].insecure` | Whether to skip verifying the server's certificate chain and host name | `false` |
|
| `services[].insecure` | Whether to skip verifying the server's certificate chain and host name | `false` |
|
||||||
| `services[].conditions` | Conditions used to determine the health of the service | `[]` |
|
| `services[].conditions` | Conditions used to determine the health of the service. See [Conditions](#conditions) | `[]` |
|
||||||
| `services[].interval` | Duration to wait between every status check | `60s` |
|
| `services[].interval` | Duration to wait between every status check | `60s` |
|
||||||
| `services[].graphql` | Whether to wrap the body in a query param (`{"query":"$body"}`) | `false` |
|
| `services[].graphql` | Whether to wrap the body in a query param (`{"query":"$body"}`) | `false` |
|
||||||
| `services[].body` | Request body | `""` |
|
| `services[].body` | Request body | `""` |
|
||||||
| `services[].headers` | Request headers | `{}` |
|
| `services[].headers` | Request headers | `{}` |
|
||||||
| `services[].dns` | Configuration for a service of type DNS. See [Monitoring using DNS queries](#monitoring-using-dns-queries) | `""` |
|
| `services[].dns` | Configuration for a service of type DNS. See [Monitoring a service using DNS queries](#monitoring-a-service-using-dns-queries). | `""` |
|
||||||
| `services[].dns.query-type` | Query type for DNS service | `""` |
|
| `services[].dns.query-type` | Query type for DNS service | `""` |
|
||||||
| `services[].dns.query-name` | Query name for DNS service | `""` |
|
| `services[].dns.query-name` | Query name for DNS service | `""` |
|
||||||
| `services[].alerts[].type` | Type of alert. Valid types: `slack`, `pagerduty`, `twilio`, `mattermost`, `messagebird`, `custom` | Required `""` |
|
| `services[].alerts[].type` | Type of alert. Valid types: `slack`, `discord`m `pagerduty`, `twilio`, `mattermost`, `messagebird`, `custom` | Required `""` |
|
||||||
| `services[].alerts[].enabled` | Whether to enable the alert | `false` |
|
| `services[].alerts[].enabled` | Whether to enable the alert | `false` |
|
||||||
| `services[].alerts[].failure-threshold` | Number of failures in a row needed before triggering the alert | `3` |
|
| `services[].alerts[].failure-threshold` | Number of failures in a row needed before triggering the alert | `3` |
|
||||||
| `services[].alerts[].success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | `2` |
|
| `services[].alerts[].success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | `2` |
|
||||||
| `services[].alerts[].send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved | `false` |
|
| `services[].alerts[].send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved | `false` |
|
||||||
| `services[].alerts[].description` | Description of the alert. Will be included in the alert sent | `""` |
|
| `services[].alerts[].description` | Description of the alert. Will be included in the alert sent | `""` |
|
||||||
| `alerting` | Configuration for alerting | `{}` |
|
| `alerting` | Configuration for alerting. See [Alerting](#alerting) | `{}` |
|
||||||
| `alerting.slack` | Configuration for alerts of type `slack` | `{}` |
|
|
||||||
| `alerting.slack.webhook-url` | Slack Webhook URL | Required `""` |
|
|
||||||
| `alerting.pagerduty` | Configuration for alerts of type `pagerduty` | `{}` |
|
|
||||||
| `alerting.pagerduty.integration-key` | PagerDuty Events API v2 integration key. | Required `""` |
|
|
||||||
| `alerting.twilio` | Settings for alerts of type `twilio` | `{}` |
|
|
||||||
| `alerting.twilio.sid` | Twilio account SID | Required `""` |
|
|
||||||
| `alerting.twilio.token` | Twilio auth token | Required `""` |
|
|
||||||
| `alerting.twilio.from` | Number to send Twilio alerts from | Required `""` |
|
|
||||||
| `alerting.twilio.to` | Number to send twilio alerts to | Required `""` |
|
|
||||||
| `alerting.mattermost` | Configuration for alerts of type `mattermost` | `{}` |
|
|
||||||
| `alerting.mattermost.webhook-url` | Mattermost Webhook URL | Required `""` |
|
|
||||||
| `alerting.mattermost.insecure` | Whether to skip verifying the server's certificate chain and host name | `false` |
|
|
||||||
| `alerting.messagebird` | Settings for alerts of type `messagebird` | `{}` |
|
|
||||||
| `alerting.messagebird.access-key` | Messagebird access key | Required `""` |
|
|
||||||
| `alerting.messagebird.originator` | The sender of the message | Required `""` |
|
|
||||||
| `alerting.messagebird.recipients` | The recipients of the message | Required `""` |
|
|
||||||
| `alerting.custom` | Configuration for custom actions on failure or alerts | `{}` |
|
|
||||||
| `alerting.custom.url` | Custom alerting request url | Required `""` |
|
|
||||||
| `alerting.custom.method` | Request method | `GET` |
|
|
||||||
| `alerting.custom.insecure` | Whether to skip verifying the server's certificate chain and host name | `false` |
|
|
||||||
| `alerting.custom.body` | Custom alerting request body. | `""` |
|
|
||||||
| `alerting.custom.headers` | Custom alerting request headers | `{}` |
|
|
||||||
| `security` | Security configuration | `{}` |
|
| `security` | Security configuration | `{}` |
|
||||||
| `security.basic` | Basic authentication security configuration | `{}` |
|
| `security.basic` | Basic authentication security configuration | `{}` |
|
||||||
| `security.basic.username` | Username for Basic authentication | Required `""` |
|
| `security.basic.username` | Username for Basic authentication | Required `""` |
|
||||||
@@ -156,7 +137,8 @@ Note that you can also add environment variables in the configuration file (i.e.
|
|||||||
| `web.address` | Address to listen on | `0.0.0.0` |
|
| `web.address` | Address to listen on | `0.0.0.0` |
|
||||||
| `web.port` | Port to listen on | `8080` |
|
| `web.port` | Port to listen on | `8080` |
|
||||||
|
|
||||||
For Kubernetes configuration, see [Kubernetes](#kubernetes-alpha)
|
- For Kubernetes configuration, see [Kubernetes](#kubernetes-alpha).
|
||||||
|
- For alerting configuration, see [Alerting](#alerting).
|
||||||
|
|
||||||
|
|
||||||
### Conditions
|
### Conditions
|
||||||
@@ -179,6 +161,8 @@ Here are some examples of conditions you can use:
|
|||||||
| `[BODY].age == [BODY].id` | JSONPath value of `$.age` is equal JSONPath `$.id` | `{"age":1,"id":1}` | |
|
| `[BODY].age == [BODY].id` | JSONPath value of `$.age` is equal JSONPath `$.id` | `{"age":1,"id":1}` | |
|
||||||
| `len([BODY].data) < 5` | Array at JSONPath `$.data` has less than 5 elements | `{"data":[{"id":1}]}` | |
|
| `len([BODY].data) < 5` | Array at JSONPath `$.data` has less than 5 elements | `{"data":[{"id":1}]}` | |
|
||||||
| `len([BODY].name) == 8` | String at JSONPath `$.name` has a length of 8 | `{"name":"john.doe"}` | `{"name":"bob"}` |
|
| `len([BODY].name) == 8` | String at JSONPath `$.name` has a length of 8 | `{"name":"john.doe"}` | `{"name":"bob"}` |
|
||||||
|
| `has([BODY].errors) == false` | JSONPath `$.errors` does not exist | `{"name":"john.doe"}` | `{"errors":[]}` |
|
||||||
|
| `has([BODY].users) == true` | JSONPath `$.users` exists | `{"users":[]}` | `{}` |
|
||||||
| `[BODY].name == pat(john*)` | String at JSONPath `$.name` matches pattern `john*` | `{"name":"john.doe"}` | `{"name":"bob"}` |
|
| `[BODY].name == pat(john*)` | String at JSONPath `$.name` matches pattern `john*` | `{"name":"john.doe"}` | `{"name":"bob"}` |
|
||||||
| `[BODY].id == any(1, 2)` | Value at JSONPath `$.id` is equal to `1` or `2` | 1, 2 | 3, 4, 5 |
|
| `[BODY].id == any(1, 2)` | Value at JSONPath `$.id` is equal to `1` or `2` | 1, 2 | 3, 4, 5 |
|
||||||
| `[CERTIFICATE_EXPIRATION] > 48h` | Certificate expiration is more than 48h away | 49h, 50h, 123h | 1h, 24h, ... |
|
| `[CERTIFICATE_EXPIRATION] > 48h` | Certificate expiration is more than 48h away | 49h, 50h, 123h | 1h, 24h, ... |
|
||||||
@@ -202,6 +186,7 @@ Here are some examples of conditions you can use:
|
|||||||
| Function | Description | Example |
|
| Function | Description | Example |
|
||||||
|:-----------|:---------------------------------------------------------------------------------------------------------------- |:-------------------------- |
|
|:-----------|:---------------------------------------------------------------------------------------------------------------- |:-------------------------- |
|
||||||
| `len` | Returns the length of the object/slice. Works only with the `[BODY]` placeholder. | `len([BODY].username) > 8`
|
| `len` | Returns the length of the object/slice. Works only with the `[BODY]` placeholder. | `len([BODY].username) > 8`
|
||||||
|
| `has` | Returns `true` or `false` based on whether a given path is valid. Works only with the `[BODY]` placeholder. | `has([BODY].errors) == false`
|
||||||
| `pat` | Specifies that the string passed as parameter should be evaluated as a pattern. Works only with `==` and `!=`. | `[IP] == pat(192.168.*)`
|
| `pat` | Specifies that the string passed as parameter should be evaluated as a pattern. Works only with `==` and `!=`. | `[IP] == pat(192.168.*)`
|
||||||
| `any` | Specifies that any one of the values passed as parameters is a valid value. Works only with `==` and `!=`. | `[BODY].ip == any(127.0.0.1, ::1)`
|
| `any` | Specifies that any one of the values passed as parameters is a valid value. Works only with `==` and `!=`. | `[BODY].ip == any(127.0.0.1, ::1)`
|
||||||
|
|
||||||
@@ -216,6 +201,36 @@ individual services with configurable descriptions and thresholds.
|
|||||||
Note that if an alerting provider is not configured properly, all alerts configured with the provider's type will be
|
Note that if an alerting provider is not configured properly, all alerts configured with the provider's type will be
|
||||||
ignored.
|
ignored.
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|:---------------------------------------- |:----------------------------------------------------------------------------- |:-------------- |
|
||||||
|
| `alerting.slack` | Configuration for alerts of type `slack` | `{}` |
|
||||||
|
| `alerting.slack.webhook-url` | Slack Webhook URL | Required `""` |
|
||||||
|
| `alerting.discord` | Configuration for alerts of type `discord` | `{}` |
|
||||||
|
| `alerting.discord.webhook-url` | Discord Webhook URL | Required `""` |
|
||||||
|
| `alerting.pagerduty` | Configuration for alerts of type `pagerduty` | `{}` |
|
||||||
|
| `alerting.pagerduty.integration-key` | PagerDuty Events API v2 integration key. | Required `""` |
|
||||||
|
| `alerting.twilio` | Settings for alerts of type `twilio` | `{}` |
|
||||||
|
| `alerting.twilio.sid` | Twilio account SID | Required `""` |
|
||||||
|
| `alerting.twilio.token` | Twilio auth token | Required `""` |
|
||||||
|
| `alerting.twilio.from` | Number to send Twilio alerts from | Required `""` |
|
||||||
|
| `alerting.twilio.to` | Number to send twilio alerts to | Required `""` |
|
||||||
|
| `alerting.mattermost` | Configuration for alerts of type `mattermost` | `{}` |
|
||||||
|
| `alerting.mattermost.webhook-url` | Mattermost Webhook URL | Required `""` |
|
||||||
|
| `alerting.mattermost.insecure` | Whether to skip verifying the server's certificate chain and host name | `false` |
|
||||||
|
| `alerting.messagebird` | Settings for alerts of type `messagebird` | `{}` |
|
||||||
|
| `alerting.messagebird.access-key` | Messagebird access key | Required `""` |
|
||||||
|
| `alerting.messagebird.originator` | The sender of the message | Required `""` |
|
||||||
|
| `alerting.messagebird.recipients` | The recipients of the message | Required `""` |
|
||||||
|
| `alerting.telegram` | Configuration for alerts of type `telegram` | `{}` |
|
||||||
|
| `alerting.telegram.token` | Telegram Bot Token | Required `""` |
|
||||||
|
| `alerting.telegram.id` | Telegram User ID | Required `""` |
|
||||||
|
| `alerting.custom` | Configuration for custom actions on failure or alerts | `{}` |
|
||||||
|
| `alerting.custom.url` | Custom alerting request url | Required `""` |
|
||||||
|
| `alerting.custom.method` | Request method | `GET` |
|
||||||
|
| `alerting.custom.insecure` | Whether to skip verifying the server's certificate chain and host name | `false` |
|
||||||
|
| `alerting.custom.body` | Custom alerting request body. | `""` |
|
||||||
|
| `alerting.custom.headers` | Custom alerting request headers | `{}` |
|
||||||
|
|
||||||
|
|
||||||
#### Configuring Slack alerts
|
#### Configuring Slack alerts
|
||||||
|
|
||||||
@@ -223,6 +238,7 @@ ignored.
|
|||||||
alerting:
|
alerting:
|
||||||
slack:
|
slack:
|
||||||
webhook-url: "https://hooks.slack.com/services/**********/**********/**********"
|
webhook-url: "https://hooks.slack.com/services/**********/**********/**********"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: twinnation
|
- name: twinnation
|
||||||
url: "https://twinnation.org/health"
|
url: "https://twinnation.org/health"
|
||||||
@@ -248,6 +264,29 @@ Here's an example of what the notifications look like:
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
#### Configuring Discord alerts
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
alerting:
|
||||||
|
discord:
|
||||||
|
webhook-url: "https://discord.com/api/webhooks/**********/**********"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: twinnation
|
||||||
|
url: "https://twinnation.org/health"
|
||||||
|
interval: 30s
|
||||||
|
alerts:
|
||||||
|
- type: discord
|
||||||
|
enabled: true
|
||||||
|
description: "healthcheck failed"
|
||||||
|
send-on-resolved: true
|
||||||
|
conditions:
|
||||||
|
- "[STATUS] == 200"
|
||||||
|
- "[BODY].status == UP"
|
||||||
|
- "[RESPONSE_TIME] < 300"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Configuring PagerDuty alerts
|
#### Configuring PagerDuty alerts
|
||||||
|
|
||||||
It is highly recommended to set `services[].alerts[].send-on-resolved` to `true` for alerts
|
It is highly recommended to set `services[].alerts[].send-on-resolved` to `true` for alerts
|
||||||
@@ -259,6 +298,7 @@ PagerDuty instead.
|
|||||||
alerting:
|
alerting:
|
||||||
pagerduty:
|
pagerduty:
|
||||||
integration-key: "********************************"
|
integration-key: "********************************"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: twinnation
|
- name: twinnation
|
||||||
url: "https://twinnation.org/health"
|
url: "https://twinnation.org/health"
|
||||||
@@ -269,7 +309,7 @@ services:
|
|||||||
failure-threshold: 3
|
failure-threshold: 3
|
||||||
success-threshold: 5
|
success-threshold: 5
|
||||||
send-on-resolved: true
|
send-on-resolved: true
|
||||||
description: "healthcheck failed 3 times in a row"
|
description: "healthcheck failed"
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
- "[BODY].status == UP"
|
- "[BODY].status == UP"
|
||||||
@@ -286,6 +326,7 @@ alerting:
|
|||||||
token: "..."
|
token: "..."
|
||||||
from: "+1-234-567-8901"
|
from: "+1-234-567-8901"
|
||||||
to: "+1-234-567-8901"
|
to: "+1-234-567-8901"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: twinnation
|
- name: twinnation
|
||||||
interval: 30s
|
interval: 30s
|
||||||
@@ -295,7 +336,7 @@ services:
|
|||||||
enabled: true
|
enabled: true
|
||||||
failure-threshold: 5
|
failure-threshold: 5
|
||||||
send-on-resolved: true
|
send-on-resolved: true
|
||||||
description: "healthcheck failed 5 times in a row"
|
description: "healthcheck failed"
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
- "[BODY].status == UP"
|
- "[BODY].status == UP"
|
||||||
@@ -310,6 +351,7 @@ alerting:
|
|||||||
mattermost:
|
mattermost:
|
||||||
webhook-url: "http://**********/hooks/**********"
|
webhook-url: "http://**********/hooks/**********"
|
||||||
insecure: true
|
insecure: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: twinnation
|
- name: twinnation
|
||||||
url: "https://twinnation.org/health"
|
url: "https://twinnation.org/health"
|
||||||
@@ -317,7 +359,7 @@ services:
|
|||||||
alerts:
|
alerts:
|
||||||
- type: mattermost
|
- type: mattermost
|
||||||
enabled: true
|
enabled: true
|
||||||
description: "healthcheck failed 3 times in a row"
|
description: "healthcheck failed"
|
||||||
send-on-resolved: true
|
send-on-resolved: true
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
@@ -349,7 +391,7 @@ services:
|
|||||||
enabled: true
|
enabled: true
|
||||||
failure-threshold: 3
|
failure-threshold: 3
|
||||||
send-on-resolved: true
|
send-on-resolved: true
|
||||||
description: "healthcheck failed 3 times in a row"
|
description: "healthcheck failed"
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
- "[BODY].status == UP"
|
- "[BODY].status == UP"
|
||||||
@@ -357,6 +399,32 @@ services:
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Configuring Telegram alerts
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
alerting:
|
||||||
|
telegram:
|
||||||
|
token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
||||||
|
id: "0123456789"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: twinnation
|
||||||
|
url: "https://twinnation.org/health"
|
||||||
|
interval: 30s
|
||||||
|
alerts:
|
||||||
|
- type: telegram
|
||||||
|
enabled: true
|
||||||
|
send-on-resolved: true
|
||||||
|
conditions:
|
||||||
|
- "[STATUS] == 200"
|
||||||
|
- "[BODY].status == UP"
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's an example of what the notifications look like:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
#### Configuring custom alerts
|
#### Configuring custom alerts
|
||||||
|
|
||||||
While they're called alerts, you can use this feature to call anything.
|
While they're called alerts, you can use this feature to call anything.
|
||||||
@@ -395,7 +463,7 @@ services:
|
|||||||
failure-threshold: 10
|
failure-threshold: 10
|
||||||
success-threshold: 3
|
success-threshold: 3
|
||||||
send-on-resolved: true
|
send-on-resolved: true
|
||||||
description: "healthcheck failed 10 times in a row"
|
description: "healthcheck failed"
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
- "[BODY].status == UP"
|
- "[BODY].status == UP"
|
||||||
@@ -414,6 +482,7 @@ alerting:
|
|||||||
As a result, the `[ALERT_TRIGGERED_OR_RESOLVED]` in the body of first example of this section would be replaced by
|
As a result, the `[ALERT_TRIGGERED_OR_RESOLVED]` in the body of first example of this section would be replaced by
|
||||||
`partial_outage` when an alert is triggered and `operational` when an alert is resolved.
|
`partial_outage` when an alert is triggered and `operational` when an alert is resolved.
|
||||||
|
|
||||||
|
|
||||||
### Kubernetes (ALPHA)
|
### Kubernetes (ALPHA)
|
||||||
|
|
||||||
> **WARNING**: This feature is in ALPHA. This means that it is very likely to change in the near future, which means that
|
> **WARNING**: This feature is in ALPHA. This means that it is very likely to change in the near future, which means that
|
||||||
@@ -527,8 +596,6 @@ services:
|
|||||||
avatar
|
avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers:
|
|
||||||
Content-Type: application/json # XXX: as of v1.9.2, this header is automatically added when graphql is set to true
|
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
- "[BODY].data.users[0].gender == female"
|
- "[BODY].data.users[0].gender == female"
|
||||||
@@ -763,3 +830,25 @@ Example: 
|
)
|
||||||
|
|
||||||
// Config is the configuration for alerting providers
|
// Config is the configuration for alerting providers
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Slack is the configuration for the slack alerting provider
|
// Custom is the configuration for the custom alerting provider
|
||||||
Slack *slack.AlertProvider `yaml:"slack"`
|
Custom *custom.AlertProvider `yaml:"custom"`
|
||||||
|
|
||||||
|
// Discord is the configuration for the discord alerting provider
|
||||||
|
Discord *discord.AlertProvider `yaml:"discord"`
|
||||||
|
|
||||||
// Mattermost is the configuration for the mattermost alerting provider
|
// Mattermost is the configuration for the mattermost alerting provider
|
||||||
Mattermost *mattermost.AlertProvider `yaml:"mattermost"`
|
Mattermost *mattermost.AlertProvider `yaml:"mattermost"`
|
||||||
@@ -20,12 +25,15 @@ type Config struct {
|
|||||||
// Messagebird is the configuration for the messagebird alerting provider
|
// Messagebird is the configuration for the messagebird alerting provider
|
||||||
Messagebird *messagebird.AlertProvider `yaml:"messagebird"`
|
Messagebird *messagebird.AlertProvider `yaml:"messagebird"`
|
||||||
|
|
||||||
// Pagerduty is the configuration for the pagerduty alerting provider
|
// PagerDuty is the configuration for the pagerduty alerting provider
|
||||||
PagerDuty *pagerduty.AlertProvider `yaml:"pagerduty"`
|
PagerDuty *pagerduty.AlertProvider `yaml:"pagerduty"`
|
||||||
|
|
||||||
|
// Slack is the configuration for the slack alerting provider
|
||||||
|
Slack *slack.AlertProvider `yaml:"slack"`
|
||||||
|
|
||||||
|
// Telegram is the configuration for the telegram alerting provider
|
||||||
|
Telegram *telegram.AlertProvider `yaml:"telegram"`
|
||||||
|
|
||||||
// Twilio is the configuration for the twilio alerting provider
|
// Twilio is the configuration for the twilio alerting provider
|
||||||
Twilio *twilio.AlertProvider `yaml:"twilio"`
|
Twilio *twilio.AlertProvider `yaml:"twilio"`
|
||||||
|
|
||||||
// Custom is the configuration for the custom alerting provider
|
|
||||||
Custom *custom.AlertProvider `yaml:"custom"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/custom"
|
||||||
|
"github.com/TwinProduction/gatus/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlertProvider is the configuration necessary for sending an alert using Discord
|
||||||
|
type AlertProvider struct {
|
||||||
|
WebhookURL string `yaml:"webhook-url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns whether the provider's configuration is valid
|
||||||
|
func (provider *AlertProvider) IsValid() bool {
|
||||||
|
return len(provider.WebhookURL) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCustomAlertProvider converts the provider into a custom.AlertProvider
|
||||||
|
func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, result *core.Result, resolved bool) *custom.AlertProvider {
|
||||||
|
var message, results string
|
||||||
|
var colorCode int
|
||||||
|
if resolved {
|
||||||
|
message = fmt.Sprintf("An alert for **%s** has been resolved after passing successfully %d time(s) in a row", service.Name, alert.SuccessThreshold)
|
||||||
|
colorCode = 3066993
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", service.Name, alert.FailureThreshold)
|
||||||
|
colorCode = 15158332
|
||||||
|
}
|
||||||
|
for _, conditionResult := range result.ConditionResults {
|
||||||
|
var prefix string
|
||||||
|
if conditionResult.Success {
|
||||||
|
prefix = ":white_check_mark:"
|
||||||
|
} else {
|
||||||
|
prefix = ":x:"
|
||||||
|
}
|
||||||
|
results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition)
|
||||||
|
}
|
||||||
|
return &custom.AlertProvider{
|
||||||
|
URL: provider.WebhookURL,
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Body: fmt.Sprintf(`{
|
||||||
|
"content": "",
|
||||||
|
"embeds": [
|
||||||
|
{
|
||||||
|
"title": ":helmet_with_white_cross: Gatus",
|
||||||
|
"description": "%s:\n> %s",
|
||||||
|
"color": %d,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Condition results",
|
||||||
|
"value": "%s",
|
||||||
|
"inline": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, message, alert.Description, colorCode, results),
|
||||||
|
Headers: map[string]string{"Content-Type": "application/json"},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/TwinProduction/gatus/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlertProvider_IsValid(t *testing.T) {
|
||||||
|
invalidProvider := AlertProvider{WebhookURL: ""}
|
||||||
|
if invalidProvider.IsValid() {
|
||||||
|
t.Error("provider shouldn't have been valid")
|
||||||
|
}
|
||||||
|
validProvider := AlertProvider{WebhookURL: "http://example.com"}
|
||||||
|
if !validProvider.IsValid() {
|
||||||
|
t.Error("provider should've been valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) {
|
||||||
|
provider := AlertProvider{WebhookURL: "http://example.com"}
|
||||||
|
customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true)
|
||||||
|
if customAlertProvider == nil {
|
||||||
|
t.Fatal("customAlertProvider shouldn't have been nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(customAlertProvider.Body, "resolved") {
|
||||||
|
t.Error("customAlertProvider.Body should've contained the substring resolved")
|
||||||
|
}
|
||||||
|
if customAlertProvider.URL != "http://example.com" {
|
||||||
|
t.Errorf("expected URL to be %s, got %s", "http://example.com", customAlertProvider.URL)
|
||||||
|
}
|
||||||
|
if customAlertProvider.Method != http.MethodPost {
|
||||||
|
t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method)
|
||||||
|
}
|
||||||
|
body := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(customAlertProvider.Body), &body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) {
|
||||||
|
provider := AlertProvider{WebhookURL: "http://example.com"}
|
||||||
|
customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "UNSUCCESSFUL_CONDITION", Success: false}}}, false)
|
||||||
|
if customAlertProvider == nil {
|
||||||
|
t.Fatal("customAlertProvider shouldn't have been nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(customAlertProvider.Body, "triggered") {
|
||||||
|
t.Error("customAlertProvider.Body should've contained the substring triggered")
|
||||||
|
}
|
||||||
|
if customAlertProvider.URL != "http://example.com" {
|
||||||
|
t.Errorf("expected URL to be %s, got %s", "http://example.com", customAlertProvider.URL)
|
||||||
|
}
|
||||||
|
if customAlertProvider.Method != http.MethodPost {
|
||||||
|
t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method)
|
||||||
|
}
|
||||||
|
body := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(customAlertProvider.Body), &body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TwinProduction/gatus/alerting/provider/custom"
|
"github.com/TwinProduction/gatus/alerting/provider/custom"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/discord"
|
||||||
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
|
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
|
||||||
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
|
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
|
||||||
"github.com/TwinProduction/gatus/alerting/provider/pagerduty"
|
"github.com/TwinProduction/gatus/alerting/provider/pagerduty"
|
||||||
"github.com/TwinProduction/gatus/alerting/provider/slack"
|
"github.com/TwinProduction/gatus/alerting/provider/slack"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/telegram"
|
||||||
"github.com/TwinProduction/gatus/alerting/provider/twilio"
|
"github.com/TwinProduction/gatus/alerting/provider/twilio"
|
||||||
"github.com/TwinProduction/gatus/core"
|
"github.com/TwinProduction/gatus/core"
|
||||||
)
|
)
|
||||||
@@ -22,9 +24,11 @@ type AlertProvider interface {
|
|||||||
var (
|
var (
|
||||||
// Validate interface implementation on compile
|
// Validate interface implementation on compile
|
||||||
_ AlertProvider = (*custom.AlertProvider)(nil)
|
_ AlertProvider = (*custom.AlertProvider)(nil)
|
||||||
_ AlertProvider = (*twilio.AlertProvider)(nil)
|
_ AlertProvider = (*discord.AlertProvider)(nil)
|
||||||
_ AlertProvider = (*slack.AlertProvider)(nil)
|
|
||||||
_ AlertProvider = (*mattermost.AlertProvider)(nil)
|
_ AlertProvider = (*mattermost.AlertProvider)(nil)
|
||||||
_ AlertProvider = (*messagebird.AlertProvider)(nil)
|
_ AlertProvider = (*messagebird.AlertProvider)(nil)
|
||||||
_ AlertProvider = (*pagerduty.AlertProvider)(nil)
|
_ AlertProvider = (*pagerduty.AlertProvider)(nil)
|
||||||
|
_ AlertProvider = (*slack.AlertProvider)(nil)
|
||||||
|
_ AlertProvider = (*telegram.AlertProvider)(nil)
|
||||||
|
_ AlertProvider = (*twilio.AlertProvider)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/custom"
|
||||||
|
"github.com/TwinProduction/gatus/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlertProvider is the configuration necessary for sending an alert using Telegram
|
||||||
|
type AlertProvider struct {
|
||||||
|
Token string `yaml:"token"`
|
||||||
|
ID string `yaml:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns whether the provider's configuration is valid
|
||||||
|
func (provider *AlertProvider) IsValid() bool {
|
||||||
|
return len(provider.Token) > 0 && len(provider.ID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCustomAlertProvider converts the provider into a custom.AlertProvider
|
||||||
|
func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, result *core.Result, resolved bool) *custom.AlertProvider {
|
||||||
|
var message, results string
|
||||||
|
if resolved {
|
||||||
|
message = fmt.Sprintf("An alert for *%s* has been resolved:\\n—\\n _healthcheck passing successfully %d time(s) in a row_\\n— ", service.Name, alert.FailureThreshold)
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("An alert for *%s* has been triggered:\\n—\\n _healthcheck failed %d time(s) in a row_\\n— ", service.Name, alert.FailureThreshold)
|
||||||
|
}
|
||||||
|
for _, conditionResult := range result.ConditionResults {
|
||||||
|
var prefix string
|
||||||
|
if conditionResult.Success {
|
||||||
|
prefix = "✅"
|
||||||
|
} else {
|
||||||
|
prefix = "❌"
|
||||||
|
}
|
||||||
|
results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition)
|
||||||
|
}
|
||||||
|
var text string
|
||||||
|
if len(alert.Description) > 0 {
|
||||||
|
text = fmt.Sprintf("⛑ *Gatus* \\n%s \\n*Description* \\n_%s_ \\n\\n*Condition results*\\n%s", message, alert.Description, results)
|
||||||
|
} else {
|
||||||
|
text = fmt.Sprintf("⛑ *Gatus* \\n%s \\n*Condition results*\\n%s", message, results)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &custom.AlertProvider{
|
||||||
|
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token),
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Body: fmt.Sprintf(`{"chat_id": "%s", "text": "%s", "parse_mode": "MARKDOWN" }`, provider.ID, text),
|
||||||
|
Headers: map[string]string{"Content-Type": "application/json"},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/TwinProduction/gatus/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlertProvider_IsValid(t *testing.T) {
|
||||||
|
invalidProvider := AlertProvider{Token: "", ID: ""}
|
||||||
|
if invalidProvider.IsValid() {
|
||||||
|
t.Error("provider shouldn't have been valid")
|
||||||
|
}
|
||||||
|
validProvider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}
|
||||||
|
if !validProvider.IsValid() {
|
||||||
|
t.Error("provider should've been valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_ToCustomAlertProviderWithResolvedAlert(t *testing.T) {
|
||||||
|
provider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "12345678"}
|
||||||
|
customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "SUCCESSFUL_CONDITION", Success: true}}}, true)
|
||||||
|
if customAlertProvider == nil {
|
||||||
|
t.Fatal("customAlertProvider shouldn't have been nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(customAlertProvider.Body, "resolved") {
|
||||||
|
t.Error("customAlertProvider.Body should've contained the substring resolved")
|
||||||
|
}
|
||||||
|
if customAlertProvider.URL != fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token) {
|
||||||
|
t.Errorf("expected URL to be %s, got %s", fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token), customAlertProvider.URL)
|
||||||
|
}
|
||||||
|
if customAlertProvider.Method != http.MethodPost {
|
||||||
|
t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method)
|
||||||
|
}
|
||||||
|
body := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(customAlertProvider.Body), &body)
|
||||||
|
//_, err := json.Marshal(customAlertProvider.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_ToCustomAlertProviderWithTriggeredAlert(t *testing.T) {
|
||||||
|
provider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "0123456789"}
|
||||||
|
customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{Description: "Healthcheck Successful"}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "UNSUCCESSFUL_CONDITION", Success: false}}}, false)
|
||||||
|
if customAlertProvider == nil {
|
||||||
|
t.Fatal("customAlertProvider shouldn't have been nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(customAlertProvider.Body, "triggered") {
|
||||||
|
t.Error("customAlertProvider.Body should've contained the substring triggered")
|
||||||
|
}
|
||||||
|
if customAlertProvider.URL != fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token) {
|
||||||
|
t.Errorf("expected URL to be %s, got %s", fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token), customAlertProvider.URL)
|
||||||
|
}
|
||||||
|
if customAlertProvider.Method != http.MethodPost {
|
||||||
|
t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method)
|
||||||
|
}
|
||||||
|
body := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(customAlertProvider.Body), &body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_ToCustomAlertProviderWithDescription(t *testing.T) {
|
||||||
|
provider := AlertProvider{Token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", ID: "0123456789"}
|
||||||
|
customAlertProvider := provider.ToCustomAlertProvider(&core.Service{}, &core.Alert{}, &core.Result{ConditionResults: []*core.ConditionResult{{Condition: "UNSUCCESSFUL_CONDITION", Success: false}}}, false)
|
||||||
|
if customAlertProvider == nil {
|
||||||
|
t.Fatal("customAlertProvider shouldn't have been nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(customAlertProvider.Body, "triggered") {
|
||||||
|
t.Error("customAlertProvider.Body should've contained the substring triggered")
|
||||||
|
}
|
||||||
|
if customAlertProvider.URL != fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token) {
|
||||||
|
t.Errorf("expected URL to be %s, got %s", fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", provider.Token), customAlertProvider.URL)
|
||||||
|
}
|
||||||
|
if customAlertProvider.Method != http.MethodPost {
|
||||||
|
t.Errorf("expected method to be %s, got %s", http.MethodPost, customAlertProvider.Method)
|
||||||
|
}
|
||||||
|
body := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(customAlertProvider.Body), &body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-15
@@ -228,12 +228,14 @@ func validateAlertingConfig(config *Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
alertTypes := []core.AlertType{
|
alertTypes := []core.AlertType{
|
||||||
core.SlackAlert,
|
core.CustomAlert,
|
||||||
|
core.DiscordAlert,
|
||||||
core.MattermostAlert,
|
core.MattermostAlert,
|
||||||
core.MessagebirdAlert,
|
core.MessagebirdAlert,
|
||||||
core.TwilioAlert,
|
|
||||||
core.PagerDutyAlert,
|
core.PagerDutyAlert,
|
||||||
core.CustomAlert,
|
core.SlackAlert,
|
||||||
|
core.TelegramAlert,
|
||||||
|
core.TwilioAlert,
|
||||||
}
|
}
|
||||||
var validProviders, invalidProviders []core.AlertType
|
var validProviders, invalidProviders []core.AlertType
|
||||||
for _, alertType := range alertTypes {
|
for _, alertType := range alertTypes {
|
||||||
@@ -255,12 +257,18 @@ func validateAlertingConfig(config *Config) {
|
|||||||
// GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding core.AlertType
|
// GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding core.AlertType
|
||||||
func GetAlertingProviderByAlertType(config *Config, alertType core.AlertType) provider.AlertProvider {
|
func GetAlertingProviderByAlertType(config *Config, alertType core.AlertType) provider.AlertProvider {
|
||||||
switch alertType {
|
switch alertType {
|
||||||
case core.SlackAlert:
|
case core.CustomAlert:
|
||||||
if config.Alerting.Slack == nil {
|
if config.Alerting.Custom == nil {
|
||||||
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return config.Alerting.Slack
|
return config.Alerting.Custom
|
||||||
|
case core.DiscordAlert:
|
||||||
|
if config.Alerting.Discord == nil {
|
||||||
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return config.Alerting.Discord
|
||||||
case core.MattermostAlert:
|
case core.MattermostAlert:
|
||||||
if config.Alerting.Mattermost == nil {
|
if config.Alerting.Mattermost == nil {
|
||||||
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
@@ -273,24 +281,30 @@ func GetAlertingProviderByAlertType(config *Config, alertType core.AlertType) pr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return config.Alerting.Messagebird
|
return config.Alerting.Messagebird
|
||||||
case core.TwilioAlert:
|
|
||||||
if config.Alerting.Twilio == nil {
|
|
||||||
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return config.Alerting.Twilio
|
|
||||||
case core.PagerDutyAlert:
|
case core.PagerDutyAlert:
|
||||||
if config.Alerting.PagerDuty == nil {
|
if config.Alerting.PagerDuty == nil {
|
||||||
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return config.Alerting.PagerDuty
|
return config.Alerting.PagerDuty
|
||||||
case core.CustomAlert:
|
case core.SlackAlert:
|
||||||
if config.Alerting.Custom == nil {
|
if config.Alerting.Slack == nil {
|
||||||
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return config.Alerting.Custom
|
return config.Alerting.Slack
|
||||||
|
case core.TelegramAlert:
|
||||||
|
if config.Alerting.Telegram == nil {
|
||||||
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return config.Alerting.Telegram
|
||||||
|
case core.TwilioAlert:
|
||||||
|
if config.Alerting.Twilio == nil {
|
||||||
|
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return config.Alerting.Twilio
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+105
-18
@@ -6,6 +6,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/TwinProduction/gatus/alerting"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/custom"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/discord"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/mattermost"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/messagebird"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/pagerduty"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/slack"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/telegram"
|
||||||
|
"github.com/TwinProduction/gatus/alerting/provider/twilio"
|
||||||
"github.com/TwinProduction/gatus/core"
|
"github.com/TwinProduction/gatus/core"
|
||||||
"github.com/TwinProduction/gatus/k8stest"
|
"github.com/TwinProduction/gatus/k8stest"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
@@ -338,12 +347,17 @@ debug: true
|
|||||||
alerting:
|
alerting:
|
||||||
slack:
|
slack:
|
||||||
webhook-url: "http://example.com"
|
webhook-url: "http://example.com"
|
||||||
|
discord:
|
||||||
|
webhook-url: "http://example.org"
|
||||||
pagerduty:
|
pagerduty:
|
||||||
integration-key: "00000000000000000000000000000000"
|
integration-key: "00000000000000000000000000000000"
|
||||||
messagebird:
|
messagebird:
|
||||||
access-key: "1"
|
access-key: "1"
|
||||||
originator: "31619191918"
|
originator: "31619191918"
|
||||||
recipients: "31619191919"
|
recipients: "31619191919"
|
||||||
|
telegram:
|
||||||
|
token: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
||||||
|
id: 0123456789
|
||||||
services:
|
services:
|
||||||
- name: twinnation
|
- name: twinnation
|
||||||
url: https://twinnation.org/health
|
url: https://twinnation.org/health
|
||||||
@@ -356,6 +370,10 @@ services:
|
|||||||
success-threshold: 5
|
success-threshold: 5
|
||||||
description: "Healthcheck failed 7 times in a row"
|
description: "Healthcheck failed 7 times in a row"
|
||||||
- type: messagebird
|
- type: messagebird
|
||||||
|
- type: discord
|
||||||
|
enabled: true
|
||||||
|
failure-threshold: 10
|
||||||
|
- type: telegram
|
||||||
enabled: true
|
enabled: true
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
@@ -369,6 +387,7 @@ services:
|
|||||||
if config.Metrics {
|
if config.Metrics {
|
||||||
t.Error("Metrics should've been false by default")
|
t.Error("Metrics should've been false by default")
|
||||||
}
|
}
|
||||||
|
// Alerting providers
|
||||||
if config.Alerting == nil {
|
if config.Alerting == nil {
|
||||||
t.Fatal("config.Alerting shouldn't have been nil")
|
t.Fatal("config.Alerting shouldn't have been nil")
|
||||||
}
|
}
|
||||||
@@ -381,12 +400,13 @@ services:
|
|||||||
if config.Alerting.PagerDuty == nil || !config.Alerting.PagerDuty.IsValid() {
|
if config.Alerting.PagerDuty == nil || !config.Alerting.PagerDuty.IsValid() {
|
||||||
t.Fatal("PagerDuty alerting config should've been valid")
|
t.Fatal("PagerDuty alerting config should've been valid")
|
||||||
}
|
}
|
||||||
if config.Alerting.Messagebird == nil || !config.Alerting.Messagebird.IsValid() {
|
|
||||||
t.Fatal("Messagebird alerting config should've been valid")
|
|
||||||
}
|
|
||||||
if config.Alerting.PagerDuty.IntegrationKey != "00000000000000000000000000000000" {
|
if config.Alerting.PagerDuty.IntegrationKey != "00000000000000000000000000000000" {
|
||||||
t.Errorf("PagerDuty integration key should've been %s, but was %s", "00000000000000000000000000000000", config.Alerting.PagerDuty.IntegrationKey)
|
t.Errorf("PagerDuty integration key should've been %s, but was %s", "00000000000000000000000000000000", config.Alerting.PagerDuty.IntegrationKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Alerting.Messagebird == nil || !config.Alerting.Messagebird.IsValid() {
|
||||||
|
t.Fatal("Messagebird alerting config should've been valid")
|
||||||
|
}
|
||||||
if config.Alerting.Messagebird.AccessKey != "1" {
|
if config.Alerting.Messagebird.AccessKey != "1" {
|
||||||
t.Errorf("Messagebird access key should've been %s, but was %s", "1", config.Alerting.Messagebird.AccessKey)
|
t.Errorf("Messagebird access key should've been %s, but was %s", "1", config.Alerting.Messagebird.AccessKey)
|
||||||
}
|
}
|
||||||
@@ -396,6 +416,24 @@ services:
|
|||||||
if config.Alerting.Messagebird.Recipients != "31619191919" {
|
if config.Alerting.Messagebird.Recipients != "31619191919" {
|
||||||
t.Errorf("Messagebird to recipients should've been %s, but was %s", "31619191919", config.Alerting.Messagebird.Recipients)
|
t.Errorf("Messagebird to recipients should've been %s, but was %s", "31619191919", config.Alerting.Messagebird.Recipients)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Alerting.Discord == nil || !config.Alerting.Discord.IsValid() {
|
||||||
|
t.Fatal("Discord alerting config should've been valid")
|
||||||
|
}
|
||||||
|
if config.Alerting.Discord.WebhookURL != "http://example.org" {
|
||||||
|
t.Errorf("Discord webhook should've been %s, but was %s", "http://example.org", config.Alerting.Discord.WebhookURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Alerting.Telegram.Token != "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" {
|
||||||
|
t.Errorf("Telegram token should've been %s, but was %s", "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", config.Alerting.Telegram.Token)
|
||||||
|
}
|
||||||
|
if config.Alerting.Telegram.ID != "0123456789" {
|
||||||
|
t.Errorf("Telegram ID should've been %s, but was %s", "012345689", config.Alerting.Telegram.ID)
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(config, core.DiscordAlert) != config.Alerting.Discord {
|
||||||
|
t.Error("expected discord configuration")
|
||||||
|
}
|
||||||
|
// Services
|
||||||
if len(config.Services) != 1 {
|
if len(config.Services) != 1 {
|
||||||
t.Error("There should've been 1 service")
|
t.Error("There should've been 1 service")
|
||||||
}
|
}
|
||||||
@@ -405,11 +443,12 @@ services:
|
|||||||
if config.Services[0].Interval != 60*time.Second {
|
if config.Services[0].Interval != 60*time.Second {
|
||||||
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
|
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
|
||||||
}
|
}
|
||||||
if config.Services[0].Alerts == nil {
|
if len(config.Services[0].Alerts) != 5 {
|
||||||
t.Fatal("The service alerts shouldn't have been nil")
|
t.Fatal("There should've been 5 alerts configured")
|
||||||
}
|
}
|
||||||
if len(config.Services[0].Alerts) != 3 {
|
|
||||||
t.Fatal("There should've been 3 alert configured")
|
if config.Services[0].Alerts[0].Type != core.SlackAlert {
|
||||||
|
t.Errorf("The type of the alert should've been %s, but it was %s", core.SlackAlert, config.Services[0].Alerts[0].Type)
|
||||||
}
|
}
|
||||||
if !config.Services[0].Alerts[0].Enabled {
|
if !config.Services[0].Alerts[0].Enabled {
|
||||||
t.Error("The alert should've been enabled")
|
t.Error("The alert should've been enabled")
|
||||||
@@ -420,23 +459,32 @@ services:
|
|||||||
if config.Services[0].Alerts[0].SuccessThreshold != 2 {
|
if config.Services[0].Alerts[0].SuccessThreshold != 2 {
|
||||||
t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Services[0].Alerts[0].SuccessThreshold)
|
t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Services[0].Alerts[0].SuccessThreshold)
|
||||||
}
|
}
|
||||||
if config.Services[0].Alerts[1].FailureThreshold != 7 {
|
|
||||||
t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 7, config.Services[0].Alerts[1].FailureThreshold)
|
|
||||||
}
|
|
||||||
if config.Services[0].Alerts[1].SuccessThreshold != 5 {
|
|
||||||
t.Errorf("The success threshold of the alert should've been %d, but it was %d", 5, config.Services[0].Alerts[1].SuccessThreshold)
|
|
||||||
}
|
|
||||||
if config.Services[0].Alerts[0].Type != core.SlackAlert {
|
|
||||||
t.Errorf("The type of the alert should've been %s, but it was %s", core.SlackAlert, config.Services[0].Alerts[0].Type)
|
|
||||||
}
|
|
||||||
if config.Services[0].Alerts[1].Type != core.PagerDutyAlert {
|
if config.Services[0].Alerts[1].Type != core.PagerDutyAlert {
|
||||||
t.Errorf("The type of the alert should've been %s, but it was %s", core.PagerDutyAlert, config.Services[0].Alerts[1].Type)
|
t.Errorf("The type of the alert should've been %s, but it was %s", core.PagerDutyAlert, config.Services[0].Alerts[1].Type)
|
||||||
}
|
}
|
||||||
if config.Services[0].Alerts[1].Description != "Healthcheck failed 7 times in a row" {
|
if config.Services[0].Alerts[1].Description != "Healthcheck failed 7 times in a row" {
|
||||||
t.Errorf("The description of the alert should've been %s, but it was %s", "Healthcheck failed 7 times in a row", config.Services[0].Alerts[0].Description)
|
t.Errorf("The description of the alert should've been %s, but it was %s", "Healthcheck failed 7 times in a row", config.Services[0].Alerts[1].Description)
|
||||||
}
|
}
|
||||||
|
if config.Services[0].Alerts[1].SuccessThreshold != 5 {
|
||||||
|
t.Errorf("The success threshold of the alert should've been %d, but it was %d", 5, config.Services[0].Alerts[1].SuccessThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
if config.Services[0].Alerts[2].Type != core.MessagebirdAlert {
|
if config.Services[0].Alerts[2].Type != core.MessagebirdAlert {
|
||||||
t.Errorf("The type of the alert should've been %s, but it was %s", core.MessagebirdAlert, config.Services[0].Alerts[1].Type)
|
t.Errorf("The type of the alert should've been %s, but it was %s", core.MessagebirdAlert, config.Services[0].Alerts[2].Type)
|
||||||
|
}
|
||||||
|
if config.Services[0].Alerts[2].Enabled {
|
||||||
|
t.Error("The alert should've been disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Services[0].Alerts[3].Type != core.DiscordAlert {
|
||||||
|
t.Errorf("The type of the alert should've been %s, but it was %s", core.DiscordAlert, config.Services[0].Alerts[3].Type)
|
||||||
|
}
|
||||||
|
if config.Services[0].Alerts[3].FailureThreshold != 10 {
|
||||||
|
t.Errorf("The failure threshold of the alert should've been %d, but it was %d", 10, config.Services[0].Alerts[3].FailureThreshold)
|
||||||
|
}
|
||||||
|
if config.Services[0].Alerts[3].SuccessThreshold != 2 {
|
||||||
|
t.Errorf("The default success threshold of the alert should've been %d, but it was %d", 2, config.Services[0].Alerts[3].SuccessThreshold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -809,3 +857,42 @@ kubernetes:
|
|||||||
// TODO: find a way to test this?
|
// TODO: find a way to test this?
|
||||||
t.Error("Function should've panicked because testing with ClusterModeIn isn't supported")
|
t.Error("Function should've panicked because testing with ClusterModeIn isn't supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetAlertingProviderByAlertType(t *testing.T) {
|
||||||
|
cfg := &Config{
|
||||||
|
Alerting: &alerting.Config{
|
||||||
|
Custom: &custom.AlertProvider{},
|
||||||
|
Discord: &discord.AlertProvider{},
|
||||||
|
Mattermost: &mattermost.AlertProvider{},
|
||||||
|
Messagebird: &messagebird.AlertProvider{},
|
||||||
|
PagerDuty: &pagerduty.AlertProvider{},
|
||||||
|
Slack: &slack.AlertProvider{},
|
||||||
|
Telegram: &telegram.AlertProvider{},
|
||||||
|
Twilio: &twilio.AlertProvider{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.CustomAlert) != cfg.Alerting.Custom {
|
||||||
|
t.Error("expected Custom configuration")
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.DiscordAlert) != cfg.Alerting.Discord {
|
||||||
|
t.Error("expected Discord configuration")
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.MattermostAlert) != cfg.Alerting.Mattermost {
|
||||||
|
t.Error("expected Mattermost configuration")
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.MessagebirdAlert) != cfg.Alerting.Messagebird {
|
||||||
|
t.Error("expected Messagebird configuration")
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.PagerDutyAlert) != cfg.Alerting.PagerDuty {
|
||||||
|
t.Error("expected PagerDuty configuration")
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.SlackAlert) != cfg.Alerting.Slack {
|
||||||
|
t.Error("expected Slack configuration")
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.TelegramAlert) != cfg.Alerting.Telegram {
|
||||||
|
t.Error("expected Telegram configuration")
|
||||||
|
}
|
||||||
|
if GetAlertingProviderByAlertType(cfg, core.TwilioAlert) != cfg.Alerting.Twilio {
|
||||||
|
t.Error("expected Twilio configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cache = gocache.NewCache().WithMaxSize(100).WithEvictionPolicy(gocache.LeastRecentlyUsed)
|
cache = gocache.NewCache().WithMaxSize(100).WithEvictionPolicy(gocache.FirstInFirstOut)
|
||||||
|
|
||||||
// staticFolder is the path to the location of the static folder from the root path of the project
|
// staticFolder is the path to the location of the static folder from the root path of the project
|
||||||
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
|
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ var (
|
|||||||
Hostname: "example.org",
|
Hostname: "example.org",
|
||||||
IP: "127.0.0.1",
|
IP: "127.0.0.1",
|
||||||
HTTPStatus: 200,
|
HTTPStatus: 200,
|
||||||
Body: []byte("body"),
|
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
Connected: true,
|
Connected: true,
|
||||||
Success: true,
|
Success: true,
|
||||||
@@ -64,7 +63,6 @@ var (
|
|||||||
Hostname: "example.org",
|
Hostname: "example.org",
|
||||||
IP: "127.0.0.1",
|
IP: "127.0.0.1",
|
||||||
HTTPStatus: 200,
|
HTTPStatus: 200,
|
||||||
Body: []byte("body"),
|
|
||||||
Errors: []string{"error-1", "error-2"},
|
Errors: []string{"error-1", "error-2"},
|
||||||
Connected: true,
|
Connected: true,
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|||||||
+11
-5
@@ -40,8 +40,11 @@ type Alert struct {
|
|||||||
type AlertType string
|
type AlertType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SlackAlert is the AlertType for the slack alerting provider
|
// CustomAlert is the AlertType for the custom alerting provider
|
||||||
SlackAlert AlertType = "slack"
|
CustomAlert AlertType = "custom"
|
||||||
|
|
||||||
|
// DiscordAlert is the AlertType for the discord alerting provider
|
||||||
|
DiscordAlert AlertType = "discord"
|
||||||
|
|
||||||
// MattermostAlert is the AlertType for the mattermost alerting provider
|
// MattermostAlert is the AlertType for the mattermost alerting provider
|
||||||
MattermostAlert AlertType = "mattermost"
|
MattermostAlert AlertType = "mattermost"
|
||||||
@@ -52,9 +55,12 @@ const (
|
|||||||
// PagerDutyAlert is the AlertType for the pagerduty alerting provider
|
// PagerDutyAlert is the AlertType for the pagerduty alerting provider
|
||||||
PagerDutyAlert AlertType = "pagerduty"
|
PagerDutyAlert AlertType = "pagerduty"
|
||||||
|
|
||||||
|
// SlackAlert is the AlertType for the slack alerting provider
|
||||||
|
SlackAlert AlertType = "slack"
|
||||||
|
|
||||||
|
// TelegramAlert is the AlertType for the telegram alerting provider
|
||||||
|
TelegramAlert AlertType = "telegram"
|
||||||
|
|
||||||
// TwilioAlert is the AlertType for the twilio alerting provider
|
// TwilioAlert is the AlertType for the twilio alerting provider
|
||||||
TwilioAlert AlertType = "twilio"
|
TwilioAlert AlertType = "twilio"
|
||||||
|
|
||||||
// CustomAlert is the AlertType for the custom alerting provider
|
|
||||||
CustomAlert AlertType = "custom"
|
|
||||||
)
|
)
|
||||||
|
|||||||
+56
-17
@@ -23,7 +23,7 @@ const (
|
|||||||
|
|
||||||
// DNSRCodePlaceholder is a place holder for DNS_RCODE
|
// DNSRCodePlaceholder is a place holder for DNS_RCODE
|
||||||
//
|
//
|
||||||
// Values that could be NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP and REFUSED
|
// Values that could replace the placeholder: NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED
|
||||||
DNSRCodePlaceholder = "[DNS_RCODE]"
|
DNSRCodePlaceholder = "[DNS_RCODE]"
|
||||||
|
|
||||||
// ResponseTimePlaceholder is a placeholder for the request response time, in milliseconds.
|
// ResponseTimePlaceholder is a placeholder for the request response time, in milliseconds.
|
||||||
@@ -51,14 +51,19 @@ const (
|
|||||||
// Usage: len([BODY].articles) == 10, len([BODY].name) > 5
|
// Usage: len([BODY].articles) == 10, len([BODY].name) > 5
|
||||||
LengthFunctionPrefix = "len("
|
LengthFunctionPrefix = "len("
|
||||||
|
|
||||||
|
// HasFunctionPrefix is the prefix for the has function
|
||||||
|
//
|
||||||
|
// Usage: has([BODY].errors) == true
|
||||||
|
HasFunctionPrefix = "has("
|
||||||
|
|
||||||
// PatternFunctionPrefix is the prefix for the pattern function
|
// PatternFunctionPrefix is the prefix for the pattern function
|
||||||
//
|
//
|
||||||
// Usage: pat(192.168.*.*)
|
// Usage: [IP] == pat(192.168.*.*)
|
||||||
PatternFunctionPrefix = "pat("
|
PatternFunctionPrefix = "pat("
|
||||||
|
|
||||||
// AnyFunctionPrefix is the prefix for the any function
|
// AnyFunctionPrefix is the prefix for the any function
|
||||||
//
|
//
|
||||||
// Usage: any(1.1.1.1, 1.0.0.1)
|
// Usage: [IP] == any(1.1.1.1, 1.0.0.1)
|
||||||
AnyFunctionPrefix = "any("
|
AnyFunctionPrefix = "any("
|
||||||
|
|
||||||
// FunctionSuffix is the suffix for all functions
|
// FunctionSuffix is the suffix for all functions
|
||||||
@@ -66,12 +71,20 @@ const (
|
|||||||
|
|
||||||
// InvalidConditionElementSuffix is the suffix that will be appended to an invalid condition
|
// InvalidConditionElementSuffix is the suffix that will be appended to an invalid condition
|
||||||
InvalidConditionElementSuffix = "(INVALID)"
|
InvalidConditionElementSuffix = "(INVALID)"
|
||||||
|
|
||||||
|
// maximumLengthBeforeTruncatingWhenComparedWithPattern is the maximum length an element being compared to a
|
||||||
|
// pattern can have.
|
||||||
|
//
|
||||||
|
// This is only used for aesthetic purposes; it does not influence whether the condition evaluation results in a
|
||||||
|
// success or a failure
|
||||||
|
maximumLengthBeforeTruncatingWhenComparedWithPattern = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
// Condition is a condition that needs to be met in order for a Service to be considered healthy.
|
// Condition is a condition that needs to be met in order for a Service to be considered healthy.
|
||||||
type Condition string
|
type Condition string
|
||||||
|
|
||||||
// evaluate the Condition with the Result of the health check
|
// evaluate the Condition with the Result of the health check
|
||||||
|
// TODO: Add a mandatory space between each operators (e.g. " == " instead of "==") (BREAKING CHANGE)
|
||||||
func (c Condition) evaluate(result *Result) bool {
|
func (c Condition) evaluate(result *Result) bool {
|
||||||
condition := string(c)
|
condition := string(c)
|
||||||
success := false
|
success := false
|
||||||
@@ -123,6 +136,12 @@ func (c Condition) evaluate(result *Result) bool {
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasBodyPlaceholder checks whether the condition has a BodyPlaceholder
|
||||||
|
// Used for determining whether the response body should be read or not
|
||||||
|
func (c Condition) hasBodyPlaceholder() bool {
|
||||||
|
return strings.Contains(string(c), BodyPlaceholder)
|
||||||
|
}
|
||||||
|
|
||||||
// isEqual compares two strings.
|
// isEqual compares two strings.
|
||||||
//
|
//
|
||||||
// Supports the pattern and the any functions.
|
// Supports the pattern and the any functions.
|
||||||
@@ -181,7 +200,7 @@ func isEqual(first, second string) bool {
|
|||||||
func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) {
|
func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) {
|
||||||
parameters := make([]string, len(elements))
|
parameters := make([]string, len(elements))
|
||||||
resolvedParameters := make([]string, len(elements))
|
resolvedParameters := make([]string, len(elements))
|
||||||
body := strings.TrimSpace(string(result.Body))
|
body := strings.TrimSpace(string(result.body))
|
||||||
for i, element := range elements {
|
for i, element := range elements {
|
||||||
element = strings.TrimSpace(element)
|
element = strings.TrimSpace(element)
|
||||||
parameters[i] = element
|
parameters[i] = element
|
||||||
@@ -203,26 +222,39 @@ func sanitizeAndResolve(elements []string, result *Result) ([]string, []string)
|
|||||||
default:
|
default:
|
||||||
// if contains the BodyPlaceholder, then evaluate json path
|
// if contains the BodyPlaceholder, then evaluate json path
|
||||||
if strings.Contains(element, BodyPlaceholder) {
|
if strings.Contains(element, BodyPlaceholder) {
|
||||||
wantLength := false
|
checkingForLength := false
|
||||||
|
checkingForExistence := false
|
||||||
if strings.HasPrefix(element, LengthFunctionPrefix) && strings.HasSuffix(element, FunctionSuffix) {
|
if strings.HasPrefix(element, LengthFunctionPrefix) && strings.HasSuffix(element, FunctionSuffix) {
|
||||||
wantLength = true
|
checkingForLength = true
|
||||||
element = strings.TrimSuffix(strings.TrimPrefix(element, LengthFunctionPrefix), FunctionSuffix)
|
element = strings.TrimSuffix(strings.TrimPrefix(element, LengthFunctionPrefix), FunctionSuffix)
|
||||||
}
|
}
|
||||||
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.TrimPrefix(element, BodyPlaceholder+"."), result.Body)
|
if strings.HasPrefix(element, HasFunctionPrefix) && strings.HasSuffix(element, FunctionSuffix) {
|
||||||
if err != nil {
|
checkingForExistence = true
|
||||||
if err.Error() != "unexpected end of JSON input" {
|
element = strings.TrimSuffix(strings.TrimPrefix(element, HasFunctionPrefix), FunctionSuffix)
|
||||||
result.Errors = append(result.Errors, err.Error())
|
}
|
||||||
}
|
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.TrimPrefix(element, BodyPlaceholder+"."), result.body)
|
||||||
if wantLength {
|
if checkingForExistence {
|
||||||
element = LengthFunctionPrefix + element + FunctionSuffix + " " + InvalidConditionElementSuffix
|
if err != nil {
|
||||||
|
element = "false"
|
||||||
} else {
|
} else {
|
||||||
element = element + " " + InvalidConditionElementSuffix
|
element = "true"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if wantLength {
|
if err != nil {
|
||||||
element = strconv.Itoa(resolvedElementLength)
|
if err.Error() != "unexpected end of JSON input" {
|
||||||
|
result.Errors = append(result.Errors, err.Error())
|
||||||
|
}
|
||||||
|
if checkingForLength {
|
||||||
|
element = LengthFunctionPrefix + element + FunctionSuffix + " " + InvalidConditionElementSuffix
|
||||||
|
} else {
|
||||||
|
element = element + " " + InvalidConditionElementSuffix
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
element = resolvedElement
|
if checkingForLength {
|
||||||
|
element = strconv.Itoa(resolvedElementLength)
|
||||||
|
} else {
|
||||||
|
element = resolvedElement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,6 +290,13 @@ func prettify(parameters []string, resolvedParameters []string, operator string)
|
|||||||
if strings.HasSuffix(resolvedParameters[0], InvalidConditionElementSuffix) || strings.HasSuffix(resolvedParameters[1], InvalidConditionElementSuffix) {
|
if strings.HasSuffix(resolvedParameters[0], InvalidConditionElementSuffix) || strings.HasSuffix(resolvedParameters[1], InvalidConditionElementSuffix) {
|
||||||
return resolvedParameters[0] + " " + operator + " " + resolvedParameters[1]
|
return resolvedParameters[0] + " " + operator + " " + resolvedParameters[1]
|
||||||
}
|
}
|
||||||
|
// If using the pattern function, truncate the parameter it's being compared to if said parameter is long enough
|
||||||
|
if strings.HasPrefix(parameters[0], PatternFunctionPrefix) && strings.HasSuffix(parameters[0], FunctionSuffix) && len(resolvedParameters[1]) > maximumLengthBeforeTruncatingWhenComparedWithPattern {
|
||||||
|
resolvedParameters[1] = fmt.Sprintf("%.25s...(truncated)", resolvedParameters[1])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(parameters[1], PatternFunctionPrefix) && strings.HasSuffix(parameters[1], FunctionSuffix) && len(resolvedParameters[0]) > maximumLengthBeforeTruncatingWhenComparedWithPattern {
|
||||||
|
resolvedParameters[0] = fmt.Sprintf("%.25s...(truncated)", resolvedParameters[0])
|
||||||
|
}
|
||||||
// First element is a placeholder
|
// First element is a placeholder
|
||||||
if parameters[0] != resolvedParameters[0] && parameters[1] == resolvedParameters[1] {
|
if parameters[0] != resolvedParameters[0] && parameters[1] == resolvedParameters[1] {
|
||||||
return parameters[0] + " (" + resolvedParameters[0] + ") " + operator + " " + parameters[1]
|
return parameters[0] + " (" + resolvedParameters[0] + ") " + operator + " " + parameters[1]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "testing"
|
|||||||
func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) {
|
func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) {
|
||||||
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
|
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
|
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
|
||||||
condition.evaluate(result)
|
condition.evaluate(result)
|
||||||
}
|
}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@@ -14,7 +14,7 @@ func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) {
|
|||||||
func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) {
|
func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) {
|
||||||
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
|
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")}
|
result := &Result{body: []byte("{\"name\": \"bob.doe\"}")}
|
||||||
condition.evaluate(result)
|
condition.evaluate(result)
|
||||||
}
|
}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@@ -23,7 +23,7 @@ func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) {
|
|||||||
func BenchmarkCondition_evaluateWithBodyString(b *testing.B) {
|
func BenchmarkCondition_evaluateWithBodyString(b *testing.B) {
|
||||||
condition := Condition("[BODY].name == john.doe")
|
condition := Condition("[BODY].name == john.doe")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
|
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
|
||||||
condition.evaluate(result)
|
condition.evaluate(result)
|
||||||
}
|
}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@@ -32,7 +32,7 @@ func BenchmarkCondition_evaluateWithBodyString(b *testing.B) {
|
|||||||
func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) {
|
func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) {
|
||||||
condition := Condition("[BODY].name == john.doe")
|
condition := Condition("[BODY].name == john.doe")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")}
|
result := &Result{body: []byte("{\"name\": \"bob.doe\"}")}
|
||||||
condition.evaluate(result)
|
condition.evaluate(result)
|
||||||
}
|
}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@@ -41,7 +41,7 @@ func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) {
|
|||||||
func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) {
|
func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) {
|
||||||
condition := Condition("len([BODY].name) == 8")
|
condition := Condition("len([BODY].name) == 8")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
|
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
|
||||||
condition.evaluate(result)
|
condition.evaluate(result)
|
||||||
}
|
}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@@ -50,7 +50,7 @@ func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) {
|
|||||||
func BenchmarkCondition_evaluateWithBodyStringLenFailure(b *testing.B) {
|
func BenchmarkCondition_evaluateWithBodyStringLenFailure(b *testing.B) {
|
||||||
condition := Condition("len([BODY].name) == 8")
|
condition := Condition("len([BODY].name) == 8")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")}
|
result := &Result{body: []byte("{\"name\": \"bob.doe\"}")}
|
||||||
condition.evaluate(result)
|
condition.evaluate(result)
|
||||||
}
|
}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|||||||
+446
-646
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -60,26 +60,26 @@ func (d *DNS) query(url string, result *Result) {
|
|||||||
switch rr.Header().Rrtype {
|
switch rr.Header().Rrtype {
|
||||||
case dns.TypeA:
|
case dns.TypeA:
|
||||||
if a, ok := rr.(*dns.A); ok {
|
if a, ok := rr.(*dns.A); ok {
|
||||||
result.Body = []byte(a.A.String())
|
result.body = []byte(a.A.String())
|
||||||
}
|
}
|
||||||
case dns.TypeAAAA:
|
case dns.TypeAAAA:
|
||||||
if aaaa, ok := rr.(*dns.AAAA); ok {
|
if aaaa, ok := rr.(*dns.AAAA); ok {
|
||||||
result.Body = []byte(aaaa.AAAA.String())
|
result.body = []byte(aaaa.AAAA.String())
|
||||||
}
|
}
|
||||||
case dns.TypeCNAME:
|
case dns.TypeCNAME:
|
||||||
if cname, ok := rr.(*dns.CNAME); ok {
|
if cname, ok := rr.(*dns.CNAME); ok {
|
||||||
result.Body = []byte(cname.Target)
|
result.body = []byte(cname.Target)
|
||||||
}
|
}
|
||||||
case dns.TypeMX:
|
case dns.TypeMX:
|
||||||
if mx, ok := rr.(*dns.MX); ok {
|
if mx, ok := rr.(*dns.MX); ok {
|
||||||
result.Body = []byte(mx.Mx)
|
result.body = []byte(mx.Mx)
|
||||||
}
|
}
|
||||||
case dns.TypeNS:
|
case dns.TypeNS:
|
||||||
if ns, ok := rr.(*dns.NS); ok {
|
if ns, ok := rr.(*dns.NS); ok {
|
||||||
result.Body = []byte(ns.Ns)
|
result.body = []byte(ns.Ns)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
result.Body = []byte("query type is not supported yet")
|
result.body = []byte("query type is not supported yet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -91,12 +91,12 @@ func TestIntegrationQuery(t *testing.T) {
|
|||||||
|
|
||||||
if test.inputDNS.QueryType == "NS" {
|
if test.inputDNS.QueryType == "NS" {
|
||||||
// Because there are often multiple nameservers backing a single domain, we'll only look at the suffix
|
// Because there are often multiple nameservers backing a single domain, we'll only look at the suffix
|
||||||
if !pattern.Match(test.expectedBody, string(result.Body)) {
|
if !pattern.Match(test.expectedBody, string(result.body)) {
|
||||||
t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody)
|
t.Errorf("got %s, expected result %s,", string(result.body), test.expectedBody)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if string(result.Body) != test.expectedBody {
|
if string(result.body) != test.expectedBody {
|
||||||
t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody)
|
t.Errorf("got %s, expected result %s,", string(result.body), test.expectedBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+8
-4
@@ -12,10 +12,7 @@ type Result struct {
|
|||||||
// DNSRCode is the response code of a DNS query in a human readable format
|
// DNSRCode is the response code of a DNS query in a human readable format
|
||||||
DNSRCode string `json:"-"`
|
DNSRCode string `json:"-"`
|
||||||
|
|
||||||
// Body is the response body
|
// Hostname extracted from Service.URL
|
||||||
Body []byte `json:"-"`
|
|
||||||
|
|
||||||
// Hostname extracted from the Service URL
|
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
|
|
||||||
// IP resolved from the Service URL
|
// IP resolved from the Service URL
|
||||||
@@ -41,4 +38,11 @@ type Result struct {
|
|||||||
|
|
||||||
// CertificateExpiration is the duration before the certificate expires
|
// CertificateExpiration is the duration before the certificate expires
|
||||||
CertificateExpiration time.Duration `json:"-"`
|
CertificateExpiration time.Duration `json:"-"`
|
||||||
|
|
||||||
|
// body is the response body
|
||||||
|
//
|
||||||
|
// Note that this variable is only used during the evaluation of a service's health.
|
||||||
|
// This means that the call Service.EvaluateHealth both populates the body (if necessary)
|
||||||
|
// and sets it to nil after the evaluation has been completed.
|
||||||
|
body []byte
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-16
@@ -58,22 +58,10 @@ func NewServiceStatus(service *Service) *ServiceStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShallowCopy creates a shallow copy of ServiceStatus
|
// WithResultPagination returns a shallow copy of the ServiceStatus with only the results
|
||||||
func (ss *ServiceStatus) ShallowCopy() *ServiceStatus {
|
|
||||||
return &ServiceStatus{
|
|
||||||
Name: ss.Name,
|
|
||||||
Group: ss.Group,
|
|
||||||
Key: ss.Key,
|
|
||||||
Results: ss.Results,
|
|
||||||
Events: ss.Events,
|
|
||||||
Uptime: ss.Uptime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithResultPagination makes a shallow copy of the ServiceStatus with only the results
|
|
||||||
// within the range defined by the page and pageSize parameters
|
// within the range defined by the page and pageSize parameters
|
||||||
func (ss *ServiceStatus) WithResultPagination(page, pageSize int) *ServiceStatus {
|
func (ss ServiceStatus) WithResultPagination(page, pageSize int) *ServiceStatus {
|
||||||
shallowCopy := ss.ShallowCopy()
|
shallowCopy := ss
|
||||||
numberOfResults := len(shallowCopy.Results)
|
numberOfResults := len(shallowCopy.Results)
|
||||||
start := numberOfResults - (page * pageSize)
|
start := numberOfResults - (page * pageSize)
|
||||||
end := numberOfResults - ((page - 1) * pageSize)
|
end := numberOfResults - ((page - 1) * pageSize)
|
||||||
@@ -90,7 +78,7 @@ func (ss *ServiceStatus) WithResultPagination(page, pageSize int) *ServiceStatus
|
|||||||
} else {
|
} else {
|
||||||
shallowCopy.Results = shallowCopy.Results[start:end]
|
shallowCopy.Results = shallowCopy.Results[start:end]
|
||||||
}
|
}
|
||||||
return shallowCopy
|
return &shallowCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddResult adds a Result to ServiceStatus.Results and makes sure that there are
|
// AddResult adds a Result to ServiceStatus.Results and makes sure that there are
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
firstCondition = Condition("[STATUS] == 200")
|
||||||
|
secondCondition = Condition("[RESPONSE_TIME] < 500")
|
||||||
|
thirdCondition = Condition("[CERTIFICATE_EXPIRATION] < 72h")
|
||||||
|
|
||||||
|
timestamp = time.Now()
|
||||||
|
|
||||||
|
testService = Service{
|
||||||
|
Name: "name",
|
||||||
|
Group: "group",
|
||||||
|
URL: "https://example.org/what/ever",
|
||||||
|
Method: "GET",
|
||||||
|
Body: "body",
|
||||||
|
Interval: 30 * time.Second,
|
||||||
|
Conditions: []*Condition{&firstCondition, &secondCondition, &thirdCondition},
|
||||||
|
Alerts: nil,
|
||||||
|
Insecure: false,
|
||||||
|
NumberOfFailuresInARow: 0,
|
||||||
|
NumberOfSuccessesInARow: 0,
|
||||||
|
}
|
||||||
|
testSuccessfulResult = Result{
|
||||||
|
Hostname: "example.org",
|
||||||
|
IP: "127.0.0.1",
|
||||||
|
HTTPStatus: 200,
|
||||||
|
body: []byte("body"),
|
||||||
|
Errors: nil,
|
||||||
|
Connected: true,
|
||||||
|
Success: true,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Duration: 150 * time.Millisecond,
|
||||||
|
CertificateExpiration: 10 * time.Hour,
|
||||||
|
ConditionResults: []*ConditionResult{
|
||||||
|
{
|
||||||
|
Condition: "[STATUS] == 200",
|
||||||
|
Success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: "[RESPONSE_TIME] < 500",
|
||||||
|
Success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: "[CERTIFICATE_EXPIRATION] < 72h",
|
||||||
|
Success: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testUnsuccessfulResult = Result{
|
||||||
|
Hostname: "example.org",
|
||||||
|
IP: "127.0.0.1",
|
||||||
|
HTTPStatus: 200,
|
||||||
|
body: []byte("body"),
|
||||||
|
Errors: []string{"error-1", "error-2"},
|
||||||
|
Connected: true,
|
||||||
|
Success: false,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Duration: 750 * time.Millisecond,
|
||||||
|
CertificateExpiration: 10 * time.Hour,
|
||||||
|
ConditionResults: []*ConditionResult{
|
||||||
|
{
|
||||||
|
Condition: "[STATUS] == 200",
|
||||||
|
Success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: "[RESPONSE_TIME] < 500",
|
||||||
|
Success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: "[CERTIFICATE_EXPIRATION] < 72h",
|
||||||
|
Success: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkServiceStatus_WithResultPagination(b *testing.B) {
|
||||||
|
service := &testService
|
||||||
|
serviceStatus := NewServiceStatus(service)
|
||||||
|
for i := 0; i < MaximumNumberOfResults; i++ {
|
||||||
|
serviceStatus.AddResult(&testSuccessfulResult)
|
||||||
|
}
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
serviceStatus.WithResultPagination(1, 20)
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ func TestServiceStatus_AddResult(t *testing.T) {
|
|||||||
serviceStatus.AddResult(&Result{Timestamp: time.Now()})
|
serviceStatus.AddResult(&Result{Timestamp: time.Now()})
|
||||||
}
|
}
|
||||||
if len(serviceStatus.Results) != MaximumNumberOfResults {
|
if len(serviceStatus.Results) != MaximumNumberOfResults {
|
||||||
t.Errorf("expected serviceStatus.Results to not exceed a length of 20")
|
t.Errorf("expected serviceStatus.Results to not exceed a length of %d", MaximumNumberOfResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -80,7 +80,7 @@ type Service struct {
|
|||||||
// NumberOfFailuresInARow is the number of unsuccessful evaluations in a row
|
// NumberOfFailuresInARow is the number of unsuccessful evaluations in a row
|
||||||
NumberOfFailuresInARow int
|
NumberOfFailuresInARow int
|
||||||
|
|
||||||
// NumberOfFailuresInARow is the number of successful evaluations in a row
|
// NumberOfSuccessesInARow is the number of successful evaluations in a row
|
||||||
NumberOfSuccessesInARow int
|
NumberOfSuccessesInARow int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +149,8 @@ func (service *Service) EvaluateHealth() *Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.Timestamp = time.Now()
|
result.Timestamp = time.Now()
|
||||||
|
// No need to keep the body after the service has been evaluated
|
||||||
|
result.body = nil
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +222,12 @@ func (service *Service) call(result *Result) {
|
|||||||
}
|
}
|
||||||
result.HTTPStatus = response.StatusCode
|
result.HTTPStatus = response.StatusCode
|
||||||
result.Connected = response.StatusCode > 0
|
result.Connected = response.StatusCode > 0
|
||||||
result.Body, err = ioutil.ReadAll(response.Body)
|
// Only read the body if there's a condition that uses the BodyPlaceholder
|
||||||
if err != nil {
|
if service.needsToReadBody() {
|
||||||
result.Errors = append(result.Errors, err.Error())
|
result.body, err = ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
result.Errors = append(result.Errors, err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,3 +252,13 @@ func (service *Service) buildHTTPRequest() *http.Request {
|
|||||||
}
|
}
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// needsToReadBody checks if there's any conditions that requires the response body to be read
|
||||||
|
func (service *Service) needsToReadBody() bool {
|
||||||
|
for _, condition := range service.Conditions {
|
||||||
|
if condition.hasBodyPlaceholder() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
+39
-14
@@ -10,7 +10,7 @@ import (
|
|||||||
func TestService_ValidateAndSetDefaults(t *testing.T) {
|
func TestService_ValidateAndSetDefaults(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-health",
|
||||||
URL: "https://twinnation.org/health",
|
URL: "https://twinnation.org/health",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition},
|
||||||
Alerts: []*Alert{{Type: PagerDutyAlert}},
|
Alerts: []*Alert{{Type: PagerDutyAlert}},
|
||||||
@@ -94,7 +94,7 @@ func TestService_ValidateAndSetDefaultsWithDNS(t *testing.T) {
|
|||||||
func TestService_GetAlertsTriggered(t *testing.T) {
|
func TestService_GetAlertsTriggered(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-health",
|
||||||
URL: "https://twinnation.org/health",
|
URL: "https://twinnation.org/health",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition},
|
||||||
Alerts: []*Alert{{Type: PagerDutyAlert, Enabled: true}},
|
Alerts: []*Alert{{Type: PagerDutyAlert, Enabled: true}},
|
||||||
@@ -118,7 +118,7 @@ func TestService_GetAlertsTriggered(t *testing.T) {
|
|||||||
func TestService_buildHTTPRequest(t *testing.T) {
|
func TestService_buildHTTPRequest(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-health",
|
||||||
URL: "https://twinnation.org/health",
|
URL: "https://twinnation.org/health",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition},
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ func TestService_buildHTTPRequest(t *testing.T) {
|
|||||||
func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
|
func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-health",
|
||||||
URL: "https://twinnation.org/health",
|
URL: "https://twinnation.org/health",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition},
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
@@ -161,7 +161,7 @@ func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
|
|||||||
func TestService_buildHTTPRequestWithHostHeader(t *testing.T) {
|
func TestService_buildHTTPRequestWithHostHeader(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-health",
|
||||||
URL: "https://twinnation.org/health",
|
URL: "https://twinnation.org/health",
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition},
|
||||||
@@ -182,13 +182,13 @@ func TestService_buildHTTPRequestWithHostHeader(t *testing.T) {
|
|||||||
func TestService_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
|
func TestService_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-graphql",
|
||||||
URL: "https://twinnation.org/graphql",
|
URL: "https://twinnation.org/graphql",
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition},
|
||||||
GraphQL: true,
|
GraphQL: true,
|
||||||
Body: `{
|
Body: `{
|
||||||
user(gender: "female") {
|
users(gender: "female") {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
gender
|
gender
|
||||||
@@ -206,16 +206,17 @@ func TestService_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
body, _ := ioutil.ReadAll(request.Body)
|
body, _ := ioutil.ReadAll(request.Body)
|
||||||
if !strings.HasPrefix(string(body), "{\"query\":") {
|
if !strings.HasPrefix(string(body), "{\"query\":") {
|
||||||
t.Error("request.Body should've started with '{\"query\":', but it didn't:", string(body))
|
t.Error("request.body should've started with '{\"query\":', but it didn't:", string(body))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationEvaluateHealth(t *testing.T) {
|
func TestIntegrationEvaluateHealth(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 200")
|
condition := Condition("[STATUS] == 200")
|
||||||
|
bodyCondition := Condition("[BODY].status == UP")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-health",
|
||||||
URL: "https://twinnation.org/health",
|
URL: "https://twinnation.org/health",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition, &bodyCondition},
|
||||||
}
|
}
|
||||||
result := service.EvaluateHealth()
|
result := service.EvaluateHealth()
|
||||||
if !result.ConditionResults[0].Success {
|
if !result.ConditionResults[0].Success {
|
||||||
@@ -232,7 +233,7 @@ func TestIntegrationEvaluateHealth(t *testing.T) {
|
|||||||
func TestIntegrationEvaluateHealthWithFailure(t *testing.T) {
|
func TestIntegrationEvaluateHealthWithFailure(t *testing.T) {
|
||||||
condition := Condition("[STATUS] == 500")
|
condition := Condition("[STATUS] == 500")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "twinnation-health",
|
||||||
URL: "https://twinnation.org/health",
|
URL: "https://twinnation.org/health",
|
||||||
Conditions: []*Condition{&condition},
|
Conditions: []*Condition{&condition},
|
||||||
}
|
}
|
||||||
@@ -252,7 +253,7 @@ func TestIntegrationEvaluateHealthForDNS(t *testing.T) {
|
|||||||
conditionSuccess := Condition("[DNS_RCODE] == NOERROR")
|
conditionSuccess := Condition("[DNS_RCODE] == NOERROR")
|
||||||
conditionBody := Condition("[BODY] == 93.184.216.34")
|
conditionBody := Condition("[BODY] == 93.184.216.34")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "TwiNNatioN",
|
Name: "example",
|
||||||
URL: "8.8.8.8",
|
URL: "8.8.8.8",
|
||||||
DNS: &DNS{
|
DNS: &DNS{
|
||||||
QueryType: "A",
|
QueryType: "A",
|
||||||
@@ -275,7 +276,7 @@ func TestIntegrationEvaluateHealthForDNS(t *testing.T) {
|
|||||||
func TestIntegrationEvaluateHealthForICMP(t *testing.T) {
|
func TestIntegrationEvaluateHealthForICMP(t *testing.T) {
|
||||||
conditionSuccess := Condition("[CONNECTED] == true")
|
conditionSuccess := Condition("[CONNECTED] == true")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "ICMP test",
|
Name: "icmp-test",
|
||||||
URL: "icmp://127.0.0.1",
|
URL: "icmp://127.0.0.1",
|
||||||
Conditions: []*Condition{&conditionSuccess},
|
Conditions: []*Condition{&conditionSuccess},
|
||||||
}
|
}
|
||||||
@@ -294,7 +295,7 @@ func TestIntegrationEvaluateHealthForICMP(t *testing.T) {
|
|||||||
func TestService_getIP(t *testing.T) {
|
func TestService_getIP(t *testing.T) {
|
||||||
conditionSuccess := Condition("[CONNECTED] == true")
|
conditionSuccess := Condition("[CONNECTED] == true")
|
||||||
service := Service{
|
service := Service{
|
||||||
Name: "Invalid URL test",
|
Name: "invalid-url-test",
|
||||||
URL: "",
|
URL: "",
|
||||||
Conditions: []*Condition{&conditionSuccess},
|
Conditions: []*Condition{&conditionSuccess},
|
||||||
}
|
}
|
||||||
@@ -304,3 +305,27 @@ func TestService_getIP(t *testing.T) {
|
|||||||
t.Error("service.getIP(result) should've thrown an error because the URL is invalid, thus cannot be parsed")
|
t.Error("service.getIP(result) should've thrown an error because the URL is invalid, thus cannot be parsed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestService_NeedsToReadBody(t *testing.T) {
|
||||||
|
statusCondition := Condition("[STATUS] == 200")
|
||||||
|
bodyCondition := Condition("[BODY].status == UP")
|
||||||
|
bodyConditionWithLength := Condition("len([BODY].tags) > 0")
|
||||||
|
if (&Service{Conditions: []*Condition{&statusCondition}}).needsToReadBody() {
|
||||||
|
t.Error("expected false, got true")
|
||||||
|
}
|
||||||
|
if !(&Service{Conditions: []*Condition{&bodyCondition}}).needsToReadBody() {
|
||||||
|
t.Error("expected true, got false")
|
||||||
|
}
|
||||||
|
if !(&Service{Conditions: []*Condition{&bodyConditionWithLength}}).needsToReadBody() {
|
||||||
|
t.Error("expected true, got false")
|
||||||
|
}
|
||||||
|
if !(&Service{Conditions: []*Condition{&statusCondition, &bodyCondition}}).needsToReadBody() {
|
||||||
|
t.Error("expected true, got false")
|
||||||
|
}
|
||||||
|
if !(&Service{Conditions: []*Condition{&bodyCondition, &statusCondition}}).needsToReadBody() {
|
||||||
|
t.Error("expected true, got false")
|
||||||
|
}
|
||||||
|
if !(&Service{Conditions: []*Condition{&bodyConditionWithLength, &statusCondition}}).needsToReadBody() {
|
||||||
|
t.Error("expected true, got false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+25
-32
@@ -1,15 +1,10 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RFC3339WithoutMinutesAndSeconds is the format defined by RFC3339 (see time.RFC3339) but with the minutes
|
|
||||||
// and seconds hardcoded to 0.
|
|
||||||
RFC3339WithoutMinutesAndSeconds = "2006-01-02T15:00:00Z07:00"
|
|
||||||
|
|
||||||
numberOfHoursInTenDays = 10 * 24
|
numberOfHoursInTenDays = 10 * 24
|
||||||
sevenDays = 7 * 24 * time.Hour
|
sevenDays = 7 * 24 * time.Hour
|
||||||
)
|
)
|
||||||
@@ -25,47 +20,45 @@ type Uptime struct {
|
|||||||
// LastHour is the uptime percentage over the past hour
|
// LastHour is the uptime percentage over the past hour
|
||||||
LastHour float64 `json:"1h"`
|
LastHour float64 `json:"1h"`
|
||||||
|
|
||||||
// SuccessCountPerHour is a map containing the number of successes per hour, per timestamp following the
|
// SuccessfulExecutionsPerHour is a map containing the number of successes (value)
|
||||||
// custom RFC3339WithoutMinutesAndSeconds format
|
// for every hourly unix timestamps (key)
|
||||||
SuccessCountPerHour map[string]uint64 `json:"-"`
|
SuccessfulExecutionsPerHour map[int64]uint64 `json:"-"`
|
||||||
|
|
||||||
// TotalCountPerHour is a map containing the total number of checks per hour, per timestamp following the
|
// TotalExecutionsPerHour is a map containing the total number of checks (value)
|
||||||
// custom RFC3339WithoutMinutesAndSeconds format
|
// for every hourly unix timestamps (key)
|
||||||
TotalCountPerHour map[string]uint64 `json:"-"`
|
TotalExecutionsPerHour map[int64]uint64 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUptime creates a new Uptime
|
// NewUptime creates a new Uptime
|
||||||
func NewUptime() *Uptime {
|
func NewUptime() *Uptime {
|
||||||
return &Uptime{
|
return &Uptime{
|
||||||
SuccessCountPerHour: make(map[string]uint64),
|
SuccessfulExecutionsPerHour: make(map[int64]uint64),
|
||||||
TotalCountPerHour: make(map[string]uint64),
|
TotalExecutionsPerHour: make(map[int64]uint64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessResult processes the result by extracting the relevant from the result and recalculating the uptime
|
// ProcessResult processes the result by extracting the relevant from the result and recalculating the uptime
|
||||||
// if necessary
|
// if necessary
|
||||||
func (uptime *Uptime) ProcessResult(result *Result) {
|
func (uptime *Uptime) ProcessResult(result *Result) {
|
||||||
timestampDateWithHour := result.Timestamp.Format(RFC3339WithoutMinutesAndSeconds)
|
if uptime.SuccessfulExecutionsPerHour == nil || uptime.TotalExecutionsPerHour == nil {
|
||||||
if result.Success {
|
uptime.SuccessfulExecutionsPerHour = make(map[int64]uint64)
|
||||||
uptime.SuccessCountPerHour[timestampDateWithHour]++
|
uptime.TotalExecutionsPerHour = make(map[int64]uint64)
|
||||||
}
|
}
|
||||||
uptime.TotalCountPerHour[timestampDateWithHour]++
|
unixTimestampFlooredAtHour := result.Timestamp.Unix() - (result.Timestamp.Unix() % 3600)
|
||||||
|
if result.Success {
|
||||||
|
uptime.SuccessfulExecutionsPerHour[unixTimestampFlooredAtHour]++
|
||||||
|
}
|
||||||
|
uptime.TotalExecutionsPerHour[unixTimestampFlooredAtHour]++
|
||||||
// Clean up only when we're starting to have too many useless keys
|
// Clean up only when we're starting to have too many useless keys
|
||||||
// Note that this is only triggered when there are more entries than there should be after
|
// Note that this is only triggered when there are more entries than there should be after
|
||||||
// 10 days, despite the fact that we are deleting everything that's older than 7 days.
|
// 10 days, despite the fact that we are deleting everything that's older than 7 days.
|
||||||
// This is to prevent re-iterating on every `ProcessResult` as soon as the uptime has been logged for 7 days.
|
// This is to prevent re-iterating on every `ProcessResult` as soon as the uptime has been logged for 7 days.
|
||||||
if len(uptime.TotalCountPerHour) > numberOfHoursInTenDays {
|
if len(uptime.TotalExecutionsPerHour) > numberOfHoursInTenDays {
|
||||||
sevenDaysAgo := time.Now().Add(-(sevenDays + time.Hour))
|
sevenDaysAgo := time.Now().Add(-(sevenDays + time.Hour)).Unix()
|
||||||
for k := range uptime.TotalCountPerHour {
|
for hourlyUnixTimestamp := range uptime.TotalExecutionsPerHour {
|
||||||
dateWithHour, err := time.Parse(time.RFC3339, k)
|
if sevenDaysAgo > hourlyUnixTimestamp {
|
||||||
if err != nil {
|
delete(uptime.TotalExecutionsPerHour, hourlyUnixTimestamp)
|
||||||
// This shouldn't happen, but we'll log it in case it does happen
|
delete(uptime.SuccessfulExecutionsPerHour, hourlyUnixTimestamp)
|
||||||
log.Println("[uptime][ProcessResult] Failed to parse programmatically generated timestamp:", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if sevenDaysAgo.Unix() > dateWithHour.Unix() {
|
|
||||||
delete(uptime.TotalCountPerHour, k)
|
|
||||||
delete(uptime.SuccessCountPerHour, k)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,9 +85,9 @@ func (uptime *Uptime) recalculate() {
|
|||||||
// The oldest uptime bracket starts 7 days ago, so we'll start from there
|
// The oldest uptime bracket starts 7 days ago, so we'll start from there
|
||||||
timestamp := now.Add(-sevenDays)
|
timestamp := now.Add(-sevenDays)
|
||||||
for now.Sub(timestamp) >= 0 {
|
for now.Sub(timestamp) >= 0 {
|
||||||
timestampDateWithHour := timestamp.Format(RFC3339WithoutMinutesAndSeconds)
|
hourlyUnixTimestamp := timestamp.Unix() - (timestamp.Unix() % 3600)
|
||||||
successCountForTimestamp := uptime.SuccessCountPerHour[timestampDateWithHour]
|
successCountForTimestamp := uptime.SuccessfulExecutionsPerHour[hourlyUnixTimestamp]
|
||||||
totalCountForTimestamp := uptime.TotalCountPerHour[timestampDateWithHour]
|
totalCountForTimestamp := uptime.TotalExecutionsPerHour[hourlyUnixTimestamp]
|
||||||
uptimeBrackets["7d_success"] += successCountForTimestamp
|
uptimeBrackets["7d_success"] += successCountForTimestamp
|
||||||
uptimeBrackets["7d_total"] += totalCountForTimestamp
|
uptimeBrackets["7d_total"] += totalCountForTimestamp
|
||||||
if now.Sub(timestamp) <= 24*time.Hour {
|
if now.Sub(timestamp) <= 24*time.Hour {
|
||||||
|
|||||||
+2
-3
@@ -51,10 +51,9 @@ func TestServiceStatus_AddResultUptimeIsCleaningUpAfterItself(t *testing.T) {
|
|||||||
timestamp := now.Add(-12 * 24 * time.Hour)
|
timestamp := now.Add(-12 * 24 * time.Hour)
|
||||||
for timestamp.Unix() <= now.Unix() {
|
for timestamp.Unix() <= now.Unix() {
|
||||||
serviceStatus.AddResult(&Result{Timestamp: timestamp, Success: true})
|
serviceStatus.AddResult(&Result{Timestamp: timestamp, Success: true})
|
||||||
if len(serviceStatus.Uptime.SuccessCountPerHour) > numberOfHoursInTenDays {
|
if len(serviceStatus.Uptime.SuccessfulExecutionsPerHour) > numberOfHoursInTenDays {
|
||||||
t.Errorf("At no point in time should there be more than %d entries in serviceStatus.SuccessCountPerHour", numberOfHoursInTenDays)
|
t.Errorf("At no point in time should there be more than %d entries in serviceStatus.SuccessfulExecutionsPerHour, but there are %d", numberOfHoursInTenDays, len(serviceStatus.Uptime.SuccessfulExecutionsPerHour))
|
||||||
}
|
}
|
||||||
//fmt.Printf("timestamp=%s; uptimeDuringLastHour=%f; timeAgo=%s\n", timestamp.Format(time.RFC3339), serviceStatus.UptimeDuringLastHour, time.Since(timestamp))
|
|
||||||
if now.Sub(timestamp) > time.Hour && serviceStatus.Uptime.LastHour != 0 {
|
if now.Sub(timestamp) > time.Hour && serviceStatus.Uptime.LastHour != 0 {
|
||||||
t.Error("most recent timestamp > 1h ago, expected serviceStatus.Uptime.LastHour to be 0, got", serviceStatus.Uptime.LastHour)
|
t.Error("most recent timestamp > 1h ago, expected serviceStatus.Uptime.LastHour to be 0, got", serviceStatus.Uptime.LastHour)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/TwinProduction/gatus
|
module github.com/TwinProduction/gatus
|
||||||
|
|
||||||
go 1.15
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.74.0 // indirect
|
cloud.google.com/go v0.74.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
@@ -142,7 +141,6 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
|||||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
@@ -161,7 +159,6 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71
|
|||||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
@@ -172,7 +169,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
|||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
@@ -184,13 +180,11 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
@@ -220,7 +214,6 @@ github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEo
|
|||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
@@ -362,21 +355,18 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
|||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
|
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
|
||||||
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
|
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
|
|
||||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
|
github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
|
||||||
@@ -385,7 +375,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
|
|||||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
|
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
|
||||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
@@ -407,7 +396,6 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ
|
|||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
@@ -418,7 +406,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
@@ -459,7 +446,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
@@ -481,7 +467,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
@@ -531,7 +516,6 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/
|
|||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
@@ -542,7 +526,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -556,7 +539,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -610,7 +592,6 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -618,7 +599,6 @@ golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -626,7 +606,6 @@ golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fq
|
|||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||||
@@ -634,7 +613,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -668,7 +646,6 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
|
|
||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
@@ -694,7 +671,6 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
|
|||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -719,7 +695,6 @@ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ
|
|||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
@@ -787,14 +762,12 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
@@ -810,12 +783,10 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
|||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
@@ -847,7 +818,6 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|||||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
||||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ func TestEval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEvalWithInvalidPath(t *testing.T) {
|
||||||
|
path := "errors"
|
||||||
|
data := `{}`
|
||||||
|
_, _, err := Eval(path, []byte(data))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error, but got", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEvalWithLongSimpleWalk(t *testing.T) {
|
func TestEvalWithLongSimpleWalk(t *testing.T) {
|
||||||
path := "long.simple.walk"
|
path := "long.simple.walk"
|
||||||
data := `{"long": {"simple": {"walk": "value"}}}`
|
data := `{"long": {"simple": {"walk": "value"}}}`
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (s *Store) GetServiceStatusByKey(key string) *core.ServiceStatus {
|
|||||||
if serviceStatus == nil {
|
if serviceStatus == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return serviceStatus.(*core.ServiceStatus).ShallowCopy()
|
return serviceStatus.(*core.ServiceStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert adds the observed result for the specified service into the store
|
// Insert adds the observed result for the specified service into the store
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ var (
|
|||||||
Hostname: "example.org",
|
Hostname: "example.org",
|
||||||
IP: "127.0.0.1",
|
IP: "127.0.0.1",
|
||||||
HTTPStatus: 200,
|
HTTPStatus: 200,
|
||||||
Body: []byte("body"),
|
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
Connected: true,
|
Connected: true,
|
||||||
Success: true,
|
Success: true,
|
||||||
@@ -59,7 +58,6 @@ var (
|
|||||||
Hostname: "example.org",
|
Hostname: "example.org",
|
||||||
IP: "127.0.0.1",
|
IP: "127.0.0.1",
|
||||||
HTTPStatus: 200,
|
HTTPStatus: 200,
|
||||||
Body: []byte("body"),
|
|
||||||
Errors: []string{"error-1", "error-2"},
|
Errors: []string{"error-1", "error-2"},
|
||||||
Connected: true,
|
Connected: true,
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -107,9 +105,6 @@ func TestStore_Insert(t *testing.T) {
|
|||||||
if r.DNSRCode != expectedResult.DNSRCode {
|
if r.DNSRCode != expectedResult.DNSRCode {
|
||||||
t.Errorf("Result at index %d should've had a DNSRCode of %s, but was actually %s", i, expectedResult.DNSRCode, r.DNSRCode)
|
t.Errorf("Result at index %d should've had a DNSRCode of %s, but was actually %s", i, expectedResult.DNSRCode, r.DNSRCode)
|
||||||
}
|
}
|
||||||
if len(r.Body) != len(expectedResult.Body) {
|
|
||||||
t.Errorf("Result at index %d should've had a body of length %d, but was actually %d", i, len(expectedResult.Body), len(r.Body))
|
|
||||||
}
|
|
||||||
if r.Hostname != expectedResult.Hostname {
|
if r.Hostname != expectedResult.Hostname {
|
||||||
t.Errorf("Result at index %d should've had a Hostname of %s, but was actually %s", i, expectedResult.Hostname, r.Hostname)
|
t.Errorf("Result at index %d should've had a Hostname of %s, but was actually %s", i, expectedResult.Hostname, r.Hostname)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ var (
|
|||||||
Hostname: "example.org",
|
Hostname: "example.org",
|
||||||
IP: "127.0.0.1",
|
IP: "127.0.0.1",
|
||||||
HTTPStatus: 200,
|
HTTPStatus: 200,
|
||||||
Body: []byte("body"),
|
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
Connected: true,
|
Connected: true,
|
||||||
Success: true,
|
Success: true,
|
||||||
@@ -58,7 +57,6 @@ var (
|
|||||||
Hostname: "example.org",
|
Hostname: "example.org",
|
||||||
IP: "127.0.0.1",
|
IP: "127.0.0.1",
|
||||||
HTTPStatus: 200,
|
HTTPStatus: 200,
|
||||||
Body: []byte("body"),
|
|
||||||
Errors: []string{"error-1", "error-2"},
|
Errors: []string{"error-1", "error-2"},
|
||||||
Connected: true,
|
Connected: true,
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -127,8 +125,11 @@ func BenchmarkStore_Insert(b *testing.B) {
|
|||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
b.Run(scenario.Name, func(b *testing.B) {
|
b.Run(scenario.Name, func(b *testing.B) {
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
scenario.Store.Insert(&testService, &testSuccessfulResult)
|
if n%100 == 0 {
|
||||||
scenario.Store.Insert(&testService, &testUnsuccessfulResult)
|
scenario.Store.Insert(&testService, &testSuccessfulResult)
|
||||||
|
} else {
|
||||||
|
scenario.Store.Insert(&testService, &testUnsuccessfulResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package watchdog
|
package watchdog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -37,26 +36,22 @@ func monitor(service *core.Service) {
|
|||||||
monitoringMutex.Lock()
|
monitoringMutex.Lock()
|
||||||
}
|
}
|
||||||
if cfg.Debug {
|
if cfg.Debug {
|
||||||
log.Printf("[watchdog][monitor] Monitoring serviceName=%s", service.Name)
|
log.Printf("[watchdog][monitor] Monitoring group=%s; service=%s", service.Group, service.Name)
|
||||||
}
|
}
|
||||||
result := service.EvaluateHealth()
|
result := service.EvaluateHealth()
|
||||||
metric.PublishMetricsForService(service, result)
|
metric.PublishMetricsForService(service, result)
|
||||||
UpdateServiceStatuses(service, result)
|
UpdateServiceStatuses(service, result)
|
||||||
var extra string
|
|
||||||
if !result.Success {
|
|
||||||
extra = fmt.Sprintf("responseBody=%s", result.Body)
|
|
||||||
}
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[watchdog][monitor] Monitored serviceName=%s; success=%v; errors=%d; requestDuration=%s; %s",
|
"[watchdog][monitor] Monitored group=%s; service=%s; success=%v; errors=%d; duration=%s",
|
||||||
|
service.Group,
|
||||||
service.Name,
|
service.Name,
|
||||||
result.Success,
|
result.Success,
|
||||||
len(result.Errors),
|
len(result.Errors),
|
||||||
result.Duration.Round(time.Millisecond),
|
result.Duration.Round(time.Millisecond),
|
||||||
extra,
|
|
||||||
)
|
)
|
||||||
HandleAlerting(service, result)
|
HandleAlerting(service, result)
|
||||||
if cfg.Debug {
|
if cfg.Debug {
|
||||||
log.Printf("[watchdog][monitor] Waiting for interval=%s before monitoring serviceName=%s again", service.Interval, service.Name)
|
log.Printf("[watchdog][monitor] Waiting for interval=%s before monitoring group=%s service=%s again", service.Interval, service.Group, service.Name)
|
||||||
}
|
}
|
||||||
if !cfg.DisableMonitoringLock {
|
if !cfg.DisableMonitoringLock {
|
||||||
monitoringMutex.Unlock()
|
monitoringMutex.Unlock()
|
||||||
|
|||||||
Generated
+4065
-2902
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,13 @@
|
|||||||
<span v-if="data.results && data.results.length" class='text-gray-500 font-light'> | {{ data.results[data.results.length - 1].hostname }}</span>
|
<span v-if="data.results && data.results.length" class='text-gray-500 font-light'> | {{ data.results[data.results.length - 1].hostname }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class='w-1/4 text-right'>
|
<div class='w-1/4 text-right'>
|
||||||
<span class='font-light status-min-max-ms' v-if="data.results && data.results.length">
|
<span class='font-light overflow-x-hidden cursor-pointer select-none' v-if="data.results && data.results.length" @click="toggleShowAverageResponseTime" :title="showAverageResponseTime ? 'Average response time' : 'Minimum and maximum response time'">
|
||||||
{{ (minResponseTime === maxResponseTime ? minResponseTime : (minResponseTime + '-' + maxResponseTime)) }}ms
|
<slot v-if="showAverageResponseTime">
|
||||||
|
~{{ averageResponseTime }}ms
|
||||||
|
</slot>
|
||||||
|
<slot v-else>
|
||||||
|
{{ (minResponseTime === maxResponseTime ? minResponseTime : (minResponseTime + '-' + maxResponseTime)) }}ms
|
||||||
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,15 +61,18 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
maximumNumberOfResults: Number,
|
maximumNumberOfResults: Number,
|
||||||
data: Object,
|
data: Object,
|
||||||
|
showAverageResponseTime: Boolean
|
||||||
},
|
},
|
||||||
emits: ['showTooltip'],
|
emits: ['showTooltip', 'toggleShowAverageResponseTime'],
|
||||||
mixins: [helper],
|
mixins: [helper],
|
||||||
methods: {
|
methods: {
|
||||||
updateMinAndMaxResponseTimes() {
|
updateMinAndMaxResponseTimes() {
|
||||||
let minResponseTime = null;
|
let minResponseTime = null;
|
||||||
let maxResponseTime = null;
|
let maxResponseTime = null;
|
||||||
|
let totalResponseTime = 0;
|
||||||
for (let i in this.data.results) {
|
for (let i in this.data.results) {
|
||||||
const responseTime = parseInt((this.data.results[i].duration/1000000).toFixed(0));
|
const responseTime = parseInt((this.data.results[i].duration/1000000).toFixed(0));
|
||||||
|
totalResponseTime += responseTime;
|
||||||
if (minResponseTime == null || minResponseTime > responseTime) {
|
if (minResponseTime == null || minResponseTime > responseTime) {
|
||||||
minResponseTime = responseTime;
|
minResponseTime = responseTime;
|
||||||
}
|
}
|
||||||
@@ -78,6 +86,9 @@ export default {
|
|||||||
if (this.maxResponseTime !== maxResponseTime) {
|
if (this.maxResponseTime !== maxResponseTime) {
|
||||||
this.maxResponseTime = maxResponseTime;
|
this.maxResponseTime = maxResponseTime;
|
||||||
}
|
}
|
||||||
|
if (this.data.results && this.data.results.length) {
|
||||||
|
this.averageResponseTime = (totalResponseTime/this.data.results.length).toFixed(0);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
generatePath() {
|
generatePath() {
|
||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
@@ -87,6 +98,9 @@ export default {
|
|||||||
},
|
},
|
||||||
showTooltip(result, event) {
|
showTooltip(result, event) {
|
||||||
this.$emit('showTooltip', result, event);
|
this.$emit('showTooltip', result, event);
|
||||||
|
},
|
||||||
|
toggleShowAverageResponseTime() {
|
||||||
|
this.$emit('toggleShowAverageResponseTime');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -100,7 +114,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
minResponseTime: 0,
|
minResponseTime: 0,
|
||||||
maxResponseTime: 0
|
maxResponseTime: 0,
|
||||||
|
averageResponseTime: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,10 +167,6 @@ export default {
|
|||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-min-max-ms {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.status-success::after {
|
.status.status-success::after {
|
||||||
content: "✓";
|
content: "✓";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,12 @@
|
|||||||
</slot>
|
</slot>
|
||||||
<div v-if="!collapsed" :class="name === 'undefined' ? '' : 'service-group-content'">
|
<div v-if="!collapsed" :class="name === 'undefined' ? '' : 'service-group-content'">
|
||||||
<slot v-for="service in services" :key="service">
|
<slot v-for="service in services" :key="service">
|
||||||
<Service :data="service" @showTooltip="showTooltip" :maximumNumberOfResults="20"/>
|
<Service
|
||||||
|
:data="service"
|
||||||
|
:maximumNumberOfResults="20"
|
||||||
|
@showTooltip="showTooltip"
|
||||||
|
@toggleShowAverageResponseTime="toggleShowAverageResponseTime" :showAverageResponseTime="showAverageResponseTime"
|
||||||
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,9 +36,10 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
services: Array
|
services: Array,
|
||||||
|
showAverageResponseTime: Boolean
|
||||||
},
|
},
|
||||||
emits: ['showTooltip'],
|
emits: ['showTooltip', 'toggleShowAverageResponseTime'],
|
||||||
methods: {
|
methods: {
|
||||||
healthCheck() {
|
healthCheck() {
|
||||||
if (this.services) {
|
if (this.services) {
|
||||||
@@ -56,10 +62,13 @@ export default {
|
|||||||
},
|
},
|
||||||
toggleGroup() {
|
toggleGroup() {
|
||||||
this.collapsed = !this.collapsed;
|
this.collapsed = !this.collapsed;
|
||||||
sessionStorage.setItem(`service-group:${this.name}:collapsed`, this.collapsed);
|
sessionStorage.setItem(`gatus:service-group:${this.name}:collapsed`, this.collapsed);
|
||||||
},
|
},
|
||||||
showTooltip(result, event) {
|
showTooltip(result, event) {
|
||||||
this.$emit('showTooltip', result, event);
|
this.$emit('showTooltip', result, event);
|
||||||
|
},
|
||||||
|
toggleShowAverageResponseTime() {
|
||||||
|
this.$emit('toggleShowAverageResponseTime');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -73,7 +82,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
healthy: true,
|
healthy: true,
|
||||||
collapsed: sessionStorage.getItem(`service-group:${this.name}:collapsed`) === "true"
|
collapsed: sessionStorage.getItem(`gatus:service-group:${this.name}:collapsed`) === "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="results">
|
<div id="results">
|
||||||
<slot v-for="serviceGroup in serviceGroups" :key="serviceGroup">
|
<slot v-for="serviceGroup in serviceGroups" :key="serviceGroup">
|
||||||
<ServiceGroup :services="serviceGroup.services" :name="serviceGroup.name" @showTooltip="showTooltip"/>
|
<ServiceGroup :services="serviceGroup.services" :name="serviceGroup.name" @showTooltip="showTooltip" @toggleShowAverageResponseTime="toggleShowAverageResponseTime" :showAverageResponseTime="showAverageResponseTime" />
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,9 +17,10 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
showStatusOnHover: Boolean,
|
showStatusOnHover: Boolean,
|
||||||
serviceStatuses: Object
|
serviceStatuses: Object,
|
||||||
|
showAverageResponseTime: Boolean
|
||||||
},
|
},
|
||||||
emits: ['showTooltip'],
|
emits: ['showTooltip', 'toggleShowAverageResponseTime'],
|
||||||
methods: {
|
methods: {
|
||||||
process() {
|
process() {
|
||||||
let outputByGroup = {};
|
let outputByGroup = {};
|
||||||
@@ -45,6 +46,9 @@ export default {
|
|||||||
},
|
},
|
||||||
showTooltip(result, event) {
|
showTooltip(result, event) {
|
||||||
this.$emit('showTooltip', result, event);
|
this.$emit('showTooltip', result, event);
|
||||||
|
},
|
||||||
|
toggleShowAverageResponseTime() {
|
||||||
|
this.$emit('toggleShowAverageResponseTime');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
↻
|
↻
|
||||||
</div>
|
</div>
|
||||||
<select class="text-center text-gray-500 text-sm" id="refresh-rate" ref="refreshInterval" @change="handleChangeRefreshInterval">
|
<select class="text-center text-gray-500 text-sm" id="refresh-rate" ref="refreshInterval" @change="handleChangeRefreshInterval">
|
||||||
<option value="10">10s</option>
|
<option value="10" :selected="refreshInterval === 10">10s</option>
|
||||||
<option value="30" selected>30s</option>
|
<option value="30" :selected="refreshInterval === 30">30s</option>
|
||||||
<option value="60">1m</option>
|
<option value="60" :selected="refreshInterval === 60">1m</option>
|
||||||
<option value="120">2m</option>
|
<option value="120" :selected="refreshInterval === 120">2m</option>
|
||||||
<option value="300">5m</option>
|
<option value="300" :selected="refreshInterval === 300">5m</option>
|
||||||
<option value="600">10m</option>
|
<option value="600" :selected="refreshInterval === 600">10m</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,6 +23,7 @@ export default {
|
|||||||
props: {},
|
props: {},
|
||||||
methods: {
|
methods: {
|
||||||
setRefreshInterval(seconds) {
|
setRefreshInterval(seconds) {
|
||||||
|
sessionStorage.setItem('gatus:refresh-interval', seconds);
|
||||||
let that = this;
|
let that = this;
|
||||||
this.refreshIntervalHandler = setInterval(function () {
|
this.refreshIntervalHandler = setInterval(function () {
|
||||||
that.refreshData();
|
that.refreshData();
|
||||||
@@ -38,6 +39,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
if (this.refreshInterval !== 10 && this.refreshInterval !== 30 && this.refreshInterval !== 60 && this.refreshInterval !== 120 && this.refreshInterval !== 300 && this.refreshInterval !== 600) {
|
||||||
|
this.refreshInterval = 60;
|
||||||
|
}
|
||||||
this.setRefreshInterval(this.refreshInterval);
|
this.setRefreshInterval(this.refreshInterval);
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
@@ -45,14 +49,11 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
refreshInterval: 30,
|
refreshInterval: sessionStorage.getItem('gatus:refresh-interval') < 10 ? 60 : parseInt(sessionStorage.getItem('gatus:refresh-interval')),
|
||||||
refreshIntervalHandler: 0,
|
refreshIntervalHandler: 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// props.refreshInterval = 30
|
|
||||||
//$("#refresh-rate").val(30);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,12 @@
|
|||||||
<slot v-if="serviceStatus">
|
<slot v-if="serviceStatus">
|
||||||
<h1 class="text-xl xl:text-3xl text-monospace text-gray-400">RECENT CHECKS</h1>
|
<h1 class="text-xl xl:text-3xl text-monospace text-gray-400">RECENT CHECKS</h1>
|
||||||
<hr class="mb-4" />
|
<hr class="mb-4" />
|
||||||
<Service :data="serviceStatus" :maximumNumberOfResults="20" @showTooltip="showTooltip" />
|
<Service
|
||||||
|
:data="serviceStatus"
|
||||||
|
:maximumNumberOfResults="20"
|
||||||
|
@showTooltip="showTooltip"
|
||||||
|
@toggleShowAverageResponseTime="toggleShowAverageResponseTime" :showAverageResponseTime="showAverageResponseTime"
|
||||||
|
/>
|
||||||
<Pagination @page="changePage"/>
|
<Pagination @page="changePage"/>
|
||||||
</slot>
|
</slot>
|
||||||
<div v-if="uptime" class="mt-12">
|
<div v-if="uptime" class="mt-12">
|
||||||
@@ -139,13 +144,16 @@ export default {
|
|||||||
let minutes = Math.ceil((new Date(start) - new Date(end))/1000/60);
|
let minutes = Math.ceil((new Date(start) - new Date(end))/1000/60);
|
||||||
return minutes + (minutes === 1 ? ' minute' : ' minutes');
|
return minutes + (minutes === 1 ? ' minute' : ' minutes');
|
||||||
},
|
},
|
||||||
showTooltip(result, event) {
|
|
||||||
this.$emit('showTooltip', result, event);
|
|
||||||
},
|
|
||||||
changePage(page) {
|
changePage(page) {
|
||||||
this.currentPage = page;
|
this.currentPage = page;
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
|
showTooltip(result, event) {
|
||||||
|
this.$emit('showTooltip', result, event);
|
||||||
|
},
|
||||||
|
toggleShowAverageResponseTime() {
|
||||||
|
this.showAverageResponseTime = !this.showAverageResponseTime;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -155,6 +163,7 @@ export default {
|
|||||||
// Since this page isn't at the root, we need to modify the server URL a bit
|
// Since this page isn't at the root, we need to modify the server URL a bit
|
||||||
serverUrl: SERVER_URL === '.' ? '..' : SERVER_URL,
|
serverUrl: SERVER_URL === '.' ? '..' : SERVER_URL,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
showAverageResponseTime: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<Services :serviceStatuses="serviceStatuses" :showStatusOnHover="true" @showTooltip="showTooltip"/>
|
<Services
|
||||||
|
:serviceStatuses="serviceStatuses"
|
||||||
|
:showStatusOnHover="true"
|
||||||
|
@showTooltip="showTooltip"
|
||||||
|
@toggleShowAverageResponseTime="toggleShowAverageResponseTime" :showAverageResponseTime="showAverageResponseTime"
|
||||||
|
/>
|
||||||
<Pagination @page="changePage"/>
|
<Pagination @page="changePage"/>
|
||||||
<Settings @refreshData="fetchData"/>
|
<Settings @refreshData="fetchData"/>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,7 +22,7 @@ export default {
|
|||||||
Services,
|
Services,
|
||||||
Settings,
|
Settings,
|
||||||
},
|
},
|
||||||
emits: ['showTooltip'],
|
emits: ['showTooltip', 'toggleShowAverageResponseTime'],
|
||||||
methods: {
|
methods: {
|
||||||
fetchData() {
|
fetchData() {
|
||||||
//console.log("[Home][fetchData] Fetching data");
|
//console.log("[Home][fetchData] Fetching data");
|
||||||
@@ -29,18 +34,22 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showTooltip(result, event) {
|
|
||||||
this.$emit('showTooltip', result, event);
|
|
||||||
},
|
|
||||||
changePage(page) {
|
changePage(page) {
|
||||||
this.currentPage = page;
|
this.currentPage = page;
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
|
showTooltip(result, event) {
|
||||||
|
this.$emit('showTooltip', result, event);
|
||||||
|
},
|
||||||
|
toggleShowAverageResponseTime() {
|
||||||
|
this.showAverageResponseTime = !this.showAverageResponseTime;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
serviceStatuses: {},
|
serviceStatuses: {},
|
||||||
currentPage: 1
|
currentPage: 1,
|
||||||
|
showAverageResponseTime: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user