mirror of
https://github.com/TwiN/gatus.git
synced 2024-11-21 23:43:27 +01:00
feat(ui): Add support for buttons below header (#106)
This commit is contained in:
parent
dcb997f501
commit
9ede992e4e
@ -186,6 +186,9 @@ If you want to test it locally, see [Docker](#docker).
|
|||||||
| `ui.header` | Header at the top of the dashboard. | `Health Status` |
|
| `ui.header` | Header at the top of the dashboard. | `Health Status` |
|
||||||
| `ui.logo` | URL to the logo to display. | `""` |
|
| `ui.logo` | URL to the logo to display. | `""` |
|
||||||
| `ui.link` | Link to open when the logo is clicked. | `""` |
|
| `ui.link` | Link to open when the logo is clicked. | `""` |
|
||||||
|
| `ui.buttons` | List of buttons to display below the header. | `[]` |
|
||||||
|
| `ui.buttons[].name` | Text to display on the button. | Required `""` |
|
||||||
|
| `ui.buttons[].link` | Link to open when the button is clicked. | Required `""` |
|
||||||
| `maintenance` | [Maintenance configuration](#maintenance). | `{}` |
|
| `maintenance` | [Maintenance configuration](#maintenance). | `{}` |
|
||||||
|
|
||||||
|
|
||||||
@ -1328,7 +1331,7 @@ web:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Badges
|
### Badges
|
||||||
### Uptime
|
#### Uptime
|
||||||
![Uptime 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/1h/badge.svg)
|
![Uptime 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/1h/badge.svg)
|
||||||
![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/24h/badge.svg)
|
![Uptime 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/24h/badge.svg)
|
||||||
![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/7d/badge.svg)
|
![Uptime 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/uptimes/7d/badge.svg)
|
||||||
@ -1361,7 +1364,7 @@ Example:
|
|||||||
If you'd like to see a visual example of each badge available, you can simply navigate to the endpoint's detail page.
|
If you'd like to see a visual example of each badge available, you can simply navigate to the endpoint's detail page.
|
||||||
|
|
||||||
|
|
||||||
### Response time
|
#### Response time
|
||||||
![Response time 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/1h/badge.svg)
|
![Response time 1h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/1h/badge.svg)
|
||||||
![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/24h/badge.svg)
|
![Response time 24h](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/24h/badge.svg)
|
||||||
![Response time 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/7d/badge.svg)
|
![Response time 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/7d/badge.svg)
|
||||||
|
@ -53,7 +53,14 @@ maintenance:
|
|||||||
duration: 4h
|
duration: 4h
|
||||||
every: [Monday, Thursday]
|
every: [Monday, Thursday]
|
||||||
ui:
|
ui:
|
||||||
title: Test
|
title: T
|
||||||
|
header: H
|
||||||
|
link: https://example.org
|
||||||
|
buttons:
|
||||||
|
- name: "Home"
|
||||||
|
link: "https://example.org"
|
||||||
|
- name: "Status page"
|
||||||
|
link: "https://status.example.org"
|
||||||
endpoints:
|
endpoints:
|
||||||
- name: website
|
- name: website
|
||||||
url: https://twin.sh/health
|
url: https://twin.sh/health
|
||||||
@ -88,8 +95,8 @@ endpoints:
|
|||||||
if config.Storage == nil || config.Storage.Path != file || config.Storage.Type != storage.TypeSQLite {
|
if config.Storage == nil || config.Storage.Path != file || config.Storage.Type != storage.TypeSQLite {
|
||||||
t.Error("expected storage to be set to sqlite, got", config.Storage)
|
t.Error("expected storage to be set to sqlite, got", config.Storage)
|
||||||
}
|
}
|
||||||
if config.UI == nil || config.UI.Title != "Test" {
|
if config.UI == nil || config.UI.Title != "T" || config.UI.Header != "H" || config.UI.Link != "https://example.org" || len(config.UI.Buttons) != 2 || config.UI.Buttons[0].Name != "Home" || config.UI.Buttons[0].Link != "https://example.org" || config.UI.Buttons[1].Name != "Status page" || config.UI.Buttons[1].Link != "https://status.example.org" {
|
||||||
t.Error("Expected Config.UI.Title to be Test")
|
t.Error("expected ui to be set to T, H, https://example.org, 2 buttons, Home and Status page, got", config.UI)
|
||||||
}
|
}
|
||||||
if mc := config.Maintenance; mc == nil || mc.Start != "00:00" || !mc.IsEnabled() || mc.Duration != 4*time.Hour || len(mc.Every) != 2 {
|
if mc := config.Maintenance; mc == nil || mc.Start != "00:00" || !mc.IsEnabled() || mc.Duration != 4*time.Hour || len(mc.Every) != 2 {
|
||||||
t.Error("Expected Config.Maintenance to be configured properly")
|
t.Error("Expected Config.Maintenance to be configured properly")
|
||||||
|
@ -2,6 +2,7 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,14 +17,31 @@ var (
|
|||||||
// StaticFolder is the path to the location of the static folder from the root path of the project
|
// StaticFolder is the path to the location of the static folder from the root path of the project
|
||||||
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
|
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
|
||||||
StaticFolder = "./web/static"
|
StaticFolder = "./web/static"
|
||||||
|
|
||||||
|
ErrButtonValidationFailed = errors.New("invalid button configuration: missing required name or link")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the configuration for the UI of Gatus
|
// Config is the configuration for the UI of Gatus
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Title string `yaml:"title,omitempty"` // Title of the page
|
Title string `yaml:"title,omitempty"` // Title of the page
|
||||||
Header string `yaml:"header,omitempty"` // Header is the text at the top 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
|
Logo string `yaml:"logo,omitempty"` // Logo to display on the page
|
||||||
Link string `yaml:"link,omitempty"` // Link to open when clicking on the logo
|
Link string `yaml:"link,omitempty"` // Link to open when clicking on the logo
|
||||||
|
Buttons []Button `yaml:"buttons,omitempty"` // Buttons to display below the header
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// GetDefaultConfig returns a Config struct with the default values
|
||||||
@ -47,6 +65,12 @@ func (cfg *Config) ValidateAndSetDefaults() error {
|
|||||||
if len(cfg.Header) == 0 {
|
if len(cfg.Header) == 0 {
|
||||||
cfg.Header = defaultLink
|
cfg.Header = defaultLink
|
||||||
}
|
}
|
||||||
|
for _, btn := range cfg.Buttons {
|
||||||
|
if err := btn.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Validate that the template works
|
||||||
t, err := template.ParseFiles(StaticFolder + "/index.html")
|
t, err := template.ParseFiles(StaticFolder + "/index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,6 +27,45 @@ func TestConfig_ValidateAndSetDefaults(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestGetDefaultConfig(t *testing.T) {
|
||||||
defaultConfig := GetDefaultConfig()
|
defaultConfig := GetDefaultConfig()
|
||||||
if defaultConfig.Title != defaultTitle {
|
if defaultConfig.Title != defaultTitle {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gatus",
|
"name": "gatus",
|
||||||
"version": "3.6.0",
|
"version": "3.7.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve --mode development",
|
"serve": "vue-cli-service serve --mode development",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.config = {logo: "{{ .Logo }}", header: "{{ .Header }}", link: "{{ .Link }}"};
|
window.config = {logo: "{{ .Logo }}", header: "{{ .Header }}", link: "{{ .Link }}", buttons: []};{{- range .Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}
|
||||||
</script>
|
</script>
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
@ -13,6 +13,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="buttons" class="flex flex-wrap">
|
||||||
|
<a v-for="button in buttons" :key="button.name" :href="button.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">
|
||||||
|
{{ button.name }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<router-view @showTooltip="showTooltip" />
|
<router-view @showTooltip="showTooltip" />
|
||||||
</div>
|
</div>
|
||||||
@ -85,6 +90,9 @@ export default {
|
|||||||
},
|
},
|
||||||
link() {
|
link() {
|
||||||
return window.config && window.config.link && window.config.link !== '{{ .Link }}' ? window.config.link : null;
|
return window.config && window.config.link && window.config.link !== '{{ .Link }}' ? window.config.link : null;
|
||||||
|
},
|
||||||
|
buttons() {
|
||||||
|
return window.config && window.config.buttons ? window.config.buttons : [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><script>window.config = {logo: "{{ .Logo }}", header: "{{ .Header }}", link: "{{ .Link }}"};</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="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="shortcut icon" href="/favicon.ico"/><meta property="description" content="Gatus is an advanced automated status page that lets you monitor your applications and configure alerts to notify you if there's an issue"/><script defer="defer" type="module" src="/js/chunk-vendors.js"></script><script defer="defer" type="module" src="/js/app.js"></script><link href="/css/app.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.js" nomodule></script><script defer="defer" src="/js/app-legacy.js" nomodule></script></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="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="shortcut icon" href="/favicon.ico"/><meta property="description" content="Gatus is an advanced automated status page that lets you monitor your applications and configure alerts to notify you if there's an issue"/><script defer="defer" type="module" src="/js/chunk-vendors.js"></script><script defer="defer" type="module" src="/js/app.js"></script><link href="/css/app.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.js" nomodule></script><script defer="defer" src="/js/app-legacy.js" nomodule></script></head><body class="dark:bg-gray-900"><noscript><strong>Enable JavaScript to view this page.</strong></noscript><div id="app"></div></body></html>
|
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