Skip to content

Commit 0740aaa

Browse files
committed
refactor(ui): Simplify json option templating
1 parent 15a8055 commit 0740aaa

File tree

3 files changed

+57
-32
lines changed

3 files changed

+57
-32
lines changed

api/spa.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package api
22

33
import (
44
_ "embed"
5-
"html/template"
65

76
"github.com/TwiN/gatus/v5/config/ui"
8-
static "github.com/TwiN/gatus/v5/web"
97
"github.com/TwiN/logr"
108
"github.com/gofiber/fiber/v2"
119
)
@@ -23,7 +21,7 @@ func SinglePageApplication(uiConfig *ui.Config) fiber.Handler {
2321
vd.Theme = "dark"
2422
}
2523
}
26-
t, err := template.ParseFS(static.FileSystem, static.IndexPath)
24+
t, err := ui.GetTemplate()
2725
if err != nil {
2826
// This should never happen, because ui.ValidateAndSetDefaults validates that the template works.
2927
logr.Errorf("[api.SinglePageApplication] Failed to parse template. This should never happen, because the template is validated on start. Error: %s", err.Error())

config/ui/ui.go

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ui
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"errors"
67
"html/template"
78

@@ -10,16 +11,16 @@ import (
1011
)
1112

1213
const (
13-
defaultTitle = "Health Dashboard | Gatus"
14-
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"
15-
defaultHeader = "Gatus"
16-
defaultDashboardHeading = "Health Dashboard"
17-
defaultDashboardSubheading = "Monitor the health of your endpoints in real-time"
18-
defaultLogo = ""
19-
defaultLink = ""
20-
defaultCustomCSS = ""
21-
defaultSortBy = "name"
22-
defaultFilterBy = "none"
14+
defaultTitle = "Health Dashboard | Gatus"
15+
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"
16+
defaultHeader = "Gatus"
17+
defaultDashboardHeading = "Health Dashboard"
18+
defaultDashboardSubheading = "Monitor the health of your endpoints in real-time"
19+
defaultLogo = ""
20+
defaultLink = ""
21+
defaultCustomCSS = ""
22+
defaultSortBy = "name"
23+
defaultFilterBy = "none"
2324
)
2425

2526
var (
@@ -28,26 +29,28 @@ var (
2829
ErrButtonValidationFailed = errors.New("invalid button configuration: missing required name or link")
2930
ErrInvalidDefaultSortBy = errors.New("invalid default-sort-by value: must be 'name', 'group', or 'health'")
3031
ErrInvalidDefaultFilterBy = errors.New("invalid default-filter-by value: must be 'none', 'failing', or 'unstable'")
32+
33+
uiTemplate *template.Template = nil
3134
)
3235

3336
// Config is the configuration for the UI of Gatus
3437
type Config struct {
35-
Title string `yaml:"title,omitempty"` // Title of the page
36-
Description string `yaml:"description,omitempty"` // Meta description of the page
37-
DashboardHeading string `yaml:"dashboard-heading,omitempty"` // Dashboard Title between header and endpoints
38-
DashboardSubheading string `yaml:"dashboard-subheading,omitempty"` // Dashboard Description between header and endpoints
39-
Header string `yaml:"header,omitempty"` // Header is the text at the top of the page
40-
Logo string `yaml:"logo,omitempty"` // Logo to display on the page
41-
Link string `yaml:"link,omitempty"` // Link to open when clicking on the logo
42-
Buttons []Button `yaml:"buttons,omitempty"` // Buttons to display below the header
43-
CustomCSS string `yaml:"custom-css,omitempty"` // Custom CSS to include in the page
44-
DarkMode *bool `yaml:"dark-mode,omitempty"` // DarkMode is a flag to enable dark mode by default
45-
DefaultSortBy string `yaml:"default-sort-by,omitempty"` // DefaultSortBy is the default sort option ('name', 'group', 'health')
46-
DefaultFilterBy string `yaml:"default-filter-by,omitempty"` // DefaultFilterBy is the default filter option ('none', 'failing', 'unstable')
38+
Title string `yaml:"title,omitempty" json:"-"` // Title of the page
39+
Description string `yaml:"description,omitempty" json:"-"` // Meta description of the page
40+
DashboardHeading string `yaml:"dashboard-heading,omitempty" json:"dashboardHeading,omitempty"` // Dashboard Title between header and endpoints
41+
DashboardSubheading string `yaml:"dashboard-subheading,omitempty" json:"dashboardSubheading,omitempty"` // Dashboard Description between header and endpoints
42+
Header string `yaml:"header,omitempty" json:"header,omitempty"` // Header is the text at the top of the page
43+
Logo string `yaml:"logo,omitempty" json:"logo,omitempty"` // Logo to display on the page
44+
Link string `yaml:"link,omitempty" json:"link,omitempty"` // Link to open when clicking on the logo
45+
Buttons []Button `yaml:"buttons,omitempty" json:"buttons,omitempty"` // Buttons to display below the header
46+
CustomCSS string `yaml:"custom-css,omitempty" json:"-"` // Custom CSS to include in the page
47+
DarkMode *bool `yaml:"dark-mode,omitempty" json:"-"` // DarkMode is a flag to enable dark mode by default
48+
DefaultSortBy string `yaml:"default-sort-by,omitempty" json:"defaultSortBy,omitempty"` // DefaultSortBy is the default sort option ('name', 'group', 'health')
49+
DefaultFilterBy string `yaml:"default-filter-by,omitempty" json:"defaultFilterBy,omitempty"` // DefaultFilterBy is the default filter option ('none', 'failing', 'unstable')
4750
//////////////////////////////////////////////
4851
// Non-configurable - used for UI rendering //
4952
//////////////////////////////////////////////
50-
MaximumNumberOfResults int `yaml:"-"` // MaximumNumberOfResults to display on the page, it's not configurable because we're passing it from the storage config
53+
MaximumNumberOfResults int `yaml:"-" json:"maximumNumberOfResults,omitempty"` // MaximumNumberOfResults to display on the page, it's not configurable because we're passing it from the storage config
5154
}
5255

5356
func (cfg *Config) IsDarkMode() bool {
@@ -59,8 +62,8 @@ func (cfg *Config) IsDarkMode() bool {
5962

6063
// Button is the configuration for a button on the UI
6164
type Button struct {
62-
Name string `yaml:"name,omitempty"` // Name is the text to display on the button
63-
Link string `yaml:"link,omitempty"` // Link to open when the button is clicked.
65+
Name string `yaml:"name,omitempty" json:"name,omitempty"` // Name is the text to display on the button
66+
Link string `yaml:"link,omitempty" json:"link,omitempty"` // Link to open when the button is clicked.
6467
}
6568

6669
// Validate validates the button configuration
@@ -134,7 +137,7 @@ func (cfg *Config) ValidateAndSetDefaults() error {
134137
}
135138
}
136139
// Validate that the template works
137-
t, err := template.ParseFS(static.FileSystem, static.IndexPath)
140+
t, err := GetTemplate()
138141
if err != nil {
139142
return err
140143
}
@@ -146,3 +149,22 @@ type ViewData struct {
146149
UI *Config
147150
Theme string
148151
}
152+
153+
func toJSON(v any) template.JS {
154+
b, err := json.Marshal(v)
155+
if err != nil {
156+
return template.JS("null")
157+
}
158+
return template.JS(b)
159+
}
160+
161+
func GetTemplate() (*template.Template, error) {
162+
if uiTemplate != nil {
163+
return uiTemplate, nil
164+
}
165+
var err error
166+
uiTemplate, err = template.New("index.html").Funcs(template.FuncMap{
167+
"toJSON": toJSON,
168+
}).ParseFS(static.FileSystem, static.IndexPath)
169+
return uiTemplate, err
170+
}

web/app/public/index.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
<html lang="en" class="{{ .Theme }}">
33
<head>
44
<meta charset="utf-8" />
5+
<title>{{ .UI.Title }}</title>
56
<script type="text/javascript">
6-
window.config = {logo: "{{ .UI.Logo }}", header: "{{ .UI.Header }}", dashboardHeading: "{{ .UI.DashboardHeading }}", dashboardSubheading: "{{ .UI.DashboardSubheading }}", link: "{{ .UI.Link }}", buttons: [], maximumNumberOfResults: "{{ .UI.MaximumNumberOfResults }}", defaultSortBy: "{{ .UI.DefaultSortBy }}", defaultFilterBy: "{{ .UI.DefaultFilterBy }}"};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}
7-
// Initialize theme immediately to prevent flash
7+
if (document.title !== decodeURIComponent("%7B%7B%20.UI.Title%20%7D%7D")) {
8+
Function(`window.config = {{ toJSON .UI }};`)(); // Wrapped in Function to prevent error in development mode
9+
}
10+
else {
11+
document.title = "Gatus (Development)";
12+
}
813
(function() {
14+
// Initialize theme immediately to prevent flash
915
const themeFromCookie = document.cookie.match(/theme=(dark|light);?/)?.[1];
1016
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1117
if (themeFromCookie === 'dark' || (!themeFromCookie && prefersDark)) {
@@ -15,7 +21,6 @@
1521
}
1622
})();
1723
</script>
18-
<title>{{ .UI.Title }}</title>
1924
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
2025
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
2126
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />

0 commit comments

Comments
 (0)