Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe4d9821f3 | ||
|
|
d01a5d418b | ||
|
|
34f8cd1eca | ||
|
|
d101c17136 | ||
|
|
ade3d05983 | ||
|
|
fbab0ef7ca | ||
|
|
9121ec1cc8 | ||
|
|
6ddf1258e5 | ||
|
|
490610ccfd | ||
|
|
0eb6958085 | ||
|
|
d20a41c7a7 | ||
|
|
4c18e0d602 | ||
|
|
da24b7e8ac | ||
|
|
c619066e25 | ||
|
|
3688dd6e6f | ||
|
|
fc778300be | ||
|
|
df560ad872 | ||
|
|
de9c366777 | ||
|
|
6a5fec2c55 | ||
|
|
01d2ed3f02 | ||
|
|
92b85ee1ab | ||
|
|
a789deb8c2 | ||
|
|
e5a94979dd | ||
|
|
3c0ea72a5c | ||
|
|
d17e893131 | ||
|
|
7ea34ec8a8 | ||
|
|
f6b99f34db | ||
|
|
37495ac3f3 | ||
|
|
557f696f88 | ||
|
|
c86492dbfd |
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 48 KiB |
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
repository: "${{ github.event.inputs.repository }}"
|
||||
ref: "${{ github.event.inputs.ref }}"
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build binary to make sure it works
|
||||
run: go build -mod vendor
|
||||
|
||||
@@ -19,10 +19,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and push docker image
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
pull: true
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.IMAGE_REPOSITORY }}:experimental
|
||||
tags: ${{ env.IMAGE_REPOSITORY }}:experimental
|
||||
@@ -4,6 +4,9 @@ on:
|
||||
workflows: ["build"]
|
||||
branches: [master]
|
||||
types: [completed]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
publish-latest:
|
||||
name: publish-latest
|
||||
@@ -24,10 +27,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and push docker image
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
pull: true
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.IMAGE_REPOSITORY }}:latest
|
||||
tags: ${{ env.IMAGE_REPOSITORY }}:latest
|
||||
@@ -24,10 +24,13 @@ docker run -p 8080:8080 --name gatus twinproduction/gatus
|
||||
For more details, see [Usage](#usage)
|
||||
</details>
|
||||
|
||||

|
||||

|
||||
|
||||
Have any feedback or questions? [Create a discussion](https://github.com/TwiN/gatus/discussions/new).
|
||||
|
||||
Like this project? Please consider [sponsoring me](https://github.com/sponsors/TwiN).
|
||||
|
||||
|
||||
## Table of Contents
|
||||
- [Why Gatus?](#why-gatus)
|
||||
- [Features](#features)
|
||||
@@ -80,6 +83,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
|
||||
- [Reloading configuration on the fly](#reloading-configuration-on-the-fly)
|
||||
- [Endpoint groups](#endpoint-groups)
|
||||
- [Exposing Gatus on a custom port](#exposing-gatus-on-a-custom-port)
|
||||
- [Keeping your configuration small](#keeping-your-configuration-small)
|
||||
- [Badges](#badges)
|
||||
- [Uptime](#uptime)
|
||||
- [Health](#health)
|
||||
@@ -88,6 +92,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
|
||||
- [High level design overview](#high-level-design-overview)
|
||||
- [Sponsors](#sponsors)
|
||||
|
||||
|
||||
## Why Gatus?
|
||||
Before getting into the specifics, I want to address the most common question:
|
||||
> Why would I use Gatus when I can just use Prometheus’ Alertmanager, Cloudwatch or even Splunk?
|
||||
@@ -117,7 +122,8 @@ The main features of Gatus are:
|
||||
- **[Badges](#badges)**:  
|
||||
- **Dark mode**
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Usage
|
||||
By default, the configuration file is expected to be at `config/config.yaml`.
|
||||
@@ -818,6 +824,7 @@ Here's an example of what the notifications look like:
|
||||
|
||||

|
||||
|
||||
|
||||
#### Configuring Telegram alerts
|
||||
| Parameter | Description | Default |
|
||||
|:----------------------------------|:-------------------------------------------------------------------------------------------|:---------------------------|
|
||||
@@ -886,6 +893,7 @@ endpoints:
|
||||
description: "healthcheck failed"
|
||||
```
|
||||
|
||||
|
||||
#### Configuring custom alerts
|
||||
| Parameter | Description | Default |
|
||||
|:--------------------------------|:-------------------------------------------------------------------------------------------|:--------------|
|
||||
@@ -1046,6 +1054,7 @@ endpoints:
|
||||
- type: pagerduty
|
||||
```
|
||||
|
||||
|
||||
### Maintenance
|
||||
If you have maintenance windows, you may not want to be annoyed by alerts.
|
||||
To do that, you'll have to use the maintenance configuration:
|
||||
@@ -1084,6 +1093,7 @@ maintenance:
|
||||
| `security.basic` | HTTP Basic configuration | `{}` |
|
||||
| `security.oidc` | OpenID Connect configuration | `{}` |
|
||||
|
||||
|
||||
#### Basic
|
||||
| Parameter | Description | Default |
|
||||
|:----------------------------------------|:-----------------------------------------------------------------------------------|:--------------|
|
||||
@@ -1102,6 +1112,7 @@ security:
|
||||
**WARNING:** Make sure to carefully select to cost of the bcrypt hash. The higher the cost, the longer it takes to compute the hash,
|
||||
and basic auth verifies the password against the hash on every request. As of 2022-01-08, I suggest a cost of 8.
|
||||
|
||||
|
||||
#### OIDC
|
||||
| Parameter | Description | Default |
|
||||
|:---------------------------------|:---------------------------------------------------------------|:--------------|
|
||||
@@ -1215,7 +1226,6 @@ and [helmfile example](https://github.com/avakarev/gatus-chart#helmfileyaml-exam
|
||||
Gatus can be deployed on Terraform by using the following module: [terraform-kubernetes-gatus](https://github.com/TwiN/terraform-kubernetes-gatus).
|
||||
|
||||
|
||||
|
||||
## Running the tests
|
||||
```console
|
||||
go test ./... -mod vendor
|
||||
@@ -1504,6 +1514,43 @@ web:
|
||||
```
|
||||
|
||||
|
||||
### Keeping your configuration small
|
||||
While not specific to Gatus, you can leverage YAML anchors to create a default configuration.
|
||||
If you have a large configuration file, this should help you keep things clean.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```yaml
|
||||
default-endpoint: &defaults
|
||||
group: core
|
||||
interval: 5m
|
||||
client:
|
||||
insecure: true
|
||||
timeout: 30s
|
||||
conditions:
|
||||
- "[STATUS] == 200"
|
||||
|
||||
endpoints:
|
||||
- name: anchor-example-1
|
||||
<<: *defaults # This will merge the configuration under &defaults with this endpoint
|
||||
url: "https://example.org"
|
||||
|
||||
- name: anchor-example-2
|
||||
<<: *defaults
|
||||
group: example # This will override the group defined in &defaults
|
||||
url: "https://example.com"
|
||||
|
||||
- name: anchor-example-3
|
||||
<<: *defaults
|
||||
url: "https://twin.sh/health"
|
||||
conditions: # This will override the conditions defined in &defaults
|
||||
- "[STATUS] == 200"
|
||||
- "[BODY].status == UP"
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
### Badges
|
||||
#### Uptime
|
||||

|
||||
@@ -1568,6 +1615,7 @@ Where:
|
||||
- `{duration}` is `7d`, `24h` or `1h`
|
||||
- `{key}` has the pattern `<GROUP_NAME>_<ENDPOINT_NAME>` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`.
|
||||
|
||||
|
||||
##### How to change the color thresholds of the response time badge
|
||||
To change the response time badges' threshold, a corresponding configuration can be added to an endpoint.
|
||||
The values in the array correspond to the levels [Awesome, Great, Good, Passable, Bad]
|
||||
|
||||
@@ -84,6 +84,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
|
||||
@@ -2,6 +2,7 @@ package discord
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -44,7 +45,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -54,6 +55,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -61,8 +63,26 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Content string `json:"content"`
|
||||
Embeds []Embed `json:"embeds"`
|
||||
}
|
||||
|
||||
type Embed struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Color int `json:"color"`
|
||||
Fields []Field `json:"fields"`
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Inline bool `json:"inline"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
var message, results string
|
||||
var colorCode int
|
||||
if resolved {
|
||||
@@ -79,29 +99,30 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
} else {
|
||||
prefix = ":x:"
|
||||
}
|
||||
results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition)
|
||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||
}
|
||||
var description string
|
||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||
description = ":\\n> " + alertDescription
|
||||
description = ":\n> " + alertDescription
|
||||
}
|
||||
return fmt.Sprintf(`{
|
||||
"content": "",
|
||||
"embeds": [
|
||||
{
|
||||
"title": ":helmet_with_white_cross: Gatus",
|
||||
"description": "%s%s",
|
||||
"color": %d,
|
||||
"fields": [
|
||||
{
|
||||
"name": "Condition results",
|
||||
"value": "%s",
|
||||
"inline": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`, message, description, colorCode, results)
|
||||
body, _ := json.Marshal(Body{
|
||||
Content: "",
|
||||
Embeds: []Embed{
|
||||
{
|
||||
Title: ":helmet_with_white_cross: Gatus",
|
||||
Description: message + description,
|
||||
Color: colorCode,
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "Condition results",
|
||||
Value: results,
|
||||
Inline: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||
|
||||
@@ -151,14 +151,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"content\": \"\",\n \"embeds\": [\n {\n \"title\": \":helmet_with_white_cross: Gatus\",\n \"description\": \"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n> description-1\",\n \"color\": 15158332,\n \"fields\": [\n {\n \"name\": \"Condition results\",\n \"value\": \":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\n \"inline\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332,\"fields\":[{\"name\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n:x: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"content\": \"\",\n \"embeds\": [\n {\n \"title\": \":helmet_with_white_cross: Gatus\",\n \"description\": \"An alert for **endpoint-name** has been resolved after passing successfully 5 time(s) in a row:\\n> description-2\",\n \"color\": 3066993,\n \"fields\": [\n {\n \"name\": \"Condition results\",\n \"value\": \":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\n \"inline\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"description\":\"An alert for **endpoint-name** has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"color\":3066993,\"fields\":[{\"name\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n:white_check_mark: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -170,15 +170,16 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
ConditionResults: []*core.ConditionResult{
|
||||
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||
{Condition: "[BODY] != \"\"", Success: scenario.Resolved},
|
||||
},
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -62,6 +62,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
|
||||
@@ -177,7 +177,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
b, _ := json.Marshal(body)
|
||||
e, _ := json.Marshal(scenario.ExpectedBody)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", e, b)
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", e, b)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package matrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
@@ -61,7 +62,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
config := provider.getConfigForGroup(endpoint.Group)
|
||||
if config.ServerURL == "" {
|
||||
config.ServerURL = defaultHomeserverURL
|
||||
@@ -86,6 +87,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -93,17 +95,22 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Format string `json:"format"`
|
||||
Body string `json:"body"`
|
||||
FormattedBody string `json:"formatted_body"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
return fmt.Sprintf(`{
|
||||
"msgtype": "m.text",
|
||||
"format": "org.matrix.custom.html",
|
||||
"body": "%s",
|
||||
"formatted_body": "%s"
|
||||
}`,
|
||||
buildPlaintextMessageBody(endpoint, alert, result, resolved),
|
||||
buildHTMLMessageBody(endpoint, alert, result, resolved),
|
||||
)
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
body, _ := json.Marshal(Body{
|
||||
MsgType: "m.text",
|
||||
Format: "org.matrix.custom.html",
|
||||
Body: buildPlaintextMessageBody(endpoint, alert, result, resolved),
|
||||
FormattedBody: buildHTMLMessageBody(endpoint, alert, result, resolved),
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// buildPlaintextMessageBody builds the message body in plaintext to include in request
|
||||
@@ -121,13 +128,13 @@ func buildPlaintextMessageBody(endpoint *core.Endpoint, alert *alert.Alert, resu
|
||||
} else {
|
||||
prefix = "✕"
|
||||
}
|
||||
results += fmt.Sprintf("\\n%s - %s", prefix, conditionResult.Condition)
|
||||
results += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition)
|
||||
}
|
||||
var description string
|
||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||
description = "\\n" + alertDescription
|
||||
description = "\n" + alertDescription
|
||||
}
|
||||
return fmt.Sprintf("%s%s\\n%s", message, description, results)
|
||||
return fmt.Sprintf("%s%s\n%s", message, description, results)
|
||||
}
|
||||
|
||||
// buildHTMLMessageBody builds the message body in HTML to include in request
|
||||
@@ -149,9 +156,9 @@ func buildHTMLMessageBody(endpoint *core.Endpoint, alert *alert.Alert, result *c
|
||||
}
|
||||
var description string
|
||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||
description = fmt.Sprintf("\\n<blockquote>%s</blockquote>", alertDescription)
|
||||
description = fmt.Sprintf("\n<blockquote>%s</blockquote>", alertDescription)
|
||||
}
|
||||
return fmt.Sprintf("<h3>%s</h3>%s\\n<h5>Condition results</h5><ul>%s</ul>", message, description, results)
|
||||
return fmt.Sprintf("<h3>%s</h3>%s\n<h5>Condition results</h5><ul>%s</ul>", message, description, results)
|
||||
}
|
||||
|
||||
// getConfigForGroup returns the appropriate configuration for a given group
|
||||
|
||||
@@ -184,14 +184,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n\t\"msgtype\": \"m.text\",\n\t\"format\": \"org.matrix.custom.html\",\n\t\"body\": \"An alert for `endpoint-name` has been triggered due to having failed 3 time(s) in a row\\ndescription-1\\n\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\n\t\"formatted_body\": \"<h3>An alert for <code>endpoint-name</code> has been triggered due to having failed 3 time(s) in a row</h3>\\n<blockquote>description-1</blockquote>\\n<h5>Condition results</h5><ul><li>❌ - <code>[CONNECTED] == true</code></li><li>❌ - <code>[STATUS] == 200</code></li></ul>\"\n}",
|
||||
ExpectedBody: "{\"msgtype\":\"m.text\",\"format\":\"org.matrix.custom.html\",\"body\":\"An alert for `endpoint-name` has been triggered due to having failed 3 time(s) in a row\\ndescription-1\\n\\n✕ - [CONNECTED] == true\\n✕ - [STATUS] == 200\",\"formatted_body\":\"\\u003ch3\\u003eAn alert for \\u003ccode\\u003eendpoint-name\\u003c/code\\u003e has been triggered due to having failed 3 time(s) in a row\\u003c/h3\\u003e\\n\\u003cblockquote\\u003edescription-1\\u003c/blockquote\\u003e\\n\\u003ch5\\u003eCondition results\\u003c/h5\\u003e\\u003cul\\u003e\\u003cli\\u003e❌ - \\u003ccode\\u003e[CONNECTED] == true\\u003c/code\\u003e\\u003c/li\\u003e\\u003cli\\u003e❌ - \\u003ccode\\u003e[STATUS] == 200\\u003c/code\\u003e\\u003c/li\\u003e\\u003c/ul\\u003e\"}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n\t\"msgtype\": \"m.text\",\n\t\"format\": \"org.matrix.custom.html\",\n\t\"body\": \"An alert for `endpoint-name` has been resolved after passing successfully 5 time(s) in a row\\ndescription-2\\n\\n✓ - [CONNECTED] == true\\n✓ - [STATUS] == 200\",\n\t\"formatted_body\": \"<h3>An alert for <code>endpoint-name</code> has been resolved after passing successfully 5 time(s) in a row</h3>\\n<blockquote>description-2</blockquote>\\n<h5>Condition results</h5><ul><li>✅ - <code>[CONNECTED] == true</code></li><li>✅ - <code>[STATUS] == 200</code></li></ul>\"\n}",
|
||||
ExpectedBody: "{\"msgtype\":\"m.text\",\"format\":\"org.matrix.custom.html\",\"body\":\"An alert for `endpoint-name` has been resolved after passing successfully 5 time(s) in a row\\ndescription-2\\n\\n✓ - [CONNECTED] == true\\n✓ - [STATUS] == 200\",\"formatted_body\":\"\\u003ch3\\u003eAn alert for \\u003ccode\\u003eendpoint-name\\u003c/code\\u003e has been resolved after passing successfully 5 time(s) in a row\\u003c/h3\\u003e\\n\\u003cblockquote\\u003edescription-2\\u003c/blockquote\\u003e\\n\\u003ch5\\u003eCondition results\\u003c/h5\\u003e\\u003cul\\u003e\\u003cli\\u003e✅ - \\u003ccode\\u003e[CONNECTED] == true\\u003c/code\\u003e\\u003c/li\\u003e\\u003cli\\u003e✅ - \\u003ccode\\u003e[STATUS] == 200\\u003c/code\\u003e\\u003c/li\\u003e\\u003c/ul\\u003e\"}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -207,11 +207,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package mattermost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -60,6 +61,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -67,9 +69,31 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
Title string `json:"title"`
|
||||
Fallback string `json:"fallback"`
|
||||
Text string `json:"text"`
|
||||
Short bool `json:"short"`
|
||||
Color string `json:"color"`
|
||||
Fields []Field `json:"fields"`
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Title string `json:"title"`
|
||||
Value string `json:"value"`
|
||||
Short bool `json:"short"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
var message, color string
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
var message, color, results string
|
||||
if resolved {
|
||||
message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||
color = "#36A64F"
|
||||
@@ -77,7 +101,6 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||
color = "#DD0000"
|
||||
}
|
||||
var results string
|
||||
for _, conditionResult := range result.ConditionResults {
|
||||
var prefix string
|
||||
if conditionResult.Success {
|
||||
@@ -85,38 +108,34 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
} else {
|
||||
prefix = ":x:"
|
||||
}
|
||||
results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition)
|
||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||
}
|
||||
var description string
|
||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||
description = ":\\n> " + alertDescription
|
||||
description = ":\n> " + alertDescription
|
||||
}
|
||||
return fmt.Sprintf(`{
|
||||
"text": "",
|
||||
"username": "gatus",
|
||||
"icon_url": "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png",
|
||||
"attachments": [
|
||||
{
|
||||
"title": ":rescue_worker_helmet: Gatus",
|
||||
"fallback": "Gatus - %s",
|
||||
"text": "%s%s",
|
||||
"short": false,
|
||||
"color": "%s",
|
||||
"fields": [
|
||||
{
|
||||
"title": "URL",
|
||||
"value": "%s",
|
||||
"short": false
|
||||
},
|
||||
{
|
||||
"title": "Condition results",
|
||||
"value": "%s",
|
||||
"short": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`, message, message, description, color, endpoint.URL, results)
|
||||
body, _ := json.Marshal(Body{
|
||||
Text: "",
|
||||
Username: "gatus",
|
||||
IconURL: "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png",
|
||||
Attachments: []Attachment{
|
||||
{
|
||||
Title: ":helmet_with_white_cross: Gatus",
|
||||
Fallback: "Gatus - " + message,
|
||||
Text: message + description,
|
||||
Short: false,
|
||||
Color: color,
|
||||
Fields: []Field{
|
||||
{
|
||||
Title: "Condition results",
|
||||
Value: results,
|
||||
Short: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||
|
||||
@@ -155,14 +155,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"text\": \"\",\n \"username\": \"gatus\",\n \"icon_url\": \"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\n \"attachments\": [\n {\n \"title\": \":rescue_worker_helmet: Gatus\",\n \"fallback\": \"Gatus - An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row\",\n \"text\": \"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n> description-1\",\n \"short\": false,\n \"color\": \"#DD0000\",\n \"fields\": [\n {\n \"title\": \"URL\",\n \"value\": \"\",\n \"short\": false\n },\n {\n \"title\": \"Condition results\",\n \"value\": \":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"text\":\"\",\"username\":\"gatus\",\"icon_url\":\"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"fallback\":\"Gatus - An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row\",\"text\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"text\": \"\",\n \"username\": \"gatus\",\n \"icon_url\": \"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\n \"attachments\": [\n {\n \"title\": \":rescue_worker_helmet: Gatus\",\n \"fallback\": \"Gatus - An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row\",\n \"text\": \"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row:\\n> description-2\",\n \"short\": false,\n \"color\": \"#36A64F\",\n \"fields\": [\n {\n \"title\": \"URL\",\n \"value\": \"\",\n \"short\": false\n },\n {\n \"title\": \"Condition results\",\n \"value\": \":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"text\":\"\",\"username\":\"gatus\",\"icon_url\":\"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"fallback\":\"Gatus - An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -178,11 +178,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package messagebird
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -33,7 +34,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
// Send an alert using the provider
|
||||
// Reference doc for messagebird: https://developers.messagebird.com/api/sms-messaging/#send-outbound-sms
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -44,6 +45,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -51,19 +53,26 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Originator string `json:"originator"`
|
||||
Recipients string `json:"recipients"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
var message string
|
||||
if resolved {
|
||||
message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.DisplayName(), alert.GetDescription())
|
||||
} else {
|
||||
message = fmt.Sprintf("TRIGGERED: %s - %s", endpoint.DisplayName(), alert.GetDescription())
|
||||
}
|
||||
return fmt.Sprintf(`{
|
||||
"originator": "%s",
|
||||
"recipients": "%s",
|
||||
"body": "%s"
|
||||
}`, provider.Originator, provider.Recipients, message)
|
||||
body, _ := json.Marshal(Body{
|
||||
Originator: provider.Originator,
|
||||
Recipients: provider.Recipients,
|
||||
Body: message,
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// GetDefaultAlert returns the provider's default alert configuration
|
||||
|
||||
@@ -118,14 +118,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{AccessKey: "1", Originator: "2", Recipients: "3"},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"originator\": \"2\",\n \"recipients\": \"3\",\n \"body\": \"TRIGGERED: endpoint-name - description-1\"\n}",
|
||||
ExpectedBody: "{\"originator\":\"2\",\"recipients\":\"3\",\"body\":\"TRIGGERED: endpoint-name - description-1\"}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{AccessKey: "4", Originator: "5", Recipients: "6"},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"originator\": \"5\",\n \"recipients\": \"6\",\n \"body\": \"RESOLVED: endpoint-name - description-2\"\n}",
|
||||
ExpectedBody: "{\"originator\":\"5\",\"recipients\":\"6\",\"body\":\"RESOLVED: endpoint-name - description-2\"}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -141,8 +141,8 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package ntfy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -39,7 +40,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
request, err := http.NewRequest(http.MethodPost, provider.URL, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -49,6 +50,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -56,8 +58,16 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Topic string `json:"topic"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Tags []string `json:"tags"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
var message, tag string
|
||||
if len(alert.GetDescription()) > 0 {
|
||||
message = endpoint.DisplayName() + " - " + alert.GetDescription()
|
||||
@@ -69,13 +79,14 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
} else {
|
||||
tag = "x"
|
||||
}
|
||||
return fmt.Sprintf(`{
|
||||
"topic": "%s",
|
||||
"title": "Gatus",
|
||||
"message": "%s",
|
||||
"tags": ["%s"],
|
||||
"priority": %d
|
||||
}`, provider.Topic, message, tag, provider.Priority)
|
||||
body, _ := json.Marshal(Body{
|
||||
Topic: provider.Topic,
|
||||
Title: "Gatus",
|
||||
Message: message,
|
||||
Tags: []string{tag},
|
||||
Priority: provider.Priority,
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// GetDefaultAlert returns the provider's default alert configuration
|
||||
|
||||
@@ -69,14 +69,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 1},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"topic\": \"example\",\n \"title\": \"Gatus\",\n \"message\": \"endpoint-name - description-1\",\n \"tags\": [\"x\"],\n \"priority\": 1\n}",
|
||||
ExpectedBody: "{\"topic\":\"example\",\"title\":\"Gatus\",\"message\":\"endpoint-name - description-1\",\"tags\":[\"x\"],\"priority\":1}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{URL: "https://ntfy.sh", Topic: "example", Priority: 2},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"topic\": \"example\",\n \"title\": \"Gatus\",\n \"message\": \"endpoint-name - description-2\",\n \"tags\": [\"white_check_mark\"],\n \"priority\": 2\n}",
|
||||
ExpectedBody: "{\"topic\":\"example\",\"title\":\"Gatus\",\"message\":\"endpoint-name - description-2\",\"tags\":[\"white_check_mark\"],\"priority\":2}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -92,11 +92,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -83,37 +83,36 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
|
||||
func (provider *AlertProvider) createAlert(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
payload := provider.buildCreateRequestBody(endpoint, alert, result, resolved)
|
||||
_, err := provider.sendRequest(restAPI, http.MethodPost, payload)
|
||||
return err
|
||||
return provider.sendRequest(restAPI, http.MethodPost, payload)
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) closeAlert(endpoint *core.Endpoint, alert *alert.Alert) error {
|
||||
payload := provider.buildCloseRequestBody(endpoint, alert)
|
||||
url := restAPI + "/" + provider.alias(buildKey(endpoint)) + "/close?identifierType=alias"
|
||||
_, err := provider.sendRequest(url, http.MethodPost, payload)
|
||||
return err
|
||||
return provider.sendRequest(url, http.MethodPost, payload)
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) sendRequest(url, method string, payload interface{}) (*http.Response, error) {
|
||||
func (provider *AlertProvider) sendRequest(url, method string, payload interface{}) error {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to build alert payload: %v", payload)
|
||||
return fmt.Errorf("error build alert with payload %v: %w", payload, err)
|
||||
}
|
||||
request, err := http.NewRequest(method, url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Authorization", "GenieKey "+provider.APIKey)
|
||||
res, err := client.GetHTTPClient(nil).Do(request)
|
||||
response, err := client.GetHTTPClient(nil).Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if res.StatusCode > 399 {
|
||||
rBody, _ := io.ReadAll(res.Body)
|
||||
return nil, fmt.Errorf("call to provider alert returned status code %d: %s", res.StatusCode, string(rBody))
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
rBody, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(rBody))
|
||||
}
|
||||
return res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) alertCreateRequest {
|
||||
|
||||
@@ -53,7 +53,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
//
|
||||
// Relevant: https://developer.pagerduty.com/docs/events-api-v2/trigger-events/
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
request, err := http.NewRequest(http.MethodPost, restAPIURL, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -63,6 +63,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -86,8 +87,21 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return nil
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
RoutingKey string `json:"routing_key"`
|
||||
DedupKey string `json:"dedup_key"`
|
||||
EventAction string `json:"event_action"`
|
||||
Payload Payload `json:"payload"`
|
||||
}
|
||||
|
||||
type Payload struct {
|
||||
Summary string `json:"summary"`
|
||||
Source string `json:"source"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
var message, eventAction, resolveKey string
|
||||
if resolved {
|
||||
message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.DisplayName(), alert.GetDescription())
|
||||
@@ -98,16 +112,17 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
eventAction = "trigger"
|
||||
resolveKey = ""
|
||||
}
|
||||
return fmt.Sprintf(`{
|
||||
"routing_key": "%s",
|
||||
"dedup_key": "%s",
|
||||
"event_action": "%s",
|
||||
"payload": {
|
||||
"summary": "%s",
|
||||
"source": "%s",
|
||||
"severity": "critical"
|
||||
}
|
||||
}`, provider.getIntegrationKeyForGroup(endpoint.Group), resolveKey, eventAction, message, endpoint.Name)
|
||||
body, _ := json.Marshal(Body{
|
||||
RoutingKey: provider.getIntegrationKeyForGroup(endpoint.Group),
|
||||
DedupKey: resolveKey,
|
||||
EventAction: eventAction,
|
||||
Payload: Payload{
|
||||
Summary: message,
|
||||
Source: "Gatus",
|
||||
Severity: "critical",
|
||||
},
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// getIntegrationKeyForGroup returns the appropriate pagerduty integration key for a given group
|
||||
|
||||
@@ -149,24 +149,24 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{IntegrationKey: "00000000000000000000000000000000"},
|
||||
Alert: alert.Alert{Description: &description},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"routing_key\": \"00000000000000000000000000000000\",\n \"dedup_key\": \"\",\n \"event_action\": \"trigger\",\n \"payload\": {\n \"summary\": \"TRIGGERED: - test\",\n \"source\": \"\",\n \"severity\": \"critical\"\n }\n}",
|
||||
ExpectedBody: "{\"routing_key\":\"00000000000000000000000000000000\",\"dedup_key\":\"\",\"event_action\":\"trigger\",\"payload\":{\"summary\":\"TRIGGERED: endpoint-name - test\",\"source\":\"Gatus\",\"severity\":\"critical\"}}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{IntegrationKey: "00000000000000000000000000000000"},
|
||||
Alert: alert.Alert{Description: &description, ResolveKey: "key"},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"routing_key\": \"00000000000000000000000000000000\",\n \"dedup_key\": \"key\",\n \"event_action\": \"resolve\",\n \"payload\": {\n \"summary\": \"RESOLVED: - test\",\n \"source\": \"\",\n \"severity\": \"critical\"\n }\n}",
|
||||
ExpectedBody: "{\"routing_key\":\"00000000000000000000000000000000\",\"dedup_key\":\"key\",\"event_action\":\"resolve\",\"payload\":{\"summary\":\"RESOLVED: endpoint-name - test\",\"source\":\"Gatus\",\"severity\":\"critical\"}}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
body := scenario.Provider.buildRequestBody(&core.Endpoint{}, &scenario.Alert, &core.Result{}, scenario.Resolved)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
body := scenario.Provider.buildRequestBody(&core.Endpoint{Name: "endpoint-name"}, &scenario.Alert, &core.Result{}, scenario.Resolved)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -42,7 +43,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -52,6 +53,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -59,8 +61,27 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Text string `json:"text"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
Short bool `json:"short"`
|
||||
Color string `json:"color"`
|
||||
Fields []Field `json:"fields"`
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Title string `json:"title"`
|
||||
Value string `json:"value"`
|
||||
Short bool `json:"short"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
var message, color, results string
|
||||
if resolved {
|
||||
message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||
@@ -76,30 +97,31 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
} else {
|
||||
prefix = ":x:"
|
||||
}
|
||||
results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition)
|
||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||
}
|
||||
var description string
|
||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||
description = ":\\n> " + alertDescription
|
||||
description = ":\n> " + alertDescription
|
||||
}
|
||||
return fmt.Sprintf(`{
|
||||
"text": "",
|
||||
"attachments": [
|
||||
{
|
||||
"title": ":helmet_with_white_cross: Gatus",
|
||||
"text": "%s%s",
|
||||
"short": false,
|
||||
"color": "%s",
|
||||
"fields": [
|
||||
{
|
||||
"title": "Condition results",
|
||||
"value": "%s",
|
||||
"short": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`, message, description, color, results)
|
||||
body, _ := json.Marshal(Body{
|
||||
Text: "",
|
||||
Attachments: []Attachment{
|
||||
{
|
||||
Title: ":helmet_with_white_cross: Gatus",
|
||||
Text: message + description,
|
||||
Short: false,
|
||||
Color: color,
|
||||
Fields: []Field{
|
||||
{
|
||||
Title: "Condition results",
|
||||
Value: results,
|
||||
Short: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||
|
||||
@@ -153,7 +153,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Endpoint: core.Endpoint{Name: "name"},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"text\": \"\",\n \"attachments\": [\n {\n \"title\": \":helmet_with_white_cross: Gatus\",\n \"text\": \"An alert for *name* has been triggered due to having failed 3 time(s) in a row:\\n> description-1\",\n \"short\": false,\n \"color\": \"#DD0000\",\n \"fields\": [\n {\n \"title\": \"Condition results\",\n \"value\": \":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}",
|
||||
},
|
||||
{
|
||||
Name: "triggered-with-group",
|
||||
@@ -161,7 +161,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Endpoint: core.Endpoint{Name: "name", Group: "group"},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"text\": \"\",\n \"attachments\": [\n {\n \"title\": \":helmet_with_white_cross: Gatus\",\n \"text\": \"An alert for *group/name* has been triggered due to having failed 3 time(s) in a row:\\n> description-1\",\n \"short\": false,\n \"color\": \"#DD0000\",\n \"fields\": [\n {\n \"title\": \"Condition results\",\n \"value\": \":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
@@ -169,7 +169,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Endpoint: core.Endpoint{Name: "name"},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"text\": \"\",\n \"attachments\": [\n {\n \"title\": \":helmet_with_white_cross: Gatus\",\n \"text\": \"An alert for *name* has been resolved after passing successfully 5 time(s) in a row:\\n> description-2\",\n \"short\": false,\n \"color\": \"#36A64F\",\n \"fields\": [\n {\n \"title\": \"Condition results\",\n \"value\": \":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}",
|
||||
},
|
||||
{
|
||||
Name: "resolved-with-group",
|
||||
@@ -177,7 +177,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Endpoint: core.Endpoint{Name: "name", Group: "group"},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"text\": \"\",\n \"attachments\": [\n {\n \"title\": \":helmet_with_white_cross: Gatus\",\n \"text\": \"An alert for *group/name* has been resolved after passing successfully 5 time(s) in a row:\\n> description-2\",\n \"short\": false,\n \"color\": \"#36A64F\",\n \"fields\": [\n {\n \"title\": \"Condition results\",\n \"value\": \":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}",
|
||||
ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -193,11 +193,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package teams
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -44,7 +45,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
request, err := http.NewRequest(http.MethodPost, provider.getWebhookURLForGroup(endpoint.Group), buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -54,6 +55,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -61,8 +63,22 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Type string `json:"@type"`
|
||||
Context string `json:"@context"`
|
||||
ThemeColor string `json:"themeColor"`
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
Sections []Section `json:"sections"`
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
ActivityTitle string `json:"activityTitle"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
var message, color string
|
||||
if resolved {
|
||||
message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||
@@ -83,25 +99,22 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
}
|
||||
var description string
|
||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||
description = ":\\n> " + alertDescription
|
||||
description = ": " + alertDescription
|
||||
}
|
||||
return fmt.Sprintf(`{
|
||||
"@type": "MessageCard",
|
||||
"@context": "http://schema.org/extensions",
|
||||
"themeColor": "%s",
|
||||
"title": "🚨 Gatus",
|
||||
"text": "%s%s",
|
||||
"sections": [
|
||||
{
|
||||
"activityTitle": "URL",
|
||||
"text": "%s"
|
||||
},
|
||||
{
|
||||
"activityTitle": "Condition results",
|
||||
"text": "%s"
|
||||
}
|
||||
]
|
||||
}`, color, message, description, endpoint.URL, results)
|
||||
body, _ := json.Marshal(Body{
|
||||
Type: "MessageCard",
|
||||
Context: "http://schema.org/extensions",
|
||||
ThemeColor: color,
|
||||
Title: "🚨 Gatus",
|
||||
Text: message + description,
|
||||
Sections: []Section{
|
||||
{
|
||||
ActivityTitle: "Condition results",
|
||||
Text: results,
|
||||
},
|
||||
},
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||
|
||||
@@ -151,14 +151,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\n \"@type\": \"MessageCard\",\n \"@context\": \"http://schema.org/extensions\",\n \"themeColor\": \"#DD0000\",\n \"title\": \"🚨 Gatus\",\n \"text\": \"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n> description-1\",\n \"sections\": [\n {\n \"activityTitle\": \"URL\",\n \"text\": \"\"\n },\n {\n \"activityTitle\": \"Condition results\",\n \"text\": \"❌ - `[CONNECTED] == true`<br/>❌ - `[STATUS] == 200`<br/>\"\n }\n ]\n}",
|
||||
ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#DD0000\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row: description-1\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x274C; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x274C; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\n \"@type\": \"MessageCard\",\n \"@context\": \"http://schema.org/extensions\",\n \"themeColor\": \"#36A64F\",\n \"title\": \"🚨 Gatus\",\n \"text\": \"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row:\\n> description-2\",\n \"sections\": [\n {\n \"activityTitle\": \"URL\",\n \"text\": \"\"\n },\n {\n \"activityTitle\": \"Condition results\",\n \"text\": \"✅ - `[CONNECTED] == true`<br/>✅ - `[STATUS] == 200`<br/>\"\n }\n ]\n}",
|
||||
ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#36A64F\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row: description-2\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x2705; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x2705; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -174,11 +174,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ package telegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -36,7 +37,7 @@ func (provider *AlertProvider) IsValid() bool {
|
||||
|
||||
// Send an alert using the provider
|
||||
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
|
||||
buffer := bytes.NewBuffer([]byte(provider.buildRequestBody(endpoint, alert, result, resolved)))
|
||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||
apiURL := provider.APIURL
|
||||
if apiURL == "" {
|
||||
apiURL = defaultAPIURL
|
||||
@@ -50,6 +51,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
@@ -57,13 +59,19 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
return err
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
Text string `json:"text"`
|
||||
ParseMode string `json:"parse_mode"`
|
||||
}
|
||||
|
||||
// buildRequestBody builds the request body for the provider
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||
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— ", endpoint.DisplayName(), alert.FailureThreshold)
|
||||
message = fmt.Sprintf("An alert for *%s* has been resolved:\n—\n _healthcheck passing successfully %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.FailureThreshold)
|
||||
} else {
|
||||
message = fmt.Sprintf("An alert for *%s* has been triggered:\\n—\\n _healthcheck failed %d time(s) in a row_\\n— ", endpoint.DisplayName(), alert.FailureThreshold)
|
||||
message = fmt.Sprintf("An alert for *%s* has been triggered:\n—\n _healthcheck failed %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.FailureThreshold)
|
||||
}
|
||||
for _, conditionResult := range result.ConditionResults {
|
||||
var prefix string
|
||||
@@ -72,15 +80,20 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
||||
} else {
|
||||
prefix = "❌"
|
||||
}
|
||||
results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition)
|
||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||
}
|
||||
var text string
|
||||
if len(alert.GetDescription()) > 0 {
|
||||
text = fmt.Sprintf("⛑ *Gatus* \\n%s \\n*Description* \\n_%s_ \\n\\n*Condition results*\\n%s", message, alert.GetDescription(), results)
|
||||
text = fmt.Sprintf("⛑ *Gatus* \n%s \n*Description* \n_%s_ \n\n*Condition results*\n%s", message, alert.GetDescription(), results)
|
||||
} else {
|
||||
text = fmt.Sprintf("⛑ *Gatus* \\n%s \\n*Condition results*\\n%s", message, results)
|
||||
text = fmt.Sprintf("⛑ *Gatus* \n%s \n*Condition results*\n%s", message, results)
|
||||
}
|
||||
return fmt.Sprintf(`{"chat_id": "%s", "text": "%s", "parse_mode": "MARKDOWN"}`, provider.ID, text)
|
||||
body, _ := json.Marshal(Body{
|
||||
ChatID: provider.ID,
|
||||
Text: text,
|
||||
ParseMode: "MARKDOWN",
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
// GetDefaultAlert returns the provider's default alert configuration
|
||||
|
||||
@@ -124,14 +124,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
Provider: AlertProvider{ID: "123"},
|
||||
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: false,
|
||||
ExpectedBody: "{\"chat_id\": \"123\", \"text\": \"⛑ *Gatus* \\nAn alert for *endpoint-name* has been triggered:\\n—\\n _healthcheck failed 3 time(s) in a row_\\n— \\n*Description* \\n_description-1_ \\n\\n*Condition results*\\n❌ - `[CONNECTED] == true`\\n❌ - `[STATUS] == 200`\\n\", \"parse_mode\": \"MARKDOWN\"}",
|
||||
ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been triggered:\\n—\\n _healthcheck failed 3 time(s) in a row_\\n— \\n*Description* \\n_description-1_ \\n\\n*Condition results*\\n❌ - `[CONNECTED] == true`\\n❌ - `[STATUS] == 200`\\n\",\"parse_mode\":\"MARKDOWN\"}",
|
||||
},
|
||||
{
|
||||
Name: "resolved",
|
||||
Provider: AlertProvider{ID: "123"},
|
||||
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||
Resolved: true,
|
||||
ExpectedBody: "{\"chat_id\": \"123\", \"text\": \"⛑ *Gatus* \\nAn alert for *endpoint-name* has been resolved:\\n—\\n _healthcheck passing successfully 3 time(s) in a row_\\n— \\n*Description* \\n_description-2_ \\n\\n*Condition results*\\n✅ - `[CONNECTED] == true`\\n✅ - `[STATUS] == 200`\\n\", \"parse_mode\": \"MARKDOWN\"}",
|
||||
ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been resolved:\\n—\\n _healthcheck passing successfully 3 time(s) in a row_\\n— \\n*Description* \\n_description-2_ \\n\\n*Condition results*\\n✅ - `[CONNECTED] == true`\\n✅ - `[STATUS] == 200`\\n\",\"parse_mode\":\"MARKDOWN\"}",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
@@ -147,11 +147,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
||||
},
|
||||
scenario.Resolved,
|
||||
)
|
||||
if body != scenario.ExpectedBody {
|
||||
t.Errorf("expected %s, got %s", scenario.ExpectedBody, body)
|
||||
if string(body) != scenario.ExpectedBody {
|
||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||
}
|
||||
out := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
t.Error("expected body to be valid JSON, got error:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -42,6 +42,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode > 399 {
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("call to provider alert returned status code %d: %s", response.StatusCode, string(body))
|
||||
|
||||
@@ -302,6 +302,7 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.E
|
||||
alertTypes := []alert.Type{
|
||||
alert.TypeCustom,
|
||||
alert.TypeDiscord,
|
||||
alert.TypeGoogleChat,
|
||||
alert.TypeEmail,
|
||||
alert.TypeMatrix,
|
||||
alert.TypeMattermost,
|
||||
|
||||
+45
-15
@@ -19,7 +19,23 @@ func Eval(path string, b []byte) (string, int, error) {
|
||||
}
|
||||
|
||||
func walk(path string, object interface{}) (string, int, error) {
|
||||
keys := strings.Split(path, ".")
|
||||
var keys []string
|
||||
startOfCurrentKey, bracketDepth := 0, 0
|
||||
for i := range path {
|
||||
if path[i] == '[' {
|
||||
bracketDepth++
|
||||
} else if path[i] == ']' {
|
||||
bracketDepth--
|
||||
}
|
||||
// If we encounter a dot, we've reached the end of a key unless we're inside a bracket
|
||||
if path[i] == '.' && bracketDepth == 0 {
|
||||
keys = append(keys, path[startOfCurrentKey:i])
|
||||
startOfCurrentKey = i + 1
|
||||
}
|
||||
}
|
||||
if startOfCurrentKey <= len(path) {
|
||||
keys = append(keys, path[startOfCurrentKey:])
|
||||
}
|
||||
currentKey := keys[0]
|
||||
switch value := extractValue(currentKey, object).(type) {
|
||||
case map[string]interface{}:
|
||||
@@ -41,33 +57,47 @@ func walk(path string, object interface{}) (string, int, error) {
|
||||
func extractValue(currentKey string, value interface{}) interface{} {
|
||||
// Check if the current key ends with [#]
|
||||
if strings.HasSuffix(currentKey, "]") && strings.Contains(currentKey, "[") {
|
||||
tmp := strings.SplitN(currentKey, "[", 3)
|
||||
arrayIndex, err := strconv.Atoi(strings.Replace(tmp[1], "]", "", 1))
|
||||
var isNestedArray bool
|
||||
var index string
|
||||
startOfBracket, endOfBracket, bracketDepth := 0, 0, 0
|
||||
for i := range currentKey {
|
||||
if currentKey[i] == '[' {
|
||||
startOfBracket = i
|
||||
bracketDepth++
|
||||
} else if currentKey[i] == ']' && bracketDepth == 1 {
|
||||
bracketDepth--
|
||||
endOfBracket = i
|
||||
index = currentKey[startOfBracket+1 : i]
|
||||
if len(currentKey) > i+1 && currentKey[i+1] == '[' {
|
||||
isNestedArray = true // there's more keys.
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
arrayIndex, err := strconv.Atoi(index)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
currentKey := tmp[0]
|
||||
// if currentKey contains only an index (i.e. [0] or 0)
|
||||
if len(currentKey) == 0 {
|
||||
currentKeyWithoutIndex := currentKey[:startOfBracket]
|
||||
// if currentKeyWithoutIndex contains only an index (i.e. [0] or 0)
|
||||
if len(currentKeyWithoutIndex) == 0 {
|
||||
array := value.([]interface{})
|
||||
if len(array) > arrayIndex {
|
||||
if len(tmp) > 2 {
|
||||
// Nested array? Go deeper.
|
||||
return extractValue(fmt.Sprintf("%s[%s", currentKey, tmp[2]), array[arrayIndex])
|
||||
if isNestedArray {
|
||||
return extractValue(currentKey[endOfBracket+1:], array[arrayIndex])
|
||||
}
|
||||
return array[arrayIndex]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if value == nil || value.(map[string]interface{})[currentKey] == nil {
|
||||
if value == nil || value.(map[string]interface{})[currentKeyWithoutIndex] == nil {
|
||||
return nil
|
||||
}
|
||||
// if currentKey contains both a key and an index (i.e. data[0])
|
||||
array := value.(map[string]interface{})[currentKey].([]interface{})
|
||||
// if currentKeyWithoutIndex contains both a key and an index (i.e. data[0])
|
||||
array := value.(map[string]interface{})[currentKeyWithoutIndex].([]interface{})
|
||||
if len(array) > arrayIndex {
|
||||
if len(tmp) > 2 {
|
||||
// Nested array? Go deeper.
|
||||
return extractValue(fmt.Sprintf("[%s", tmp[2]), array[arrayIndex])
|
||||
if isNestedArray {
|
||||
return extractValue(currentKey[endOfBracket+1:], array[arrayIndex])
|
||||
}
|
||||
return array[arrayIndex]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package jsonpath
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkEval(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Eval("ids[0]", []byte(`{"ids": [1, 2]}`))
|
||||
Eval("long.simple.walk", []byte(`{"long": {"simple": {"walk": "value"}}}`))
|
||||
Eval("data[0].apps[1].name", []byte(`{"data": [{"apps": [{"name":"app1"}, {"name":"app2"}, {"name":"app3"}]}]}`))
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,22 @@ func TestEval(t *testing.T) {
|
||||
ExpectedOutputLength: 5,
|
||||
ExpectedError: false,
|
||||
},
|
||||
{
|
||||
Name: "map-of-arrays-of-maps",
|
||||
Path: "data[0].apps[1].name",
|
||||
Data: `{"data": [{"apps": [{"name":"app1"}, {"name":"app2"}, {"name":"app3"}]}]}`,
|
||||
ExpectedOutput: "app2",
|
||||
ExpectedOutputLength: 4,
|
||||
ExpectedError: false,
|
||||
},
|
||||
{
|
||||
Name: "map-of-arrays-of-maps-with-missing-element",
|
||||
Path: "data[0].apps[1].name",
|
||||
Data: `{"data": [{"apps": []}]}`,
|
||||
ExpectedOutput: "",
|
||||
ExpectedOutputLength: 0,
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "partially-invalid-path-issue122",
|
||||
Path: "data.name.invalid",
|
||||
|
||||
Generated
+33
@@ -8,6 +8,8 @@
|
||||
"name": "gatus",
|
||||
"version": "4.0.0",
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.3",
|
||||
"@heroicons/vue": "^2.0.12",
|
||||
"core-js": "3.22.8",
|
||||
"vue": "3.2.37",
|
||||
"vue-router": "4.0.16"
|
||||
@@ -1768,6 +1770,25 @@
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@headlessui/vue": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.3.tgz",
|
||||
"integrity": "sha512-Is4iakKts9u9E0+jEZNzoJpBjwq2SamwEIoEl2RlyYSu6Zco536GsPXaQEfg/o7Eyc1GUUlcL+dJd4Rt7qyf7A==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroicons/vue": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.0.12.tgz",
|
||||
"integrity": "sha512-ypuQl/Wei7BoShO65AdRVikXDaSt6hJBRuKs2pVd/6HvxfEz18oXraH+J5G/QPIwObMEVhMb6nkOs9l3WZv/gA==",
|
||||
"peerDependencies": {
|
||||
"vue": ">= 3"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
|
||||
@@ -12804,6 +12825,18 @@
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@headlessui/vue": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.3.tgz",
|
||||
"integrity": "sha512-Is4iakKts9u9E0+jEZNzoJpBjwq2SamwEIoEl2RlyYSu6Zco536GsPXaQEfg/o7Eyc1GUUlcL+dJd4Rt7qyf7A==",
|
||||
"requires": {}
|
||||
},
|
||||
"@heroicons/vue": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.0.12.tgz",
|
||||
"integrity": "sha512-ypuQl/Wei7BoShO65AdRVikXDaSt6hJBRuKs2pVd/6HvxfEz18oXraH+J5G/QPIwObMEVhMb6nkOs9l3WZv/gA==",
|
||||
"requires": {}
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.3",
|
||||
"@heroicons/vue": "^2.0.12",
|
||||
"core-js": "3.22.8",
|
||||
"vue": "3.2.37",
|
||||
"vue-router": "4.0.16"
|
||||
|
||||
+4
-9
@@ -7,10 +7,10 @@
|
||||
<div class="text-3xl xl:text-5xl lg:text-4xl font-light">{{ header }}</div>
|
||||
</div>
|
||||
<div class="w-1/4 flex justify-end">
|
||||
<a :href="link" target="_blank" style="width:100px">
|
||||
<component :is="link ? 'a' : 'div'" :href="link" target="_blank" style="width:100px">
|
||||
<img v-if="logo" :src="logo" alt="Gatus" class="object-scale-down" style="max-width: 100px; min-width: 50px; min-height:50px;" />
|
||||
<img v-else src="./assets/logo.svg" alt="Gatus" class="object-scale-down" style="max-width: 100px; min-width: 50px; min-height:50px;" />
|
||||
</a>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="buttons" class="flex flex-wrap">
|
||||
@@ -27,12 +27,7 @@
|
||||
<h2 class="mt-4 text-center text-4xl font-extrabold text-gray-800 dark:text-gray-200">
|
||||
Gatus
|
||||
</h2>
|
||||
<div class="mt-8 py-7 px-4 rounded-sm sm:bg-gray-100 sm:border sm:border-gray-300 sm:shadow-2xl sm:px-10">
|
||||
<div class="sm:mx-auto sm:w-full">
|
||||
<h2 class="mb-3 text-center text-xl font-bold text-gray-600 dark:text-gray-200 dark:sm:text-gray-600 ">
|
||||
Sign in
|
||||
</h2>
|
||||
</div>
|
||||
<div class="py-7 px-4 rounded-sm sm:px-10">
|
||||
<div v-if="$route && $route.query.error" class="text-red-500 text-center mb-5">
|
||||
<div class="text-sm">
|
||||
<span class="text-red-500" v-if="$route.query.error === 'access_denied'">You do not have access to this status page</span>
|
||||
@@ -40,7 +35,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a :href="`${SERVER_URL}/oidc/login`" class="max-w-lg mx-auto w-full flex justify-center py-3 px-4 border border-green-800 rounded-md shadow-lg text-sm text-white bg-green-700 hover:bg-green-800">
|
||||
<a :href="`${SERVER_URL}/oidc/login`" class="max-w-lg mx-auto w-full flex justify-center py-3 px-4 border border-green-800 rounded-md shadow-lg text-sm text-white bg-green-700 bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800">
|
||||
Login with OIDC
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -8,7 +8,7 @@
|
||||
<span v-if="data.results && data.results.length && data.results[data.results.length - 1].hostname" class='text-gray-500 font-light'> | {{ data.results[data.results.length - 1].hostname }}</span>
|
||||
</div>
|
||||
<div class='w-1/4 text-right'>
|
||||
<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'">
|
||||
<span class='font-light overflow-x-hidden cursor-pointer select-none hover:text-gray-500' v-if="data.results && data.results.length" @click="toggleShowAverageResponseTime" :title="showAverageResponseTime ? 'Average response time' : 'Minimum and maximum response time'">
|
||||
<slot v-if="showAverageResponseTime">
|
||||
~{{ averageResponseTime }}ms
|
||||
</slot>
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
<div :class="endpoints.length === 0 ? 'mt-3' : 'mt-4'">
|
||||
<slot v-if="name !== 'undefined'">
|
||||
<div class="endpoint-group pt-2 border dark:bg-gray-800 dark:border-gray-500" @click="toggleGroup">
|
||||
<h5 class='font-mono text-gray-400 text-xl font-medium pb-2 px-3 dark:text-gray-200 dark:hover:text-gray-500 dark:border-gray-500'>
|
||||
<span v-if="healthy" class='text-green-600'>✓</span>
|
||||
<span v-else class='text-yellow-400'>~</span>
|
||||
{{ name }}
|
||||
<span class='float-right endpoint-group-arrow'>
|
||||
<h5 class="font-mono text-gray-400 text-xl font-medium pb-2 px-3 dark:text-gray-200 dark:hover:text-gray-500 dark:border-gray-500">
|
||||
<span class="endpoint-group-arrow mr-2">
|
||||
{{ collapsed ? '▼' : '▲' }}
|
||||
</span>
|
||||
{{ name }}
|
||||
<span v-if="healthy" class="float-right text-green-600 w-7 hover:scale-110" title="Operational">
|
||||
<CheckCircleIcon />
|
||||
</span>
|
||||
<span v-else class="float-right text-yellow-500 text-sm w-7 hover:scale-110" title="Partial Outage">
|
||||
<ExclamationCircleIcon />
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
</slot>
|
||||
@@ -28,11 +32,14 @@
|
||||
|
||||
<script>
|
||||
import Endpoint from './Endpoint.vue';
|
||||
import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/vue/20/solid'
|
||||
|
||||
export default {
|
||||
name: 'EndpointGroup',
|
||||
components: {
|
||||
Endpoint
|
||||
Endpoint,
|
||||
CheckCircleIcon,
|
||||
ExclamationCircleIcon
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
@@ -44,9 +51,8 @@ export default {
|
||||
healthCheck() {
|
||||
if (this.endpoints) {
|
||||
for (let i in this.endpoints) {
|
||||
for (let j in this.endpoints[i].results) {
|
||||
if (!this.endpoints[i].results[j].success) {
|
||||
// Set the endpoint group to unhealthy (only if it's currently healthy)
|
||||
if (this.endpoints[i].results && this.endpoints[i].results.length > 0) {
|
||||
if (!this.endpoints[i].results[this.endpoints[i].results.length-1].success) {
|
||||
if (this.healthy) {
|
||||
this.healthy = false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div id="settings" class="flex bg-gray-200 border-gray-300 rounded border shadow dark:text-gray-200 dark:bg-gray-800 dark:border-gray-500">
|
||||
<div class="text-xs text-gray-600 rounded-xl py-1 px-2 dark:text-gray-200">
|
||||
↻
|
||||
<div class="text-xs text-gray-600 rounded-xl py-1.5 px-1.5 dark:text-gray-200">
|
||||
<ArrowPathIcon class="w-3"/>
|
||||
</div>
|
||||
<select class="text-center text-gray-500 text-xs dark:text-gray-200 dark:bg-gray-800 border-r border-l border-gray-300 dark:border-gray-500" id="refresh-rate" ref="refreshInterval" @change="handleChangeRefreshInterval">
|
||||
<option value="10" :selected="refreshInterval === 10">10s</option>
|
||||
@@ -12,16 +12,24 @@
|
||||
<option value="600" :selected="refreshInterval === 600">10m</option>
|
||||
</select>
|
||||
<button @click="toggleDarkMode" class="text-xs p-1">
|
||||
<slot v-if="darkMode">☀</slot>
|
||||
<slot v-else>🌙</slot>
|
||||
<slot v-if="darkMode"><SunIcon class="w-4"/></slot>
|
||||
<slot v-else><MoonIcon class="w-4 text-gray-500"/></slot>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { MoonIcon, SunIcon } from '@heroicons/vue/20/solid'
|
||||
import { ArrowPathIcon } from '@heroicons/vue/24/solid'
|
||||
|
||||
export default {
|
||||
name: 'Settings',
|
||||
components: {
|
||||
ArrowPathIcon,
|
||||
MoonIcon,
|
||||
SunIcon
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
setRefreshInterval(seconds) {
|
||||
|
||||
@@ -67,17 +67,14 @@
|
||||
<hr />
|
||||
<ul role="list" class="px-0 xl:px-24 divide-y divide-gray-200 dark:divide-gray-600">
|
||||
<li v-for="event in events" :key="event" class="p-3 my-4">
|
||||
<h2 class="text-lg">
|
||||
<img v-if="event.type === 'HEALTHY'" src="../assets/arrow-up-green.png" alt="Healthy"
|
||||
class="border border-green-600 rounded-full opacity-75 bg-green-100 mr-2 inline" width="26"/>
|
||||
<img v-else-if="event.type === 'UNHEALTHY'" src="../assets/arrow-down-red.png" alt="Unhealthy"
|
||||
class="border border-red-500 rounded-full opacity-75 bg-red-100 mr-2 inline" width="26"/>
|
||||
<img v-else-if="event.type === 'START'" src="../assets/arrow-right-black.png" alt="Start"
|
||||
class="border border-gray-500 rounded-full opacity-75 bg-gray-100 mr-2 inline" width="26"/>
|
||||
<h2 class="text-sm sm:text-lg">
|
||||
<ArrowUpCircleIcon v-if="event.type === 'HEALTHY'" class="w-8 inline mr-2 text-green-600" />
|
||||
<ArrowDownCircleIcon v-else-if="event.type === 'UNHEALTHY'" class="w-8 inline mr-2 text-red-500" />
|
||||
<PlayCircleIcon v-else-if="event.type === 'START'" class="w-8 inline mr-2 text-gray-400 dark:text-gray-100" />
|
||||
{{ event.fancyText }}
|
||||
</h2>
|
||||
<div class="flex mt-1 text-sm text-gray-400">
|
||||
<div class="flex-1 text-left pl-10">
|
||||
<div class="flex mt-1 text-xs sm:text-sm text-gray-400">
|
||||
<div class="flex-2 text-left pl-12">
|
||||
{{ prettifyTimestamp(event.timestamp) }}
|
||||
</div>
|
||||
<div class="flex-1 text-right">
|
||||
@@ -98,6 +95,7 @@ import Endpoint from '@/components/Endpoint.vue';
|
||||
import {SERVER_URL} from "@/main.js";
|
||||
import {helper} from "@/mixins/helper.js";
|
||||
import Pagination from "@/components/Pagination";
|
||||
import { ArrowDownCircleIcon, ArrowUpCircleIcon, PlayCircleIcon } from '@heroicons/vue/20/solid'
|
||||
|
||||
export default {
|
||||
name: 'Details',
|
||||
@@ -105,6 +103,9 @@ export default {
|
||||
Pagination,
|
||||
Endpoint,
|
||||
Settings,
|
||||
ArrowDownCircleIcon,
|
||||
ArrowUpCircleIcon,
|
||||
PlayCircleIcon
|
||||
},
|
||||
emits: ['showTooltip'],
|
||||
mixins: [helper],
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
/*
|
||||
! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com
|
||||
*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Consolas,Monaco,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.coLine truncated
|
||||
*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Consolas,Monaco,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.coLine truncated
|
||||
@@ -1 +1 @@
|
||||
(function(){"use strict";var e={2099:function(e,t,s){s.d(t,{L:function(){return ms}});s(7727);var n=s(9963),o=s(6252),a=s(3577),r=s.p+"img/logo.svg";const i={class:"mb-2"},l={class:"flex flex-wrap"},g={class:"w-3/4 text-left my-auto"},d={class:"text-3xl xl:text-5xl lg:text-4xl font-light"},h={class:"w-1/4 flex justify-end"},u=["href"],c=["src"],p={key:1,src:r,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},m={key:0,class:"flex flex-wrap"},A=["href"],f={key:2,class:"mx-auto max-w-md pt-12"},v=(0,o._)("img",{src:r,alt:"Gatus",class:"mx-auto",style:{"max-width":"160px","min-width":"50px","min-height":"50px"}},null,-1),w=(0,o._)("h2",{class:"mt-4 text-center text-4xl font-extrabold text-gray-800 dark:text-gray-200"}," Gatus ",-1),x={class:"mt-8 py-7 px-4 rounded-sm sm:bg-gray-100 sm:border sm:border-gray-300 sm:shadow-2xl sm:px-10"},y=(0,o._)("div",{class:"sm:mx-auto sm:w-full"},[(0,o._)("h2",{class:"mb-3 text-center text-xl font-bold text-gray-600 dark:text-gray-200 dark:sm:text-gray-600"}," Sign in ")],-1),k={key:0,class:"text-red-500 text-center mb-5"},T={class:"text-sm"},b={key:0,class:"text-red-500"},R={key:1,class:"text-red-500"},I=["href"];function D(e,t,s,n,r,D){const C=(0,o.up)("Loading"),S=(0,o.up)("router-view"),E=(0,o.up)("Tooltip"),B=(0,o.up)("Social");return(0,o.wg)(),(0,o.iD)(o.HY,null,[r.retrievedConfig?((0,o.wg)(),(0,o.iD)("div",{key:1,class:(0,a.C_)([r.config&&r.config.oidc&&!r.config.authenticated?"hidden":"","container container-xs relative mx-auto xl:rounded xl:border xl:shadow-xl xl:my-5 p-5 pb-12 xl:pb-5 text-left dark:bg-gray-800 dark:text-gray-200 dark:border-gray-500"]),id:"global"},[(0,o._)("div",i,[(0,o._)("div",l,[(0,o._)("div",g,[(0,o._)("div",d,(0,a.zw)(D.header),1)]),(0,o._)("div",h,[(0,o._)("a",{href:D.link,target:"_blank",style:{width:"100px"}},[D.logo?((0,o.wg)(),(0,o.iD)("img",{key:0,src:D.logo,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},null,8,c)):((0,o.wg)(),(0,o.iD)("img",p))],8,u)])]),D.buttons?((0,o.wg)(),(0,o.iD)("div",m,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(D.buttons,(e=>((0,o.wg)(),(0,o.iD)("a",{key:e.name,href:e.link,target:"_blank",class:"px-2 py-0.5 font-medium select-none text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-400 hover:underline"},(0,a.zw)(e.name),9,A)))),128))])):(0,o.kq)("",!0)]),(0,o.Wm)(S,{onShowTooltip:D.showTooltip},null,8,["onShowTooltip"])],2)):((0,o.wg)(),(0,o.j4)(C,{key:0,class:"h-64 w-64 px-4"})),r.config&&r.config.oidc&&!r.config.authenticated?((0,o.wg)(),(0,o.iD)("div",f,[v,w,(0,o._)("div",x,[y,e.$route&&e.$route.query.error?((0,o.wg)(),(0,o.iD)("div",k,[(0,o._)("div",T,["access_denied"===e.$route.query.error?((0,o.wg)(),(0,o.iD)("span",b,"You do not have access to this status page")):((0,o.wg)(),(0,o.iD)("span",R,(0,a.zw)(e.$route.query.error),1))])])):(0,o.kq)("",!0),(0,o._)("div",null,[(0,o._)("a",{href:`${r.SERVER_URL}/oidc/login`,class:"max-w-lg mx-auto w-full flex justify-center py-3 px-4 border border-green-800 rounded-md shadow-lg text-sm text-white bg-green-700 hover:bg-green-800"}," Login with OIDC ",8,I)])])])):(0,o.kq)("",!0),(0,o.Wm)(E,{result:r.tooltip.result,event:r.tooltip.event},null,8,["result","event"]),(0,o.Wm)(B)],64)}const C=e=>((0,o.dD)("data-v-a4b3d200"),e=e(),(0,o.Cn)(),e),S={id:"social"},E=C((()=>(0,o._)("a",{href:"https://github.com/TwiN/gatus",target:"_blank",title:"Gatus on GitHub"},[(0,o._)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"32",height:"32",viewBox:"0 0 16 16",class:"hover:scale-110"},[(0,o._)("path",{fill:"gray",d:"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"})])],-1))),B=[E];function P(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",S,B)}var H={name:"Social"},U=s(3744);const O=(0,U.Z)(H,[["render",P],["__scopeId","data-v-a4b3d200"]]);var z=O;const M=(0,o._)("div",{class:"tooltip-title"},"Timestamp:",-1),Q={id:"tooltip-timestamp"},W=(0,o._)("div",{class:"tooltip-title"},"Response time:",-1),q={id:"tooltip-response-time"},X=(0,o._)("div",{class:"tooltip-title"},"Conditions:",-1),Y={id:"tooltip-conditions"},K=(0,o._)("br",null,null,-1),G={key:0,id:"tooltip-errors-container"},F=(0,o._)("div",{class:"tooltip-title"},"Errors:",-1),j={id:"tooltip-errors"},L=(0,o._)("br",null,null,-1);function Z(e,t,s,n,r,i){return(0,o.wg)(),(0,o.iD)("div",{id:"tooltip",ref:"tooltip",class:(0,a.C_)(r.hidden?"invisible":""),style:(0,a.j5)("top:"+r.top+"px; leftLine truncated
|
||||
(function(){"use strict";var e={8718:function(e,t,s){s.d(t,{L:function(){return cs}});s(7727);var n=s(9963),o=s(6252),a=s(3577),r=s.p+"img/logo.svg";const i={class:"mb-2"},l={class:"flex flex-wrap"},d={class:"w-3/4 text-left my-auto"},g={class:"text-3xl xl:text-5xl lg:text-4xl font-light"},h={class:"w-1/4 flex justify-end"},u=["src"],c={key:1,src:r,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},p={key:0,class:"flex flex-wrap"},m=["href"],v={key:2,class:"mx-auto max-w-md pt-12"},f=(0,o._)("img",{src:r,alt:"Gatus",class:"mx-auto",style:{"max-width":"160px","min-width":"50px","min-height":"50px"}},null,-1),w=(0,o._)("h2",{class:"mt-4 text-center text-4xl font-extrabold text-gray-800 dark:text-gray-200"}," Gatus ",-1),x={class:"py-7 px-4 rounded-sm sm:px-10"},y={key:0,class:"text-red-500 text-center mb-5"},k={class:"text-sm"},T={key:0,class:"text-red-500"},b={key:1,class:"text-red-500"},R=["href"];function _(e,t,s,n,r,_){const S=(0,o.up)("Loading"),I=(0,o.up)("router-view"),D=(0,o.up)("Tooltip"),A=(0,o.up)("Social");return(0,o.wg)(),(0,o.iD)(o.HY,null,[r.retrievedConfig?((0,o.wg)(),(0,o.iD)("div",{key:1,class:(0,a.C_)([r.config&&r.config.oidc&&!r.config.authenticated?"hidden":"","container container-xs relative mx-auto xl:rounded xl:border xl:shadow-xl xl:my-5 p-5 pb-12 xl:pb-5 text-left dark:bg-gray-800 dark:text-gray-200 dark:border-gray-500"]),id:"global"},[(0,o._)("div",i,[(0,o._)("div",l,[(0,o._)("div",d,[(0,o._)("div",g,(0,a.zw)(_.header),1)]),(0,o._)("div",h,[((0,o.wg)(),(0,o.j4)((0,o.LL)(_.link?"a":"div"),{href:_.link,target:"_blank",style:{width:"100px"}},{default:(0,o.w5)((()=>[_.logo?((0,o.wg)(),(0,o.iD)("img",{key:0,src:_.logo,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},null,8,u)):((0,o.wg)(),(0,o.iD)("img",c))])),_:1},8,["href"]))])]),_.buttons?((0,o.wg)(),(0,o.iD)("div",p,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(_.buttons,(e=>((0,o.wg)(),(0,o.iD)("a",{key:e.name,href:e.link,target:"_blank",class:"px-2 py-0.5 font-medium select-none text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-400 hover:underline"},(0,a.zw)(e.name),9,m)))),128))])):(0,o.kq)("",!0)]),(0,o.Wm)(I,{onShowTooltip:_.showTooltip},null,8,["onShowTooltip"])],2)):((0,o.wg)(),(0,o.j4)(S,{key:0,class:"h-64 w-64 px-4"})),r.config&&r.config.oidc&&!r.config.authenticated?((0,o.wg)(),(0,o.iD)("div",v,[f,w,(0,o._)("div",x,[e.$route&&e.$route.query.error?((0,o.wg)(),(0,o.iD)("div",y,[(0,o._)("div",k,["access_denied"===e.$route.query.error?((0,o.wg)(),(0,o.iD)("span",T,"You do not have access to this status page")):((0,o.wg)(),(0,o.iD)("span",b,(0,a.zw)(e.$route.query.error),1))])])):(0,o.kq)("",!0),(0,o._)("div",null,[(0,o._)("a",{href:`${r.SERVER_URL}/oidc/login`,class:"max-w-lg mx-auto w-full flex justify-center py-3 px-4 border border-green-800 rounded-md shadow-lg text-sm text-white bg-green-700 bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800"}," Login with OIDC ",8,R)])])])):(0,o.kq)("",!0),(0,o.Wm)(D,{result:r.tooltip.result,event:r.tooltip.event},null,8,["result","event"]),(0,o.Wm)(A)],64)}const S=e=>((0,o.dD)("data-v-a4b3d200"),e=e(),(0,o.Cn)(),e),I={id:"social"},D=S((()=>(0,o._)("a",{href:"https://github.com/TwiN/gatus",target:"_blank",title:"Gatus on GitHub"},[(0,o._)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"32",height:"32",viewBox:"0 0 16 16",class:"hover:scale-110"},[(0,o._)("path",{fill:"gray",d:"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"})])],-1))),A=[D];function $(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",I,A)}var C={name:"Social"},P=s(3744);const E=(0,P.Z)(C,[["render",$],["__scopeId","data-v-a4b3d200"]]);var H=E;const L=(0,o._)("div",{class:"tooltip-title"},"Timestamp:",-1),U={id:"tooltip-timestamp"},W=(0,o._)("div",{class:"tooltip-title"},"Response time:",-1),M={id:"tooltip-response-time"},O=(0,o._)("div",{class:"tooltip-title"},"Conditions:",-1),j={id:"tooltip-conditions"},B=(0,o._)("br",null,null,-1),q={key:0,id:"tooltip-errors-container"},Y=(0,o._)("div",{class:"tooltip-title"},"Errors:",-1),z={id:"tooltip-errors"},N=(0,o._)("br",null,null,-1);function Z(e,t,s,n,r,i){return(0,o.wg)(),(0,o.iD)("div",{id:"tooltip",ref:"tooltip",class:(0,a.C_)(r.hidden?"invisible":""),style:(0,a.j5)("top:"+r.top+"px; left:"+r.left+"px")},[s.result?(0,o.WI)(e.$slots,"default",{key:0},(()=>[L,(0,o._)("code",U,(0,a.zw)(e.prettifyTimestamLine truncated
|
||||
@@ -1,7 +1,7 @@
|
||||
(self["webpackChunkgatus"]=self["webpackChunkgatus"]||[]).push([[998],{2262:function(e,t,n){"use strict";n.d(t,{Bj:function(){return i},Fl:function(){return Ge},IU:function(){return Re},Jd:function(){return k},PG:function(){return Se},SU:function(){return Ue},Um:function(){return xe},WL:function(){return Je},X$:function(){return E},X3:function(){return Ee},XI:function(){return Me},Xl:function(){return Ae},dq:function(){return Fe},iH:function(){return Le},j:function(){return C},lk:function(){return S},qj:function(){return _e},qq:function(){return b},yT:function(){return Oe}});var r=n(3577);let o;class i{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&o&&(this.parent=o,this.index=(o.scopes||(o.scopes=[])).push(this)-1)}run(e){if(this.active){const t=o;try{return o=this,e()}finally{o=t}}else 0}on(){o=this}off(){o=this.parent}stop(e){if(this.active){let t,n;for(t=0,n=this.effects.length;t<n;t++)this.effects[t].stop();for(t=0,n=this.cleanups.length;t<n;t++)this.cleanups[t]();if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].stop(!0);if(this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function s(e,t=o){t&&t.active&&t.effects.push(e)}const c=e=>{const t=new Set(e);return t.w=0,t.n=0,t},u=e=>(e.w&h)>0,l=e=>(e.n&h)>0,a=({deps:e})=>{if(e.length)for(let t=0;t<e.length;t++)e[t].w|=h},f=e=>{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r<t.length;r++){const o=t[r];u(o)&&!l(o)?o.delete(e):t[n++]=o,o.w&=~h,o.n&=~h}t.length=n}},p=new WeakMap;let d=0,h=1;const v=30;let g;const m=Symbol(""),y=Symbol("");class b{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],this.parent=void 0,s(this,n)}run(){if(!this.active)return this.fn();let e=g,t=x;while(e){if(e===this)return;e=e.parent}try{return this.parent=g,g=this,x=!0,h=1<<++d,d<=v?a(this):_(this),this.fn()}finally{d<=v&&f(this),h=1<<--d,g=this.parent,x=t,this.parent=void 0,this.deferStop&&this.stop()}}stop(){g===this?this.deferStop=!0:this.active&&(_(this),this.onStop&&this.onStop(),this.active=!1)}}function _(e){const{deps:t}=e;if(t.length){for(let n=0;n<t.length;n++)t[n].delete(e);t.length=0}}let x=!0;const w=[];function k(){w.push(x),x=!1}function S(){const e=w.pop();x=void 0===e||e}function C(e,t,n){if(x&&g){let t=p.get(e);t||p.set(e,t=new Map);let r=t.get(n);r||t.set(n,r=c());const o=void 0;O(r,o)}}function O(e,t){let n=!1;d<=v?l(e)||(e.n|=h,n=!u(e)):n=!e.has(g),n&&(e.add(g),g.deps.push(e))}function E(e,t,n,o,i,s){const u=p.get(e);if(!u)return;let l=[];if("clear"===t)l=[...u.values()];else if("length"===n&&(0,r.kJ)(e))u.forEach(((e,t)=>{("length"===t||t>=o)&&l.push(e)}));else switch(void 0!==n&&l.push(u.get(n)),t){case"add":(0,r.kJ)(e)?(0,r.S0)(n)&&l.push(u.get("length")):(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"delete":(0,r.kJ)(e)||(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"set":(0,r._N)(e)&&l.push(u.get(m));break}if(1===l.length)l[0]&&R(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);R(c(e))}}function R(e,t){const n=(0,r.kJ)(e)?e:[...e];for(const r of n)r.computed&&A(r,t);for(const r of n)r.computed||A(r,t)}function A(e,t){(e!==g||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const I=(0,r.fY)("__proto__,__v_isRef,__isVue"),P=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>"arguments"!==e&&"caller"!==e)).map((e=>Symbol[e])).filter(r.yk)),T=$(),j=$(!1,!0),F=$(!0),L=M();function M(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=Re(this);for(let t=0,o=this.length;t<o;t++)C(n,"get",t+"");const r=n[t](...e);return-1===r||!1===r?n[t](...e.map(Re)):r}})),["push","pop","shift","unshift","splice"].forEach((t=>{e[t]=function(...e){k();const n=Re(this)[t].apply(this,e);return S(),n}})),e}function $(e=!1,t=!1){return function(n,o,i){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_isShallow"===o)return t;if("__v_raw"===o&&i===(e?t?me:ge:t?ve:he).get(n))return n;const s=(0,r.kJ)(n);if(!e&&s&&(0,r.RI)(L,o))return Reflect.get(L,o,i);const c=Reflect.get(n,o,i);return((0,r.yk)(o)?P.has(o):I(o))?c:(e||C(n,"get",o),t?c:Fe(c)?s&&(0,r.S0)(o)?c:c.value:(0,r.Kn)(c)?e?we(c):_e(c):c)}}const N=D(),U=D(!0);function D(e=!1){return function(t,n,o,i){let s=t[n];if(Ce(s)&&Fe(s)&&!Fe(o))return!1;if(!e&&!Ce(o)&&(Oe(o)||(o=Re(o),s=Re(s)),!(0,r.kJ)(t)&&Fe(s)&&!Fe(o)))return s.value=o,!0;const c=(0,r.kJ)(t)&&(0,r.S0)(n)?Number(n)<t.length:(0,r.RI)(t,n),u=Reflect.set(t,n,o,i);return t===Re(i)&&(c?(0,r.aU)(o,s)&&E(t,"set",n,o,s):E(t,"add",n,o)),u}}function J(e,t){const n=(0,r.RI)(e,t),o=e[t],i=Reflect.deleteProperty(e,t);return i&&n&&E(e,"delete",t,void 0,o),i}function B(e,t){const n=Reflect.has(e,t);return(0,r.yk)(t)&&P.has(t)||C(e,"has",t),n}function G(e){return C(e,"iterate",(0,r.kJ)(e)?"length":m),Reflect.ownKeys(e)}const V={get:T,set:N,deleteProperty:J,has:B,ownKeys:G},q={get:F,set(e,t){return!0},deleteProperty(e,t){return!Line truncated
|
||||
(self["webpackChunkgatus"]=self["webpackChunkgatus"]||[]).push([[998],{2262:function(e,t,n){"use strict";n.d(t,{Bj:function(){return i},Fl:function(){return ze},IU:function(){return Ae},Jd:function(){return k},PG:function(){return Se},SU:function(){return Ue},Um:function(){return xe},WL:function(){return Be},X$:function(){return E},X3:function(){return Ee},XI:function(){return Le},Xl:function(){return Re},dq:function(){return Fe},iH:function(){return Me},j:function(){return C},lk:function(){return S},qj:function(){return _e},qq:function(){return b},yT:function(){return Oe}});var r=n(3577);let o;class i{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&o&&(this.parent=o,this.index=(o.scopes||(o.scopes=[])).push(this)-1)}run(e){if(this.active){const t=o;try{return o=this,e()}finally{o=t}}else 0}on(){o=this}off(){o=this.parent}stop(e){if(this.active){let t,n;for(t=0,n=this.effects.length;t<n;t++)this.effects[t].stop();for(t=0,n=this.cleanups.length;t<n;t++)this.cleanups[t]();if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].stop(!0);if(this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function s(e,t=o){t&&t.active&&t.effects.push(e)}const c=e=>{const t=new Set(e);return t.w=0,t.n=0,t},u=e=>(e.w&h)>0,l=e=>(e.n&h)>0,a=({deps:e})=>{if(e.length)for(let t=0;t<e.length;t++)e[t].w|=h},f=e=>{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r<t.length;r++){const o=t[r];u(o)&&!l(o)?o.delete(e):t[n++]=o,o.w&=~h,o.n&=~h}t.length=n}},p=new WeakMap;let d=0,h=1;const v=30;let g;const m=Symbol(""),y=Symbol("");class b{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],this.parent=void 0,s(this,n)}run(){if(!this.active)return this.fn();let e=g,t=x;while(e){if(e===this)return;e=e.parent}try{return this.parent=g,g=this,x=!0,h=1<<++d,d<=v?a(this):_(this),this.fn()}finally{d<=v&&f(this),h=1<<--d,g=this.parent,x=t,this.parent=void 0,this.deferStop&&this.stop()}}stop(){g===this?this.deferStop=!0:this.active&&(_(this),this.onStop&&this.onStop(),this.active=!1)}}function _(e){const{deps:t}=e;if(t.length){for(let n=0;n<t.length;n++)t[n].delete(e);t.length=0}}let x=!0;const w=[];function k(){w.push(x),x=!1}function S(){const e=w.pop();x=void 0===e||e}function C(e,t,n){if(x&&g){let t=p.get(e);t||p.set(e,t=new Map);let r=t.get(n);r||t.set(n,r=c());const o=void 0;O(r,o)}}function O(e,t){let n=!1;d<=v?l(e)||(e.n|=h,n=!u(e)):n=!e.has(g),n&&(e.add(g),g.deps.push(e))}function E(e,t,n,o,i,s){const u=p.get(e);if(!u)return;let l=[];if("clear"===t)l=[...u.values()];else if("length"===n&&(0,r.kJ)(e))u.forEach(((e,t)=>{("length"===t||t>=o)&&l.push(e)}));else switch(void 0!==n&&l.push(u.get(n)),t){case"add":(0,r.kJ)(e)?(0,r.S0)(n)&&l.push(u.get("length")):(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"delete":(0,r.kJ)(e)||(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"set":(0,r._N)(e)&&l.push(u.get(m));break}if(1===l.length)l[0]&&A(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);A(c(e))}}function A(e,t){const n=(0,r.kJ)(e)?e:[...e];for(const r of n)r.computed&&R(r,t);for(const r of n)r.computed||R(r,t)}function R(e,t){(e!==g||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const I=(0,r.fY)("__proto__,__v_isRef,__isVue"),P=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>"arguments"!==e&&"caller"!==e)).map((e=>Symbol[e])).filter(r.yk)),T=$(),j=$(!1,!0),F=$(!0),M=L();function L(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=Ae(this);for(let t=0,o=this.length;t<o;t++)C(n,"get",t+"");const r=n[t](...e);return-1===r||!1===r?n[t](...e.map(Ae)):r}})),["push","pop","shift","unshift","splice"].forEach((t=>{e[t]=function(...e){k();const n=Ae(this)[t].apply(this,e);return S(),n}})),e}function $(e=!1,t=!1){return function(n,o,i){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_isShallow"===o)return t;if("__v_raw"===o&&i===(e?t?me:ge:t?ve:he).get(n))return n;const s=(0,r.kJ)(n);if(!e&&s&&(0,r.RI)(M,o))return Reflect.get(M,o,i);const c=Reflect.get(n,o,i);return((0,r.yk)(o)?P.has(o):I(o))?c:(e||C(n,"get",o),t?c:Fe(c)?s&&(0,r.S0)(o)?c:c.value:(0,r.Kn)(c)?e?we(c):_e(c):c)}}const N=D(),U=D(!0);function D(e=!1){return function(t,n,o,i){let s=t[n];if(Ce(s)&&Fe(s)&&!Fe(o))return!1;if(!e&&!Ce(o)&&(Oe(o)||(o=Ae(o),s=Ae(s)),!(0,r.kJ)(t)&&Fe(s)&&!Fe(o)))return s.value=o,!0;const c=(0,r.kJ)(t)&&(0,r.S0)(n)?Number(n)<t.length:(0,r.RI)(t,n),u=Reflect.set(t,n,o,i);return t===Ae(i)&&(c?(0,r.aU)(o,s)&&E(t,"set",n,o,s):E(t,"add",n,o)),u}}function B(e,t){const n=(0,r.RI)(e,t),o=e[t],i=Reflect.deleteProperty(e,t);return i&&n&&E(e,"delete",t,void 0,o),i}function J(e,t){const n=Reflect.has(e,t);return(0,r.yk)(t)&&P.has(t)||C(e,"has",t),n}function z(e){return C(e,"iterate",(0,r.kJ)(e)?"length":m),Reflect.ownKeys(e)}const G={get:T,set:N,deleteProperty:B,has:J,ownKeys:z},V={get:F,set(e,t){return!0},deleteProperty(e,t){return!Line truncated
|
||||
/*!
|
||||
* vue-router v4.0.16
|
||||
* (c) 2022 Eduardo San Martin Morote
|
||||
* @license MIT
|
||||
*/
|
||||
const i="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag,s=e=>i?Symbol(e):"_vr_"+e,c=s("rvlm"),u=s("rvd"),l=s("r"),a=s("rl"),f=s("rvl"),p="undefined"!==typeof window;function d(e){return e.__esModule||i&&"Module"===e[Symbol.toStringTag]}const h=Object.assign;function v(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}const g=()=>{};const m=/\/$/,y=e=>e.replace(m,"");function b(e,t,n="/"){let r,o={},i="",s="";const c=t.indexOf("?"),u=t.indexOf("#",c>-1?c:0);return c>-1&&(r=t.slice(0,c),i=t.slice(c+1,u>-1?u:t.length),o=e(i)),u>-1&&(r=r||t.slice(0,u),s=t.slice(u,t.length)),r=E(null!=r?r:t,n),{fullPath:r+(i&&"?")+i+s,path:r,query:o,hash:s}}function _(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function x(e,t){return t&&e.toLowerCase().startsWith(t.toLowerCase())?e.slice(t.length)||"/":e}function w(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&k(t.matched[r],n.matched[o])&&S(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function k(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function S(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!C(e[n],t[n]))return!1;return!0}function C(e,t){return Array.isArray(e)?O(e,t):Array.isArray(t)?O(t,e):e===t}function O(e,t){return Array.isArray(t)?e.length===t.length&&e.every(((e,n)=>e===t[n])):1===e.length&&e[0]===t}function E(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,i,s=n.length-1;for(o=0;o<r.length;o++)if(i=r[o],1!==s&&"."!==i){if(".."!==i)break;s--}return n.slice(0,s).join("/")+"/"+r.slice(o-(o===r.length?1:0)).join("/")}var R,A;(function(e){e["pop"]="pop",e["push"]="push"})(R||(R={})),function(e){e["back"]="back",e["forward"]="forward",e["unknown"]=""}(A||(A={}));function I(e){if(!e)if(p){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),y(e)}const P=/^[^#]+#/;function T(e,t){return e.replace(P,"#")+t}function j(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const F=()=>({left:window.pageXOffset,top:window.pageYOffset});function L(e){let t;if("el"in e){const n=e.el,r="string"===typeof n&&n.startsWith("#");0;const o="string"===typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=j(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function M(e,t){const n=history.state?history.state.position-t:-1;return n+e}const $=new Map;function N(e,t){$.set(e,t)}function U(e){const t=$.get(e);return $.delete(e),t}let D=()=>location.protocol+"//"+location.host;function J(e,t){const{pathname:n,search:r,hash:o}=t,i=e.indexOf("#");if(i>-1){let t=o.includes(e.slice(i))?e.slice(i).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),x(n,"")}const s=x(n,e);return s+r+o}function B(e,t,n,r){let o=[],i=[],s=null;const c=({state:i})=>{const c=J(e,location),u=n.value,l=t.value;let a=0;if(i){if(n.value=c,t.value=i,s&&s===u)return void(s=null);a=l?i.position-l.position:0}else r(c);o.forEach((e=>{e(n.value,u,{delta:a,type:R.pop,direction:a?a>0?A.forward:A.back:A.unknown})}))};function u(){s=n.value}function l(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return i.push(t),t}function a(){const{history:e}=window;e.state&&e.replaceState(h({},e.state,{scroll:F()}),"")}function f(){for(const e of i)e();i=[],window.removeEventListener("popstate",c),window.removeEventListener("beforeunload",a)}return window.addEventListener("popstate",c),window.addEventListener("beforeunload",a),{pauseListeners:u,listen:l,destroy:f}}function G(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?F():null}}function V(e){const{history:t,location:n}=window,r={value:J(e,n)},o={value:t.state};function i(r,i,s){const c=e.indexOf("#"),u=c>-1?(n.host&&document.querySelector("base")?e:e.slice(c))+r:D()+e+r;try{t[s?"replaceState":"pushState"](i,"",u),o.value=i}catch(l){console.error(l),n[s?"replace":"assign"](u)}}function s(e,n){const s=h({},t.state,G(o.value.back,e,o.value.forward,!0),n,{position:o.value.position});i(e,s,!0),r.value=e}function c(e,n){const s=h({},o.value,t.state,{forward:e,scroll:F()});i(s.current,s,!0);const c=h({},G(r.value,e,null),{position:s.position+1},n);i(e,c,!1),r.value=e}return o.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:c,replace:s}}function q(e){e=I(e);const t=V(e),n=B(e,t.state,t.location,t.replace);function r(e,t=!0){t||n.pauseListeners(),history.go(e)}const o=h({location:"",base:e,go:r,createHref:T.bind(null,e)},t,n);return Object.defineProperty(o,"loLine truncated
|
||||
const i="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag,s=e=>i?Symbol(e):"_vr_"+e,c=s("rvlm"),u=s("rvd"),l=s("r"),a=s("rl"),f=s("rvl"),p="undefined"!==typeof window;function d(e){return e.__esModule||i&&"Module"===e[Symbol.toStringTag]}const h=Object.assign;function v(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}const g=()=>{};const m=/\/$/,y=e=>e.replace(m,"");function b(e,t,n="/"){let r,o={},i="",s="";const c=t.indexOf("?"),u=t.indexOf("#",c>-1?c:0);return c>-1&&(r=t.slice(0,c),i=t.slice(c+1,u>-1?u:t.length),o=e(i)),u>-1&&(r=r||t.slice(0,u),s=t.slice(u,t.length)),r=E(null!=r?r:t,n),{fullPath:r+(i&&"?")+i+s,path:r,query:o,hash:s}}function _(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function x(e,t){return t&&e.toLowerCase().startsWith(t.toLowerCase())?e.slice(t.length)||"/":e}function w(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&k(t.matched[r],n.matched[o])&&S(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function k(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function S(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!C(e[n],t[n]))return!1;return!0}function C(e,t){return Array.isArray(e)?O(e,t):Array.isArray(t)?O(t,e):e===t}function O(e,t){return Array.isArray(t)?e.length===t.length&&e.every(((e,n)=>e===t[n])):1===e.length&&e[0]===t}function E(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,i,s=n.length-1;for(o=0;o<r.length;o++)if(i=r[o],1!==s&&"."!==i){if(".."!==i)break;s--}return n.slice(0,s).join("/")+"/"+r.slice(o-(o===r.length?1:0)).join("/")}var A,R;(function(e){e["pop"]="pop",e["push"]="push"})(A||(A={})),function(e){e["back"]="back",e["forward"]="forward",e["unknown"]=""}(R||(R={}));function I(e){if(!e)if(p){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),y(e)}const P=/^[^#]+#/;function T(e,t){return e.replace(P,"#")+t}function j(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const F=()=>({left:window.pageXOffset,top:window.pageYOffset});function M(e){let t;if("el"in e){const n=e.el,r="string"===typeof n&&n.startsWith("#");0;const o="string"===typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=j(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function L(e,t){const n=history.state?history.state.position-t:-1;return n+e}const $=new Map;function N(e,t){$.set(e,t)}function U(e){const t=$.get(e);return $.delete(e),t}let D=()=>location.protocol+"//"+location.host;function B(e,t){const{pathname:n,search:r,hash:o}=t,i=e.indexOf("#");if(i>-1){let t=o.includes(e.slice(i))?e.slice(i).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),x(n,"")}const s=x(n,e);return s+r+o}function J(e,t,n,r){let o=[],i=[],s=null;const c=({state:i})=>{const c=B(e,location),u=n.value,l=t.value;let a=0;if(i){if(n.value=c,t.value=i,s&&s===u)return void(s=null);a=l?i.position-l.position:0}else r(c);o.forEach((e=>{e(n.value,u,{delta:a,type:A.pop,direction:a?a>0?R.forward:R.back:R.unknown})}))};function u(){s=n.value}function l(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return i.push(t),t}function a(){const{history:e}=window;e.state&&e.replaceState(h({},e.state,{scroll:F()}),"")}function f(){for(const e of i)e();i=[],window.removeEventListener("popstate",c),window.removeEventListener("beforeunload",a)}return window.addEventListener("popstate",c),window.addEventListener("beforeunload",a),{pauseListeners:u,listen:l,destroy:f}}function z(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?F():null}}function G(e){const{history:t,location:n}=window,r={value:B(e,n)},o={value:t.state};function i(r,i,s){const c=e.indexOf("#"),u=c>-1?(n.host&&document.querySelector("base")?e:e.slice(c))+r:D()+e+r;try{t[s?"replaceState":"pushState"](i,"",u),o.value=i}catch(l){console.error(l),n[s?"replace":"assign"](u)}}function s(e,n){const s=h({},t.state,z(o.value.back,e,o.value.forward,!0),n,{position:o.value.position});i(e,s,!0),r.value=e}function c(e,n){const s=h({},o.value,t.state,{forward:e,scroll:F()});i(s.current,s,!0);const c=h({},z(r.value,e,null),{position:s.position+1},n);i(e,c,!1),r.value=e}return o.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:c,replace:s}}function V(e){e=I(e);const t=G(e),n=J(e,t.state,t.location,t.replace);function r(e,t=!0){t||n.pauseListeners(),history.go(e)}const o=h({location:"",base:e,go:r,createHref:T.bind(null,e)},t,n);return Object.defineProperty(o,"loLine truncated
|
||||
Reference in new issue
Block a user