Compare commits
No commits in common. "main" and "solidjs" have entirely different histories.
137 changed files with 2475 additions and 4487 deletions
5
.github/workflows/binary-release.yml
vendored
5
.github/workflows/binary-release.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
generate:
|
generate:
|
||||||
name: Create release-artifacts
|
name: Create release-artifacts
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
|
|
@ -21,8 +21,7 @@ jobs:
|
||||||
- uses: goreleaser/goreleaser-action@v6
|
- uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: latest
|
version: '~> v2'
|
||||||
args: release --clean
|
args: release --clean
|
||||||
workdir: backend
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
4
.github/workflows/main-docker-all.yml
vendored
4
.github/workflows/main-docker-all.yml
vendored
|
|
@ -13,7 +13,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|
@ -57,5 +57,3 @@ jobs:
|
||||||
ghcr.io/${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:v2
|
ghcr.io/${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:v2
|
||||||
ghcr.io/${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:latest
|
ghcr.io/${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:latest
|
||||||
ghcr.io/${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
|
ghcr.io/${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
|
||||||
52
.github/workflows/new-dev-docker.yml
vendored
52
.github/workflows/new-dev-docker.yml
vendored
|
|
@ -1,52 +0,0 @@
|
||||||
name: New-Dev-Docker
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
# push:
|
|
||||||
# branches: [ "main" ]
|
|
||||||
# paths:
|
|
||||||
# - 'Dockerfile'
|
|
||||||
# - 'src/**'
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: watchyourlan
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:dev
|
|
||||||
ghcr.io/${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:dev
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
@ -18,15 +18,16 @@ builds:
|
||||||
- "7"
|
- "7"
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- id: systemd
|
- maintainer: aceberg <aceberg_a@proton.me>
|
||||||
formats:
|
|
||||||
- deb
|
|
||||||
- rpm
|
|
||||||
maintainer: aceberg <aceberg_a@proton.me>
|
|
||||||
description: Lightweight network IP scanner with web GUI
|
description: Lightweight network IP scanner with web GUI
|
||||||
homepage: https://github.com/aceberg/watchyourlan
|
homepage: https://github.com/aceberg/watchyourlan
|
||||||
license: MIT
|
license: MIT
|
||||||
section: utils
|
section: utils
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
- apk
|
||||||
|
- termux.deb
|
||||||
dependencies: # Don't forget to edit!
|
dependencies: # Don't forget to edit!
|
||||||
- arp-scan
|
- arp-scan
|
||||||
- tzdata
|
- tzdata
|
||||||
|
|
@ -36,24 +37,11 @@ nfpms:
|
||||||
scripts:
|
scripts:
|
||||||
postinstall: ./configs/postinstall.sh
|
postinstall: ./configs/postinstall.sh
|
||||||
|
|
||||||
- id: alpine
|
|
||||||
formats:
|
|
||||||
- apk
|
|
||||||
maintainer: aceberg <aceberg_a@proton.me>
|
|
||||||
description: Lightweight network IP scanner with web GUI
|
|
||||||
homepage: https://github.com/aceberg/watchyourlan
|
|
||||||
license: MIT
|
|
||||||
section: utils
|
|
||||||
dependencies: # Don't forget to edit!
|
|
||||||
- arp-scan
|
|
||||||
- tzdata
|
|
||||||
contents:
|
|
||||||
- src: ./configs/watchyourlan
|
|
||||||
dst: /etc/init.d/watchyourlan
|
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- files:
|
- files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
|
- README.md
|
||||||
|
- CHANGELOG.md
|
||||||
- src: ./configs/watchyourlan.service
|
- src: ./configs/watchyourlan.service
|
||||||
dst: watchyourlan.service
|
dst: watchyourlan.service
|
||||||
- src: ./configs/install.sh
|
- src: ./configs/install.sh
|
||||||
2
.version
2
.version
|
|
@ -1 +1 @@
|
||||||
backend/internal/web/public/version
|
internal/web/public/version
|
||||||
39
CHANGELOG.md
39
CHANGELOG.md
|
|
@ -1,45 +1,6 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [v2.1.4] - 2025-09-10
|
|
||||||
### Added
|
|
||||||
- Swagger API docs (`/swagger/index.html`)
|
|
||||||
- Add host from API [#72](https://github.com/aceberg/WatchYourLAN/issues/72)
|
|
||||||
- Trigger rescan from API or by pressing `Save` on `Config/Scan settings` [#74](https://github.com/aceberg/WatchYourLAN/issues/74)
|
|
||||||
- Delete selected hosts [#195](https://github.com/aceberg/WatchYourLAN/issues/195)
|
|
||||||
- Wake-on-LAN [#135](https://github.com/aceberg/WatchYourLAN/issues/135), [#196](https://github.com/aceberg/WatchYourLAN/issues/196)
|
|
||||||
|
|
||||||
## [v2.1.3] - 2025-07-26
|
|
||||||
### Fixed
|
|
||||||
- Memory leak bug [#149](https://github.com/aceberg/WatchYourLAN/issues/149)
|
|
||||||
- Duplicated devices bug [#187](https://github.com/aceberg/WatchYourLAN/issues/187) [#198](https://github.com/aceberg/WatchYourLAN/issues/198)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- **DEPRECATED:** `HIST_IN_DB` config option. Now history is always stored in `DB`
|
|
||||||
- Upd to `go 1.24.5`
|
|
||||||
- Moved `DB` handling to `GORM`
|
|
||||||
- Moved to maintained `Shoutrrr`: [github.com/nicholas-fedor/shoutrrr](https://github.com/nicholas-fedor/shoutrrr) ([#197](https://github.com/aceberg/WatchYourLAN/issues/197))
|
|
||||||
|
|
||||||
## [v2.1.2] - 2025-03-30
|
|
||||||
### Fixed
|
|
||||||
- Edit names bug
|
|
||||||
- History page full rerenders replaced with only rerendering updated data
|
|
||||||
- Select options reset
|
|
||||||
|
|
||||||
## [v2.1.1] - 2025-03-26
|
|
||||||
### Fixed
|
|
||||||
- Filter bug in Chrome
|
|
||||||
|
|
||||||
## [v2.1.0] - 2025-03-25
|
|
||||||
### Added
|
|
||||||
- Rewrited GUI in `SolidJS` and `TypeScript`
|
|
||||||
- Prometheus integration [#181](https://github.com/aceberg/WatchYourLAN/pull/181)
|
|
||||||
- Optimized Docker build [#180](https://github.com/aceberg/WatchYourLAN/pull/180)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Vite: file names
|
|
||||||
- Node Path bug
|
|
||||||
|
|
||||||
## [v2.0.4] - 2024-10-21
|
## [v2.0.4] - 2024-10-21
|
||||||
### Added
|
### Added
|
||||||
- Notification test [#147](https://github.com/aceberg/WatchYourLAN/issues/147)
|
- Notification test [#147](https://github.com/aceberg/WatchYourLAN/issues/147)
|
||||||
|
|
|
||||||
18
Dockerfile
18
Dockerfile
|
|
@ -1,18 +1,8 @@
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx
|
FROM golang:alpine AS builder
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
|
RUN apk add build-base
|
||||||
|
COPY . /src
|
||||||
COPY --from=xx / /
|
RUN cd /src/cmd/WatchYourLAN/ && CGO_ENABLED=0 go build -o /WatchYourLAN .
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
|
|
||||||
COPY backend/go.mod backend/go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY backend/ .
|
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
RUN CGO_ENABLED=0 xx-go build -ldflags='-w -s' -o /WatchYourLAN ./cmd/WatchYourLAN
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|
|
||||||
18
FAQ.md
18
FAQ.md
|
|
@ -1,18 +0,0 @@
|
||||||
# FAQ
|
|
||||||
|
|
||||||
## Allow custom MAC vendor overrides
|
|
||||||
Issues [#169](https://github.com/aceberg/WatchYourLAN/issues/169), [#185](https://github.com/aceberg/WatchYourLAN/issues/185)
|
|
||||||
|
|
||||||
WatchYourLAN is using `arp-scan`, so most of its options are available to WYL users.
|
|
||||||
|
|
||||||
1. Prepare a [mac-vendor.txt](https://manpages.debian.org/testing/arp-scan/mac-vendor.5.en.html) file with additional MACs and put it in a mounted WYL directory.
|
|
||||||
2. If you are using `IFACES` variable to define interfaces, add path to mac-vendor.txt to `ARP_ARGS`
|
|
||||||
```yaml
|
|
||||||
arp_args: --macfile=/data/WatchYourLAN/mac-vendor.txt
|
|
||||||
```
|
|
||||||
3. For interfaces defined in `ARP_STRS` add the same directly in the beginning of `ARP_STRS` string
|
|
||||||
```yaml
|
|
||||||
arp_strs:
|
|
||||||
- --macfile=/data/WatchYourLAN/mac-vendor.txt -gNx 10.144.0.1/24 -I eth0
|
|
||||||
```
|
|
||||||
4. **WARNING!** To see an updated vendor, you'll have to delete host and wait for the next scan.
|
|
||||||
|
|
@ -7,7 +7,7 @@ mod:
|
||||||
run:
|
run:
|
||||||
cd cmd/WatchYourLAN/ && \
|
cd cmd/WatchYourLAN/ && \
|
||||||
sudo \
|
sudo \
|
||||||
go run . #-n http://192.168.2.3:8850
|
go run . #-c /data/config #-n http://192.168.2.3:8850
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
|
@ -17,6 +17,3 @@ lint:
|
||||||
golint ./...
|
golint ./...
|
||||||
|
|
||||||
check: fmt lint
|
check: fmt lint
|
||||||
|
|
||||||
swag:
|
|
||||||
swag init -g main.go --dir cmd/WatchYourLAN,internal/api,internal/models
|
|
||||||
23
README.md
23
README.md
|
|
@ -5,19 +5,17 @@
|
||||||
|
|
||||||
[](https://github.com/aceberg/WatchYourLAN/actions/workflows/main-docker-all.yml)
|
[](https://github.com/aceberg/WatchYourLAN/actions/workflows/main-docker-all.yml)
|
||||||
[](https://goreportcard.com/report/github.com/aceberg/WatchYourLAN)
|
[](https://goreportcard.com/report/github.com/aceberg/WatchYourLAN)
|
||||||
|
[](https://codeclimate.com/github/aceberg/WatchYourLAN/maintainability)
|
||||||
[](https://hub.docker.com/r/aceberg/watchyourlan)
|
[](https://hub.docker.com/r/aceberg/watchyourlan)
|
||||||
[](https://github.com/aceberg/WatchYourLAN/discussions)
|
[](https://github.com/aceberg/WatchYourLAN/discussions)
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/11642" target="_blank"><img src="https://trendshift.io/api/badge/repositories/11642" alt="aceberg%2FWatchYourLAN | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
|
||||||
|
|
||||||
Lightweight network IP scanner with web GUI. Features:
|
Lightweight network IP scanner with web GUI. Features:
|
||||||
- Send notification when new host is found
|
- Send notification when new host is found
|
||||||
- Monitor hosts online/offline history
|
- Monitor hosts online/offline history
|
||||||
- Keep a list of all hosts in the network
|
- Keep a list of all hosts in the network
|
||||||
- Send data to `InfluxDB2` or `Prometheus` to make a `Grafana` dashboard
|
- Send data to `InfluxDB2` to make a `Grafana` dashboard
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Please, consider making a [donation](https://github.com/aceberg#donate). Even $10 will make a difference to me.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -93,7 +91,7 @@ Configuration can be done through config file, GUI or environment variables. Var
|
||||||
| THEME | Any theme name from https://bootswatch.com in lowcase or [additional](https://github.com/aceberg/aceberg-bootswatch-fork) | sand |
|
| THEME | Any theme name from https://bootswatch.com in lowcase or [additional](https://github.com/aceberg/aceberg-bootswatch-fork) | sand |
|
||||||
| COLOR | Background color: light or dark | dark |
|
| COLOR | Background color: light or dark | dark |
|
||||||
| NODEPATH | Path to local node modules | |
|
| NODEPATH | Path to local node modules | |
|
||||||
| SHOUTRRR_URL | WatchYourLAN uses [Shoutrrr](https://github.com/nicholas-fedor/shoutrrr) to send notifications. It is already integrated, just needs a correct URL. Examples for Discord, Email, Gotify, Matrix, Ntfy, Pushover, Slack, Telegram, Generic Webhook and etc are [here](https://nicholas-fedor.github.io/shoutrrr/) | |
|
| SHOUTRRR_URL | WatchYourLAN uses [Shoutrrr](https://github.com/containrrr/shoutrrr) to send notifications. It is already integrated, just needs a correct URL. Examples for Discord, Email, Gotify, Matrix, Ntfy, Pushover, Slack, Telegram, Generic Webhook and etc are [here](https://containrrr.dev/shoutrrr/v0.8/services/gotify/) | |
|
||||||
|
|
||||||
### Scan settings
|
### Scan settings
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|
|
@ -104,7 +102,7 @@ Configuration can be done through config file, GUI or environment variables. Var
|
||||||
| ARP_STRS ARP_STRS_JOINED | See [docs/VLAN_ARP_SCAN.md](https://github.com/aceberg/WatchYourLAN/blob/main/docs/VLAN_ARP_SCAN.md). | |
|
| ARP_STRS ARP_STRS_JOINED | See [docs/VLAN_ARP_SCAN.md](https://github.com/aceberg/WatchYourLAN/blob/main/docs/VLAN_ARP_SCAN.md). | |
|
||||||
| LOG_LEVEL | Log level: `debug`, `info`, `warn` or `error` | info |
|
| LOG_LEVEL | Log level: `debug`, `info`, `warn` or `error` | info |
|
||||||
| TRIM_HIST | Remove history after (hours) | 48 |
|
| TRIM_HIST | Remove history after (hours) | 48 |
|
||||||
| HIST_IN_DB | DEPRECATED since 2.1.3. Now History is always stored in DB. Use TRIM_HIST to reduce DB size | |
|
| HIST_IN_DB | Store History in DB - if `false`, the History will be stored only in memory and will be lost on app restart. Though, it will keep the app DB smaller (and InfluxDB is recommended for long term History storage) | false |
|
||||||
| USE_DB | Either `sqlite` or `postgres` | sqlite |
|
| USE_DB | Either `sqlite` or `postgres` | sqlite |
|
||||||
| PG_CONNECT | Address to connect to PostgreSQL. (Example: `postgres://username:password@192.168.0.1:5432/dbname?sslmode=disable`). Full list of URL parameters [here](https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters) | |
|
| PG_CONNECT | Address to connect to PostgreSQL. (Example: `postgres://username:password@192.168.0.1:5432/dbname?sslmode=disable`). Full list of URL parameters [here](https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters) | |
|
||||||
|
|
||||||
|
|
@ -120,13 +118,6 @@ This config matches Grafana's config for InfluxDB data source
|
||||||
| INFLUX_ORG | InfluxDB2 org | | home |
|
| INFLUX_ORG | InfluxDB2 org | | home |
|
||||||
| INFLUX_TOKEN | Secret token, generated by InfluxDB2 | | |
|
| INFLUX_TOKEN | Secret token, generated by InfluxDB2 | | |
|
||||||
|
|
||||||
### Prometheus config
|
|
||||||
This config configures the Prometheus data source
|
|
||||||
|
|
||||||
| Variable | Description | Default | Example |
|
|
||||||
| -------- | ----------- | ------- | ------- |
|
|
||||||
| PROMETHEUS_ENABLE | Enable the Prometheus `/metrics` endpoint | false | true |
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Config file
|
## Config file
|
||||||
|
|
@ -139,6 +130,7 @@ Config file name is `config_v2.yaml`. Example:
|
||||||
```yaml
|
```yaml
|
||||||
arp_args: ""
|
arp_args: ""
|
||||||
color: dark
|
color: dark
|
||||||
|
hist_in_db: false
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
ifaces: enp4s0
|
ifaces: enp4s0
|
||||||
influx_addr: ""
|
influx_addr: ""
|
||||||
|
|
@ -151,7 +143,6 @@ log_level: info
|
||||||
nodepath: ""
|
nodepath: ""
|
||||||
pg_connect: ""
|
pg_connect: ""
|
||||||
port: "8840"
|
port: "8840"
|
||||||
prometheus_enable: false
|
|
||||||
shoutrrr_url: "gotify://192.168.0.1:8083/AwQqpAae.rrl5Ob/?title=Unknown host detected&DisableTLS=yes"
|
shoutrrr_url: "gotify://192.168.0.1:8083/AwQqpAae.rrl5Ob/?title=Unknown host detected&DisableTLS=yes"
|
||||||
theme: sand
|
theme: sand
|
||||||
timeout: 60
|
timeout: 60
|
||||||
|
|
@ -192,7 +183,7 @@ docker run --name wyl \
|
||||||
-v $DOCKERDATAPATH/wyl:/data/WatchYourLAN \
|
-v $DOCKERDATAPATH/wyl:/data/WatchYourLAN \
|
||||||
aceberg/watchyourlan -n "http://$YOUR_IP:8850"
|
aceberg/watchyourlan -n "http://$YOUR_IP:8850"
|
||||||
```
|
```
|
||||||
Or use [docker-compose](docker-compose.yml)
|
Or use [docker-compose](docker-compose-local.yml)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
// @title WatchYourLAN API
|
|
||||||
// @version 0.1
|
|
||||||
// @description Lightweight network IP scanner written in Go
|
|
||||||
// @contact.url https://github.com/aceberg/WatchYourLAN
|
|
||||||
// @license.name MIT
|
|
||||||
// @license.url https://opensource.org/licenses/MIT
|
|
||||||
// @BasePath /api/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
// "net/http"
|
|
||||||
|
|
||||||
// _ "net/http/pprof"
|
|
||||||
|
|
||||||
// Import Swagger docs
|
|
||||||
_ "github.com/aceberg/WatchYourLAN/docs"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/routines"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/web"
|
|
||||||
)
|
|
||||||
|
|
||||||
const dirPath = "/data/WatchYourLAN"
|
|
||||||
const nodePath = ""
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
dirPtr := flag.String("d", dirPath, "Path to config dir")
|
|
||||||
nodePtr := flag.String("n", nodePath, "Path to node modules")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// pprof - memory leak detect
|
|
||||||
// go tool pprof -alloc_space http://localhost:8085/debug/pprof/heap
|
|
||||||
// (pprof) web
|
|
||||||
// (pprof) list db.Select
|
|
||||||
//
|
|
||||||
// go func() {
|
|
||||||
// http.ListenAndServe("localhost:8085", nil)
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// Make AppConfig
|
|
||||||
conf.Start(*dirPtr, *nodePtr)
|
|
||||||
|
|
||||||
gdb.Start()
|
|
||||||
|
|
||||||
routines.ScanRestart()
|
|
||||||
routines.HistoryTrim()
|
|
||||||
|
|
||||||
web.Gui()
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#!/sbin/openrc-run
|
|
||||||
name="WatchYourLAN"
|
|
||||||
description="Lightweight network IP scanner with web GUI"
|
|
||||||
command="/usr/bin/watchyourlan"
|
|
||||||
command_background=true
|
|
||||||
pidfile="/run/watchyourlan.pid"
|
|
||||||
|
|
@ -1,630 +0,0 @@
|
||||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
|
||||||
package docs
|
|
||||||
|
|
||||||
import "github.com/swaggo/swag"
|
|
||||||
|
|
||||||
const docTemplate = `{
|
|
||||||
"schemes": {{ marshal .Schemes }},
|
|
||||||
"swagger": "2.0",
|
|
||||||
"info": {
|
|
||||||
"description": "{{escape .Description}}",
|
|
||||||
"title": "{{.Title}}",
|
|
||||||
"contact": {
|
|
||||||
"url": "https://github.com/aceberg/WatchYourLAN"
|
|
||||||
},
|
|
||||||
"license": {
|
|
||||||
"name": "MIT",
|
|
||||||
"url": "https://opensource.org/licenses/MIT"
|
|
||||||
},
|
|
||||||
"version": "{{.Version}}"
|
|
||||||
},
|
|
||||||
"host": "{{.Host}}",
|
|
||||||
"basePath": "{{.BasePath}}",
|
|
||||||
"paths": {
|
|
||||||
"/all": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve all hosts from the database",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Get all hosts",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/config": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the current configuration used by the app",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Get application configuration",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Conf"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/edit/{id}/{name}/{known}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Update a host's name and optionally toggle its \"known\" status",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Edit host",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "New name for the host",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Pass 'toggle' to flip the known/unknown status",
|
|
||||||
"name": "known",
|
|
||||||
"in": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/history": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve the complete history of all hosts. Not recommended, the output can be a lot",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"history"
|
|
||||||
],
|
|
||||||
"summary": "Get full history",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/history/{mac}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve the latest history entries for a specific host by MAC address",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"history"
|
|
||||||
],
|
|
||||||
"summary": "Get history by MAC",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "MAC address of the host",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of history entries to return",
|
|
||||||
"name": "num",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/history/{mac}/{date}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve history for a specific host on a given date\nThe date format is flexible and can be:\n- Year only: ` + "`" + `2025` + "`" + `\n- Year + month: ` + "`" + `2025-09` + "`" + `\n- Full date: ` + "`" + `2025-09-06` + "`" + `\n- Full timestamp: ` + "`" + `2025-09-06 00:58:26` + "`" + `",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"history"
|
|
||||||
],
|
|
||||||
"summary": "Get history by date",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "MAC address of the host",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Date filter (supports YYYY, YYYY-MM, YYYY-MM-DD, YYYY-MM-DD HH:mm:ss)",
|
|
||||||
"name": "date",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/add/{mac}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Add host by MAC, with optional Name, IP, Hardware\nReturns ` + "`" + `models.Host` + "`" + ` with this MAC form DB, either just added or existing",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Add host manually",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host MAC",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "IP",
|
|
||||||
"name": "ip",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Hardware",
|
|
||||||
"name": "hw",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/del/{id}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Remove a host from the database by its unique ID",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Delete host",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/{id}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve detailed information about a host by its unique ID",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Get host by ID",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/notify_test": {
|
|
||||||
"get": {
|
|
||||||
"description": "Trigger a test notification to verify notification settings",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Send test notification",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/port/{addr}/{port}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Check whether a given TCP port on an address is open or closed",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"network"
|
|
||||||
],
|
|
||||||
"summary": "Check port state",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "IP address or hostname",
|
|
||||||
"name": "addr",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Port number",
|
|
||||||
"name": "port",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "true if open, false if closed",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/rescan": {
|
|
||||||
"get": {
|
|
||||||
"description": "Manually trigger rescan",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Rescan all interfaces now",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/status/{iface}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve summary statistics of hosts, optionally filtered by interface",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Get network status",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Interface name (omit for all interfaces)",
|
|
||||||
"name": "iface",
|
|
||||||
"in": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Stat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/version": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the current running version of the application",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Get application version",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/wol/{mac}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Send a magic packet to wake up a host by its MAC address",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"network"
|
|
||||||
],
|
|
||||||
"summary": "Send Wake-on-LAN packet",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "MAC address of the host",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "true if sent successfully",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"definitions": {
|
|
||||||
"models.Conf": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"arpArgs": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"arpStrs": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"confPath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"dbpath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"dirPath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"host": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"ifaces": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxAddr": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxBucket": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxEnable": {
|
|
||||||
"description": "InfluxDB",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"influxOrg": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxSkipTLS": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"influxToken": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"logLevel": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"nodePath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"pgconnect": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"port": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"prometheusEnable": {
|
|
||||||
"description": "Prometheus",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"shoutURL": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"theme": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"timeout": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"trimHist": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"useDB": {
|
|
||||||
"description": "PostgreSQL",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.Host": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"date": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"dns": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"hw": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"iface": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"ip": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"known": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"mac": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"now": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.Stat": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"known": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"offline": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"online": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"unknown": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
|
||||||
var SwaggerInfo = &swag.Spec{
|
|
||||||
Version: "0.1",
|
|
||||||
Host: "",
|
|
||||||
BasePath: "/api/",
|
|
||||||
Schemes: []string{},
|
|
||||||
Title: "WatchYourLAN API",
|
|
||||||
Description: "Lightweight network IP scanner written in Go",
|
|
||||||
InfoInstanceName: "swagger",
|
|
||||||
SwaggerTemplate: docTemplate,
|
|
||||||
LeftDelim: "{{",
|
|
||||||
RightDelim: "}}",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
|
||||||
}
|
|
||||||
|
|
@ -1,605 +0,0 @@
|
||||||
{
|
|
||||||
"swagger": "2.0",
|
|
||||||
"info": {
|
|
||||||
"description": "Lightweight network IP scanner written in Go",
|
|
||||||
"title": "WatchYourLAN API",
|
|
||||||
"contact": {
|
|
||||||
"url": "https://github.com/aceberg/WatchYourLAN"
|
|
||||||
},
|
|
||||||
"license": {
|
|
||||||
"name": "MIT",
|
|
||||||
"url": "https://opensource.org/licenses/MIT"
|
|
||||||
},
|
|
||||||
"version": "0.1"
|
|
||||||
},
|
|
||||||
"basePath": "/api/",
|
|
||||||
"paths": {
|
|
||||||
"/all": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve all hosts from the database",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Get all hosts",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/config": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the current configuration used by the app",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Get application configuration",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Conf"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/edit/{id}/{name}/{known}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Update a host's name and optionally toggle its \"known\" status",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Edit host",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "New name for the host",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Pass 'toggle' to flip the known/unknown status",
|
|
||||||
"name": "known",
|
|
||||||
"in": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/history": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve the complete history of all hosts. Not recommended, the output can be a lot",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"history"
|
|
||||||
],
|
|
||||||
"summary": "Get full history",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/history/{mac}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve the latest history entries for a specific host by MAC address",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"history"
|
|
||||||
],
|
|
||||||
"summary": "Get history by MAC",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "MAC address of the host",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of history entries to return",
|
|
||||||
"name": "num",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/history/{mac}/{date}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve history for a specific host on a given date\nThe date format is flexible and can be:\n- Year only: `2025`\n- Year + month: `2025-09`\n- Full date: `2025-09-06`\n- Full timestamp: `2025-09-06 00:58:26`",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"history"
|
|
||||||
],
|
|
||||||
"summary": "Get history by date",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "MAC address of the host",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Date filter (supports YYYY, YYYY-MM, YYYY-MM-DD, YYYY-MM-DD HH:mm:ss)",
|
|
||||||
"name": "date",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/add/{mac}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Add host by MAC, with optional Name, IP, Hardware\nReturns `models.Host` with this MAC form DB, either just added or existing",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Add host manually",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host MAC",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "IP",
|
|
||||||
"name": "ip",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Hardware",
|
|
||||||
"name": "hw",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/del/{id}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Remove a host from the database by its unique ID",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Delete host",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/host/{id}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve detailed information about a host by its unique ID",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"hosts"
|
|
||||||
],
|
|
||||||
"summary": "Get host by ID",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Host ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/notify_test": {
|
|
||||||
"get": {
|
|
||||||
"description": "Trigger a test notification to verify notification settings",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Send test notification",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/port/{addr}/{port}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Check whether a given TCP port on an address is open or closed",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"network"
|
|
||||||
],
|
|
||||||
"summary": "Check port state",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "IP address or hostname",
|
|
||||||
"name": "addr",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Port number",
|
|
||||||
"name": "port",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "true if open, false if closed",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/rescan": {
|
|
||||||
"get": {
|
|
||||||
"description": "Manually trigger rescan",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Rescan all interfaces now",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/status/{iface}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Retrieve summary statistics of hosts, optionally filtered by interface",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Get network status",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Interface name (omit for all interfaces)",
|
|
||||||
"name": "iface",
|
|
||||||
"in": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/models.Stat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/version": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the current running version of the application",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"system"
|
|
||||||
],
|
|
||||||
"summary": "Get application version",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/wol/{mac}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Send a magic packet to wake up a host by its MAC address",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"network"
|
|
||||||
],
|
|
||||||
"summary": "Send Wake-on-LAN packet",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "MAC address of the host",
|
|
||||||
"name": "mac",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "true if sent successfully",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"definitions": {
|
|
||||||
"models.Conf": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"arpArgs": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"arpStrs": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"confPath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"dbpath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"dirPath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"host": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"ifaces": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxAddr": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxBucket": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxEnable": {
|
|
||||||
"description": "InfluxDB",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"influxOrg": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"influxSkipTLS": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"influxToken": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"logLevel": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"nodePath": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"pgconnect": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"port": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"prometheusEnable": {
|
|
||||||
"description": "Prometheus",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"shoutURL": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"theme": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"timeout": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"trimHist": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"useDB": {
|
|
||||||
"description": "PostgreSQL",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.Host": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"date": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"dns": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"hw": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"iface": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"ip": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"known": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"mac": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"now": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"models.Stat": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"known": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"offline": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"online": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"unknown": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,409 +0,0 @@
|
||||||
basePath: /api/
|
|
||||||
definitions:
|
|
||||||
models.Conf:
|
|
||||||
properties:
|
|
||||||
arpArgs:
|
|
||||||
type: string
|
|
||||||
arpStrs:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
color:
|
|
||||||
type: string
|
|
||||||
confPath:
|
|
||||||
type: string
|
|
||||||
dbpath:
|
|
||||||
type: string
|
|
||||||
dirPath:
|
|
||||||
type: string
|
|
||||||
host:
|
|
||||||
type: string
|
|
||||||
ifaces:
|
|
||||||
type: string
|
|
||||||
influxAddr:
|
|
||||||
type: string
|
|
||||||
influxBucket:
|
|
||||||
type: string
|
|
||||||
influxEnable:
|
|
||||||
description: InfluxDB
|
|
||||||
type: boolean
|
|
||||||
influxOrg:
|
|
||||||
type: string
|
|
||||||
influxSkipTLS:
|
|
||||||
type: boolean
|
|
||||||
influxToken:
|
|
||||||
type: string
|
|
||||||
logLevel:
|
|
||||||
type: string
|
|
||||||
nodePath:
|
|
||||||
type: string
|
|
||||||
pgconnect:
|
|
||||||
type: string
|
|
||||||
port:
|
|
||||||
type: string
|
|
||||||
prometheusEnable:
|
|
||||||
description: Prometheus
|
|
||||||
type: boolean
|
|
||||||
shoutURL:
|
|
||||||
type: string
|
|
||||||
theme:
|
|
||||||
type: string
|
|
||||||
timeout:
|
|
||||||
type: integer
|
|
||||||
trimHist:
|
|
||||||
type: integer
|
|
||||||
useDB:
|
|
||||||
description: PostgreSQL
|
|
||||||
type: string
|
|
||||||
version:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
models.Host:
|
|
||||||
properties:
|
|
||||||
date:
|
|
||||||
type: string
|
|
||||||
dns:
|
|
||||||
type: string
|
|
||||||
hw:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: integer
|
|
||||||
iface:
|
|
||||||
type: string
|
|
||||||
ip:
|
|
||||||
type: string
|
|
||||||
known:
|
|
||||||
type: integer
|
|
||||||
mac:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
now:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
models.Stat:
|
|
||||||
properties:
|
|
||||||
known:
|
|
||||||
type: integer
|
|
||||||
offline:
|
|
||||||
type: integer
|
|
||||||
online:
|
|
||||||
type: integer
|
|
||||||
total:
|
|
||||||
type: integer
|
|
||||||
unknown:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
info:
|
|
||||||
contact:
|
|
||||||
url: https://github.com/aceberg/WatchYourLAN
|
|
||||||
description: Lightweight network IP scanner written in Go
|
|
||||||
license:
|
|
||||||
name: MIT
|
|
||||||
url: https://opensource.org/licenses/MIT
|
|
||||||
title: WatchYourLAN API
|
|
||||||
version: "0.1"
|
|
||||||
paths:
|
|
||||||
/all:
|
|
||||||
get:
|
|
||||||
description: Retrieve all hosts from the database
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/models.Host'
|
|
||||||
type: array
|
|
||||||
summary: Get all hosts
|
|
||||||
tags:
|
|
||||||
- hosts
|
|
||||||
/config:
|
|
||||||
get:
|
|
||||||
description: Returns the current configuration used by the app
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/models.Conf'
|
|
||||||
summary: Get application configuration
|
|
||||||
tags:
|
|
||||||
- system
|
|
||||||
/edit/{id}/{name}/{known}:
|
|
||||||
get:
|
|
||||||
description: Update a host's name and optionally toggle its "known" status
|
|
||||||
parameters:
|
|
||||||
- description: Host ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: New name for the host
|
|
||||||
in: path
|
|
||||||
name: name
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Pass 'toggle' to flip the known/unknown status
|
|
||||||
in: path
|
|
||||||
name: known
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: Edit host
|
|
||||||
tags:
|
|
||||||
- hosts
|
|
||||||
/history:
|
|
||||||
get:
|
|
||||||
description: Retrieve the complete history of all hosts. Not recommended, the
|
|
||||||
output can be a lot
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/models.Host'
|
|
||||||
type: array
|
|
||||||
summary: Get full history
|
|
||||||
tags:
|
|
||||||
- history
|
|
||||||
/history/{mac}:
|
|
||||||
get:
|
|
||||||
description: Retrieve the latest history entries for a specific host by MAC
|
|
||||||
address
|
|
||||||
parameters:
|
|
||||||
- description: MAC address of the host
|
|
||||||
in: path
|
|
||||||
name: mac
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Number of history entries to return
|
|
||||||
in: query
|
|
||||||
name: num
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/models.Host'
|
|
||||||
type: array
|
|
||||||
summary: Get history by MAC
|
|
||||||
tags:
|
|
||||||
- history
|
|
||||||
/history/{mac}/{date}:
|
|
||||||
get:
|
|
||||||
description: |-
|
|
||||||
Retrieve history for a specific host on a given date
|
|
||||||
The date format is flexible and can be:
|
|
||||||
- Year only: `2025`
|
|
||||||
- Year + month: `2025-09`
|
|
||||||
- Full date: `2025-09-06`
|
|
||||||
- Full timestamp: `2025-09-06 00:58:26`
|
|
||||||
parameters:
|
|
||||||
- description: MAC address of the host
|
|
||||||
in: path
|
|
||||||
name: mac
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Date filter (supports YYYY, YYYY-MM, YYYY-MM-DD, YYYY-MM-DD HH:mm:ss)
|
|
||||||
in: path
|
|
||||||
name: date
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/models.Host'
|
|
||||||
type: array
|
|
||||||
summary: Get history by date
|
|
||||||
tags:
|
|
||||||
- history
|
|
||||||
/host/{id}:
|
|
||||||
get:
|
|
||||||
description: Retrieve detailed information about a host by its unique ID
|
|
||||||
parameters:
|
|
||||||
- description: Host ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/models.Host'
|
|
||||||
summary: Get host by ID
|
|
||||||
tags:
|
|
||||||
- hosts
|
|
||||||
/host/add/{mac}:
|
|
||||||
get:
|
|
||||||
description: |-
|
|
||||||
Add host by MAC, with optional Name, IP, Hardware
|
|
||||||
Returns `models.Host` with this MAC form DB, either just added or existing
|
|
||||||
parameters:
|
|
||||||
- description: Host MAC
|
|
||||||
in: path
|
|
||||||
name: mac
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Name
|
|
||||||
in: query
|
|
||||||
name: name
|
|
||||||
type: string
|
|
||||||
- description: IP
|
|
||||||
in: query
|
|
||||||
name: ip
|
|
||||||
type: string
|
|
||||||
- description: Hardware
|
|
||||||
in: query
|
|
||||||
name: hw
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/models.Host'
|
|
||||||
summary: Add host manually
|
|
||||||
tags:
|
|
||||||
- hosts
|
|
||||||
/host/del/{id}:
|
|
||||||
get:
|
|
||||||
description: Remove a host from the database by its unique ID
|
|
||||||
parameters:
|
|
||||||
- description: Host ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: Delete host
|
|
||||||
tags:
|
|
||||||
- hosts
|
|
||||||
/notify_test:
|
|
||||||
get:
|
|
||||||
description: Trigger a test notification to verify notification settings
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: Send test notification
|
|
||||||
tags:
|
|
||||||
- system
|
|
||||||
/port/{addr}/{port}:
|
|
||||||
get:
|
|
||||||
description: Check whether a given TCP port on an address is open or closed
|
|
||||||
parameters:
|
|
||||||
- description: IP address or hostname
|
|
||||||
in: path
|
|
||||||
name: addr
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Port number
|
|
||||||
in: path
|
|
||||||
name: port
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: true if open, false if closed
|
|
||||||
schema:
|
|
||||||
type: boolean
|
|
||||||
summary: Check port state
|
|
||||||
tags:
|
|
||||||
- network
|
|
||||||
/rescan:
|
|
||||||
get:
|
|
||||||
description: Manually trigger rescan
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: Rescan all interfaces now
|
|
||||||
tags:
|
|
||||||
- system
|
|
||||||
/status/{iface}:
|
|
||||||
get:
|
|
||||||
description: Retrieve summary statistics of hosts, optionally filtered by interface
|
|
||||||
parameters:
|
|
||||||
- description: Interface name (omit for all interfaces)
|
|
||||||
in: path
|
|
||||||
name: iface
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/models.Stat'
|
|
||||||
summary: Get network status
|
|
||||||
tags:
|
|
||||||
- system
|
|
||||||
/version:
|
|
||||||
get:
|
|
||||||
description: Returns the current running version of the application
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: Get application version
|
|
||||||
tags:
|
|
||||||
- system
|
|
||||||
/wol/{mac}:
|
|
||||||
get:
|
|
||||||
description: Send a magic packet to wake up a host by its MAC address
|
|
||||||
parameters:
|
|
||||||
- description: MAC address of the host
|
|
||||||
in: path
|
|
||||||
name: mac
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: true if sent successfully
|
|
||||||
schema:
|
|
||||||
type: boolean
|
|
||||||
summary: Send Wake-on-LAN packet
|
|
||||||
tags:
|
|
||||||
- network
|
|
||||||
swagger: "2.0"
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
module github.com/aceberg/WatchYourLAN
|
|
||||||
|
|
||||||
go 1.25.1
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/aceberg/gorm-sqlite v1.6.0
|
|
||||||
github.com/gin-gonic/gin v1.10.1
|
|
||||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0
|
|
||||||
github.com/linde12/gowol v0.0.0-20180926075039-797e4d01634c
|
|
||||||
github.com/nicholas-fedor/shoutrrr v0.8.18
|
|
||||||
github.com/prometheus/client_golang v1.23.1
|
|
||||||
github.com/spf13/viper v1.20.1
|
|
||||||
github.com/swaggo/files v1.0.1
|
|
||||||
github.com/swaggo/gin-swagger v1.6.1
|
|
||||||
github.com/swaggo/swag v1.16.6
|
|
||||||
gorm.io/driver/postgres v1.6.0
|
|
||||||
gorm.io/gorm v1.30.3
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
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.18.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
|
||||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
|
||||||
github.com/go-openapi/spec v0.20.4 // indirect
|
|
||||||
github.com/go-openapi/swag v0.19.15 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
|
||||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
|
||||||
github.com/oapi-codegen/runtime v1.0.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
|
||||||
github.com/prometheus/common v0.66.0 // indirect
|
|
||||||
github.com/prometheus/procfs v0.16.1 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.10.0 // indirect
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
|
||||||
github.com/spf13/cast v1.9.2 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
|
||||||
golang.org/x/crypto v0.41.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
|
||||||
golang.org/x/mod v0.27.0 // indirect
|
|
||||||
golang.org/x/net v0.43.0 // indirect
|
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
|
||||||
golang.org/x/text v0.28.0 // indirect
|
|
||||||
golang.org/x/tools v0.36.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.8 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
modernc.org/libc v1.65.10 // indirect
|
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
|
||||||
modernc.org/memory v1.11.0 // indirect
|
|
||||||
modernc.org/sqlite v1.38.0 // indirect
|
|
||||||
)
|
|
||||||
302
backend/go.sum
302
backend/go.sum
|
|
@ -1,302 +0,0 @@
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
|
||||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
|
||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
|
||||||
github.com/aceberg/gorm-sqlite v1.6.0 h1:I1c4uqC82Uc7JVt2+cjushdvQogN/C1I3XOiyn3yoUI=
|
|
||||||
github.com/aceberg/gorm-sqlite v1.6.0/go.mod h1:3g//0BOzaOv9dO8G9j7JZOJA6qIURpPaht9oDY7LKpA=
|
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
|
||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
|
||||||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/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.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
|
||||||
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.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
|
||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
|
||||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
|
||||||
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.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
|
||||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
|
||||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
|
||||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
|
||||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
|
||||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
|
||||||
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=
|
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
|
||||||
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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
|
||||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
|
||||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
|
|
||||||
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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
|
||||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
|
|
||||||
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
|
||||||
github.com/linde12/gowol v0.0.0-20180926075039-797e4d01634c h1:QRJTb9zWXQL+yUajUqbp+VLtN+DQaYRloOxNwylsuVc=
|
|
||||||
github.com/linde12/gowol v0.0.0-20180926075039-797e4d01634c/go.mod h1:YeHfx3xIWda3noSterlj6d3+PdRRCTRox269+zhLmbM=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
|
||||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
|
||||||
github.com/nicholas-fedor/shoutrrr v0.8.18 h1:xYVtx2ipcEoLJFWViRpndC8zuTSRbhPEehsTkEBO4NQ=
|
|
||||||
github.com/nicholas-fedor/shoutrrr v0.8.18/go.mod h1:RYx646RI7DazdYQo49IWCuMeqq+D5zC3//F6I+qhCgY=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
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.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
|
|
||||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
|
||||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y=
|
|
||||||
github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ=
|
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
|
||||||
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
|
|
||||||
github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4=
|
|
||||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
|
||||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
|
||||||
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
|
|
||||||
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
|
||||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
|
||||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
|
||||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
|
||||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
|
||||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
|
||||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
|
||||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
|
||||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
|
||||||
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
|
|
||||||
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
|
|
||||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
|
||||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
|
||||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
|
||||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
|
||||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
|
||||||
gorm.io/gorm v1.30.3 h1:QiG8upl0Sg9ba2Zatfjy0fy4It2iNBL2/eMdvEkdXNs=
|
|
||||||
gorm.io/gorm v1.30.3/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
|
||||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
|
||||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
|
||||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
|
||||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
|
||||||
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
|
|
||||||
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
|
||||||
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
|
|
||||||
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
|
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
|
||||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
|
||||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
|
||||||
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
|
||||||
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getHistory godoc
|
|
||||||
// @Summary Get full history
|
|
||||||
// @Description Retrieve the complete history of all hosts. Not recommended, the output can be a lot
|
|
||||||
// @Tags history
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {array} models.Host
|
|
||||||
// @Router /history [get]
|
|
||||||
func getHistory(c *gin.Context) {
|
|
||||||
hosts, _ := gdb.Select("history")
|
|
||||||
c.IndentedJSON(http.StatusOK, hosts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHistoryByMAC godoc
|
|
||||||
// @Summary Get history by MAC
|
|
||||||
// @Description Retrieve the latest history entries for a specific host by MAC address
|
|
||||||
// @Tags history
|
|
||||||
// @Produce json
|
|
||||||
// @Param mac path string true "MAC address of the host"
|
|
||||||
// @Param num query int true "Number of history entries to return"
|
|
||||||
// @Success 200 {array} models.Host
|
|
||||||
// @Router /history/{mac} [get]
|
|
||||||
func getHistoryByMAC(c *gin.Context) {
|
|
||||||
mac := c.Param("mac")
|
|
||||||
numStr := c.Query("num")
|
|
||||||
num, _ := strconv.Atoi(numStr)
|
|
||||||
hosts := gdb.SelectLatest(mac, num)
|
|
||||||
c.IndentedJSON(http.StatusOK, hosts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHistoryByDate godoc
|
|
||||||
// @Summary Get history by date
|
|
||||||
// @Description Retrieve history for a specific host on a given date
|
|
||||||
// @Description The date format is flexible and can be:
|
|
||||||
// @Description - Year only: `2025`
|
|
||||||
// @Description - Year + month: `2025-09`
|
|
||||||
// @Description - Full date: `2025-09-06`
|
|
||||||
// @Description - Full timestamp: `2025-09-06 00:58:26`
|
|
||||||
// @Tags history
|
|
||||||
// @Produce json
|
|
||||||
// @Param mac path string true "MAC address of the host"
|
|
||||||
// @Param date path string true "Date filter (supports YYYY, YYYY-MM, YYYY-MM-DD, YYYY-MM-DD HH:mm:ss)"
|
|
||||||
// @Success 200 {array} models.Host
|
|
||||||
// @Router /history/{mac}/{date} [get]
|
|
||||||
func getHistoryByDate(c *gin.Context) {
|
|
||||||
mac := c.Param("mac")
|
|
||||||
date := c.Param("date")
|
|
||||||
hosts := gdb.SelectByDate(mac, date)
|
|
||||||
c.IndentedJSON(http.StatusOK, hosts)
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getAllHosts godoc
|
|
||||||
// @Summary Get all hosts
|
|
||||||
// @Description Retrieve all hosts from the database
|
|
||||||
// @Tags hosts
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {array} models.Host
|
|
||||||
// @Router /all [get]
|
|
||||||
func getAllHosts(c *gin.Context) {
|
|
||||||
allHosts, _ := gdb.Select("now")
|
|
||||||
c.IndentedJSON(http.StatusOK, allHosts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHost godoc
|
|
||||||
// @Summary Get host by ID
|
|
||||||
// @Description Retrieve detailed information about a host by its unique ID
|
|
||||||
// @Tags hosts
|
|
||||||
// @Produce json
|
|
||||||
// @Param id path string true "Host ID"
|
|
||||||
// @Success 200 {object} models.Host
|
|
||||||
// @Router /host/{id} [get]
|
|
||||||
func getHost(c *gin.Context) {
|
|
||||||
idStr := c.Param("id")
|
|
||||||
host := getHostByID(idStr) // functions.go
|
|
||||||
_, host.DNS = check.DNS(host)
|
|
||||||
c.IndentedJSON(http.StatusOK, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delHost godoc
|
|
||||||
// @Summary Delete host
|
|
||||||
// @Description Remove a host from the database by its unique ID
|
|
||||||
// @Tags hosts
|
|
||||||
// @Produce json
|
|
||||||
// @Param id path string true "Host ID"
|
|
||||||
// @Success 200 {string} string "OK"
|
|
||||||
// @Router /host/del/{id} [get]
|
|
||||||
func delHost(c *gin.Context) {
|
|
||||||
idStr := c.Param("id")
|
|
||||||
host := getHostByID(idStr) // functions.go
|
|
||||||
gdb.Delete("now", host.ID)
|
|
||||||
slog.Info("Deleting from DB", "host", host)
|
|
||||||
c.IndentedJSON(http.StatusOK, "OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
// addHost godoc
|
|
||||||
// @Summary Add host manually
|
|
||||||
// @Description Add host by MAC, with optional Name, IP, Hardware
|
|
||||||
// @Description Returns `models.Host` with this MAC form DB, either just added or existing
|
|
||||||
// @Tags hosts
|
|
||||||
// @Produce json
|
|
||||||
// @Param mac path string true "Host MAC"
|
|
||||||
// @Param name query string false "Name"
|
|
||||||
// @Param ip query string false "IP"
|
|
||||||
// @Param hw query string false "Hardware"
|
|
||||||
// @Success 200 {object} models.Host
|
|
||||||
// @Router /host/add/{mac} [get]
|
|
||||||
func addHost(c *gin.Context) {
|
|
||||||
|
|
||||||
mac := c.Param("mac")
|
|
||||||
hosts := gdb.SelectByMAC("now", mac)
|
|
||||||
|
|
||||||
if len(hosts) > 0 {
|
|
||||||
slog.Warn("Host with this MAC already exists", "host", hosts[0])
|
|
||||||
} else {
|
|
||||||
var host models.Host
|
|
||||||
|
|
||||||
host.Mac = mac
|
|
||||||
host.Name = c.Query("name")
|
|
||||||
host.IP = c.Query("ip")
|
|
||||||
host.Hw = c.Query("hw")
|
|
||||||
|
|
||||||
gdb.Update("now", host)
|
|
||||||
hosts = gdb.SelectByMAC("now", mac)
|
|
||||||
|
|
||||||
slog.Info("Added host to DB", "host", hosts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, hosts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// editHost godoc
|
|
||||||
// @Summary Edit host
|
|
||||||
// @Description Update a host's name and optionally toggle its "known" status
|
|
||||||
// @Tags hosts
|
|
||||||
// @Produce json
|
|
||||||
// @Param id path string true "Host ID"
|
|
||||||
// @Param name path string true "New name for the host"
|
|
||||||
// @Param known path string false "Pass 'toggle' to flip the known/unknown status"
|
|
||||||
// @Success 200 {string} string "OK"
|
|
||||||
// @Router /edit/{id}/{name}/{known} [get]
|
|
||||||
func editHost(c *gin.Context) {
|
|
||||||
|
|
||||||
idStr := c.Param("id")
|
|
||||||
name := c.Param("name")
|
|
||||||
toggleKnown := c.Param("known")
|
|
||||||
|
|
||||||
host := getHostByID(idStr) // functions.go
|
|
||||||
|
|
||||||
host.Name = name
|
|
||||||
|
|
||||||
if toggleKnown == "/toggle" {
|
|
||||||
host.Known = 1 - host.Known
|
|
||||||
}
|
|
||||||
|
|
||||||
gdb.Update("now", host)
|
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, "OK")
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/linde12/gowol"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/portscan"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getPortState godoc
|
|
||||||
// @Summary Check port state
|
|
||||||
// @Description Check whether a given TCP port on an address is open or closed
|
|
||||||
// @Tags network
|
|
||||||
// @Produce json
|
|
||||||
// @Param addr path string true "IP address or hostname"
|
|
||||||
// @Param port path string true "Port number"
|
|
||||||
// @Success 200 {boolean} bool "true if open, false if closed"
|
|
||||||
// @Router /port/{addr}/{port} [get]
|
|
||||||
func getPortState(c *gin.Context) {
|
|
||||||
addr := c.Param("addr")
|
|
||||||
port := c.Param("port")
|
|
||||||
state := portscan.IsOpen(addr, port)
|
|
||||||
c.IndentedJSON(http.StatusOK, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendWOL godoc
|
|
||||||
// @Summary Send Wake-on-LAN packet
|
|
||||||
// @Description Send a magic packet to wake up a host by its MAC address
|
|
||||||
// @Tags network
|
|
||||||
// @Produce json
|
|
||||||
// @Param mac path string true "MAC address of the host"
|
|
||||||
// @Success 200 {boolean} bool "true if sent successfully"
|
|
||||||
// @Router /wol/{mac} [get]
|
|
||||||
func sendWOL(c *gin.Context) {
|
|
||||||
|
|
||||||
mac := c.Param("mac")
|
|
||||||
|
|
||||||
packet, err := gowol.NewMagicPacket(mac)
|
|
||||||
|
|
||||||
if !check.IfError(err) {
|
|
||||||
err = packet.Send("255.255.255.255")
|
|
||||||
|
|
||||||
slog.Info("Wake-on-LAN: " + mac)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, !check.IfError(err))
|
|
||||||
}
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/notify"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/routines"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getVersion godoc
|
|
||||||
// @Summary Get application version
|
|
||||||
// @Description Returns the current running version of the application
|
|
||||||
// @Tags system
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {string} string
|
|
||||||
// @Router /version [get]
|
|
||||||
func getVersion(c *gin.Context) {
|
|
||||||
c.IndentedJSON(http.StatusOK, conf.AppConfig.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// triggerRescan godoc
|
|
||||||
// @Summary Rescan all interfaces now
|
|
||||||
// @Description Manually trigger rescan
|
|
||||||
// @Tags system
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {string} string "OK"
|
|
||||||
// @Router /rescan [get]
|
|
||||||
func triggerRescan(c *gin.Context) {
|
|
||||||
routines.ScanRestart()
|
|
||||||
c.Status(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConfig godoc
|
|
||||||
// @Summary Get application configuration
|
|
||||||
// @Description Returns the current configuration used by the app
|
|
||||||
// @Tags system
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} models.Conf
|
|
||||||
// @Router /config [get]
|
|
||||||
func getConfig(c *gin.Context) {
|
|
||||||
c.IndentedJSON(http.StatusOK, conf.AppConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyTest godoc
|
|
||||||
// @Summary Send test notification
|
|
||||||
// @Description Trigger a test notification to verify notification settings
|
|
||||||
// @Tags system
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {string} string "OK"
|
|
||||||
// @Router /notify_test [get]
|
|
||||||
func notifyTest(c *gin.Context) {
|
|
||||||
notify.Test()
|
|
||||||
c.Status(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStatus godoc
|
|
||||||
// @Summary Get network status
|
|
||||||
// @Description Retrieve summary statistics of hosts, optionally filtered by interface
|
|
||||||
// @Tags system
|
|
||||||
// @Produce json
|
|
||||||
// @Param iface path string false "Interface name (omit for all interfaces)"
|
|
||||||
// @Success 200 {object} models.Stat
|
|
||||||
// @Router /status/{iface} [get]
|
|
||||||
func getStatus(c *gin.Context) {
|
|
||||||
var status models.Stat
|
|
||||||
var searchHosts []models.Host
|
|
||||||
|
|
||||||
allHosts, _ := gdb.Select("now")
|
|
||||||
|
|
||||||
iface := c.Param("iface")
|
|
||||||
iface = iface[1:]
|
|
||||||
|
|
||||||
if iface != "" && iface != "undefined" {
|
|
||||||
for _, host := range allHosts {
|
|
||||||
if iface == host.Iface {
|
|
||||||
searchHosts = append(searchHosts, host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
searchHosts = allHosts
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, host := range searchHosts {
|
|
||||||
status.Total = status.Total + 1
|
|
||||||
|
|
||||||
if host.Known > 0 {
|
|
||||||
status.Known = status.Known + 1
|
|
||||||
} else {
|
|
||||||
status.Unknown = status.Unknown + 1
|
|
||||||
}
|
|
||||||
if host.Now > 0 {
|
|
||||||
status.Online = status.Online + 1
|
|
||||||
} else {
|
|
||||||
status.Offline = status.Offline + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, status)
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/routines"
|
|
||||||
)
|
|
||||||
|
|
||||||
func saveConfigHandler(c *gin.Context) {
|
|
||||||
|
|
||||||
conf.AppConfig.Host = c.PostForm("host")
|
|
||||||
conf.AppConfig.Port = c.PostForm("port")
|
|
||||||
conf.AppConfig.Theme = c.PostForm("theme")
|
|
||||||
conf.AppConfig.Color = c.PostForm("color")
|
|
||||||
conf.AppConfig.NodePath = c.PostForm("node")
|
|
||||||
conf.AppConfig.ShoutURL = c.PostForm("shout")
|
|
||||||
|
|
||||||
conf.Write(conf.AppConfig)
|
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveSettingsHandler(c *gin.Context) {
|
|
||||||
|
|
||||||
conf.AppConfig.LogLevel = c.PostForm("log")
|
|
||||||
conf.AppConfig.ArpArgs = c.PostForm("arpargs")
|
|
||||||
conf.AppConfig.Ifaces = c.PostForm("ifaces")
|
|
||||||
|
|
||||||
useDB := c.PostForm("usedb")
|
|
||||||
pgConnect := c.PostForm("pgconnect")
|
|
||||||
|
|
||||||
if useDB != conf.AppConfig.UseDB || pgConnect != conf.AppConfig.PGConnect {
|
|
||||||
conf.AppConfig.UseDB = c.PostForm("usedb")
|
|
||||||
conf.AppConfig.PGConnect = c.PostForm("pgconnect")
|
|
||||||
gdb.Connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := c.PostForm("timeout")
|
|
||||||
trimHist := c.PostForm("trim")
|
|
||||||
conf.AppConfig.Timeout, _ = strconv.Atoi(timeout)
|
|
||||||
conf.AppConfig.TrimHist, _ = strconv.Atoi(trimHist)
|
|
||||||
|
|
||||||
arpStrs := c.PostFormArray("arpstrs")
|
|
||||||
conf.AppConfig.ArpStrs = []string{}
|
|
||||||
for _, s := range arpStrs {
|
|
||||||
if s != "" {
|
|
||||||
conf.AppConfig.ArpStrs = append(conf.AppConfig.ArpStrs, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.Write(conf.AppConfig)
|
|
||||||
|
|
||||||
routines.ScanRestart()
|
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveInfluxHandler(c *gin.Context) {
|
|
||||||
|
|
||||||
conf.AppConfig.InfluxAddr = c.PostForm("addr")
|
|
||||||
conf.AppConfig.InfluxToken = c.PostForm("token")
|
|
||||||
conf.AppConfig.InfluxOrg = c.PostForm("org")
|
|
||||||
conf.AppConfig.InfluxBucket = c.PostForm("bucket")
|
|
||||||
|
|
||||||
enable := c.PostForm("enable")
|
|
||||||
skip := c.PostForm("skip")
|
|
||||||
conf.AppConfig.InfluxEnable = enable == "on"
|
|
||||||
conf.AppConfig.InfluxSkipTLS = skip == "on"
|
|
||||||
|
|
||||||
conf.Write(conf.AppConfig)
|
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
|
||||||
}
|
|
||||||
|
|
||||||
func savePrometheusHandler(c *gin.Context) {
|
|
||||||
enable := c.PostForm("enable")
|
|
||||||
|
|
||||||
conf.AppConfig.PrometheusEnable = enable == "on"
|
|
||||||
|
|
||||||
conf.Write(conf.AppConfig)
|
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getHostByID(idStr string) (oneHost models.Host) {
|
|
||||||
|
|
||||||
id, _ := strconv.Atoi(idStr)
|
|
||||||
oneHost = gdb.SelectByID(id)
|
|
||||||
|
|
||||||
return oneHost
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
swaggerFiles "github.com/swaggo/files"
|
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Routes - start API routes
|
|
||||||
func Routes(router *gin.Engine) {
|
|
||||||
|
|
||||||
r0 := router.Group("/api")
|
|
||||||
{
|
|
||||||
r0.GET("/all", getAllHosts) // api-hosts.go
|
|
||||||
r0.GET("/edit/:id/:name/*known", editHost) // api-hosts.go
|
|
||||||
r0.GET("/host/:id", getHost) // api-hosts.go
|
|
||||||
r0.GET("/host/del/:id", delHost) // api-hosts.go
|
|
||||||
r0.GET("/host/add/:mac", addHost) // api-hosts.go
|
|
||||||
|
|
||||||
r0.GET("/config", getConfig) // api-system.go
|
|
||||||
r0.GET("/notify_test", notifyTest) // api-system.go
|
|
||||||
r0.GET("/status/*iface", getStatus) // api-system.go
|
|
||||||
r0.GET("/version", getVersion) // api-system.go
|
|
||||||
r0.GET("/rescan", triggerRescan) // api-system.go
|
|
||||||
|
|
||||||
r0.GET("/history", getHistory) // api-history.go
|
|
||||||
r0.GET("/history/:mac", getHistoryByMAC) // api-history.go
|
|
||||||
r0.GET("/history/:mac/:date", getHistoryByDate) // api-history.go
|
|
||||||
|
|
||||||
r0.GET("/port/:addr/:port", getPortState) // api-network.go
|
|
||||||
r0.GET("/wol/:mac", sendWOL) // api-network.go
|
|
||||||
|
|
||||||
r0.POST("/config/", saveConfigHandler) // config.go
|
|
||||||
r0.POST("/config_settings/", saveSettingsHandler) // config.go
|
|
||||||
r0.POST("/config_influx/", saveInfluxHandler) // config.go
|
|
||||||
r0.POST("/config_prometheus/", savePrometheusHandler) // config.go
|
|
||||||
}
|
|
||||||
|
|
||||||
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package check
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNS - returns DNS names of a host
|
|
||||||
func DNS(host models.Host) (name, dns string) {
|
|
||||||
|
|
||||||
dnsNames, _ := net.LookupAddr(host.IP)
|
|
||||||
|
|
||||||
if len(dnsNames) > 0 {
|
|
||||||
name = dnsNames[0]
|
|
||||||
dns = strings.Join(dnsNames, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
return name, dns
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package conf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AppConfig - app config
|
|
||||||
var AppConfig models.Conf
|
|
||||||
|
|
||||||
// Start - initial config
|
|
||||||
func Start(dirPath, nodePath string) {
|
|
||||||
|
|
||||||
confPath := dirPath + "/config_v2.yaml"
|
|
||||||
check.Path(confPath)
|
|
||||||
|
|
||||||
AppConfig = read(confPath)
|
|
||||||
|
|
||||||
AppConfig.DirPath = dirPath
|
|
||||||
AppConfig.ConfPath = confPath
|
|
||||||
AppConfig.DBPath = dirPath + "/scan.db"
|
|
||||||
if nodePath != "" {
|
|
||||||
AppConfig.NodePath = nodePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package conf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Write - write config to file
|
|
||||||
func Write(config models.Conf) {
|
|
||||||
|
|
||||||
slog.Info("Writing new config to " + config.ConfPath)
|
|
||||||
|
|
||||||
viper.SetConfigFile(config.ConfPath)
|
|
||||||
viper.SetConfigType("yaml")
|
|
||||||
|
|
||||||
viper.Set("HOST", config.Host)
|
|
||||||
viper.Set("PORT", config.Port)
|
|
||||||
viper.Set("THEME", config.Theme)
|
|
||||||
viper.Set("COLOR", config.Color)
|
|
||||||
viper.Set("NODEPATH", config.NodePath)
|
|
||||||
viper.Set("LOG_LEVEL", config.LogLevel)
|
|
||||||
viper.Set("ARP_ARGS", config.ArpArgs)
|
|
||||||
viper.Set("ARP_STRS", config.ArpStrs)
|
|
||||||
viper.Set("ARP_STRS_JOINED", "") // Can be set only with ENV
|
|
||||||
viper.Set("IFACES", config.Ifaces)
|
|
||||||
viper.Set("TIMEOUT", config.Timeout)
|
|
||||||
viper.Set("TRIM_HIST", config.TrimHist)
|
|
||||||
viper.Set("SHOUTRRR_URL", config.ShoutURL)
|
|
||||||
|
|
||||||
viper.Set("USE_DB", config.UseDB)
|
|
||||||
viper.Set("PG_CONNECT", config.PGConnect)
|
|
||||||
|
|
||||||
viper.Set("influx_enable", config.InfluxEnable)
|
|
||||||
viper.Set("influx_skip_tls", config.InfluxSkipTLS)
|
|
||||||
viper.Set("influx_addr", config.InfluxAddr)
|
|
||||||
viper.Set("influx_token", config.InfluxToken)
|
|
||||||
viper.Set("influx_org", config.InfluxOrg)
|
|
||||||
viper.Set("influx_bucket", config.InfluxBucket)
|
|
||||||
|
|
||||||
viper.Set("PROMETHEUS_ENABLE", config.PrometheusEnable)
|
|
||||||
|
|
||||||
err := viper.WriteConfig()
|
|
||||||
check.IfError(err)
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
package gdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update - update or create host
|
|
||||||
func Update(table string, oneHost models.Host) {
|
|
||||||
|
|
||||||
tab := db.Table(table)
|
|
||||||
result := tab.Save(&oneHost)
|
|
||||||
check.IfError(result.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete - delete host from DB
|
|
||||||
func Delete(table string, id int) {
|
|
||||||
|
|
||||||
tab := db.Table(table)
|
|
||||||
result := tab.Delete(&models.Host{}, id)
|
|
||||||
check.IfError(result.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteOldHistory - delete a list of hosts from History
|
|
||||||
func DeleteOldHistory(date string) int64 {
|
|
||||||
|
|
||||||
tab := db.Table("history")
|
|
||||||
result := tab.Where("\"DATE\" < ?", date).Delete(&models.Host{})
|
|
||||||
check.IfError(result.Error)
|
|
||||||
|
|
||||||
return result.RowsAffected
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear - delete all hosts from table
|
|
||||||
func Clear(table string) {
|
|
||||||
|
|
||||||
tab := db.Table(table)
|
|
||||||
result := tab.Where("1 = 1").Delete(&models.Host{})
|
|
||||||
check.IfError(result.Error)
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package gdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Select - get all hosts
|
|
||||||
func Select(table string) (dbHosts []models.Host, ok bool) {
|
|
||||||
|
|
||||||
tab := db.Table(table)
|
|
||||||
err := tab.Find(&dbHosts).Error
|
|
||||||
|
|
||||||
return dbHosts, !check.IfError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectByID - get host by ID
|
|
||||||
func SelectByID(id int) (host models.Host) {
|
|
||||||
|
|
||||||
tab := db.Table("now")
|
|
||||||
tab.First(&host, id)
|
|
||||||
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectByMAC - get all hosts by MAC
|
|
||||||
func SelectByMAC(table, mac string) (hosts []models.Host) {
|
|
||||||
|
|
||||||
tab := db.Table(table)
|
|
||||||
tab.Where("\"MAC\" = ?", mac).Find(&hosts)
|
|
||||||
|
|
||||||
return hosts
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectByDate - get all hosts by MAC and DATE
|
|
||||||
func SelectByDate(mac, date string) (hosts []models.Host) {
|
|
||||||
|
|
||||||
tab := db.Table("history")
|
|
||||||
tab.
|
|
||||||
Where("\"MAC\" = ?", mac).
|
|
||||||
Where("\"DATE\" LIKE ?", date+"%").
|
|
||||||
Find(&hosts)
|
|
||||||
|
|
||||||
return hosts
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectLatest - get latest hosts by MAC
|
|
||||||
func SelectLatest(mac string, number int) (hosts []models.Host) {
|
|
||||||
|
|
||||||
tab := db.Table("history")
|
|
||||||
tab.
|
|
||||||
Where("\"MAC\" = ?", mac).
|
|
||||||
Order("\"DATE\" DESC").
|
|
||||||
Limit(number).
|
|
||||||
Find(&hosts)
|
|
||||||
|
|
||||||
return hosts
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
package gdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
sqlite "github.com/aceberg/gorm-sqlite"
|
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
"gorm.io/gorm/schema"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
var db *gorm.DB
|
|
||||||
var gormConf *gorm.Config
|
|
||||||
|
|
||||||
// Start working with DB
|
|
||||||
func Start() {
|
|
||||||
var tab *gorm.DB
|
|
||||||
var err error
|
|
||||||
|
|
||||||
newLogger := logger.New(
|
|
||||||
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
|
||||||
logger.Config{
|
|
||||||
SlowThreshold: 5 * time.Second,
|
|
||||||
LogLevel: logger.Warn,
|
|
||||||
IgnoreRecordNotFoundError: true,
|
|
||||||
Colorful: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
gormConf = &gorm.Config{
|
|
||||||
Logger: newLogger,
|
|
||||||
NamingStrategy: schema.NamingStrategy{
|
|
||||||
NoLowerCase: true,
|
|
||||||
// So upper case Columns could work in both PostgreSQL and SQLite
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Connect()
|
|
||||||
|
|
||||||
// Migrate the schema
|
|
||||||
tab = db.Table("now")
|
|
||||||
err = tab.AutoMigrate(&models.Host{})
|
|
||||||
check.IfError(err)
|
|
||||||
|
|
||||||
tab = db.Table("history")
|
|
||||||
err = tab.AutoMigrate(&models.Host{})
|
|
||||||
check.IfError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect - choose DB and connect
|
|
||||||
func Connect() {
|
|
||||||
var err error
|
|
||||||
var pgFail bool
|
|
||||||
|
|
||||||
if conf.AppConfig.UseDB == "postgres" {
|
|
||||||
db, err = gorm.Open(postgres.Open(conf.AppConfig.PGConnect), gormConf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
pgFail = true
|
|
||||||
|
|
||||||
slog.Error("PostgreSQL connection error:", "err", err)
|
|
||||||
slog.Warn("Falling back to SQLite")
|
|
||||||
} else {
|
|
||||||
slog.Info("Connected to DB: PostgreSQL")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pgFail || conf.AppConfig.UseDB != "postgres" {
|
|
||||||
|
|
||||||
db, err = gorm.Open(sqlite.Open(conf.AppConfig.DBPath), gormConf)
|
|
||||||
|
|
||||||
if !check.IfError(err) {
|
|
||||||
slog.Info("Connected to DB: SQLite")
|
|
||||||
db.Exec("PRAGMA journal_mode = wal;")
|
|
||||||
db.Exec("PRAGMA busy_timeout = 5000;")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package notify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/nicholas-fedor/shoutrrr"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Unknown - send message to log and shoutrrr
|
|
||||||
func Unknown(host models.Host) {
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("Unknown host found. Name: '%s', IP: '%s', MAC: '%s', Hw: '%s', Iface: '%s'", host.DNS, host.IP, host.Mac, host.Hw, host.Iface)
|
|
||||||
|
|
||||||
slog.Warn(msg)
|
|
||||||
shout(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test Shoutrrr notification
|
|
||||||
func Test() {
|
|
||||||
|
|
||||||
msg := "test notification"
|
|
||||||
slog.Info("Sending " + msg)
|
|
||||||
shout(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// shout - send msg to Shoutrrr
|
|
||||||
func shout(msg string) {
|
|
||||||
|
|
||||||
hostname, _ := os.Hostname()
|
|
||||||
wyl := "WatchYourLAN on '" + hostname + "': "
|
|
||||||
|
|
||||||
if conf.AppConfig.ShoutURL != "" {
|
|
||||||
err := shoutrrr.Send(conf.AppConfig.ShoutURL, wyl+msg)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Notification failed (shoutrrr): ", "", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler - display Prometheus metrics
|
|
||||||
func Handler() func(c *gin.Context) {
|
|
||||||
h := promhttp.Handler()
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
if !conf.AppConfig.PrometheusEnable {
|
|
||||||
c.AbortWithStatus(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.ServeHTTP(c.Writer, c.Request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var up = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Namespace: "watch_your_lan",
|
|
||||||
Name: "up",
|
|
||||||
Help: "Whether the host is up (1 for yes, 0 for no)",
|
|
||||||
}, []string{"ip", "iface", "name", "mac", "known"})
|
|
||||||
|
|
||||||
// Add a Prometheus metric
|
|
||||||
func Add(oneHist models.Host) {
|
|
||||||
if oneHist.Name == "" {
|
|
||||||
oneHist.Name = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
up.With(prometheus.Labels{
|
|
||||||
"ip": oneHist.IP,
|
|
||||||
"iface": oneHist.Iface,
|
|
||||||
"name": oneHist.Name,
|
|
||||||
"mac": oneHist.Mac,
|
|
||||||
"known": strconv.Itoa(oneHist.Known),
|
|
||||||
}).Set(float64(oneHist.Now))
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package routines
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/arp"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/influx"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/notify"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func startScan(quit chan bool) {
|
|
||||||
var lastDate, nowDate, plusDate time.Time
|
|
||||||
var foundHosts []models.Host
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
nowDate = time.Now()
|
|
||||||
plusDate = lastDate.Add(time.Duration(conf.AppConfig.Timeout) * time.Second)
|
|
||||||
|
|
||||||
if nowDate.After(plusDate) {
|
|
||||||
|
|
||||||
foundHosts = arp.Scan(conf.AppConfig.Ifaces, conf.AppConfig.ArpArgs, conf.AppConfig.ArpStrs)
|
|
||||||
|
|
||||||
// Make map of found hosts
|
|
||||||
foundHostsMap := make(map[string]models.Host)
|
|
||||||
for _, fHost := range foundHosts {
|
|
||||||
foundHostsMap[fHost.Mac] = fHost
|
|
||||||
}
|
|
||||||
|
|
||||||
compareHosts(foundHostsMap)
|
|
||||||
|
|
||||||
lastDate = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(1) * time.Minute)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareHosts(foundHostsMap map[string]models.Host) {
|
|
||||||
|
|
||||||
allHosts, ok := gdb.Select("now")
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, aHost := range allHosts {
|
|
||||||
|
|
||||||
fHost, exists := foundHostsMap[aHost.Mac]
|
|
||||||
if exists {
|
|
||||||
|
|
||||||
aHost.Iface = fHost.Iface
|
|
||||||
aHost.IP = fHost.IP
|
|
||||||
aHost.Date = fHost.Date
|
|
||||||
aHost.Now = 1
|
|
||||||
|
|
||||||
delete(foundHostsMap, aHost.Mac)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
aHost.Now = 0
|
|
||||||
}
|
|
||||||
gdb.Update("now", aHost)
|
|
||||||
|
|
||||||
aHost.ID = 0
|
|
||||||
aHost.Date = time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
gdb.Update("history", aHost)
|
|
||||||
|
|
||||||
if conf.AppConfig.InfluxEnable {
|
|
||||||
influx.Add(conf.AppConfig, aHost)
|
|
||||||
}
|
|
||||||
if conf.AppConfig.PrometheusEnable {
|
|
||||||
prometheus.Add(aHost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fHost := range foundHostsMap {
|
|
||||||
|
|
||||||
fHost.Name, fHost.DNS = check.DNS(fHost)
|
|
||||||
notify.Unknown(fHost) // Log and Shoutrrr
|
|
||||||
|
|
||||||
gdb.Update("now", fHost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package routines
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/gdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HistoryTrim - routine for History
|
|
||||||
func HistoryTrim() {
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Duration(1) * time.Hour) // Every hour
|
|
||||||
|
|
||||||
hours := conf.AppConfig.TrimHist
|
|
||||||
nowMinus := time.Now().Add(-time.Duration(hours) * time.Hour)
|
|
||||||
date := nowMinus.Format("2006-01-02 15:04:05")
|
|
||||||
|
|
||||||
slog.Info("Removing all History before", "date", date)
|
|
||||||
|
|
||||||
n := gdb.DeleteOldHistory(date)
|
|
||||||
slog.Info("Removed records from History", "n", n)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func indexHandler(c *gin.Context) {
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "index.html", true)
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
import{t as m,k as n,l as c,h as I,m as $,n as N,p as S,q as E,i as t,e as i,r as M,F as U,b as O,s as _,S as P}from"./index.js";import{M as j}from"./MacHistory.js";var k=m('<input class=form-control placeholder="Show elements"title="Nomber of elements to show"style=max-width:10em;>');function q(l){const o=e=>{localStorage.setItem(l.name,e),n(+e),c()==0&&n(200)};return(()=>{var e=k();return e.$$input=r=>o(r.target.value),e})()}I(["input"]);var A=m('<div class="card border-primary"><div class="card-header d-flex justify-content-between"></div><div class=card-body><table class="table table-striped table-hover"><tbody></tbody>'),D=m("<tr><td class=opacity-50 style=width:2em;>.</td><td><a></a><br><a></a></td><td>");function B(){let l=[];l.push(...$);const o=localStorage.getItem("histShow");return n(+o),(c()===0||isNaN(c()))&&n(200),N(()=>{S()&&(l=[],l.push(...$),console.log("Upd on Filter"),E(!1))}),(()=>{var e=A(),r=e.firstChild,g=r.nextSibling,w=g.firstChild,y=w.firstChild;return t(r,i(M,{}),null),t(r,i(q,{name:"histShow"}),null),t(y,i(P,{get when(){return!S()},get children(){return i(U,{each:l,children:(a,x)=>(()=>{var f=D(),d=f.firstChild,C=d.firstChild,u=d.nextSibling,h=u.firstChild,F=h.nextSibling,b=F.nextSibling,H=u.nextSibling;return t(d,()=>x()+1,C),t(h,()=>a.Name),t(b,()=>a.IP),t(H,i(j,{get mac(){return a.Mac},date:""})),O(s=>{var p="/host/"+a.ID,v="http://"+a.IP;return p!==s.e&&_(h,"href",s.e=p),v!==s.t&&_(b,"href",s.t=v),s},{e:void 0,t:void 0}),f})()})}})),e})()}export{B as default};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
import{u as rt,t as u,i as e,j as I,b as p,s as K,v as B,w as st,x as dt,h as N,c as m,e as C,F as ct,y as ot,o as z,k as $t,z as ut,A as bt,B as ht}from"./index.js";import{M as ft}from"./MacHistory.js";var gt=u('<div class="card border-primary"><div class=card-header>Host</div><div class="card-body table-responsive"><table class="table table-striped table-hover"><tbody><tr><td>ID</td><td></td></tr><tr><td>Name</td><td><input type=text class=form-control></td></tr><tr><td>DNS name</td><td></td></tr><tr><td>Iface</td><td></td></tr><tr><td>IP</td><td><a target=_blank></a></td></tr><tr><td>MAC</td><td></td></tr><tr><td>Hardware</td><td></td></tr><tr><td>Date</td><td></td></tr><tr><td>Known</td><td><div class="form-check form-switch"><input class=form-check-input type=checkbox></div></td></tr><tr><td>Online</td><td> <button type=button class="btn btn-outline-success">Wake-on-LAN</button></td></tr></tbody></table><button type=button class="btn btn-outline-danger">Delete host'),_t=u('<i class="bi bi-check-circle-fill"style=color:var(--bs-success);>'),mt=u('<i class="bi bi-circle-fill"style=color:var(--bs-gray-500);>');function vt(t){let l="";const i=rt(async a=>{await B(t.host.ID,a,"")},300),n=async a=>{l=a,i(a)},d=async()=>{l==""&&(l=t.host.Name),await B(t.host.ID,l,"toggle")},_=async()=>{await dt(t.host.ID),window.location.href="/"},h=async()=>{await st(t.host.Mac)};return(()=>{var a=gt(),f=a.firstChild,v=f.nextSibling,o=v.firstChild,D=o.firstChild,r=D.firstChild,$=r.firstChild,g=$.nextSibling,s=r.nextSibling,y=s.firstChild,w=y.nextSibling,k=w.firstChild,S=s.nextSibling,c=S.firstChild,b=c.nextSibling,x=S.nextSibling,P=x.firstChild,G=P.nextSibling,M=x.nextSibling,R=M.firstChild,q=R.nextSibling,A=q.firstChild,L=M.nextSibling,J=L.firstChild,Q=J.nextSibling,E=L.nextSibling,U=E.firstChild,V=U.nextSibling,O=E.nextSibling,X=O.firstChild,Y=X.nextSibling,F=O.nextSibling,Z=F.firstChild,tt=Z.nextSibling,et=tt.firstChild,T=et.firstChild,lt=F.nextSibling,it=lt.firstChild,W=it.nextSibling,j=W.firstChild,nt=j.nextSibling,at=o.nextSibling;return e(g,()=>t.host.ID),k.$$input=H=>n(H.target.value),e(b,()=>t.host.DNS),e(G,()=>t.host.Iface),e(A,()=>t.host.IP),e(Q,()=>t.host.Mac),e(V,()=>t.host.Hw),e(Y,()=>t.host.Date),T.$$click=d,e(W,(()=>{var H=I(()=>t.host.Now==1);return()=>H()?_t():mt()})(),j),nt.$$click=h,at.$$click=_,p(()=>K(A,"href","http://"+t.host.IP)),p(()=>k.value=t.host.Name),p(()=>T.checked=t.host.Known==1),a})()}N(["input","click"]);var St=u('<div class="card border-primary"><div class=card-header>Port Scan</div><div class=card-body><form class=input-group><input type=text class=form-control placeholder=1><input type=text class=form-control placeholder=65535><button type=button class="btn btn-primary">Scan</button></form><div class=mt-2>'),xt=u('<div class="d-flex justify-content-between mt-2"><button type=button class="btn btn-warning">Stop/Continue</button><div>Scanning port: '),pt=u("<a class=me-4 target=_blank>");function Ct(t){let l=!1;const[i,n]=m(""),[d,_]=m(""),[h,a]=m(""),[f,v]=m([]),o=async()=>{l=!1;let r=Number(i());(Number.isNaN(r)||r<1||r>65535)&&(r=1);let $=Number(d());(Number.isNaN($)||$<1||$>65535)&&($=65535);let g;for(let s=r;s<=$&&!l;s++)a(s.toString()),g=await ot(t.IP,s),g&&v([...f(),s])},D=()=>{l?(n(h()),o()):l=!0};return(()=>{var r=St(),$=r.firstChild,g=$.nextSibling,s=g.firstChild,y=s.firstChild,w=y.nextSibling,k=w.nextSibling,S=s.nextSibling;return y.$$input=c=>n(c.target.value),w.$$input=c=>_(c.target.value),k.$$click=o,e(g,(()=>{var c=I(()=>h()!="");return()=>c()?(()=>{var b=xt(),x=b.firstChild,P=x.nextSibling;return P.firstChild,x.$$click=D,e(P,h,null),b})():[]})(),S),e(S,C(ct,{get each(){return f()},children:c=>(()=>{var b=pt();return e(b,c),p(()=>K(b,"href","http://"+t.IP+":"+c)),b})()})),r})()}N(["input","click"]);var yt=u('<div class="card border-primary"><div class=card-header><div class=input-group style=width:fit-content;><span class=input-group-text>Host History for</span><input type=date class=form-control></div></div><div class=card-body>');function wt(t){const[l,i]=m("");z(()=>{$t(15e3),i(new Date().toLocaleDateString("en-CA"))});const n=d=>{i(""),i(d)};return(()=>{var d=yt(),_=d.firstChild,h=_.firstChild,a=h.firstChild,f=a.nextSibling,v=_.nextSibling;return f.$$input=o=>n(o.currentTarget.value),e(v,(()=>{var o=I(()=>t.mac!==""&&l()!=="");return()=>o()?C(ft,{get mac(){return t.mac},get date(){return l()}}):"Loading..."})()),p(()=>f.value=l()),d})()}N(["input"]);var kt=u("<div class=row><div class=col-md></div><div class=col-md>"),Pt=u('<div class="row mt-4"><div class=col-md>');function It(){const[t,l]=m(ut);return z(async()=>{const i=bt(),n=await ht(i.id);l(n)}),[(()=>{var i=kt(),n=i.firstChild,d=n.nextSibling;return e(n,C(vt,{get host(){return t()}})),e(d,C(Ct,{get IP(){return t().IP}})),i})(),(()=>{var i=Pt(),n=i.firstChild;return e(n,C(wt,{get mac(){return t().Mac}})),i})()]}export{It as default};
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import{C as y,D as f,E as v,o as m,G as H,e as u,S as d,t as D,b as I,s as b,H as h,l as M,F as g}from"./index.js";async function w(a,r){let e=[];return r===""?e=await y(a):e=await f(a,r),e!=null?(e.sort((s,t)=>s.Date<t.Date?1:-1),e):[]}var x=D("<i>");function F(a){const[r,e]=v([]);let s;return m(async()=>{const t=await w(a.mac,a.date);e(t),s=setInterval(async()=>{const o=await w(a.mac,a.date);e(o)},6e4)}),H(()=>{clearInterval(s)}),u(g,{each:r,children:(t,o)=>u(d,{get when(){return o()<M()},get children(){var i=x();return I(n=>{var c="Date:"+t.Date+`
|
|
||||||
Iface:`+t.Iface+`
|
|
||||||
IP:`+t.IP+`
|
|
||||||
Known:`+t.Known,l=t.Now===0?"my-box-off":"my-box-on";return c!==n.e&&b(i,"title",n.e=c),l!==n.t&&h(i,n.t=l),n},{e:void 0,t:void 0}),i}})})}export{F as M};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
:root{--transparent-light: #ffffff15}.my-btn{height:100%;background-color:#0000;color:var(--bs-primary);text-align:center;cursor:pointer;padding:1px;border-radius:15%}.my-btn:hover{background-color:var(--transparent-light)}.my-box-on:before,.my-box-off:before{content:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='18'></svg>");border-left:thin solid black}.my-box-on{background-color:var(--bs-success)}.my-box-off{background-color:var(--bs-gray-500)}.my-box-on:hover,.my-box-off:hover{background-color:#0000001a}
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
VERSION=2.1.4
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{{ define "index.html" }}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-bs-theme="dark">
|
|
||||||
<head>
|
|
||||||
<title>WatchYourLAN</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<!--Favicon-->
|
|
||||||
<link rel="icon" type="image/x-icon" href="/fs/public/favicon.png">
|
|
||||||
<!-- JS & CSS -->
|
|
||||||
<script type="module" crossorigin src="/fs/public/assets/index.js"></script>
|
|
||||||
<link rel="stylesheet" crossorigin href="/fs/public/assets/index.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"html/template"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/api"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
|
||||||
"github.com/aceberg/WatchYourLAN/internal/prometheus"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// templFS - html templates
|
|
||||||
//
|
|
||||||
//go:embed templates/*
|
|
||||||
var templFS embed.FS
|
|
||||||
|
|
||||||
// pubFS - public folder
|
|
||||||
//
|
|
||||||
//go:embed public/*
|
|
||||||
var pubFS embed.FS
|
|
||||||
|
|
||||||
// Gui - start web server
|
|
||||||
func Gui() {
|
|
||||||
const (
|
|
||||||
colorCyan = "\033[36m"
|
|
||||||
colorReset = "\033[0m"
|
|
||||||
)
|
|
||||||
|
|
||||||
file, err := pubFS.ReadFile("public/version")
|
|
||||||
check.IfError(err)
|
|
||||||
conf.AppConfig.Version = string(file)[8:]
|
|
||||||
|
|
||||||
address := conf.AppConfig.Host + ":" + conf.AppConfig.Port
|
|
||||||
|
|
||||||
slog.Info(colorCyan + "\n=================================== " +
|
|
||||||
"\n WatchYourLAN Version: " + conf.AppConfig.Version +
|
|
||||||
"\n Config dir: " + conf.AppConfig.DirPath +
|
|
||||||
"\n Default DB: " + conf.AppConfig.UseDB +
|
|
||||||
"\n Log level: " + conf.AppConfig.LogLevel +
|
|
||||||
"\n Web GUI: http://" + address +
|
|
||||||
"\n=================================== " + colorReset)
|
|
||||||
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
router := gin.New()
|
|
||||||
router.Use(gin.Recovery())
|
|
||||||
|
|
||||||
templ := template.Must(template.New("").ParseFS(templFS, "templates/*"))
|
|
||||||
router.SetHTMLTemplate(templ) // templates
|
|
||||||
|
|
||||||
router.StaticFS("/fs/", http.FS(pubFS)) // public
|
|
||||||
|
|
||||||
router.GET("/", indexHandler) // index.go
|
|
||||||
router.GET("/config", indexHandler) // index.go
|
|
||||||
router.GET("/history", indexHandler) // index.go
|
|
||||||
router.GET("/host/*any", indexHandler) // index.go
|
|
||||||
router.GET("/metrics", prometheus.Handler())
|
|
||||||
|
|
||||||
api.Routes(router)
|
|
||||||
|
|
||||||
err = router.Run(address)
|
|
||||||
check.IfError(err)
|
|
||||||
}
|
|
||||||
31
cmd/WatchYourLAN/main.go
Normal file
31
cmd/WatchYourLAN/main.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
// "net/http"
|
||||||
|
|
||||||
|
// _ "net/http/pprof"
|
||||||
|
_ "time/tzdata"
|
||||||
|
|
||||||
|
"github.com/aceberg/WatchYourLAN/internal/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dirPath = "/data/WatchYourLAN"
|
||||||
|
const nodePath = ""
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dirPtr := flag.String("d", dirPath, "Path to config dir")
|
||||||
|
nodePtr := flag.String("n", nodePath, "Path to node modules")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// pprof - memory leak detect
|
||||||
|
// go tool pprof -alloc_space http://localhost:8085/debug/pprof/heap
|
||||||
|
// (pprof) web
|
||||||
|
// (pprof) list db.Select
|
||||||
|
//
|
||||||
|
// go func() {
|
||||||
|
// http.ListenAndServe("localhost:8085", nil)
|
||||||
|
// }()
|
||||||
|
|
||||||
|
web.Gui(*dirPtr, *nodePtr) // webgui.go
|
||||||
|
}
|
||||||
25
docker-compose-local.yml
Normal file
25
docker-compose-local.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
node-bootstrap:
|
||||||
|
image: aceberg/node-bootstrap
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 8850:8850
|
||||||
|
wyl:
|
||||||
|
image: aceberg/watchyourlan
|
||||||
|
network_mode: "host"
|
||||||
|
restart: unless-stopped
|
||||||
|
command: "-n http://YOUR_IP:8850" # put your server IP or DNS name here
|
||||||
|
depends_on:
|
||||||
|
- node-bootstrap
|
||||||
|
volumes:
|
||||||
|
- ~/.dockerdata/wyl:/data/WatchYourLAN
|
||||||
|
environment:
|
||||||
|
TZ: Asia/Novosibirsk # required: needs your TZ for correct time
|
||||||
|
IFACES: "enp4s0 wlxf4ec3892dd51" # required: 1 or more interface
|
||||||
|
HOST: "0.0.0.0" # optional, default: 0.0.0.0
|
||||||
|
PORT: "8840" # optional, default: 8840
|
||||||
|
TIMEOUT: "120" # optional, time in seconds, default: 120
|
||||||
|
SHOUTRRR_URL: "" # optional, set url to notify
|
||||||
|
THEME: "sand" # optional
|
||||||
|
COLOR: "dark" # optional
|
||||||
|
|
@ -1,28 +1,17 @@
|
||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
# node-bootstrap: # optional, local themes and icons
|
|
||||||
# image: aceberg/node-bootstrap # dockerhub
|
|
||||||
# # image: ghcr.io/aceberg/node-bootstrap # or github
|
|
||||||
# restart: unless-stopped
|
|
||||||
# ports:
|
|
||||||
# - 8850:8850
|
|
||||||
wyl:
|
wyl:
|
||||||
image: aceberg/watchyourlan # dockerhub
|
image: aceberg/watchyourlan
|
||||||
# image: ghcr.io/aceberg/watchyourlan # or github
|
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# uncomment those if you are using local node-bootstrap:
|
|
||||||
# command: "-n http://YOUR_IP:8850" # put your server IP or DNS name here
|
|
||||||
# depends_on:
|
|
||||||
# - node-bootstrap
|
|
||||||
volumes:
|
volumes:
|
||||||
- ~/.dockerdata/wyl:/data/WatchYourLAN
|
- ~/.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
|
||||||
IFACES: "enp4s0 wlxf4ec3892dd51" # required: 1 or more interface
|
IFACES: "enp4s0 wlxf4ec3892dd51" # required: 1 or more interface
|
||||||
# HOST: "0.0.0.0" # optional, default: 0.0.0.0
|
HOST: "0.0.0.0" # optional, default: 0.0.0.0
|
||||||
# PORT: "8840" # optional, default: 8840
|
PORT: "8840" # optional, default: 8840
|
||||||
# TIMEOUT: "120" # optional, time in seconds, default: 120
|
TIMEOUT: "120" # optional, time in seconds, default: 120
|
||||||
# SHOUTRRR_URL: "" # optional, set url to notify
|
SHOUTRRR_URL: "" # optional, set url to notify
|
||||||
# THEME: "sand" # optional
|
THEME: "sand" # optional
|
||||||
# COLOR: "dark" # optional
|
COLOR: "dark" # optional
|
||||||
14
docs/API.md
14
docs/API.md
|
|
@ -6,19 +6,9 @@ Returns all hosts in `json`.
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/history
|
GET /api/history/*mac
|
||||||
```
|
```
|
||||||
Returns all History. Not recommended, the output can be a lot.
|
Returns all History. If `mac` is not empty, returns only history of a device with this `mac`.
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/history/:mac/:date
|
|
||||||
```
|
|
||||||
Returns only history of a device with this `mac` filtered by `date`. `date` format can be anything from `2` to `2025-07` to `2025-07-26`.
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/history/:mac?num=20
|
|
||||||
```
|
|
||||||
Returns only last 20 lines of history of a device with this `mac`.
|
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ build:
|
||||||
|
|
||||||
replace:
|
replace:
|
||||||
cd ../backend/internal/web/public/assets/ && \
|
cd ../backend/internal/web/public/assets/ && \
|
||||||
cat index.js | sed 's/assets/fs\/public\/assets/g;s/http:\/\/0.0.0.0:8840//' > tmp && \
|
cat index-*.js | sed 's/assets/fs\/public\/assets/g;s/http:\/\/0.0.0.0:8840//' > index.js && \
|
||||||
mv tmp index.js
|
cat index-*.css | sed 's/assets/fs\/public\/assets/g' > index.css && \
|
||||||
|
rm index-*
|
||||||
|
|
||||||
all: build replace
|
all: build replace
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>WatchYourLAN</title>
|
<title>WatchYourLAN</title>
|
||||||
|
<link rel="stylesheet" href="http://192.168.2.3:8850/node_modules/bootstrap-icons/font/bootstrap-icons.css">
|
||||||
|
<link href="http://192.168.2.3:8850/node_modules/bootswatch/dist/sand/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="http://192.168.2.3:8850/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
|
|
@ -1,14 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "watchyourlan",
|
"name": "watchyourlan",
|
||||||
"version": "2.1.3",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "watchyourlan",
|
"name": "watchyourlan",
|
||||||
"version": "2.1.3",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solid-primitives/scheduled": "^1.5.2",
|
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"solid-js": "^1.9.5"
|
"solid-js": "^1.9.5"
|
||||||
},
|
},
|
||||||
|
|
@ -965,15 +964,6 @@
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@solid-primitives/scheduled": {
|
|
||||||
"version": "1.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@solid-primitives/scheduled/-/scheduled-1.5.2.tgz",
|
|
||||||
"integrity": "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"solid-js": "^1.6.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@solidjs/router": {
|
"node_modules/@solidjs/router": {
|
||||||
"version": "0.15.3",
|
"version": "0.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.15.3.tgz",
|
"resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.15.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "watchyourlan",
|
"name": "watchyourlan",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.1.4",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solid-primitives/scheduled": "^1.5.2",
|
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"solid-js": "^1.9.5"
|
"solid-js": "^1.9.5"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,10 @@
|
||||||
/* VARS */
|
|
||||||
:root {
|
|
||||||
--transparent-light: #ffffff15;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sort button */
|
/* Sort button */
|
||||||
.my-btn {
|
.my-btn {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #00000000;
|
background-color: #00000000;
|
||||||
color: var(--bs-primary);
|
color: var(--bs-primary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
|
||||||
padding: 1px;
|
|
||||||
border-radius: 15%;
|
|
||||||
}
|
}
|
||||||
.my-btn:hover {
|
.my-btn:hover {
|
||||||
background-color: var(--transparent-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* History box */
|
|
||||||
.my-box-on::before, .my-box-off::before {
|
|
||||||
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='18'></svg>");
|
|
||||||
border-left: thin solid black;
|
|
||||||
}
|
|
||||||
.my-box-on {
|
|
||||||
background-color: var(--bs-success);
|
|
||||||
}
|
|
||||||
.my-box-off {
|
|
||||||
background-color: var(--bs-gray-500);
|
|
||||||
}
|
|
||||||
.my-box-on:hover, .my-box-off:hover {
|
|
||||||
background-color: #0000001a;
|
background-color: #0000001a;
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import './App.css';
|
||||||
import { runAtStart } from './functions/atstart';
|
import { runAtStart } from './functions/atstart';
|
||||||
|
|
||||||
import Body from './pages/Body';
|
import Body from './pages/Body';
|
||||||
import Header from './components/Header';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
|
|
@ -12,26 +11,19 @@ function App() {
|
||||||
runAtStart();
|
runAtStart();
|
||||||
});
|
});
|
||||||
|
|
||||||
const Config = lazy(() => import("./pages/Config"));
|
|
||||||
const History = lazy(() => import("./pages/History"));
|
|
||||||
const HostPage = lazy(() => import("./pages/HostPage"));
|
const HostPage = lazy(() => import("./pages/HostPage"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Header></Header>
|
|
||||||
<div class="container-lg">
|
<div class="container-lg">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md mt-4 mb-4">
|
<div class="col-md mt-4 mb-4">
|
||||||
<Router>
|
<Router>
|
||||||
<Route path="/" component={Body}/>
|
<Route path="/" component={Body}/>
|
||||||
<Route path="/config" component={Config}/>
|
|
||||||
<Route path="/history" component={History}/>
|
|
||||||
<Route path="/host/:id" component={HostPage}/>
|
<Route path="/host/:id" component={HostPage}/>
|
||||||
</Router>
|
</Router>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import { Show } from "solid-js";
|
|
||||||
import { editNames, selectedIDs, setEditNames } from "../../functions/exports";
|
|
||||||
import Filter from "../Filter";
|
|
||||||
import Search from "../Search";
|
|
||||||
import { getHosts } from "../../functions/atstart";
|
|
||||||
import { apiDelHost } from "../../functions/api";
|
|
||||||
|
|
||||||
function CardHead() {
|
|
||||||
|
|
||||||
const handleEditNames = (toggle: boolean) => {
|
|
||||||
if (!toggle) {
|
|
||||||
getHosts();
|
|
||||||
}
|
|
||||||
setEditNames(toggle);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDel = async () => {
|
|
||||||
const ids = selectedIDs();
|
|
||||||
|
|
||||||
for (let id of ids) {
|
|
||||||
await apiDelHost(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.href = '/';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md mt-1 mb-1">
|
|
||||||
<div class="d-flex justify-left">
|
|
||||||
<Filter></Filter>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md mt-1 mb-1">
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<Search></Search>
|
|
||||||
<Show
|
|
||||||
when={editNames()}
|
|
||||||
fallback={<button class="btn btn-outline-primary" title="Toggle edit" onClick={[handleEditNames, true]}>Edit</button>}
|
|
||||||
>
|
|
||||||
<button type="button" onClick={handleDel} title="Delete selected hosts" class="btn btn-outline-danger">Delete selected</button>
|
|
||||||
<button class="btn btn-primary" title="Toggle edit" onClick={[handleEditNames, false]}>Edit</button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CardHead
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { createSignal, For } from "solid-js";
|
|
||||||
import { Host } from "../../functions/exports";
|
|
||||||
import { sortByAnyField } from "../../functions/sort";
|
|
||||||
|
|
||||||
function TableHead() {
|
|
||||||
|
|
||||||
const [sortField, setSortField] = createSignal<string>('');
|
|
||||||
|
|
||||||
const showSort = () => {
|
|
||||||
let field = localStorage.getItem("sortField") as string;
|
|
||||||
field === "Mac" ? field = "MAC" : '';
|
|
||||||
field === "Hw" ? field = "Hardware" : '';
|
|
||||||
field === "Now" ? field = "On" : '';
|
|
||||||
setSortField(field);
|
|
||||||
};
|
|
||||||
showSort();
|
|
||||||
|
|
||||||
const handleSort = (sortBy: string) => {
|
|
||||||
setSortField(sortBy);
|
|
||||||
sortBy === "MAC" ? sortBy = "Mac" : '';
|
|
||||||
sortBy === "Hardware" ? sortBy = "Hw" : '';
|
|
||||||
sortBy === "On" ? sortBy = "Now" : '';
|
|
||||||
sortByAnyField(sortBy as keyof Host);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="width: 2em;"></th>
|
|
||||||
<For each={["Name", "Iface", "IP", "MAC", "Hardware", "Date", "Known", "On"]}>{(key) =>
|
|
||||||
<th
|
|
||||||
style={key === sortField() ? "color: var(--bs-primary);" : ''}
|
|
||||||
>{key} <i
|
|
||||||
class="bi bi-sort-down-alt my-btn"
|
|
||||||
onClick={[handleSort, key]}
|
|
||||||
title={"Sort by " + key}
|
|
||||||
></i></th>
|
|
||||||
}</For>
|
|
||||||
<th style="width: 2em;" title="Edit"><i class="bi bi-pencil-fill"></i></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TableHead
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
import { createSignal, Show } from "solid-js";
|
|
||||||
import { editNames, selectedIDs, setSelectedIDs } from "../../functions/exports";
|
|
||||||
import { apiEditHost } from "../../functions/api";
|
|
||||||
|
|
||||||
import { debounce } from "@solid-primitives/scheduled";
|
|
||||||
|
|
||||||
function TableRow(_props: any) {
|
|
||||||
|
|
||||||
const [name, setName] = createSignal(_props.host.Name);
|
|
||||||
|
|
||||||
let now = <i class="bi bi-circle-fill" style="color:var(--bs-gray-500);"></i>;
|
|
||||||
if (_props.host.Now == 1) {
|
|
||||||
now = <i class="bi bi-check-circle-fill" style="color:var(--bs-success);"></i>;
|
|
||||||
};
|
|
||||||
|
|
||||||
let known:boolean;
|
|
||||||
_props.host.Known === 1 ? known = true : known = false;
|
|
||||||
|
|
||||||
const debouncedApi = debounce(async (val: string) => {
|
|
||||||
await apiEditHost(_props.host.ID, val, "");
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
const handleInput = async (n: string) => {
|
|
||||||
setName(n);
|
|
||||||
debouncedApi(n);
|
|
||||||
};
|
|
||||||
const handleToggle = async () => {
|
|
||||||
await apiEditHost(_props.host.ID, name(), "toggle");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheck = (checked: boolean) => {
|
|
||||||
const id = _props.host.ID;
|
|
||||||
setSelectedIDs(prev => {
|
|
||||||
if (checked) {
|
|
||||||
return prev.includes(id) ? prev : [...prev, id];
|
|
||||||
} else {
|
|
||||||
return prev.filter(item => item !== id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td class="opacity-50">{_props.index}.</td>
|
|
||||||
<td>
|
|
||||||
<Show
|
|
||||||
when={editNames()}
|
|
||||||
fallback={name()}
|
|
||||||
>
|
|
||||||
<input type="text" class="form-control" value={name()}
|
|
||||||
onInput={e => handleInput(e.target.value)}></input>
|
|
||||||
</Show>
|
|
||||||
</td>
|
|
||||||
<td>{_props.host.Iface}</td>
|
|
||||||
<td><a href={"http://" + _props.host.IP} target="_blank">{_props.host.IP}</a></td>
|
|
||||||
<td>{_props.host.Mac}</td>
|
|
||||||
<td title={_props.host.Hw}>{_props.host.Hw.slice(0,12)+".."}</td>
|
|
||||||
<td>{_props.host.Date}</td>
|
|
||||||
<td>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" checked={known}
|
|
||||||
onClick={handleToggle}></input>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{now}</td>
|
|
||||||
<td>
|
|
||||||
<Show
|
|
||||||
when={editNames()}
|
|
||||||
fallback={
|
|
||||||
<a href={"/host/" + _props.host.ID}>
|
|
||||||
<i class="bi bi-three-dots-vertical my-btn p-2" title="More"></i>
|
|
||||||
</a>}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="form-check-input"
|
|
||||||
checked={selectedIDs().includes(_props.host.ID)}
|
|
||||||
onChange={e => handleCheck((e.target as HTMLInputElement).checked)}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TableRow
|
|
||||||
28
frontend/src/components/BodyCardHead.tsx
Normal file
28
frontend/src/components/BodyCardHead.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
import { editNames, setEditNames } from "../functions/exports";
|
||||||
|
import Filter from "./Filter";
|
||||||
|
import Search from "./Search";
|
||||||
|
|
||||||
|
function BodyCardHead() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md mt-1 mb-1">
|
||||||
|
<Filter></Filter>
|
||||||
|
</div>
|
||||||
|
<div class="col-md mt-1 mb-1">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<Search></Search>
|
||||||
|
<Show
|
||||||
|
when={editNames()}
|
||||||
|
fallback={<button class="btn btn-outline-primary" onClick={[setEditNames, true]}>Edit names</button>}
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary" onClick={[setEditNames, false]}>Edit names</button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BodyCardHead
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
import { createSignal, onMount } from "solid-js";
|
|
||||||
import { apiGetVersion } from "../../functions/api"
|
|
||||||
|
|
||||||
function About() {
|
|
||||||
|
|
||||||
const [version, setVersion] = createSignal('');
|
|
||||||
const [link, setLink] = createSignal('');
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const v = await apiGetVersion();
|
|
||||||
setVersion(v);
|
|
||||||
setLink("https://github.com/aceberg/WatchYourLAN/releases/tag/"+v);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-header">
|
|
||||||
About (<a href={link()} target="_blank">{version()}</a>)
|
|
||||||
</div>
|
|
||||||
<div class="card-body table-responsive">
|
|
||||||
<table class="table table-striped"><tbody>
|
|
||||||
<tr>
|
|
||||||
<td><b>Swagger API docs</b></td>
|
|
||||||
<td><a href="/swagger/index.html" target="_blank">/swagger/index.html</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Local node-bootstrap URL</b></td>
|
|
||||||
<td>local themes and fonts (optional). If empty, the app will pull everything from <code>cdn</code></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Shoutrrr URL</b></td>
|
|
||||||
<td>provides notifications to Discord, Email, Gotify, Telegram and other services. <a href="https://shoutrrr.nickfedor.com/services/overview/" target="_blank">Link to documentation</a></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Interfaces</b></td>
|
|
||||||
<td>one or more, space separated</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Timeout (seconds)</b></td>
|
|
||||||
<td>time between scans</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Args for arp-scan</b></td>
|
|
||||||
<td>pass your own arguments to <code>arp-scan</code>. Enable <b>debug</b> log level to see resulting command. (Example: <code>-r 1</code>). See <a href="https://github.com/aceberg/WatchYourLAN/blob/main/docs/VLAN_ARP_SCAN.md" target="_blank">docs</a> for more</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Arp Strings</b></td>
|
|
||||||
<td>can setup scans for <code>vlans</code>, <code>docker0</code> and etcetera. See <a href="https://github.com/aceberg/WatchYourLAN/blob/main/docs/VLAN_ARP_SCAN.md" target="_blank">docs</a> for more</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Trim History</b></td>
|
|
||||||
<td>remove history after (hours)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>PG Connect URL</b></td>
|
|
||||||
<td>address to connect to PostgreSQL DB. (Example: <code>postgres://username:password@192.168.0.1:5432/dbname?sslmode=disable</code>). Full list of URL parameters <a href="https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters" target="_blank">here</a></td>
|
|
||||||
</tr>
|
|
||||||
</tbody></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default About
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
import { For, Show } from "solid-js";
|
|
||||||
import { apiPath, apiTestNotify } from "../../functions/api"
|
|
||||||
import { appConfig } from "../../functions/exports"
|
|
||||||
|
|
||||||
function Basic() {
|
|
||||||
|
|
||||||
const themes = ["cerulean", "cosmo", "cyborg", "darkly", "emerald", "flatly", "grass", "grayscale", "journal", "litera", "lumen", "lux", "materia", "minty", "morph", "ocean", "pulse", "quartz", "sand", "sandstone", "simplex", "sketchy", "slate", "solar", "spacelab", "superhero", "united", "vapor", "wood", "yeti", "zephyr"];
|
|
||||||
|
|
||||||
const handleTestNotify = () => {
|
|
||||||
apiTestNotify();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-header">Basic config</div>
|
|
||||||
<div class="card-body table-responsive">
|
|
||||||
<form action={apiPath + '/api/config/'} method="post">
|
|
||||||
<table class="table table-borderless">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Host</td>
|
|
||||||
<td><input name="host" type="text" class="form-control" value={appConfig().Host}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Port</td>
|
|
||||||
<td><input name="port" type="text" class="form-control" value={appConfig().Port}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Theme</td>
|
|
||||||
<td>
|
|
||||||
<select name="theme" class="form-select">
|
|
||||||
<For each={themes}>{theme =>
|
|
||||||
<Show
|
|
||||||
when={theme == appConfig().Theme}
|
|
||||||
fallback={<option value={theme}>{theme}</option>}
|
|
||||||
>
|
|
||||||
<option value={theme} selected>{theme}</option>
|
|
||||||
</Show>
|
|
||||||
}</For>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Color mode</td>
|
|
||||||
<td>
|
|
||||||
<select name="color" class="form-select">
|
|
||||||
<Show
|
|
||||||
when={appConfig().Color == "dark"}
|
|
||||||
fallback={<>
|
|
||||||
<option value="dark">dark</option>
|
|
||||||
<option value="light" selected>light</option>
|
|
||||||
</>}
|
|
||||||
>
|
|
||||||
<option value="dark" selected>dark</option>
|
|
||||||
<option value="light">light</option>
|
|
||||||
</Show>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Local node-bootstrap URL</td>
|
|
||||||
<td><input name="node" type="text" class="form-control" value={appConfig().NodePath}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Shoutrrr URL</td>
|
|
||||||
<td>
|
|
||||||
<textarea name="shout" class="form-control" style="width: 100%;" rows="3" wrap="soft">{appConfig().ShoutURL}</textarea>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><button type="submit" class="btn btn-primary">Save</button></td>
|
|
||||||
<td><button onClick={handleTestNotify} type="button" style="float: right;" class="btn btn-info">Test notification</button></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Basic
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
function Donate() {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-info">
|
|
||||||
<div class="card-header">Donations and Work</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p>Every <a href="https://github.com/aceberg#donate" target="_blank">donation</a> is truly appreciated!</p>
|
|
||||||
<p>I am offering self-hosted and mobile apps development for a small price.
|
|
||||||
The Open Source apps I created are listed in my <a href="https://aceberg.github.io/portfolio/" target="_blank">portfolio</a>. They are all human-coded, not generated by AI. Please, <a href="https://github.com/aceberg#contact" target="_blank">contact</a> me, if you are interested.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Donate
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import { apiPath } from "../../functions/api"
|
|
||||||
import { appConfig } from "../../functions/exports"
|
|
||||||
|
|
||||||
function Influx() {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-header">InfluxDB2 config</div>
|
|
||||||
<div class="card-body table-responsive">
|
|
||||||
<form action={apiPath + '/api/config_influx/'} method="post">
|
|
||||||
<table class="table table-borderless"><tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Enable</td>
|
|
||||||
<td>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
{appConfig().InfluxEnable
|
|
||||||
? <input class="form-check-input" type="checkbox" name="enable" checked></input>
|
|
||||||
: <input class="form-check-input" type="checkbox" name="enable"></input>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Address</td>
|
|
||||||
<td><input name="addr" type="text" class="form-control" value={appConfig().InfluxAddr}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Token</td>
|
|
||||||
<td><input name="token" type="text" class="form-control" value={appConfig().InfluxToken}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Org</td>
|
|
||||||
<td><input name="org" type="text" class="form-control" value={appConfig().InfluxOrg}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Bucket</td>
|
|
||||||
<td><input name="bucket" type="text" class="form-control" value={appConfig().InfluxBucket}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Skip TLS verify</td>
|
|
||||||
<td>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
{appConfig().InfluxSkipTLS
|
|
||||||
? <input class="form-check-input" type="checkbox" name="skip" checked></input>
|
|
||||||
: <input class="form-check-input" type="checkbox" name="skip"></input>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><button type="submit" class="btn btn-primary">Save</button></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tbody></table>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Influx
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import { apiPath } from "../../functions/api"
|
|
||||||
import { appConfig } from "../../functions/exports"
|
|
||||||
|
|
||||||
function Prometheus() {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-header">Prometheus config</div>
|
|
||||||
<div class="card-body table-responsive">
|
|
||||||
<form action={apiPath + '/api/config_prometheus/'} method="post">
|
|
||||||
<table class="table table-borderless"><tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Enable</td>
|
|
||||||
<td>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
{appConfig().PrometheusEnable
|
|
||||||
? <input class="form-check-input" type="checkbox" name="enable" checked></input>
|
|
||||||
: <input class="form-check-input" type="checkbox" name="enable"></input>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><button type="submit" class="btn btn-primary">Save</button></td>
|
|
||||||
<td>
|
|
||||||
<a href="/metrics" target="_blank">/metrics</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody></table>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Prometheus
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
import { For, Show } from "solid-js"
|
|
||||||
import { appConfig } from "../../functions/exports"
|
|
||||||
import { apiPath } from "../../functions/api"
|
|
||||||
|
|
||||||
function Scan() {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-header">Scan settings</div>
|
|
||||||
<div class="card-body table-responsive">
|
|
||||||
<form action={apiPath + '/api/config_settings/'} method="post">
|
|
||||||
<table class="table table-borderless"><tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Interfaces</td>
|
|
||||||
<td><input name="ifaces" type="text" class="form-control" value={appConfig().Ifaces}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Timeout (seconds)</td>
|
|
||||||
<td><input name="timeout" type="number" class="form-control" value={appConfig().Timeout}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Args for arp-scan</td>
|
|
||||||
<td><input name="arpargs" type="text" class="form-control" value={appConfig().ArpArgs}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Arp Strings</td>
|
|
||||||
<td>
|
|
||||||
<For each={appConfig().ArpStrs}>{arpStr =>
|
|
||||||
<input name="arpstrs" type="text" class="form-control" value={arpStr}></input>
|
|
||||||
}</For>
|
|
||||||
<input name="arpstrs" type="text" class="form-control"></input>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Log level</td>
|
|
||||||
<td><select name="log" class="form-select">
|
|
||||||
<For each={["debug","info","warn","error"]}>{level =>
|
|
||||||
<Show
|
|
||||||
when={level == appConfig().LogLevel}
|
|
||||||
fallback={<option value={level}>{level}</option>}
|
|
||||||
>
|
|
||||||
<option value={level} selected>{level}</option>
|
|
||||||
</Show>
|
|
||||||
}</For>
|
|
||||||
</select></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Trim History (hours)</td>
|
|
||||||
<td><input name="trim" type="number" class="form-control" value={appConfig().TrimHist}></input></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Use DB</td>
|
|
||||||
<td><select name="usedb" class="form-select">
|
|
||||||
<Show
|
|
||||||
when={appConfig().UseDB == "sqlite"}
|
|
||||||
fallback={<>
|
|
||||||
<option value="sqlite">sqlite</option>
|
|
||||||
<option value="postgres" selected>postgres</option>
|
|
||||||
</>}
|
|
||||||
>
|
|
||||||
<option value="sqlite" selected>sqlite</option>
|
|
||||||
<option value="postgres">postgres</option>
|
|
||||||
</Show>
|
|
||||||
</select></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>PG Connect URL</td>
|
|
||||||
<td>
|
|
||||||
<textarea name="pgconnect" class="form-control" style="width: 100%;" rows="3" wrap="soft">{appConfig().PGConnect}</textarea>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><button type="submit" class="btn btn-primary">Save</button></td>
|
|
||||||
<td class="text-muted">*Pressing <b>Save</b> button will trigger rescan</td>
|
|
||||||
</tr>
|
|
||||||
</tbody></table>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Scan
|
|
||||||
|
|
@ -1,49 +1,35 @@
|
||||||
import { createSignal, For } from "solid-js";
|
import { For } from "solid-js";
|
||||||
import { Host, ifaces, setHistUpdOnFilter } from "../functions/exports";
|
import { Host, ifaces } from "../functions/exports";
|
||||||
import { filterFunc } from "../functions/filter";
|
import { filterFunc } from "../functions/filter";
|
||||||
|
|
||||||
|
|
||||||
function Filter() {
|
function Filter() {
|
||||||
type FilterEvent = Event & {
|
|
||||||
currentTarget: HTMLSelectElement;
|
|
||||||
target: HTMLSelectElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [selectValue, setSelectValue] = createSignal("");
|
const handleFilter = (field: keyof Host, value: any) => {
|
||||||
|
filterFunc(field, value);
|
||||||
const handleFilter = (field: keyof Host, event: FilterEvent) => {
|
|
||||||
const value = event.target ? event.target.value : 0;
|
|
||||||
filterFunc(field, value);
|
|
||||||
setHistUpdOnFilter(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
filterFunc("ID", 0);
|
|
||||||
setSelectValue("something");
|
|
||||||
setSelectValue("");
|
|
||||||
setHistUpdOnFilter(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col input-group">
|
<div class="col input-group">
|
||||||
<select onChange={(event)=>{handleFilter("Iface", event)}} class="form-select" title="Filter by Iface" value={selectValue()}>
|
<span class="input-group-text">Filter by</span>
|
||||||
<option value="" selected disabled>Iface</option>
|
<select class="form-select">
|
||||||
|
<option selected disabled>Iface</option>
|
||||||
<For each={ifaces()}>{(iface) =>
|
<For each={ifaces()}>{(iface) =>
|
||||||
<option value={iface}>{iface}</option>
|
<option onClick={()=>{handleFilter("Iface", iface)}}>{iface}</option>
|
||||||
}</For>
|
}</For>
|
||||||
</select>
|
</select>
|
||||||
<select onChange={(event)=>{handleFilter("Known", event)}} class="form-select" title="Filter by Known" value={selectValue()}>
|
<select class="form-select">
|
||||||
<option value="" selected disabled>Known</option>
|
<option selected disabled>Known</option>
|
||||||
<option value={1}>Known</option>
|
<option onClick={()=>{handleFilter("Known", 1)}}>Known</option>
|
||||||
<option value={0}>Unknown</option>
|
<option onClick={()=>{handleFilter("Known", 0)}}>Unknown</option>
|
||||||
</select>
|
</select>
|
||||||
<select onChange={(event)=>{handleFilter("Now", event)}} class="form-select" title="Filter by Online" value={selectValue()}>
|
<select class="form-select">
|
||||||
<option value="" selected disabled>Online</option>
|
<option selected disabled>Online</option>
|
||||||
<option value={1}>On</option>
|
<option onClick={()=>{handleFilter("Now", 1)}}>Online</option>
|
||||||
<option value={0}>Off</option>
|
<option onClick={()=>{handleFilter("Now", 0)}}>Offline</option>
|
||||||
</select>
|
</select>
|
||||||
<button onClick={handleReset} class="btn btn-outline-primary" title="Reset filter">Reset filter</button>
|
<button onClick={()=>{handleFilter("ID", 0)}} class="btn btn-outline-primary">Reset filter</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import { createSignal } from "solid-js";
|
|
||||||
import { appConfig, setAppConfig } from "../functions/exports";
|
|
||||||
import { apiGetConfig } from "../functions/api";
|
|
||||||
|
|
||||||
function Header() {
|
|
||||||
|
|
||||||
const [themePath, setThemePath] = createSignal('');
|
|
||||||
const [iconsPath, setIconsPath] = createSignal('');
|
|
||||||
|
|
||||||
const setCurrentTheme = async () => {
|
|
||||||
setAppConfig(await apiGetConfig());
|
|
||||||
|
|
||||||
const theme = appConfig().Theme?appConfig().Theme:"sand";
|
|
||||||
const color = appConfig().Color?appConfig().Color:"dark";
|
|
||||||
|
|
||||||
if (appConfig().NodePath == '') {
|
|
||||||
setThemePath("https://cdn.jsdelivr.net/npm/aceberg-bootswatch-fork@v5.3.3-2/dist/"+theme+"/bootstrap.min.css");
|
|
||||||
setIconsPath("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css");
|
|
||||||
} else {
|
|
||||||
setThemePath(appConfig().NodePath+"/node_modules/bootswatch/dist/"+theme+"/bootstrap.min.css");
|
|
||||||
setIconsPath(appConfig().NodePath+"/node_modules/bootstrap-icons/font/bootstrap-icons.css");
|
|
||||||
}
|
|
||||||
|
|
||||||
document.documentElement.setAttribute("data-bs-theme", color);
|
|
||||||
color === "dark"
|
|
||||||
? document.documentElement.style.setProperty('--transparent-light', '#ffffff15')
|
|
||||||
: document.documentElement.style.setProperty('--transparent-light', '#00000015');
|
|
||||||
}
|
|
||||||
setCurrentTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<link rel="stylesheet" href={iconsPath()}></link> {/* icons */}
|
|
||||||
<link rel="stylesheet" href={themePath()}></link> {/* theme */}
|
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-primary">
|
|
||||||
<div class="container-lg">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
<img src="/fs/public/favicon.png" style="width: 2em"/>
|
|
||||||
</a>
|
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" href="/" title="Home">Home</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" href="/config/" title="Config">Config</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" href="/history/" title="History">History</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active fs-3 ms-md-2" target="_blank" href="https://github.com/aceberg/WatchYourLAN" title="Github"><i class="bi bi-github"></i></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { setShow, show } from "../functions/exports";
|
|
||||||
|
|
||||||
function HistShow(_props: any) {
|
|
||||||
|
|
||||||
const handleSaveShow = (showStr: string) => {
|
|
||||||
localStorage.setItem(_props.name, showStr);
|
|
||||||
|
|
||||||
setShow(+showStr);
|
|
||||||
show() == 0 ? setShow(200) : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<input class="form-control" onInput={e => handleSaveShow(e.target.value)} placeholder="Show elements" title="Nomber of elements to show" style="max-width: 10em;"></input>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HistShow
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { createSignal, onMount } from "solid-js";
|
|
||||||
import { setShow } from "../../functions/exports";
|
|
||||||
import MacHistory from "../MacHistory"
|
|
||||||
|
|
||||||
function HistCard(_props: any) {
|
|
||||||
|
|
||||||
const [today, setToday] = createSignal('');
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
setShow(15000);
|
|
||||||
setToday(new Date().toLocaleDateString("en-CA"));
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDate = (date: string) => {
|
|
||||||
setToday("");
|
|
||||||
setToday(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="input-group" style="width: fit-content;">
|
|
||||||
<span class="input-group-text">Host History for</span>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
class="form-control"
|
|
||||||
value={today()}
|
|
||||||
onInput={(e) => handleDate(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{_props.mac !== "" && today() !== ""
|
|
||||||
? <MacHistory mac={_props.mac} date={today()}></MacHistory>
|
|
||||||
: <>Loading...</>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HistCard
|
|
||||||
|
|
@ -1,41 +1,31 @@
|
||||||
import { apiDelHost, apiEditHost, apiWOL } from "../../functions/api";
|
import { apiDelHost, apiEditHost } from "../functions/api";
|
||||||
|
import { currentHost } from "../functions/exports";
|
||||||
|
|
||||||
import { debounce } from "@solid-primitives/scheduled";
|
function HostPageHost() {
|
||||||
|
|
||||||
function HostCard(_props: any) {
|
|
||||||
|
|
||||||
let name:string = "";
|
let name:string = "";
|
||||||
|
|
||||||
const debouncedApi = debounce(async (val: string) => {
|
|
||||||
await apiEditHost(_props.host.ID, val, "");
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
const handleInput = async (n: string) => {
|
const handleInput = async (n: string) => {
|
||||||
|
|
||||||
name = n;
|
name = n;
|
||||||
debouncedApi(n);
|
await apiEditHost(currentHost().ID, name, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggle = async () => {
|
const handleToggle = async () => {
|
||||||
|
|
||||||
if (name == "") {
|
if (name == "") {
|
||||||
name = _props.host.Name;
|
name = currentHost().Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
await apiEditHost(_props.host.ID, name, 'toggle');
|
await apiEditHost(currentHost().ID, name, 'toggle');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDel = async () => {
|
const handleDel = async () => {
|
||||||
|
|
||||||
await apiDelHost(_props.host.ID);
|
await apiDelHost(currentHost().ID);
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWOL = async () => {
|
|
||||||
|
|
||||||
await apiWOL(_props.host.Mac);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="card border-primary">
|
<div class="card border-primary">
|
||||||
<div class="card-header">Host</div>
|
<div class="card-header">Host</div>
|
||||||
|
|
@ -44,40 +34,40 @@ function HostCard(_props: any) {
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>ID</td>
|
<td>ID</td>
|
||||||
<td>{_props.host.ID}</td>
|
<td>{currentHost().ID}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" class="form-control" value={_props.host.Name}
|
<input type="text" class="form-control" value={currentHost().Name}
|
||||||
onInput={e => handleInput(e.target.value)}></input>
|
onInput={e => handleInput(e.target.value)}></input>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>DNS name</td>
|
<td>DNS name</td>
|
||||||
<td>{_props.host.DNS}</td>
|
<td>{currentHost().DNS}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Iface</td>
|
<td>Iface</td>
|
||||||
<td>{_props.host.Iface}</td>
|
<td>{currentHost().Iface}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>IP</td>
|
<td>IP</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={"http://" + _props.host.IP} target="_blank">{_props.host.IP}</a>
|
<a href={"http://" + currentHost().IP} target="_blank">{currentHost().IP}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>MAC</td>
|
<td>MAC</td>
|
||||||
<td>{_props.host.Mac}</td>
|
<td>{currentHost().Mac}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Hardware</td>
|
<td>Hardware</td>
|
||||||
<td>{_props.host.Hw}</td>
|
<td>{currentHost().Hw}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Date</td>
|
<td>Date</td>
|
||||||
<td>{_props.host.Date}</td>
|
<td>{currentHost().Date}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Known</td>
|
<td>Known</td>
|
||||||
|
|
@ -85,7 +75,7 @@ function HostCard(_props: any) {
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox"
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
checked={_props.host.Known == 1
|
checked={currentHost().Known == 1
|
||||||
? true
|
? true
|
||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
|
|
@ -95,13 +85,10 @@ function HostCard(_props: any) {
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Online</td>
|
<td>Online</td>
|
||||||
<td>{_props.host.Now == 1
|
<td>{currentHost().Now == 1
|
||||||
? <i class="bi bi-check-circle-fill" style="color:var(--bs-success);"></i>
|
? <i class="bi bi-check-circle-fill" style="color:var(--bs-success);"></i>
|
||||||
: <i class="bi bi-circle-fill" style="color:var(--bs-gray-500);"></i>
|
: <i class="bi bi-circle-fill" style="color:var(--bs-gray-500);"></i>
|
||||||
}
|
}</td>
|
||||||
|
|
||||||
<button type="button" onClick={handleWOL} class="btn btn-outline-success">Wake-on-LAN</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -111,4 +98,4 @@ function HostCard(_props: any) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HostCard
|
export default HostPageHost
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { createSignal, For } from "solid-js";
|
import { createSignal, For } from "solid-js";
|
||||||
import { apiPortScan } from "../../functions/api";
|
import { apiPortScan } from "../functions/api";
|
||||||
|
import { currentHost } from "../functions/exports";
|
||||||
|
|
||||||
function Ping(_props: any) {
|
|
||||||
|
function HostPagePing() {
|
||||||
|
|
||||||
let stop = false;
|
let stop = false;
|
||||||
|
|
||||||
|
|
@ -29,7 +31,7 @@ function Ping(_props: any) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
setCurPort(i.toString());
|
setCurPort(i.toString());
|
||||||
portOpened = await apiPortScan(_props.IP, i);
|
portOpened = await apiPortScan(currentHost().IP, i);
|
||||||
if (portOpened) {
|
if (portOpened) {
|
||||||
setFoundPorts([...foundPorts(), i]);
|
setFoundPorts([...foundPorts(), i]);
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +67,7 @@ function Ping(_props: any) {
|
||||||
}
|
}
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<For each={foundPorts()}>{(port) =>
|
<For each={foundPorts()}>{(port) =>
|
||||||
<a class="me-4" href={"http://" + _props.IP + ":" + port} target="_blank">{port}</a>
|
<a class="me-4" href={"http://" + currentHost().IP + ":" + port} target="_blank">{port}</a>
|
||||||
}</For>
|
}</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -73,4 +75,4 @@ function Ping(_props: any) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Ping
|
export default HostPagePing
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { For, onCleanup, onMount, Show } from "solid-js";
|
|
||||||
import { getHistoryForMac } from "../functions/history";
|
|
||||||
import { Host, show } from "../functions/exports";
|
|
||||||
import { createStore } from "solid-js/store";
|
|
||||||
|
|
||||||
function MacHistory(_props: any) {
|
|
||||||
|
|
||||||
const [hist, setHist] = createStore<Host[]>([]);
|
|
||||||
let interval: number;
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const newHistory = await getHistoryForMac(_props.mac, _props.date);
|
|
||||||
setHist(newHistory);
|
|
||||||
interval = setInterval(async () => {
|
|
||||||
// console.log("Upd Hist", new Date());
|
|
||||||
const newHistory = await getHistoryForMac(_props.mac, _props.date);
|
|
||||||
setHist(newHistory);
|
|
||||||
}, 60000); // 60000 ms = 1 minute
|
|
||||||
});
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
clearInterval(interval);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<For each={hist}>{(h, index) =>
|
|
||||||
<Show
|
|
||||||
when={index() < show()}
|
|
||||||
>
|
|
||||||
<i title={"Date:"+h.Date+"\nIface:"+h.Iface+"\nIP:"+h.IP+"\nKnown:"+h.Known}
|
|
||||||
class={h.Now === 0?"my-box-off":"my-box-on"}></i>
|
|
||||||
</Show>
|
|
||||||
}</For>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MacHistory
|
|
||||||
|
|
@ -7,7 +7,7 @@ function Search() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<input onInput={e => handleSearch(e.target.value)} class="form-control" placeholder="Search" style="max-width: 10em;" title="Search"></input>
|
<input onInput={e => handleSearch(e.target.value)} class="form-control" placeholder="Search" style="max-width: 10em;"></input>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
frontend/src/components/TableHead.tsx
Normal file
28
frontend/src/components/TableHead.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Host } from "../functions/exports";
|
||||||
|
import { sortByAnyField } from "../functions/sort";
|
||||||
|
|
||||||
|
function TableHead() {
|
||||||
|
|
||||||
|
const handleSort = (sortBy: keyof Host) => {
|
||||||
|
sortByAnyField(sortBy);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 3em;"></th>
|
||||||
|
<th>Name <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Name"]}></i></th>
|
||||||
|
<th>Iface <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Iface"]}></i></th>
|
||||||
|
<th>IP <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "IP"]}></i></th>
|
||||||
|
<th>MAC <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Mac"]}></i></th>
|
||||||
|
<th>Hardware <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Hw"]}></i></th>
|
||||||
|
<th>Date <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Date"]}></i></th>
|
||||||
|
<th>Known <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Known"]}></i></th>
|
||||||
|
<th>Online <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Now"]}></i></th>
|
||||||
|
<th style="width: 2em;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableHead
|
||||||
62
frontend/src/components/TableRow.tsx
Normal file
62
frontend/src/components/TableRow.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { createSignal, Show } from "solid-js";
|
||||||
|
import { editNames } from "../functions/exports";
|
||||||
|
import { apiEditHost } from "../functions/api";
|
||||||
|
|
||||||
|
function TableRow(_props: any) {
|
||||||
|
|
||||||
|
const [name, setName] = createSignal(_props.host.Name);
|
||||||
|
|
||||||
|
const link = "http://" + _props.host.IP;
|
||||||
|
const edit = "/host/" + _props.host.ID;
|
||||||
|
|
||||||
|
let now = <i class="bi bi-circle-fill" style="color:var(--bs-gray-500);"></i>;
|
||||||
|
if (_props.host.Now == 1) {
|
||||||
|
now = <i class="bi bi-check-circle-fill" style="color:var(--bs-success);"></i>;
|
||||||
|
};
|
||||||
|
|
||||||
|
let known = false;
|
||||||
|
if (_props.host.Known == 1) {
|
||||||
|
known = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInput = async (n: string) => {
|
||||||
|
// console.log('Edit:', n);
|
||||||
|
setName(n);
|
||||||
|
await apiEditHost(_props.host.ID, name(), '');
|
||||||
|
};
|
||||||
|
const handleToggle = async () => {
|
||||||
|
await apiEditHost(_props.host.ID, name(), 'toggle');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td class="opacity-50">{_props.index}.</td>
|
||||||
|
<td>
|
||||||
|
<Show
|
||||||
|
when={editNames()}
|
||||||
|
fallback={name()}
|
||||||
|
>
|
||||||
|
<input type="text" class="form-control" value={name()}
|
||||||
|
onInput={e => handleInput(e.target.value)}></input>
|
||||||
|
</Show>
|
||||||
|
</td>
|
||||||
|
<td>{_props.host.Iface}</td>
|
||||||
|
<td><a href={link} target="_blank">{_props.host.IP}</a></td>
|
||||||
|
<td>{_props.host.Mac}</td>
|
||||||
|
<td>{_props.host.Hw}</td>
|
||||||
|
<td>{_props.host.Date}</td>
|
||||||
|
<td>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" checked={known}
|
||||||
|
onClick={handleToggle}></input>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{now}</td>
|
||||||
|
<td>
|
||||||
|
<a href={edit}><i class="bi bi-pencil-square my-btn"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableRow
|
||||||
|
|
@ -1,37 +1,15 @@
|
||||||
export const apiPath = 'http://0.0.0.0:8840';
|
const api = 'http://0.0.0.0:8840';
|
||||||
|
|
||||||
export const apiGetAllHosts = async () => {
|
export const apiGetAllHosts = async () => {
|
||||||
const url = apiPath+'/api/all';
|
const url = api+'/api/all';
|
||||||
const hosts = await (await fetch(url)).json();
|
const hosts = await (await fetch(url)).json();
|
||||||
|
|
||||||
return hosts;
|
return hosts;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const apiGetConfig = async () => {
|
|
||||||
|
|
||||||
const url = apiPath+'/api/config';
|
|
||||||
const res = await (await fetch(url)).json();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const apiGetVersion = async () => {
|
|
||||||
|
|
||||||
const url = apiPath+'/api/version';
|
|
||||||
const res = await (await fetch(url)).json();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const apiTestNotify = async () => {
|
|
||||||
|
|
||||||
const url = apiPath+'/api/notify_test';
|
|
||||||
await fetch(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const apiEditHost = async (id:number, name:string, known:string) => {
|
export const apiEditHost = async (id:number, name:string, known:string) => {
|
||||||
|
|
||||||
const url = apiPath+'/api/edit/'+id+'/'+name+'/'+known;
|
const url = api+'/api/edit/'+id+'/'+name+'/'+known;
|
||||||
const res = await (await fetch(url)).json();
|
const res = await (await fetch(url)).json();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -39,7 +17,7 @@ export const apiEditHost = async (id:number, name:string, known:string) => {
|
||||||
|
|
||||||
export const apiGetHost = async (id:string) => {
|
export const apiGetHost = async (id:string) => {
|
||||||
|
|
||||||
const url = apiPath+'/api/host/'+id;
|
const url = api+'/api/host/'+id;
|
||||||
const res = await (await fetch(url)).json();
|
const res = await (await fetch(url)).json();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -47,7 +25,7 @@ export const apiGetHost = async (id:string) => {
|
||||||
|
|
||||||
export const apiDelHost = async (id:number) => {
|
export const apiDelHost = async (id:number) => {
|
||||||
|
|
||||||
const url = apiPath+'/api/host/del/'+id;
|
const url = api+'/api/host/del/'+id;
|
||||||
const res = await (await fetch(url)).json();
|
const res = await (await fetch(url)).json();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -55,29 +33,7 @@ export const apiDelHost = async (id:number) => {
|
||||||
|
|
||||||
export const apiPortScan = async (ip:string, port:number) => {
|
export const apiPortScan = async (ip:string, port:number) => {
|
||||||
|
|
||||||
const url = apiPath+'/api/port/'+ip+'/'+port;
|
const url = api+'/api/port/'+ip+'/'+port;
|
||||||
const res = await (await fetch(url)).json();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const apiGetHistory = async (mac:string) => {
|
|
||||||
const url = apiPath+'/api/history/'+mac+'/?num=210';
|
|
||||||
const hosts = await (await fetch(url)).json();
|
|
||||||
|
|
||||||
return hosts;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const apiGetHistoryByDate = async (mac:string, date: string) => {
|
|
||||||
const url = apiPath+'/api/history/'+mac+'/'+date;
|
|
||||||
const hosts = await (await fetch(url)).json();
|
|
||||||
|
|
||||||
return hosts;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const apiWOL = async (mac:string) => {
|
|
||||||
|
|
||||||
const url = apiPath+'/api/wol/'+mac;
|
|
||||||
const res = await (await fetch(url)).json();
|
const res = await (await fetch(url)).json();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
|
||||||
|
|
@ -12,24 +12,22 @@ export function runAtStart() {
|
||||||
}, 60000); // 60000 ms = 1 minute
|
}, 60000); // 60000 ms = 1 minute
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHosts() {
|
async function getHosts() {
|
||||||
const hosts = await apiGetAllHosts();
|
const hosts = await apiGetAllHosts();
|
||||||
|
setAllHosts(hosts);
|
||||||
|
setBkpHosts(hosts);
|
||||||
|
|
||||||
if (hosts !== null && hosts.length > 0) {
|
sortAtStart();
|
||||||
setAllHosts(hosts);
|
filterAtStart();
|
||||||
setBkpHosts(hosts);
|
listIfaces();
|
||||||
|
// console.log("UPD");
|
||||||
listIfaces();
|
|
||||||
sortAtStart();
|
|
||||||
filterAtStart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listIfaces() {
|
function listIfaces() {
|
||||||
|
|
||||||
let ifaces:string[] = [];
|
let ifaces:string[] = [];
|
||||||
|
|
||||||
for (let host of allHosts) {
|
for (let host of allHosts()) {
|
||||||
if (!ifaces.includes(host.Iface)) {
|
if (!ifaces.includes(host.Iface)) {
|
||||||
ifaces.push(host.Iface);
|
ifaces.push(host.Iface);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { createSignal } from "solid-js";
|
import { createSignal } from "solid-js";
|
||||||
import { createStore } from "solid-js/store";
|
|
||||||
|
|
||||||
export interface Host {
|
export interface Host {
|
||||||
ID: number;
|
ID: number;
|
||||||
|
|
@ -14,33 +13,6 @@ export interface Host {
|
||||||
Now: number;
|
Now: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Conf {
|
|
||||||
Host: string;
|
|
||||||
Port: string;
|
|
||||||
Theme: string;
|
|
||||||
Color: string;
|
|
||||||
DirPath: string;
|
|
||||||
Timeout: number;
|
|
||||||
NodePath: string;
|
|
||||||
LogLevel: string;
|
|
||||||
Ifaces: string;
|
|
||||||
ArpArgs: string;
|
|
||||||
ArpStrs: string[];
|
|
||||||
TrimHist: number;
|
|
||||||
ShoutURL: string;
|
|
||||||
UseDB: string;
|
|
||||||
PGConnect: string;
|
|
||||||
// InfluxDB
|
|
||||||
InfluxEnable: boolean;
|
|
||||||
InfluxAddr: string;
|
|
||||||
InfluxToken: string;
|
|
||||||
InfluxOrg: string;
|
|
||||||
InfluxBucket: string;
|
|
||||||
InfluxSkipTLS: boolean;
|
|
||||||
// Prometheus
|
|
||||||
PrometheusEnable: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const emptyHost:Host = {
|
export const emptyHost:Host = {
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Name: "",
|
Name: "",
|
||||||
|
|
@ -54,42 +26,19 @@ export const emptyHost:Host = {
|
||||||
Now: 0,
|
Now: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyConf:Conf = {
|
export interface Conf {
|
||||||
Host: "",
|
Color: string;
|
||||||
Port: "",
|
Timeout: number;
|
||||||
Theme: "",
|
|
||||||
Color: "",
|
|
||||||
DirPath: "",
|
|
||||||
Timeout: 120,
|
|
||||||
NodePath: "",
|
|
||||||
LogLevel: "",
|
|
||||||
Ifaces: "",
|
|
||||||
ArpArgs: "",
|
|
||||||
ArpStrs: [],
|
|
||||||
TrimHist: 48,
|
|
||||||
ShoutURL: "",
|
|
||||||
UseDB: "",
|
|
||||||
PGConnect: "",
|
|
||||||
InfluxEnable: false,
|
|
||||||
InfluxAddr: "",
|
|
||||||
InfluxToken: "",
|
|
||||||
InfluxOrg: "",
|
|
||||||
InfluxBucket: "",
|
|
||||||
InfluxSkipTLS: false,
|
|
||||||
PrometheusEnable: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const [allHosts, setAllHosts] = createStore<Host[]>([]);
|
|
||||||
|
export const [allHosts, setAllHosts] = createSignal<Host[]>([]);
|
||||||
export const [bkpHosts, setBkpHosts] = createSignal<Host[]>([]);
|
export const [bkpHosts, setBkpHosts] = createSignal<Host[]>([]);
|
||||||
|
|
||||||
export const [ifaces, setIfaces] = createSignal<string[]>([]);
|
export const [ifaces, setIfaces] = createSignal<string[]>([]);
|
||||||
|
|
||||||
export const [appConfig, setAppConfig] = createSignal<Conf>(emptyConf);
|
export const [appConfig, setAppConfig] = createSignal<Conf>();
|
||||||
|
|
||||||
export const [editNames, setEditNames] = createSignal(false);
|
export const [editNames, setEditNames] = createSignal(false);
|
||||||
|
|
||||||
export const [show, setShow] = createSignal<number>(200);
|
export const [currentHost, setCurrentHost] = createSignal<Host>(emptyHost);
|
||||||
|
|
||||||
export const [histUpdOnFilter, setHistUpdOnFilter] = createSignal(false);
|
|
||||||
|
|
||||||
export const [selectedIDs, setSelectedIDs] = createSignal<number[]>([]);
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function filterAtStart() {
|
||||||
|
|
||||||
export function filterFunc(field: keyof Host, value: any) {
|
export function filterFunc(field: keyof Host, value: any) {
|
||||||
|
|
||||||
let addrsArray = allHosts;
|
let addrsArray = allHosts();
|
||||||
|
|
||||||
if (oldFilter == field) {
|
if (oldFilter == field) {
|
||||||
addrsArray = bkpHosts();
|
addrsArray = bkpHosts();
|
||||||
|
|
@ -35,5 +35,6 @@ export function filterFunc(field: keyof Host, value: any) {
|
||||||
addrsArray = bkpHosts();
|
addrsArray = bkpHosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAllHosts([]);
|
||||||
setAllHosts(addrsArray);
|
setAllHosts(addrsArray);
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { apiGetHistory, apiGetHistoryByDate } from "./api";
|
|
||||||
import { Host } from "./exports";
|
|
||||||
|
|
||||||
export async function getHistoryForMac(mac: string, date: string) {
|
|
||||||
let h:Host[] = [];
|
|
||||||
if (date === "") {
|
|
||||||
h = await apiGetHistory(mac);
|
|
||||||
} else {
|
|
||||||
h = await apiGetHistoryByDate(mac, date);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (h != null) {
|
|
||||||
h.sort((a:Host, b:Host) => (a.Date < b.Date ? 1 : -1));
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
@ -7,15 +7,18 @@ export function searchFunc(s: string) {
|
||||||
const sl = s.toLowerCase();
|
const sl = s.toLowerCase();
|
||||||
let newArray:Host[] = [];
|
let newArray:Host[] = [];
|
||||||
|
|
||||||
for (let item of allHosts) {
|
for (let item of allHosts()) {
|
||||||
|
|
||||||
if (searchItem(item, sl)) {
|
if (searchItem(item, sl)) {
|
||||||
newArray.push(item);
|
newArray.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAllHosts([]);
|
||||||
setAllHosts(newArray);
|
setAllHosts(newArray);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
setAllHosts([]);
|
||||||
setAllHosts(bkpHosts());
|
setAllHosts(bkpHosts());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { bkpHosts, Host, setAllHosts } from "./exports";
|
import { allHosts, Host, setAllHosts } from "./exports";
|
||||||
|
|
||||||
let down = false;
|
let down = false;
|
||||||
let oldField = '';
|
let oldField = '';
|
||||||
|
|
@ -23,14 +23,16 @@ export function sortByAnyField(field: keyof Host) {
|
||||||
|
|
||||||
localStorage.setItem("sortDown", down.toString());
|
localStorage.setItem("sortDown", down.toString());
|
||||||
localStorage.setItem("sortField", field);
|
localStorage.setItem("sortField", field);
|
||||||
|
// checkNotEmpty(addrsArray);
|
||||||
|
|
||||||
let someArray = bkpHosts();
|
let someArray = allHosts();
|
||||||
if (field == 'IP') {
|
if (field == 'IP') {
|
||||||
someArray.sort((a, b) => sortIP(a, b, down));
|
someArray.sort((a, b) => sortIP(a, b, down));
|
||||||
} else {
|
} else {
|
||||||
someArray.sort((a, b) => byField(a, b, field, down));
|
someArray.sort((a, b) => byField(a, b, field, down));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAllHosts([]);
|
||||||
setAllHosts(someArray);
|
setAllHosts(someArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,24 @@
|
||||||
import { For, onMount } from "solid-js";
|
import { For } from "solid-js";
|
||||||
|
|
||||||
import { allHosts } from "../functions/exports";
|
import { allHosts } from "../functions/exports";
|
||||||
|
|
||||||
import TableRow from "../components/Body/TableRow";
|
import TableRow from "../components/TableRow";
|
||||||
import TableHead from "../components/Body/TableHead";
|
import TableHead from "../components/TableHead";
|
||||||
import CardHead from "../components/Body/CardHead";
|
import BodyCardHead from "../components/BodyCardHead";
|
||||||
import { getHosts } from "../functions/atstart";
|
|
||||||
|
|
||||||
function Body() {
|
function Body() {
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
getHosts();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="card border-primary">
|
<div class="card border-primary">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<CardHead></CardHead>
|
<BodyCardHead></BodyCardHead>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive">
|
<div class="card-body table-responsive">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<TableHead></TableHead>
|
<TableHead></TableHead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<For each={allHosts}>{(host, index) =>
|
<For each={allHosts()}>{(host, index) =>
|
||||||
<TableRow host={host} index={index() + 1}></TableRow>
|
<TableRow host={host} index={index}></TableRow>
|
||||||
}</For>
|
}</For>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import About from "../components/Config/About"
|
|
||||||
import Basic from "../components/Config/Basic"
|
|
||||||
import Donate from "../components/Config/Donate"
|
|
||||||
import Influx from "../components/Config/Influx"
|
|
||||||
import Prometheus from "../components/Config/Prometheus"
|
|
||||||
import Scan from "../components/Config/Scan"
|
|
||||||
|
|
||||||
function Config() {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md">
|
|
||||||
|
|
||||||
<Basic></Basic>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<Donate></Donate>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4 mb-4">
|
|
||||||
<Scan></Scan>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md">
|
|
||||||
|
|
||||||
<Influx></Influx>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<Prometheus></Prometheus>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
|
||||||
<About></About>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Config
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { createEffect, For, Show } from "solid-js"
|
|
||||||
import Filter from "../components/Filter"
|
|
||||||
import { allHosts, histUpdOnFilter, Host, setHistUpdOnFilter, setShow, show } from "../functions/exports"
|
|
||||||
import MacHistory from "../components/MacHistory"
|
|
||||||
import HistShow from "../components/HistShow"
|
|
||||||
|
|
||||||
function History() {
|
|
||||||
|
|
||||||
let hosts: Host[] = [];
|
|
||||||
hosts.push(...allHosts);
|
|
||||||
|
|
||||||
const showStr = localStorage.getItem("histShow") as string;
|
|
||||||
setShow(+showStr);
|
|
||||||
(show() === 0 || isNaN(show())) ? setShow(200) : '';
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (histUpdOnFilter()) {
|
|
||||||
hosts = [];
|
|
||||||
hosts.push(...allHosts);
|
|
||||||
console.log("Upd on Filter");
|
|
||||||
setHistUpdOnFilter(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-header d-flex justify-content-between">
|
|
||||||
<Filter></Filter>
|
|
||||||
<HistShow name="histShow"></HistShow>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<tbody>
|
|
||||||
<Show
|
|
||||||
when={!histUpdOnFilter()}
|
|
||||||
>
|
|
||||||
<For each={hosts}>{(host, index) =>
|
|
||||||
<tr>
|
|
||||||
<td class="opacity-50" style="width: 2em;">{index()+1}.</td>
|
|
||||||
<td>
|
|
||||||
<a href={"/host/"+host.ID}>{host.Name}</a><br></br>
|
|
||||||
<a href={"http://"+host.IP}>{host.IP}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<MacHistory mac={host.Mac} date=""></MacHistory>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}</For>
|
|
||||||
</Show>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default History
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
import { useParams } from "@solidjs/router";
|
import { useParams } from "@solidjs/router";
|
||||||
import { createSignal, onMount } from "solid-js";
|
|
||||||
|
|
||||||
import { apiGetHost } from "../functions/api";
|
import { apiGetHost } from "../functions/api";
|
||||||
|
|
||||||
import HostCard from "../components/HostPage/HostCard";
|
import HostPageHost from "../components/HostPageHost";
|
||||||
import Ping from "../components/HostPage/Ping";
|
import { setCurrentHost } from "../functions/exports";
|
||||||
import HistCard from "../components/HostPage/HistCard";
|
import { onMount } from "solid-js";
|
||||||
import { emptyHost, Host } from "../functions/exports";
|
import HostPagePing from "../components/HostPagePing";
|
||||||
|
|
||||||
function HostPage() {
|
function HostPage() {
|
||||||
|
|
||||||
const [currentHost, setCurrentHost] = createSignal<Host>(emptyHost);
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const host = await apiGetHost(params.id);
|
const host = await apiGetHost(params.id);
|
||||||
|
|
@ -20,21 +16,14 @@ function HostPage() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<HostCard host={currentHost()}></HostCard>
|
<HostPageHost></HostPageHost>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<Ping IP={currentHost().IP}></Ping>
|
<HostPagePing></HostPagePing>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-md">
|
|
||||||
<HistCard mac={currentHost().Mac}></HistCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,4 @@ import solid from 'vite-plugin-solid'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [solid()],
|
plugins: [solid()],
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
entryFileNames: `assets/[name].js`,
|
|
||||||
chunkFileNames: `assets/[name].js`,
|
|
||||||
assetFileNames: `assets/[name].[ext]`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
69
go.mod
Normal file
69
go.mod
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
module github.com/aceberg/WatchYourLAN
|
||||||
|
|
||||||
|
go 1.23.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
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/spf13/viper v1.19.0
|
||||||
|
modernc.org/sqlite v1.36.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
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
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
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
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/oapi-codegen/runtime v1.0.0 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
modernc.org/libc v1.61.13 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.8.2 // indirect
|
||||||
|
)
|
||||||
212
go.sum
Normal file
212
go.sum
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
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=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
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=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
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=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
|
||||||
|
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=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
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=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
|
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=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||||
|
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||||
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||||
|
golang.org/x/mod v0.19.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/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||||
|
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||||
|
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
|
||||||
|
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
|
||||||
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
|
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
|
||||||
|
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
|
||||||
|
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
||||||
|
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
|
||||||
|
modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
@ -9,7 +10,8 @@ import (
|
||||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
"github.com/aceberg/WatchYourLAN/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func read(path string) (config models.Conf) {
|
// Get - get app config
|
||||||
|
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")
|
||||||
|
|
@ -22,6 +24,7 @@ func read(path string) (config models.Conf) {
|
||||||
viper.SetDefault("IFACES", "")
|
viper.SetDefault("IFACES", "")
|
||||||
viper.SetDefault("TIMEOUT", 120)
|
viper.SetDefault("TIMEOUT", 120)
|
||||||
viper.SetDefault("TRIM_HIST", 48)
|
viper.SetDefault("TRIM_HIST", 48)
|
||||||
|
viper.SetDefault("HIST_IN_DB", false)
|
||||||
viper.SetDefault("SHOUTRRR_URL", "")
|
viper.SetDefault("SHOUTRRR_URL", "")
|
||||||
|
|
||||||
viper.SetDefault("USE_DB", "sqlite")
|
viper.SetDefault("USE_DB", "sqlite")
|
||||||
|
|
@ -29,8 +32,6 @@ func read(path string) (config models.Conf) {
|
||||||
|
|
||||||
viper.SetDefault("INFLUX_ENABLE", false)
|
viper.SetDefault("INFLUX_ENABLE", false)
|
||||||
|
|
||||||
viper.SetDefault("PROMETHEUS_ENABLE", false)
|
|
||||||
|
|
||||||
viper.SetConfigFile(path)
|
viper.SetConfigFile(path)
|
||||||
viper.SetConfigType("yaml")
|
viper.SetConfigType("yaml")
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
|
|
@ -49,6 +50,7 @@ func read(path string) (config models.Conf) {
|
||||||
config.Ifaces = viper.Get("IFACES").(string)
|
config.Ifaces = viper.Get("IFACES").(string)
|
||||||
config.Timeout = viper.GetInt("TIMEOUT")
|
config.Timeout = viper.GetInt("TIMEOUT")
|
||||||
config.TrimHist = viper.GetInt("TRIM_HIST")
|
config.TrimHist = viper.GetInt("TRIM_HIST")
|
||||||
|
config.HistInDB = viper.GetBool("HIST_IN_DB")
|
||||||
config.ShoutURL = viper.Get("SHOUTRRR_URL").(string)
|
config.ShoutURL = viper.Get("SHOUTRRR_URL").(string)
|
||||||
|
|
||||||
config.UseDB = viper.Get("USE_DB").(string)
|
config.UseDB = viper.Get("USE_DB").(string)
|
||||||
|
|
@ -61,10 +63,8 @@ func read(path string) (config models.Conf) {
|
||||||
config.InfluxOrg, _ = viper.Get("INFLUX_ORG").(string)
|
config.InfluxOrg, _ = viper.Get("INFLUX_ORG").(string)
|
||||||
config.InfluxBucket, _ = viper.Get("INFLUX_BUCKET").(string)
|
config.InfluxBucket, _ = viper.Get("INFLUX_BUCKET").(string)
|
||||||
|
|
||||||
config.PrometheusEnable = viper.GetBool("PROMETHEUS_ENABLE")
|
|
||||||
|
|
||||||
joined := viper.Get("ARP_STRS_JOINED").(string)
|
joined := viper.Get("ARP_STRS_JOINED").(string)
|
||||||
// slog.Info("ARP_STRS_JOINED: " + joined)
|
slog.Info("ARP_STRS_JOINED: " + joined)
|
||||||
|
|
||||||
if joined != "" {
|
if joined != "" {
|
||||||
config.ArpStrs = strings.Split(joined, ",")
|
config.ArpStrs = strings.Split(joined, ",")
|
||||||
|
|
@ -72,3 +72,38 @@ func read(path string) (config models.Conf) {
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write - write config to file
|
||||||
|
func Write(config models.Conf) {
|
||||||
|
|
||||||
|
viper.SetConfigFile(config.ConfPath)
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
|
||||||
|
viper.Set("HOST", config.Host)
|
||||||
|
viper.Set("PORT", config.Port)
|
||||||
|
viper.Set("THEME", config.Theme)
|
||||||
|
viper.Set("COLOR", config.Color)
|
||||||
|
viper.Set("NODEPATH", config.NodePath)
|
||||||
|
viper.Set("LOG_LEVEL", config.LogLevel)
|
||||||
|
viper.Set("ARP_ARGS", config.ArpArgs)
|
||||||
|
viper.Set("ARP_STRS", config.ArpStrs)
|
||||||
|
viper.Set("ARP_STRS_JOINED", "") // Can be set only with ENV
|
||||||
|
viper.Set("IFACES", config.Ifaces)
|
||||||
|
viper.Set("TIMEOUT", config.Timeout)
|
||||||
|
viper.Set("TRIM_HIST", config.TrimHist)
|
||||||
|
viper.Set("HIST_IN_DB", config.HistInDB)
|
||||||
|
viper.Set("SHOUTRRR_URL", config.ShoutURL)
|
||||||
|
|
||||||
|
viper.Set("USE_DB", config.UseDB)
|
||||||
|
viper.Set("PG_CONNECT", config.PGConnect)
|
||||||
|
|
||||||
|
viper.Set("influx_enable", config.InfluxEnable)
|
||||||
|
viper.Set("influx_skip_tls", config.InfluxSkipTLS)
|
||||||
|
viper.Set("influx_addr", config.InfluxAddr)
|
||||||
|
viper.Set("influx_token", config.InfluxToken)
|
||||||
|
viper.Set("influx_org", config.InfluxOrg)
|
||||||
|
viper.Set("influx_bucket", config.InfluxBucket)
|
||||||
|
|
||||||
|
err := viper.WriteConfig()
|
||||||
|
check.IfError(err)
|
||||||
|
}
|
||||||
42
internal/db/choose_db.go
Normal file
42
internal/db/choose_db.go
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/aceberg/WatchYourLAN/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data to connect to DB
|
||||||
|
type Data struct {
|
||||||
|
Use string
|
||||||
|
Path string
|
||||||
|
SQLitePath string
|
||||||
|
PGConnect string
|
||||||
|
PrimaryKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentDB Data
|
||||||
|
|
||||||
|
func setCurrentDB() {
|
||||||
|
|
||||||
|
if currentDB.Use == "postgres" && currentDB.PGConnect != "" {
|
||||||
|
currentDB.Path = currentDB.PGConnect
|
||||||
|
currentDB.PrimaryKey = "BIGSERIAL PRIMARY KEY"
|
||||||
|
} else {
|
||||||
|
currentDB.Use = "sqlite"
|
||||||
|
currentDB.Path = currentDB.SQLitePath
|
||||||
|
currentDB.PrimaryKey = "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Using DB", "type", currentDB.Use)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCurrent - set paths and which DB to use
|
||||||
|
func SetCurrent(config models.Conf) {
|
||||||
|
|
||||||
|
currentDB.Use = config.UseDB
|
||||||
|
currentDB.SQLitePath = config.DBPath
|
||||||
|
currentDB.PGConnect = config.PGConnect
|
||||||
|
|
||||||
|
setCurrentDB()
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
92
internal/db/edit.go
Normal file
92
internal/db/edit.go
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/aceberg/WatchYourLAN/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create - create DB if not exists
|
||||||
|
func Create() {
|
||||||
|
|
||||||
|
sqlStatement := `CREATE TABLE IF NOT EXISTS "now" (
|
||||||
|
"ID" ` + currentDB.PrimaryKey + `,
|
||||||
|
"NAME" TEXT NOT NULL,
|
||||||
|
"DNS" TEXT NOT NULL,
|
||||||
|
"IFACE" TEXT,
|
||||||
|
"IP" TEXT,
|
||||||
|
"MAC" TEXT,
|
||||||
|
"HW" TEXT,
|
||||||
|
"DATE" TEXT,
|
||||||
|
"KNOWN" INTEGER DEFAULT 0,
|
||||||
|
"NOW" INTEGER DEFAULT 0
|
||||||
|
);`
|
||||||
|
dbExec(sqlStatement)
|
||||||
|
|
||||||
|
sqlStatement = `CREATE TABLE IF NOT EXISTS "history" (
|
||||||
|
"ID" ` + currentDB.PrimaryKey + `,
|
||||||
|
"NAME" TEXT NOT NULL,
|
||||||
|
"DNS" TEXT NOT NULL,
|
||||||
|
"IFACE" TEXT,
|
||||||
|
"IP" TEXT,
|
||||||
|
"MAC" TEXT,
|
||||||
|
"HW" TEXT,
|
||||||
|
"DATE" TEXT,
|
||||||
|
"KNOWN" INTEGER DEFAULT 0,
|
||||||
|
"NOW" INTEGER DEFAULT 0
|
||||||
|
);`
|
||||||
|
dbExec(sqlStatement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert - insert host into table
|
||||||
|
func Insert(table string, oneHost models.Host) {
|
||||||
|
oneHost.Name = quoteStr(oneHost.Name)
|
||||||
|
oneHost.Hw = quoteStr(oneHost.Hw)
|
||||||
|
sqlStatement := `INSERT INTO %s ("NAME", "DNS", "IFACE", "IP", "MAC", "HW", "DATE", "KNOWN", "NOW") VALUES ('%s','%s','%s','%s','%s','%s','%s','%d','%d');`
|
||||||
|
sqlStatement = fmt.Sprintf(sqlStatement, table, oneHost.Name, oneHost.DNS, oneHost.Iface, oneHost.IP, oneHost.Mac, oneHost.Hw, oneHost.Date, oneHost.Known, oneHost.Now)
|
||||||
|
|
||||||
|
dbExec(sqlStatement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update - update host
|
||||||
|
func Update(table string, oneHost models.Host) {
|
||||||
|
oneHost.Name = quoteStr(oneHost.Name)
|
||||||
|
oneHost.Hw = quoteStr(oneHost.Hw)
|
||||||
|
sqlStatement := `UPDATE %s set
|
||||||
|
"NAME" = '%s', "DNS" = '%s', "IFACE" = '%s', "IP" = '%s', "MAC" = '%s', "HW" = '%s', "DATE" = '%s', "KNOWN" = '%d', "NOW" = '%d'
|
||||||
|
WHERE "ID" = '%d';`
|
||||||
|
sqlStatement = fmt.Sprintf(sqlStatement, table, oneHost.Name, oneHost.DNS, oneHost.Iface, oneHost.IP, oneHost.Mac, oneHost.Hw, oneHost.Date, oneHost.Known, oneHost.Now, oneHost.ID)
|
||||||
|
|
||||||
|
dbExec(sqlStatement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete - delete host from DB
|
||||||
|
func Delete(table string, id int) {
|
||||||
|
sqlStatement := `DELETE FROM %s WHERE "ID"='%d';`
|
||||||
|
sqlStatement = fmt.Sprintf(sqlStatement, table, id)
|
||||||
|
dbExec(sqlStatement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteList - delete a list of hosts from History
|
||||||
|
func DeleteList(ids []int) {
|
||||||
|
if len(ids) > 0 {
|
||||||
|
idString := ""
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
idString = idString + fmt.Sprintf("%d, ", id)
|
||||||
|
}
|
||||||
|
idString = idString[:len(idString)-2]
|
||||||
|
|
||||||
|
sqlStatement := `DELETE FROM history WHERE "ID" IN (%s);`
|
||||||
|
sqlStatement = fmt.Sprintf(sqlStatement, idString)
|
||||||
|
|
||||||
|
dbExec(sqlStatement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear - delete all hosts from table
|
||||||
|
func Clear(table string) {
|
||||||
|
sqlStatement := `DELETE FROM %s;`
|
||||||
|
sqlStatement = fmt.Sprintf(sqlStatement, table)
|
||||||
|
dbExec(sqlStatement)
|
||||||
|
}
|
||||||
7
internal/db/quote_str.go
Normal file
7
internal/db/quote_str.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func quoteStr(str string) string {
|
||||||
|
return strings.ReplaceAll(str, "'", "''")
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue