From b1a31ebe7dcfea208d914e30310d2d638c20ccf7 Mon Sep 17 00:00:00 2001 From: Chris Roy Date: Thu, 19 Mar 2026 23:31:10 -0400 Subject: [PATCH 1/6] added a network monitor module --- app/widget_maker.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/widget_maker.go b/app/widget_maker.go index 1362d8cbc..518a7aad5 100644 --- a/app/widget_maker.go +++ b/app/widget_maker.go @@ -50,6 +50,7 @@ import ( "github.com/wtfutil/wtf/modules/lunarphase" "github.com/wtfutil/wtf/modules/mercurial" "github.com/wtfutil/wtf/modules/nbascore" + "github.com/wtfutil/wtf/modules/netmon" "github.com/wtfutil/wtf/modules/newrelic" "github.com/wtfutil/wtf/modules/nextbus" "github.com/wtfutil/wtf/modules/opsgenie" @@ -260,6 +261,9 @@ func MakeWidget( case "nbascore": settings := nbascore.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = nbascore.NewWidget(tviewApp, redrawChan, pages, settings) + case "netmon": + settings := netmon.NewSettingsFromYAML(moduleName, moduleConfig, config) + widget = netmon.NewWidget(tviewApp, redrawChan, settings) case "newrelic": settings := newrelic.NewSettingsFromYAML(moduleName, moduleConfig, config) widget = newrelic.NewWidget(tviewApp, redrawChan, pages, settings) From 2a766b544ec1fa61b224b00131913bdc68b97293 Mon Sep 17 00:00:00 2001 From: Chris Roy Date: Thu, 19 Mar 2026 23:40:45 -0400 Subject: [PATCH 2/6] . --- modules/netmon/example-config.yml | 38 ++++++++++++++++ modules/netmon/settings.go | 33 ++++++++++++++ modules/netmon/widget.go | 76 +++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 modules/netmon/example-config.yml create mode 100644 modules/netmon/settings.go create mode 100644 modules/netmon/widget.go diff --git a/modules/netmon/example-config.yml b/modules/netmon/example-config.yml new file mode 100644 index 000000000..0352dc7fc --- /dev/null +++ b/modules/netmon/example-config.yml @@ -0,0 +1,38 @@ +wtf: + colors: + # background: black + # foreground: blue + border: + focusable: darkslateblue + focused: orange + normal: gray + checked: yellow + highlight: + fore: black + back: gray + rows: + even: yellow + odd: white + grid: + # How _wide_ the columns are, in terminal characters. In this case we have + # four columns, each of which are 35 characters wide. + # columns: [50, ] + # How _high_ the rows are, in terminal lines. In this case we have four rows + # that support ten line of text and one of four. + # rows: [50] + refreshInterval: 1 + mods: + netmon: + type: netmon + title: "Network Monitor" + enabled: true + ignoreLoopback: true + ignoreBridges: true + ignoreDocker: true + ignoreVETH: true + position: + top: 0 + left: 0 + height: 2 + width: 2 + refreshInterval: 1 diff --git a/modules/netmon/settings.go b/modules/netmon/settings.go new file mode 100644 index 000000000..a5534fe64 --- /dev/null +++ b/modules/netmon/settings.go @@ -0,0 +1,33 @@ +package netmon + +import ( + "github.com/olebedev/config" + "github.com/wtfutil/wtf/cfg" +) + +const ( + defaultFocusable = false + defaultTitle = "Network Monitor" +) + +type Settings struct { + *cfg.Common + + ignoreLoopback bool + ignoreBridges bool + ignoreDocker bool + ignoreVETH bool +} + +func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { + settings := Settings{ + Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), + + ignoreLoopback: ymlConfig.UBool("ignoreLoopback"), + ignoreBridges: ymlConfig.UBool("ignoreBridges"), + ignoreDocker: ymlConfig.UBool("ignoreDocker"), + ignoreVETH: ymlConfig.UBool("ignoreVETH"), + } + + return &settings +} diff --git a/modules/netmon/widget.go b/modules/netmon/widget.go new file mode 100644 index 000000000..e84746a6a --- /dev/null +++ b/modules/netmon/widget.go @@ -0,0 +1,76 @@ +package netmon + +import ( + "fmt" + "strings" + + "github.com/shirou/gopsutil/net" + + "github.com/rivo/tview" + "github.com/wtfutil/wtf/view" +) + +type Widget struct { + view.TextWidget + settings *Settings + interfaces map[string]*NIC +} + +type NIC struct { + Name string + Sent uint64 + Recv uint64 +} + +func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Settings) *Widget { + widget := Widget{ + TextWidget: view.NewTextWidget(tviewApp, redrawChan, nil, settings.Common), + settings: settings, + } + + widget.SetupNICs() + widget.View.SetWrap(true) + + return &widget +} +func (widget *Widget) SetupNICs() { + widget.interfaces = make(map[string]*NIC) + interfaces, _ := net.IOCounters(true) + + for _, nic := range interfaces { + if widget.settings.ignoreLoopback && strings.HasPrefix(nic.Name, "lo") || + widget.settings.ignoreBridges && strings.HasPrefix(nic.Name, "br") || + widget.settings.ignoreDocker && strings.HasPrefix(nic.Name, "docker") || + widget.settings.ignoreVETH && strings.HasPrefix(nic.Name, "veth") { + continue + } + widget.interfaces[nic.Name] = &NIC{ + Name: nic.Name, + Sent: nic.BytesSent, + Recv: nic.BytesRecv} + } +} +func (widget *Widget) Refresh() { + widget.Redraw(widget.content) +} + +func (widget *Widget) content() (string, string, bool) { + var content strings.Builder + + interfaces, err := net.IOCounters(true) + if err != nil { + return widget.CommonSettings().Title, err.Error(), true + } + for _, nic := range interfaces { + if _, ok := widget.interfaces[nic.Name]; ok { + prevSent := widget.interfaces[nic.Name].Sent + prevRecv := widget.interfaces[nic.Name].Recv + fmt.Fprintf(&content, "%s:\t%5v▲ \t%5v▼\n", nic.Name, (nic.BytesSent-prevSent)/1024, (nic.BytesRecv-prevRecv)/1024) + widget.interfaces[nic.Name].Sent = nic.BytesSent + widget.interfaces[nic.Name].Recv = nic.BytesRecv + } + + } + + return widget.CommonSettings().Title, content.String(), true +} From 70612e8077d235aa8c9ad7fc88f4d8a945a6fc7f Mon Sep 17 00:00:00 2001 From: Chris Roy Date: Sat, 21 Mar 2026 20:44:13 -0400 Subject: [PATCH 3/6] make pretty --- modules/netmon/widget.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/modules/netmon/widget.go b/modules/netmon/widget.go index e84746a6a..9b1d78033 100644 --- a/modules/netmon/widget.go +++ b/modules/netmon/widget.go @@ -65,12 +65,28 @@ func (widget *Widget) content() (string, string, bool) { if _, ok := widget.interfaces[nic.Name]; ok { prevSent := widget.interfaces[nic.Name].Sent prevRecv := widget.interfaces[nic.Name].Recv - fmt.Fprintf(&content, "%s:\t%5v▲ \t%5v▼\n", nic.Name, (nic.BytesSent-prevSent)/1024, (nic.BytesRecv-prevRecv)/1024) widget.interfaces[nic.Name].Sent = nic.BytesSent widget.interfaces[nic.Name].Recv = nic.BytesRecv + fmt.Fprintf(&content, "%s:\t▲%9s \t▼%9s\n", + nic.Name, + pretty(nic.BytesSent-prevSent), + pretty(nic.BytesRecv-prevRecv)) } } return widget.CommonSettings().Title, content.String(), true } + +func pretty(bytes uint64) string { + bits := bytes * 8 + bps := "bps" + if bits > 1000000 { + bits = bits / 1000000 + bps = "M" + bps + } else if bits > 1000 { + bits = bits / 1000 + bps = "K" + bps + } + return fmt.Sprintf("%v %s", bits, bps) +} From 0c9b4a34d28df1ea0c3a80ed16115fd0e85487c5 Mon Sep 17 00:00:00 2001 From: Chris Roy Date: Sat, 21 Mar 2026 21:05:44 -0400 Subject: [PATCH 4/6] code cleanup, needs more --- modules/netmon/widget.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/netmon/widget.go b/modules/netmon/widget.go index 9b1d78033..299cb89dc 100644 --- a/modules/netmon/widget.go +++ b/modules/netmon/widget.go @@ -62,19 +62,15 @@ func (widget *Widget) content() (string, string, bool) { return widget.CommonSettings().Title, err.Error(), true } for _, nic := range interfaces { - if _, ok := widget.interfaces[nic.Name]; ok { - prevSent := widget.interfaces[nic.Name].Sent - prevRecv := widget.interfaces[nic.Name].Recv - widget.interfaces[nic.Name].Sent = nic.BytesSent - widget.interfaces[nic.Name].Recv = nic.BytesRecv + if NIC, ok := widget.interfaces[nic.Name]; ok { + prevSent, prevRecv := NIC.Sent, NIC.Recv + NIC.Sent, NIC.Recv = nic.BytesSent, nic.BytesRecv fmt.Fprintf(&content, "%s:\t▲%9s \t▼%9s\n", nic.Name, pretty(nic.BytesSent-prevSent), pretty(nic.BytesRecv-prevRecv)) } - } - return widget.CommonSettings().Title, content.String(), true } @@ -82,10 +78,10 @@ func pretty(bytes uint64) string { bits := bytes * 8 bps := "bps" if bits > 1000000 { - bits = bits / 1000000 + bits /= 1000000 bps = "M" + bps } else if bits > 1000 { - bits = bits / 1000 + bits /= 1000 bps = "K" + bps } return fmt.Sprintf("%v %s", bits, bps) From 1868ce84c2c5e859122e2e498dd9aab397446729 Mon Sep 17 00:00:00 2001 From: Chris Roy Date: Sat, 21 Mar 2026 23:07:58 -0400 Subject: [PATCH 5/6] added show only interface, and ignore ethernet --- modules/netmon/example-config.yml | 8 +++++--- modules/netmon/settings.go | 12 ++++++++++-- modules/netmon/widget.go | 27 ++++++++++++++++++++++----- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/modules/netmon/example-config.yml b/modules/netmon/example-config.yml index 0352dc7fc..053254b59 100644 --- a/modules/netmon/example-config.yml +++ b/modules/netmon/example-config.yml @@ -26,10 +26,12 @@ wtf: type: netmon title: "Network Monitor" enabled: true - ignoreLoopback: true + showOnly: "" + ignoreLoopback: false + ignoreEthernet: false ignoreBridges: true - ignoreDocker: true - ignoreVETH: true + ignoreDocker: false + ignoreVeth: true position: top: 0 left: 0 diff --git a/modules/netmon/settings.go b/modules/netmon/settings.go index a5534fe64..de0309a72 100644 --- a/modules/netmon/settings.go +++ b/modules/netmon/settings.go @@ -13,20 +13,28 @@ const ( type Settings struct { *cfg.Common + showOnly string + ignoreLoopback bool + ignoreEthernet bool + ignoreWireless bool ignoreBridges bool ignoreDocker bool - ignoreVETH bool + ignoreVeth bool } func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { settings := Settings{ Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), + showOnly: ymlConfig.UString("showOnly"), + ignoreLoopback: ymlConfig.UBool("ignoreLoopback"), + ignoreEthernet: ymlConfig.UBool("ignoreEthernet"), + ignoreWireless: ymlConfig.UBool("ignoreWireless"), ignoreBridges: ymlConfig.UBool("ignoreBridges"), ignoreDocker: ymlConfig.UBool("ignoreDocker"), - ignoreVETH: ymlConfig.UBool("ignoreVETH"), + ignoreVeth: ymlConfig.UBool("ignoreVeth"), } return &settings diff --git a/modules/netmon/widget.go b/modules/netmon/widget.go index 299cb89dc..37a797b20 100644 --- a/modules/netmon/widget.go +++ b/modules/netmon/widget.go @@ -33,23 +33,32 @@ func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Sett return &widget } + func (widget *Widget) SetupNICs() { widget.interfaces = make(map[string]*NIC) interfaces, _ := net.IOCounters(true) for _, nic := range interfaces { if widget.settings.ignoreLoopback && strings.HasPrefix(nic.Name, "lo") || + widget.settings.ignoreEthernet && strings.HasPrefix(nic.Name, "e") || + widget.settings.ignoreWireless && strings.HasPrefix(nic.Name, "wl") || widget.settings.ignoreBridges && strings.HasPrefix(nic.Name, "br") || widget.settings.ignoreDocker && strings.HasPrefix(nic.Name, "docker") || - widget.settings.ignoreVETH && strings.HasPrefix(nic.Name, "veth") { + widget.settings.ignoreVeth && strings.HasPrefix(nic.Name, "veth") { continue } - widget.interfaces[nic.Name] = &NIC{ - Name: nic.Name, - Sent: nic.BytesSent, - Recv: nic.BytesRecv} + if widget.settings.showOnly != "" { + if widget.settings.showOnly == nic.Name { + widget.addInterface(nic) + break + } + } else { + widget.addInterface(nic) + } + } } + func (widget *Widget) Refresh() { widget.Redraw(widget.content) } @@ -74,6 +83,14 @@ func (widget *Widget) content() (string, string, bool) { return widget.CommonSettings().Title, content.String(), true } +func (widget *Widget) addInterface(nic net.IOCountersStat) { + widget.interfaces[nic.Name] = &NIC{ + Name: nic.Name, + Sent: nic.BytesSent, + Recv: nic.BytesRecv, + } +} + func pretty(bytes uint64) string { bits := bytes * 8 bps := "bps" From 8f5ae8a6d885df8145cce55b26f189ab00c41c0d Mon Sep 17 00:00:00 2001 From: Chris Roy Date: Mon, 23 Mar 2026 19:26:40 -0400 Subject: [PATCH 6/6] More cleanup of display --- modules/netmon/example-config.yml | 16 ++++++---------- modules/netmon/widget.go | 19 ++++++++++++------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/netmon/example-config.yml b/modules/netmon/example-config.yml index 053254b59..8188d2754 100644 --- a/modules/netmon/example-config.yml +++ b/modules/netmon/example-config.yml @@ -14,20 +14,16 @@ wtf: even: yellow odd: white grid: - # How _wide_ the columns are, in terminal characters. In this case we have - # four columns, each of which are 35 characters wide. - # columns: [50, ] - # How _high_ the rows are, in terminal lines. In this case we have four rows - # that support ten line of text and one of four. - # rows: [50] + columns: [35] + rows: [8] refreshInterval: 1 mods: netmon: type: netmon title: "Network Monitor" enabled: true - showOnly: "" - ignoreLoopback: false + showOnly: "wlo1" + ignoreLoopback: true ignoreEthernet: false ignoreBridges: true ignoreDocker: false @@ -35,6 +31,6 @@ wtf: position: top: 0 left: 0 - height: 2 - width: 2 + height: 1 + width: 1 refreshInterval: 1 diff --git a/modules/netmon/widget.go b/modules/netmon/widget.go index 37a797b20..396b5fabd 100644 --- a/modules/netmon/widget.go +++ b/modules/netmon/widget.go @@ -29,8 +29,6 @@ func NewWidget(tviewApp *tview.Application, redrawChan chan bool, settings *Sett } widget.SetupNICs() - widget.View.SetWrap(true) - return &widget } @@ -74,10 +72,10 @@ func (widget *Widget) content() (string, string, bool) { if NIC, ok := widget.interfaces[nic.Name]; ok { prevSent, prevRecv := NIC.Sent, NIC.Recv NIC.Sent, NIC.Recv = nic.BytesSent, nic.BytesRecv - fmt.Fprintf(&content, "%s:\t▲%9s \t▼%9s\n", - nic.Name, - pretty(nic.BytesSent-prevSent), - pretty(nic.BytesRecv-prevRecv)) + fmt.Fprintf(&content, "%-9s▲%9s ▼%9s\n", + PrettyName(nic.Name), + prettyBits(nic.BytesSent-prevSent), + prettyBits(nic.BytesRecv-prevRecv)) } } return widget.CommonSettings().Title, content.String(), true @@ -91,7 +89,14 @@ func (widget *Widget) addInterface(nic net.IOCountersStat) { } } -func pretty(bytes uint64) string { +func PrettyName(name string) string { + if len(name) > 7 { + name = name[:6] + "…" + } + return name + ":" +} + +func prettyBits(bytes uint64) string { bits := bytes * 8 bps := "bps" if bits > 1000000 {