mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
Merge branch 'grafana' of github.com:schoentoon/wtf into schoentoon-grafana
This commit is contained in:
commit
546f73d56c
@ -33,6 +33,7 @@ import (
|
||||
"github.com/wtfutil/wtf/modules/gitlabtodo"
|
||||
"github.com/wtfutil/wtf/modules/gitter"
|
||||
"github.com/wtfutil/wtf/modules/googleanalytics"
|
||||
"github.com/wtfutil/wtf/modules/grafana"
|
||||
"github.com/wtfutil/wtf/modules/gspreadsheets"
|
||||
"github.com/wtfutil/wtf/modules/hackernews"
|
||||
"github.com/wtfutil/wtf/modules/hibp"
|
||||
@ -189,6 +190,9 @@ func MakeWidget(
|
||||
case "gspreadsheets":
|
||||
settings := gspreadsheets.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = gspreadsheets.NewWidget(app, settings)
|
||||
case "grafana":
|
||||
settings := grafana.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = grafana.NewWidget(app, pages, settings)
|
||||
case "hackernews":
|
||||
settings := hackernews.NewSettingsFromYAML(moduleName, moduleConfig, config)
|
||||
widget = hackernews.NewWidget(app, pages, settings)
|
||||
|
105
modules/grafana/client.go
Normal file
105
modules/grafana/client.go
Normal file
@ -0,0 +1,105 @@
|
||||
package grafana
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
)
|
||||
|
||||
type AlertState int
|
||||
|
||||
const (
|
||||
Alerting AlertState = iota
|
||||
Pending
|
||||
NoData
|
||||
Paused
|
||||
Ok
|
||||
)
|
||||
|
||||
var toString = map[AlertState]string{
|
||||
Alerting: "alerting",
|
||||
Pending: "pending",
|
||||
NoData: "no_data",
|
||||
Paused: "paused",
|
||||
Ok: "ok",
|
||||
}
|
||||
|
||||
var toID = map[string]AlertState{
|
||||
"alerting": Alerting,
|
||||
"pending": Pending,
|
||||
"no_data": NoData,
|
||||
"paused": Paused,
|
||||
"ok": Ok,
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the enum as a quoted json string
|
||||
func (s AlertState) MarshalJSON() ([]byte, error) {
|
||||
buffer := bytes.NewBufferString(`"`)
|
||||
buffer.WriteString(toString[s])
|
||||
buffer.WriteString(`"`)
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmashals a quoted json string to the enum value
|
||||
func (s *AlertState) UnmarshalJSON(b []byte) error {
|
||||
var j string
|
||||
err := json.Unmarshal(b, &j)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if we somehow get an invalid value we'll end up in the alerting state
|
||||
*s = toID[j]
|
||||
return nil
|
||||
}
|
||||
|
||||
type Alert struct {
|
||||
Name string `json:"name"`
|
||||
State AlertState `json:"state"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
apiKey string
|
||||
baseURI string
|
||||
}
|
||||
|
||||
func NewClient(settings *Settings) *Client {
|
||||
return &Client{
|
||||
apiKey: settings.apiKey,
|
||||
baseURI: settings.baseURI,
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) Alerts() ([]Alert, error) {
|
||||
// query the alerts API of Grafana https://grafana.com/docs/grafana/latest/http_api/alerting/
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/alerts", client.baseURI), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if client.apiKey != "" {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", client.apiKey))
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = res.Body.Close() }()
|
||||
|
||||
var out []Alert
|
||||
err = utils.ParseJSON(&out, res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
return out[i].State < out[j].State
|
||||
})
|
||||
|
||||
return out, nil
|
||||
}
|
57
modules/grafana/display.go
Normal file
57
modules/grafana/display.go
Normal file
@ -0,0 +1,57 @@
|
||||
package grafana
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (widget *Widget) content() (string, string, bool) {
|
||||
title := widget.CommonSettings().Title
|
||||
|
||||
var out string
|
||||
if widget.Err != nil {
|
||||
return title, widget.Err.Error(), false
|
||||
} else {
|
||||
for idx, alert := range widget.Alerts {
|
||||
out += fmt.Sprintf(` ["%d"][%s]%s - %s[""]`,
|
||||
idx,
|
||||
stateColor(alert.State),
|
||||
stateToEmoji(alert.State),
|
||||
alert.Name,
|
||||
)
|
||||
out += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
return title, out, false
|
||||
}
|
||||
|
||||
func stateColor(state AlertState) string {
|
||||
switch state {
|
||||
case Ok:
|
||||
return "green"
|
||||
case Paused:
|
||||
return "yellow"
|
||||
case Alerting:
|
||||
return "red"
|
||||
case Pending:
|
||||
return "orange"
|
||||
case NoData:
|
||||
return "yellow"
|
||||
default:
|
||||
return "white"
|
||||
}
|
||||
}
|
||||
|
||||
func stateToEmoji(state AlertState) string {
|
||||
switch state {
|
||||
case Ok:
|
||||
return "✔"
|
||||
case Paused:
|
||||
return "⏸"
|
||||
case Alerting:
|
||||
return "✘"
|
||||
case Pending:
|
||||
return "?"
|
||||
case NoData:
|
||||
return "?"
|
||||
}
|
||||
return ""
|
||||
}
|
10
modules/grafana/keyboard.go
Normal file
10
modules/grafana/keyboard.go
Normal file
@ -0,0 +1,10 @@
|
||||
package grafana
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
func (widget *Widget) initializeKeyboardControls() {
|
||||
widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous alert")
|
||||
widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next alert")
|
||||
widget.SetKeyboardKey(tcell.KeyEnter, widget.openAlert, "Open alert in browser")
|
||||
widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection")
|
||||
}
|
40
modules/grafana/settings.go
Normal file
40
modules/grafana/settings.go
Normal file
@ -0,0 +1,40 @@
|
||||
package grafana
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFocusable = true
|
||||
defaultTitle = "Grafana"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
common *cfg.Common
|
||||
|
||||
apiKey string `help:"Your Grafana API token."`
|
||||
baseURI string `help:"Base url of your grafana instance"`
|
||||
}
|
||||
|
||||
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_GRAFANA_API_KEY")),
|
||||
baseURI: ymlConfig.UString("baseUri", ""),
|
||||
}
|
||||
|
||||
if settings.baseURI == "" {
|
||||
log.Fatal("baseUri for grafana is empty, but is required")
|
||||
} else {
|
||||
settings.baseURI = strings.TrimSuffix(settings.baseURI, "/")
|
||||
}
|
||||
|
||||
return &settings
|
||||
}
|
107
modules/grafana/widget.go
Normal file
107
modules/grafana/widget.go
Normal file
@ -0,0 +1,107 @@
|
||||
package grafana
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
"github.com/wtfutil/wtf/view"
|
||||
)
|
||||
|
||||
type Widget struct {
|
||||
view.KeyboardWidget
|
||||
view.TextWidget
|
||||
|
||||
Client *Client
|
||||
Alerts []Alert
|
||||
Err error
|
||||
Selected int
|
||||
|
||||
settings *Settings
|
||||
}
|
||||
|
||||
func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget {
|
||||
widget := Widget{
|
||||
KeyboardWidget: view.NewKeyboardWidget(app, pages, settings.common),
|
||||
TextWidget: view.NewTextWidget(app, settings.common),
|
||||
|
||||
Client: NewClient(settings),
|
||||
Selected: -1,
|
||||
|
||||
settings: settings,
|
||||
}
|
||||
|
||||
widget.initializeKeyboardControls()
|
||||
widget.View.SetRegions(true)
|
||||
widget.View.SetInputCapture(widget.InputCapture)
|
||||
|
||||
widget.KeyboardWidget.SetView(widget.View)
|
||||
|
||||
return &widget
|
||||
}
|
||||
|
||||
/* -------------------- Exported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) Refresh() {
|
||||
alerts, err := widget.Client.Alerts()
|
||||
if err != nil {
|
||||
widget.Err = err
|
||||
widget.Alerts = nil
|
||||
} else {
|
||||
widget.Err = nil
|
||||
widget.Alerts = alerts
|
||||
}
|
||||
|
||||
widget.Redraw(widget.content)
|
||||
}
|
||||
|
||||
// GetSelected returns the index of the currently highlighted item as an int
|
||||
func (widget *Widget) GetSelected() int {
|
||||
if widget.Selected < 0 {
|
||||
return 0
|
||||
}
|
||||
return widget.Selected
|
||||
}
|
||||
|
||||
// Next cycles the currently highlighted text down
|
||||
func (widget *Widget) Next() {
|
||||
widget.Selected++
|
||||
if widget.Selected >= len(widget.Alerts) {
|
||||
widget.Selected = 0
|
||||
}
|
||||
widget.View.Highlight(strconv.Itoa(widget.Selected)).ScrollToHighlight()
|
||||
}
|
||||
|
||||
// Prev cycles the currently highlighted text up
|
||||
func (widget *Widget) Prev() {
|
||||
widget.Selected--
|
||||
if widget.Selected < 0 {
|
||||
widget.Selected = len(widget.Alerts) - 1
|
||||
}
|
||||
widget.View.Highlight(strconv.Itoa(widget.Selected)).ScrollToHighlight()
|
||||
}
|
||||
|
||||
// Unselect stops highlighting the text and jumps the scroll position to the top
|
||||
func (widget *Widget) Unselect() {
|
||||
widget.Selected = -1
|
||||
widget.View.Highlight()
|
||||
widget.View.ScrollToBeginning()
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) HelpText() string {
|
||||
return widget.KeyboardWidget.HelpText()
|
||||
}
|
||||
|
||||
func (widget *Widget) openAlert() {
|
||||
currentSelection := widget.View.GetHighlights()
|
||||
if widget.Selected >= 0 && currentSelection[0] != "" {
|
||||
url := widget.Alerts[widget.GetSelected()].URL
|
||||
if url[0] == '/' {
|
||||
url = fmt.Sprintf("%s%s", widget.settings.baseURI, url)
|
||||
}
|
||||
utils.OpenFile(url)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user