Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2af3425b9e | ||
|
|
31bf2aeb80 | ||
|
|
787f6f0d74 | ||
|
|
17a431321c | ||
|
|
05e9add16d | ||
|
|
c4ef56511d | ||
|
|
cfa2c8ef6f | ||
|
|
f36b6863ce | ||
|
|
24482cf7a0 | ||
|
|
d661a0ea6d | ||
|
|
a0ec6941ab | ||
|
|
5e711fb3b9 | ||
|
|
ab66e7ec8a |
BIN
.github/assets/logo.png
vendored
Normal file
BIN
.github/assets/logo.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
19
README.md
19
README.md
@@ -25,6 +25,7 @@ docker run -p 8080:8080 --name gatus twinproduction/gatus
|
||||
For more details, see [Usage](#usage)
|
||||
</details>
|
||||
|
||||
Have any feedback or want to share your good/bad experience with Gatus? Feel free to email me at [feedback@gatus.io](mailto:feedback@gatus.io)
|
||||
|
||||
## Table of Contents
|
||||
- [Why Gatus?](#why-gatus)
|
||||
@@ -103,7 +104,7 @@ The main features of Gatus are:
|
||||
- **Alerting**: While having a pretty visual dashboard is useful to keep track of the state of your application(s), you probably don't want to stare at it all day. Thus, notifications via Slack, Mattermost, Messagebird, PagerDuty, Twilio and Teams are supported out of the box with the ability to configure a custom alerting provider for any needs you might have, whether it be a different provider or a custom application that manages automated rollbacks.
|
||||
- **Metrics**
|
||||
- **Low resource consumption**: As with most Go applications, the resource footprint that this application requires is negligibly small.
|
||||
- **[Badges](#badges)**:  
|
||||
- **[Badges](#badges)**:  
|
||||
|
||||
|
||||
## Usage
|
||||
@@ -1129,9 +1130,9 @@ web:
|
||||
|
||||
### Badges
|
||||
### Uptime
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Gatus can automatically generate a SVG badge for one of your monitored endpoints.
|
||||
This allows you to put badges in your individual applications' README or even create your own status page, if you
|
||||
@@ -1156,15 +1157,15 @@ https://example.com/api/v1/endpoints/_frontend/uptimes/7d/badge.svg
|
||||
```
|
||||
Example:
|
||||
```
|
||||

|
||||

|
||||
```
|
||||
If you'd like to see a visual example of each badges available, you can simply navigate to the endpoint's detail page.
|
||||
|
||||
|
||||
### Response time
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
The endpoint to generate a badge is the following:
|
||||
```
|
||||
@@ -1188,7 +1189,7 @@ Specific endpoints can also be queried by using the following pattern:
|
||||
```
|
||||
/api/v1/endpoints/{group}_{endpoint}/statuses
|
||||
```
|
||||
Example: https://status.twin.sh/api/v1/endpoints/core_website-home/statuses
|
||||
Example: https://status.twin.sh/api/v1/endpoints/core_blog-home/statuses
|
||||
|
||||
Gzip compression will be used if the `Accept-Encoding` HTTP header contains `gzip`.
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ func (provider *AlertProvider) ToCustomAlertProvider(endpoint *core.Endpoint, al
|
||||
Body: fmt.Sprintf(`{
|
||||
"text": "",
|
||||
"username": "gatus",
|
||||
"icon_url": "https://raw.githubusercontent.com/TwiN/gatus/master/static/logo.png",
|
||||
"icon_url": "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png",
|
||||
"attachments": [
|
||||
{
|
||||
"title": ":rescue_worker_helmet: Gatus",
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestHandle(t *testing.T) {
|
||||
defer os.Clearenv()
|
||||
Handle(cfg.Security, cfg.Web, cfg.UI, cfg.Metrics)
|
||||
defer Shutdown()
|
||||
request, _ := http.NewRequest("GET", "/health", nil)
|
||||
request, _ := http.NewRequest("GET", "/health", http.NoBody)
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
server.Handler.ServeHTTP(responseRecorder, request)
|
||||
if responseRecorder.Code != http.StatusOK {
|
||||
|
||||
@@ -43,13 +43,12 @@ func UptimeBadge(writer http.ResponseWriter, request *http.Request) {
|
||||
uptime, err := store.Get().GetUptimeByKey(key, from, time.Now())
|
||||
if err != nil {
|
||||
if err == common.ErrEndpointNotFound {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
http.Error(writer, err.Error(), http.StatusNotFound)
|
||||
} else if err == common.ErrInvalidTimeRange {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
http.Error(writer, err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
_, _ = writer.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
formattedDate := time.Now().Format(http.TimeFormat)
|
||||
@@ -82,13 +81,12 @@ func ResponseTimeBadge(writer http.ResponseWriter, request *http.Request) {
|
||||
averageResponseTime, err := store.Get().GetAverageResponseTimeByKey(key, from, time.Now())
|
||||
if err != nil {
|
||||
if err == common.ErrEndpointNotFound {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
http.Error(writer, err.Error(), http.StatusNotFound)
|
||||
} else if err == common.ErrInvalidTimeRange {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
http.Error(writer, err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
_, _ = writer.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
formattedDate := time.Now().Format(http.TimeFormat)
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestUptimeBadge(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", scenario.Path, nil)
|
||||
request, _ := http.NewRequest("GET", scenario.Path, http.NoBody)
|
||||
if scenario.Gzip {
|
||||
request.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
|
||||
@@ -45,18 +45,16 @@ func ResponseTimeChart(writer http.ResponseWriter, r *http.Request) {
|
||||
hourlyAverageResponseTime, err := store.Get().GetHourlyAverageResponseTimeByKey(vars["key"], from, time.Now())
|
||||
if err != nil {
|
||||
if err == common.ErrEndpointNotFound {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
http.Error(writer, err.Error(), http.StatusNotFound)
|
||||
} else if err == common.ErrInvalidTimeRange {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
http.Error(writer, err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
_, _ = writer.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
if len(hourlyAverageResponseTime) == 0 {
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
_, _ = writer.Write(nil)
|
||||
http.Error(writer, "", http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
series := chart.TimeSeries{
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestResponseTimeChart(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", scenario.Path, nil)
|
||||
request, _ := http.NewRequest("GET", scenario.Path, http.NoBody)
|
||||
if scenario.Gzip {
|
||||
request.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestEndpointStatus(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", scenario.Path, nil)
|
||||
request, _ := http.NewRequest("GET", scenario.Path, http.NoBody)
|
||||
if scenario.Gzip {
|
||||
request.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
@@ -211,7 +211,7 @@ func TestEndpointStatuses(t *testing.T) {
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", scenario.Path, nil)
|
||||
request, _ := http.NewRequest("GET", scenario.Path, http.NoBody)
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(responseRecorder, request)
|
||||
if responseRecorder.Code != scenario.ExpectedCode {
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestFavIcon(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", scenario.Path, nil)
|
||||
request, _ := http.NewRequest("GET", scenario.Path, http.NoBody)
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(responseRecorder, request)
|
||||
if responseRecorder.Code != scenario.ExpectedCode {
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestCreateRouter(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", scenario.Path, nil)
|
||||
request, _ := http.NewRequest("GET", scenario.Path, http.NoBody)
|
||||
if scenario.Gzip {
|
||||
request.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestSinglePageApplication(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", scenario.Path, nil)
|
||||
request, _ := http.NewRequest("GET", scenario.Path, http.NoBody)
|
||||
if scenario.Gzip {
|
||||
request.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestExtractPageAndPageSizeFromRequest(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run("page-"+scenario.Page+"-pageSize-"+scenario.PageSize, func(t *testing.T) {
|
||||
request, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/statuses?page=%s&pageSize=%s", scenario.Page, scenario.PageSize), nil)
|
||||
request, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/statuses?page=%s&pageSize=%s", scenario.Page, scenario.PageSize), http.NoBody)
|
||||
actualPage, actualPageSize := extractPageAndPageSizeFromRequest(request)
|
||||
if actualPage != scenario.ExpectedPage {
|
||||
t.Errorf("expected %d, got %d", scenario.ExpectedPage, actualPage)
|
||||
|
||||
2
go.mod
2
go.mod
@@ -4,7 +4,7 @@ go 1.17
|
||||
|
||||
require (
|
||||
github.com/TwiN/gocache v1.2.4
|
||||
github.com/TwiN/health v1.0.1
|
||||
github.com/TwiN/health v1.1.0
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
|
||||
|
||||
4
go.sum
4
go.sum
@@ -35,8 +35,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/TwiN/gocache v1.2.4 h1:AfJ1YRcxtQ/zZEN61URDwk/dwFG7LSRenU5qIm9dQzo=
|
||||
github.com/TwiN/gocache v1.2.4/go.mod h1:BjabsQQy6z5uHDorHa4LJVPEzFeitLIDbCtdv3gc1gA=
|
||||
github.com/TwiN/health v1.0.1 h1:Q8lE6mTMPG4A5nHXq5Xa+NY4Y8LkQdRBWh1ReUkuc6Y=
|
||||
github.com/TwiN/health v1.0.1/go.mod h1:Bt+lEvSi6C/9NWb7OoGmUmgtS4dfPeMM9EINnURv5dE=
|
||||
github.com/TwiN/health v1.1.0 h1:IbXV4b5VPxzfIqOPiP/19JdBNFYM0oEDReLbUazhb2k=
|
||||
github.com/TwiN/health v1.1.0/go.mod h1:Bt+lEvSi6C/9NWb7OoGmUmgtS4dfPeMM9EINnURv5dE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSQLStorageRequiresFile = errors.New("sql storage requires a non-empty file to be defined")
|
||||
ErrMemoryStorageDoesNotSupportFile = errors.New("memory storage does not support persistence, use sqlite if you want persistence on file")
|
||||
ErrSQLStorageRequiresPath = errors.New("sql storage requires a non-empty path to be defined")
|
||||
ErrMemoryStorageDoesNotSupportPath = errors.New("memory storage does not support persistence, use sqlite if you want persistence on file")
|
||||
ErrCannotSetBothFileAndPath = errors.New("file has been deprecated in favor of path: you cannot set both of them")
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (c *Config) ValidateAndSetDefaults() error {
|
||||
c.Type = TypeMemory
|
||||
}
|
||||
if (c.Type == TypePostgres || c.Type == TypeSQLite) && len(c.Path) == 0 {
|
||||
return ErrSQLStorageRequiresFile
|
||||
return ErrSQLStorageRequiresPath
|
||||
}
|
||||
if c.Type == TypeMemory && len(c.Path) > 0 {
|
||||
log.Println("WARNING: Your configuration is using a storage of type memory with persistence, which has been deprecated")
|
||||
@@ -53,7 +53,7 @@ func (c *Config) ValidateAndSetDefaults() error {
|
||||
log.Println("WARNING: If you want persistence, use 'storage.type: sqlite' instead of 'storage.type: memory'")
|
||||
log.Println("WARNING: See https://github.com/TwiN/gatus/issues/198")
|
||||
// XXX: Uncomment the following line for v4.0.0
|
||||
//return ErrMemoryStorageDoesNotSupportFile
|
||||
//return ErrMemoryStorageDoesNotSupportPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -493,12 +493,6 @@ func (s *Store) getEndpointStatusByKey(tx *sql.Tx, key string, parameters *pagin
|
||||
log.Printf("[sql][getEndpointStatusByKey] Failed to retrieve results for key=%s: %s", key, err.Error())
|
||||
}
|
||||
}
|
||||
//if parameters.IncludeUptime {
|
||||
// now := time.Now()
|
||||
// endpointStatus.Uptime.LastHour, _, err = s.getEndpointUptime(tx, endpointID, now.Add(-time.Hour), now)
|
||||
// endpointStatus.Uptime.LastTwentyFourHours, _, err = s.getEndpointUptime(tx, endpointID, now.Add(-24*time.Hour), now)
|
||||
// endpointStatus.Uptime.LastSevenDays, _, err = s.getEndpointUptime(tx, endpointID, now.Add(-7*24*time.Hour), now)
|
||||
//}
|
||||
return endpointStatus, nil
|
||||
}
|
||||
|
||||
@@ -574,6 +568,10 @@ func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, pag
|
||||
results = append([]*core.Result{result}, results...)
|
||||
idResultMap[id] = result
|
||||
}
|
||||
if len(idResultMap) == 0 {
|
||||
// If there's no result, we'll just return an empty/nil slice
|
||||
return
|
||||
}
|
||||
// Get condition results
|
||||
args := make([]interface{}, 0, len(idResultMap))
|
||||
query := `SELECT endpoint_result_id, condition, success
|
||||
|
||||
@@ -74,6 +74,7 @@ var (
|
||||
|
||||
func Get() Store {
|
||||
if !initialized {
|
||||
// This only happens in tests
|
||||
log.Println("[store][Get] Provider requested before it was initialized, automatically initializing")
|
||||
err := Initialize(nil)
|
||||
if err != nil {
|
||||
@@ -92,12 +93,12 @@ func Initialize(cfg *storage.Config) error {
|
||||
cancelFunc()
|
||||
}
|
||||
if cfg == nil {
|
||||
// This only happens in tests
|
||||
log.Println("[store][Initialize] nil storage config passed as parameter. This should only happen in tests. Defaulting to an empty config.")
|
||||
cfg = &storage.Config{}
|
||||
}
|
||||
if len(cfg.Path) == 0 && cfg.Type != storage.TypePostgres {
|
||||
log.Printf("[store][Initialize] Creating storage provider with type=%s and file=%s", cfg.Type, cfg.Path)
|
||||
} else {
|
||||
log.Printf("[store][Initialize] Creating storage provider with type=%s", cfg.Type)
|
||||
log.Printf("[store][Initialize] Creating storage provider of type=%s", cfg.Type)
|
||||
}
|
||||
ctx, cancelFunc = context.WithCancel(context.Background())
|
||||
switch cfg.Type {
|
||||
|
||||
@@ -184,13 +184,10 @@ func TestStore_GetEndpointStatusForMissingStatusReturnsNil(t *testing.T) {
|
||||
func TestStore_GetAllEndpointStatuses(t *testing.T) {
|
||||
scenarios := initStoresAndBaseScenarios(t, "TestStore_GetAllEndpointStatuses")
|
||||
defer cleanUp(scenarios)
|
||||
firstResult := testSuccessfulResult
|
||||
secondResult := testUnsuccessfulResult
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.Name, func(t *testing.T) {
|
||||
scenario.Store.Insert(&testEndpoint, &firstResult)
|
||||
scenario.Store.Insert(&testEndpoint, &secondResult)
|
||||
// Can't be bothered dealing with timezone issues on the worker that runs the automated tests
|
||||
scenario.Store.Insert(&testEndpoint, &testSuccessfulResult)
|
||||
scenario.Store.Insert(&testEndpoint, &testUnsuccessfulResult)
|
||||
endpointStatuses, err := scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20))
|
||||
if err != nil {
|
||||
t.Error("shouldn't have returned an error, got", err.Error())
|
||||
@@ -210,6 +207,37 @@ func TestStore_GetAllEndpointStatuses(t *testing.T) {
|
||||
}
|
||||
scenario.Store.Clear()
|
||||
})
|
||||
t.Run(scenario.Name+"-page-2", func(t *testing.T) {
|
||||
otherEndpoint := testEndpoint
|
||||
otherEndpoint.Name = testEndpoint.Name + "-other"
|
||||
scenario.Store.Insert(&testEndpoint, &testSuccessfulResult)
|
||||
scenario.Store.Insert(&otherEndpoint, &testSuccessfulResult)
|
||||
scenario.Store.Insert(&otherEndpoint, &testSuccessfulResult)
|
||||
scenario.Store.Insert(&otherEndpoint, &testSuccessfulResult)
|
||||
endpointStatuses, err := scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(2, 2))
|
||||
if err != nil {
|
||||
t.Error("shouldn't have returned an error, got", err.Error())
|
||||
}
|
||||
if len(endpointStatuses) != 2 {
|
||||
t.Fatal("expected 2 endpoint statuses")
|
||||
}
|
||||
if endpointStatuses[0] == nil || endpointStatuses[1] == nil {
|
||||
t.Fatal("expected endpoint status to exist")
|
||||
}
|
||||
if len(endpointStatuses[0].Results) != 0 {
|
||||
t.Error("expected 0 results on the first endpoint, got", len(endpointStatuses[0].Results))
|
||||
}
|
||||
if len(endpointStatuses[1].Results) != 1 {
|
||||
t.Error("expected 1 result on the second endpoint, got", len(endpointStatuses[1].Results))
|
||||
}
|
||||
if len(endpointStatuses[0].Events) != 0 {
|
||||
t.Error("expected 0 events on the first endpoint, got", len(endpointStatuses[0].Events))
|
||||
}
|
||||
if len(endpointStatuses[1].Events) != 0 {
|
||||
t.Error("expected 0 events on the second endpoint, got", len(endpointStatuses[1].Events))
|
||||
}
|
||||
scenario.Store.Clear()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/TwiN/health/Makefile
generated
vendored
Normal file
2
vendor/github.com/TwiN/health/Makefile
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bench:
|
||||
go test -bench . -race
|
||||
47
vendor/github.com/TwiN/health/health.go
generated
vendored
47
vendor/github.com/TwiN/health/health.go
generated
vendored
@@ -1,6 +1,9 @@
|
||||
package health
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
handler = &healthHandler{
|
||||
@@ -13,6 +16,8 @@ var (
|
||||
type healthHandler struct {
|
||||
useJSON bool
|
||||
status Status
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// WithJSON configures whether the handler should output a response in JSON or in raw text
|
||||
@@ -24,30 +29,48 @@ func (h *healthHandler) WithJSON(v bool) *healthHandler {
|
||||
}
|
||||
|
||||
// ServeHTTP serves the HTTP request for the health handler
|
||||
func (h healthHandler) ServeHTTP(writer http.ResponseWriter, _ *http.Request) {
|
||||
var status int
|
||||
func (h *healthHandler) ServeHTTP(writer http.ResponseWriter, _ *http.Request) {
|
||||
var statusCode int
|
||||
var body []byte
|
||||
if h.status == Up {
|
||||
status = http.StatusOK
|
||||
handlerStatus := h.getStatus()
|
||||
if handlerStatus == Up {
|
||||
statusCode = http.StatusOK
|
||||
} else {
|
||||
status = http.StatusInternalServerError
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
if h.useJSON {
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
body = []byte(`{"status":"` + h.status + `"}`)
|
||||
body = []byte(`{"status":"` + handlerStatus + `"}`)
|
||||
} else {
|
||||
body = []byte(h.status)
|
||||
body = []byte(handlerStatus)
|
||||
}
|
||||
writer.WriteHeader(status)
|
||||
writer.WriteHeader(statusCode)
|
||||
_, _ = writer.Write(body)
|
||||
}
|
||||
|
||||
func (h *healthHandler) getStatus() Status {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
return h.status
|
||||
}
|
||||
|
||||
func (h *healthHandler) setStatus(status Status) {
|
||||
h.Lock()
|
||||
h.status = status
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
// Handler retrieves the health handler
|
||||
func Handler() *healthHandler {
|
||||
return handler
|
||||
}
|
||||
|
||||
// SetStatus sets the status to be reflected by the health handler
|
||||
func SetStatus(status Status) {
|
||||
handler.status = status
|
||||
// GetStatus retrieves the current status returned by the health handler
|
||||
func GetStatus() Status {
|
||||
return handler.getStatus()
|
||||
}
|
||||
|
||||
// SetStatus sets the status to be returned by the health handler
|
||||
func SetStatus(status Status) {
|
||||
handler.setStatus(status)
|
||||
}
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -1,7 +1,7 @@
|
||||
# github.com/TwiN/gocache v1.2.4
|
||||
## explicit; go 1.16
|
||||
github.com/TwiN/gocache
|
||||
# github.com/TwiN/health v1.0.1
|
||||
# github.com/TwiN/health v1.1.0
|
||||
## explicit; go 1.17
|
||||
github.com/TwiN/health
|
||||
# github.com/beorn7/perks v1.0.1
|
||||
|
||||
5306
web/app/package-lock.json
generated
5306
web/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gatus",
|
||||
"version": "3.2.2",
|
||||
"version": "3.3.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --mode development",
|
||||
@@ -8,22 +8,22 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.17.3",
|
||||
"vue": "^3.2.11",
|
||||
"core-js": "^3.19.1",
|
||||
"vue": "3.2.21",
|
||||
"vue-router": "^4.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^5.0.0-beta.3",
|
||||
"@vue/cli-plugin-eslint": "^5.0.0-beta.3",
|
||||
"@vue/cli-plugin-router": "^5.0.0-beta.3",
|
||||
"@vue/cli-service": "^5.0.0-beta.3",
|
||||
"@vue/compiler-sfc": "^3.2.11",
|
||||
"autoprefixer": "^10.3.4",
|
||||
"@vue/cli-plugin-babel": "5.0.0-beta.6",
|
||||
"@vue/cli-plugin-eslint": "5.0.0-beta.6",
|
||||
"@vue/cli-plugin-router": "5.0.0-beta.6",
|
||||
"@vue/cli-service": "5.0.0-beta.6",
|
||||
"@vue/compiler-sfc": "3.2.21",
|
||||
"autoprefixer": "10.4.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^7.17.0",
|
||||
"postcss": "^8.3.6",
|
||||
"tailwindcss": "^2.2.15"
|
||||
"tailwindcss": "^2.2.19"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script type="text/javascript">
|
||||
window.config = {
|
||||
logo: "{{ .Logo }}"
|
||||
};
|
||||
window.config = {logo: "{{ .Logo }}"};
|
||||
</script>
|
||||
<title>{{ .Title }}</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="w-1/4 flex justify-end">
|
||||
<img v-if="getLogo" :src="getLogo" alt="Gatus" class="object-scale-down" style="max-width: 100px; min-width: 50px; min-height:50px;"/>
|
||||
<img v-if="!getLogo" src="./assets/logo.png" alt="Gatus" class="object-scale-down" style="max-width: 100px; min-width: 50px; min-height:50px;"/>
|
||||
<img v-else src="./assets/logo.png" alt="Gatus" class="object-scale-down" style="max-width: 100px; min-width: 50px; min-height:50px;"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,11 @@ export const helper = {
|
||||
methods: {
|
||||
generatePrettyTimeAgo(t) {
|
||||
let differenceInMs = new Date().getTime() - new Date(t).getTime();
|
||||
if (differenceInMs > 3600000) {
|
||||
if (differenceInMs > 3*86400000) { // If it was more than 3 days ago, we'll display the number of days ago
|
||||
let days = (differenceInMs / 86400000).toFixed(0);
|
||||
return days + " day" + (days !== "1" ? "s" : "") + " ago";
|
||||
}
|
||||
if (differenceInMs > 3600000) { // If it was more than 1h ago, display the number of hours ago
|
||||
let hours = (differenceInMs / 3600000).toFixed(0);
|
||||
return hours + " hour" + (hours !== "1" ? "s" : "") + " ago";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><script>window.config = {
|
||||
logo: "{{ .Logo }}"
|
||||
};</script><title>{{ .Title }}</title><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><script defer="defer" src="/js/chunk-vendors.js" type="module"></script><script defer="defer" src="/js/app.js" type="module"></script><link href="/css/app.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.js" nomodule></script><script defer="defer" src="/js/app-legacy.js" nomodule></script></head><body class="dark:bg-gray-900"><noscript><strong>Enable JavaScript to view this page.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><script>window.config = {logo: "{{ .Logo }}"};</script><title>{{ .Title }}</title><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><script defer="defer" type="module" src="/js/chunk-vendors.js"></script><script defer="defer" type="module" src="/js/app.js"></script><link href="/css/app.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.js" nomodule></script><script defer="defer" src="/js/app-legacy.js" nomodule></script></head><body class="dark:bg-gray-900"><noscript><strong>Enable JavaScript to view this page.</strong></noscript><div id="app"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user