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) diff --git a/modules/netmon/example-config.yml b/modules/netmon/example-config.yml new file mode 100644 index 000000000..8188d2754 --- /dev/null +++ b/modules/netmon/example-config.yml @@ -0,0 +1,36 @@ +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: + columns: [35] + rows: [8] + refreshInterval: 1 + mods: + netmon: + type: netmon + title: "Network Monitor" + enabled: true + showOnly: "wlo1" + ignoreLoopback: true + ignoreEthernet: false + ignoreBridges: true + ignoreDocker: false + ignoreVeth: true + position: + top: 0 + left: 0 + height: 1 + width: 1 + refreshInterval: 1 diff --git a/modules/netmon/settings.go b/modules/netmon/settings.go new file mode 100644 index 000000000..de0309a72 --- /dev/null +++ b/modules/netmon/settings.go @@ -0,0 +1,41 @@ +package netmon + +import ( + "github.com/olebedev/config" + "github.com/wtfutil/wtf/cfg" +) + +const ( + defaultFocusable = false + defaultTitle = "Network Monitor" +) + +type Settings struct { + *cfg.Common + + showOnly string + + ignoreLoopback bool + ignoreEthernet bool + ignoreWireless 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), + + 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"), + } + + return &settings +} diff --git a/modules/netmon/widget.go b/modules/netmon/widget.go new file mode 100644 index 000000000..396b5fabd --- /dev/null +++ b/modules/netmon/widget.go @@ -0,0 +1,110 @@ +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() + 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") { + continue + } + 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) +} + +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 NIC, ok := widget.interfaces[nic.Name]; ok { + prevSent, prevRecv := NIC.Sent, NIC.Recv + NIC.Sent, NIC.Recv = nic.BytesSent, nic.BytesRecv + 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 +} + +func (widget *Widget) addInterface(nic net.IOCountersStat) { + widget.interfaces[nic.Name] = &NIC{ + Name: nic.Name, + Sent: nic.BytesSent, + Recv: nic.BytesRecv, + } +} + +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 { + bits /= 1000000 + bps = "M" + bps + } else if bits > 1000 { + bits /= 1000 + bps = "K" + bps + } + return fmt.Sprintf("%v %s", bits, bps) +}