Compare commits

...

4 Commits

Author SHA1 Message Date
TwiN
5d626f2934 test(ui): Improve validation tests for UI config 2025-11-16 15:41:25 -05:00
Reze
75c1b290f6 feat(ui): customizable dashboard heading and subheading (#1235)
* Made the Dashboard Text customizable

* Aligned with spaces, changed feature name to DashboardHeading and DashboardSubheading

* rebuild frontend

---------

Co-authored-by: macmoritz <tratarmoritz@gmail.com>
2025-11-16 15:33:26 -05:00
Giampaolo
fe7b74f555 docs: Remove ECS Fargate section from README (#1389)
Remove ECS Fargate section from README

Removed ECS Fargate deployment section from README.
2025-11-12 07:26:47 -05:00
Zee Aslam
ed4c270a25 docs: Add note to README.md regarding CAP_NET_RAW (#1384)
* docs: Add note to README.md regarding CAP_NET_RAW

Signed-off-by: Zee Aslam <zeet6613@gmail.com>

* docs: fix inconsistent markdown

Signed-off-by: Zee Aslam <zeet6613@gmail.com>

---------

Signed-off-by: Zee Aslam <zeet6613@gmail.com>
2025-11-09 15:50:24 -05:00
7 changed files with 158 additions and 54 deletions

View File

@@ -110,7 +110,6 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
- [Helm Chart](#helm-chart)
- [Terraform](#terraform)
- [Kubernetes](#kubernetes)
- [ECS Fargate](#ecs-fargate)
- [Running the tests](#running-the-tests)
- [Using in Production](#using-in-production)
- [FAQ](#faq)
@@ -264,6 +263,8 @@ If you want to test it locally, see [Docker](#docker).
| `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...`. |
| `ui.dashboard-heading` | Dashboard title between header and endpoints | `Health Dashboard` |
| `ui.dashboard-subheading` | Dashboard description between header and endpoints | `Monitor the health of your endpoints in real-time` |
| `ui.header` | Header at the top of the dashboard. | `Gatus` |
| `ui.logo` | URL to the logo to display. | `""` |
| `ui.link` | Link to open when the logo is clicked. | `""` |
@@ -2809,10 +2810,6 @@ To get more details, please check [chart's configuration](https://github.com/Twi
Gatus can be deployed on Kubernetes using Terraform by using the following module: [terraform-kubernetes-gatus](https://github.com/TwiN/terraform-kubernetes-gatus).
#### ECS Fargate
Gatus can be deployed on ECS Fargate using Terraform by using the following module: [terraform-aws-gatus-ecs](https://github.com/GiamPy5/terraform-aws-gatus-ecs).
## Running the tests
```console
go test -v ./...
@@ -3023,6 +3020,9 @@ You can specify a domain prefixed by `icmp://`, or an IP address prefixed by `ic
If you run Gatus on Linux, please read the Linux section on [https://github.com/prometheus-community/pro-bing#linux]
if you encounter any problems.
Prior to `v5.31.0`, some environment setups required adding `CAP_NET_RAW` capabilities to allow pings to work.
As of `v5.31.0`, this is no longer necessary, and ICMP checks will work with unprivileged pings unless running as root. See #1346 for details.
### Monitoring an endpoint using DNS queries
Defining a `dns` configuration in an endpoint will automatically mark said endpoint as an endpoint of type DNS:

View File

@@ -10,14 +10,16 @@ import (
)
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 = "Gatus"
defaultLogo = ""
defaultLink = ""
defaultCustomCSS = ""
defaultSortBy = "name"
defaultFilterBy = "none"
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 = "Gatus"
defaultDashboardHeading = "Health Dashboard"
defaultDashboardSubheading = "Monitor the health of your endpoints in real-time"
defaultLogo = ""
defaultLink = ""
defaultCustomCSS = ""
defaultSortBy = "name"
defaultFilterBy = "none"
)
var (
@@ -30,17 +32,18 @@ var (
// 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
DefaultSortBy string `yaml:"default-sort-by,omitempty"` // DefaultSortBy is the default sort option ('name', 'group', 'health')
DefaultFilterBy string `yaml:"default-filter-by,omitempty"` // DefaultFilterBy is the default filter option ('none', 'failing', 'unstable')
Title string `yaml:"title,omitempty"` // Title of the page
Description string `yaml:"description,omitempty"` // Meta description of the page
DashboardHeading string `yaml:"dashboard-heading,omitempty"` // Dashboard Title between header and endpoints
DashboardSubheading string `yaml:"dashboard-subheading,omitempty"` // Dashboard Description between header and endpoints
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
DefaultSortBy string `yaml:"default-sort-by,omitempty"` // DefaultSortBy is the default sort option ('name', 'group', 'health')
DefaultFilterBy string `yaml:"default-filter-by,omitempty"` // DefaultFilterBy is the default filter option ('none', 'failing', 'unstable')
//////////////////////////////////////////////
// Non-configurable - used for UI rendering //
//////////////////////////////////////////////
@@ -73,6 +76,8 @@ func GetDefaultConfig() *Config {
return &Config{
Title: defaultTitle,
Description: defaultDescription,
DashboardHeading: defaultDashboardHeading,
DashboardSubheading: defaultDashboardSubheading,
Header: defaultHeader,
Logo: defaultLogo,
Link: defaultLink,
@@ -92,6 +97,12 @@ func (cfg *Config) ValidateAndSetDefaults() error {
if len(cfg.Description) == 0 {
cfg.Description = defaultDescription
}
if len(cfg.DashboardHeading) == 0 {
cfg.DashboardHeading = defaultDashboardHeading
}
if len(cfg.DashboardSubheading) == 0 {
cfg.DashboardSubheading = defaultDashboardSubheading
}
if len(cfg.Header) == 0 {
cfg.Header = defaultHeader
}

View File

@@ -7,31 +7,110 @@ import (
)
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)
}
if cfg.DefaultSortBy != defaultSortBy {
t.Errorf("expected defaultSortBy to be %s, got %s", defaultSortBy, cfg.DefaultSortBy)
}
if cfg.DefaultFilterBy != defaultFilterBy {
t.Errorf("expected defaultFilterBy to be %s, got %s", defaultFilterBy, cfg.DefaultFilterBy)
}
t.Run("empty-config", func(t *testing.T) {
cfg := &Config{
Title: "",
Description: "",
DashboardHeading: "",
DashboardSubheading: "",
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.DashboardHeading != defaultDashboardHeading {
t.Errorf("expected DashboardHeading to be %s, got %s", defaultDashboardHeading, cfg.DashboardHeading)
}
if cfg.DashboardSubheading != defaultDashboardSubheading {
t.Errorf("expected DashboardSubheading to be %s, got %s", defaultDashboardSubheading, cfg.DashboardSubheading)
}
if cfg.Header != defaultHeader {
t.Errorf("expected header to be %s, got %s", defaultHeader, cfg.Header)
}
if cfg.DefaultSortBy != defaultSortBy {
t.Errorf("expected defaultSortBy to be %s, got %s", defaultSortBy, cfg.DefaultSortBy)
}
if cfg.DefaultFilterBy != defaultFilterBy {
t.Errorf("expected defaultFilterBy to be %s, got %s", defaultFilterBy, cfg.DefaultFilterBy)
}
})
t.Run("custom-values", func(t *testing.T) {
cfg := &Config{
Title: "Custom Title",
Description: "Custom Description",
DashboardHeading: "Production Status",
DashboardSubheading: "Monitor all production endpoints",
Header: "My Company",
Logo: "https://example.com/logo.png",
Link: "https://example.com",
DefaultSortBy: "health",
DefaultFilterBy: "failing",
}
if err := cfg.ValidateAndSetDefaults(); err != nil {
t.Error("expected no error, got", err.Error())
}
if cfg.Title != "Custom Title" {
t.Errorf("expected title to be preserved, got %s", cfg.Title)
}
if cfg.Description != "Custom Description" {
t.Errorf("expected description to be preserved, got %s", cfg.Description)
}
if cfg.DashboardHeading != "Production Status" {
t.Errorf("expected DashboardHeading to be preserved, got %s", cfg.DashboardHeading)
}
if cfg.DashboardSubheading != "Monitor all production endpoints" {
t.Errorf("expected DashboardSubheading to be preserved, got %s", cfg.DashboardSubheading)
}
if cfg.Header != "My Company" {
t.Errorf("expected header to be preserved, got %s", cfg.Header)
}
if cfg.Logo != "https://example.com/logo.png" {
t.Errorf("expected logo to be preserved, got %s", cfg.Logo)
}
if cfg.Link != "https://example.com" {
t.Errorf("expected link to be preserved, got %s", cfg.Link)
}
if cfg.DefaultSortBy != "health" {
t.Errorf("expected defaultSortBy to be preserved, got %s", cfg.DefaultSortBy)
}
if cfg.DefaultFilterBy != "failing" {
t.Errorf("expected defaultFilterBy to be preserved, got %s", cfg.DefaultFilterBy)
}
})
t.Run("partial-custom-values", func(t *testing.T) {
cfg := &Config{
Title: "Custom Title",
DashboardHeading: "My Dashboard",
Header: "",
DashboardSubheading: "",
}
if err := cfg.ValidateAndSetDefaults(); err != nil {
t.Error("expected no error, got", err.Error())
}
if cfg.Title != "Custom Title" {
t.Errorf("expected custom title to be preserved, got %s", cfg.Title)
}
if cfg.DashboardHeading != "My Dashboard" {
t.Errorf("expected custom DashboardHeading to be preserved, got %s", cfg.DashboardHeading)
}
if cfg.DashboardSubheading != defaultDashboardSubheading {
t.Errorf("expected DashboardSubheading to use default, got %s", cfg.DashboardSubheading)
}
if cfg.Header != defaultHeader {
t.Errorf("expected header to use default, got %s", cfg.Header)
}
if cfg.Description != defaultDescription {
t.Errorf("expected description to use default, got %s", cfg.Description)
}
})
}
func TestButton_Validate(t *testing.T) {
@@ -78,6 +157,12 @@ func TestGetDefaultConfig(t *testing.T) {
if defaultConfig.Title != defaultTitle {
t.Error("expected GetDefaultConfig() to return defaultTitle, got", defaultConfig.Title)
}
if defaultConfig.DashboardHeading != defaultDashboardHeading {
t.Error("expected GetDefaultConfig() to return defaultDashboardHeading, got", defaultConfig.DashboardHeading)
}
if defaultConfig.DashboardSubheading != defaultDashboardSubheading {
t.Error("expected GetDefaultConfig() to return defaultDashboardSubheading, got", defaultConfig.DashboardSubheading)
}
if defaultConfig.Logo != defaultLogo {
t.Error("expected GetDefaultConfig() to return defaultLogo, got", defaultConfig.Logo)
}

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<script type="text/javascript">
window.config = {logo: "{{ .UI.Logo }}", header: "{{ .UI.Header }}", link: "{{ .UI.Link }}", buttons: [], maximumNumberOfResults: "{{ .UI.MaximumNumberOfResults }}", defaultSortBy: "{{ .UI.DefaultSortBy }}", defaultFilterBy: "{{ .UI.DefaultFilterBy }}"};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}
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}}
// Initialize theme immediately to prevent flash
(function() {
const themeFromCookie = document.cookie.match(/theme=(dark|light);?/)?.[1];

View File

@@ -4,8 +4,8 @@
<div class="mb-6">
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-4xl font-bold tracking-tight">Health Dashboard</h1>
<p class="text-muted-foreground mt-2">Monitor the health of your endpoints in real-time</p>
<h1 class="text-4xl font-bold tracking-tight">{{ dashboardHeading }}</h1>
<p class="text-muted-foreground mt-2">{{ dashboardSubheading }}</p>
</div>
<div class="flex items-center gap-4">
<Button
@@ -532,6 +532,14 @@ const initializeCollapsedGroups = () => {
}
}
const dashboardHeading = computed(() => {
return window.config && window.config.dashboardHeading && window.config.dashboardHeading !== '{{ .UI.DashboardHeading }}' ? window.config.dashboardHeading : "Health Dashboard"
})
const dashboardSubheading = computed(() => {
return window.config && window.config.dashboardSubheading && window.config.dashboardSubheading !== '{{ .UI.dashboardSubheading }}' ? window.config.dashboardSubheading : "Monitor the health of your endpoints in real-time"
})
onMounted(() => {
fetchData()
})

View File

@@ -1,4 +1,4 @@
<!doctype html><html lang="en" class="{{ .Theme }}"><head><meta charset="utf-8"/><script>window.config = {logo: "{{ .UI.Logo }}", header: "{{ .UI.Header }}", link: "{{ .UI.Link }}", buttons: [], maximumNumberOfResults: "{{ .UI.MaximumNumberOfResults }}", defaultSortBy: "{{ .UI.DefaultSortBy }}", defaultFilterBy: "{{ .UI.DefaultFilterBy }}"};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}
<!doctype html><html lang="en" class="{{ .Theme }}"><head><meta charset="utf-8"/><script>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}}
// Initialize theme immediately to prevent flash
(function() {
const themeFromCookie = document.cookie.match(/theme=(dark|light);?/)?.[1];

File diff suppressed because one or more lines are too long