mirror of
https://github.com/TwiN/gatus.git
synced 2024-11-24 17:04:42 +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.logo` | URL to the logo to display. | `""` |
|
||||
| `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). | `{}` |
|
||||
|
||||
|
||||
@ -1328,7 +1331,7 @@ web:
|
||||
```
|
||||
|
||||
### Badges
|
||||
### Uptime
|
||||
#### Uptime
|
||||
![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 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.
|
||||
|
||||
|
||||
### Response time
|
||||
#### Response time
|
||||
![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 7d](https://status.twin.sh/api/v1/endpoints/core_blog-external/response-times/7d/badge.svg)
|
||||
|
@ -53,7 +53,14 @@ maintenance:
|
||||
duration: 4h
|
||||
every: [Monday, Thursday]
|
||||
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:
|
||||
- name: website
|
||||
url: https://twin.sh/health
|
||||
@ -88,8 +95,8 @@ endpoints:
|
||||
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)
|
||||
}
|
||||
if config.UI == nil || config.UI.Title != "Test" {
|
||||
t.Error("Expected Config.UI.Title to be 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 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 {
|
||||
t.Error("Expected Config.Maintenance to be configured properly")
|
||||
|
@ -2,6 +2,7 @@ package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
@ -16,6 +17,8 @@ var (
|
||||
// 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
|
||||
StaticFolder = "./web/static"
|
||||
|
||||
ErrButtonValidationFailed = errors.New("invalid button configuration: missing required name or link")
|
||||
)
|
||||
|
||||
// Config is the configuration for the UI of Gatus
|
||||
@ -24,6 +27,21 @@ type Config struct {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -47,6 +65,12 @@ func (cfg *Config) ValidateAndSetDefaults() error {
|
||||
if len(cfg.Header) == 0 {
|
||||
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")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"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) {
|
||||
defaultConfig := GetDefaultConfig()
|
||||
if defaultConfig.Title != defaultTitle {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gatus",
|
||||
"version": "3.6.0",
|
||||
"version": "3.7.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --mode development",
|
||||
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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>
|
||||
<title>{{ .Title }}</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
@ -13,6 +13,11 @@
|
||||
</a>
|
||||
</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>
|
||||
<router-view @showTooltip="showTooltip" />
|
||||
</div>
|
||||
@ -85,6 +90,9 @@ export default {
|
||||
},
|
||||
link() {
|
||||
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() {
|
||||
|
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