diff --git a/go.mod b/go.mod index 0926a49..61035d9 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/aceberg/WatchYourLAN go 1.22.6 require ( + github.com/containrrr/shoutrrr v0.8.0 github.com/gin-gonic/gin v1.10.0 github.com/influxdata/influxdb-client-go/v2 v2.14.0 github.com/jmoiron/sqlx v1.4.0 @@ -17,6 +18,7 @@ require ( github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -32,6 +34,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index dba30ef..20c6ce7 100644 --- a/go.sum +++ b/go.sum @@ -12,12 +12,16 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec= +github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -28,6 +32,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -38,8 +44,12 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -55,6 +65,8 @@ github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjw github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -74,6 +86,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= @@ -89,6 +104,10 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -146,6 +165,7 @@ golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= diff --git a/internal/arp/arpscan.go b/internal/arp/arpscan.go index 669e988..518aa01 100644 --- a/internal/arp/arpscan.go +++ b/internal/arp/arpscan.go @@ -1,7 +1,7 @@ package arp import ( - "log" + "log/slog" "os/exec" "strings" "time" @@ -49,8 +49,8 @@ func Scan(ifaces string) []models.Host { for _, iface := range perString { text = scanIface(iface) - log.Println("INFO: scanning interface", iface) - log.Println("INFO: found IPs:", text) + slog.Debug("Scanning interface " + iface) + slog.Debug("Found IPs: \n" + text) foundHosts = append(foundHosts, parseOutput(text, iface)...) } diff --git a/internal/conf/getconfig.go b/internal/conf/getconfig.go index 6a36647..1f18597 100644 --- a/internal/conf/getconfig.go +++ b/internal/conf/getconfig.go @@ -15,11 +15,12 @@ func Get(path string) (config models.Conf) { viper.SetDefault("THEME", "solar") viper.SetDefault("COLOR", "dark") viper.SetDefault("NODEPATH", "") - // viper.SetDefault("SCANER", "arpscan") + viper.SetDefault("LOG_LEVEL", "info") viper.SetDefault("ARP_ARGS", "") viper.SetDefault("IFACES", "") viper.SetDefault("TIMEOUT", 60) viper.SetDefault("TRIM_HIST", 48) + viper.SetDefault("SHOUTRRR_URL", "") viper.SetDefault("INFLUX_ENABLE", false) viper.SetConfigFile(path) @@ -34,11 +35,13 @@ func Get(path string) (config models.Conf) { config.Theme = viper.Get("THEME").(string) config.Color = viper.Get("COLOR").(string) config.NodePath = viper.Get("NODEPATH").(string) - // config.Scaner = viper.Get("SCANER").(string) + config.LogLevel = viper.Get("LOG_LEVEL").(string) config.ArpArgs = viper.Get("ARP_ARGS").(string) config.Ifaces = viper.Get("IFACES").(string) config.Timeout = viper.GetInt("TIMEOUT") config.TrimHist = viper.GetInt("TRIM_HIST") + config.ShoutURL = viper.Get("SHOUTRRR_URL").(string) + config.InfluxEnable = viper.GetBool("INFLUX_ENABLE") config.InfluxSkipTLS = viper.GetBool("INFLUX_SKIP_TLS") config.InfluxAddr, _ = viper.Get("INFLUX_ADDR").(string) @@ -60,11 +63,12 @@ func Write(config models.Conf) { viper.Set("THEME", config.Theme) viper.Set("COLOR", config.Color) viper.Set("NODEPATH", config.NodePath) - // viper.Set("SCANER", config.Scaner) + viper.Set("LOG_LEVEL", config.LogLevel) viper.Set("ARP_ARGS", config.ArpArgs) viper.Set("IFACES", config.Ifaces) viper.Set("TIMEOUT", config.Timeout) viper.Set("TRIM_HIST", config.TrimHist) + viper.Set("SHOUTRRR_URL", config.ShoutURL) viper.Set("influx_enable", config.InfluxEnable) viper.Set("influx_skip_tls", config.InfluxSkipTLS) diff --git a/internal/models/models.go b/internal/models/models.go index 6f517e5..a68376f 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -10,10 +10,12 @@ type Conf struct { ConfPath string DBPath string NodePath string + LogLevel string Ifaces string ArpArgs string Timeout int TrimHist int + ShoutURL string InfluxEnable bool InfluxAddr string InfluxToken string diff --git a/internal/notify/shout.go b/internal/notify/shout.go new file mode 100644 index 0000000..73c811e --- /dev/null +++ b/internal/notify/shout.go @@ -0,0 +1,16 @@ +package notify + +import ( + "github.com/containrrr/shoutrrr" + "log/slog" +) + +// Shout - send message with shoutrrr +func Shout(message string, url string) { + if url != "" { + err := shoutrrr.Send(url, message) + if err != nil { + slog.Error("Notification failed (shoutrrr): ", err) + } + } +} diff --git a/internal/web/api.go b/internal/web/api.go index 9e2d2e0..73ab84d 100644 --- a/internal/web/api.go +++ b/internal/web/api.go @@ -1,7 +1,7 @@ package web import ( - "log" + // "log" "net/http" "github.com/gin-gonic/gin" @@ -25,7 +25,6 @@ func apiHistory(c *gin.Context) { if mac != "/" { mac = mac[1:] - log.Println("MAC", mac) hosts = getHostsByMAC(mac, histHosts) } else { hosts = histHosts diff --git a/internal/web/config.go b/internal/web/config.go index b2b1578..e92fe09 100644 --- a/internal/web/config.go +++ b/internal/web/config.go @@ -35,8 +35,10 @@ func saveConfigHandler(c *gin.Context) { appConfig.Theme = c.PostForm("theme") appConfig.Color = c.PostForm("color") appConfig.NodePath = c.PostForm("node") + appConfig.LogLevel = c.PostForm("log") appConfig.ArpArgs = c.PostForm("arpargs") appConfig.Ifaces = c.PostForm("ifaces") + appConfig.ShoutURL = c.PostForm("shout") timeout := c.PostForm("timeout") trimHist := c.PostForm("trim") @@ -47,6 +49,8 @@ func saveConfigHandler(c *gin.Context) { slog.Info("writing new config to " + appConfig.ConfPath) + updateRoutines() // routines-upd.go + c.Redirect(http.StatusFound, "/config") } diff --git a/internal/web/routines-upd.go b/internal/web/routines-upd.go new file mode 100644 index 0000000..0a35bb7 --- /dev/null +++ b/internal/web/routines-upd.go @@ -0,0 +1,40 @@ +package web + +import ( + "log/slog" + + "github.com/aceberg/WatchYourLAN/internal/db" +) + +func updateRoutines() { + + close(quitScan) + + setLogLevel() + + db.Create(appConfig.DBPath) + + allHosts = db.Select(appConfig.DBPath, "now") + histHosts = db.Select(appConfig.DBPath, "history") + + quitScan = make(chan bool) + go startScan(quitScan) // scan-routine.go +} + +func setLogLevel() { + var level slog.Level + + switch appConfig.LogLevel { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + // invalid log level, handle error + } + slog.SetLogLoggerLevel(level) +} diff --git a/internal/web/scan.go b/internal/web/scan-routine.go similarity index 82% rename from internal/web/scan.go rename to internal/web/scan-routine.go index abbf5a4..9e1786f 100644 --- a/internal/web/scan.go +++ b/internal/web/scan-routine.go @@ -1,31 +1,23 @@ package web import ( - // "log/slog" + "fmt" + "log/slog" "time" "github.com/aceberg/WatchYourLAN/internal/arp" "github.com/aceberg/WatchYourLAN/internal/db" "github.com/aceberg/WatchYourLAN/internal/influx" "github.com/aceberg/WatchYourLAN/internal/models" + "github.com/aceberg/WatchYourLAN/internal/notify" ) -func updateScan() { - db.Create(appConfig.DBPath) - - allHosts = db.Select(appConfig.DBPath, "now") - histHosts = db.Select(appConfig.DBPath, "history") - - quitScan = make(chan bool) - go startScan() -} - -func startScan() { +func startScan(quit chan bool) { var lastDate time.Time for { select { - case <-quitScan: + case <-quit: return default: nowDate := time.Now() @@ -80,7 +72,10 @@ func compareHosts(foundHosts []models.Host) { for _, fHost := range foundHostsMap { - // NOTIFY, LOG + msg := fmt.Sprintf("Unknown host IP: '%s', MAC: '%s', Hw: '%s', Iface: '%s'", fHost.IP, fHost.Mac, fHost.Hw, fHost.Iface) + slog.Warn(msg) + notify.Shout(msg, appConfig.ShoutURL) // Notify through Shoutrrr + fHost.Name, fHost.DNS = updateDNS(fHost) db.Insert(appConfig.DBPath, "now", fHost) } diff --git a/internal/web/templates/config.html b/internal/web/templates/config.html index 9cc5a6d..006843d 100644 --- a/internal/web/templates/config.html +++ b/internal/web/templates/config.html @@ -38,6 +38,23 @@