status.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package prober
  4. import (
  5. "embed"
  6. "fmt"
  7. "html/template"
  8. "net/http"
  9. "strings"
  10. "time"
  11. "tailscale.com/tsweb"
  12. "tailscale.com/util/mak"
  13. )
  14. //go:embed status.html
  15. var statusFiles embed.FS
  16. var statusTpl = template.Must(template.ParseFS(statusFiles, "status.html"))
  17. type statusHandlerOpt func(*statusHandlerParams)
  18. type statusHandlerParams struct {
  19. title string
  20. pageLinks map[string]string
  21. probeLinks map[string]string
  22. }
  23. // WithTitle sets the title of the status page.
  24. func WithTitle(title string) statusHandlerOpt {
  25. return func(opts *statusHandlerParams) {
  26. opts.title = title
  27. }
  28. }
  29. // WithPageLink adds a top-level link to the status page.
  30. func WithPageLink(text, url string) statusHandlerOpt {
  31. return func(opts *statusHandlerParams) {
  32. mak.Set(&opts.pageLinks, text, url)
  33. }
  34. }
  35. // WithProbeLink adds a link to each probe on the status page.
  36. // The textTpl and urlTpl are Go templates that will be rendered
  37. // with the respective ProbeInfo struct as the data.
  38. func WithProbeLink(textTpl, urlTpl string) statusHandlerOpt {
  39. return func(opts *statusHandlerParams) {
  40. mak.Set(&opts.probeLinks, textTpl, urlTpl)
  41. }
  42. }
  43. // StatusHandler is a handler for the probe overview HTTP endpoint.
  44. // It shows a list of probes and their current status.
  45. func (p *Prober) StatusHandler(opts ...statusHandlerOpt) tsweb.ReturnHandlerFunc {
  46. params := &statusHandlerParams{
  47. title: "Prober Status",
  48. }
  49. for _, opt := range opts {
  50. opt(params)
  51. }
  52. return func(w http.ResponseWriter, r *http.Request) error {
  53. type probeStatus struct {
  54. ProbeInfo
  55. TimeSinceLastStart time.Duration
  56. TimeSinceLastEnd time.Duration
  57. Links map[string]template.URL
  58. }
  59. vars := struct {
  60. Title string
  61. Links map[string]template.URL
  62. TotalProbes int64
  63. UnhealthyProbes int64
  64. Probes map[string]probeStatus
  65. }{
  66. Title: params.title,
  67. }
  68. for text, url := range params.pageLinks {
  69. mak.Set(&vars.Links, text, template.URL(url))
  70. }
  71. for name, info := range p.ProbeInfo() {
  72. vars.TotalProbes++
  73. if info.Error != "" {
  74. vars.UnhealthyProbes++
  75. }
  76. s := probeStatus{ProbeInfo: info}
  77. if !info.Start.IsZero() {
  78. s.TimeSinceLastStart = time.Since(info.Start).Truncate(time.Second)
  79. }
  80. if !info.End.IsZero() {
  81. s.TimeSinceLastEnd = time.Since(info.End).Truncate(time.Second)
  82. }
  83. for textTpl, urlTpl := range params.probeLinks {
  84. text, err := renderTemplate(textTpl, info)
  85. if err != nil {
  86. return tsweb.Error(500, err.Error(), err)
  87. }
  88. url, err := renderTemplate(urlTpl, info)
  89. if err != nil {
  90. return tsweb.Error(500, err.Error(), err)
  91. }
  92. mak.Set(&s.Links, text, template.URL(url))
  93. }
  94. mak.Set(&vars.Probes, name, s)
  95. }
  96. if err := statusTpl.ExecuteTemplate(w, "status", vars); err != nil {
  97. return tsweb.HTTPError{Code: 500, Err: err, Msg: "error rendering status page"}
  98. }
  99. return nil
  100. }
  101. }
  102. // renderTemplate renders the given Go template with the provided data
  103. // and returns the result as a string.
  104. func renderTemplate(tpl string, data any) (string, error) {
  105. t, err := template.New("").Parse(tpl)
  106. if err != nil {
  107. return "", fmt.Errorf("error parsing template %q: %w", tpl, err)
  108. }
  109. var buf strings.Builder
  110. if err := t.ExecuteTemplate(&buf, "", data); err != nil {
  111. return "", fmt.Errorf("error rendering template %q with data %v: %w", tpl, data, err)
  112. }
  113. return buf.String(), nil
  114. }