Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
447e140479 | ||
|
|
6908199716 | ||
|
|
b12f652553 | ||
|
|
83edca6e80 | ||
|
|
636688b43e | ||
|
|
4fdb55d632 | ||
|
|
a05daeda2e | ||
|
|
0bd0c1fd15 | ||
|
|
eb3ca71c72 | ||
|
|
37325cd78a | ||
|
|
f6e7e346b6 | ||
|
|
b5e742acde |
No files matched your search
+2
-1
@@ -4,4 +4,5 @@ Dockerfile
|
||||
.idea
|
||||
.git
|
||||
web/app
|
||||
*.db
|
||||
*.db
|
||||
testdata
|
||||
@@ -28,6 +28,6 @@ jobs:
|
||||
# was configured by the "Set up Go" step (otherwise, it'd use sudo's "go" executable)
|
||||
run: sudo env "PATH=$PATH" "GOROOT=$GOROOT" go test ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||
- name: Codecov
|
||||
uses: codecov/codecov-action@v3.1.1
|
||||
uses: codecov/codecov-action@v3.1.3
|
||||
with:
|
||||
files: ./coverage.txt
|
||||
@@ -67,9 +67,11 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
|
||||
- [Setting a default alert](#setting-a-default-alert)
|
||||
- [Maintenance](#maintenance)
|
||||
- [Security](#security)
|
||||
- [Basic](#basic)
|
||||
- [Basic Authentication](#basic-authentication)
|
||||
- [OIDC](#oidc)
|
||||
- [TLS Encryption](#tls-encryption)
|
||||
- [Metrics](#metrics)
|
||||
- [Connectivity](#connectivity)
|
||||
- [Remote instances (EXPERIMENTAL)](#remote-instances-experimental)
|
||||
- [Deployment](#deployment)
|
||||
- [Docker](#docker)
|
||||
@@ -87,7 +89,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
|
||||
- [Monitoring an endpoint using ICMP](#monitoring-an-endpoint-using-icmp)
|
||||
- [Monitoring an endpoint using DNS queries](#monitoring-an-endpoint-using-dns-queries)
|
||||
- [Monitoring an endpoint using STARTTLS](#monitoring-an-endpoint-using-starttls)
|
||||
- [Monitoring an endpoint using TLS](#monitoring-an-endpoint-using-tls)
|
||||
- [Monitoring an endpoint using TLS](#monitoring-an-endpoint-using-tls)>
|
||||
- [Monitoring domain expiration](#monitoring-domain-expiration)
|
||||
- [disable-monitoring-lock](#disable-monitoring-lock)
|
||||
- [Reloading configuration on the fly](#reloading-configuration-on-the-fly)
|
||||
@@ -228,6 +230,8 @@ If you want to test it locally, see [Docker](#docker).
|
||||
| `web` | Web configuration. | `{}` |
|
||||
| `web.address` | Address to listen on. | `0.0.0.0` |
|
||||
| `web.port` | Port to listen on. | `8080` |
|
||||
| `web.tls.certificate-file` | Optional public certificate file for TLS in PEM format. | `` |
|
||||
| `web.tls.private-key-file` | Optional private key file for TLS in PEM format. | `` |
|
||||
| `ui` | UI configuration. | `{}` |
|
||||
| `ui.title` | [Title of the document](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title). | `Health Dashboard ǀ Gatus` |
|
||||
| `ui.description` | Meta description for the page. | `Gatus is an advanced...`. |
|
||||
@@ -1053,13 +1057,13 @@ As a result, the `[ALERT_TRIGGERED_OR_RESOLVED]` in the body of first example of
|
||||
|
||||
|
||||
#### Setting a default alert
|
||||
| Parameter | Description | Default |
|
||||
|:----------------------------------------------|:------------------------------------------------------------------------------|:--------|
|
||||
| `alerting.*.default-alert.enabled` | Whether to enable the alert | N/A |
|
||||
| `alerting.*.default-alert.failure-threshold` | Number of failures in a row needed before triggering the alert | N/A |
|
||||
| `alerting.*.default-alert.success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | N/A |
|
||||
| `alerting.*.default-alert.send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved | N/A |
|
||||
| `alerting.*.default-alert.description` | Description of the alert. Will be included in the alert sent | N/A |
|
||||
| Parameter | Description | Default |
|
||||
|:---------------------------------------------|:------------------------------------------------------------------------------|:--------|
|
||||
| `alerting.*.default-alert.enabled` | Whether to enable the alert | N/A |
|
||||
| `alerting.*.default-alert.failure-threshold` | Number of failures in a row needed before triggering the alert | N/A |
|
||||
| `alerting.*.default-alert.success-threshold` | Number of successes in a row before an ongoing incident is marked as resolved | N/A |
|
||||
| `alerting.*.default-alert.send-on-resolved` | Whether to send a notification once a triggered alert is marked as resolved | N/A |
|
||||
| `alerting.*.default-alert.description` | Description of the alert. Will be included in the alert sent | N/A |
|
||||
|
||||
> ⚠ You must still specify the `type` of the alert in the endpoint configuration even if you set the default alert of a provider.
|
||||
|
||||
@@ -1175,14 +1179,14 @@ maintenance:
|
||||
|
||||
|
||||
### Security
|
||||
| Parameter | Description | Default |
|
||||
|:---------------------------------|:-----------------------------|:--------------|
|
||||
| `security` | Security configuration | `{}` |
|
||||
| `security.basic` | HTTP Basic configuration | `{}` |
|
||||
| `security.oidc` | OpenID Connect configuration | `{}` |
|
||||
| Parameter | Description | Default |
|
||||
|:-----------------|:-----------------------------|:--------|
|
||||
| `security` | Security configuration | `{}` |
|
||||
| `security.basic` | HTTP Basic configuration | `{}` |
|
||||
| `security.oidc` | OpenID Connect configuration | `{}` |
|
||||
|
||||
|
||||
#### Basic
|
||||
#### Basic Authentication
|
||||
| Parameter | Description | Default |
|
||||
|:----------------------------------------|:-----------------------------------------------------------------------------------|:--------------|
|
||||
| `security.basic` | HTTP Basic configuration | `{}` |
|
||||
@@ -1226,6 +1230,17 @@ security:
|
||||
|
||||
Confused? Read [Securing Gatus with OIDC using Auth0](https://twin.sh/articles/56/securing-gatus-with-oidc-using-auth0).
|
||||
|
||||
### TLS Encryption
|
||||
Gatus supports basic encryption with TLS. To enable this, certificate files in PEM format have to be provided.
|
||||
The example below shows an example configuration which makes gatus respond on port 4443 to HTTPS requests.
|
||||
|
||||
```yaml
|
||||
web:
|
||||
port: 4443
|
||||
tls:
|
||||
certificate-file: "server.crt"
|
||||
private-key-file: "server.key"
|
||||
```
|
||||
|
||||
### Metrics
|
||||
To enable metrics, you must set `metrics` to `true`. Doing so will expose Prometheus-friendly metrics at the `/metrics`
|
||||
@@ -1242,6 +1257,28 @@ endpoint on the same port your application is configured to run on (`web.port`).
|
||||
See [examples/docker-compose-grafana-prometheus](.examples/docker-compose-grafana-prometheus) for further documentation as well as an example.
|
||||
|
||||
|
||||
### Connectivity
|
||||
| Parameter | Description | Default |
|
||||
|:--------------------------------|:-------------------------------------------|:--------------|
|
||||
| `connectivity` | Connectivity configuration | `{}` |
|
||||
| `connectivity.checker` | Connectivity checker configuration | Required `{}` |
|
||||
| `connectivity.checker.target` | Host to use for validating connectivity | Required `""` |
|
||||
| `connectivity.checker.interval` | Interval at which to validate connectivity | `1m` |
|
||||
|
||||
While Gatus is used to monitor other services, it is possible for Gatus itself to lose connectivity to the internet.
|
||||
In order to prevent Gatus from reporting endpoints as unhealthy when Gatus itself is unhealthy, you may configure
|
||||
Gatus to periodically check for internet connectivity.
|
||||
|
||||
All endpoint executions are skipped while the connectivity checker deems connectivity to be down.
|
||||
|
||||
```yaml
|
||||
connectivity:
|
||||
checker:
|
||||
target: 1.1.1.1:53
|
||||
interval: 60s
|
||||
```
|
||||
|
||||
|
||||
### Remote instances (EXPERIMENTAL)
|
||||
This feature allows you to retrieve endpoint statuses from a remote Gatus instance.
|
||||
|
||||
@@ -1253,12 +1290,12 @@ This is an experimental feature. It may be removed or updated in a breaking mann
|
||||
there are known issues with this feature. If you'd like to provide some feedback, please write a comment in [#64](https://github.com/TwiN/gatus/issues/64).
|
||||
Use at your own risk.
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|:-----------------------------------|:---------------------------------------------|:---------------|
|
||||
| `remote` | Remote configuration | `{}` |
|
||||
| `remote.instances` | List of remote instances | Required `[]` |
|
||||
| `remote.instances.endpoint-prefix` | String to prefix all endpoint names with | `""` |
|
||||
| `remote.instances.url` | URL from which to retrieve endpoint statuses | Required `""` |
|
||||
| Parameter | Description | Default |
|
||||
|:-----------------------------------|:---------------------------------------------|:--------------|
|
||||
| `remote` | Remote configuration | `{}` |
|
||||
| `remote.instances` | List of remote instances | Required `[]` |
|
||||
| `remote.instances.endpoint-prefix` | String to prefix all endpoint names with | `""` |
|
||||
| `remote.instances.url` | URL from which to retrieve endpoint statuses | Required `""` |
|
||||
|
||||
```yaml
|
||||
remote:
|
||||
@@ -1386,11 +1423,11 @@ simple health checks used for alerting (PagerDuty/Twilio) to `30s`.
|
||||
|
||||
|
||||
### Default timeouts
|
||||
| Endpoint type | Timeout |
|
||||
|:---------------|:--------|
|
||||
| HTTP | 10s |
|
||||
| TCP | 10s |
|
||||
| ICMP | 10s |
|
||||
| Endpoint type | Timeout |
|
||||
|:--------------|:--------|
|
||||
| HTTP | 10s |
|
||||
| TCP | 10s |
|
||||
| ICMP | 10s |
|
||||
|
||||
To modify the timeout, see [Client configuration](#client-configuration).
|
||||
|
||||
@@ -1784,6 +1821,8 @@ No such header is required to query the API.
|
||||
## Sponsors
|
||||
You can find the full list of sponsors [here](https://github.com/sponsors/TwiN).
|
||||
|
||||
_There is currently no sponsors_
|
||||
<!-- _There is currently no sponsors_ -->
|
||||
|
||||
[<img src="https://github.com/8ball030.png" width="50" />](https://github.com/8ball030)
|
||||
|
||||
<!-- [<img src="https://github.com/$user.png" width="50" />](https://github.com/$user) -->
|
||||
@@ -191,6 +191,9 @@ func TestCanCreateTCPConnection(t *testing.T) {
|
||||
if CanCreateTCPConnection("127.0.0.1", &Config{Timeout: 5 * time.Second}) {
|
||||
t.Error("should've failed, because there's no port in the address")
|
||||
}
|
||||
if !CanCreateTCPConnection("1.1.1.1:53", &Config{Timeout: 5 * time.Second}) {
|
||||
t.Error("should've succeeded, because that IP should always™ be up")
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks if a HTTP client configured with `configureOAuth2()` automatically
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/TwiN/gatus/v5/alerting"
|
||||
"github.com/TwiN/gatus/v5/alerting/alert"
|
||||
"github.com/TwiN/gatus/v5/alerting/provider"
|
||||
"github.com/TwiN/gatus/v5/config/connectivity"
|
||||
"github.com/TwiN/gatus/v5/config/maintenance"
|
||||
"github.com/TwiN/gatus/v5/config/remote"
|
||||
"github.com/TwiN/gatus/v5/config/ui"
|
||||
@@ -91,6 +92,9 @@ type Config struct {
|
||||
// WARNING: This is in ALPHA and may change or be completely removed in the future
|
||||
Remote *remote.Config `yaml:"remote,omitempty"`
|
||||
|
||||
// Connectivity is the configuration for connectivity
|
||||
Connectivity *connectivity.Config `yaml:"connectivity,omitempty"`
|
||||
|
||||
configPath string // path to the file or directory from which config was loaded
|
||||
lastFileModTime time.Time // last modification time
|
||||
}
|
||||
@@ -252,10 +256,20 @@ func parseAndValidateConfigBytes(yamlBytes []byte) (config *Config, err error) {
|
||||
if err := validateRemoteConfig(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateConnectivityConfig(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateConnectivityConfig(config *Config) error {
|
||||
if config.Connectivity != nil {
|
||||
return config.Connectivity.ValidateAndSetDefaults()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRemoteConfig(config *Config) error {
|
||||
if config.Remote != nil {
|
||||
if err := config.Remote.ValidateAndSetDefaults(); err != nil {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package connectivity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TwiN/gatus/v5/client"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidInterval = errors.New("connectivity.checker.interval must be 5s or higher")
|
||||
ErrInvalidDNSTarget = errors.New("connectivity.checker.target must be suffixed with :53")
|
||||
)
|
||||
|
||||
// Config is the configuration for the connectivity checker.
|
||||
type Config struct {
|
||||
Checker *Checker `yaml:"checker,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Config) ValidateAndSetDefaults() error {
|
||||
if c.Checker != nil {
|
||||
if c.Checker.Interval == 0 {
|
||||
c.Checker.Interval = 60 * time.Second
|
||||
} else if c.Checker.Interval < 5*time.Second {
|
||||
return ErrInvalidInterval
|
||||
}
|
||||
if !strings.HasSuffix(c.Checker.Target, ":53") {
|
||||
return ErrInvalidDNSTarget
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checker is the configuration for making sure Gatus has access to the internet.
|
||||
type Checker struct {
|
||||
Target string `yaml:"target"` // e.g. 1.1.1.1:53
|
||||
Interval time.Duration `yaml:"interval,omitempty"`
|
||||
|
||||
isConnected bool
|
||||
lastCheck time.Time
|
||||
}
|
||||
|
||||
func (c Checker) Check() bool {
|
||||
return client.CanCreateTCPConnection(c.Target, &client.Config{Timeout: 5 * time.Second})
|
||||
}
|
||||
|
||||
func (c *Checker) IsConnected() bool {
|
||||
if now := time.Now(); now.After(c.lastCheck.Add(c.Interval)) {
|
||||
c.lastCheck, c.isConnected = now, c.Check()
|
||||
}
|
||||
return c.isConnected
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package connectivity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
cfg *Config
|
||||
expectedErr error
|
||||
expectedInterval time.Duration
|
||||
}{
|
||||
{
|
||||
name: "good-config",
|
||||
cfg: &Config{Checker: &Checker{Target: "1.1.1.1:53", Interval: 10 * time.Second}},
|
||||
expectedInterval: 10 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "good-config-with-default-interval",
|
||||
cfg: &Config{Checker: &Checker{Target: "8.8.8.8:53", Interval: 0}},
|
||||
expectedInterval: 60 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "config-with-interval-too-low",
|
||||
cfg: &Config{Checker: &Checker{Target: "1.1.1.1:53", Interval: 4 * time.Second}},
|
||||
expectedErr: ErrInvalidInterval,
|
||||
},
|
||||
{
|
||||
name: "config-with-invalid-target-due-to-missing-port",
|
||||
cfg: &Config{Checker: &Checker{Target: "1.1.1.1", Interval: 15 * time.Second}},
|
||||
expectedErr: ErrInvalidDNSTarget,
|
||||
},
|
||||
{
|
||||
name: "config-with-invalid-target-due-to-invalid-dns-port",
|
||||
cfg: &Config{Checker: &Checker{Target: "1.1.1.1:52", Interval: 15 * time.Second}},
|
||||
expectedErr: ErrInvalidDNSTarget,
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
err := scenario.cfg.ValidateAndSetDefaults()
|
||||
if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", scenario.expectedErr) {
|
||||
t.Errorf("expected error %v, got %v", scenario.expectedErr, err)
|
||||
}
|
||||
if err == nil && scenario.expectedErr == nil {
|
||||
if scenario.cfg.Checker.Interval != scenario.expectedInterval {
|
||||
t.Errorf("expected interval %v, got %v", scenario.expectedInterval, scenario.cfg.Checker.Interval)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecker_IsConnected(t *testing.T) {
|
||||
checker := &Checker{Target: "1.1.1.1:53", Interval: 10 * time.Second}
|
||||
if !checker.IsConnected() {
|
||||
t.Error("expected checker.IsConnected() to be true")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
@@ -21,6 +23,19 @@ type Config struct {
|
||||
|
||||
// Port to listen on (default to 8080 specified by DefaultPort)
|
||||
Port int `yaml:"port"`
|
||||
|
||||
// TLS configuration (optional)
|
||||
TLS *TLSConfig `yaml:"tls,omitempty"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
// CertificateFile is the public certificate for TLS in PEM format.
|
||||
CertificateFile string `yaml:"certificate-file,omitempty"`
|
||||
|
||||
// PrivateKeyFile is the private key file for TLS in PEM format.
|
||||
PrivateKeyFile string `yaml:"private-key-file,omitempty"`
|
||||
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// GetDefaultConfig returns a Config struct with the default values
|
||||
@@ -40,6 +55,12 @@ func (web *Config) ValidateAndSetDefaults() error {
|
||||
} else if web.Port < 0 || web.Port > math.MaxUint16 {
|
||||
return fmt.Errorf("invalid port: value should be between %d and %d", 0, math.MaxUint16)
|
||||
}
|
||||
// Try to load the TLS certificates
|
||||
if web.TLS != nil {
|
||||
if err := web.TLS.loadConfig(); err != nil {
|
||||
return fmt.Errorf("invalid tls config: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,3 +68,22 @@ func (web *Config) ValidateAndSetDefaults() error {
|
||||
func (web *Config) SocketAddress() string {
|
||||
return fmt.Sprintf("%s:%d", web.Address, web.Port)
|
||||
}
|
||||
|
||||
func (t *TLSConfig) loadConfig() error {
|
||||
if len(t.CertificateFile) > 0 && len(t.PrivateKeyFile) > 0 {
|
||||
certificate, err := tls.LoadX509KeyPair(t.CertificateFile, t.PrivateKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.tlsConfig = &tls.Config{Certificates: []tls.Certificate{certificate}}
|
||||
return nil
|
||||
}
|
||||
return errors.New("certificate-file and private-key-file must be specified")
|
||||
}
|
||||
|
||||
func (web *Config) TLSConfig() *tls.Config {
|
||||
if web.TLS != nil {
|
||||
return web.TLS.tlsConfig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -12,6 +12,9 @@ func TestGetDefaultConfig(t *testing.T) {
|
||||
if defaultConfig.Address != DefaultAddress {
|
||||
t.Error("expected default config to have the default address")
|
||||
}
|
||||
if defaultConfig.TLS != nil {
|
||||
t.Error("expected default config to have TLS disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
|
||||
@@ -63,3 +66,56 @@ func TestConfig_SocketAddress(t *testing.T) {
|
||||
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_TLSConfig(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
cfg *Config
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "good-tls-config",
|
||||
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/cert.key"}},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "missing-crt-file",
|
||||
cfg: &Config{TLS: &TLSConfig{CertificateFile: "doesnotexist", PrivateKeyFile: "../../testdata/cert.key"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad-crt-file",
|
||||
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/cert.key"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing-private-key-file",
|
||||
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "doesnotexist"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad-private-key-file",
|
||||
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/cert.pem", PrivateKeyFile: "../../testdata/badcert.key"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad-cert-and-private-key-file",
|
||||
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../../testdata/badcert.pem", PrivateKeyFile: "../../testdata/badcert.key"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
err := scenario.cfg.ValidateAndSetDefaults()
|
||||
if (err != nil) != scenario.expectedErr {
|
||||
t.Errorf("expected the existence of an error to be %v, got %v", scenario.expectedErr, err)
|
||||
return
|
||||
}
|
||||
if !scenario.expectedErr {
|
||||
if scenario.cfg.TLS.tlsConfig == nil {
|
||||
t.Error("TLS configuration was not correctly loaded although no error was returned")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,10 @@ func Handle(cfg *config.Config) {
|
||||
if os.Getenv("ENVIRONMENT") == "dev" {
|
||||
router = handler.DevelopmentCORS(router)
|
||||
}
|
||||
tlsConfig := cfg.Web.TLSConfig()
|
||||
server = &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", cfg.Web.Address, cfg.Web.Port),
|
||||
TLSConfig: tlsConfig,
|
||||
Handler: router,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
@@ -35,7 +37,11 @@ func Handle(cfg *config.Config) {
|
||||
if os.Getenv("ROUTER_TEST") == "true" {
|
||||
return
|
||||
}
|
||||
log.Println("[controller][Handle]", server.ListenAndServe())
|
||||
if tlsConfig != nil {
|
||||
log.Println("[controller][Handle]", server.ListenAndServeTLS("", ""))
|
||||
} else {
|
||||
log.Println("[controller][Handle]", server.ListenAndServe())
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown stops the server
|
||||
|
||||
@@ -45,6 +45,48 @@ func TestHandle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTLS(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
tls *web.TLSConfig
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
name: "good-tls-config",
|
||||
tls: &web.TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"},
|
||||
expectedStatusCode: 200,
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Web: &web.Config{Address: "0.0.0.0", Port: rand.Intn(65534), TLS: scenario.tls},
|
||||
Endpoints: []*core.Endpoint{
|
||||
{Name: "frontend", Group: "core"},
|
||||
{Name: "backend", Group: "core"},
|
||||
},
|
||||
}
|
||||
if err := cfg.Web.ValidateAndSetDefaults(); err != nil {
|
||||
t.Error("expected no error from web (TLS) validation, got", err)
|
||||
}
|
||||
_ = os.Setenv("ROUTER_TEST", "true")
|
||||
_ = os.Setenv("ENVIRONMENT", "dev")
|
||||
defer os.Clearenv()
|
||||
Handle(cfg)
|
||||
defer Shutdown()
|
||||
request, _ := http.NewRequest("GET", "/health", http.NoBody)
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
server.Handler.ServeHTTP(responseRecorder, request)
|
||||
if responseRecorder.Code != scenario.expectedStatusCode {
|
||||
t.Errorf("expected GET /health to return status code %d, got %d", scenario.expectedStatusCode, responseRecorder.Code)
|
||||
}
|
||||
if server == nil {
|
||||
t.Fatal("server should've been set (but because we set ROUTER_TEST, it shouldn't have been started)")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShutdown(t *testing.T) {
|
||||
// Pretend that we called controller.Handle(), which initializes the server variable
|
||||
server = &http.Server{}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module github.com/TwiN/gatus/v5
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/TwiN/deepmerge v0.2.0
|
||||
github.com/TwiN/g8 v1.4.0
|
||||
github.com/TwiN/g8/v2 v2.0.0
|
||||
github.com/TwiN/gocache/v2 v2.2.0
|
||||
github.com/TwiN/health v1.6.0
|
||||
github.com/TwiN/whois v1.1.0
|
||||
@@ -18,8 +18,8 @@ require (
|
||||
github.com/prometheus-community/pro-bing v0.1.0
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.0
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/oauth2 v0.5.0
|
||||
golang.org/x/crypto v0.8.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.20.4
|
||||
@@ -43,9 +43,9 @@ require (
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/tools v0.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
||||
@@ -36,8 +36,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/TwiN/deepmerge v0.2.0 h1:8P1tp2qDXllX6V1Ailg2XA074easAcvLMmW9v1jn0aE=
|
||||
github.com/TwiN/deepmerge v0.2.0/go.mod h1:cR9OWsvI13y+FxrbnPLuF6BX2tbYeOjkiI6JWjEGqAE=
|
||||
github.com/TwiN/g8 v1.4.0 h1:RUk5xTtxKCdMo0GGSbBVyjtAAfi2nqVbA9E0C4u5Cxo=
|
||||
github.com/TwiN/g8 v1.4.0/go.mod h1:ECyGJsoIb99klUfvVQoS1StgRLte9yvvPigGrHdy284=
|
||||
github.com/TwiN/g8/v2 v2.0.0 h1:+hwIbRLMhDd2iwHzkZUPp2FkX7yTx8ddYOnS91HkDqQ=
|
||||
github.com/TwiN/g8/v2 v2.0.0/go.mod h1:4sVAF27q8T8ISggRa/Fb0drw7wpB22B6eWd+/+SGMqE=
|
||||
github.com/TwiN/gocache/v2 v2.2.0 h1:M3B36KyH24BntxLrLaUb2kgTdq8DzCnfod0IekLG57w=
|
||||
github.com/TwiN/gocache/v2 v2.2.0/go.mod h1:SnUuBsrwGQeNcDG6vhkOMJnqErZM0JGjgIkuKryokYA=
|
||||
github.com/TwiN/health v1.6.0 h1:L2ks575JhRgQqWWOfKjw9B0ec172hx7GdToqkYUycQM=
|
||||
@@ -261,8 +261,8 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -334,8 +334,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -344,8 +344,8 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
|
||||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -400,8 +400,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/TwiN/g8"
|
||||
g8 "github.com/TwiN/g8/v2"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
wat
|
||||
-----END PRIVATE KEY-----
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
wat
|
||||
-----END CERTIFICATE-----
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJh67FWpz8wrN1mM/
|
||||
CebkZN0zF83691ZVD83XlbNLRUqhRANCAAScfyPxScqz+Z/yNtAID/FOORy9J6LM
|
||||
DUAJevGDvAZCMp/nh+Ps3nLrMoRlykcux3mq+N8HPlJ8R3eetB4S1tHY
|
||||
-----END PRIVATE KEY-----
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBaDCCAQ2gAwIBAgICBNIwCgYIKoZIzj0EAwIwFTETMBEGA1UEChMKR2F0dXMg
|
||||
dGVzdDAgFw0yMzA0MjIxODUwMDVaGA8yMjk3MDIwNDE4NTAwNVowFTETMBEGA1UE
|
||||
ChMKR2F0dXMgdGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJx/I/FJyrP5
|
||||
n/I20AgP8U45HL0noswNQAl68YO8BkIyn+eH4+zecusyhGXKRy7Hear43wc+UnxH
|
||||
d560HhLW0dijSzBJMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD
|
||||
ATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAKBggqhkjOPQQD
|
||||
AgNJADBGAiEA/SdthKOoNw3azSHuPid7XJsXYB8DisIC9LBwcb/QTMECIQCAB36Y
|
||||
OI15ao+J/RUz2sXdPXCAN8hlohi6OnmZmJB32g==
|
||||
-----END CERTIFICATE-----
|
||||
+12
-8
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/TwiN/gatus/v5/alerting"
|
||||
"github.com/TwiN/gatus/v5/config"
|
||||
"github.com/TwiN/gatus/v5/config/connectivity"
|
||||
"github.com/TwiN/gatus/v5/config/maintenance"
|
||||
"github.com/TwiN/gatus/v5/core"
|
||||
"github.com/TwiN/gatus/v5/metrics"
|
||||
@@ -30,15 +31,15 @@ func Monitor(cfg *config.Config) {
|
||||
if endpoint.IsEnabled() {
|
||||
// To prevent multiple requests from running at the same time, we'll wait for a little before each iteration
|
||||
time.Sleep(777 * time.Millisecond)
|
||||
go monitor(endpoint, cfg.Alerting, cfg.Maintenance, cfg.DisableMonitoringLock, cfg.Metrics, cfg.Debug, ctx)
|
||||
go monitor(endpoint, cfg.Alerting, cfg.Maintenance, cfg.Connectivity, cfg.DisableMonitoringLock, cfg.Metrics, cfg.Debug, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// monitor a single endpoint in a loop
|
||||
func monitor(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, disableMonitoringLock, enabledMetrics, debug bool, ctx context.Context) {
|
||||
func monitor(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock, enabledMetrics, debug bool, ctx context.Context) {
|
||||
// Run it immediately on start
|
||||
execute(endpoint, alertingConfig, maintenanceConfig, disableMonitoringLock, enabledMetrics, debug)
|
||||
execute(endpoint, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics, debug)
|
||||
// Loop for the next executions
|
||||
for {
|
||||
select {
|
||||
@@ -46,16 +47,22 @@ func monitor(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenan
|
||||
log.Printf("[watchdog][monitor] Canceling current execution of group=%s; endpoint=%s", endpoint.Group, endpoint.Name)
|
||||
return
|
||||
case <-time.After(endpoint.Interval):
|
||||
execute(endpoint, alertingConfig, maintenanceConfig, disableMonitoringLock, enabledMetrics, debug)
|
||||
execute(endpoint, alertingConfig, maintenanceConfig, connectivityConfig, disableMonitoringLock, enabledMetrics, debug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func execute(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, disableMonitoringLock, enabledMetrics, debug bool) {
|
||||
func execute(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenanceConfig *maintenance.Config, connectivityConfig *connectivity.Config, disableMonitoringLock, enabledMetrics, debug bool) {
|
||||
if !disableMonitoringLock {
|
||||
// By placing the lock here, we prevent multiple endpoints from being monitored at the exact same time, which
|
||||
// could cause performance issues and return inaccurate results
|
||||
monitoringMutex.Lock()
|
||||
defer monitoringMutex.Unlock()
|
||||
}
|
||||
// If there's a connectivity checker configured, check if Gatus has internet connectivity
|
||||
if connectivityConfig != nil && connectivityConfig.Checker != nil && !connectivityConfig.Checker.IsConnected() {
|
||||
log.Println("[watchdog][execute] No connectivity; skipping execution")
|
||||
return
|
||||
}
|
||||
if debug {
|
||||
log.Printf("[watchdog][execute] Monitoring group=%s; endpoint=%s", endpoint.Group, endpoint.Name)
|
||||
@@ -79,9 +86,6 @@ func execute(endpoint *core.Endpoint, alertingConfig *alerting.Config, maintenan
|
||||
if debug {
|
||||
log.Printf("[watchdog][execute] Waiting for interval=%s before monitoring group=%s endpoint=%s again", endpoint.Interval, endpoint.Group, endpoint.Name)
|
||||
}
|
||||
if !disableMonitoringLock {
|
||||
monitoringMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateEndpointStatuses updates the slice of endpoint statuses
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<Loading v-if="!retrievedData" class="h-64 w-64 px-4 my-24"/>
|
||||
<slot v-else>
|
||||
<slot>
|
||||
<Endpoints
|
||||
v-show="retrievedData"
|
||||
:endpointStatuses="endpointStatuses"
|
||||
:showStatusOnHover="true"
|
||||
@showTooltip="showTooltip"
|
||||
@toggleShowAverageResponseTime="toggleShowAverageResponseTime"
|
||||
:showAverageResponseTime="showAverageResponseTime"
|
||||
/>
|
||||
<Pagination @page="changePage"/>
|
||||
<Pagination v-show="retrievedData" @page="changePage"/>
|
||||
</slot>
|
||||
<Settings @refreshData="fetchData"/>
|
||||
</template>
|
||||
@@ -31,6 +32,7 @@ export default {
|
||||
emits: ['showTooltip', 'toggleShowAverageResponseTime'],
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.retrievedData = false;
|
||||
fetch(`${SERVER_URL}/api/v1/endpoints/statuses?page=${this.currentPage}`, {credentials: 'include'})
|
||||
.then(response => {
|
||||
this.retrievedData = true;
|
||||
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><script>window.config = {logo: "{{ .Logo }}", header: "{{ .Header }}", link: "{{ .Link }}", buttons: []};{{- range .Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}</script><title>{{ .Title }}</title><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="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"><link rel="shortcut icon" href="/favicon.ico"/><meta name="description" content="{{ .Description }}"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="{{ .Title }}"><meta name="application-name" content="{{ .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>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><script>window.config = {logo: "{{ .Logo }}", header: "{{ .Header }}", link: "{{ .Link }}", buttons: []};{{- range .Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}</script><title>{{ .Title }}</title><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="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"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="description" content="{{ .Description }}"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/><meta name="apple-mobile-web-app-title" content="{{ .Title }}"/><meta name="application-name" content="{{ .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>
|
||||
@@ -1 +1 @@
|
||||
(function(){"use strict";var e={8718:function(e,t,s){s.d(t,{L:function(){return cs}});s(7727);var n=s(9963),o=s(6252),a=s(3577),r=s.p+"img/logo.svg";const i={class:"mb-2"},l={class:"flex flex-wrap"},d={class:"w-3/4 text-left my-auto"},g={class:"text-3xl xl:text-5xl lg:text-4xl font-light"},h={class:"w-1/4 flex justify-end"},u=["src"],c={key:1,src:r,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},p={key:0,class:"flex flex-wrap"},m=["href"],v={key:2,class:"mx-auto max-w-md pt-12"},f=(0,o._)("img",{src:r,alt:"Gatus",class:"mx-auto",style:{"max-width":"160px","min-width":"50px","min-height":"50px"}},null,-1),w=(0,o._)("h2",{class:"mt-4 text-center text-4xl font-extrabold text-gray-800 dark:text-gray-200"}," Gatus ",-1),x={class:"py-7 px-4 rounded-sm sm:px-10"},y={key:0,class:"text-red-500 text-center mb-5"},k={class:"text-sm"},T={key:0,class:"text-red-500"},b={key:1,class:"text-red-500"},R=["href"];function _(e,t,s,n,r,_){const S=(0,o.up)("Loading"),I=(0,o.up)("router-view"),D=(0,o.up)("Tooltip"),A=(0,o.up)("Social");return(0,o.wg)(),(0,o.iD)(o.HY,null,[r.retrievedConfig?((0,o.wg)(),(0,o.iD)("div",{key:1,class:(0,a.C_)([r.config&&r.config.oidc&&!r.config.authenticated?"hidden":"","container container-xs relative mx-auto xl:rounded xl:border xl:shadow-xl xl:my-5 p-5 pb-12 xl:pb-5 text-left dark:bg-gray-800 dark:text-gray-200 dark:border-gray-500"]),id:"global"},[(0,o._)("div",i,[(0,o._)("div",l,[(0,o._)("div",d,[(0,o._)("div",g,(0,a.zw)(_.header),1)]),(0,o._)("div",h,[((0,o.wg)(),(0,o.j4)((0,o.LL)(_.link?"a":"div"),{href:_.link,target:"_blank",style:{width:"100px"}},{default:(0,o.w5)((()=>[_.logo?((0,o.wg)(),(0,o.iD)("img",{key:0,src:_.logo,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},null,8,u)):((0,o.wg)(),(0,o.iD)("img",c))])),_:1},8,["href"]))])]),_.buttons?((0,o.wg)(),(0,o.iD)("div",p,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(_.buttons,(e=>((0,o.wg)(),(0,o.iD)("a",{key:e.name,href:e.link,target:"_blank",class:"px-2 py-0.5 font-medium select-none text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-400 hover:underline"},(0,a.zw)(e.name),9,m)))),128))])):(0,o.kq)("",!0)]),(0,o.Wm)(I,{onShowTooltip:_.showTooltip},null,8,["onShowTooltip"])],2)):((0,o.wg)(),(0,o.j4)(S,{key:0,class:"h-64 w-64 px-4"})),r.config&&r.config.oidc&&!r.config.authenticated?((0,o.wg)(),(0,o.iD)("div",v,[f,w,(0,o._)("div",x,[e.$route&&e.$route.query.error?((0,o.wg)(),(0,o.iD)("div",y,[(0,o._)("div",k,["access_denied"===e.$route.query.error?((0,o.wg)(),(0,o.iD)("span",T,"You do not have access to this status page")):((0,o.wg)(),(0,o.iD)("span",b,(0,a.zw)(e.$route.query.error),1))])])):(0,o.kq)("",!0),(0,o._)("div",null,[(0,o._)("a",{href:`${r.SERVER_URL}/oidc/login`,class:"max-w-lg mx-auto w-full flex justify-center py-3 px-4 border border-green-800 rounded-md shadow-lg text-sm text-white bg-green-700 bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800"}," Login with OIDC ",8,R)])])])):(0,o.kq)("",!0),(0,o.Wm)(D,{result:r.tooltip.result,event:r.tooltip.event},null,8,["result","event"]),(0,o.Wm)(A)],64)}const S=e=>((0,o.dD)("data-v-a4b3d200"),e=e(),(0,o.Cn)(),e),I={id:"social"},D=S((()=>(0,o._)("a",{href:"https://github.com/TwiN/gatus",target:"_blank",title:"Gatus on GitHub"},[(0,o._)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"32",height:"32",viewBox:"0 0 16 16",class:"hover:scale-110"},[(0,o._)("path",{fill:"gray",d:"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"})])],-1))),A=[D];function $(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",I,A)}var C={name:"Social"},P=s(3744);const E=(0,P.Z)(C,[["render",$],["__scopeId","data-v-a4b3d200"]]);var H=E;const L=(0,o._)("div",{class:"tooltip-title"},"Timestamp:",-1),U={id:"tooltip-timestamp"},W=(0,o._)("div",{class:"tooltip-title"},"Response time:",-1),M={id:"tooltip-response-time"},O=(0,o._)("div",{class:"tooltip-title"},"Conditions:",-1),j={id:"tooltip-conditions"},B=(0,o._)("br",null,null,-1),q={key:0,id:"tooltip-errors-container"},Y=(0,o._)("div",{class:"tooltip-title"},"Errors:",-1),z={id:"tooltip-errors"},N=(0,o._)("br",null,null,-1);function Z(e,t,s,n,r,i){return(0,o.wg)(),(0,o.iD)("div",{id:"tooltip",ref:"tooltip",class:(0,a.C_)(r.hidden?"invisible":""),style:(0,a.j5)("top:"+r.top+"px; left:"+r.left+"px")},[s.result?(0,o.WI)(e.$slots,"default",{key:0},(()=>[L,(0,o._)("code",U,(0,a.zw)(e.prettifyTimestamLine truncated
|
||||
(function(){"use strict";var e={3576:function(e,t,s){s.d(t,{L:function(){return cs}});s(7727);var n=s(9963),o=s(6252),a=s(3577),r=s.p+"img/logo.svg";const i={class:"mb-2"},l={class:"flex flex-wrap"},d={class:"w-3/4 text-left my-auto"},g={class:"text-3xl xl:text-5xl lg:text-4xl font-light"},h={class:"w-1/4 flex justify-end"},u=["src"],c={key:1,src:r,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},p={key:0,class:"flex flex-wrap"},m=["href"],v={key:2,class:"mx-auto max-w-md pt-12"},f=(0,o._)("img",{src:r,alt:"Gatus",class:"mx-auto",style:{"max-width":"160px","min-width":"50px","min-height":"50px"}},null,-1),w=(0,o._)("h2",{class:"mt-4 text-center text-4xl font-extrabold text-gray-800 dark:text-gray-200"}," Gatus ",-1),x={class:"py-7 px-4 rounded-sm sm:px-10"},y={key:0,class:"text-red-500 text-center mb-5"},k={class:"text-sm"},T={key:0,class:"text-red-500"},b={key:1,class:"text-red-500"},R=["href"];function _(e,t,s,n,r,_){const S=(0,o.up)("Loading"),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",h,[((0,o.wg)(),(0,o.j4)((0,o.LL)(_.link?"a":"div"),{href:_.link,target:"_blank",style:{width:"100px"}},{default:(0,o.w5)((()=>[_.logo?((0,o.wg)(),(0,o.iD)("img",{key:0,src:_.logo,alt:"Gatus",class:"object-scale-down",style:{"max-width":"100px","min-width":"50px","min-height":"50px"}},null,8,u)):((0,o.wg)(),(0,o.iD)("img",c))])),_:1},8,["href"]))])]),_.buttons?((0,o.wg)(),(0,o.iD)("div",p,[((0,o.wg)(!0),(0,o.iD)(o.HY,null,(0,o.Ko)(_.buttons,(e=>((0,o.wg)(),(0,o.iD)("a",{key:e.name,href:e.link,target:"_blank",class:"px-2 py-0.5 font-medium select-none text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-400 hover:underline"},(0,a.zw)(e.name),9,m)))),128))])):(0,o.kq)("",!0)]),(0,o.Wm)(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 $(e,t,s,n,a,r){return(0,o.wg)(),(0,o.iD)("div",D,A)}var C={name:"Social"},P=s(3744);const E=(0,P.Z)(C,[["render",$],["__scopeId","data-v-a4b3d200"]]);var H=E;const L=(0,o._)("div",{class:"tooltip-title"},"Timestamp:",-1),U={id:"tooltip-timestamp"},W=(0,o._)("div",{class:"tooltip-title"},"Response time:",-1),M={id:"tooltip-response-time"},O=(0,o._)("div",{class:"tooltip-title"},"Conditions:",-1),j={id:"tooltip-conditions"},q=(0,o._)("br",null,null,-1),B={key:0,id:"tooltip-errors-container"},Y=(0,o._)("div",{class:"tooltip-title"},"Errors:",-1),z={id:"tooltip-errors"},N=(0,o._)("br",null,null,-1);function Z(e,t,s,n,r,i){return(0,o.wg)(),(0,o.iD)("div",{id:"tooltip",ref:"tooltip",class:(0,a.C_)(r.hidden?"invisible":""),style:(0,a.j5)("top:"+r.top+"px; left:"+r.left+"px")},[s.result?(0,o.WI)(e.$slots,"default",{key:0},(()=>[L,(0,o._)("code",U,(0,a.zw)(e.prettifyTimestamLine truncated
|
||||
@@ -1,7 +1,7 @@
|
||||
(self["webpackChunkgatus"]=self["webpackChunkgatus"]||[]).push([[998],{2262:function(e,t,n){"use strict";n.d(t,{Bj:function(){return i},Fl:function(){return ze},IU:function(){return Ae},Jd:function(){return k},PG:function(){return Se},SU:function(){return Ue},Um:function(){return xe},WL:function(){return Be},X$:function(){return E},X3:function(){return Ee},XI:function(){return Le},Xl:function(){return Re},dq:function(){return Fe},iH:function(){return Me},j:function(){return C},lk:function(){return S},qj:function(){return _e},qq:function(){return b},yT:function(){return Oe}});var r=n(3577);let o;class i{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&o&&(this.parent=o,this.index=(o.scopes||(o.scopes=[])).push(this)-1)}run(e){if(this.active){const t=o;try{return o=this,e()}finally{o=t}}else 0}on(){o=this}off(){o=this.parent}stop(e){if(this.active){let t,n;for(t=0,n=this.effects.length;t<n;t++)this.effects[t].stop();for(t=0,n=this.cleanups.length;t<n;t++)this.cleanups[t]();if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].stop(!0);if(this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function s(e,t=o){t&&t.active&&t.effects.push(e)}const c=e=>{const t=new Set(e);return t.w=0,t.n=0,t},u=e=>(e.w&h)>0,l=e=>(e.n&h)>0,a=({deps:e})=>{if(e.length)for(let t=0;t<e.length;t++)e[t].w|=h},f=e=>{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r<t.length;r++){const o=t[r];u(o)&&!l(o)?o.delete(e):t[n++]=o,o.w&=~h,o.n&=~h}t.length=n}},p=new WeakMap;let d=0,h=1;const v=30;let g;const m=Symbol(""),y=Symbol("");class b{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],this.parent=void 0,s(this,n)}run(){if(!this.active)return this.fn();let e=g,t=x;while(e){if(e===this)return;e=e.parent}try{return this.parent=g,g=this,x=!0,h=1<<++d,d<=v?a(this):_(this),this.fn()}finally{d<=v&&f(this),h=1<<--d,g=this.parent,x=t,this.parent=void 0,this.deferStop&&this.stop()}}stop(){g===this?this.deferStop=!0:this.active&&(_(this),this.onStop&&this.onStop(),this.active=!1)}}function _(e){const{deps:t}=e;if(t.length){for(let n=0;n<t.length;n++)t[n].delete(e);t.length=0}}let x=!0;const w=[];function k(){w.push(x),x=!1}function S(){const e=w.pop();x=void 0===e||e}function C(e,t,n){if(x&&g){let t=p.get(e);t||p.set(e,t=new Map);let r=t.get(n);r||t.set(n,r=c());const o=void 0;O(r,o)}}function O(e,t){let n=!1;d<=v?l(e)||(e.n|=h,n=!u(e)):n=!e.has(g),n&&(e.add(g),g.deps.push(e))}function E(e,t,n,o,i,s){const u=p.get(e);if(!u)return;let l=[];if("clear"===t)l=[...u.values()];else if("length"===n&&(0,r.kJ)(e))u.forEach(((e,t)=>{("length"===t||t>=o)&&l.push(e)}));else switch(void 0!==n&&l.push(u.get(n)),t){case"add":(0,r.kJ)(e)?(0,r.S0)(n)&&l.push(u.get("length")):(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"delete":(0,r.kJ)(e)||(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"set":(0,r._N)(e)&&l.push(u.get(m));break}if(1===l.length)l[0]&&A(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);A(c(e))}}function A(e,t){const n=(0,r.kJ)(e)?e:[...e];for(const r of n)r.computed&&R(r,t);for(const r of n)r.computed||R(r,t)}function R(e,t){(e!==g||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const I=(0,r.fY)("__proto__,__v_isRef,__isVue"),P=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>"arguments"!==e&&"caller"!==e)).map((e=>Symbol[e])).filter(r.yk)),T=$(),j=$(!1,!0),F=$(!0),M=L();function L(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=Ae(this);for(let t=0,o=this.length;t<o;t++)C(n,"get",t+"");const r=n[t](...e);return-1===r||!1===r?n[t](...e.map(Ae)):r}})),["push","pop","shift","unshift","splice"].forEach((t=>{e[t]=function(...e){k();const n=Ae(this)[t].apply(this,e);return S(),n}})),e}function $(e=!1,t=!1){return function(n,o,i){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_isShallow"===o)return t;if("__v_raw"===o&&i===(e?t?me:ge:t?ve:he).get(n))return n;const s=(0,r.kJ)(n);if(!e&&s&&(0,r.RI)(M,o))return Reflect.get(M,o,i);const c=Reflect.get(n,o,i);return((0,r.yk)(o)?P.has(o):I(o))?c:(e||C(n,"get",o),t?c:Fe(c)?s&&(0,r.S0)(o)?c:c.value:(0,r.Kn)(c)?e?we(c):_e(c):c)}}const N=D(),U=D(!0);function D(e=!1){return function(t,n,o,i){let s=t[n];if(Ce(s)&&Fe(s)&&!Fe(o))return!1;if(!e&&!Ce(o)&&(Oe(o)||(o=Ae(o),s=Ae(s)),!(0,r.kJ)(t)&&Fe(s)&&!Fe(o)))return s.value=o,!0;const c=(0,r.kJ)(t)&&(0,r.S0)(n)?Number(n)<t.length:(0,r.RI)(t,n),u=Reflect.set(t,n,o,i);return t===Ae(i)&&(c?(0,r.aU)(o,s)&&E(t,"set",n,o,s):E(t,"add",n,o)),u}}function B(e,t){const n=(0,r.RI)(e,t),o=e[t],i=Reflect.deleteProperty(e,t);return i&&n&&E(e,"delete",t,void 0,o),i}function J(e,t){const n=Reflect.has(e,t);return(0,r.yk)(t)&&P.has(t)||C(e,"has",t),n}function z(e){return C(e,"iterate",(0,r.kJ)(e)?"length":m),Reflect.ownKeys(e)}const G={get:T,set:N,deleteProperty:B,has:J,ownKeys:z},V={get:F,set(e,t){return!0},deleteProperty(e,t){return!Line truncated
|
||||
(self["webpackChunkgatus"]=self["webpackChunkgatus"]||[]).push([[998],{2262:function(e,t,n){"use strict";n.d(t,{Bj:function(){return i},Fl:function(){return Ve},IU:function(){return Ae},Jd:function(){return k},PG:function(){return Se},SU:function(){return Ue},Um:function(){return xe},WL:function(){return Be},X$:function(){return E},X3:function(){return Ee},XI:function(){return Le},Xl:function(){return Re},dq:function(){return Fe},iH:function(){return Me},j:function(){return C},lk:function(){return S},qj:function(){return _e},qq:function(){return b},yT:function(){return Oe}});var r=n(3577);let o;class i{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&o&&(this.parent=o,this.index=(o.scopes||(o.scopes=[])).push(this)-1)}run(e){if(this.active){const t=o;try{return o=this,e()}finally{o=t}}else 0}on(){o=this}off(){o=this.parent}stop(e){if(this.active){let t,n;for(t=0,n=this.effects.length;t<n;t++)this.effects[t].stop();for(t=0,n=this.cleanups.length;t<n;t++)this.cleanups[t]();if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].stop(!0);if(this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function s(e,t=o){t&&t.active&&t.effects.push(e)}const c=e=>{const t=new Set(e);return t.w=0,t.n=0,t},u=e=>(e.w&h)>0,l=e=>(e.n&h)>0,a=({deps:e})=>{if(e.length)for(let t=0;t<e.length;t++)e[t].w|=h},f=e=>{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r<t.length;r++){const o=t[r];u(o)&&!l(o)?o.delete(e):t[n++]=o,o.w&=~h,o.n&=~h}t.length=n}},p=new WeakMap;let d=0,h=1;const v=30;let g;const m=Symbol(""),y=Symbol("");class b{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],this.parent=void 0,s(this,n)}run(){if(!this.active)return this.fn();let e=g,t=x;while(e){if(e===this)return;e=e.parent}try{return this.parent=g,g=this,x=!0,h=1<<++d,d<=v?a(this):_(this),this.fn()}finally{d<=v&&f(this),h=1<<--d,g=this.parent,x=t,this.parent=void 0,this.deferStop&&this.stop()}}stop(){g===this?this.deferStop=!0:this.active&&(_(this),this.onStop&&this.onStop(),this.active=!1)}}function _(e){const{deps:t}=e;if(t.length){for(let n=0;n<t.length;n++)t[n].delete(e);t.length=0}}let x=!0;const w=[];function k(){w.push(x),x=!1}function S(){const e=w.pop();x=void 0===e||e}function C(e,t,n){if(x&&g){let t=p.get(e);t||p.set(e,t=new Map);let r=t.get(n);r||t.set(n,r=c());const o=void 0;O(r,o)}}function O(e,t){let n=!1;d<=v?l(e)||(e.n|=h,n=!u(e)):n=!e.has(g),n&&(e.add(g),g.deps.push(e))}function E(e,t,n,o,i,s){const u=p.get(e);if(!u)return;let l=[];if("clear"===t)l=[...u.values()];else if("length"===n&&(0,r.kJ)(e))u.forEach(((e,t)=>{("length"===t||t>=o)&&l.push(e)}));else switch(void 0!==n&&l.push(u.get(n)),t){case"add":(0,r.kJ)(e)?(0,r.S0)(n)&&l.push(u.get("length")):(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"delete":(0,r.kJ)(e)||(l.push(u.get(m)),(0,r._N)(e)&&l.push(u.get(y)));break;case"set":(0,r._N)(e)&&l.push(u.get(m));break}if(1===l.length)l[0]&&A(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);A(c(e))}}function A(e,t){const n=(0,r.kJ)(e)?e:[...e];for(const r of n)r.computed&&R(r,t);for(const r of n)r.computed||R(r,t)}function R(e,t){(e!==g||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const I=(0,r.fY)("__proto__,__v_isRef,__isVue"),P=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>"arguments"!==e&&"caller"!==e)).map((e=>Symbol[e])).filter(r.yk)),T=$(),j=$(!1,!0),F=$(!0),M=L();function L(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=Ae(this);for(let t=0,o=this.length;t<o;t++)C(n,"get",t+"");const r=n[t](...e);return-1===r||!1===r?n[t](...e.map(Ae)):r}})),["push","pop","shift","unshift","splice"].forEach((t=>{e[t]=function(...e){k();const n=Ae(this)[t].apply(this,e);return S(),n}})),e}function $(e=!1,t=!1){return function(n,o,i){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_isShallow"===o)return t;if("__v_raw"===o&&i===(e?t?me:ge:t?ve:he).get(n))return n;const s=(0,r.kJ)(n);if(!e&&s&&(0,r.RI)(M,o))return Reflect.get(M,o,i);const c=Reflect.get(n,o,i);return((0,r.yk)(o)?P.has(o):I(o))?c:(e||C(n,"get",o),t?c:Fe(c)?s&&(0,r.S0)(o)?c:c.value:(0,r.Kn)(c)?e?we(c):_e(c):c)}}const N=D(),U=D(!0);function D(e=!1){return function(t,n,o,i){let s=t[n];if(Ce(s)&&Fe(s)&&!Fe(o))return!1;if(!e&&!Ce(o)&&(Oe(o)||(o=Ae(o),s=Ae(s)),!(0,r.kJ)(t)&&Fe(s)&&!Fe(o)))return s.value=o,!0;const c=(0,r.kJ)(t)&&(0,r.S0)(n)?Number(n)<t.length:(0,r.RI)(t,n),u=Reflect.set(t,n,o,i);return t===Ae(i)&&(c?(0,r.aU)(o,s)&&E(t,"set",n,o,s):E(t,"add",n,o)),u}}function B(e,t){const n=(0,r.RI)(e,t),o=e[t],i=Reflect.deleteProperty(e,t);return i&&n&&E(e,"delete",t,void 0,o),i}function J(e,t){const n=Reflect.has(e,t);return(0,r.yk)(t)&&P.has(t)||C(e,"has",t),n}function V(e){return C(e,"iterate",(0,r.kJ)(e)?"length":m),Reflect.ownKeys(e)}const z={get:T,set:N,deleteProperty:B,has:J,ownKeys:V},G={get:F,set(e,t){return!0},deleteProperty(e,t){return!Line truncated
|
||||
/*!
|
||||
* vue-router v4.0.16
|
||||
* (c) 2022 Eduardo San Martin Morote
|
||||
* @license MIT
|
||||
*/
|
||||
const i="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag,s=e=>i?Symbol(e):"_vr_"+e,c=s("rvlm"),u=s("rvd"),l=s("r"),a=s("rl"),f=s("rvl"),p="undefined"!==typeof window;function d(e){return e.__esModule||i&&"Module"===e[Symbol.toStringTag]}const h=Object.assign;function v(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}const g=()=>{};const m=/\/$/,y=e=>e.replace(m,"");function b(e,t,n="/"){let r,o={},i="",s="";const c=t.indexOf("?"),u=t.indexOf("#",c>-1?c:0);return c>-1&&(r=t.slice(0,c),i=t.slice(c+1,u>-1?u:t.length),o=e(i)),u>-1&&(r=r||t.slice(0,u),s=t.slice(u,t.length)),r=E(null!=r?r:t,n),{fullPath:r+(i&&"?")+i+s,path:r,query:o,hash:s}}function _(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function x(e,t){return t&&e.toLowerCase().startsWith(t.toLowerCase())?e.slice(t.length)||"/":e}function w(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&k(t.matched[r],n.matched[o])&&S(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function k(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function S(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!C(e[n],t[n]))return!1;return!0}function C(e,t){return Array.isArray(e)?O(e,t):Array.isArray(t)?O(t,e):e===t}function O(e,t){return Array.isArray(t)?e.length===t.length&&e.every(((e,n)=>e===t[n])):1===e.length&&e[0]===t}function E(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,i,s=n.length-1;for(o=0;o<r.length;o++)if(i=r[o],1!==s&&"."!==i){if(".."!==i)break;s--}return n.slice(0,s).join("/")+"/"+r.slice(o-(o===r.length?1:0)).join("/")}var A,R;(function(e){e["pop"]="pop",e["push"]="push"})(A||(A={})),function(e){e["back"]="back",e["forward"]="forward",e["unknown"]=""}(R||(R={}));function I(e){if(!e)if(p){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),y(e)}const P=/^[^#]+#/;function T(e,t){return e.replace(P,"#")+t}function j(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const F=()=>({left:window.pageXOffset,top:window.pageYOffset});function M(e){let t;if("el"in e){const n=e.el,r="string"===typeof n&&n.startsWith("#");0;const o="string"===typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=j(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function L(e,t){const n=history.state?history.state.position-t:-1;return n+e}const $=new Map;function N(e,t){$.set(e,t)}function U(e){const t=$.get(e);return $.delete(e),t}let D=()=>location.protocol+"//"+location.host;function B(e,t){const{pathname:n,search:r,hash:o}=t,i=e.indexOf("#");if(i>-1){let t=o.includes(e.slice(i))?e.slice(i).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),x(n,"")}const s=x(n,e);return s+r+o}function J(e,t,n,r){let o=[],i=[],s=null;const c=({state:i})=>{const c=B(e,location),u=n.value,l=t.value;let a=0;if(i){if(n.value=c,t.value=i,s&&s===u)return void(s=null);a=l?i.position-l.position:0}else r(c);o.forEach((e=>{e(n.value,u,{delta:a,type:A.pop,direction:a?a>0?R.forward:R.back:R.unknown})}))};function u(){s=n.value}function l(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return i.push(t),t}function a(){const{history:e}=window;e.state&&e.replaceState(h({},e.state,{scroll:F()}),"")}function f(){for(const e of i)e();i=[],window.removeEventListener("popstate",c),window.removeEventListener("beforeunload",a)}return window.addEventListener("popstate",c),window.addEventListener("beforeunload",a),{pauseListeners:u,listen:l,destroy:f}}function z(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?F():null}}function G(e){const{history:t,location:n}=window,r={value:B(e,n)},o={value:t.state};function i(r,i,s){const c=e.indexOf("#"),u=c>-1?(n.host&&document.querySelector("base")?e:e.slice(c))+r:D()+e+r;try{t[s?"replaceState":"pushState"](i,"",u),o.value=i}catch(l){console.error(l),n[s?"replace":"assign"](u)}}function s(e,n){const s=h({},t.state,z(o.value.back,e,o.value.forward,!0),n,{position:o.value.position});i(e,s,!0),r.value=e}function c(e,n){const s=h({},o.value,t.state,{forward:e,scroll:F()});i(s.current,s,!0);const c=h({},z(r.value,e,null),{position:s.position+1},n);i(e,c,!1),r.value=e}return o.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:c,replace:s}}function V(e){e=I(e);const t=G(e),n=J(e,t.state,t.location,t.replace);function r(e,t=!0){t||n.pauseListeners(),history.go(e)}const o=h({location:"",base:e,go:r,createHref:T.bind(null,e)},t,n);return Object.defineProperty(o,"loLine truncated
|
||||
const i="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag,s=e=>i?Symbol(e):"_vr_"+e,c=s("rvlm"),u=s("rvd"),l=s("r"),a=s("rl"),f=s("rvl"),p="undefined"!==typeof window;function d(e){return e.__esModule||i&&"Module"===e[Symbol.toStringTag]}const h=Object.assign;function v(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}const g=()=>{};const m=/\/$/,y=e=>e.replace(m,"");function b(e,t,n="/"){let r,o={},i="",s="";const c=t.indexOf("?"),u=t.indexOf("#",c>-1?c:0);return c>-1&&(r=t.slice(0,c),i=t.slice(c+1,u>-1?u:t.length),o=e(i)),u>-1&&(r=r||t.slice(0,u),s=t.slice(u,t.length)),r=E(null!=r?r:t,n),{fullPath:r+(i&&"?")+i+s,path:r,query:o,hash:s}}function _(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function x(e,t){return t&&e.toLowerCase().startsWith(t.toLowerCase())?e.slice(t.length)||"/":e}function w(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&k(t.matched[r],n.matched[o])&&S(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function k(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function S(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!C(e[n],t[n]))return!1;return!0}function C(e,t){return Array.isArray(e)?O(e,t):Array.isArray(t)?O(t,e):e===t}function O(e,t){return Array.isArray(t)?e.length===t.length&&e.every(((e,n)=>e===t[n])):1===e.length&&e[0]===t}function E(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,i,s=n.length-1;for(o=0;o<r.length;o++)if(i=r[o],1!==s&&"."!==i){if(".."!==i)break;s--}return n.slice(0,s).join("/")+"/"+r.slice(o-(o===r.length?1:0)).join("/")}var A,R;(function(e){e["pop"]="pop",e["push"]="push"})(A||(A={})),function(e){e["back"]="back",e["forward"]="forward",e["unknown"]=""}(R||(R={}));function I(e){if(!e)if(p){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),y(e)}const P=/^[^#]+#/;function T(e,t){return e.replace(P,"#")+t}function j(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const F=()=>({left:window.pageXOffset,top:window.pageYOffset});function M(e){let t;if("el"in e){const n=e.el,r="string"===typeof n&&n.startsWith("#");0;const o="string"===typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=j(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function L(e,t){const n=history.state?history.state.position-t:-1;return n+e}const $=new Map;function N(e,t){$.set(e,t)}function U(e){const t=$.get(e);return $.delete(e),t}let D=()=>location.protocol+"//"+location.host;function B(e,t){const{pathname:n,search:r,hash:o}=t,i=e.indexOf("#");if(i>-1){let t=o.includes(e.slice(i))?e.slice(i).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),x(n,"")}const s=x(n,e);return s+r+o}function J(e,t,n,r){let o=[],i=[],s=null;const c=({state:i})=>{const c=B(e,location),u=n.value,l=t.value;let a=0;if(i){if(n.value=c,t.value=i,s&&s===u)return void(s=null);a=l?i.position-l.position:0}else r(c);o.forEach((e=>{e(n.value,u,{delta:a,type:A.pop,direction:a?a>0?R.forward:R.back:R.unknown})}))};function u(){s=n.value}function l(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return i.push(t),t}function a(){const{history:e}=window;e.state&&e.replaceState(h({},e.state,{scroll:F()}),"")}function f(){for(const e of i)e();i=[],window.removeEventListener("popstate",c),window.removeEventListener("beforeunload",a)}return window.addEventListener("popstate",c),window.addEventListener("beforeunload",a),{pauseListeners:u,listen:l,destroy:f}}function V(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?F():null}}function z(e){const{history:t,location:n}=window,r={value:B(e,n)},o={value:t.state};function i(r,i,s){const c=e.indexOf("#"),u=c>-1?(n.host&&document.querySelector("base")?e:e.slice(c))+r:D()+e+r;try{t[s?"replaceState":"pushState"](i,"",u),o.value=i}catch(l){console.error(l),n[s?"replace":"assign"](u)}}function s(e,n){const s=h({},t.state,V(o.value.back,e,o.value.forward,!0),n,{position:o.value.position});i(e,s,!0),r.value=e}function c(e,n){const s=h({},o.value,t.state,{forward:e,scroll:F()});i(s.current,s,!0);const c=h({},V(r.value,e,null),{position:s.position+1},n);i(e,c,!1),r.value=e}return o.value||i(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:c,replace:s}}function G(e){e=I(e);const t=z(e),n=J(e,t.state,t.location,t.replace);function r(e,t=!0){t||n.pauseListeners(),history.go(e)}const o=h({location:"",base:e,go:r,createHref:T.bind(null,e)},t,n);return Object.defineProperty(o,"loLine truncated
|
||||
Reference in new issue
Block a user