PostgreSQL - switch to SQLite on error
This commit is contained in:
parent
d6ee3a059f
commit
eaa0c27bd7
13 changed files with 145 additions and 197 deletions
|
|
@ -6,8 +6,6 @@ builds:
|
||||||
env: [CGO_ENABLED=0]
|
env: [CGO_ENABLED=0]
|
||||||
goos:
|
goos:
|
||||||
- linux
|
- linux
|
||||||
# - windows
|
|
||||||
# - darwin
|
|
||||||
goarch:
|
goarch:
|
||||||
- 386
|
- 386
|
||||||
- amd64
|
- amd64
|
||||||
|
|
@ -17,15 +15,6 @@ builds:
|
||||||
- "5"
|
- "5"
|
||||||
- "6"
|
- "6"
|
||||||
- "7"
|
- "7"
|
||||||
# ignore:
|
|
||||||
# - goos: darwin
|
|
||||||
# goarch: 386
|
|
||||||
# - goos: darwin
|
|
||||||
# goarch: arm
|
|
||||||
# - goos: windows
|
|
||||||
# goarch: 386
|
|
||||||
# - goos: windows
|
|
||||||
# goarch: arm
|
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- maintainer: aceberg <aceberg_a@proton.me>
|
- maintainer: aceberg <aceberg_a@proton.me>
|
||||||
|
|
|
||||||
75
README.md
75
README.md
|
|
@ -10,82 +10,66 @@
|
||||||

|

