diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a019bdf..186136b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,8 +6,6 @@ builds: env: [CGO_ENABLED=0] goos: - linux - # - windows - # - darwin goarch: - 386 - amd64 @@ -17,15 +15,6 @@ builds: - "5" - "6" - "7" - # ignore: - # - goos: darwin - # goarch: 386 - # - goos: darwin - # goarch: arm - # - goos: windows - # goarch: 386 - # - goos: windows - # goarch: arm nfpms: - maintainer: aceberg diff --git a/README.md b/README.md index 4f06a8c..538b2f0 100644 --- a/README.md +++ b/README.md @@ -10,82 +10,66 @@ ![Docker Pulls](https://img.shields.io/docker/pulls/aceberg/watchyourlan) Lightweight network IP scanner with web GUI -- [Quick start](https://github.com/aceberg/WatchYourLAN#quick-start) -- [Install .deb](https://github.com/aceberg/ppa) -- [Config](https://github.com/aceberg/WatchYourLAN#config) -- [Config file](https://github.com/aceberg/WatchYourLAN#config-file) -- [Options](https://github.com/aceberg/WatchYourLAN#options) -- [Local network only](https://github.com/aceberg/WatchYourLAN#local-network-only) -- [Thanks](https://github.com/aceberg/WatchYourLAN#thanks) +> [!WARNING] +> This README is about version 2.0, which is going to be released soon. There will be BREAKING CHANGES! Version 1.0 can be found in this brunch: [v1](https://github.com/aceberg/WatchYourLAN/tree/v1) -![Screenshot_v0.6](https://raw.githubusercontent.com/aceberg/WatchYourLAN/main/assets/Screenshot_v0.6.png) +![Screenshot_1](https://raw.githubusercontent.com/aceberg/WatchYourLAN/main/assets/Screenshot_1.png) ## Quick start +
+ Expand + Replace `$YOURTIMEZONE` with correct time zone and `$YOURIFACE` with network interface you want to scan. Network mode must be `host`. Set `$DOCKERDATAPATH` for container to save data: ```sh docker run --name wyl \ - -e "IFACE=$YOURIFACE" \ + -e "IFACES=$YOURIFACE" \ -e "TZ=$YOURTIMEZONE" \ --network="host" \ - -v $DOCKERDATAPATH/wyl:/data \ + -v $DOCKERDATAPATH/wyl:/data/WatchYourLAN \ aceberg/watchyourlan ``` Web GUI should be at http://localhost:8840 -## Config +
+## Config +
+ Expand Configuration can be done through config file or environment variables | Variable | Description | Default | | -------- | ----------- | ------- | -| ARP_TIMEOUT | Per host timeout for arp-scan (in milliseconds) | 500 | -| AUTH | Enable Session-Cookie authentication | false | -| AUTH_EXPIRE | Session expiration time. A number and suffix: **m, h, d** or **M**. | 7d | -| AUTH_USER | Username | "" | -| AUTH_PASSWORD | Encrypted password (bcrypt). [How to encrypt password with bcrypt?](docs/BCRYPT.md) | "" | | COLOR | Background color: light or dark | dark | -| DBPATH | Path to Database | /data/db.sqlite | -| GUIIP | Address for web GUI | 0.0.0.0 | -| GUIPORT | Port for web GUI | 8840 | -| HISTORY_DAYS | Keep devices online/offline history for (days) | 30 | -| IFACE | Interface to scan. Could be one or more, separated by space. Currently `docker0` is not allowed, as arp-scan wouldn't work with it correctly | enp1s0 | -| IGNOREIP | If you want to detect unknown hosts by MAC only, set this variable to "yes" | no | -| LOGLEVEL | How much log output you want to see ("short" or "verbose") | verbose | -| SHOUTRRR_URL | Url to any notification service supported by [Shoutrrr](https://github.com/containrrr/shoutrrr) (gotify, email, telegram and others) or [Generic Webhook](https://github.com/containrrr/shoutrrr/blob/main/docs/services/generic.md) | "" | -| THEME | Any theme name from https://bootswatch.com in lowcase or [additional](https://github.com/aceberg/aceberg-bootswatch-fork) (emerald, grass, sand) | solar | -| TIMEOUT | Time between scans (seconds) | 60 (1 minute) | +
## Config file -> [!WARNING] -> Config file format has been migrated to YAML in release [v1.0.0](https://github.com/aceberg/WatchYourLAN/releases/tag/1.0.0). -Config file path is `/data/config.yaml`. -All variables could be set there. Example: -```yaml -color: light -dbpath: /data/db.sqlite -guiip: 192.168.2.1 -guiport: "8840" -iface: enp1s0 -ignoreip: "no" -loglevel: short -shoutrrr_url: gotify://192.168.2.1:8083/AwQqpAae.rrl5Ob/?title=Unknown host detected&DisableTLS=yes -theme: solar -timeout: 120 -``` +
+ Expand + + +
## Options +
+ Expand + | Key | Description | Default | | -------- | ----------- | ------- | | -c | Path to config file | /data/config.yaml | | -n | Path to node modules (see below) | "" | +
+ ## Local network only +
+ Expand + By default, this app pulls themes, icons and fonts from the internet. But, in some cases, it may be useful to have an independent from global network setup. I created a separate [image](https://github.com/aceberg/my-dockerfiles/tree/main/node-bootstrap) with all necessary modules and fonts. Run with Docker: ```sh @@ -103,8 +87,15 @@ docker run --name wyl \ ``` Or use [docker-compose](docker-compose-local.yml) +
+ ## Thanks +
+ Expand + - All go packages listed in [dependencies](https://github.com/aceberg/WatchYourLAN/network/dependencies) - Favicon and logo: [Access point icons created by Freepik - Flaticon](https://www.flaticon.com/free-icons/access-point) - [Bootstrap](https://getbootstrap.com/) - Themes: [Free themes for Bootstrap](https://bootswatch.com) + +
\ No newline at end of file diff --git a/assets/Screenshot 2024-08-29 at 01-41-12 WatchYourLAN.png b/assets/Screenshot 2024-08-29 at 01-41-12 WatchYourLAN.png new file mode 100644 index 0000000..f93293a Binary files /dev/null and b/assets/Screenshot 2024-08-29 at 01-41-12 WatchYourLAN.png differ diff --git a/assets/Screenshot 2024-08-29 at 11-17-59 WatchYourLAN.png b/assets/Screenshot 2024-08-29 at 11-17-59 WatchYourLAN.png new file mode 100644 index 0000000..a4dfaa0 Binary files /dev/null and b/assets/Screenshot 2024-08-29 at 11-17-59 WatchYourLAN.png differ diff --git a/assets/Screenshot_1.png b/assets/Screenshot_1.png new file mode 100644 index 0000000..f5dc8c5 Binary files /dev/null and b/assets/Screenshot_1.png differ diff --git a/docker-compose.yml b/docker-compose.yml index b975e58..35d15f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: network_mode: "host" restart: unless-stopped volumes: - - ~/.dockerdata/wyl:/data + - ~/.dockerdata/wyl:/data/WatchYourLAN environment: TZ: Asia/Novosibirsk # required: needs your TZ for correct time IFACE: "virbr-bw wlxf4ec3892dd51" # required: 1 or more interface diff --git a/internal/conf/getconfig.go b/internal/conf/getconfig.go index 0044c9c..8318f15 100644 --- a/internal/conf/getconfig.go +++ b/internal/conf/getconfig.go @@ -12,13 +12,13 @@ func Get(path string) (config models.Conf) { viper.SetDefault("HOST", "0.0.0.0") viper.SetDefault("PORT", "8840") - viper.SetDefault("THEME", "solar") + viper.SetDefault("THEME", "sand") viper.SetDefault("COLOR", "dark") viper.SetDefault("NODEPATH", "") viper.SetDefault("LOG_LEVEL", "info") viper.SetDefault("ARP_ARGS", "") viper.SetDefault("IFACES", "") - viper.SetDefault("TIMEOUT", 60) + viper.SetDefault("TIMEOUT", 120) viper.SetDefault("TRIM_HIST", 48) viper.SetDefault("SHOUTRRR_URL", "") diff --git a/internal/db/choose_db.go b/internal/db/choose_db.go index 4ca05a9..fdce076 100644 --- a/internal/db/choose_db.go +++ b/internal/db/choose_db.go @@ -9,6 +9,7 @@ import ( // Data to connect to DB type Data struct { Use string + Path string SQLitePath string PGConnect string PrimaryKey string @@ -16,25 +17,18 @@ type Data struct { var currentDB Data -func dbExec(sqlStatement string) { +func setCurrentDB() { - if currentDB.Use == "postgres" { - dbExecPG(sqlStatement) + if currentDB.Use == "postgres" && currentDB.PGConnect != "" { + currentDB.Path = currentDB.PGConnect + currentDB.PrimaryKey = "BIGSERIAL PRIMARY KEY" } else { - dbExecLite(sqlStatement) - } -} - -// Select - select all from table -func Select(table string) (dbHosts []models.Host) { - - if currentDB.Use == "postgres" { - dbHosts = selectPG(table) - } else { - dbHosts = selectLite(table) + currentDB.Use = "sqlite" + currentDB.Path = currentDB.SQLitePath + currentDB.PrimaryKey = "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE" } - return dbHosts + slog.Info("Using DB", "type", currentDB.Use) } // SetCurrent - set paths and which DB to use @@ -44,12 +38,5 @@ func SetCurrent(config models.Conf) { currentDB.SQLitePath = config.DBPath currentDB.PGConnect = config.PGConnect - if currentDB.Use == "postgres" && currentDB.PGConnect != "" { - currentDB.PrimaryKey = "BIGSERIAL PRIMARY KEY" - } else { - currentDB.Use = "sqlite" - currentDB.PrimaryKey = "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE" - } - - slog.Info("Using DB", "type", currentDB.Use) + setCurrentDB() } diff --git a/internal/db/connect.go b/internal/db/connect.go new file mode 100644 index 0000000..a2a32b1 --- /dev/null +++ b/internal/db/connect.go @@ -0,0 +1,36 @@ +package db + +import ( + "log/slog" + + "github.com/jmoiron/sqlx" + + // import postgres + _ "github.com/lib/pq" + + // Import sqlite module + _ "modernc.org/sqlite" + + "github.com/aceberg/WatchYourLAN/internal/check" +) + +func connectDB() (*sqlx.DB, bool) { + var ok bool + + db, err := sqlx.Open(currentDB.Use, currentDB.Path) + check.IfError(err) + + err = db.Ping() + if check.IfError(err) && currentDB.Use == "postgres" { + slog.Warn("PostgreSQL connection error. Falling back to SQLite.") + currentDB.Use = "sqlite" + setCurrentDB() + Create() + } + + if err == nil { + ok = true + } + + return db, ok +} diff --git a/internal/db/postgres.go b/internal/db/postgres.go deleted file mode 100644 index a2763ef..0000000 --- a/internal/db/postgres.go +++ /dev/null @@ -1,49 +0,0 @@ -package db - -import ( - "log/slog" - - "github.com/jmoiron/sqlx" - - // import postgres - _ "github.com/lib/pq" - - "github.com/aceberg/WatchYourLAN/internal/check" - "github.com/aceberg/WatchYourLAN/internal/models" -) - -func dbExecPG(sqlStatement string) { - - // slog.Debug("PG Exec " + sqlStatement) - - db, err := sqlx.Connect("postgres", currentDB.PGConnect) - if check.IfError(err) { - slog.Warn("PostgreSQL connection error. Falling back to SQLite.") - currentDB.Use = "sqlite" - Create() - } - defer db.Close() - - _, err = db.Exec(sqlStatement) - check.IfError(err) -} - -func selectPG(table string) (dbHosts []models.Host) { - - sqlStatement := `SELECT * FROM ` + table + ` ORDER BY "DATE" DESC` - - // slog.Debug("PG Select " + sqlStatement) - - db, err := sqlx.Connect("postgres", currentDB.PGConnect) - if check.IfError(err) { - slog.Warn("PostgreSQL connection error. Falling back to SQLite.") - currentDB.Use = "sqlite" - Create() - } - defer db.Close() - - err = db.Select(&dbHosts, sqlStatement) - check.IfError(err) - - return dbHosts -} diff --git a/internal/db/select-exec.go b/internal/db/select-exec.go new file mode 100644 index 0000000..e087b1c --- /dev/null +++ b/internal/db/select-exec.go @@ -0,0 +1,42 @@ +package db + +import ( + // "log/slog" + "sync" + + "github.com/aceberg/WatchYourLAN/internal/check" + "github.com/aceberg/WatchYourLAN/internal/models" +) + +var mu sync.Mutex + +func dbExec(sqlStatement string) { + + db, ok := connectDB() + defer db.Close() + + if ok { + mu.Lock() + _, err := db.Exec(sqlStatement) + mu.Unlock() + check.IfError(err) + } +} + +// Select - get all hosts +func Select(table string) (dbHosts []models.Host) { + + sqlStatement := `SELECT * FROM ` + table + ` ORDER BY "DATE" DESC` + + db, ok := connectDB() + defer db.Close() + + if ok { + mu.Lock() + err := db.Select(&dbHosts, sqlStatement) + mu.Unlock() + check.IfError(err) + } + + return dbHosts +} diff --git a/internal/db/sqlite.go b/internal/db/sqlite.go deleted file mode 100644 index e06d434..0000000 --- a/internal/db/sqlite.go +++ /dev/null @@ -1,44 +0,0 @@ -package db - -import ( - "sync" - - "github.com/jmoiron/sqlx" - - // Import sqlite module - _ "modernc.org/sqlite" - - "github.com/aceberg/WatchYourLAN/internal/check" - "github.com/aceberg/WatchYourLAN/internal/models" -) - -var mu sync.Mutex - -func dbExecLite(sqlStatement string) { - - mu.Lock() - db, err := sqlx.Connect("sqlite", currentDB.SQLitePath) - check.IfError(err) - defer db.Close() - - _, err = db.Exec(sqlStatement) - mu.Unlock() - - check.IfError(err) -} - -func selectLite(table string) (dbHosts []models.Host) { - - sqlStatement := "SELECT * FROM " + table + " ORDER BY DATE DESC" - - mu.Lock() - db, _ := sqlx.Connect("sqlite", currentDB.SQLitePath) - defer db.Close() - - err := db.Select(&dbHosts, sqlStatement) - mu.Unlock() - - check.IfError(err) - - return dbHosts -} diff --git a/internal/web/public/js/sort.js b/internal/web/public/js/sort.js index 75c37af..a145f9c 100644 --- a/internal/web/public/js/sort.js +++ b/internal/web/public/js/sort.js @@ -12,46 +12,42 @@ function displayArrayData(someArray) { } function sortByAny(someArray, field) { - // console.log("Field =", field); if (field != oldField) { - - if (field == 'IP') { - someArray.sort((a, b) => sortIP(a, b, true)); - } else { - someArray.sort(byFieldDown(field)); - } - oldField = field; + down = true; } else { - - if (field == 'IP') { - someArray.sort((a, b) => sortIP(a, b, false)); - } else { - someArray.sort(byFieldUp(field)); - } - oldField = ''; + down = false; + } + + if (field == 'IP') { + someArray.sort((a, b) => sortIP(a, b, down)); + } else { + someArray.sort((a, b) => byField(a, b, field, down)); } displayArrayData(someArray); } -function byFieldUp(fieldName){ - return (a, b) => a[fieldName] < b[fieldName] ? 1 : -1; -} - -function byFieldDown(fieldName){ - return (a, b) => a[fieldName] > b[fieldName] ? 1 : -1; +function byField(a, b, fieldName, down){ + if (a[fieldName] > b[fieldName]) { + return down; + } else { + return !down; + } } function sortIP(a, b, down) { - const num1 = Number(a.IP.split(".").map((num) => (`000${num}`).slice(-3) ).join("")); - const num2 = Number(b.IP.split(".").map((num) => (`000${num}`).slice(-3) ).join("")); + const num1 = numIP(a); + const num2 = numIP(b); if (down) { return num1-num2; } else { return num2-num1; - } - + } +} + +function numIP(a) { + return Number(a.IP.split(".").map((num) => (`000${num}`).slice(-3) ).join("")); } \ No newline at end of file