Repository: TwiN/gatus

Issue:
Make MaximumNumberOfResults and MaximumNumberOfEvents configurable
### Describe the feature request

Right now only 100 checks and 50 events are stored. If we have frequent checks, for instance with 10s internal, we will store results only for 16 last minutes which is not enough for retrospective analysis. It would be very great to configure the numbers. 

### Why do you personally want this feature to be implemented?

We would like to store old data. Currently, for this we have to do checks less frequently.

### How long have you been using this project?

_No response_

### Additional information

_No response_

Discussion:
@BassSingh: I like this :)@mensinda: I also ran into this, and decided to work around this limitation manually. Turns out, increasing the hard coded values is relatively easy. For anyone else who runs into this limitation until #476 is merged:

```diff
diff --git a/storage/store/common/limits.go b/storage/store/common/limits.go
index 92007220..a5187bb8 100644
--- a/storage/store/common/limits.go
+++ b/storage/store/common/limits.go
@@ -2,8 +2,8 @@ package common

 const (
 	// MaximumNumberOfResults is the maximum number of results that an endpoint can have
-	MaximumNumberOfResults = 100
+	MaximumNumberOfResults = 1000

 	// MaximumNumberOfEvents is the maximum number of events that an endpoint can have
-	MaximumNumberOfEvents = 50
+	MaximumNumberOfEvents = 500
 )
diff --git a/web/app/src/components/EndpointGroup.vue b/web/app/src/components/EndpointGroup.vue
index dbc1fa63..aa801014 100644
--- a/web/app/src/components/EndpointGroup.vue
+++ b/web/app/src/components/EndpointGroup.vue
@@ -18,7 +18,7 @@
       <slot v-for="(endpoint, idx) in endpoints" :key="idx">
         <Endpoint
             :data="endpoint"
-            :maximumNumberOfResults="20"
+            :maximumNumberOfResults="40"
             @showTooltip="showTooltip"
             @toggleShowAverageResponseTime="toggleShowAverageResponseTime" :showAverageResponseTime="showAverageResponseTime"
         />
diff --git a/web/app/src/components/Pagination.vue b/web/app/src/components/Pagination.vue
index 3f909e1f..d88ab31d 100644
--- a/web/app/src/components/Pagination.vue
+++ b/web/app/src/components/Pagination.vue
@@ -1,7 +1,7 @@
 <template>
   <div class="mt-3 flex">
     <div class="flex-1">
-      <button v-if="currentPage < 5" @click="nextPage" class="bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600">&lt;</button>
+      <button v-if="currentPage < 25" @click="nextPage" class="bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600">&lt;</button>
     </div>
     <div class="flex-1 text-right">
       <button v-if="currentPage > 1" @click="previousPage" class="bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600">&gt;</button>
diff --git a/web/app/src/views/Details.vue b/web/app/src/views/Details.vue
index 18706475..41894380 100644
--- a/web/app/src/views/Details.vue
+++ b/web/app/src/views/Details.vue
@@ -9,7 +9,7 @@
       <hr class="mb-4"/>
       <Endpoint
           :data="endpointStatus"
-          :maximumNumberOfResults="20"
+          :maximumNumberOfResults="40"
           @showTooltip="showTooltip"
           @toggleShowAverageResponseTime="toggleShowAverageResponseTime"
           :showAverageResponseTime="showAverageResponseTime"
@@ -112,7 +112,7 @@ export default {
   methods: {
     fetchData() {
       //console.log("[Details][fetchData] Fetching data");
-      fetch(`${this.serverUrl}/api/v1/endpoints/${this.$route.params.key}/statuses?page=${this.currentPage}`, {credentials: 'include'})
+      fetch(`${this.serverUrl}/api/v1/endpoints/${this.$route.params.key}/statuses?page=${this.currentPage}&pageSize=40`, {credentials: 'include'})
       .then(response => {
         if (response.status === 200) {
           response.json().then(data => {
diff --git a/web/app/src/views/Home.vue b/web/app/src/views/Home.vue
index 21b95d55..dfae7957 100644
--- a/web/app/src/views/Home.vue
+++ b/web/app/src/views/Home.vue
@@ -32,7 +32,7 @@ export default {
   emits: ['showTooltip', 'toggleShowAverageResponseTime'],
   methods: {
     fetchData() {
-      fetch(`${SERVER_URL}/api/v1/endpoints/statuses?page=${this.currentPage}`, {credentials: 'include'})
+      fetch(`${SERVER_URL}/api/v1/endpoints/statuses?page=${this.currentPage}&pageSize=40`, {credentials: 'include'})
       .then(response => {
         this.retrievedData = true;
         if (response.status === 200) {
```

After applying the patch:

```
cd web/app
npm install
npm run build
cd ..
docker build .
```@TwiN: This feature request is a big one, and I'm finally getting around to it once and for all in #1110 
It's still a WIP, but it should be merged very soon.

Solution patch:
diff --git a/README.md b/README.md
index d2621c7d..a809ae42 100644
--- a/README.md
+++ b/README.md
@@ -382,12 +382,14 @@ Here are some examples of conditions you can use:
 
 
 ### Storage
-| Parameter         | Description                                                                                                                                        | Default    |
-|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------|:-----------|
-| `storage`         | Storage configuration                                                                                                                              | `{}`       |
-| `storage.path`    | Path to persist the data in. Only supported for types `sqlite` and `postgres`.                                                                     | `""`       |
-| `storage.type`    | Type of storage. Valid types: `memory`, `sqlite`, `postgres`.                                                                                      | `"memory"` |
-| `storage.caching` | Whether to use write-through caching. Improves loading time for large dashboards. <br />Only supported if `storage.type` is `sqlite` or `postgres` | `false`    |
+| Parameter                           | Description                                                                                                                                        | Default    |
+|:------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------|:-----------|
+| `storage`                           | Storage configuration                                                                                                                              | `{}`       |
+| `storage.path`                      | Path to persist the data in. Only supported for types `sqlite` and `postgres`.                                                                     | `""`       |
+| `storage.type`                      | Type of storage. Valid types: `memory`, `sqlite`, `postgres`.                                                                                      | `"memory"` |
+| `storage.caching`                   | Whether to use write-through caching. Improves loading time for large dashboards. <br />Only supported if `storage.type` is `sqlite` or `postgres` | `false`    |
+| `storage.maximum-number-of-results` | The maximum number of results that an endpoint can have                                                                                            | `100`      |
+| `storage.maximum-number-of-events`  | The maximum number of events that an endpoint can have                                                                                             | `50`       |
 
 The results for each endpoint health check as well as the data for uptime and the past events must be persisted
 so that they can be displayed on the dashboard. These parameters allow you to configure the storage in question.
@@ -398,6 +400,8 @@ so that they can be displayed on the dashboard. These parameters allow you to co
 # Because the data is stored in memory, the data will not survive a restart.
 storage:
   type: memory
+  maximum-number-of-results: 200
+  maximum-number-of-events: 5
 ```
 - If `storage.type` is `sqlite`, `storage.path` must not be blank:
 ```yaml
diff --git a/api/api.go b/api/api.go
index b918af2a..b5e04ff7 100644
--- a/api/api.go
+++ b/api/api.go
@@ -126,6 +126,6 @@ func (a *API) createRouter(cfg *config.Config) *fiber.App {
 		}
 	}
 	protectedAPIRouter.Get("/v1/endpoints/statuses", EndpointStatuses(cfg))
-	protectedAPIRouter.Get("/v1/endpoints/:key/statuses", EndpointStatus)
+	protectedAPIRouter.Get("/v1/endpoints/:key/statuses", EndpointStatus(cfg))
 	return app
 }
diff --git a/api/endpoint_status.go b/api/endpoint_status.go
index 691fa8ca..4d8777d8 100644
--- a/api/endpoint_status.go
+++ b/api/endpoint_status.go
@@ -20,7 +20,7 @@ import (
 // Due to how intensive this operation can be on the storage, this function leverages a cache.
 func EndpointStatuses(cfg *config.Config) fiber.Handler {
 	return func(c *fiber.Ctx) error {
-		page, pageSize := extractPageAndPageSizeFromRequest(c)
+		page, pageSize := extractPageAndPageSizeFromRequest(c, cfg.Storage.MaximumNumberOfResults)
 		value, exists := cache.Get(fmt.Sprintf("endpoint-status-%d-%d", page, pageSize))
 		var data []byte
 		if !exists {
@@ -83,25 +83,27 @@ func getEndpointStatusesFromRemoteInstances(remoteConfig *remote.Config) ([]*end
 }
 
 // EndpointStatus retrieves a single endpoint.Status by group and endpoint name
-func EndpointStatus(c *fiber.Ctx) error {
-	page, pageSize := extractPageAndPageSizeFromRequest(c)
-	endpointStatus, err := store.Get().GetEndpointStatusByKey(c.Params("key"), paging.NewEndpointStatusParams().WithResults(page, pageSize).WithEvents(1, common.MaximumNumberOfEvents))
-	if err != nil {
-		if errors.Is(err, common.ErrEndpointNotFound) {
-			return c.Status(404).SendString(err.Error())
+func EndpointStatus(cfg *config.Config) fiber.Handler {
+	return func(c *fiber.Ctx) error {
+		page, pageSize := extractPageAndPageSizeFromRequest(c, cfg.Storage.MaximumNumberOfResults)
+		endpointStatus, err := store.Get().GetEndpointStatusByKey(c.Params("key"), paging.NewEndpointStatusParams().WithResults(page, pageSize).WithEvents(1, cfg.Storage.MaximumNumberOfEvents))
+		if err != nil {
+			if errors.Is(err, common.ErrEndpointNotFound) {
+				return c.Status(404).SendString(err.Error())
+			}
+			logr.Errorf("[api.EndpointStatus] Failed to retrieve endpoint status: %s", err.Error())
+			return c.Status(500).SendString(err.Error())
 		}
-		logr.Errorf("[api.EndpointStatus] Failed to retrieve endpoint status: %s", err.Error())
-		return c.Status(500).SendString(err.Error())
-	}
-	if endpointStatus == nil { // XXX: is this check necessary?
-		logr.Errorf("[api.EndpointStatus] Endpoint with key=%s not found", c.Params("key"))
-		return c.Status(404).SendString("not found")
-	}
-	output, err := json.Marshal(endpointStatus)
-	if err != nil {
-		logr.Errorf("[api.EndpointStatus] Unable to marshal object to JSON: %s", err.Error())
-		return c.Status(500).SendString("unable to marshal object to JSON")
+		if endpointStatus == nil { // XXX: is this check necessary?
+			logr.Errorf("[api.EndpointStatus] Endpoint with key=%s not found", c.Params("key"))
+			return c.Status(404).SendString("not found")
+		}
+		output, err := json.Marshal(endpointStatus)
+		if err != nil {
+			logr.Errorf("[api.EndpointStatus] Unable to marshal object to JSON: %s", err.Error())
+			return c.Status(500).SendString("unable to marshal object to JSON")
+		}
+		c.Set("Content-Type", "application/json")
+		return c.Status(200).Send(output)
 	}
-	c.Set("Content-Type", "application/json")
-	return c.Status(200).Send(output)
 }
diff --git a/api/util.go b/api/util.go
index 90e647b5..766ab782 100644
--- a/api/util.go
+++ b/api/util.go
@@ -3,7 +3,6 @@ package api
 import (
 	"strconv"
 
-	"github.com/TwiN/gatus/v5/storage/store/common"
 	"github.com/gofiber/fiber/v2"
 )
 
@@ -13,12 +12,9 @@ const (
 
 	// DefaultPageSize is the default page siZE to use if none is specified or an invalid value is provided
 	DefaultPageSize = 20
-
-	// MaximumPageSize is the maximum page size allowed
-	MaximumPageSize = common.MaximumNumberOfResults
 )
 
-func extractPageAndPageSizeFromRequest(c *fiber.Ctx) (page, pageSize int) {
+func extractPageAndPageSizeFromRequest(c *fiber.Ctx, maximumNumberOfResults int) (page, pageSize int) {
 	var err error
 	if pageParameter := c.Query("page"); len(pageParameter) == 0 {
 		page = DefaultPage
@@ -38,8 +34,8 @@ func extractPageAndPageSizeFromRequest(c *fiber.Ctx) (page, pageSize int) {
 		if err != nil {
 			pageSize = DefaultPageSize
 		}
-		if pageSize > MaximumPageSize {
-			pageSize = MaximumPageSize
+		if pageSize > maximumNumberOfResults {
+			pageSize = maximumNumberOfResults
 		} else if pageSize < 1 {
 			pageSize = DefaultPageSize
 		}
diff --git a/config/config.go b/config/config.go
index 61e1b128..701dd84e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -280,6 +280,8 @@ func parseAndValidateConfigBytes(yamlBytes []byte) (config *Config, err error) {
 		if err := validateConnectivityConfig(config); err != nil {
 			return nil, err
 		}
+		// Cross-config changes
+		config.UI.MaximumNumberOfResults = config.Storage.MaximumNumberOfResults
 	}
 	return
 }
@@ -303,7 +305,9 @@ func validateRemoteConfig(config *Config) error {
 func validateStorageConfig(config *Config) error {
 	if config.Storage == nil {
 		config.Storage = &storage.Config{
-			Type: storage.TypeMemory,
+			Type:                   storage.TypeMemory,
+			MaximumNumberOfResults: storage.DefaultMaximumNumberOfResults,
+			MaximumNumberOfEvents:  storage.DefaultMaximumNumberOfEvents,
 		}
 	} else {
 		if err := config.Storage.ValidateAndSetDefaults(); err != nil {
diff --git a/config/ui/ui.go b/config/ui/ui.go
index 33acf248..1055a6b0 100644
--- a/config/ui/ui.go
+++ b/config/ui/ui.go
@@ -5,6 +5,7 @@ import (
 	"errors"
 	"html/template"
 
+	"github.com/TwiN/gatus/v5/storage"
 	static "github.com/TwiN/gatus/v5/web"
 )
 
@@ -33,6 +34,8 @@ type Config struct {
 	Buttons     []Button `yaml:"buttons,omitempty"`     // Buttons to display below the header
 	CustomCSS   string   `yaml:"custom-css,omitempty"`  // Custom CSS to include in the page
 	DarkMode    *bool    `yaml:"dark-mode,omitempty"`   // DarkMode is a flag to enable dark mode by default
+
+	MaximumNumberOfResults int // MaximumNumberOfResults to display on the page, it's not configurable because we're passing it from the storage config
 }
 
 func (cfg *Config) IsDarkMode() bool {
@@ -59,13 +62,14 @@ func (btn *Button) Validate() error {
 // GetDefaultConfig returns a Config struct with the default values
 func GetDefaultConfig() *Config {
 	return &Config{
-		Title:       defaultTitle,
-		Description: defaultDescription,
-		Header:      defaultHeader,
-		Logo:        defaultLogo,
-		Link:        defaultLink,
-		CustomCSS:   defaultCustomCSS,
-		DarkMode:    &defaultDarkMode,
+		Title:                  defaultTitle,
+		Description:            defaultDescription,
+		Header:                 defaultHeader,
+		Logo:                   defaultLogo,
+		Link:                   defaultLink,
+		CustomCSS:              defaultCustomCSS,
+		DarkMode:               &defaultDarkMode,
+		MaximumNumberOfResults: storage.DefaultMaximumNumberOfResults,
 	}
 }
 
diff --git a/storage/config.go b/storage/config.go
index 5ee3539d..a707d5ef 100644
--- a/storage/config.go
+++ b/storage/config.go
@@ -4,6 +4,11 @@ import (
 	"errors"
 )
 
+const (
+	DefaultMaximumNumberOfResults = 100
+	DefaultMaximumNumberOfEvents  = 50
+)
+
 var (
 	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")
@@ -25,6 +30,12 @@ type Config struct {
 	// as they happen, also known as the write-through caching strategy.
 	// Does not apply if Config.Type is not TypePostgres or TypeSQLite.
 	Caching bool `yaml:"caching,omitempty"`
+
+	// MaximumNumberOfResults is the number of results each endpoint should be able to provide
+	MaximumNumberOfResults int `yaml:"maximum-number-of-results,omitempty"`
+
+	// MaximumNumberOfEvents is the number of events each endpoint should be able to provide
+	MaximumNumberOfEvents int `yaml:"maximum-number-of-events,omitempty"`
 }
 
 // ValidateAndSetDefaults validates the configuration and sets the default values (if applicable)
@@ -38,5 +49,11 @@ func (c *Config) ValidateAndSetDefaults() error {
 	if c.Type == TypeMemory && len(c.Path) > 0 {
 		return ErrMemoryStorageDoesNotSupportPath
 	}
+	if c.MaximumNumberOfResults <= 0 {
+		c.MaximumNumberOfResults = DefaultMaximumNumberOfResults
+	}
+	if c.MaximumNumberOfEvents <= 0 {
+		c.MaximumNumberOfEvents = DefaultMaximumNumberOfEvents
+	}
 	return nil
 }
diff --git a/storage/store/common/limits.go b/storage/store/common/limits.go
deleted file mode 100644
index 92007220..00000000
--- a/storage/store/common/limits.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package common
-
-const (
-	// MaximumNumberOfResults is the maximum number of results that an endpoint can have
-	MaximumNumberOfResults = 100
-
-	// MaximumNumberOfEvents is the maximum number of events that an endpoint can have
-	MaximumNumberOfEvents = 50
-)
diff --git a/storage/store/memory/memory.go b/storage/store/memory/memory.go
index 6451381d..41714dcd 100644
--- a/storage/store/memory/memory.go
+++ b/storage/store/memory/memory.go
@@ -17,15 +17,20 @@ type Store struct {
 	sync.RWMutex
 
 	cache *gocache.Cache
+
+	maximumNumberOfResults int // maximum number of results that an endpoint can have
+	maximumNumberOfEvents  int // maximum number of events that an endpoint can have
 }
 
 // NewStore creates a new store using gocache.Cache
 //
 // This store holds everything in memory, and if the file parameter is not blank,
 // supports eventual persistence.
-func NewStore() (*Store, error) {
+func NewStore(maximumNumberOfResults, maximumNumberOfEvents int) (*Store, error) {
 	store := &Store{
-		cache: gocache.NewCache().WithMaxSize(gocache.NoMaxSize),
+		cache:                  gocache.NewCache().WithMaxSize(gocache.NoMaxSize),
+		maximumNumberOfResults: maximumNumberOfResults,
+		maximumNumberOfEvents:  maximumNumberOfEvents,
 	}
 	return store, nil
 }
@@ -151,7 +156,7 @@ func (s *Store) Insert(ep *endpoint.Endpoint, result *endpoint.Result) error {
 			Timestamp: time.Now(),
 		})
 	}
-	AddResult(status.(*endpoint.Status), result)
+	AddResult(status.(*endpoint.Status), result, s.maximumNumberOfResults, s.maximumNumberOfEvents)
 	s.cache.Set(key, status)
 	s.Unlock()
 	return nil
diff --git a/storage/store/memory/util.go b/storage/store/memory/util.go
index 961f740a..acb17eb1 100644
--- a/storage/store/memory/util.go
+++ b/storage/store/memory/util.go
@@ -2,7 +2,6 @@ package memory
 
 import (
 	"github.com/TwiN/gatus/v5/config/endpoint"
-	"github.com/TwiN/gatus/v5/storage/store/common"
 	"github.com/TwiN/gatus/v5/storage/store/common/paging"
 )
 
@@ -51,7 +50,7 @@ func getStartAndEndIndex(numberOfResults int, page, pageSize int) (int, int) {
 
 // AddResult adds a Result to Status.Results and makes sure that there are
 // no more than MaximumNumberOfResults results in the Results slice
-func AddResult(ss *endpoint.Status, result *endpoint.Result) {
+func AddResult(ss *endpoint.Status, result *endpoint.Result, maximumNumberOfResults, maximumNumberOfEvents int) {
 	if ss == nil {
 		return
 	}
@@ -59,11 +58,11 @@ func AddResult(ss *endpoint.Status, result *endpoint.Result) {
 		// Check if there's any change since the last result
 		if ss.Results[len(ss.Results)-1].Success != result.Success {
 			ss.Events = append(ss.Events, endpoint.NewEventFromResult(result))
-			if len(ss.Events) > common.MaximumNumberOfEvents {
+			if len(ss.Events) > maximumNumberOfEvents {
 				// Doing ss.Events[1:] would usually be sufficient, but in the case where for some reason, the slice has
 				// more than one extra element, we can get rid of all of them at once and thus returning the slice to a
 				// length of MaximumNumberOfEvents by using ss.Events[len(ss.Events)-MaximumNumberOfEvents:] instead
-				ss.Events = ss.Events[len(ss.Events)-common.MaximumNumberOfEvents:]
+				ss.Events = ss.Events[len(ss.Events)-maximumNumberOfEvents:]
 			}
 		}
 	} else {
@@ -71,11 +70,11 @@ func AddResult(ss *endpoint.Status, result *endpoint.Result) {
 		ss.Events = append(ss.Events, endpoint.NewEventFromResult(result))
 	}
 	ss.Results = append(ss.Results, result)
-	if len(ss.Results) > common.MaximumNumberOfResults {
+	if len(ss.Results) > maximumNumberOfResults {
 		// Doing ss.Results[1:] would usually be sufficient, but in the case where for some reason, the slice has more
 		// than one extra element, we can get rid of all of them at once and thus returning the slice to a length of
 		// MaximumNumberOfResults by using ss.Results[len(ss.Results)-MaximumNumberOfResults:] instead
-		ss.Results = ss.Results[len(ss.Results)-common.MaximumNumberOfResults:]
+		ss.Results = ss.Results[len(ss.Results)-maximumNumberOfResults:]
 	}
 	processUptimeAfterResult(ss.Uptime, result)
 }
diff --git a/storage/store/sql/sql.go b/storage/store/sql/sql.go
index 30b7906f..cb0dd058 100644
--- a/storage/store/sql/sql.go
+++ b/storage/store/sql/sql.go
@@ -28,8 +28,8 @@ const (
 	// for aesthetic purposes, I deemed it wasn't worth the performance impact of yet another one-to-many table.
 	arraySeparator = "|~|"
 
-	eventsCleanUpThreshold  = common.MaximumNumberOfEvents + 10  // Maximum number of events before triggering a cleanup
-	resultsCleanUpThreshold = common.MaximumNumberOfResults + 10 // Maximum number of results before triggering a cleanup
+	eventsAboveMaximumCleanUpThreshold  = 10 // Maximum number of events above the configured maximum before triggering a cleanup
+	resultsAboveMaximumCleanUpThreshold = 10 // Maximum number of results above the configured maximum before triggering a cleanup
 
 	uptimeTotalEntriesMergeThreshold = 100                 // Maximum number of uptime entries before triggering a merge
 	uptimeAgeCleanUpThreshold        = 32 * 24 * time.Hour // Maximum uptime age before triggering a cleanup
@@ -58,17 +58,25 @@ type Store struct {
 	// writeThroughCache is a cache used to drastically decrease read latency by pre-emptively
 	// caching writes as they happen. If nil, writes are not cached.
 	writeThroughCache *gocache.Cache
+
+	maximumNumberOfResults int // maximum number of results that an endpoint can have
+	maximumNumberOfEvents  int // maximum number of events that an endpoint can have
 }
 
 // NewStore initializes the database and creates the schema if it doesn't already exist in the path specified
-func NewStore(driver, path string, caching bool) (*Store, error) {
+func NewStore(driver, path string, caching bool, maximumNumberOfResults, maximumNumberOfEvents int) (*Store, error) {
 	if len(driver) == 0 {
 		return nil, ErrDatabaseDriverNotSpecified
 	}
 	if len(path) == 0 {
 		return nil, ErrPathNotSpecified
 	}
-	store := &Store{driver: driver, path: path}
+	store := &Store{
+		driver:                 driver,
+		path:                   path,
+		maximumNumberOfResults: maximumNumberOfResults,
+		maximumNumberOfEvents:  maximumNumberOfEvents,
+	}
 	var err error
 	if store.db, err = sql.Open(driver, path); err != nil {
 		return nil, err
@@ -293,10 +301,10 @@ func (s *Store) Insert(ep *endpoint.Endpoint, result *endpoint.Result) error {
 				}
 			}
 		}
-		// Clean up old events if there's more than twice the maximum number of events
+		// Clean up old events if we're above the threshold
 		// This lets us both keep the table clean without impacting performance too much
 		// (since we're only deleting MaximumNumberOfEvents at a time instead of 1)
-		if numberOfEvents > eventsCleanUpThreshold {
+		if numberOfEvents > int64(s.maximumNumberOfEvents+eventsAboveMaximumCleanUpThreshold) {
 			if err = s.deleteOldEndpointEvents(tx, endpointID); err != nil {
 				logr.Errorf("[sql.Insert] Failed to delete old events for endpoint with key=%s: %s", ep.Key(), err.Error())
 			}
@@ -313,7 +321,7 @@ func (s *Store) Insert(ep *endpoint.Endpoint, result *endpoint.Result) error {
 	if err != nil {
 		logr.Errorf("[sql.Insert] Failed to retrieve total number of results for endpoint with key=%s: %s", ep.Key(), err.Error())
 	} else {
-		if numberOfResults > resultsCleanUpThreshold {
+		if numberOfResults > int64(s.maximumNumberOfResults+resultsAboveMaximumCleanUpThreshold) {
 			if err = s.deleteOldEndpointResults(tx, endpointID); err != nil {
 				logr.Errorf("[sql.Insert] Failed to delete old results for endpoint with key=%s: %s", ep.Key(), err.Error())
 			}
@@ -941,7 +949,7 @@ func (s *Store) deleteOldEndpointEvents(tx *sql.Tx, endpointID int64) error {
 				)
 		`,
 		endpointID,
-		common.MaximumNumberOfEvents,
+		s.maximumNumberOfEvents,
 	)
 	return err
 }
@@ -961,7 +969,7 @@ func (s *Store) deleteOldEndpointResults(tx *sql.Tx, endpointID int64) error {
 				)
 		`,
 		endpointID,
-		common.MaximumNumberOfResults,
+		s.maximumNumberOfResults,
 	)
 	return err
 }
diff --git a/storage/store/store.go b/storage/store/store.go
index 729e6482..f7b581ec 100644
--- a/storage/store/store.go
+++ b/storage/store/store.go
@@ -111,7 +111,10 @@ func Initialize(cfg *storage.Config) error {
 	if cfg == nil {
 		// This only happens in tests
 		logr.Warn("[store.Initialize] nil storage config passed as parameter. This should only happen in tests. Defaulting to an empty config.")
-		cfg = &storage.Config{}
+		cfg = &storage.Config{
+			MaximumNumberOfResults: storage.DefaultMaximumNumberOfResults,
+			MaximumNumberOfEvents:  storage.DefaultMaximumNumberOfEvents,
+		}
 	}
 	if len(cfg.Path) == 0 && cfg.Type != storage.TypePostgres {
 		logr.Infof("[store.Initialize] Creating storage provider of type=%s", cfg.Type)
@@ -119,14 +122,14 @@ func Initialize(cfg *storage.Config) error {
 	ctx, cancelFunc = context.WithCancel(context.Background())
 	switch cfg.Type {
 	case storage.TypeSQLite, storage.TypePostgres:
-		store, err = sql.NewStore(string(cfg.Type), cfg.Path, cfg.Caching)
+		store, err = sql.NewStore(string(cfg.Type), cfg.Path, cfg.Caching, cfg.MaximumNumberOfResults, cfg.MaximumNumberOfEvents)
 		if err != nil {
 			return err
 		}
 	case storage.TypeMemory:
 		fallthrough
 	default:
-		store, _ = memory.NewStore()
+		store, _ = memory.NewStore(cfg.MaximumNumberOfResults, cfg.MaximumNumberOfEvents)
 	}
 	return nil
 }
diff --git a/web/app/public/index.html b/web/app/public/index.html
index 31982192..87d01e8f 100644
--- a/web/app/public/index.html
+++ b/web/app/public/index.html
@@ -3,7 +3,7 @@
   <head>
     <meta charset="utf-8" />
     <script type="text/javascript">
-      window.config = {logo: "{{ .UI.Logo }}", header: "{{ .UI.Header }}", link: "{{ .UI.Link }}", buttons: []};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}
+      window.config = {logo: "{{ .UI.Logo }}", header: "{{ .UI.Header }}", link: "{{ .UI.Link }}", buttons: [], maximumNumberOfResults: "{{ .UI.MaximumNumberOfResults }}"};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}
     </script>
     <title>{{ .UI.Title }}</title>
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
diff --git a/web/app/src/components/Pagination.vue b/web/app/src/components/Pagination.vue
index 3f909e1f..b8ab1ff5 100644
--- a/web/app/src/components/Pagination.vue
+++ b/web/app/src/components/Pagination.vue
@@ -1,7 +1,7 @@
 <template>
   <div class="mt-3 flex">
     <div class="flex-1">
-      <button v-if="currentPage < 5" @click="nextPage" class="bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600">&lt;</button>
+      <button v-if="currentPage < maxPages" @click="nextPage" class="bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600">&lt;</button>
     </div>
     <div class="flex-1 text-right">
       <button v-if="currentPage > 1" @click="previousPage" class="bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600">&gt;</button>
@@ -13,6 +13,9 @@
 <script>
 export default {
   name: 'Pagination',
+  props: {
+    numberOfResultsPerPage: Number,
+  },
   components: {},
   emits: ['page'],
   methods: {
@@ -25,6 +28,11 @@ export default {
       this.$emit('page', this.currentPage);
     }
   },
+  computed: {
+    maxPages() {
+      return Math.ceil(parseInt(window.config.maximumNumberOfResults) / this.numberOfResultsPerPage)
+    }
+  },
   data() {
     return {
       currentPage: 1,
diff --git a/web/app/src/views/Details.vue b/web/app/src/views/Details.vue
index fad1d351..d5f303f8 100644
--- a/web/app/src/views/Details.vue
+++ b/web/app/src/views/Details.vue
@@ -14,7 +14,7 @@
           @toggleShowAverageResponseTime="toggleShowAverageResponseTime"
           :showAverageResponseTime="showAverageResponseTime"
       />
-      <Pagination @page="changePage"/>
+      <Pagination @page="changePage" :numberOfResultsPerPage="20" />
     </slot>
     <div v-if="endpointStatus && endpointStatus.key" class="mt-12">
       <h1 class="text-xl xl:text-3xl font-mono text-gray-400">UPTIME</h1>
diff --git a/web/app/src/views/Home.vue b/web/app/src/views/Home.vue
index 21b95d55..29a2dfdf 100644
--- a/web/app/src/views/Home.vue
+++ b/web/app/src/views/Home.vue
@@ -9,7 +9,7 @@
         @toggleShowAverageResponseTime="toggleShowAverageResponseTime"
         :showAverageResponseTime="showAverageResponseTime"
     />
-    <Pagination v-show="retrievedData" @page="changePage"/>
+    <Pagination v-show="retrievedData" @page="changePage" :numberOfResultsPerPage="20" />
   </slot>
   <Settings @refreshData="fetchData"/>
 </template>
diff --git a/web/static/css/app.css b/web/static/css/app.css
index 12707def..613ca68e 100644
--- a/web/static/css/app.css
+++ b/web/static/css/app.css
@@ -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: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-2{top:.5rem}.left-5{left:1.25rem}.bottom-2{bottom:.5rem}.float-right{float:right}.mx-auto{margin-left:auto;margin-right:auto}.my-auto{margin-top:auto;margin-bottom:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.my-24{margin-top:6rem;margin-bottom:6rem}.mb-2{margin-bottom:.5rem}.mt-4{margin-top:1rem}.mb-5{margin-bottom:1.25rem}.mt-3{margin-top:.75rem}.mr-2{margin-right:.5rem}.mb-4{margin-bottom:1rem}.mt-12{margin-top:3rem}.mt-6{margin-top:1.5rem}.mb-10{margin-bottom:2.5rem}.mb-1{margin-bottom:.25rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-64{height:16rem}.h-6{height:1.5rem}.w-64{width:16rem}.w-3\/4{width:75%}.w-1\/4{width:25%}.w-full{width:100%}.w-1\/2{width:50%}.w-7{width:1.75rem}.w-3{width:.75rem}.w-4{width:1rem}.w-8{width:2rem}.max-w-md{max-width:28rem}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-x-hidden{overflow-x:hidden}.rounded-sm{border-radius:.125rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded{border-radius:.25rem}.rounded-xl{border-radius:.75rem}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-green-800{--tw-border-opacity:1;border-color:rgb(22 101 52/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-green-600{--tw-gradient-from:#16a34a;--tw-gradient-to:rgba(22,163,74,0);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-green-700{--tw-gradient-to:#15803d}.object-scale-down{-o-object-fit:scale-down;object-fit:scale-down}.p-5{padding:1.25rem}.p-1{padding:.25rem}.p-3{padding:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.py-7{padding-top:1.75rem;padding-bottom:1.75rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-0{padding-left:0;padding-right:0}.pb-12{padding-bottom:3rem}.pt-12{padding-top:3rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-1{padding-left:.25rem}.pb-0\.5{padding-bottom:.125rem}.pb-0{padding-bottom:0}.pl-12{padding-left:3rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:Consolas,Monaco,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.text-2xl{font-size:1.5rem;line-height:2rem}.font-light{font-weight:300}.font-medium{font-weight:500}.font-extrabold{font-weight:800}.font-bold{font-weight:700}.leading-6{line-height:1.5rem}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.transition{transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.bg-success{background-color:#28a745}html:not(.dark) body{background-color:#f7f9fb}html{height:100%}body{padding-top:20px;padding-bottom:20px;min-height:100vh}#global{margin-top:0!important}#global,#results{max-width:1280px}@media screen and (max-width:1279px){body{padding-top:0;padding-bottom:0}#global{min-height:100vh}}@media (hover:hover) and (pointer:fine){.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:from-green-700:hover{--tw-gradient-from:#15803d;--tw-gradient-to:rgba(21,128,61,0);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:to-green-800:hover{--tw-gradient-to:#166534}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:text-blue-800:hover{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity))}.hover\:underline:hover{-webkit-text-decoration-line:underline;text-decoration-line:underline}}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity))}.dark .dark\:divide-gray-600>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(75 85 99/var(--tw-divide-opacity))}.dark .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}@media (hover:hover) and (pointer:fine){.dark .dark\:hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:hover\:bg-gray-600:hover{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.dark .dark\:hover\:text-gray-400:hover{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:hover\:text-blue-400:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.dark .dark\:hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}}@media (min-width:640px){.sm\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:text-4xl{font-size:2.25rem;line-height:2.5rem}}@media (min-width:1280px){.xl\:my-5{margin-top:1.25rem;margin-bottom:1.25rem}.xl\:rounded{border-radius:.25rem}.xl\:border{border-width:1px}.xl\:px-24{padding-left:6rem;padding-right:6rem}.xl\:pb-5{padding-bottom:1.25rem}.xl\:text-5xl{font-size:3rem;line-height:1}.xl\:text-3xl{font-size:1.875rem;line-height:2.25rem}.xl\:shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}}#settings{position:fixed;left:10px;bottom:10px}#settings select:focus{box-shadow:none}.endpoint:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.endpoint:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px;border-bottom-width:3px}.status-over-time{overflow:auto}.status-over-time>span:not(:first-child){margin-left:2px}.status{cursor:pointer;transition:all .5s ease-in-out;overflow-x:hidden;color:#fff;width:5%;font-size:75%;font-weight:700;text-align:center}.status:hover{opacity:.7;transition:opacity .1s ease-in-out;color:#000}.status-time-ago{color:#6a737d;opacity:.5;margin-top:5px}.status.status-success:after{content:"✓"}.status.status-failure:after{content:"X"}@media screen and (max-width:600px){.status.status-failure:after,.status.status-success:after{content:" ";white-space:pre}}.endpoint-group{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.endpoint-group h5:hover{color:#1b1e21}.endpoint-group-content>div:first-child{border-top-left-radius:0;border-top-right-radius:0}.endpoint[data-v-62613fe4]{border-radius:3px;border-bottom-width:3px}
\ No newline at end of file
+*/*,: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: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-2{top:.5rem}.left-5{left:1.25rem}.bottom-2{bottom:.5rem}.float-right{float:right}.mx-auto{margin-left:auto;margin-right:auto}.my-auto{margin-top:auto;margin-bottom:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.my-24{margin-top:6rem;margin-bottom:6rem}.mb-2{margin-bottom:.5rem}.mt-4{margin-top:1rem}.mb-5{margin-bottom:1.25rem}.mt-3{margin-top:.75rem}.mr-2{margin-right:.5rem}.mb-4{margin-bottom:1rem}.mt-12{margin-top:3rem}.mt-6{margin-top:1.5rem}.mb-10{margin-bottom:2.5rem}.mb-1{margin-bottom:.25rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-64{height:16rem}.h-6{height:1.5rem}.w-64{width:16rem}.w-3\/4{width:75%}.w-1\/4{width:25%}.w-full{width:100%}.w-1\/2{width:50%}.w-7{width:1.75rem}.w-3{width:.75rem}.w-4{width:1rem}.w-8{width:2rem}.max-w-md{max-width:28rem}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-x-hidden{overflow-x:hidden}.rounded-sm{border-radius:.125rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded{border-radius:.25rem}.rounded-xl{border-radius:.75rem}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-green-800{--tw-border-opacity:1;border-color:rgb(22 101 52/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-green-600{--tw-gradient-from:#16a34a;--tw-gradient-to:rgba(22,163,74,0);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-green-700{--tw-gradient-to:#15803d}.object-scale-down{-o-object-fit:scale-down;object-fit:scale-down}.p-5{padding:1.25rem}.p-1{padding:.25rem}.p-3{padding:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.py-7{padding-top:1.75rem;padding-bottom:1.75rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-0{padding-left:0;padding-right:0}.pb-12{padding-bottom:3rem}.pt-12{padding-top:3rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-1{padding-left:.25rem}.pb-0\.5{padding-bottom:.125rem}.pb-0{padding-bottom:0}.pl-12{padding-left:3rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:Consolas,Monaco,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.text-2xl{font-size:1.5rem;line-height:2rem}.font-light{font-weight:300}.font-medium{font-weight:500}.font-extrabold{font-weight:800}.font-bold{font-weight:700}.leading-6{line-height:1.5rem}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.transition{transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.bg-success{background-color:#28a745}html:not(.dark) body{background-color:#f7f9fb}html{height:100%}body{padding-top:20px;padding-bottom:20px;min-height:100vh}#global{margin-top:0!important}#global,#results{max-width:1280px}@media screen and (max-width:1279px){body{padding-top:0;padding-bottom:0}#global{min-height:100vh}}@media (hover:hover) and (pointer:fine){.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:from-green-700:hover{--tw-gradient-from:#15803d;--tw-gradient-to:rgba(21,128,61,0);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:to-green-800:hover{--tw-gradient-to:#166534}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.hover\:text-blue-800:hover{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity))}.hover\:underline:hover{-webkit-text-decoration-line:underline;text-decoration-line:underline}}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity))}.dark .dark\:divide-gray-600>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(75 85 99/var(--tw-divide-opacity))}.dark .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}@media (hover:hover) and (pointer:fine){.dark .dark\:hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:hover\:bg-gray-600:hover{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.dark .dark\:hover\:text-gray-400:hover{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:hover\:text-blue-400:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.dark .dark\:hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}}@media (min-width:640px){.sm\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:text-4xl{font-size:2.25rem;line-height:2.5rem}}@media (min-width:1280px){.xl\:my-5{margin-top:1.25rem;margin-bottom:1.25rem}.xl\:rounded{border-radius:.25rem}.xl\:border{border-width:1px}.xl\:px-24{padding-left:6rem;padding-right:6rem}.xl\:pb-5{padding-bottom:1.25rem}.xl\:text-5xl{font-size:3rem;line-height:1}.xl\:text-3xl{font-size:1.875rem;line-height:2.25rem}.xl\:shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}}#settings{position:fixed;left:10px;bottom:10px}#settings select:focus{box-shadow:none}.endpoint:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.endpoint:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px;border-bottom-width:3px}.status-over-time{overflow:auto}.status-over-time>span:not(:first-child){margin-left:2px}.status{cursor:pointer;transition:all .5s ease-in-out;overflow-x:hidden;color:#fff;width:5%;font-size:75%;font-weight:700;text-align:center}.status:hover{opacity:.7;transition:opacity .1s ease-in-out;color:#000}.status-time-ago{color:#6a737d;opacity:.5;margin-top:5px}.status.status-success:after{content:"✓"}.status.status-failure:after{content:"X"}@media screen and (max-width:600px){.status.status-failure:after,.status.status-success:after{content:" ";white-space:pre}}.endpoint-group{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.endpoint-group h5:hover{color:#1b1e21}.endpoint-group-content>div:first-child{border-top-left-radius:0;border-top-right-radius:0}.endpoint[data-v-2e619502]{border-radius:3px;border-bottom-width:3px}
\ No newline at end of file
diff --git a/web/static/index.html b/web/static/index.html
index be309704..ccd99fa6 100644
--- a/web/static/index.html
+++ b/web/static/index.html
@@ -1 +1 @@
-<!doctype html><html lang="en" class="{{ .Theme }}"><head><meta charset="utf-8"/><script>window.config = {logo: "{{ .UI.Logo }}", header: "{{ .UI.Header }}", link: "{{ .UI.Link }}", buttons: []};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}</script><title>{{ .UI.Title }}</title><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="stylesheet" href="/css/custom.css"/><meta name="description" content="{{ .UI.Description }}"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/><meta name="apple-mobile-web-app-title" content="{{ .UI.Title }}"/><meta name="application-name" content="{{ .UI.Title }}"/><meta name="theme-color" content="#f7f9fb"/><script defer="defer" src="/js/chunk-vendors.js"></script><script defer="defer" src="/js/app.js"></script><link href="/css/app.css" rel="stylesheet"></head><body class="dark:bg-gray-900"><noscript><strong>Enable JavaScript to view this page.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
+<!doctype html><html lang="en" class="{{ .Theme }}"><head><meta charset="utf-8"/><script>window.config = {logo: "{{ .UI.Logo }}", header: "{{ .UI.Header }}", link: "{{ .UI.Link }}", buttons: [], maximumNumberOfResults: "{{ .UI.MaximumNumberOfResults }}"};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}</script><title>{{ .UI.Title }}</title><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="stylesheet" href="/css/custom.css"/><meta name="description" content="{{ .UI.Description }}"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/><meta name="apple-mobile-web-app-title" content="{{ .UI.Title }}"/><meta name="application-name" content="{{ .UI.Title }}"/><meta name="theme-color" content="#f7f9fb"/><script defer="defer" src="/js/chunk-vendors.js"></script><script defer="defer" src="/js/app.js"></script><link href="/css/app.css" rel="stylesheet"></head><body class="dark:bg-gray-900"><noscript><strong>Enable JavaScript to view this page.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
diff --git a/web/static/js/app.js b/web/static/js/app.js
index 29ffcec2..25a4528e 100644
--- a/web/static/js/app.js
+++ b/web/static/js/app.js
@@ -1 +1 @@
-(function(){"use strict";var e={2434:function(e,t,s){s.d(t,{L:function(){return bs}});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"},u={class:"w-1/4 flex justify-end"},h=["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"),D=(0,o.up)("router-view"),I=(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",u,[((0,o.wg)(),(0,o.j4)((0,o.LL)(_.link?"a":"div"),{href:_.link,target:"_blank",class:"flex items-center justify-center",style:{width:"100px","min-height":"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,h)):((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)(D,{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)(I,{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),D={id:"social"},I=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=[I];function C(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",D,A)}var $={name:"Social"},P=s(3744);const E=(0,P.Z)($,[["render",C],["__scopeId","data-v-a4b3d200"]]);var U=E;const L=(0,o._)("div",{class:"tooltip-title"},"Timestamp:",-1),H={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),B={id:"tooltip-conditions"},j=(0,o._)("br",null,null,-1),q={key:1,id:"tooltip-errors-container"},z=(0,o._)("div",{class:"tooltip-title"},"Errors:",-1),Y={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",H,(0,a.zw)(e.prettifyTimestamp(s.result.timestamp)),1),W,(0,o._)("code",M,(0,a.zw)((s.result.duration/1e6).toFixed(0))+"ms",1),s.result.conditionResults&&s.result.conditionResults.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[O,(0,o._)("code",B,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.result.conditionResults,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Uk)((0,a.zw)(t.success?"✓":"X")+" ~ "+(0,a.zw)(t.condition),1),j])))),128))])])):(0,o.kq)("",!0),s.result.errors&&s.result.errors.length?((0,o.wg)(),(0,o.iD)("div",q,[z,(0,o._)("code",Y,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.result.errors,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Uk)(" - "+(0,a.zw)(t),1),N])))),128))])])):(0,o.kq)("",!0)])):(0,o.kq)("",!0)],6)}s(5306);const G={methods:{generatePrettyTimeAgo(e){let t=(new Date).getTime()-new Date(e).getTime();if(t<500)return"now";if(t>2592e5){let e=(t/864e5).toFixed(0);return e+" day"+("1"!==e?"s":"")+" ago"}if(t>36e5){let e=(t/36e5).toFixed(0);return e+" hour"+("1"!==e?"s":"")+" ago"}if(t>6e4){let e=(t/6e4).toFixed(0);return e+" minute"+("1"!==e?"s":"")+" ago"}let s=(t/1e3).toFixed(0);return s+" second"+("1"!==s?"s":"")+" ago"},generatePrettyTimeDifference(e,t){let s=Math.ceil((new Date(e)-new Date(t))/1e3/60);return s+(1===s?" minute":" minutes")},prettifyTimestamp(e){let t=new Date(e),s=t.getFullYear(),n=(t.getMonth()+1<10?"0":"")+(t.getMonth()+1),o=(t.getDate()<10?"0":"")+t.getDate(),a=(t.getHours()<10?"0":"")+t.getHours(),r=(t.getMinutes()<10?"0":"")+t.getMinutes(),i=(t.getSeconds()<10?"0":"")+t.getSeconds();return s+"-"+n+"-"+o+" "+a+":"+r+":"+i}}};var F={name:"Endpoints",props:{event:Event,result:Object},mixins:[G],methods:{htmlEntities(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")},reposition(){if(this.event&&this.event.type)if("mouseenter"===this.event.type){let e=this.event.target.getBoundingClientRect().y+30,t=this.event.target.getBoundingClientRect().x,s=this.$refs.tooltip.getBoundingClientRect();t+window.scrollX+s.width+50>document.body.getBoundingClientRect().width&&(t=this.event.target.getBoundingClientRect().x-s.width+this.event.target.getBoundingClientRect().width,t<0&&(t+=-t)),e+window.scrollY+s.height+50>document.body.getBoundingClientRect().height&&e>=0&&(e=this.event.target.getBoundingClientRect().y-(s.height+10),e<0&&(e=this.event.target.getBoundingClientRect().y+30)),this.top=e,this.left=t}else"mouseleave"===this.event.type&&(this.hidden=!0)}},watch:{event:function(e){e&&e.type&&("mouseenter"===e.type?this.hidden=!1:"mouseleave"===e.type&&(this.hidden=!0))}},updated(){this.reposition()},created(){this.reposition()},data(){return{hidden:!0,top:0,left:0}}};const K=(0,P.Z)(F,[["render",Z]]);var V=K;const J={class:"flex justify-center items-center mx-auto"},X=(0,o._)("img",{class:(0,a.C_)("animate-spin opacity-60 rounded-full"),src:r,alt:"Gatus logo"},null,-1),Q=[X];function ee(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",J,Q)}var te={};const se=(0,P.Z)(te,[["render",ee]]);var ne=se,oe={name:"App",components:{Loading:ne,Social:U,Tooltip:V},methods:{fetchConfig(){fetch(`${bs}/api/v1/config`,{credentials:"include"}).then((e=>{this.retrievedConfig=!0,200===e.status&&e.json().then((e=>{this.config=e}))}))},showTooltip(e,t){this.tooltip={result:e,event:t}}},computed:{logo(){return window.config&&window.config.logo&&"{{ .UI.Logo }}"!==window.config.logo?window.config.logo:""},header(){return window.config&&window.config.header&&"{{ .UI.Header }}"!==window.config.header?window.config.header:"Health Status"},link(){return window.config&&window.config.link&&"{{ .UI.Link }}"!==window.config.link?window.config.link:null},buttons(){return window.config&&window.config.buttons?window.config.buttons:[]}},data(){return{error:"",retrievedConfig:!1,config:{oidc:!1,authenticated:!0},tooltip:{},SERVER_URL:bs}},created(){this.fetchConfig()}};const ae=(0,P.Z)(oe,[["render",_]]);var re=ae,ie=s(2119);function le(e,t,s,a,r,i){const l=(0,o.up)("Loading"),d=(0,o.up)("Endpoints"),g=(0,o.up)("Pagination"),u=(0,o.up)("Settings");return(0,o.wg)(),(0,o.iD)(o.HY,null,[r.retrievedData?(0,o.kq)("",!0):((0,o.wg)(),(0,o.j4)(l,{key:0,class:"h-64 w-64 px-4 my-24"})),(0,o.WI)(e.$slots,"default",{},(()=>[(0,o.wy)((0,o.Wm)(d,{endpointStatuses:r.endpointStatuses,showStatusOnHover:!0,onShowTooltip:i.showTooltip,onToggleShowAverageResponseTime:i.toggleShowAverageResponseTime,showAverageResponseTime:r.showAverageResponseTime},null,8,["endpointStatuses","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"]),[[n.F8,r.retrievedData]]),(0,o.wy)((0,o.Wm)(g,{onPage:i.changePage},null,8,["onPage"]),[[n.F8,r.retrievedData]])])),(0,o.Wm)(u,{onRefreshData:i.fetchData},null,8,["onRefreshData"])],64)}s(3948);const de={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"},ge={class:"text-xs text-gray-600 rounded-xl py-1.5 px-1.5 dark:text-gray-200"},ue=["selected"],he=["selected"],ce=["selected"],pe=["selected"],me=["selected"],ve=["selected"];function fe(e,t,s,n,a,r){const i=(0,o.up)("ArrowPathIcon"),l=(0,o.up)("SunIcon"),d=(0,o.up)("MoonIcon");return(0,o.wg)(),(0,o.iD)("div",de,[(0,o._)("div",ge,[(0,o.Wm)(i,{class:"w-3"})]),(0,o._)("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 pl-1",id:"refresh-rate",ref:"refreshInterval",onChange:t[0]||(t[0]=(...e)=>r.handleChangeRefreshInterval&&r.handleChangeRefreshInterval(...e))},[(0,o._)("option",{value:"10",selected:10===a.refreshInterval},"10s",8,ue),(0,o._)("option",{value:"30",selected:30===a.refreshInterval},"30s",8,he),(0,o._)("option",{value:"60",selected:60===a.refreshInterval},"1m",8,ce),(0,o._)("option",{value:"120",selected:120===a.refreshInterval},"2m",8,pe),(0,o._)("option",{value:"300",selected:300===a.refreshInterval},"5m",8,me),(0,o._)("option",{value:"600",selected:600===a.refreshInterval},"10m",8,ve)],544),(0,o._)("button",{onClick:t[1]||(t[1]=(...e)=>r.toggleDarkMode&&r.toggleDarkMode(...e)),class:"text-xs p-1"},[a.darkMode?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o.Wm)(l,{class:"w-4"})])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[(0,o.Wm)(d,{class:"w-4 text-gray-500"})]))])])}var we=s(6758),xe=s(4913),ye=s(7886);function ke(){var e;const t=null===(e=document.cookie.match(/theme=(dark|light);?/))||void 0===e?void 0:e[1];return"dark"===t||!t&&(window.matchMedia("(prefers-color-scheme: dark)").matches||document.documentElement.classList.contains("dark"))}var Te={name:"Settings",components:{ArrowPathIcon:ye.Z,MoonIcon:we.Z,SunIcon:xe.Z},props:{},methods:{setRefreshInterval(e){localStorage.setItem("gatus:refresh-interval",e);let t=this;this.refreshIntervalHandler=setInterval((function(){t.refreshData()}),1e3*e)},refreshData(){this.$emit("refreshData")},handleChangeRefreshInterval(){this.refreshData(),clearInterval(this.refreshIntervalHandler),this.setRefreshInterval(this.$refs.refreshInterval.value)},toggleDarkMode(){ke()?document.cookie="theme=light; path=/; max-age=31536000; samesite=strict":document.cookie="theme=dark; path=/; max-age=31536000; samesite=strict",this.applyTheme()},applyTheme(){ke()?(this.darkMode=!0,document.documentElement.classList.add("dark")):(this.darkMode=!1,document.documentElement.classList.remove("dark"))}},created(){10!==this.refreshInterval&&30!==this.refreshInterval&&60!==this.refreshInterval&&120!==this.refreshInterval&&300!==this.refreshInterval&&600!==this.refreshInterval&&(this.refreshInterval=300),this.setRefreshInterval(this.refreshInterval),this.applyTheme()},unmounted(){clearInterval(this.refreshIntervalHandler)},data(){return{refreshInterval:localStorage.getItem("gatus:refresh-interval")<10?300:parseInt(localStorage.getItem("gatus:refresh-interval")),refreshIntervalHandler:0,darkMode:ke()}}};const be=(0,P.Z)(Te,[["render",fe]]);var Re=be;const _e={id:"results"};function Se(e,t,s,n,a,r){const i=(0,o.up)("EndpointGroup");return(0,o.wg)(),(0,o.iD)("div",_e,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(a.endpointGroups,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Wm)(i,{endpoints:t.endpoints,name:t.name,onShowTooltip:r.showTooltip,onToggleShowAverageResponseTime:r.toggleShowAverageResponseTime,showAverageResponseTime:s.showAverageResponseTime},null,8,["endpoints","name","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"])])))),128))])}const De={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"},Ie={class:"endpoint-group-arrow mr-2"},Ae={key:0,class:"rounded-xl bg-red-600 text-white px-2 font-bold leading-6 float-right h-6 text-center hover:scale-110 text-sm",title:"Partial Outage"},Ce={key:1,class:"float-right text-green-600 w-7 hover:scale-110",title:"Operational"};function $e(e,t,s,n,r,i){const l=(0,o.up)("CheckCircleIcon"),d=(0,o.up)("Endpoint");return(0,o.wg)(),(0,o.iD)("div",{class:(0,a.C_)(0===s.endpoints.length?"mt-3":"mt-4")},["undefined"!==s.name?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o._)("div",{class:"endpoint-group pt-2 border dark:bg-gray-800 dark:border-gray-500",onClick:t[0]||(t[0]=(...e)=>i.toggleGroup&&i.toggleGroup(...e))},[(0,o._)("h5",De,[(0,o._)("span",Ie,(0,a.zw)(r.collapsed?"▼":"▲"),1),(0,o.Uk)(" "+(0,a.zw)(s.name)+" ",1),r.unhealthyCount?((0,o.wg)(),(0,o.iD)("span",Ae,(0,a.zw)(r.unhealthyCount),1)):((0,o.wg)(),(0,o.iD)("span",Ce,[(0,o.Wm)(l)]))])])])):(0,o.kq)("",!0),r.collapsed?(0,o.kq)("",!0):((0,o.wg)(),(0,o.iD)("div",{key:1,class:(0,a.C_)("undefined"===s.name?"":"endpoint-group-content")},[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.endpoints,((t,n)=>(0,o.WI)(e.$slots,"default",{key:n},(()=>[(0,o.Wm)(d,{data:t,maximumNumberOfResults:20,onShowTooltip:i.showTooltip,onToggleShowAverageResponseTime:i.toggleShowAverageResponseTime,showAverageResponseTime:s.showAverageResponseTime},null,8,["data","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"])])))),128))],2))],2)}const Pe={key:0,class:"endpoint px-3 py-3 border-l border-r border-t rounded-none hover:bg-gray-100 dark:hover:bg-gray-700 dark:border-gray-500"},Ee={class:"flex flex-wrap mb-2"},Ue={class:"w-3/4"},Le={key:0,class:"text-gray-500 font-light"},He={class:"w-1/4 text-right"},We=["title"],Me={class:"status-over-time flex flex-row"},Oe=["onMouseenter"],Be=["onMouseenter"],je={class:"flex flex-wrap status-time-ago"},qe={class:"w-1/2"},ze={class:"w-1/2 text-right"},Ye=(0,o._)("div",{class:"w-1/2"},"   ",-1);function Ne(e,t,s,n,r,i){const l=(0,o.up)("router-link");return s.data?((0,o.wg)(),(0,o.iD)("div",Pe,[(0,o._)("div",Ee,[(0,o._)("div",Ue,[(0,o.Wm)(l,{to:i.generatePath(),class:"font-bold hover:text-blue-800 hover:underline dark:hover:text-blue-400",title:"View detailed endpoint health"},{default:(0,o.w5)((()=>[(0,o.Uk)((0,a.zw)(s.data.name),1)])),_:1},8,["to"]),s.data.results&&s.data.results.length&&s.data.results[s.data.results.length-1].hostname?((0,o.wg)(),(0,o.iD)("span",Le," | "+(0,a.zw)(s.data.results[s.data.results.length-1].hostname),1)):(0,o.kq)("",!0)]),(0,o._)("div",He,[s.data.results&&s.data.results.length?((0,o.wg)(),(0,o.iD)("span",{key:0,class:"font-light overflow-x-hidden cursor-pointer select-none hover:text-gray-500",onClick:t[0]||(t[0]=(...e)=>i.toggleShowAverageResponseTime&&i.toggleShowAverageResponseTime(...e)),title:s.showAverageResponseTime?"Average response time":"Minimum and maximum response time"},[s.showAverageResponseTime?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o.Uk)(" ~"+(0,a.zw)(r.averageResponseTime)+"ms ",1)])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[(0,o.Uk)((0,a.zw)(r.minResponseTime===r.maxResponseTime?r.minResponseTime:r.minResponseTime+"-"+r.maxResponseTime)+"ms ",1)]))],8,We)):(0,o.kq)("",!0)])]),(0,o._)("div",null,[(0,o._)("div",Me,[s.data.results&&s.data.results.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[s.data.results.length<s.maximumNumberOfResults?(0,o.WI)(e.$slots,"default",{key:0},(()=>[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.maximumNumberOfResults-s.data.results.length,(e=>((0,o.wg)(),(0,o.iD)("span",{key:e,class:"status rounded border border-dashed border-gray-400"}," ")))),128))])):(0,o.kq)("",!0),((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.data.results,(s=>(0,o.WI)(e.$slots,"default",{key:s},(()=>[s.success?((0,o.wg)(),(0,o.iD)("span",{key:0,class:"status status-success rounded bg-success",onMouseenter:e=>i.showTooltip(s,e),onMouseleave:t[1]||(t[1]=e=>i.showTooltip(null,e))},null,40,Oe)):((0,o.wg)(),(0,o.iD)("span",{key:1,class:"status status-failure rounded bg-red-600",onMouseenter:e=>i.showTooltip(s,e),onMouseleave:t[2]||(t[2]=e=>i.showTooltip(null,e))},null,40,Be))])))),128))])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.maximumNumberOfResults,(e=>((0,o.wg)(),(0,o.iD)("span",{key:e,class:"status rounded border border-dashed border-gray-400"}," ")))),128))]))])]),(0,o._)("div",je,[s.data.results&&s.data.results.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o._)("div",qe,(0,a.zw)(e.generatePrettyTimeAgo(s.data.results[0].timestamp)),1),(0,o._)("div",ze,(0,a.zw)(e.generatePrettyTimeAgo(s.data.results[s.data.results.length-1].timestamp)),1)])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[Ye]))])])):(0,o.kq)("",!0)}var Ze={name:"Endpoint",props:{maximumNumberOfResults:Number,data:Object,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],mixins:[G],methods:{updateMinAndMaxResponseTimes(){let e=null,t=null,s=0;for(let n in this.data.results){const o=parseInt((this.data.results[n].duration/1e6).toFixed(0));s+=o,(null==e||e>o)&&(e=o),(null==t||t<o)&&(t=o)}this.minResponseTime!==e&&(this.minResponseTime=e),this.maxResponseTime!==t&&(this.maxResponseTime=t),this.data.results&&this.data.results.length&&(this.averageResponseTime=(s/this.data.results.length).toFixed(0))},generatePath(){return this.data?`/endpoints/${this.data.key}`:"/"},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{data:function(){this.updateMinAndMaxResponseTimes()}},created(){this.updateMinAndMaxResponseTimes()},data(){return{minResponseTime:0,maxResponseTime:0,averageResponseTime:0}}};const Ge=(0,P.Z)(Ze,[["render",Ne]]);var Fe=Ge,Ke=s(1818),Ve={name:"EndpointGroup",components:{Endpoint:Fe,CheckCircleIcon:Ke.Z},props:{name:String,endpoints:Array,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{healthCheck(){let e=0;if(this.endpoints)for(let t in this.endpoints)this.endpoints[t].results&&this.endpoints[t].results.length>0&&(this.endpoints[t].results[this.endpoints[t].results.length-1].success||e++);this.unhealthyCount=e},toggleGroup(){this.collapsed=!this.collapsed,localStorage.setItem(`gatus:endpoint-group:${this.name}:collapsed`,this.collapsed)},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{endpoints:function(){this.healthCheck()}},created(){this.healthCheck()},data(){return{unhealthyCount:0,collapsed:"true"===localStorage.getItem(`gatus:endpoint-group:${this.name}:collapsed`)}}};const Je=(0,P.Z)(Ve,[["render",$e]]);var Xe=Je,Qe={name:"Endpoints",components:{EndpointGroup:Xe},props:{showStatusOnHover:Boolean,endpointStatuses:Object,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{process(){let e={};for(let s in this.endpointStatuses){let t=this.endpointStatuses[s];e[t.group]&&0!==e[t.group].length||(e[t.group]=[]),e[t.group].push(t)}let t=[];for(let s in e)"undefined"!==s&&t.push({name:s,endpoints:e[s]});e["undefined"]&&t.push({name:"undefined",endpoints:e["undefined"]}),this.endpointGroups=t},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{endpointStatuses:function(){this.process()}},data(){return{userClickedStatus:!1,endpointGroups:[]}}};const et=(0,P.Z)(Qe,[["render",Se]]);var tt=et;const st={class:"mt-3 flex"},nt={class:"flex-1"},ot={class:"flex-1 text-right"};function at(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",st,[(0,o._)("div",nt,[a.currentPage<5?((0,o.wg)(),(0,o.iD)("button",{key:0,onClick:t[0]||(t[0]=(...e)=>r.nextPage&&r.nextPage(...e)),class:"bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},"<")):(0,o.kq)("",!0)]),(0,o._)("div",ot,[a.currentPage>1?((0,o.wg)(),(0,o.iD)("button",{key:0,onClick:t[1]||(t[1]=(...e)=>r.previousPage&&r.previousPage(...e)),class:"bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},">")):(0,o.kq)("",!0)])])}var rt={name:"Pagination",components:{},emits:["page"],methods:{nextPage(){this.currentPage++,this.$emit("page",this.currentPage)},previousPage(){this.currentPage--,this.$emit("page",this.currentPage)}},data(){return{currentPage:1}}};const it=(0,P.Z)(rt,[["render",at]]);var lt=it,dt={name:"Home",components:{Loading:ne,Pagination:lt,Endpoints:tt,Settings:Re},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{fetchData(){fetch(`${bs}/api/v1/endpoints/statuses?page=${this.currentPage}`,{credentials:"include"}).then((e=>{this.retrievedData=!0,200===e.status?e.json().then((e=>{JSON.stringify(this.endpointStatuses)!==JSON.stringify(e)&&(this.endpointStatuses=e)})):e.text().then((e=>{console.log(`[Home][fetchData] Error: ${e}`)}))}))},changePage(e){this.retrievedData=!1,this.currentPage=e,this.fetchData()},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.showAverageResponseTime=!this.showAverageResponseTime}},data(){return{endpointStatuses:[],currentPage:1,showAverageResponseTime:!0,retrievedData:!1}},created(){this.retrievedData=!1,this.fetchData()}};const gt=(0,P.Z)(dt,[["render",le]]);var ut=gt;const ht=e=>((0,o.dD)("data-v-62613fe4"),e=e(),(0,o.Cn)(),e),ct=(0,o.Uk)(" ← "),pt=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"RECENT CHECKS",-1))),mt=ht((()=>(0,o._)("hr",{class:"mb-4"},null,-1))),vt={key:1,class:"mt-12"},ft=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"UPTIME",-1))),wt=ht((()=>(0,o._)("hr",null,null,-1))),xt={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},yt={class:"flex-1"},kt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 30 days",-1))),Tt=["src"],bt={class:"flex-1"},Rt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 7 days",-1))),_t=["src"],St={class:"flex-1"},Dt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 24 hours",-1))),It=["src"],At={class:"flex-1"},Ct=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last hour",-1))),$t=["src"],Pt={key:2,class:"mt-12"},Et={class:"flex items-center justify-between"},Ut=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"RESPONSE TIME",-1))),Lt=ht((()=>(0,o._)("option",{value:"24h"},"24 hours",-1))),Ht=ht((()=>(0,o._)("option",{value:"7d"},"7 days",-1))),Wt=ht((()=>(0,o._)("option",{value:"30d"},"30 days",-1))),Mt=[Lt,Ht,Wt],Ot=["src"],Bt={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},jt={class:"flex-1"},qt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 30 days",-1))),zt=["src"],Yt={class:"flex-1"},Nt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 7 days",-1))),Zt=["src"],Gt={class:"flex-1"},Ft=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 24 hours",-1))),Kt=["src"],Vt={class:"flex-1"},Jt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last hour",-1))),Xt=["src"],Qt={key:3},es=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400 mt-4"},"CURRENT HEALTH",-1))),ts=ht((()=>(0,o._)("hr",null,null,-1))),ss={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},ns={class:"flex-1"},os=["src"],as={key:4},rs=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400 mt-4"},"EVENTS",-1))),is=ht((()=>(0,o._)("hr",null,null,-1))),ls={role:"list",class:"px-0 xl:px-24 divide-y divide-gray-200 dark:divide-gray-600"},ds={class:"text-sm sm:text-lg"},gs={class:"flex mt-1 text-xs sm:text-sm text-gray-400"},us={class:"flex-2 text-left pl-12"},hs={class:"flex-1 text-right"};function cs(e,t,s,r,i,l){const d=(0,o.up)("router-link"),g=(0,o.up)("Endpoint"),u=(0,o.up)("Pagination"),h=(0,o.up)("ArrowUpCircleIcon"),c=(0,o.up)("ArrowDownCircleIcon"),p=(0,o.up)("PlayCircleIcon"),m=(0,o.up)("Settings");return(0,o.wg)(),(0,o.iD)(o.HY,null,[(0,o.Wm)(d,{to:"../",class:"absolute top-2 left-5 inline-block px-2 pb-0.5 text-sm text-black bg-gray-100 rounded hover:bg-gray-200 focus:outline-none border border-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},{default:(0,o.w5)((()=>[ct])),_:1}),(0,o._)("div",null,[i.endpointStatus?(0,o.WI)(e.$slots,"default",{key:0},(()=>[pt,mt,(0,o.Wm)(g,{data:i.endpointStatus,maximumNumberOfResults:20,onShowTooltip:l.showTooltip,onToggleShowAverageResponseTime:l.toggleShowAverageResponseTime,showAverageResponseTime:i.showAverageResponseTime},null,8,["data","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"]),(0,o.Wm)(u,{onPage:l.changePage},null,8,["onPage"])]),!0):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",vt,[ft,wt,(0,o._)("div",xt,[(0,o._)("div",yt,[kt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("30d"),alt:"30d uptime badge",class:"mx-auto"},null,8,Tt)]),(0,o._)("div",bt,[Rt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("7d"),alt:"7d uptime badge",class:"mx-auto"},null,8,_t)]),(0,o._)("div",St,[Dt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("24h"),alt:"24h uptime badge",class:"mx-auto"},null,8,It)]),(0,o._)("div",At,[Ct,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("1h"),alt:"1h uptime badge",class:"mx-auto"},null,8,$t)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key&&i.showResponseTimeChartAndBadges?((0,o.wg)(),(0,o.iD)("div",Pt,[(0,o._)("div",Et,[Ut,(0,o.wy)((0,o._)("select",{"onUpdate:modelValue":t[0]||(t[0]=e=>i.selectedChartDuration=e),class:"text-sm bg-gray-400 text-white border border-gray-600 rounded-md px-3 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500"},Mt,512),[[n.bM,i.selectedChartDuration]])]),(0,o._)("img",{src:l.generateResponseTimeChartImageURL(i.selectedChartDuration),alt:"response time chart",class:"mt-6"},null,8,Ot),(0,o._)("div",Bt,[(0,o._)("div",jt,[qt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("30d"),alt:"7d response time badge",class:"mx-auto mt-2"},null,8,zt)]),(0,o._)("div",Yt,[Nt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("7d"),alt:"7d response time badge",class:"mx-auto mt-2"},null,8,Zt)]),(0,o._)("div",Gt,[Ft,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("24h"),alt:"24h response time badge",class:"mx-auto mt-2"},null,8,Kt)]),(0,o._)("div",Vt,[Jt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("1h"),alt:"1h response time badge",class:"mx-auto mt-2"},null,8,Xt)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",Qt,[es,ts,(0,o._)("div",ss,[(0,o._)("div",ns,[(0,o._)("img",{src:l.generateHealthBadgeImageURL(),alt:"health badge",class:"mx-auto"},null,8,os)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",as,[rs,is,(0,o._)("ul",ls,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(i.events,(t=>((0,o.wg)(),(0,o.iD)("li",{key:t,class:"p-3 my-4"},[(0,o._)("h2",ds,["HEALTHY"===t.type?((0,o.wg)(),(0,o.j4)(h,{key:0,class:"w-8 inline mr-2 text-green-600"})):"UNHEALTHY"===t.type?((0,o.wg)(),(0,o.j4)(c,{key:1,class:"w-8 inline mr-2 text-red-500"})):"START"===t.type?((0,o.wg)(),(0,o.j4)(p,{key:2,class:"w-8 inline mr-2 text-gray-400 dark:text-gray-100"})):(0,o.kq)("",!0),(0,o.Uk)(" "+(0,a.zw)(t.fancyText),1)]),(0,o._)("div",gs,[(0,o._)("div",us,(0,a.zw)(e.prettifyTimestamp(t.timestamp)),1),(0,o._)("div",hs,(0,a.zw)(t.fancyTimeAgo),1)])])))),128))])])):(0,o.kq)("",!0)]),(0,o.Wm)(m,{onRefreshData:l.fetchData},null,8,["onRefreshData"])],64)}var ps=s(9505),ms=s(7163),vs=s(8585),fs={name:"Details",components:{Pagination:lt,Endpoint:Fe,Settings:Re,ArrowDownCircleIcon:ps.Z,ArrowUpCircleIcon:ms.Z,PlayCircleIcon:vs.Z},emits:["showTooltip"],mixins:[G],methods:{fetchData(){fetch(`${this.serverUrl}/api/v1/endpoints/${this.$route.params.key}/statuses?page=${this.currentPage}`,{credentials:"include"}).then((e=>{200===e.status?e.json().then((e=>{if(JSON.stringify(this.endpointStatus)!==JSON.stringify(e)){this.endpointStatus=e;let t=[];for(let s=e.events.length-1;s>=0;s--){let n=e.events[s];if(s===e.events.length-1)"UNHEALTHY"===n.type?n.fancyText="Endpoint is unhealthy":"HEALTHY"===n.type?n.fancyText="Endpoint is healthy":"START"===n.type&&(n.fancyText="Monitoring started");else{let t=e.events[s+1];"HEALTHY"===n.type?n.fancyText="Endpoint became healthy":"UNHEALTHY"===n.type?n.fancyText=t?"Endpoint was unhealthy for "+this.generatePrettyTimeDifference(t.timestamp,n.timestamp):"Endpoint became unhealthy":"START"===n.type&&(n.fancyText="Monitoring started")}n.fancyTimeAgo=this.generatePrettyTimeAgo(n.timestamp),t.push(n)}this.events=t;for(let s=0;s<e.results.length;s++)if(e.results[s].duration>0){this.showResponseTimeChartAndBadges=!0;break}}})):e.text().then((e=>{console.log(`[Details][fetchData] Error: ${e}`)}))}))},generateHealthBadgeImageURL(){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/health/badge.svg`},generateUptimeBadgeImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/uptimes/${e}/badge.svg`},generateResponseTimeBadgeImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/response-times/${e}/badge.svg`},generateResponseTimeChartImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/response-times/${e}/chart.svg`},changePage(e){this.currentPage=e,this.fetchData()},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.showAverageResponseTime=!this.showAverageResponseTime}},data(){return{endpointStatus:{},events:[],hourlyAverageResponseTime:{},selectedChartDuration:"24h",serverUrl:"."===bs?"..":bs,currentPage:1,showAverageResponseTime:!0,showResponseTimeChartAndBadges:!1,chartLabels:[],chartValues:[]}},created(){this.fetchData()}};const ws=(0,P.Z)(fs,[["render",cs],["__scopeId","data-v-62613fe4"]]);var xs=ws;const ys=[{path:"/",name:"Home",component:ut},{path:"/endpoints/:key",name:"Details",component:xs}],ks=(0,ie.p7)({history:(0,ie.PO)("/"),routes:ys});var Ts=ks;const bs="";(0,n.ri)(re).use(Ts).mount("#app")}},t={};function s(n){var o=t[n];if(void 0!==o)return o.exports;var a=t[n]={exports:{}};return e[n](a,a.exports,s),a.exports}s.m=e,function(){var e=[];s.O=function(t,n,o,a){if(!n){var r=1/0;for(g=0;g<e.length;g++){n=e[g][0],o=e[g][1],a=e[g][2];for(var i=!0,l=0;l<n.length;l++)(!1&a||r>=a)&&Object.keys(s.O).every((function(e){return s.O[e](n[l])}))?n.splice(l--,1):(i=!1,a<r&&(r=a));if(i){e.splice(g--,1);var d=o();void 0!==d&&(t=d)}}return t}a=a||0;for(var g=e.length;g>0&&e[g-1][2]>a;g--)e[g]=e[g-1];e[g]=[n,o,a]}}(),function(){s.d=function(e,t){for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}}(),function(){s.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}()}(),function(){s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}}(),function(){s.p="/"}(),function(){var e={143:0};s.O.j=function(t){return 0===e[t]};var t=function(t,n){var o,a,r=n[0],i=n[1],l=n[2],d=0;if(r.some((function(t){return 0!==e[t]}))){for(o in i)s.o(i,o)&&(s.m[o]=i[o]);if(l)var g=l(s)}for(t&&t(n);d<r.length;d++)a=r[d],s.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return s.O(g)},n=self["webpackChunkgatus"]=self["webpackChunkgatus"]||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))}();var n=s.O(void 0,[998],(function(){return s(2434)}));n=s.O(n)})();
\ No newline at end of file
+(function(){"use strict";var e={9351:function(e,t,s){s.d(t,{L:function(){return bs}});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"},u={class:"w-1/4 flex justify-end"},h=["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"],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:"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"),D=(0,o.up)("router-view"),I=(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",u,[((0,o.wg)(),(0,o.j4)((0,o.LL)(_.link?"a":"div"),{href:_.link,target:"_blank",class:"flex items-center justify-center",style:{width:"100px","min-height":"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,h)):((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)(D,{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",f,[v,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)(I,{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),D={id:"social"},I=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=[I];function C(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",D,A)}var P={name:"Social"},$=s(3744);const E=(0,$.Z)(P,[["render",C],["__scopeId","data-v-a4b3d200"]]);var U=E;const L=(0,o._)("div",{class:"tooltip-title"},"Timestamp:",-1),H={id:"tooltip-timestamp"},O=(0,o._)("div",{class:"tooltip-title"},"Response time:",-1),W={id:"tooltip-response-time"},M=(0,o._)("div",{class:"tooltip-title"},"Conditions:",-1),B={id:"tooltip-conditions"},j=(0,o._)("br",null,null,-1),q={key:1,id:"tooltip-errors-container"},z=(0,o._)("div",{class:"tooltip-title"},"Errors:",-1),N={id:"tooltip-errors"},Y=(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",H,(0,a.zw)(e.prettifyTimestamp(s.result.timestamp)),1),O,(0,o._)("code",W,(0,a.zw)((s.result.duration/1e6).toFixed(0))+"ms",1),s.result.conditionResults&&s.result.conditionResults.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[M,(0,o._)("code",B,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.result.conditionResults,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Uk)((0,a.zw)(t.success?"✓":"X")+" ~ "+(0,a.zw)(t.condition),1),j])))),128))])])):(0,o.kq)("",!0),s.result.errors&&s.result.errors.length?((0,o.wg)(),(0,o.iD)("div",q,[z,(0,o._)("code",N,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.result.errors,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Uk)(" - "+(0,a.zw)(t),1),Y])))),128))])])):(0,o.kq)("",!0)])):(0,o.kq)("",!0)],6)}s(5306);const G={methods:{generatePrettyTimeAgo(e){let t=(new Date).getTime()-new Date(e).getTime();if(t<500)return"now";if(t>2592e5){let e=(t/864e5).toFixed(0);return e+" day"+("1"!==e?"s":"")+" ago"}if(t>36e5){let e=(t/36e5).toFixed(0);return e+" hour"+("1"!==e?"s":"")+" ago"}if(t>6e4){let e=(t/6e4).toFixed(0);return e+" minute"+("1"!==e?"s":"")+" ago"}let s=(t/1e3).toFixed(0);return s+" second"+("1"!==s?"s":"")+" ago"},generatePrettyTimeDifference(e,t){let s=Math.ceil((new Date(e)-new Date(t))/1e3/60);return s+(1===s?" minute":" minutes")},prettifyTimestamp(e){let t=new Date(e),s=t.getFullYear(),n=(t.getMonth()+1<10?"0":"")+(t.getMonth()+1),o=(t.getDate()<10?"0":"")+t.getDate(),a=(t.getHours()<10?"0":"")+t.getHours(),r=(t.getMinutes()<10?"0":"")+t.getMinutes(),i=(t.getSeconds()<10?"0":"")+t.getSeconds();return s+"-"+n+"-"+o+" "+a+":"+r+":"+i}}};var F={name:"Endpoints",props:{event:Event,result:Object},mixins:[G],methods:{htmlEntities(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")},reposition(){if(this.event&&this.event.type)if("mouseenter"===this.event.type){let e=this.event.target.getBoundingClientRect().y+30,t=this.event.target.getBoundingClientRect().x,s=this.$refs.tooltip.getBoundingClientRect();t+window.scrollX+s.width+50>document.body.getBoundingClientRect().width&&(t=this.event.target.getBoundingClientRect().x-s.width+this.event.target.getBoundingClientRect().width,t<0&&(t+=-t)),e+window.scrollY+s.height+50>document.body.getBoundingClientRect().height&&e>=0&&(e=this.event.target.getBoundingClientRect().y-(s.height+10),e<0&&(e=this.event.target.getBoundingClientRect().y+30)),this.top=e,this.left=t}else"mouseleave"===this.event.type&&(this.hidden=!0)}},watch:{event:function(e){e&&e.type&&("mouseenter"===e.type?this.hidden=!1:"mouseleave"===e.type&&(this.hidden=!0))}},updated(){this.reposition()},created(){this.reposition()},data(){return{hidden:!0,top:0,left:0}}};const K=(0,$.Z)(F,[["render",Z]]);var V=K;const J={class:"flex justify-center items-center mx-auto"},X=(0,o._)("img",{class:(0,a.C_)("animate-spin opacity-60 rounded-full"),src:r,alt:"Gatus logo"},null,-1),Q=[X];function ee(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",J,Q)}var te={};const se=(0,$.Z)(te,[["render",ee]]);var ne=se,oe={name:"App",components:{Loading:ne,Social:U,Tooltip:V},methods:{fetchConfig(){fetch(`${bs}/api/v1/config`,{credentials:"include"}).then((e=>{this.retrievedConfig=!0,200===e.status&&e.json().then((e=>{this.config=e}))}))},showTooltip(e,t){this.tooltip={result:e,event:t}}},computed:{logo(){return window.config&&window.config.logo&&"{{ .UI.Logo }}"!==window.config.logo?window.config.logo:""},header(){return window.config&&window.config.header&&"{{ .UI.Header }}"!==window.config.header?window.config.header:"Health Status"},link(){return window.config&&window.config.link&&"{{ .UI.Link }}"!==window.config.link?window.config.link:null},buttons(){return window.config&&window.config.buttons?window.config.buttons:[]}},data(){return{error:"",retrievedConfig:!1,config:{oidc:!1,authenticated:!0},tooltip:{},SERVER_URL:bs}},created(){this.fetchConfig()}};const ae=(0,$.Z)(oe,[["render",_]]);var re=ae,ie=s(2119);function le(e,t,s,a,r,i){const l=(0,o.up)("Loading"),d=(0,o.up)("Endpoints"),g=(0,o.up)("Pagination"),u=(0,o.up)("Settings");return(0,o.wg)(),(0,o.iD)(o.HY,null,[r.retrievedData?(0,o.kq)("",!0):((0,o.wg)(),(0,o.j4)(l,{key:0,class:"h-64 w-64 px-4 my-24"})),(0,o.WI)(e.$slots,"default",{},(()=>[(0,o.wy)((0,o.Wm)(d,{endpointStatuses:r.endpointStatuses,showStatusOnHover:!0,onShowTooltip:i.showTooltip,onToggleShowAverageResponseTime:i.toggleShowAverageResponseTime,showAverageResponseTime:r.showAverageResponseTime},null,8,["endpointStatuses","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"]),[[n.F8,r.retrievedData]]),(0,o.wy)((0,o.Wm)(g,{onPage:i.changePage,numberOfResultsPerPage:20},null,8,["onPage"]),[[n.F8,r.retrievedData]])])),(0,o.Wm)(u,{onRefreshData:i.fetchData},null,8,["onRefreshData"])],64)}s(3948);const de={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"},ge={class:"text-xs text-gray-600 rounded-xl py-1.5 px-1.5 dark:text-gray-200"},ue=["selected"],he=["selected"],ce=["selected"],pe=["selected"],me=["selected"],fe=["selected"];function ve(e,t,s,n,a,r){const i=(0,o.up)("ArrowPathIcon"),l=(0,o.up)("SunIcon"),d=(0,o.up)("MoonIcon");return(0,o.wg)(),(0,o.iD)("div",de,[(0,o._)("div",ge,[(0,o.Wm)(i,{class:"w-3"})]),(0,o._)("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 pl-1",id:"refresh-rate",ref:"refreshInterval",onChange:t[0]||(t[0]=(...e)=>r.handleChangeRefreshInterval&&r.handleChangeRefreshInterval(...e))},[(0,o._)("option",{value:"10",selected:10===a.refreshInterval},"10s",8,ue),(0,o._)("option",{value:"30",selected:30===a.refreshInterval},"30s",8,he),(0,o._)("option",{value:"60",selected:60===a.refreshInterval},"1m",8,ce),(0,o._)("option",{value:"120",selected:120===a.refreshInterval},"2m",8,pe),(0,o._)("option",{value:"300",selected:300===a.refreshInterval},"5m",8,me),(0,o._)("option",{value:"600",selected:600===a.refreshInterval},"10m",8,fe)],544),(0,o._)("button",{onClick:t[1]||(t[1]=(...e)=>r.toggleDarkMode&&r.toggleDarkMode(...e)),class:"text-xs p-1"},[a.darkMode?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o.Wm)(l,{class:"w-4"})])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[(0,o.Wm)(d,{class:"w-4 text-gray-500"})]))])])}var we=s(6758),xe=s(4913),ye=s(7886);function ke(){var e;const t=null===(e=document.cookie.match(/theme=(dark|light);?/))||void 0===e?void 0:e[1];return"dark"===t||!t&&(window.matchMedia("(prefers-color-scheme: dark)").matches||document.documentElement.classList.contains("dark"))}var Te={name:"Settings",components:{ArrowPathIcon:ye.Z,MoonIcon:we.Z,SunIcon:xe.Z},props:{},methods:{setRefreshInterval(e){localStorage.setItem("gatus:refresh-interval",e);let t=this;this.refreshIntervalHandler=setInterval((function(){t.refreshData()}),1e3*e)},refreshData(){this.$emit("refreshData")},handleChangeRefreshInterval(){this.refreshData(),clearInterval(this.refreshIntervalHandler),this.setRefreshInterval(this.$refs.refreshInterval.value)},toggleDarkMode(){ke()?document.cookie="theme=light; path=/; max-age=31536000; samesite=strict":document.cookie="theme=dark; path=/; max-age=31536000; samesite=strict",this.applyTheme()},applyTheme(){ke()?(this.darkMode=!0,document.documentElement.classList.add("dark")):(this.darkMode=!1,document.documentElement.classList.remove("dark"))}},created(){10!==this.refreshInterval&&30!==this.refreshInterval&&60!==this.refreshInterval&&120!==this.refreshInterval&&300!==this.refreshInterval&&600!==this.refreshInterval&&(this.refreshInterval=300),this.setRefreshInterval(this.refreshInterval),this.applyTheme()},unmounted(){clearInterval(this.refreshIntervalHandler)},data(){return{refreshInterval:localStorage.getItem("gatus:refresh-interval")<10?300:parseInt(localStorage.getItem("gatus:refresh-interval")),refreshIntervalHandler:0,darkMode:ke()}}};const be=(0,$.Z)(Te,[["render",ve]]);var Re=be;const _e={id:"results"};function Se(e,t,s,n,a,r){const i=(0,o.up)("EndpointGroup");return(0,o.wg)(),(0,o.iD)("div",_e,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(a.endpointGroups,(t=>(0,o.WI)(e.$slots,"default",{key:t},(()=>[(0,o.Wm)(i,{endpoints:t.endpoints,name:t.name,onShowTooltip:r.showTooltip,onToggleShowAverageResponseTime:r.toggleShowAverageResponseTime,showAverageResponseTime:s.showAverageResponseTime},null,8,["endpoints","name","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"])])))),128))])}const De={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"},Ie={class:"endpoint-group-arrow mr-2"},Ae={key:0,class:"rounded-xl bg-red-600 text-white px-2 font-bold leading-6 float-right h-6 text-center hover:scale-110 text-sm",title:"Partial Outage"},Ce={key:1,class:"float-right text-green-600 w-7 hover:scale-110",title:"Operational"};function Pe(e,t,s,n,r,i){const l=(0,o.up)("CheckCircleIcon"),d=(0,o.up)("Endpoint");return(0,o.wg)(),(0,o.iD)("div",{class:(0,a.C_)(0===s.endpoints.length?"mt-3":"mt-4")},["undefined"!==s.name?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o._)("div",{class:"endpoint-group pt-2 border dark:bg-gray-800 dark:border-gray-500",onClick:t[0]||(t[0]=(...e)=>i.toggleGroup&&i.toggleGroup(...e))},[(0,o._)("h5",De,[(0,o._)("span",Ie,(0,a.zw)(r.collapsed?"▼":"▲"),1),(0,o.Uk)(" "+(0,a.zw)(s.name)+" ",1),r.unhealthyCount?((0,o.wg)(),(0,o.iD)("span",Ae,(0,a.zw)(r.unhealthyCount),1)):((0,o.wg)(),(0,o.iD)("span",Ce,[(0,o.Wm)(l)]))])])])):(0,o.kq)("",!0),r.collapsed?(0,o.kq)("",!0):((0,o.wg)(),(0,o.iD)("div",{key:1,class:(0,a.C_)("undefined"===s.name?"":"endpoint-group-content")},[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.endpoints,((t,n)=>(0,o.WI)(e.$slots,"default",{key:n},(()=>[(0,o.Wm)(d,{data:t,maximumNumberOfResults:20,onShowTooltip:i.showTooltip,onToggleShowAverageResponseTime:i.toggleShowAverageResponseTime,showAverageResponseTime:s.showAverageResponseTime},null,8,["data","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"])])))),128))],2))],2)}const $e={key:0,class:"endpoint px-3 py-3 border-l border-r border-t rounded-none hover:bg-gray-100 dark:hover:bg-gray-700 dark:border-gray-500"},Ee={class:"flex flex-wrap mb-2"},Ue={class:"w-3/4"},Le={key:0,class:"text-gray-500 font-light"},He={class:"w-1/4 text-right"},Oe=["title"],We={class:"status-over-time flex flex-row"},Me=["onMouseenter"],Be=["onMouseenter"],je={class:"flex flex-wrap status-time-ago"},qe={class:"w-1/2"},ze={class:"w-1/2 text-right"},Ne=(0,o._)("div",{class:"w-1/2"},"   ",-1);function Ye(e,t,s,n,r,i){const l=(0,o.up)("router-link");return s.data?((0,o.wg)(),(0,o.iD)("div",$e,[(0,o._)("div",Ee,[(0,o._)("div",Ue,[(0,o.Wm)(l,{to:i.generatePath(),class:"font-bold hover:text-blue-800 hover:underline dark:hover:text-blue-400",title:"View detailed endpoint health"},{default:(0,o.w5)((()=>[(0,o.Uk)((0,a.zw)(s.data.name),1)])),_:1},8,["to"]),s.data.results&&s.data.results.length&&s.data.results[s.data.results.length-1].hostname?((0,o.wg)(),(0,o.iD)("span",Le," | "+(0,a.zw)(s.data.results[s.data.results.length-1].hostname),1)):(0,o.kq)("",!0)]),(0,o._)("div",He,[s.data.results&&s.data.results.length?((0,o.wg)(),(0,o.iD)("span",{key:0,class:"font-light overflow-x-hidden cursor-pointer select-none hover:text-gray-500",onClick:t[0]||(t[0]=(...e)=>i.toggleShowAverageResponseTime&&i.toggleShowAverageResponseTime(...e)),title:s.showAverageResponseTime?"Average response time":"Minimum and maximum response time"},[s.showAverageResponseTime?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o.Uk)(" ~"+(0,a.zw)(r.averageResponseTime)+"ms ",1)])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[(0,o.Uk)((0,a.zw)(r.minResponseTime===r.maxResponseTime?r.minResponseTime:r.minResponseTime+"-"+r.maxResponseTime)+"ms ",1)]))],8,Oe)):(0,o.kq)("",!0)])]),(0,o._)("div",null,[(0,o._)("div",We,[s.data.results&&s.data.results.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[s.data.results.length<s.maximumNumberOfResults?(0,o.WI)(e.$slots,"default",{key:0},(()=>[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.maximumNumberOfResults-s.data.results.length,(e=>((0,o.wg)(),(0,o.iD)("span",{key:e,class:"status rounded border border-dashed border-gray-400"}," ")))),128))])):(0,o.kq)("",!0),((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.data.results,(s=>(0,o.WI)(e.$slots,"default",{key:s},(()=>[s.success?((0,o.wg)(),(0,o.iD)("span",{key:0,class:"status status-success rounded bg-success",onMouseenter:e=>i.showTooltip(s,e),onMouseleave:t[1]||(t[1]=e=>i.showTooltip(null,e))},null,40,Me)):((0,o.wg)(),(0,o.iD)("span",{key:1,class:"status status-failure rounded bg-red-600",onMouseenter:e=>i.showTooltip(s,e),onMouseleave:t[2]||(t[2]=e=>i.showTooltip(null,e))},null,40,Be))])))),128))])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(s.maximumNumberOfResults,(e=>((0,o.wg)(),(0,o.iD)("span",{key:e,class:"status rounded border border-dashed border-gray-400"}," ")))),128))]))])]),(0,o._)("div",je,[s.data.results&&s.data.results.length?(0,o.WI)(e.$slots,"default",{key:0},(()=>[(0,o._)("div",qe,(0,a.zw)(e.generatePrettyTimeAgo(s.data.results[0].timestamp)),1),(0,o._)("div",ze,(0,a.zw)(e.generatePrettyTimeAgo(s.data.results[s.data.results.length-1].timestamp)),1)])):(0,o.WI)(e.$slots,"default",{key:1},(()=>[Ne]))])])):(0,o.kq)("",!0)}var Ze={name:"Endpoint",props:{maximumNumberOfResults:Number,data:Object,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],mixins:[G],methods:{updateMinAndMaxResponseTimes(){let e=null,t=null,s=0;for(let n in this.data.results){const o=parseInt((this.data.results[n].duration/1e6).toFixed(0));s+=o,(null==e||e>o)&&(e=o),(null==t||t<o)&&(t=o)}this.minResponseTime!==e&&(this.minResponseTime=e),this.maxResponseTime!==t&&(this.maxResponseTime=t),this.data.results&&this.data.results.length&&(this.averageResponseTime=(s/this.data.results.length).toFixed(0))},generatePath(){return this.data?`/endpoints/${this.data.key}`:"/"},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{data:function(){this.updateMinAndMaxResponseTimes()}},created(){this.updateMinAndMaxResponseTimes()},data(){return{minResponseTime:0,maxResponseTime:0,averageResponseTime:0}}};const Ge=(0,$.Z)(Ze,[["render",Ye]]);var Fe=Ge,Ke=s(1818),Ve={name:"EndpointGroup",components:{Endpoint:Fe,CheckCircleIcon:Ke.Z},props:{name:String,endpoints:Array,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{healthCheck(){let e=0;if(this.endpoints)for(let t in this.endpoints)this.endpoints[t].results&&this.endpoints[t].results.length>0&&(this.endpoints[t].results[this.endpoints[t].results.length-1].success||e++);this.unhealthyCount=e},toggleGroup(){this.collapsed=!this.collapsed,localStorage.setItem(`gatus:endpoint-group:${this.name}:collapsed`,this.collapsed)},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{endpoints:function(){this.healthCheck()}},created(){this.healthCheck()},data(){return{unhealthyCount:0,collapsed:"true"===localStorage.getItem(`gatus:endpoint-group:${this.name}:collapsed`)}}};const Je=(0,$.Z)(Ve,[["render",Pe]]);var Xe=Je,Qe={name:"Endpoints",components:{EndpointGroup:Xe},props:{showStatusOnHover:Boolean,endpointStatuses:Object,showAverageResponseTime:Boolean},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{process(){let e={};for(let s in this.endpointStatuses){let t=this.endpointStatuses[s];e[t.group]&&0!==e[t.group].length||(e[t.group]=[]),e[t.group].push(t)}let t=[];for(let s in e)"undefined"!==s&&t.push({name:s,endpoints:e[s]});e["undefined"]&&t.push({name:"undefined",endpoints:e["undefined"]}),this.endpointGroups=t},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.$emit("toggleShowAverageResponseTime")}},watch:{endpointStatuses:function(){this.process()}},data(){return{userClickedStatus:!1,endpointGroups:[]}}};const et=(0,$.Z)(Qe,[["render",Se]]);var tt=et;const st={class:"mt-3 flex"},nt={class:"flex-1"},ot={class:"flex-1 text-right"};function at(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",st,[(0,o._)("div",nt,[a.currentPage<r.maxPages?((0,o.wg)(),(0,o.iD)("button",{key:0,onClick:t[0]||(t[0]=(...e)=>r.nextPage&&r.nextPage(...e)),class:"bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},"<")):(0,o.kq)("",!0)]),(0,o._)("div",ot,[a.currentPage>1?((0,o.wg)(),(0,o.iD)("button",{key:0,onClick:t[1]||(t[1]=(...e)=>r.previousPage&&r.previousPage(...e)),class:"bg-gray-100 hover:bg-gray-200 text-gray-500 border border-gray-200 px-2 rounded font-mono dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},">")):(0,o.kq)("",!0)])])}var rt={name:"Pagination",props:{numberOfResultsPerPage:Number},components:{},emits:["page"],methods:{nextPage(){this.currentPage++,this.$emit("page",this.currentPage)},previousPage(){this.currentPage--,this.$emit("page",this.currentPage)}},computed:{maxPages(){return Math.ceil(parseInt(window.config.maximumNumberOfResults)/this.numberOfResultsPerPage)}},data(){return{currentPage:1}}};const it=(0,$.Z)(rt,[["render",at]]);var lt=it,dt={name:"Home",components:{Loading:ne,Pagination:lt,Endpoints:tt,Settings:Re},emits:["showTooltip","toggleShowAverageResponseTime"],methods:{fetchData(){fetch(`${bs}/api/v1/endpoints/statuses?page=${this.currentPage}`,{credentials:"include"}).then((e=>{this.retrievedData=!0,200===e.status?e.json().then((e=>{JSON.stringify(this.endpointStatuses)!==JSON.stringify(e)&&(this.endpointStatuses=e)})):e.text().then((e=>{console.log(`[Home][fetchData] Error: ${e}`)}))}))},changePage(e){this.retrievedData=!1,this.currentPage=e,this.fetchData()},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.showAverageResponseTime=!this.showAverageResponseTime}},data(){return{endpointStatuses:[],currentPage:1,showAverageResponseTime:!0,retrievedData:!1}},created(){this.retrievedData=!1,this.fetchData()}};const gt=(0,$.Z)(dt,[["render",le]]);var ut=gt;const ht=e=>((0,o.dD)("data-v-2e619502"),e=e(),(0,o.Cn)(),e),ct=(0,o.Uk)(" ← "),pt=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"RECENT CHECKS",-1))),mt=ht((()=>(0,o._)("hr",{class:"mb-4"},null,-1))),ft={key:1,class:"mt-12"},vt=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"UPTIME",-1))),wt=ht((()=>(0,o._)("hr",null,null,-1))),xt={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},yt={class:"flex-1"},kt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 30 days",-1))),Tt=["src"],bt={class:"flex-1"},Rt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 7 days",-1))),_t=["src"],St={class:"flex-1"},Dt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 24 hours",-1))),It=["src"],At={class:"flex-1"},Ct=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last hour",-1))),Pt=["src"],$t={key:2,class:"mt-12"},Et={class:"flex items-center justify-between"},Ut=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400"},"RESPONSE TIME",-1))),Lt=ht((()=>(0,o._)("option",{value:"24h"},"24 hours",-1))),Ht=ht((()=>(0,o._)("option",{value:"7d"},"7 days",-1))),Ot=ht((()=>(0,o._)("option",{value:"30d"},"30 days",-1))),Wt=[Lt,Ht,Ot],Mt=["src"],Bt={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},jt={class:"flex-1"},qt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 30 days",-1))),zt=["src"],Nt={class:"flex-1"},Yt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 7 days",-1))),Zt=["src"],Gt={class:"flex-1"},Ft=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last 24 hours",-1))),Kt=["src"],Vt={class:"flex-1"},Jt=ht((()=>(0,o._)("h2",{class:"text-sm text-gray-400 mb-1"},"Last hour",-1))),Xt=["src"],Qt={key:3},es=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400 mt-4"},"CURRENT HEALTH",-1))),ts=ht((()=>(0,o._)("hr",null,null,-1))),ss={class:"flex space-x-4 text-center text-2xl mt-6 relative bottom-2 mb-10"},ns={class:"flex-1"},os=["src"],as={key:4},rs=ht((()=>(0,o._)("h1",{class:"text-xl xl:text-3xl font-mono text-gray-400 mt-4"},"EVENTS",-1))),is=ht((()=>(0,o._)("hr",null,null,-1))),ls={role:"list",class:"px-0 xl:px-24 divide-y divide-gray-200 dark:divide-gray-600"},ds={class:"text-sm sm:text-lg"},gs={class:"flex mt-1 text-xs sm:text-sm text-gray-400"},us={class:"flex-2 text-left pl-12"},hs={class:"flex-1 text-right"};function cs(e,t,s,r,i,l){const d=(0,o.up)("router-link"),g=(0,o.up)("Endpoint"),u=(0,o.up)("Pagination"),h=(0,o.up)("ArrowUpCircleIcon"),c=(0,o.up)("ArrowDownCircleIcon"),p=(0,o.up)("PlayCircleIcon"),m=(0,o.up)("Settings");return(0,o.wg)(),(0,o.iD)(o.HY,null,[(0,o.Wm)(d,{to:"../",class:"absolute top-2 left-5 inline-block px-2 pb-0.5 text-sm text-black bg-gray-100 rounded hover:bg-gray-200 focus:outline-none border border-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600"},{default:(0,o.w5)((()=>[ct])),_:1}),(0,o._)("div",null,[i.endpointStatus?(0,o.WI)(e.$slots,"default",{key:0},(()=>[pt,mt,(0,o.Wm)(g,{data:i.endpointStatus,maximumNumberOfResults:20,onShowTooltip:l.showTooltip,onToggleShowAverageResponseTime:l.toggleShowAverageResponseTime,showAverageResponseTime:i.showAverageResponseTime},null,8,["data","onShowTooltip","onToggleShowAverageResponseTime","showAverageResponseTime"]),(0,o.Wm)(u,{onPage:l.changePage,numberOfResultsPerPage:20},null,8,["onPage"])]),!0):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",ft,[vt,wt,(0,o._)("div",xt,[(0,o._)("div",yt,[kt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("30d"),alt:"30d uptime badge",class:"mx-auto"},null,8,Tt)]),(0,o._)("div",bt,[Rt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("7d"),alt:"7d uptime badge",class:"mx-auto"},null,8,_t)]),(0,o._)("div",St,[Dt,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("24h"),alt:"24h uptime badge",class:"mx-auto"},null,8,It)]),(0,o._)("div",At,[Ct,(0,o._)("img",{src:l.generateUptimeBadgeImageURL("1h"),alt:"1h uptime badge",class:"mx-auto"},null,8,Pt)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key&&i.showResponseTimeChartAndBadges?((0,o.wg)(),(0,o.iD)("div",$t,[(0,o._)("div",Et,[Ut,(0,o.wy)((0,o._)("select",{"onUpdate:modelValue":t[0]||(t[0]=e=>i.selectedChartDuration=e),class:"text-sm bg-gray-400 text-white border border-gray-600 rounded-md px-3 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500"},Wt,512),[[n.bM,i.selectedChartDuration]])]),(0,o._)("img",{src:l.generateResponseTimeChartImageURL(i.selectedChartDuration),alt:"response time chart",class:"mt-6"},null,8,Mt),(0,o._)("div",Bt,[(0,o._)("div",jt,[qt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("30d"),alt:"7d response time badge",class:"mx-auto mt-2"},null,8,zt)]),(0,o._)("div",Nt,[Yt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("7d"),alt:"7d response time badge",class:"mx-auto mt-2"},null,8,Zt)]),(0,o._)("div",Gt,[Ft,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("24h"),alt:"24h response time badge",class:"mx-auto mt-2"},null,8,Kt)]),(0,o._)("div",Vt,[Jt,(0,o._)("img",{src:l.generateResponseTimeBadgeImageURL("1h"),alt:"1h response time badge",class:"mx-auto mt-2"},null,8,Xt)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",Qt,[es,ts,(0,o._)("div",ss,[(0,o._)("div",ns,[(0,o._)("img",{src:l.generateHealthBadgeImageURL(),alt:"health badge",class:"mx-auto"},null,8,os)])])])):(0,o.kq)("",!0),i.endpointStatus&&i.endpointStatus.key?((0,o.wg)(),(0,o.iD)("div",as,[rs,is,(0,o._)("ul",ls,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(i.events,(t=>((0,o.wg)(),(0,o.iD)("li",{key:t,class:"p-3 my-4"},[(0,o._)("h2",ds,["HEALTHY"===t.type?((0,o.wg)(),(0,o.j4)(h,{key:0,class:"w-8 inline mr-2 text-green-600"})):"UNHEALTHY"===t.type?((0,o.wg)(),(0,o.j4)(c,{key:1,class:"w-8 inline mr-2 text-red-500"})):"START"===t.type?((0,o.wg)(),(0,o.j4)(p,{key:2,class:"w-8 inline mr-2 text-gray-400 dark:text-gray-100"})):(0,o.kq)("",!0),(0,o.Uk)(" "+(0,a.zw)(t.fancyText),1)]),(0,o._)("div",gs,[(0,o._)("div",us,(0,a.zw)(e.prettifyTimestamp(t.timestamp)),1),(0,o._)("div",hs,(0,a.zw)(t.fancyTimeAgo),1)])])))),128))])])):(0,o.kq)("",!0)]),(0,o.Wm)(m,{onRefreshData:l.fetchData},null,8,["onRefreshData"])],64)}var ps=s(9505),ms=s(7163),fs=s(8585),vs={name:"Details",components:{Pagination:lt,Endpoint:Fe,Settings:Re,ArrowDownCircleIcon:ps.Z,ArrowUpCircleIcon:ms.Z,PlayCircleIcon:fs.Z},emits:["showTooltip"],mixins:[G],methods:{fetchData(){fetch(`${this.serverUrl}/api/v1/endpoints/${this.$route.params.key}/statuses?page=${this.currentPage}`,{credentials:"include"}).then((e=>{200===e.status?e.json().then((e=>{if(JSON.stringify(this.endpointStatus)!==JSON.stringify(e)){this.endpointStatus=e;let t=[];for(let s=e.events.length-1;s>=0;s--){let n=e.events[s];if(s===e.events.length-1)"UNHEALTHY"===n.type?n.fancyText="Endpoint is unhealthy":"HEALTHY"===n.type?n.fancyText="Endpoint is healthy":"START"===n.type&&(n.fancyText="Monitoring started");else{let t=e.events[s+1];"HEALTHY"===n.type?n.fancyText="Endpoint became healthy":"UNHEALTHY"===n.type?n.fancyText=t?"Endpoint was unhealthy for "+this.generatePrettyTimeDifference(t.timestamp,n.timestamp):"Endpoint became unhealthy":"START"===n.type&&(n.fancyText="Monitoring started")}n.fancyTimeAgo=this.generatePrettyTimeAgo(n.timestamp),t.push(n)}this.events=t;for(let s=0;s<e.results.length;s++)if(e.results[s].duration>0){this.showResponseTimeChartAndBadges=!0;break}}})):e.text().then((e=>{console.log(`[Details][fetchData] Error: ${e}`)}))}))},generateHealthBadgeImageURL(){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/health/badge.svg`},generateUptimeBadgeImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/uptimes/${e}/badge.svg`},generateResponseTimeBadgeImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/response-times/${e}/badge.svg`},generateResponseTimeChartImageURL(e){return`${this.serverUrl}/api/v1/endpoints/${this.endpointStatus.key}/response-times/${e}/chart.svg`},changePage(e){this.currentPage=e,this.fetchData()},showTooltip(e,t){this.$emit("showTooltip",e,t)},toggleShowAverageResponseTime(){this.showAverageResponseTime=!this.showAverageResponseTime}},data(){return{endpointStatus:{},events:[],hourlyAverageResponseTime:{},selectedChartDuration:"24h",serverUrl:"."===bs?"..":bs,currentPage:1,showAverageResponseTime:!0,showResponseTimeChartAndBadges:!1,chartLabels:[],chartValues:[]}},created(){this.fetchData()}};const ws=(0,$.Z)(vs,[["render",cs],["__scopeId","data-v-2e619502"]]);var xs=ws;const ys=[{path:"/",name:"Home",component:ut},{path:"/endpoints/:key",name:"Details",component:xs}],ks=(0,ie.p7)({history:(0,ie.PO)("/"),routes:ys});var Ts=ks;const bs="";(0,n.ri)(re).use(Ts).mount("#app")}},t={};function s(n){var o=t[n];if(void 0!==o)return o.exports;var a=t[n]={exports:{}};return e[n](a,a.exports,s),a.exports}s.m=e,function(){var e=[];s.O=function(t,n,o,a){if(!n){var r=1/0;for(g=0;g<e.length;g++){n=e[g][0],o=e[g][1],a=e[g][2];for(var i=!0,l=0;l<n.length;l++)(!1&a||r>=a)&&Object.keys(s.O).every((function(e){return s.O[e](n[l])}))?n.splice(l--,1):(i=!1,a<r&&(r=a));if(i){e.splice(g--,1);var d=o();void 0!==d&&(t=d)}}return t}a=a||0;for(var g=e.length;g>0&&e[g-1][2]>a;g--)e[g]=e[g-1];e[g]=[n,o,a]}}(),function(){s.d=function(e,t){for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}}(),function(){s.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}()}(),function(){s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}}(),function(){s.p="/"}(),function(){var e={143:0};s.O.j=function(t){return 0===e[t]};var t=function(t,n){var o,a,r=n[0],i=n[1],l=n[2],d=0;if(r.some((function(t){return 0!==e[t]}))){for(o in i)s.o(i,o)&&(s.m[o]=i[o]);if(l)var g=l(s)}for(t&&t(n);d<r.length;d++)a=r[d],s.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return s.O(g)},n=self["webpackChunkgatus"]=self["webpackChunkgatus"]||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))}();var n=s.O(void 0,[998],(function(){return s(9351)}));n=s.O(n)})();
\ No newline at end of file


Test patch:
diff --git a/api/endpoint_status_test.go b/api/endpoint_status_test.go
index 08725393..f835db06 100644
--- a/api/endpoint_status_test.go
+++ b/api/endpoint_status_test.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/TwiN/gatus/v5/config"
 	"github.com/TwiN/gatus/v5/config/endpoint"
+	"github.com/TwiN/gatus/v5/storage"
 	"github.com/TwiN/gatus/v5/storage/store"
 	"github.com/TwiN/gatus/v5/watchdog"
 )
@@ -95,6 +96,10 @@ func TestEndpointStatus(t *testing.T) {
 				Group: "core",
 			},
 		},
+		Storage: &storage.Config{
+			MaximumNumberOfResults: storage.DefaultMaximumNumberOfResults,
+			MaximumNumberOfEvents:  storage.DefaultMaximumNumberOfEvents,
+		},
 	}
 	watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Duration: time.Millisecond, Timestamp: time.Now()})
 	watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Duration: time.Second, Timestamp: time.Now()})
@@ -156,7 +161,13 @@ func TestEndpointStatuses(t *testing.T) {
 	// Can't be bothered dealing with timezone issues on the worker that runs the automated tests
 	firstResult.Timestamp = time.Time{}
 	secondResult.Timestamp = time.Time{}
-	api := New(&config.Config{Metrics: true})
+	api := New(&config.Config{
+		Metrics: true,
+		Storage: &storage.Config{
+			MaximumNumberOfResults: storage.DefaultMaximumNumberOfResults,
+			MaximumNumberOfEvents:  storage.DefaultMaximumNumberOfEvents,
+		},
+	})
 	router := api.Router()
 	type Scenario struct {
 		Name         string
diff --git a/api/util_test.go b/api/util_test.go
index 652146ed..a0e846a9 100644
--- a/api/util_test.go
+++ b/api/util_test.go
@@ -4,54 +4,62 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/TwiN/gatus/v5/storage"
 	"github.com/gofiber/fiber/v2"
 	"github.com/valyala/fasthttp"
 )
 
 func TestExtractPageAndPageSizeFromRequest(t *testing.T) {
 	type Scenario struct {
-		Name             string
-		Page             string
-		PageSize         string
-		ExpectedPage     int
-		ExpectedPageSize int
+		Name                   string
+		Page                   string
+		PageSize               string
+		ExpectedPage           int
+		ExpectedPageSize       int
+		MaximumNumberOfResults int
 	}
 	scenarios := []Scenario{
 		{
-			Page:             "1",
-			PageSize:         "20",
-			ExpectedPage:     1,
-			ExpectedPageSize: 20,
+			Page:                   "1",
+			PageSize:               "20",
+			ExpectedPage:           1,
+			ExpectedPageSize:       20,
+			MaximumNumberOfResults: 20,
 		},
 		{
-			Page:             "2",
-			PageSize:         "10",
-			ExpectedPage:     2,
-			ExpectedPageSize: 10,
+			Page:                   "2",
+			PageSize:               "10",
+			ExpectedPage:           2,
+			ExpectedPageSize:       10,
+			MaximumNumberOfResults: 40,
 		},
 		{
-			Page:             "2",
-			PageSize:         "10",
-			ExpectedPage:     2,
-			ExpectedPageSize: 10,
+			Page:                   "2",
+			PageSize:               "10",
+			ExpectedPage:           2,
+			ExpectedPageSize:       10,
+			MaximumNumberOfResults: 200,
 		},
 		{
-			Page:             "1",
-			PageSize:         "999999",
-			ExpectedPage:     1,
-			ExpectedPageSize: MaximumPageSize,
+			Page:                   "1",
+			PageSize:               "999999",
+			ExpectedPage:           1,
+			ExpectedPageSize:       storage.DefaultMaximumNumberOfResults,
+			MaximumNumberOfResults: 100,
 		},
 		{
-			Page:             "-1",
-			PageSize:         "-1",
-			ExpectedPage:     DefaultPage,
-			ExpectedPageSize: DefaultPageSize,
+			Page:                   "-1",
+			PageSize:               "-1",
+			ExpectedPage:           DefaultPage,
+			ExpectedPageSize:       DefaultPageSize,
+			MaximumNumberOfResults: 20,
 		},
 		{
-			Page:             "invalid",
-			PageSize:         "invalid",
-			ExpectedPage:     DefaultPage,
-			ExpectedPageSize: DefaultPageSize,
+			Page:                   "invalid",
+			PageSize:               "invalid",
+			ExpectedPage:           DefaultPage,
+			ExpectedPageSize:       DefaultPageSize,
+			MaximumNumberOfResults: 100,
 		},
 	}
 	for _, scenario := range scenarios {
@@ -61,7 +69,7 @@ func TestExtractPageAndPageSizeFromRequest(t *testing.T) {
 			c := app.AcquireCtx(&fasthttp.RequestCtx{})
 			defer app.ReleaseCtx(c)
 			c.Request().SetRequestURI(fmt.Sprintf("/api/v1/statuses?page=%s&pageSize=%s", scenario.Page, scenario.PageSize))
-			actualPage, actualPageSize := extractPageAndPageSizeFromRequest(c)
+			actualPage, actualPageSize := extractPageAndPageSizeFromRequest(c, scenario.MaximumNumberOfResults)
 			if actualPage != scenario.ExpectedPage {
 				t.Errorf("expected %d, got %d", scenario.ExpectedPage, actualPage)
 			}
diff --git a/config/config_test.go b/config/config_test.go
index 8ed3ddf1..12ee5922 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -330,6 +330,8 @@ func TestParseAndValidateConfigBytes(t *testing.T) {
 storage:
   type: sqlite
   path: %s
+  maximum-number-of-results: 10
+  maximum-number-of-events: 5
 
 maintenance:
   enabled: true
@@ -386,6 +388,9 @@ endpoints:
 	if config.Storage == nil || config.Storage.Path != file || config.Storage.Type != storage.TypeSQLite {
 		t.Error("expected storage to be set to sqlite, got", config.Storage)
 	}
+	if config.Storage == nil || config.Storage.MaximumNumberOfResults != 10 || config.Storage.MaximumNumberOfEvents != 5 {
+		t.Error("expected MaximumNumberOfResults and MaximumNumberOfEvents to be set to 10 and 5, got", config.Storage.MaximumNumberOfResults, config.Storage.MaximumNumberOfEvents)
+	}
 	if config.UI == nil || config.UI.Title != "T" || config.UI.Header != "H" || config.UI.Link != "https://example.org" || len(config.UI.Buttons) != 2 || config.UI.Buttons[0].Name != "Home" || config.UI.Buttons[0].Link != "https://example.org" || config.UI.Buttons[1].Name != "Status page" || config.UI.Buttons[1].Link != "https://status.example.org" {
 		t.Error("expected ui to be set to T, H, https://example.org, 2 buttons, Home and Status page, got", config.UI)
 	}
diff --git a/storage/store/memory/memory_test.go b/storage/store/memory/memory_test.go
index 5d489677..544577bf 100644
--- a/storage/store/memory/memory_test.go
+++ b/storage/store/memory/memory_test.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/TwiN/gatus/v5/config/endpoint"
+	"github.com/TwiN/gatus/v5/storage"
 	"github.com/TwiN/gatus/v5/storage/store/common/paging"
 )
 
@@ -82,7 +83,7 @@ var (
 // Note that are much more extensive tests in /storage/store/store_test.go.
 // This test is simply an extra sanity check
 func TestStore_SanityCheck(t *testing.T) {
-	store, _ := NewStore()
+	store, _ := NewStore(storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	store.Insert(&testEndpoint, &testSuccessfulResult)
 	endpointStatuses, _ := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams())
@@ -122,7 +123,7 @@ func TestStore_SanityCheck(t *testing.T) {
 }
 
 func TestStore_Save(t *testing.T) {
-	store, err := NewStore()
+	store, err := NewStore(storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		t.Fatal("expected no error, got", err.Error())
 	}
diff --git a/storage/store/memory/uptime_test.go b/storage/store/memory/uptime_test.go
index 3d9f36c1..61a798a7 100644
--- a/storage/store/memory/uptime_test.go
+++ b/storage/store/memory/uptime_test.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/TwiN/gatus/v5/config/endpoint"
+	"github.com/TwiN/gatus/v5/storage"
 )
 
 func TestProcessUptimeAfterResult(t *testing.T) {
@@ -50,7 +51,7 @@ func TestAddResultUptimeIsCleaningUpAfterItself(t *testing.T) {
 	// Start 12 days ago
 	timestamp := now.Add(-12 * 24 * time.Hour)
 	for timestamp.Unix() <= now.Unix() {
-		AddResult(status, &endpoint.Result{Timestamp: timestamp, Success: true})
+		AddResult(status, &endpoint.Result{Timestamp: timestamp, Success: true}, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 		if len(status.Uptime.HourlyStatistics) > uptimeCleanUpThreshold {
 			t.Errorf("At no point in time should there be more than %d entries in status.SuccessfulExecutionsPerHour, but there are %d", uptimeCleanUpThreshold, len(status.Uptime.HourlyStatistics))
 		}
diff --git a/storage/store/memory/util_bench_test.go b/storage/store/memory/util_bench_test.go
index 36252829..d8f1d26a 100644
--- a/storage/store/memory/util_bench_test.go
+++ b/storage/store/memory/util_bench_test.go
@@ -4,15 +4,15 @@ import (
 	"testing"
 
 	"github.com/TwiN/gatus/v5/config/endpoint"
-	"github.com/TwiN/gatus/v5/storage/store/common"
+	"github.com/TwiN/gatus/v5/storage"
 	"github.com/TwiN/gatus/v5/storage/store/common/paging"
 )
 
 func BenchmarkShallowCopyEndpointStatus(b *testing.B) {
 	ep := &testEndpoint
 	status := endpoint.NewStatus(ep.Group, ep.Name)
-	for i := 0; i < common.MaximumNumberOfResults; i++ {
-		AddResult(status, &testSuccessfulResult)
+	for i := 0; i < storage.DefaultMaximumNumberOfResults; i++ {
+		AddResult(status, &testSuccessfulResult, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	}
 	for n := 0; n < b.N; n++ {
 		ShallowCopyEndpointStatus(status, paging.NewEndpointStatusParams().WithResults(1, 20))
diff --git a/storage/store/memory/util_test.go b/storage/store/memory/util_test.go
index 1de445e7..8287dafe 100644
--- a/storage/store/memory/util_test.go
+++ b/storage/store/memory/util_test.go
@@ -5,24 +5,24 @@ import (
 	"time"
 
 	"github.com/TwiN/gatus/v5/config/endpoint"
-	"github.com/TwiN/gatus/v5/storage/store/common"
+	"github.com/TwiN/gatus/v5/storage"
 	"github.com/TwiN/gatus/v5/storage/store/common/paging"
 )
 
 func TestAddResult(t *testing.T) {
 	ep := &endpoint.Endpoint{Name: "name", Group: "group"}
 	endpointStatus := endpoint.NewStatus(ep.Group, ep.Name)
-	for i := 0; i < (common.MaximumNumberOfResults+common.MaximumNumberOfEvents)*2; i++ {
-		AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: time.Now()})
+	for i := 0; i < (storage.DefaultMaximumNumberOfResults+storage.DefaultMaximumNumberOfEvents)*2; i++ {
+		AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: time.Now()}, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	}
-	if len(endpointStatus.Results) != common.MaximumNumberOfResults {
-		t.Errorf("expected endpointStatus.Results to not exceed a length of %d", common.MaximumNumberOfResults)
+	if len(endpointStatus.Results) != storage.DefaultMaximumNumberOfResults {
+		t.Errorf("expected endpointStatus.Results to not exceed a length of %d", storage.DefaultMaximumNumberOfResults)
 	}
-	if len(endpointStatus.Events) != common.MaximumNumberOfEvents {
-		t.Errorf("expected endpointStatus.Events to not exceed a length of %d", common.MaximumNumberOfEvents)
+	if len(endpointStatus.Events) != storage.DefaultMaximumNumberOfEvents {
+		t.Errorf("expected endpointStatus.Events to not exceed a length of %d", storage.DefaultMaximumNumberOfEvents)
 	}
 	// Try to add nil endpointStatus
-	AddResult(nil, &endpoint.Result{Timestamp: time.Now()})
+	AddResult(nil, &endpoint.Result{Timestamp: time.Now()}, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 }
 
 func TestShallowCopyEndpointStatus(t *testing.T) {
@@ -30,7 +30,7 @@ func TestShallowCopyEndpointStatus(t *testing.T) {
 	endpointStatus := endpoint.NewStatus(ep.Group, ep.Name)
 	ts := time.Now().Add(-25 * time.Hour)
 	for i := 0; i < 25; i++ {
-		AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: ts})
+		AddResult(endpointStatus, &endpoint.Result{Success: i%2 == 0, Timestamp: ts}, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 		ts = ts.Add(time.Hour)
 	}
 	if len(ShallowCopyEndpointStatus(endpointStatus, paging.NewEndpointStatusParams().WithResults(-1, -1)).Results) != 0 {
diff --git a/storage/store/sql/sql_test.go b/storage/store/sql/sql_test.go
index 7261c8fa..5dbfb899 100644
--- a/storage/store/sql/sql_test.go
+++ b/storage/store/sql/sql_test.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/TwiN/gatus/v5/alerting/alert"
 	"github.com/TwiN/gatus/v5/config/endpoint"
-	"github.com/TwiN/gatus/v5/storage/store/common"
+	"github.com/TwiN/gatus/v5/storage"
 	"github.com/TwiN/gatus/v5/storage/store/common/paging"
 )
 
@@ -84,13 +84,13 @@ var (
 )
 
 func TestNewStore(t *testing.T) {
-	if _, err := NewStore("", t.TempDir()+"/TestNewStore.db", false); !errors.Is(err, ErrDatabaseDriverNotSpecified) {
+	if _, err := NewStore("", t.TempDir()+"/TestNewStore.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents); !errors.Is(err, ErrDatabaseDriverNotSpecified) {
 		t.Error("expected error due to blank driver parameter")
 	}
-	if _, err := NewStore("sqlite", "", false); !errors.Is(err, ErrPathNotSpecified) {
+	if _, err := NewStore("sqlite", "", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents); !errors.Is(err, ErrPathNotSpecified) {
 		t.Error("expected error due to blank path parameter")
 	}
-	if store, err := NewStore("sqlite", t.TempDir()+"/TestNewStore.db", true); err != nil {
+	if store, err := NewStore("sqlite", t.TempDir()+"/TestNewStore.db", true, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents); err != nil {
 		t.Error("shouldn't have returned any error, got", err.Error())
 	} else {
 		_ = store.db.Close()
@@ -98,7 +98,7 @@ func TestNewStore(t *testing.T) {
 }
 
 func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpOldUptimeEntriesProperly.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpOldUptimeEntriesProperly.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	now := time.Now().Truncate(time.Hour)
 	now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
@@ -155,7 +155,7 @@ func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) {
 }
 
 func TestStore_HourlyUptimeEntriesAreMergedIntoDailyUptimeEntriesProperly(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_HourlyUptimeEntriesAreMergedIntoDailyUptimeEntriesProperly.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_HourlyUptimeEntriesAreMergedIntoDailyUptimeEntriesProperly.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	now := time.Now().Truncate(time.Hour)
 	now = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
@@ -212,7 +212,7 @@ func TestStore_HourlyUptimeEntriesAreMergedIntoDailyUptimeEntriesProperly(t *tes
 }
 
 func TestStore_getEndpointUptime(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpEventsAndResultsProperly.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpEventsAndResultsProperly.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Clear()
 	defer store.Close()
 	// Add 768 hourly entries (32 days)
@@ -274,13 +274,15 @@ func TestStore_getEndpointUptime(t *testing.T) {
 }
 
 func TestStore_InsertCleansUpEventsAndResultsProperly(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpEventsAndResultsProperly.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpEventsAndResultsProperly.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Clear()
 	defer store.Close()
+	resultsCleanUpThreshold := store.maximumNumberOfResults + resultsAboveMaximumCleanUpThreshold
+	eventsCleanUpThreshold := store.maximumNumberOfEvents + eventsAboveMaximumCleanUpThreshold
 	for i := 0; i < resultsCleanUpThreshold+eventsCleanUpThreshold; i++ {
 		store.Insert(&testEndpoint, &testSuccessfulResult)
 		store.Insert(&testEndpoint, &testUnsuccessfulResult)
-		ss, _ := store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(1, common.MaximumNumberOfResults*5).WithEvents(1, common.MaximumNumberOfEvents*5))
+		ss, _ := store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithResults(1, storage.DefaultMaximumNumberOfResults*5).WithEvents(1, storage.DefaultMaximumNumberOfEvents*5))
 		if len(ss.Results) > resultsCleanUpThreshold+1 {
 			t.Errorf("number of results shouldn't have exceeded %d, reached %d", resultsCleanUpThreshold, len(ss.Results))
 		}
@@ -291,7 +293,7 @@ func TestStore_InsertCleansUpEventsAndResultsProperly(t *testing.T) {
 }
 
 func TestStore_InsertWithCaching(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertWithCaching.db", true)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertWithCaching.db", true, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	// Add 2 results
 	store.Insert(&testEndpoint, &testSuccessfulResult)
@@ -326,7 +328,7 @@ func TestStore_InsertWithCaching(t *testing.T) {
 
 func TestStore_Persistence(t *testing.T) {
 	path := t.TempDir() + "/TestStore_Persistence.db"
-	store, _ := NewStore("sqlite", path, false)
+	store, _ := NewStore("sqlite", path, false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	store.Insert(&testEndpoint, &testSuccessfulResult)
 	store.Insert(&testEndpoint, &testUnsuccessfulResult)
 	if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); uptime != 0.5 {
@@ -341,15 +343,15 @@ func TestStore_Persistence(t *testing.T) {
 	if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour*24*30), time.Now()); uptime != 0.5 {
 		t.Errorf("the uptime over the past 30d should've been 0.5, got %f", uptime)
 	}
-	ssFromOldStore, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents))
+	ssFromOldStore, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, storage.DefaultMaximumNumberOfResults).WithEvents(1, storage.DefaultMaximumNumberOfEvents))
 	if ssFromOldStore == nil || ssFromOldStore.Group != "group" || ssFromOldStore.Name != "name" || len(ssFromOldStore.Events) != 3 || len(ssFromOldStore.Results) != 2 {
 		store.Close()
 		t.Fatal("sanity check failed")
 	}
 	store.Close()
-	store, _ = NewStore("sqlite", path, false)
+	store, _ = NewStore("sqlite", path, false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
-	ssFromNewStore, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents))
+	ssFromNewStore, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, storage.DefaultMaximumNumberOfResults).WithEvents(1, storage.DefaultMaximumNumberOfEvents))
 	if ssFromNewStore == nil || ssFromNewStore.Group != "group" || ssFromNewStore.Name != "name" || len(ssFromNewStore.Events) != 3 || len(ssFromNewStore.Results) != 2 {
 		t.Fatal("failed sanity check")
 	}
@@ -411,7 +413,7 @@ func TestStore_Persistence(t *testing.T) {
 }
 
 func TestStore_Save(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_Save.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_Save.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	if store.Save() != nil {
 		t.Error("Save shouldn't do anything for this store")
@@ -421,7 +423,7 @@ func TestStore_Save(t *testing.T) {
 // Note that are much more extensive tests in /storage/store/store_test.go.
 // This test is simply an extra sanity check
 func TestStore_SanityCheck(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_SanityCheck.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_SanityCheck.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	store.Insert(&testEndpoint, &testSuccessfulResult)
 	endpointStatuses, _ := store.GetAllEndpointStatuses(paging.NewEndpointStatusParams())
@@ -465,7 +467,7 @@ func TestStore_SanityCheck(t *testing.T) {
 
 // TestStore_InvalidTransaction tests what happens if an invalid transaction is passed as parameter
 func TestStore_InvalidTransaction(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InvalidTransaction.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InvalidTransaction.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	tx, _ := store.db.Begin()
 	tx.Commit()
@@ -523,7 +525,7 @@ func TestStore_InvalidTransaction(t *testing.T) {
 }
 
 func TestStore_NoRows(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_NoRows.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_NoRows.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	tx, _ := store.db.Begin()
 	defer tx.Rollback()
@@ -537,7 +539,7 @@ func TestStore_NoRows(t *testing.T) {
 
 // This tests very unlikely cases where a table is deleted.
 func TestStore_BrokenSchema(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_BrokenSchema.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_BrokenSchema.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	if err := store.Insert(&testEndpoint, &testSuccessfulResult); err != nil {
 		t.Fatal("expected no error, got", err.Error())
@@ -725,7 +727,7 @@ func TestCacheKey(t *testing.T) {
 }
 
 func TestTriggeredEndpointAlertsPersistence(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestTriggeredEndpointAlertsPersistence.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestTriggeredEndpointAlertsPersistence.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	yes, desc := false, "description"
 	ep := testEndpoint
@@ -791,7 +793,7 @@ func TestTriggeredEndpointAlertsPersistence(t *testing.T) {
 }
 
 func TestStore_DeleteAllTriggeredAlertsNotInChecksumsByEndpoint(t *testing.T) {
-	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_DeleteAllTriggeredAlertsNotInChecksumsByEndpoint.db", false)
+	store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_DeleteAllTriggeredAlertsNotInChecksumsByEndpoint.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	defer store.Close()
 	yes, desc := false, "description"
 	ep1 := testEndpoint
diff --git a/storage/store/store_bench_test.go b/storage/store/store_bench_test.go
index 0ed4c2db..dd4a0dd3 100644
--- a/storage/store/store_bench_test.go
+++ b/storage/store/store_bench_test.go
@@ -6,17 +6,18 @@ import (
 	"time"
 
 	"github.com/TwiN/gatus/v5/config/endpoint"
+	"github.com/TwiN/gatus/v5/storage"
 	"github.com/TwiN/gatus/v5/storage/store/common/paging"
 	"github.com/TwiN/gatus/v5/storage/store/memory"
 	"github.com/TwiN/gatus/v5/storage/store/sql"
 )
 
 func BenchmarkStore_GetAllEndpointStatuses(b *testing.B) {
-	memoryStore, err := memory.NewStore()
+	memoryStore, err := memory.NewStore(storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		b.Fatal("failed to create store:", err.Error())
 	}
-	sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetAllEndpointStatuses.db", false)
+	sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetAllEndpointStatuses.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		b.Fatal("failed to create store:", err.Error())
 	}
@@ -81,11 +82,11 @@ func BenchmarkStore_GetAllEndpointStatuses(b *testing.B) {
 }
 
 func BenchmarkStore_Insert(b *testing.B) {
-	memoryStore, err := memory.NewStore()
+	memoryStore, err := memory.NewStore(storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		b.Fatal("failed to create store:", err.Error())
 	}
-	sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_Insert.db", false)
+	sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_Insert.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		b.Fatal("failed to create store:", err.Error())
 	}
@@ -153,11 +154,11 @@ func BenchmarkStore_Insert(b *testing.B) {
 }
 
 func BenchmarkStore_GetEndpointStatusByKey(b *testing.B) {
-	memoryStore, err := memory.NewStore()
+	memoryStore, err := memory.NewStore(storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		b.Fatal("failed to create store:", err.Error())
 	}
-	sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetEndpointStatusByKey.db", false)
+	sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetEndpointStatusByKey.db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		b.Fatal("failed to create store:", err.Error())
 	}
diff --git a/storage/store/store_test.go b/storage/store/store_test.go
index 6f18f446..1dcfb5ac 100644
--- a/storage/store/store_test.go
+++ b/storage/store/store_test.go
@@ -91,15 +91,15 @@ type Scenario struct {
 }
 
 func initStoresAndBaseScenarios(t *testing.T, testName string) []*Scenario {
-	memoryStore, err := memory.NewStore()
+	memoryStore, err := memory.NewStore(storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		t.Fatal("failed to create store:", err.Error())
 	}
-	sqliteStore, err := sql.NewStore("sqlite", t.TempDir()+"/"+testName+".db", false)
+	sqliteStore, err := sql.NewStore("sqlite", t.TempDir()+"/"+testName+".db", false, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		t.Fatal("failed to create store:", err.Error())
 	}
-	sqliteStoreWithCaching, err := sql.NewStore("sqlite", t.TempDir()+"/"+testName+"-with-caching.db", true)
+	sqliteStoreWithCaching, err := sql.NewStore("sqlite", t.TempDir()+"/"+testName+"-with-caching.db", true, storage.DefaultMaximumNumberOfResults, storage.DefaultMaximumNumberOfEvents)
 	if err != nil {
 		t.Fatal("failed to create store:", err.Error())
 	}
@@ -138,7 +138,7 @@ func TestStore_GetEndpointStatusByKey(t *testing.T) {
 		t.Run(scenario.Name, func(t *testing.T) {
 			scenario.Store.Insert(&testEndpoint, &firstResult)
 			scenario.Store.Insert(&testEndpoint, &secondResult)
-			endpointStatus, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
+			endpointStatus, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, storage.DefaultMaximumNumberOfEvents).WithResults(1, storage.DefaultMaximumNumberOfResults))
 			if err != nil {
 				t.Fatal("shouldn't have returned an error, got", err.Error())
 			}
@@ -158,7 +158,7 @@ func TestStore_GetEndpointStatusByKey(t *testing.T) {
 				t.Error("The result at index 0 should've been older than the result at index 1")
 			}
 			scenario.Store.Insert(&testEndpoint, &thirdResult)
-			endpointStatus, err = scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
+			endpointStatus, err = scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, storage.DefaultMaximumNumberOfEvents).WithResults(1, storage.DefaultMaximumNumberOfResults))
 			if err != nil {
 				t.Fatal("shouldn't have returned an error, got", err.Error())
 			}
@@ -176,21 +176,21 @@ func TestStore_GetEndpointStatusForMissingStatusReturnsNil(t *testing.T) {
 	for _, scenario := range scenarios {
 		t.Run(scenario.Name, func(t *testing.T) {
 			scenario.Store.Insert(&testEndpoint, &testSuccessfulResult)
-			endpointStatus, err := scenario.Store.GetEndpointStatus("nonexistantgroup", "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
+			endpointStatus, err := scenario.Store.GetEndpointStatus("nonexistantgroup", "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, storage.DefaultMaximumNumberOfEvents).WithResults(1, storage.DefaultMaximumNumberOfResults))
 			if !errors.Is(err, common.ErrEndpointNotFound) {
 				t.Error("should've returned ErrEndpointNotFound, got", err)
 			}
 			if endpointStatus != nil {
 				t.Errorf("Returned endpoint status for group '%s' and name '%s' not nil after inserting the endpoint into the store", testEndpoint.Group, testEndpoint.Name)
 			}
-			endpointStatus, err = scenario.Store.GetEndpointStatus(testEndpoint.Group, "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
+			endpointStatus, err = scenario.Store.GetEndpointStatus(testEndpoint.Group, "nonexistantname", paging.NewEndpointStatusParams().WithEvents(1, storage.DefaultMaximumNumberOfEvents).WithResults(1, storage.DefaultMaximumNumberOfResults))
 			if !errors.Is(err, common.ErrEndpointNotFound) {
 				t.Error("should've returned ErrEndpointNotFound, got", err)
 			}
 			if endpointStatus != nil {
 				t.Errorf("Returned endpoint status for group '%s' and name '%s' not nil after inserting the endpoint into the store", testEndpoint.Group, "nonexistantname")
 			}
-			endpointStatus, err = scenario.Store.GetEndpointStatus("nonexistantgroup", testEndpoint.Name, paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
+			endpointStatus, err = scenario.Store.GetEndpointStatus("nonexistantgroup", testEndpoint.Name, paging.NewEndpointStatusParams().WithEvents(1, storage.DefaultMaximumNumberOfEvents).WithResults(1, storage.DefaultMaximumNumberOfResults))
 			if !errors.Is(err, common.ErrEndpointNotFound) {
 				t.Error("should've returned ErrEndpointNotFound, got", err)
 			}
@@ -470,7 +470,7 @@ func TestStore_Insert(t *testing.T) {
 		t.Run(scenario.Name, func(t *testing.T) {
 			scenario.Store.Insert(&testEndpoint, &firstResult)
 			scenario.Store.Insert(&testEndpoint, &secondResult)
-			ss, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
+			ss, err := scenario.Store.GetEndpointStatusByKey(testEndpoint.Key(), paging.NewEndpointStatusParams().WithEvents(1, storage.DefaultMaximumNumberOfEvents).WithResults(1, storage.DefaultMaximumNumberOfResults))
 			if err != nil {
 				t.Error("shouldn't have returned an error, got", err)
 			}