From 24ee0acb456f0a88db8527203444b9c02536bdb1 Mon Sep 17 00:00:00 2001 From: aceberg <1502200+aceberg@users.noreply.github.com> Date: Sun, 7 Sep 2025 03:33:42 +0700 Subject: [PATCH] Add host from API, trigger rescan --- CHANGELOG.md | 4 +- backend/docs/docs.go | 67 +++++++++++++++++++ backend/docs/swagger.json | 67 +++++++++++++++++++ backend/docs/swagger.yaml | 46 +++++++++++++ backend/internal/api/api-hosts.go | 37 ++++++++++ backend/internal/api/api-system.go | 13 ++++ backend/internal/api/routes.go | 2 + backend/internal/gdb/select.go | 4 +- backend/internal/notify/shout.go | 2 +- backend/internal/web/public/assets/Config.js | 2 +- .../internal/web/public/assets/HostPage.js | 2 +- .../internal/web/public/assets/MacHistory.js | 4 +- backend/internal/web/public/assets/index.js | 2 +- frontend/src/components/Body/CardHead.tsx | 2 +- frontend/src/components/Body/TableHead.tsx | 2 +- frontend/src/components/Body/TableRow.tsx | 4 +- frontend/src/components/Config/About.tsx | 2 +- frontend/src/components/Config/Scan.tsx | 2 +- frontend/src/components/HostPage/HostCard.tsx | 8 +-- frontend/src/pages/Body.tsx | 7 +- 20 files changed, 255 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e29f20..cafaea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,10 @@ All notable changes to this project will be documented in this file. ## [v2.1.4] - 2025-09 ### Added - Swagger API docs +- Add host from API - Delete selected hosts - Wake-on-LAN [#135](https://github.com/aceberg/WatchYourLAN/issues/135), [#196](https://github.com/aceberg/WatchYourLAN/issues/196) -### Fixed -- Names not fully saved while editing with GUI - ## [v2.1.3] - 2025-07-26 ### Fixed - Memory leak bug [#149](https://github.com/aceberg/WatchYourLAN/issues/149) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 3ae7880..a89c311 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -207,6 +207,53 @@ const docTemplate = `{ } } }, + "/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", @@ -321,6 +368,26 @@ const docTemplate = `{ } } }, + "/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", diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 7f85eed..21f36b4 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -200,6 +200,53 @@ } } }, + "/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", @@ -314,6 +361,26 @@ } } }, + "/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", diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 5fe97a6..e24c518 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -254,6 +254,39 @@ paths: 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 @@ -310,6 +343,19 @@ paths: 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 diff --git a/backend/internal/api/api-hosts.go b/backend/internal/api/api-hosts.go index 28a1e36..e692439 100644 --- a/backend/internal/api/api-hosts.go +++ b/backend/internal/api/api-hosts.go @@ -8,6 +8,7 @@ import ( "github.com/aceberg/WatchYourLAN/internal/check" "github.com/aceberg/WatchYourLAN/internal/gdb" + "github.com/aceberg/WatchYourLAN/internal/models" ) // getAllHosts godoc @@ -53,6 +54,42 @@ func delHost(c *gin.Context) { 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 diff --git a/backend/internal/api/api-system.go b/backend/internal/api/api-system.go index 581bb8e..780201a 100644 --- a/backend/internal/api/api-system.go +++ b/backend/internal/api/api-system.go @@ -9,6 +9,7 @@ import ( "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 @@ -22,6 +23,18 @@ 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 diff --git a/backend/internal/api/routes.go b/backend/internal/api/routes.go index 6baede5..f27223a 100644 --- a/backend/internal/api/routes.go +++ b/backend/internal/api/routes.go @@ -16,11 +16,13 @@ func Routes(router *gin.Engine) { 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 diff --git a/backend/internal/gdb/select.go b/backend/internal/gdb/select.go index a4885b1..352fdb6 100644 --- a/backend/internal/gdb/select.go +++ b/backend/internal/gdb/select.go @@ -24,9 +24,9 @@ func SelectByID(id int) (host models.Host) { } // SelectByMAC - get all hosts by MAC -func SelectByMAC(mac string) (hosts []models.Host) { +func SelectByMAC(table, mac string) (hosts []models.Host) { - tab := db.Table("history") + tab := db.Table(table) tab.Where("\"MAC\" = ?", mac).Find(&hosts) return hosts diff --git a/backend/internal/notify/shout.go b/backend/internal/notify/shout.go index 1545028..a464a72 100644 --- a/backend/internal/notify/shout.go +++ b/backend/internal/notify/shout.go @@ -17,7 +17,6 @@ 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) } @@ -25,6 +24,7 @@ func Unknown(host models.Host) { func Test() { msg := "test notification" + slog.Info("Sending " + msg) shout(msg) } diff --git a/backend/internal/web/public/assets/Config.js b/backend/internal/web/public/assets/Config.js index 44a4304..894ad64 100644 --- a/backend/internal/web/public/assets/Config.js +++ b/backend/internal/web/public/assets/Config.js @@ -1 +1 @@ -import{c as J,o as K,a as X,t,i as r,b,s as H,d as W,e as c,S as V,f as e,F as M,g as Z,h as tt,j as Q}from"./index.js";var et=t('
About ()
Swagger API docs/swagger/index.html
Local node-bootstrap URLlocal themes and fonts (optional). If empty, the app will pull everything from cdn
Shoutrrr URLprovides notifications to Discord, Email, Gotify, Telegram and other services. Link to documentation
Interfacesone or more, space separated
Timeout (seconds)time between scans
Args for arp-scanpass your own arguments to arp-scan. Enable debug log level to see resulting command. (Example: -r 1). See docs for more
Arp Stringscan setup scans for vlans, docker0 and etcetera. See docs for more
Trim Historyremove history after (hours)
PG Connect URLaddress to connect to PostgreSQL DB. (Example: postgres://username:password@192.168.0.1:5432/dbname?sslmode=disable). Full list of URL parameters here');function rt(){const[i,d]=J(""),[s,a]=J("");return K(async()=>{const l=await X();d(l),a("https://github.com/aceberg/WatchYourLAN/releases/tag/"+l)}),(()=>{var l=et(),n=l.firstChild,o=n.firstChild,p=o.nextSibling;return r(p,i),b(()=>H(p,"href",s())),l})()}var lt=t("