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 }