Compare commits

..

13 Commits

Author SHA1 Message Date
TwiN
2af3425b9e Fix #202: Postgres error when an endpoint has a second page but others do not 2021-11-16 22:56:16 -05:00
TwiN
31bf2aeb80 Update TwiN/health to v1.1.0 2021-11-15 20:11:13 -05:00
TwiN
787f6f0d74 Add feedback email address 2021-11-12 00:32:11 -05:00
TwiN
17a431321c Pass http.NoBody instead of nil as body 2021-11-11 00:14:00 -05:00
TwiN
05e9add16d Regenerate static assets 2021-11-09 00:16:48 -05:00
TwiN
c4ef56511d Update dependencies 2021-11-09 00:07:44 -05:00
TwiN
cfa2c8ef6f Minor updates 2021-11-09 00:06:41 -05:00
TwiN
f36b6863ce Minor update 2021-11-08 23:54:06 -05:00
TwiN
24482cf7a0 Fix icon_url for Mattermost 2021-11-08 21:07:16 -05:00
TwiN
d661a0ea6d Add logo.png in .github/assets 2021-11-08 21:05:16 -05:00
TwiN
a0ec6941ab Display number of days rather than hours if >72h 2021-11-08 20:57:58 -05:00
TwiN
5e711fb3b9 Use http.Error instead of writer.Write 2021-11-08 20:56:35 -05:00
TwiN
ab66e7ec8a Fix badge examples 2021-11-08 02:22:43 -05:00
33 changed files with 2135 additions and 3410 deletions

BIN
.github/assets/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -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)**: ![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_website-external/uptimes/7d/badge.svg) ![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_website-external/response-times/24h/badge.svg)
- **[Badges](#badges)**: ![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/7d/badge.svg) ![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/24h/badge.svg)
## Usage
@@ -1129,9 +1130,9 @@ web:
### Badges
### Uptime
![Uptime 1h](https://status.twin.sh/api/v1/endpoints/core_website-external/uptimes/1h/badge.svg)
![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_website-external/uptimes/24h/badge.svg)
![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_website-external/uptimes/7d/badge.svg)
![Uptime 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/1h/badge.svg)
![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/24h/badge.svg)
![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/7d/badge.svg)
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:
```
![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_website-external/uptimes/24h/badge.svg)
![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/24h/badge.svg)
```
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
![Response time 1h](https://status.twin.sh/api/v1/endpoints/core_website-external/response-times/1h/badge.svg)
![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_website-external/response-times/24h/badge.svg)
![Response time 7d](https://status.twin.sh/api/v1/endpoints/core_website-external/response-times/7d/badge.svg)
![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)
![Response time 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/7d/badge.svg)
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`.

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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")
}

View File

@@ -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{

View File

@@ -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")
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
View File

@@ -0,0 +1,2 @@
bench:
go test -bench . -race

View File

@@ -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
View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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,

View File

@@ -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">

View File

@@ -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>

View File

@@ -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

View File

@@ -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