|
||||||
|
|
||||||
Lightweight network IP scanner with web GUI
|
Lightweight network IP scanner with web GUI
|
||||||
- [Quick start](https://github.com/aceberg/WatchYourLAN#quick-start)
|
> [!WARNING]
|
||||||
- [Install .deb](https://github.com/aceberg/ppa)
|
> 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)
|
||||||
- [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)
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
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:
|
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
|
```sh
|
||||||
docker run --name wyl \
|
docker run --name wyl \
|
||||||
-e "IFACE=$YOURIFACE" \
|
-e "IFACES=$YOURIFACE" \
|
||||||
-e "TZ=$YOURTIMEZONE" \
|
-e "TZ=$YOURTIMEZONE" \
|
||||||
--network="host" \
|
--network="host" \
|
||||||
-v $DOCKERDATAPATH/wyl:/data \
|
-v $DOCKERDATAPATH/wyl:/data/WatchYourLAN \
|
||||||
aceberg/watchyourlan
|
aceberg/watchyourlan
|
||||||
```
|
```
|
||||||
Web GUI should be at http://localhost:8840
|
Web GUI should be at http://localhost:8840
|
||||||
|
|
||||||
## Config
|
</details>
|
||||||
|
|
||||||
|
## Config
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Configuration can be done through config file or environment variables
|
Configuration can be done through config file or environment variables
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
| -------- | ----------- | ------- |
|
| -------- | ----------- | ------- |
|
||||||
| ARP_TIMEOUT | Per host timeout for <b>arp-scan</b> (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 |
|
| 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) |
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Config file
|
## 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`.
|
<details>
|
||||||
All variables could be set there. Example:
|
<summary>Expand</summary>
|
||||||
```yaml
|
|
||||||
color: light
|
|
||||||
dbpath: /data/db.sqlite
|
</details>
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
| Key | Description | Default |
|
| Key | Description | Default |
|
||||||
| -------- | ----------- | ------- |
|
| -------- | ----------- | ------- |
|
||||||
| -c | Path to config file | /data/config.yaml |
|
| -c | Path to config file | /data/config.yaml |
|
||||||
| -n | Path to node modules (see below) | "" |
|
| -n | Path to node modules (see below) | "" |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Local network only
|
## Local network only
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
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.
|
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:
|
Run with Docker:
|
||||||
```sh
|
```sh
|
||||||
|
|
@ -103,8 +87,15 @@ docker run --name wyl \
|
||||||
```
|
```
|
||||||
Or use [docker-compose](docker-compose-local.yml)
|
Or use [docker-compose](docker-compose-local.yml)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
- All go packages listed in [dependencies](https://github.com/aceberg/WatchYourLAN/network/dependencies)
|
- 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)
|
- Favicon and logo: [Access point icons created by Freepik - Flaticon](https://www.flaticon.com/free-icons/access-point)
|
||||||
- [Bootstrap](https://getbootstrap.com/)
|
- [Bootstrap](https://getbootstrap.com/)
|
||||||
- Themes: [Free themes for Bootstrap](https://bootswatch.com)
|
- Themes: [Free themes for Bootstrap](https://bootswatch.com)
|
||||||
|
|
||||||
|
</details>
|
||||||
BIN
assets/Screenshot 2024-08-29 at 01-41-12 WatchYourLAN.png
Normal file
BIN
assets/Screenshot 2024-08-29 at 01-41-12 WatchYourLAN.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
BIN
assets/Screenshot 2024-08-29 at 11-17-59 WatchYourLAN.png
Normal file
BIN
assets/Screenshot 2024-08-29 at 11-17-59 WatchYourLAN.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
BIN
assets/Screenshot_1.png
Normal file
BIN
assets/Screenshot_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
|
|
@ -5,7 +5,7 @@ services:
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ~/.dockerdata/wyl:/data
|
- ~/.dockerdata/wyl:/data/WatchYourLAN
|
||||||
environment:
|
environment:
|
||||||
TZ: Asia/Novosibirsk # required: needs your TZ for correct time
|
TZ: Asia/Novosibirsk # required: needs your TZ for correct time
|
||||||
IFACE: "virbr-bw wlxf4ec3892dd51" # required: 1 or more interface
|
IFACE: "virbr-bw wlxf4ec3892dd51" # required: 1 or more interface
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@ func Get(path string) (config models.Conf) {
|
||||||
|
|
||||||
viper.SetDefault("HOST", "0.0.0.0")
|
viper.SetDefault("HOST", "0.0.0.0")
|
||||||
viper.SetDefault("PORT", "8840")
|
viper.SetDefault("PORT", "8840")
|
||||||
viper.SetDefault("THEME", "solar")
|
viper.SetDefault("THEME", "sand")
|
||||||
viper.SetDefault("COLOR", "dark")
|
viper.SetDefault("COLOR", "dark")
|
||||||
viper.SetDefault("NODEPATH", "")
|
viper.SetDefault("NODEPATH", "")
|
||||||
viper.SetDefault("LOG_LEVEL", "info")
|
viper.SetDefault("LOG_LEVEL", "info")
|
||||||
viper.SetDefault("ARP_ARGS", "")
|
viper.SetDefault("ARP_ARGS", "")
|
||||||
viper.SetDefault("IFACES", "")
|
viper.SetDefault("IFACES", "")
|
||||||
viper.SetDefault("TIMEOUT", 60)
|
viper.SetDefault("TIMEOUT", 120)
|
||||||
viper.SetDefault("TRIM_HIST", 48)
|
viper.SetDefault("TRIM_HIST", 48)
|
||||||
viper.SetDefault("SHOUTRRR_URL", "")
|
viper.SetDefault("SHOUTRRR_URL", "")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
// Data to connect to DB
|
// Data to connect to DB
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Use string
|
Use string
|
||||||
|
Path string
|
||||||
SQLitePath string
|
SQLitePath string
|
||||||
PGConnect string
|
PGConnect string
|
||||||
PrimaryKey string
|
PrimaryKey string
|
||||||
|
|
@ -16,25 +17,18 @@ type Data struct {
|
||||||
|
|
||||||
var currentDB Data
|
var currentDB Data
|
||||||
|
|
||||||
func dbExec(sqlStatement string) {
|
func setCurrentDB() {
|
||||||
|
|
||||||
if currentDB.Use == "postgres" {
|
if currentDB.Use == "postgres" && currentDB.PGConnect != "" {
|
||||||
dbExecPG(sqlStatement)
|
currentDB.Path = currentDB.PGConnect
|
||||||
|
currentDB.PrimaryKey = "BIGSERIAL PRIMARY KEY"
|
||||||
} else {
|
} else {
|
||||||
dbExecLite(sqlStatement)
|
currentDB.Use = "sqlite"
|
||||||
}
|
currentDB.Path = currentDB.SQLitePath
|
||||||
}
|
currentDB.PrimaryKey = "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE"
|
||||||
|
|
||||||
// Select - select all from table
|
|
||||||
func Select(table string) (dbHosts []models.Host) {
|
|
||||||
|
|
||||||
if currentDB.Use == "postgres" {
|
|
||||||
dbHosts = selectPG(table)
|
|
||||||
} else {
|
|
||||||
dbHosts = selectLite(table)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbHosts
|
slog.Info("Using DB", "type", currentDB.Use)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCurrent - set paths and which DB to use
|
// SetCurrent - set paths and which DB to use
|
||||||
|
|
@ -44,12 +38,5 @@ func SetCurrent(config models.Conf) {
|
||||||
currentDB.SQLitePath = config.DBPath
|
currentDB.SQLitePath = config.DBPath
|
||||||
currentDB.PGConnect = config.PGConnect
|
currentDB.PGConnect = config.PGConnect
|
||||||
|
|
||||||
if currentDB.Use == "postgres" && currentDB.PGConnect != "" {
|
setCurrentDB()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
internal/db/connect.go
Normal file
36
internal/db/connect.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
42
internal/db/select-exec.go
Normal file
42
internal/db/select-exec.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -12,46 +12,42 @@ function displayArrayData(someArray) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortByAny(someArray, field) {
|
function sortByAny(someArray, field) {
|
||||||
// console.log("Field =", field);
|
|
||||||
|
|
||||||
if (field != oldField) {
|
if (field != oldField) {
|
||||||
|
|
||||||
if (field == 'IP') {
|
|
||||||
someArray.sort((a, b) => sortIP(a, b, true));
|
|
||||||
} else {
|
|
||||||
someArray.sort(byFieldDown(field));
|
|
||||||
}
|
|
||||||
|
|
||||||
oldField = field;
|
oldField = field;
|
||||||
|
down = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (field == 'IP') {
|
|
||||||
someArray.sort((a, b) => sortIP(a, b, false));
|
|
||||||
} else {
|
|
||||||
someArray.sort(byFieldUp(field));
|
|
||||||
}
|
|
||||||
|
|
||||||
oldField = '';
|
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);
|
displayArrayData(someArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
function byFieldUp(fieldName){
|
function byField(a, b, fieldName, down){
|
||||||
return (a, b) => a[fieldName] < b[fieldName] ? 1 : -1;
|
if (a[fieldName] > b[fieldName]) {
|
||||||
}
|
return down;
|
||||||
|
} else {
|
||||||
function byFieldDown(fieldName){
|
return !down;
|
||||||
return (a, b) => a[fieldName] > b[fieldName] ? 1 : -1;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortIP(a, b, down) {
|
function sortIP(a, b, down) {
|
||||||
const num1 = Number(a.IP.split(".").map((num) => (`000${num}`).slice(-3) ).join(""));
|
const num1 = numIP(a);
|
||||||
const num2 = Number(b.IP.split(".").map((num) => (`000${num}`).slice(-3) ).join(""));
|
const num2 = numIP(b);
|
||||||
if (down) {
|
if (down) {
|
||||||
return num1-num2;
|
return num1-num2;
|
||||||
} else {
|
} else {
|
||||||
return num2-num1;
|
return num2-num1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function numIP(a) {
|
||||||
|
return Number(a.IP.split(".").map((num) => (`000${num}`).slice(-3) ).join(""));
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue