Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented UptimeRobot widget #983

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/widget_maker.go
Expand Up @@ -67,6 +67,7 @@ import (
"github.com/wtfutil/wtf/modules/twitter"
"github.com/wtfutil/wtf/modules/twitterstats"
"github.com/wtfutil/wtf/modules/unknown"
"github.com/wtfutil/wtf/modules/uptimerobot"
"github.com/wtfutil/wtf/modules/victorops"
"github.com/wtfutil/wtf/modules/weatherservices/arpansagovau"
"github.com/wtfutil/wtf/modules/weatherservices/prettyweather"
Expand Down Expand Up @@ -290,6 +291,9 @@ func MakeWidget(
case "twitterstats":
settings := twitterstats.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = twitterstats.NewWidget(app, pages, settings)
case "uptimerobot":
settings := uptimerobot.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = uptimerobot.NewWidget(app, pages, settings)
case "victorops":
settings := victorops.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = victorops.NewWidget(app, settings)
Expand Down
6 changes: 6 additions & 0 deletions modules/uptimerobot/keyboard.go
@@ -0,0 +1,6 @@
package uptimerobot

func (widget *Widget) initializeKeyboardControls() {
widget.SetKeyboardChar("/", widget.ShowHelp, "Show/hide this help widget")
widget.SetKeyboardChar("r", widget.Refresh, "Refresh widget")
}
37 changes: 37 additions & 0 deletions modules/uptimerobot/settings.go
@@ -0,0 +1,37 @@
package uptimerobot

import (
"os"

"github.com/olebedev/config"
"github.com/wtfutil/wtf/cfg"
)

const (
defaultFocusable = true
defaultTitle = "Uptime Robot"
)

type Settings struct {
common *cfg.Common

apiKey string `help:"An UptimeRobot API key."`
uptimePeriods string `help:"The periods over which to display uptime (in days, dash-separated)." optional:"true"`
offlineFirst bool `help:"Display offline monitors at the top." optional:"true"`
}

func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {

settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),

apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_UPTIMEROBOT_APIKEY")),
uptimePeriods: ymlConfig.UString("uptimePeriods", "30"),
offlineFirst: ymlConfig.UBool("offlineFirst", false),
}

cfg.ModuleSecret(name, globalConfig, &settings.apiKey).
Service("https://api.uptimerobot.com").Load()

return &settings
}
190 changes: 190 additions & 0 deletions modules/uptimerobot/widget.go
@@ -0,0 +1,190 @@
package uptimerobot

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"

"github.com/rivo/tview"
"github.com/wtfutil/wtf/view"
)

type Widget struct {
view.KeyboardWidget
view.ScrollableWidget

monitors []Monitor
settings *Settings
err error
}

func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget {
widget := &Widget{
KeyboardWidget: view.NewKeyboardWidget(app, pages, settings.common),
ScrollableWidget: view.NewScrollableWidget(app, settings.common),

settings: settings,
}

widget.SetRenderFunction(widget.Render)
widget.initializeKeyboardControls()
widget.View.SetInputCapture(widget.InputCapture)

widget.KeyboardWidget.SetView(widget.View)

return widget
}

/* -------------------- Exported Functions -------------------- */

func (widget *Widget) Refresh() {
if widget.Disabled() {
return
}

monitors, err := widget.getMonitors()

if widget.settings.offlineFirst {
var tmp Monitor
var next int
for i := 0; i < len(monitors); i++ {
if monitors[i].State != 2 {
tmp = monitors[i]
for j := i; j > next; j-- {
monitors[j] = monitors[j-1]
}
monitors[next] = tmp
next++
}
}
}

widget.monitors = monitors
widget.err = err
widget.SetItemCount(len(monitors))

widget.Render()
}

// Render sets up the widget data for redrawing to the screen
func (widget *Widget) Render() {
widget.Redraw(widget.content)
}

/* -------------------- Unexported Functions -------------------- */

func (widget *Widget) content() (string, string, bool) {
numUp := 0
for _, monitor := range widget.monitors {
if monitor.State == 2 {
numUp++
}
}

title := fmt.Sprintf("UptimeRobot (%d/%d)", numUp, len(widget.monitors))

if widget.err != nil {
return title, widget.err.Error(), true
}

if widget.monitors == nil {
return title, "No monitors to display", false
}

str := widget.contentFrom(widget.monitors)

return title, str, false
}

func (widget *Widget) contentFrom(monitors []Monitor) string {
var str string

for _, monitor := range monitors {
prefix := ""

switch monitor.State {
case 2:
prefix += "[green] + "
case 8:
case 9:
prefix += "[red] - "
default:
prefix += "[yellow] ~ "
}

str += fmt.Sprintf(`%s%s [gray](%s)[white]
`,
prefix,
monitor.Name,
formatUptimes(monitor.Uptime),
)
}

return str
}

func formatUptimes(str string) string {
splits := strings.Split(str, "-")
str = ""
for i, s := range splits {
if i != 0 {
str += "|"
}
s = s[:5]
s = strings.TrimRight(s, "0")
s = strings.TrimRight(s, ".") + "%"
str += s
}
return str
}

type Monitor struct {
Name string `json:"friendly_name"`
// Monitor state, see: https://uptimerobot.com/api/#parameters
State int8 `json:"status"`
// Uptime ratio, preformatted, e.g.: 100.000-97.233-96.975
Uptime string `json:"custom_uptime_ratio"`
}

func (widget *Widget) getMonitors() ([]Monitor, error) {
// See: https://uptimerobot.com/api/#getMonitorsWrap
resp, err_h := http.PostForm("https://api.uptimerobot.com/v2/getMonitors",
url.Values{
"api_key": {widget.settings.apiKey},
"format": {"json"},
"custom_uptime_ratios": {widget.settings.uptimePeriods},
},
)

if err_h != nil {
return nil, err_h
}

body, _ := ioutil.ReadAll(resp.Body)

// First pass to read the status
c := make(map[string]json.RawMessage)
err_j1 := json.Unmarshal([]byte(body), &c)

if err_j1 != nil {
return nil, err_j1
}

if string(c["stat"]) != `"ok"` {
return nil, errors.New(string(body))
}

// Second pass to get the actual info
var monitors []Monitor
err_j2 := json.Unmarshal(c["monitors"], &monitors)

if err_j2 != nil {
return nil, err_j2
}

return monitors, nil
}