This commit is contained in:
2025-04-04 19:06:29 -07:00
parent ebb45b13bb
commit 393381d456
275 changed files with 56094 additions and 2 deletions

112
config/ui/ui.go Normal file
View File

@ -0,0 +1,112 @@
package ui
import (
"bytes"
"errors"
"html/template"
static "github.com/TwiN/gatus/v5/web"
)
const (
defaultTitle = "Health Dashboard | Gatus"
defaultDescription = "Gatus is an advanced automated status page that lets you monitor your applications and configure alerts to notify you if there's an issue"
defaultHeader = "Health Status"
defaultLogo = ""
defaultLink = ""
defaultCustomCSS = ""
)
var (
defaultDarkMode = true
ErrButtonValidationFailed = errors.New("invalid button configuration: missing required name or link")
)
// Config is the configuration for the UI of Gatus
type Config struct {
Title string `yaml:"title,omitempty"` // Title of the page
Description string `yaml:"description,omitempty"` // Meta description of the page
Header string `yaml:"header,omitempty"` // Header is the text at the top of the page
Logo string `yaml:"logo,omitempty"` // Logo to display on the page
Link string `yaml:"link,omitempty"` // Link to open when clicking on the logo
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
}
func (cfg *Config) IsDarkMode() bool {
if cfg.DarkMode != nil {
return *cfg.DarkMode
}
return defaultDarkMode
}
// Button is the configuration for a button on the UI
type Button struct {
Name string `yaml:"name,omitempty"` // Name is the text to display on the button
Link string `yaml:"link,omitempty"` // Link to open when the button is clicked.
}
// Validate validates the button configuration
func (btn *Button) Validate() error {
if len(btn.Name) == 0 || len(btn.Link) == 0 {
return ErrButtonValidationFailed
}
return nil
}
// 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,
}
}
// ValidateAndSetDefaults validates the UI configuration and sets the default values if necessary.
func (cfg *Config) ValidateAndSetDefaults() error {
if len(cfg.Title) == 0 {
cfg.Title = defaultTitle
}
if len(cfg.Description) == 0 {
cfg.Description = defaultDescription
}
if len(cfg.Header) == 0 {
cfg.Header = defaultHeader
}
if len(cfg.Logo) == 0 {
cfg.Logo = defaultLogo
}
if len(cfg.Link) == 0 {
cfg.Link = defaultLink
}
if len(cfg.CustomCSS) == 0 {
cfg.CustomCSS = defaultCustomCSS
}
if cfg.DarkMode == nil {
cfg.DarkMode = &defaultDarkMode
}
for _, btn := range cfg.Buttons {
if err := btn.Validate(); err != nil {
return err
}
}
// Validate that the template works
t, err := template.ParseFS(static.FileSystem, static.IndexPath)
if err != nil {
return err
}
var buffer bytes.Buffer
return t.Execute(&buffer, ViewData{UI: cfg, Theme: "dark"})
}
type ViewData struct {
UI *Config
Theme string
}

77
config/ui/ui_test.go Normal file
View File

@ -0,0 +1,77 @@
package ui
import (
"strconv"
"testing"
)
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
cfg := &Config{
Title: "",
Description: "",
Header: "",
Logo: "",
Link: "",
}
if err := cfg.ValidateAndSetDefaults(); err != nil {
t.Error("expected no error, got", err.Error())
}
if cfg.Title != defaultTitle {
t.Errorf("expected title to be %s, got %s", defaultTitle, cfg.Title)
}
if cfg.Description != defaultDescription {
t.Errorf("expected description to be %s, got %s", defaultDescription, cfg.Description)
}
if cfg.Header != defaultHeader {
t.Errorf("expected header to be %s, got %s", defaultHeader, cfg.Header)
}
}
func TestButton_Validate(t *testing.T) {
scenarios := []struct {
Name, Link string
ExpectedError error
}{
{
Name: "",
Link: "",
ExpectedError: ErrButtonValidationFailed,
},
{
Name: "",
Link: "link",
ExpectedError: ErrButtonValidationFailed,
},
{
Name: "name",
Link: "",
ExpectedError: ErrButtonValidationFailed,
},
{
Name: "name",
Link: "link",
ExpectedError: nil,
},
}
for i, scenario := range scenarios {
t.Run(strconv.Itoa(i)+"_"+scenario.Name+"_"+scenario.Link, func(t *testing.T) {
button := &Button{
Name: scenario.Name,
Link: scenario.Link,
}
if err := button.Validate(); err != scenario.ExpectedError {
t.Errorf("expected error %v, got %v", scenario.ExpectedError, err)
}
})
}
}
func TestGetDefaultConfig(t *testing.T) {
defaultConfig := GetDefaultConfig()
if defaultConfig.Title != defaultTitle {
t.Error("expected GetDefaultConfig() to return defaultTitle, got", defaultConfig.Title)
}
if defaultConfig.Logo != defaultLogo {
t.Error("expected GetDefaultConfig() to return defaultLogo, got", defaultConfig.Logo)
}
}