mirror of
https://github.com/TwiN/gatus.git
synced 2024-12-22 23:02:22 +01:00
Remove web.context-root
This commit is contained in:
parent
d8d8e8720b
commit
a1679ddc5e
@ -151,7 +151,6 @@ Note that you can also add environment variables in the configuration file (i.e.
|
|||||||
| `web` | Web configuration | `{}` |
|
| `web` | Web configuration | `{}` |
|
||||||
| `web.address` | Address to listen on | `0.0.0.0` |
|
| `web.address` | Address to listen on | `0.0.0.0` |
|
||||||
| `web.port` | Port to listen on | `8080` |
|
| `web.port` | Port to listen on | `8080` |
|
||||||
| `web.context-root` | Context root at which Gatus will be exposed (frontend and backend) | `/` |
|
|
||||||
|
|
||||||
For Kubernetes configuration, see [Kubernetes](#kubernetes-alpha)
|
For Kubernetes configuration, see [Kubernetes](#kubernetes-alpha)
|
||||||
|
|
||||||
|
@ -28,9 +28,6 @@ const (
|
|||||||
|
|
||||||
// DefaultPort is the default port the service will listen on
|
// DefaultPort is the default port the service will listen on
|
||||||
DefaultPort = 8080
|
DefaultPort = 8080
|
||||||
|
|
||||||
// DefaultContextRoot is the default context root of the web application
|
|
||||||
DefaultContextRoot = "/"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -153,7 +150,7 @@ func parseAndValidateConfigBytes(yamlBytes []byte) (config *Config, err error) {
|
|||||||
|
|
||||||
func validateWebConfig(config *Config) {
|
func validateWebConfig(config *Config) {
|
||||||
if config.Web == nil {
|
if config.Web == nil {
|
||||||
config.Web = &webConfig{Address: DefaultAddress, Port: DefaultPort, ContextRoot: DefaultContextRoot}
|
config.Web = &webConfig{Address: DefaultAddress, Port: DefaultPort}
|
||||||
} else {
|
} else {
|
||||||
config.Web.validateAndSetDefaults()
|
config.Web.validateAndSetDefaults()
|
||||||
}
|
}
|
||||||
|
@ -119,9 +119,6 @@ services:
|
|||||||
if config.Web.Port != DefaultPort {
|
if config.Web.Port != DefaultPort {
|
||||||
t.Errorf("Port should have been %d, because it is the default value", DefaultPort)
|
t.Errorf("Port should have been %d, because it is the default value", DefaultPort)
|
||||||
}
|
}
|
||||||
if config.Web.ContextRoot != DefaultContextRoot {
|
|
||||||
t.Errorf("ContextRoot should have been %s, because it is the default value", DefaultContextRoot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseAndValidateConfigBytesWithAddress(t *testing.T) {
|
func TestParseAndValidateConfigBytesWithAddress(t *testing.T) {
|
||||||
@ -226,44 +223,6 @@ services:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseAndValidateConfigBytesWithPortAndHostAndContextRoot(t *testing.T) {
|
|
||||||
config, err := parseAndValidateConfigBytes([]byte(`
|
|
||||||
web:
|
|
||||||
port: 12345
|
|
||||||
address: 127.0.0.1
|
|
||||||
context-root: /deeply/nested/down=/their
|
|
||||||
services:
|
|
||||||
- name: twinnation
|
|
||||||
url: https://twinnation.org/health
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
`))
|
|
||||||
if err != nil {
|
|
||||||
t.Error("No error should've been returned")
|
|
||||||
}
|
|
||||||
if config == nil {
|
|
||||||
t.Fatal("Config shouldn't have been nil")
|
|
||||||
}
|
|
||||||
if config.Metrics {
|
|
||||||
t.Error("Metrics should've been false by default")
|
|
||||||
}
|
|
||||||
if config.Services[0].URL != "https://twinnation.org/health" {
|
|
||||||
t.Errorf("URL should have been %s", "https://twinnation.org/health")
|
|
||||||
}
|
|
||||||
if config.Services[0].Interval != 60*time.Second {
|
|
||||||
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
|
|
||||||
}
|
|
||||||
if config.Web.Address != "127.0.0.1" {
|
|
||||||
t.Errorf("Bind address should have been %s, because it is specified in config", "127.0.0.1")
|
|
||||||
}
|
|
||||||
if config.Web.Port != 12345 {
|
|
||||||
t.Errorf("Port should have been %d, because it is specified in config", 12345)
|
|
||||||
}
|
|
||||||
if config.Web.ContextRoot != "/deeply/nested/down=/their/" {
|
|
||||||
t.Errorf("Port should have been %s, because it is specified in config", "/deeply/nested/down=/their/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseAndValidateConfigBytesWithInvalidPort(t *testing.T) {
|
func TestParseAndValidateConfigBytesWithInvalidPort(t *testing.T) {
|
||||||
defer func() { recover() }()
|
defer func() { recover() }()
|
||||||
_, _ = parseAndValidateConfigBytes([]byte(`
|
_, _ = parseAndValidateConfigBytes([]byte(`
|
||||||
@ -311,9 +270,6 @@ services:
|
|||||||
if config.Web.Port != DefaultPort {
|
if config.Web.Port != DefaultPort {
|
||||||
t.Errorf("Port should have been %d, because it is the default value", DefaultPort)
|
t.Errorf("Port should have been %d, because it is the default value", DefaultPort)
|
||||||
}
|
}
|
||||||
if config.Web.ContextRoot != DefaultContextRoot {
|
|
||||||
t.Errorf("ContextRoot should have been %s, because it is the default value", DefaultContextRoot)
|
|
||||||
}
|
|
||||||
if userAgent := config.Services[0].Headers["User-Agent"]; userAgent != "Test/2.0" {
|
if userAgent := config.Services[0].Headers["User-Agent"]; userAgent != "Test/2.0" {
|
||||||
t.Errorf("User-Agent should've been %s, got %s", "Test/2.0", userAgent)
|
t.Errorf("User-Agent should've been %s, got %s", "Test/2.0", userAgent)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// webConfig is the structure which supports the configuration of the endpoint
|
// webConfig is the structure which supports the configuration of the endpoint
|
||||||
@ -15,9 +13,6 @@ type webConfig struct {
|
|||||||
|
|
||||||
// Port to listen on (default to 8080 specified by DefaultPort)
|
// Port to listen on (default to 8080 specified by DefaultPort)
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
|
|
||||||
// ContextRoot set the root context for the web application
|
|
||||||
ContextRoot string `yaml:"context-root"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateAndSetDefaults checks and sets the default values for fields that are not set
|
// validateAndSetDefaults checks and sets the default values for fields that are not set
|
||||||
@ -32,32 +27,9 @@ func (web *webConfig) validateAndSetDefaults() {
|
|||||||
} else if web.Port < 0 || web.Port > math.MaxUint16 {
|
} else if web.Port < 0 || web.Port > math.MaxUint16 {
|
||||||
panic(fmt.Sprintf("invalid port: value should be between %d and %d", 0, math.MaxUint16))
|
panic(fmt.Sprintf("invalid port: value should be between %d and %d", 0, math.MaxUint16))
|
||||||
}
|
}
|
||||||
// Validate the ContextRoot
|
|
||||||
if len(web.ContextRoot) == 0 {
|
|
||||||
web.ContextRoot = DefaultContextRoot
|
|
||||||
} else {
|
|
||||||
trimmedContextRoot := strings.Trim(web.ContextRoot, "/")
|
|
||||||
if len(trimmedContextRoot) == 0 {
|
|
||||||
web.ContextRoot = DefaultContextRoot
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rootContextURL, err := url.Parse(trimmedContextRoot)
|
|
||||||
if err != nil {
|
|
||||||
panic("invalid context root:" + err.Error())
|
|
||||||
}
|
|
||||||
if rootContextURL.Path != trimmedContextRoot {
|
|
||||||
panic("invalid context root: too complex")
|
|
||||||
}
|
|
||||||
web.ContextRoot = "/" + strings.Trim(rootContextURL.Path, "/") + "/"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketAddress returns the combination of the Address and the Port
|
// SocketAddress returns the combination of the Address and the Port
|
||||||
func (web *webConfig) SocketAddress() string {
|
func (web *webConfig) SocketAddress() string {
|
||||||
return fmt.Sprintf("%s:%d", web.Address, web.Port)
|
return fmt.Sprintf("%s:%d", web.Address, web.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrependWithContextRoot appends the given path to the ContextRoot
|
|
||||||
func (web *webConfig) PrependWithContextRoot(path string) string {
|
|
||||||
return web.ContextRoot + strings.Trim(path, "/")
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,86 +13,3 @@ func TestWebConfig_SocketAddress(t *testing.T) {
|
|||||||
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
|
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebConfig_PrependWithContextRoot(t *testing.T) {
|
|
||||||
web := &webConfig{ContextRoot: "/status/"}
|
|
||||||
if result := web.PrependWithContextRoot("/api/v1/results"); result != "/status/api/v1/results" {
|
|
||||||
t.Errorf("expected %s, got %s", "/status/api/v1/results", result)
|
|
||||||
}
|
|
||||||
if result := web.PrependWithContextRoot("/health"); result != "/status/health" {
|
|
||||||
t.Errorf("expected %s, got %s", "/status/health", result)
|
|
||||||
}
|
|
||||||
if result := web.PrependWithContextRoot("/health/"); result != "/status/health" {
|
|
||||||
t.Errorf("expected %s, got %s", "/status/health", result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validContextRootTest specifies all test case which should end up in
|
|
||||||
// a valid context root used to bind the web interface to
|
|
||||||
var validContextRootTests = []struct {
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
expectedPath string
|
|
||||||
}{
|
|
||||||
{"Empty", "", "/"},
|
|
||||||
{"/", "/", "/"},
|
|
||||||
{"///", "///", "/"},
|
|
||||||
{"Single character 'a'", "a", "/a/"},
|
|
||||||
{"Slash at the beginning", "/status", "/status/"},
|
|
||||||
{"Slashes at start and end", "/status/", "/status/"},
|
|
||||||
{"Multiple slashes at start", "//status", "/status/"},
|
|
||||||
{"Multiple slashes at start and end", "///status////", "/status/"},
|
|
||||||
{"Contains '@' in path'", "me@/status/gatus", "/me@/status/gatus/"},
|
|
||||||
{"Nested context with trailing slash", "/status/gatus/", "/status/gatus/"},
|
|
||||||
{"Nested context without trailing slash", "/status/gatus/system", "/status/gatus/system/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebConfig_ValidContextRoots(t *testing.T) {
|
|
||||||
for idx, test := range validContextRootTests {
|
|
||||||
t.Run(fmt.Sprintf("%d: %s", idx, test.name), func(t *testing.T) {
|
|
||||||
expectValidResultForContextRoot(t, test.path, test.expectedPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectValidResultForContextRoot(t *testing.T, path string, expected string) {
|
|
||||||
web := &webConfig{
|
|
||||||
ContextRoot: path,
|
|
||||||
}
|
|
||||||
web.validateAndSetDefaults()
|
|
||||||
if web.ContextRoot != expected {
|
|
||||||
t.Errorf("expected %s, got %s", expected, web.ContextRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalidContextRootTests contains all tests for context root which are
|
|
||||||
// expected to fail and stop program execution
|
|
||||||
var invalidContextRootTests = []struct {
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
}{
|
|
||||||
{"Only a fragment identifier", "#"},
|
|
||||||
{"Invalid character in path", "/invalid" + string([]byte{0x7F})},
|
|
||||||
{"Starts with protocol", "http://status/gatus"},
|
|
||||||
{"Path with fragment", "/status/gatus#here"},
|
|
||||||
{"Starts with '://'", "://status"},
|
|
||||||
{"Contains query parameter", "/status/h?ello=world"},
|
|
||||||
{"Contains '?'", "/status?"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebConfig_InvalidContextRoots(t *testing.T) {
|
|
||||||
for idx, test := range invalidContextRootTests {
|
|
||||||
t.Run(fmt.Sprintf("%d: %s", idx, test.name), func(t *testing.T) {
|
|
||||||
expectInvalidResultForContextRoot(t, test.path)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectInvalidResultForContextRoot(t *testing.T, path string) {
|
|
||||||
defer func() { recover() }()
|
|
||||||
|
|
||||||
web := &webConfig{ContextRoot: path}
|
|
||||||
web.validateAndSetDefaults()
|
|
||||||
|
|
||||||
t.Fatal(fmt.Sprintf("Should've panicked because the configuration specifies an invalid context root: %s", path))
|
|
||||||
}
|
|
||||||
|
@ -47,7 +47,7 @@ func Handle() {
|
|||||||
WriteTimeout: 15 * time.Second,
|
WriteTimeout: 15 * time.Second,
|
||||||
IdleTimeout: 15 * time.Second,
|
IdleTimeout: 15 * time.Second,
|
||||||
}
|
}
|
||||||
log.Printf("[controller][Handle] Listening on %s%s\n", cfg.Web.SocketAddress(), cfg.Web.ContextRoot)
|
log.Println("[controller][Handle] Listening on" + cfg.Web.SocketAddress())
|
||||||
log.Fatal(server.ListenAndServe())
|
log.Fatal(server.ListenAndServe())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,13 +56,13 @@ func CreateRouter(cfg *config.Config) *mux.Router {
|
|||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.HandleFunc("/favicon.ico", favIconHandler).Methods("GET") // favicon needs to be always served from the root
|
router.HandleFunc("/favicon.ico", favIconHandler).Methods("GET") // favicon needs to be always served from the root
|
||||||
router.HandleFunc("/services/{service}", spaHandler).Methods("GET")
|
router.HandleFunc("/services/{service}", spaHandler).Methods("GET")
|
||||||
router.HandleFunc(cfg.Web.PrependWithContextRoot("/api/v1/statuses"), secureIfNecessary(cfg, serviceStatusesHandler)).Methods("GET")
|
router.HandleFunc("/api/v1/statuses", secureIfNecessary(cfg, serviceStatusesHandler)).Methods("GET")
|
||||||
router.HandleFunc(cfg.Web.PrependWithContextRoot("/api/v1/statuses/{key}"), secureIfNecessary(cfg, GzipHandlerFunc(serviceStatusHandler))).Methods("GET")
|
router.HandleFunc("/api/v1/statuses/{key}", secureIfNecessary(cfg, GzipHandlerFunc(serviceStatusHandler))).Methods("GET")
|
||||||
router.HandleFunc(cfg.Web.PrependWithContextRoot("/api/v1/badges/uptime/{duration}/{identifier}"), badgeHandler).Methods("GET")
|
router.HandleFunc("/api/v1/badges/uptime/{duration}/{identifier}", badgeHandler).Methods("GET")
|
||||||
router.HandleFunc(cfg.Web.PrependWithContextRoot("/health"), healthHandler).Methods("GET")
|
router.HandleFunc("/health", healthHandler).Methods("GET")
|
||||||
router.PathPrefix(cfg.Web.ContextRoot).Handler(GzipHandler(http.StripPrefix(cfg.Web.ContextRoot, http.FileServer(http.Dir("./web/static")))))
|
router.PathPrefix("/").Handler(GzipHandler(http.FileServer(http.Dir("./web/static"))))
|
||||||
if cfg.Metrics {
|
if cfg.Metrics {
|
||||||
router.Handle(cfg.Web.PrependWithContextRoot("/metrics"), promhttp.Handler()).Methods("GET")
|
router.Handle("/metrics", promhttp.Handler()).Methods("GET")
|
||||||
}
|
}
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ export default {
|
|||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
return '/';
|
return '/';
|
||||||
}
|
}
|
||||||
return '/services/' + this.data.key;
|
return `/services/${this.data.key}`;
|
||||||
},
|
},
|
||||||
showTooltip(result, event) {
|
showTooltip(result, event) {
|
||||||
this.$emit('showTooltip', result, event);
|
this.$emit('showTooltip', result, event);
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import {createRouter, createWebHistory} from 'vue-router'
|
||||||
import Home from '../views/Home.vue'
|
import Home from '@/views/Home'
|
||||||
import Details from "@/views/Details";
|
import Details from "@/views/Details";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: Home
|
component: Home
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/services/:key',
|
path: '/services/:key',
|
||||||
name: 'Details',
|
name: 'Details',
|
||||||
component: Details
|
component: Details,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
history: createWebHistory(process.env.BASE_URL),
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link to="/" class="absolute top-2 left-2 inline-block px-2 py-0 text-lg text-black transition bg-gray-100 rounded shadow ripple hover:shadow-lg hover:bg-gray-200 focus:outline-none">
|
<router-link to="../" class="absolute top-2 left-2 inline-block px-2 py-0 text-lg text-black transition bg-gray-100 rounded shadow ripple hover:shadow-lg hover:bg-gray-200 focus:outline-none">
|
||||||
←
|
←
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="container mx-auto">
|
<div class="container mx-auto">
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
filenameHashing: false,
|
filenameHashing: false,
|
||||||
productionSourceMap: false,
|
productionSourceMap: false,
|
||||||
outputDir: '../static'
|
outputDir: '../static',
|
||||||
|
publicPath: '/'
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user