Add NetworkDevices widget to display and manage network devices
This commit is contained in:
parent
9bcb0b15f7
commit
1cf70b65ba
1
.gitignore
vendored
1
.gitignore
vendored
@ -107,4 +107,5 @@ go.work.sum
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
config.toml
|
config.toml
|
||||||
|
devices.json
|
||||||
old
|
old
|
||||||
|
|||||||
96
apis/networking/main.go
Normal file
96
apis/networking/main.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package networking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const deviceFile = "devices.json"
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Interface string `json:"interface"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDevices() map[string]map[string]Device {
|
||||||
|
result := make(map[string]map[string]Device)
|
||||||
|
devices := make(map[string]Device)
|
||||||
|
arpDevices := arpDevices()
|
||||||
|
knownDevices := knownDevices()
|
||||||
|
for mac, device := range arpDevices {
|
||||||
|
if strings.HasPrefix(device.Interface, "macvlan") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if known, ok := knownDevices[mac]; ok {
|
||||||
|
devices[mac] = Device{Name: known.Name, IP: device.IP, Interface: device.Interface, Icon: known.Icon}
|
||||||
|
} else {
|
||||||
|
devices[mac] = device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for mac, device := range knownDevices {
|
||||||
|
if _, ok := devices[mac]; !ok {
|
||||||
|
devices[mac] = Device{Name: device.Name, IP: device.IP, Interface: "offline", Icon: device.Icon}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeDevices(devices)
|
||||||
|
|
||||||
|
for mac, device := range devices {
|
||||||
|
if _, ok := result[device.Interface]; !ok {
|
||||||
|
result[device.Interface] = make(map[string]Device)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(device.Interface, "br") {
|
||||||
|
device.Icon = "\uE7B0"
|
||||||
|
device.Interface = "bridge"
|
||||||
|
}
|
||||||
|
if device.Icon == "" {
|
||||||
|
device.Icon = "\U000F0C8A"
|
||||||
|
}
|
||||||
|
result[device.Interface][mac] = device
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDevices(devices map[string]Device) {
|
||||||
|
marshal, err := json.MarshalIndent(devices, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(deviceFile, marshal, 0644); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func knownDevices() map[string]Device {
|
||||||
|
knownDevices := make(map[string]Device)
|
||||||
|
file, err := os.ReadFile(deviceFile)
|
||||||
|
if err == nil {
|
||||||
|
if err := json.Unmarshal(file, &knownDevices); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return knownDevices
|
||||||
|
}
|
||||||
|
|
||||||
|
func arpDevices() map[string]Device {
|
||||||
|
arp := exec.Command("arp", "-a")
|
||||||
|
var out bytes.Buffer
|
||||||
|
arp.Stdout = &out
|
||||||
|
if err := arp.Run(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
res := out.String()
|
||||||
|
lines := strings.Split(res, "\n")
|
||||||
|
devices := make(map[string]Device)
|
||||||
|
for l := range lines {
|
||||||
|
entry := strings.Fields(lines[l])
|
||||||
|
if len(entry) > 6 {
|
||||||
|
devices[entry[3]] = Device{Name: entry[0], IP: strings.ReplaceAll(strings.ReplaceAll(entry[1], "(", ""), ")", ""), Interface: entry[6]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return devices
|
||||||
|
}
|
||||||
7
main.go
7
main.go
@ -36,6 +36,7 @@ func initWidgets(ctx context.Context, term *tcell.Terminal) {
|
|||||||
widgets.New("Date", widgets.Date()),
|
widgets.New("Date", widgets.Date()),
|
||||||
widgets.New("ChangeTitle", widgets.TextInput(titleUpdate, textinput.Label("Update Title: "), textinput.PlaceHolder("New Title"))),
|
widgets.New("ChangeTitle", widgets.TextInput(titleUpdate, textinput.Label("Update Title: "), textinput.PlaceHolder("New Title"))),
|
||||||
widgets.New("Wifi", widgets.WifiQRCode()),
|
widgets.New("Wifi", widgets.WifiQRCode()),
|
||||||
|
widgets.New("NetworkDevices", widgets.NetworkDevices()),
|
||||||
widgets.New("Docker", widgets.DockerList()),
|
widgets.New("Docker", widgets.DockerList()),
|
||||||
widgets.New("PiHole", widgets.PiholeStats()),
|
widgets.New("PiHole", widgets.PiholeStats()),
|
||||||
widgets.New("PiHoleBlocked", widgets.PiholeBlocked()),
|
widgets.New("PiHoleBlocked", widgets.PiholeBlocked()),
|
||||||
@ -52,6 +53,12 @@ func layout() []container.Option {
|
|||||||
container.Border(linestyle.Light),
|
container.Border(linestyle.Light),
|
||||||
container.BorderColor(cell.ColorWhite)),
|
container.BorderColor(cell.ColorWhite)),
|
||||||
),
|
),
|
||||||
|
grid.RowHeightFixed(44,
|
||||||
|
grid.Widget(widgets.Get["NetworkDevices"],
|
||||||
|
container.BorderTitle("Network Devices"),
|
||||||
|
container.Border(linestyle.Light),
|
||||||
|
container.BorderColor(cell.ColorWhite)),
|
||||||
|
),
|
||||||
grid.RowHeightFixed(1,
|
grid.RowHeightFixed(1,
|
||||||
grid.Widget(widgets.Get["empty"]),
|
grid.Widget(widgets.Get["empty"]),
|
||||||
),
|
),
|
||||||
|
|||||||
70
widgets/networkdevices.go
Normal file
70
widgets/networkdevices.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package widgets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ArinDash/apis/networking"
|
||||||
|
"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 NetworkDevicesOptions struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkDevices() NetworkDevicesOptions {
|
||||||
|
widgetOptions["NetworkDevicesOptions"] = createNetworkDevicesList
|
||||||
|
return NetworkDevicesOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNetworkDevicesList(ctx context.Context, _ terminalapi.Terminal, _ interface{}) widgetapi.Widget {
|
||||||
|
list := util.PanicOnErrorWithResult(text.New())
|
||||||
|
go util.Periodic(ctx, 20*time.Second, func() error {
|
||||||
|
interfaces := networking.GetDevices()
|
||||||
|
list.Reset()
|
||||||
|
for iface, devices := range interfaces {
|
||||||
|
status := cell.ColorGreen
|
||||||
|
if iface == "offline" {
|
||||||
|
status = cell.ColorGray
|
||||||
|
}
|
||||||
|
if err := list.Write(fmt.Sprintf("=== %s ===\n", iface)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, mac := range sortedKeys(devices) {
|
||||||
|
if err := list.Write(fmt.Sprintf("|%-15s|", mac), text.WriteCellOpts(cell.FgColor(cell.ColorGray))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := list.Write(fmt.Sprintf("%2s ", devices[mac].Icon), text.WriteCellOpts(cell.FgColor(status))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := list.Write(fmt.Sprintf(" %s", devices[mac].Name), text.WriteCellOpts(cell.FgColor(cell.ColorWhite))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := list.Write(fmt.Sprintf(" (%s)\n", devices[mac].IP), text.WriteCellOpts(cell.FgColor(cell.ColorGray))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedKeys(devices map[string]networking.Device) []string {
|
||||||
|
macs := make([]string, 0, len(devices))
|
||||||
|
for mac := range devices {
|
||||||
|
macs = append(macs, mac)
|
||||||
|
}
|
||||||
|
sort.SliceStable(macs, func(i, j int) bool {
|
||||||
|
return strings.Compare(devices[macs[i]].Name, devices[macs[j]].Name) < 0
|
||||||
|
})
|
||||||
|
return macs
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user