diff --git a/kv/memberlist/kv_init_service.go b/kv/memberlist/kv_init_service.go index e63aac2f4..a15e194c4 100644 --- a/kv/memberlist/kv_init_service.go +++ b/kv/memberlist/kv_init_service.go @@ -2,6 +2,7 @@ package memberlist import ( "context" + _ "embed" "encoding/json" "fmt" "html/template" @@ -157,7 +158,7 @@ func (kvs *KVInitService) ServeHTTP(w http.ResponseWriter, req *http.Request) { sent, received := kv.getSentAndReceivedMessages() - v := pageData{ + v := statusPageData{ Now: time.Now(), Memberlist: kv.memberlist, SortedMembers: members, @@ -183,7 +184,7 @@ func (kvs *KVInitService) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - err := pageTemplate.Execute(w, v) + err := defaultPageTemplate.Execute(w, v) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } @@ -276,7 +277,9 @@ func downloadKey(w http.ResponseWriter, kv *KV, store map[string]valueDesc, key _, _ = w.Write(encoded) } -type pageData struct { +// statusPageData is the root element of the status page served through HTTP. +// Depending on the accepted content-type, it will be either marshaled as JSON or provided to the rendered HTML template. +type statusPageData struct { Now time.Time Memberlist *memberlist.Memberlist SortedMembers []*memberlist.Node @@ -285,149 +288,8 @@ type pageData struct { ReceivedMessages []message } -var pageTemplate = template.Must(template.New("webpage").Funcs(template.FuncMap{ +//go:embed status.gohtml +var defaultPageContent string +var defaultPageTemplate = template.Must(template.New("webpage").Funcs(template.FuncMap{ "StringsJoin": strings.Join, -}).Parse(pageContent)) - -const pageContent = ` - - -
- -Current time: {{ .Now }}
- -Key | -Value Details | -Actions | -
---|---|---|
{{ $k }} | -{{ $v }} | -- json - | json-pretty - | struct - | download - | -
Note that value "version" is node-specific. It starts with 0 (on restart), and increases on each received update. Size is in bytes.
- -Name | -Address | -State | -
---|---|---|
{{ .Name }} | -{{ .Address }} | -{{ .State }} | -
State: 0 = Alive, 1 = Suspect, 2 = Dead, 3 = Left
- -ID | -Time | -Key | -Value in the Message | -Version After Update (0 = no change) | -Changes | -Actions | -
---|---|---|---|---|---|---|
{{ .ID }} | -{{ .Time.Format "15:04:05.000" }} | -{{ .Pair.Key }} | -size: {{ .Pair.Value | len }}, codec: {{ .Pair.Codec }} | -{{ .Version }} | -{{ StringsJoin .Changes ", " }} | -- json - | json-pretty - | struct - | -
ID | -Time | -Key | -Value | -Version | -Changes | -Actions | -
---|---|---|---|---|---|---|
{{ .ID }} | -{{ .Time.Format "15:04:05.000" }} | -{{ .Pair.Key }} | -size: {{ .Pair.Value | len }}, codec: {{ .Pair.Codec }} | -{{ .Version }} | -{{ StringsJoin .Changes ", " }} | -- json - | json-pretty - | struct - | -
Current time: {{ .Now }}
+ +Key | +Value Details | +Actions | +
---|---|---|
{{ $k }} | +{{ $v }} | ++ json + | json-pretty + | struct + | download + | +
Note that value "version" is node-specific. It starts with 0 (on restart), and increases on each received update. + Size is in bytes.
+ +Name | +Address | +State | +
---|---|---|
{{ .Name }} | +{{ .Address }} | +{{ .State }} | +
State: 0 = Alive, 1 = Suspect, 2 = Dead, 3 = Left
+ +ID | +Time | +Key | +Value in the Message | +Version After Update (0 = no change) | +Changes | +Actions | +
---|---|---|---|---|---|---|
{{ .ID }} | +{{ .Time.Format "15:04:05.000" }} | +{{ .Pair.Key }} | +size: {{ .Pair.Value | len }}, codec: {{ .Pair.Codec }} | +{{ .Version }} | +{{ StringsJoin .Changes ", " }} | ++ json + | json-pretty + | struct + | +
ID | +Time | +Key | +Value | +Version | +Changes | +Actions | +
---|---|---|---|---|---|---|
{{ .ID }} | +{{ .Time.Format "15:04:05.000" }} | +{{ .Pair.Key }} | +size: {{ .Pair.Value | len }}, codec: {{ .Pair.Codec }} | +{{ .Version }} | +{{ StringsJoin .Changes ", " }} | ++ json + | json-pretty + | struct + | +
Current time: {{ .Now }}
- - -` - -var pageTemplate *template.Template +//go:embed status.gohtml +var defaultPageContent string +var defaultPageTemplate *template.Template func init() { t := template.New("webpage") t.Funcs(template.FuncMap{"mod": func(i, j int) bool { return i%j == 0 }}) - pageTemplate = template.Must(t.Parse(pageContent)) + defaultPageTemplate = template.Must(t.Parse(defaultPageContent)) } type ingesterDesc struct { @@ -114,12 +49,17 @@ type ringAccess interface { type ringPageHandler struct { r ringAccess heartbeatPeriod time.Duration + template *template.Template } -func newRingPageHandler(r ringAccess, heartbeatPeriod time.Duration) *ringPageHandler { +func newRingPageHandler(r ringAccess, heartbeatPeriod time.Duration, template *template.Template) *ringPageHandler { + if template == nil { + template = defaultPageTemplate + } return &ringPageHandler{ r: r, heartbeatPeriod: heartbeatPeriod, + template: template, } } @@ -195,7 +135,7 @@ func (h *ringPageHandler) handle(w http.ResponseWriter, req *http.Request) { Ingesters: ingesters, Now: now, ShowTokens: tokensParam == "true", - }, pageTemplate, req) + }, h.template, req) } // RenderHTTPResponse either responds with json or a rendered html page using the passed in template diff --git a/ring/lifecycler.go b/ring/lifecycler.go index 43d458ced..409966fb8 100644 --- a/ring/lifecycler.go +++ b/ring/lifecycler.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "html/template" "net/http" "os" "sort" @@ -50,6 +51,10 @@ type LifecyclerConfig struct { // Injected internally ListenPort int `yaml:"-"` + + // CustomHTTPHandlerTemplate will be rendered by HTTPHandler instead of the embedded default one, if provided. + // This option is set internally and never exposed to the user. + CustomHTTPHandlerTemplate *template.Template `yaml:"-"` } // RegisterFlags adds the flags required to config this to the given FlagSet. @@ -870,7 +875,7 @@ func (i *Lifecycler) getRing(ctx context.Context) (*Desc, error) { } func (i *Lifecycler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - newRingPageHandler(i, i.cfg.HeartbeatPeriod).handle(w, req) + newRingPageHandler(i, i.cfg.HeartbeatPeriod, i.cfg.CustomHTTPHandlerTemplate).handle(w, req) } // unregister removes our entry from consul. diff --git a/ring/ring.go b/ring/ring.go index 6c7e4a49f..a23775297 100644 --- a/ring/ring.go +++ b/ring/ring.go @@ -6,6 +6,7 @@ import ( "context" "flag" "fmt" + "html/template" "math" "math/rand" "net/http" @@ -131,6 +132,10 @@ type Config struct { // Whether the shuffle-sharding subring cache is disabled. This option is set // internally and never exposed to the user. SubringCacheDisabled bool `yaml:"-"` + + // CustomHTTPHandlerTemplate will be rendered by HTTPHandler instead of the embedded default one, if provided. + // This option is set internally and never exposed to the user. + CustomHTTPHandlerTemplate *template.Template `yaml:"-"` } // RegisterFlags adds the flags required to config this to the given FlagSet with a specified prefix @@ -861,7 +866,7 @@ func (r *Ring) getRing(ctx context.Context) (*Desc, error) { } func (r *Ring) ServeHTTP(w http.ResponseWriter, req *http.Request) { - newRingPageHandler(r, r.cfg.HeartbeatTimeout).handle(w, req) + newRingPageHandler(r, r.cfg.HeartbeatTimeout, r.cfg.CustomHTTPHandlerTemplate).handle(w, req) } // Operation describes which instances can be included in the replica set, based on their state. diff --git a/ring/status.gohtml b/ring/status.gohtml new file mode 100644 index 000000000..4a8832ffa --- /dev/null +++ b/ring/status.gohtml @@ -0,0 +1,69 @@ +{{- /*gotype: github.com/grafana/dskit/ring.httpResponse */ -}} + + + + +Current time: {{ .Now }}
+ + + \ No newline at end of file