From a83958500ed598124927ccfb730d8721ac18ca76 Mon Sep 17 00:00:00 2001 From: Arindy Date: Sun, 28 Dec 2025 14:25:14 +0100 Subject: [PATCH] Add HTTPProber widget and API to display website statuses --- .gitignore | 2 + apis/httpprober/main.go | 86 +++++++++++++++++++++++++++++++++++++++++ main.go | 8 ++++ widgets/httpProber.go | 64 ++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 apis/httpprober/main.go create mode 100644 widgets/httpProber.go diff --git a/.gitignore b/.gitignore index 873a0b6..7a7940c 100644 --- a/.gitignore +++ b/.gitignore @@ -108,4 +108,6 @@ go.work.sum config.toml devices.json +websites.json + old diff --git a/apis/httpprober/main.go b/apis/httpprober/main.go new file mode 100644 index 0000000..e49029b --- /dev/null +++ b/apis/httpprober/main.go @@ -0,0 +1,86 @@ +package httpprober + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" +) + +const websitesFile = "websites.json" + +type Website struct { + URL string `json:"url"` + Icon string `json:"icon"` + Status int `json:"status"` +} + +func RunProbe() []Website { + results := make([]Website, 0) + websites := websites() + + client, err := buildHTTPClient() + if err != nil { + return websites + } + + for _, website := range websites { + if website.URL != "" && !strings.Contains(website.URL, "://") { + website.URL = "https://" + website.URL + } + website.Status = testMethod(client, website) + results = append(results, website) + } + + return results +} + +func websites() []Website { + websites := make([]Website, 0) + file, err := os.ReadFile(websitesFile) + if err == nil { + if err := json.Unmarshal(file, &websites); err != nil { + panic(err) + } + } + return websites +} + +func buildHTTPClient() (*http.Client, error) { + timeout := 10 + + client := &http.Client{ + Timeout: time.Duration(timeout) * time.Second, + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{}, + } + + client.Transport = transport + + return client, nil +} + +func testMethod(client *http.Client, target Website) int { + req, err := http.NewRequest("GET", target.URL, nil) + if err != nil { + fmt.Println(err) + return -2 + } + + resp, err := client.Do(req) + if err != nil { + fmt.Println(err) + return -1 + } + defer func(body io.ReadCloser) { + _ = body.Close() + }(resp.Body) + + return resp.StatusCode +} diff --git a/main.go b/main.go index 000f8ca..26d2ec8 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ func initWidgets(ctx context.Context, term *tcell.Terminal) { widgets.New("Wifi", widgets.WifiQRCode()), widgets.New("NetworkDevices", widgets.NetworkDevices()), widgets.New("Docker", widgets.DockerList()), + widgets.New("HTTPProber", widgets.HTTPProber()), widgets.New("PiHole", widgets.PiholeStats()), widgets.New("PiHoleBlocked", widgets.PiholeBlocked()), ) @@ -104,6 +105,13 @@ func layout() []container.Option { ), ), grid.ColWidthPerc(35, + grid.RowHeightPerc(20, + grid.Widget(widgets.Get["HTTPProber"], + container.BorderTitle("Website Status"), + container.Border(linestyle.Light), + container.BorderColor(cell.ColorWhite), + ), + ), grid.RowHeightPerc(25, grid.Widget(widgets.Get["Docker"], container.BorderTitle("Docker"), diff --git a/widgets/httpProber.go b/widgets/httpProber.go new file mode 100644 index 0000000..b83081f --- /dev/null +++ b/widgets/httpProber.go @@ -0,0 +1,64 @@ +package widgets + +import ( + "ArinDash/apis/httpprober" + "ArinDash/util" + "context" + "fmt" + "sort" + "strings" + "time" + + "github.com/mum4k/termdash/cell" + "github.com/mum4k/termdash/terminal/terminalapi" + "github.com/mum4k/termdash/widgetapi" + "github.com/mum4k/termdash/widgets/text" +) + +type HTTPProberOptions struct { +} + +func HTTPProber() HTTPProberOptions { + widgetOptions["HTTPProberOptions"] = createHTTPProberList + return HTTPProberOptions{} +} + +func createHTTPProberList(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget { + list := util.PanicOnErrorWithResult(text.New()) + go util.Periodic(ctx, 1*time.Minute, func() error { + websites := httpprober.RunProbe() + sort.Slice(websites, func(i, j int) bool { + return websites[i].Status < websites[j].Status + }) + sort.Slice(websites, func(i, j int) bool { + return websites[i].URL < websites[j].URL + }) + list.Reset() + for _, website := range websites { + var status []cell.Option + if website.Status >= 500 { + status = append(status, cell.FgColor(cell.ColorRed)) + status = append(status, cell.BgColor(cell.ColorBlack)) + } else if website.Status >= 400 { + status = append(status, cell.FgColor(cell.ColorYellow)) + status = append(status, cell.BgColor(cell.ColorDefault)) + } else if website.Status >= 200 { + status = append(status, cell.FgColor(cell.ColorGreen)) + status = append(status, cell.BgColor(cell.ColorDefault)) + } else { + status = append(status, cell.FgColor(cell.ColorGray)) + status = append(status, cell.BgColor(cell.ColorDefault)) + } + if err := list.Write(fmt.Sprintf("%2s ", website.Icon), text.WriteCellOpts(status...)); err != nil { + return err + } + if err := list.Write(fmt.Sprintf("%s\n", strings.Split(website.URL, "://")[1]), text.WriteCellOpts(status...)); err != nil { + return err + } + } + + return nil + }) + + return list +}