github.com/wtfutil/wtf@v0.43.0/modules/healthchecks/widget.go (about)

     1  package healthchecks
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/url"
     7  	"time"
     8  
     9  	"github.com/rivo/tview"
    10  	"github.com/wtfutil/wtf/utils"
    11  	"github.com/wtfutil/wtf/view"
    12  )
    13  
    14  const (
    15  	userAgent = "WTFUtil"
    16  )
    17  
    18  type Widget struct {
    19  	view.ScrollableWidget
    20  	checks   []Checks
    21  	settings *Settings
    22  	err      error
    23  }
    24  
    25  type Health struct {
    26  	Checks []Checks `json:"checks"`
    27  }
    28  
    29  type Checks struct {
    30  	Name         string    `json:"name"`
    31  	Tags         string    `json:"tags"`
    32  	Desc         string    `json:"desc"`
    33  	Grace        int       `json:"grace"`
    34  	NPings       int       `json:"n_pings"`
    35  	Status       string    `json:"status"`
    36  	LastPing     time.Time `json:"last_ping"`
    37  	NextPing     time.Time `json:"next_ping"`
    38  	ManualResume bool      `json:"manual_resume"`
    39  	Methods      string    `json:"methods"`
    40  	PingURL      string    `json:"ping_url"`
    41  	UpdateURL    string    `json:"update_url"`
    42  	PauseURL     string    `json:"pause_url"`
    43  	Channels     string    `json:"channels"`
    44  	Timeout      int       `json:"timeout,omitempty"`
    45  	Schedule     string    `json:"schedule,omitempty"`
    46  	Tz           string    `json:"tz,omitempty"`
    47  }
    48  
    49  func NewWidget(tviewApp *tview.Application, redrawChan chan bool, pages *tview.Pages, settings *Settings) *Widget {
    50  	widget := &Widget{
    51  		ScrollableWidget: view.NewScrollableWidget(tviewApp, redrawChan, pages, settings.Common),
    52  		settings:         settings,
    53  	}
    54  
    55  	widget.SetRenderFunction(widget.Render)
    56  	widget.initializeKeyboardControls()
    57  
    58  	return widget
    59  }
    60  
    61  /* -------------------- Exported Functions -------------------- */
    62  
    63  func (widget *Widget) Refresh() {
    64  	if widget.Disabled() {
    65  		return
    66  	}
    67  
    68  	checks, err := widget.getExistingChecks()
    69  	widget.checks = checks
    70  	widget.err = err
    71  	widget.SetItemCount(len(checks))
    72  	widget.Render()
    73  }
    74  
    75  // Render sets up the widget data for redrawing to the screen
    76  func (widget *Widget) Render() {
    77  	widget.Redraw(widget.content)
    78  }
    79  
    80  /* -------------------- Unexported Functions -------------------- */
    81  
    82  func (widget *Widget) content() (string, string, bool) {
    83  	numUp := 0
    84  	for _, check := range widget.checks {
    85  		if check.Status == "up" {
    86  			numUp++
    87  		}
    88  	}
    89  
    90  	title := fmt.Sprintf("Healthchecks (%d/%d)", numUp, len(widget.checks))
    91  
    92  	if widget.err != nil {
    93  		return title, widget.err.Error(), true
    94  	}
    95  
    96  	if widget.checks == nil {
    97  		return title, "No checks to display", false
    98  	}
    99  
   100  	str := widget.contentFrom(widget.checks)
   101  
   102  	return title, str, false
   103  }
   104  
   105  func (widget *Widget) contentFrom(checks []Checks) string {
   106  	var str string
   107  
   108  	for _, check := range checks {
   109  		prefix := ""
   110  
   111  		switch check.Status {
   112  		case "up":
   113  			prefix += "[green] + "
   114  		case "down":
   115  			prefix += "[red] - "
   116  		default:
   117  			prefix += "[yellow] ~ "
   118  		}
   119  
   120  		str += fmt.Sprintf(`%s%s [gray](%s|%d)[white]%s`,
   121  			prefix,
   122  			check.Name,
   123  			timeSincePing(check.LastPing),
   124  			check.NPings,
   125  			"\n",
   126  		)
   127  	}
   128  
   129  	return str
   130  }
   131  
   132  func timeSincePing(ts time.Time) string {
   133  	dur := time.Since(ts)
   134  	return dur.Truncate(time.Second).String()
   135  }
   136  
   137  func makeURL(baseurl string, path string, tags []string) (string, error) {
   138  	u, err := url.Parse(baseurl)
   139  	if err != nil {
   140  		return "", err
   141  	}
   142  	u.Path = path
   143  	q := u.Query()
   144  	// If we have several tags
   145  	if len(tags) > 0 {
   146  		for _, tag := range tags {
   147  			q.Add("tag", tag)
   148  		}
   149  		u.RawQuery = q.Encode()
   150  	}
   151  	return u.String(), nil
   152  }
   153  
   154  func (widget *Widget) getExistingChecks() ([]Checks, error) {
   155  	// See: https://healthchecks.io/docs/api/#list-checks
   156  	u, err := makeURL(widget.settings.apiURL, "/api/v1/checks/", widget.settings.tags)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	req, err := http.NewRequest("GET", u, http.NoBody)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	req.Header.Set("User-Agent", userAgent)
   165  	req.Header.Set("X-Api-Key", widget.settings.apiKey)
   166  	resp, err := http.DefaultClient.Do(req)
   167  
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	if resp.StatusCode != 200 {
   173  		return nil, fmt.Errorf(resp.Status)
   174  	}
   175  
   176  	defer func() { _ = resp.Body.Close() }()
   177  
   178  	var health Health
   179  	err = utils.ParseJSON(&health, resp.Body)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	return health.Checks, nil
   185  }