diff --git a/README.md b/README.md index d09ac9d4..beff1f26 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga - [Badges](#badges) - [Uptime](#uptime) - [Health](#health) + - [Health (Shields.io)](#health-shieldsio) - [Response time](#response-time) - [How to change the color thresholds of the response time badge](#how-to-change-the-color-thresholds-of-the-response-time-badge) - [API](#api) @@ -1958,6 +1959,25 @@ https://example.com/api/v1/endpoints/core_frontend/health/badge.svg ``` +#### Health (Shields.io) +![Health](https://img.shields.io/endpoint?url=https%3A%2F%2Fstatus.twin.sh%2Fapi%2Fv1%2Fendpoints%2Fcore_blog-external%2Fhealth%2Fbadge.shields) + +The path to generate a badge is the following: +``` +/api/v1/endpoints/{key}/health/badge.shields +``` +Where: +- `{key}` has the pattern `_` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`. + +For instance, if you want the current status of the endpoint `frontend` in the group `core`, +the URL would look like this: +``` +https://example.com/api/v1/endpoints/core_frontend/health/badge.shields +``` + +See more information about the Shields.io badge endpoint [here](https://shields.io/badges/endpoint-badge). + + #### Response time ![Response time 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/1h/badge.svg) ![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/24h/badge.svg) diff --git a/api/api.go b/api/api.go index 532be382..b9e97a09 100644 --- a/api/api.go +++ b/api/api.go @@ -66,6 +66,7 @@ func (a *API) createRouter(cfg *config.Config) *fiber.App { unprotectedAPIRouter := apiRouter.Group("/") unprotectedAPIRouter.Get("/v1/config", ConfigHandler{securityConfig: cfg.Security}.GetConfig) unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.svg", HealthBadge) + unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.shields", HealthBadgeShields) unprotectedAPIRouter.Get("/v1/endpoints/:key/uptimes/:duration/badge.svg", UptimeBadge) unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/badge.svg", ResponseTimeBadge(cfg)) unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/chart.svg", ResponseTimeChart) diff --git a/api/badge.go b/api/badge.go index 24cb3002..13c27c03 100644 --- a/api/badge.go +++ b/api/badge.go @@ -1,6 +1,7 @@ package api import ( + "encoding/json" "fmt" "strconv" "strings" @@ -125,6 +126,36 @@ func HealthBadge(c *fiber.Ctx) error { return c.Status(200).Send(generateHealthBadgeSVG(healthStatus)) } +func HealthBadgeShields(c *fiber.Ctx) error { + key := c.Params("key") + pagingConfig := paging.NewEndpointStatusParams() + status, err := store.Get().GetEndpointStatusByKey(key, pagingConfig.WithResults(1, 1)) + if err != nil { + if err == common.ErrEndpointNotFound { + return c.Status(404).SendString(err.Error()) + } else if err == common.ErrInvalidTimeRange { + return c.Status(400).SendString(err.Error()) + } + return c.Status(500).SendString(err.Error()) + } + healthStatus := HealthStatusUnknown + if len(status.Results) > 0 { + if status.Results[0].Success { + healthStatus = HealthStatusUp + } else { + healthStatus = HealthStatusDown + } + } + c.Set("Content-Type", "application/json") + c.Set("Cache-Control", "no-cache, no-store, must-revalidate") + c.Set("Expires", "0") + jsonData, err := generateHealthBadgeShields(healthStatus) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + return c.Status(200).Send(jsonData) +} + func generateUptimeBadgeSVG(duration string, uptime float64) []byte { var labelWidth, valueWidth, valueWidthAdjustment int switch duration { @@ -299,6 +330,17 @@ func generateHealthBadgeSVG(healthStatus string) []byte { return svg } +func generateHealthBadgeShields(healthStatus string) ([]byte, error) { + color := getBadgeShieldsColorFromHealth(healthStatus) + data := map[string]interface{}{ + "schemaVersion": 1, + "label": "gatus", + "message": healthStatus, + "color": color, + } + return json.Marshal(data) +} + func getBadgeColorFromHealth(healthStatus string) string { if healthStatus == HealthStatusUp { return badgeColorHexAwesome @@ -307,3 +349,12 @@ func getBadgeColorFromHealth(healthStatus string) string { } return badgeColorHexPassable } + +func getBadgeShieldsColorFromHealth(healthStatus string) string { + if healthStatus == HealthStatusUp { + return "brightgreen" + } else if healthStatus == HealthStatusDown { + return "red" + } + return "yellow" +} diff --git a/api/badge_test.go b/api/badge_test.go index d0ca313c..d3da9da6 100644 --- a/api/badge_test.go +++ b/api/badge_test.go @@ -110,6 +110,21 @@ func TestBadge(t *testing.T) { Path: "/api/v1/endpoints/invalid_key/health/badge.svg", ExpectedCode: http.StatusNotFound, }, + { + Name: "badge-shields-health-up", + Path: "/api/v1/endpoints/core_frontend/health/badge.shields", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-shields-health-down", + Path: "/api/v1/endpoints/core_backend/health/badge.shields", + ExpectedCode: http.StatusOK, + }, + { + Name: "badge-shields-health-for-invalid-key", + Path: "/api/v1/endpoints/invalid_key/health/badge.shields", + ExpectedCode: http.StatusNotFound, + }, { Name: "chart-response-time-24h", Path: "/api/v1/endpoints/core_backend/response-times/24h/chart.svg",