Merge 986e952ac4 into ca9f644bcc
This commit is contained in:
commit
b3b49cb5fc
19 changed files with 89 additions and 43 deletions
|
|
@ -90,6 +90,7 @@ Configuration can be done through config file, GUI or environment variables. Var
|
||||||
| TZ | Set your timezone for correct time | |
|
| TZ | Set your timezone for correct time | |
|
||||||
| HOST | Listen address | 0.0.0.0 |
|
| HOST | Listen address | 0.0.0.0 |
|
||||||
| PORT | Port for web GUI | 8840 |
|
| PORT | Port for web GUI | 8840 |
|
||||||
|
| BASE_PATH | Optional web path prefix for UI and API (no trailing slash) | |
|
||||||
| 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 | |
|
||||||
|
|
@ -138,6 +139,7 @@ Config file name is `config_v2.yaml`. Example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
arp_args: ""
|
arp_args: ""
|
||||||
|
base_path: ""
|
||||||
color: dark
|
color: dark
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
ifaces: enp4s0
|
ifaces: enp4s0
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
|
@ -20,6 +21,9 @@ func saveConfigHandler(c *gin.Context) {
|
||||||
conf.AppConfig.NodePath = c.PostForm("node")
|
conf.AppConfig.NodePath = c.PostForm("node")
|
||||||
conf.AppConfig.ShoutURL = c.PostForm("shout")
|
conf.AppConfig.ShoutURL = c.PostForm("shout")
|
||||||
|
|
||||||
|
basePath := c.PostForm("basepath")
|
||||||
|
conf.AppConfig.BasePath = strings.TrimRight(basePath, "/")
|
||||||
|
|
||||||
conf.Write(conf.AppConfig)
|
conf.Write(conf.AppConfig)
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
c.Redirect(http.StatusFound, c.Request.Referer())
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Routes - start API routes
|
// Routes - start API routes
|
||||||
func Routes(router *gin.Engine) {
|
func Routes(router gin.IRouter) {
|
||||||
|
|
||||||
r0 := router.Group("/api")
|
r0 := router.Group("/api")
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ func read(path string) (config models.Conf) {
|
||||||
|
|
||||||
viper.SetDefault("HOST", "0.0.0.0")
|
viper.SetDefault("HOST", "0.0.0.0")
|
||||||
viper.SetDefault("PORT", "8840")
|
viper.SetDefault("PORT", "8840")
|
||||||
|
viper.SetDefault("BASE_PATH", "")
|
||||||
viper.SetDefault("THEME", "sand")
|
viper.SetDefault("THEME", "sand")
|
||||||
viper.SetDefault("COLOR", "dark")
|
viper.SetDefault("COLOR", "dark")
|
||||||
viper.SetDefault("NODEPATH", "")
|
viper.SetDefault("NODEPATH", "")
|
||||||
|
|
@ -40,6 +41,7 @@ func read(path string) (config models.Conf) {
|
||||||
|
|
||||||
config.Host = viper.Get("HOST").(string)
|
config.Host = viper.Get("HOST").(string)
|
||||||
config.Port = viper.Get("PORT").(string)
|
config.Port = viper.Get("PORT").(string)
|
||||||
|
config.BasePath = strings.Trim(viper.GetString("BASE_PATH"), "/")
|
||||||
config.Theme = viper.Get("THEME").(string)
|
config.Theme = viper.Get("THEME").(string)
|
||||||
config.Color = viper.Get("COLOR").(string)
|
config.Color = viper.Get("COLOR").(string)
|
||||||
config.NodePath = viper.Get("NODEPATH").(string)
|
config.NodePath = viper.Get("NODEPATH").(string)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ func Write(config models.Conf) {
|
||||||
|
|
||||||
viper.Set("HOST", config.Host)
|
viper.Set("HOST", config.Host)
|
||||||
viper.Set("PORT", config.Port)
|
viper.Set("PORT", config.Port)
|
||||||
|
viper.Set("BASE_PATH", "") // Can be set only with ENV
|
||||||
viper.Set("THEME", config.Theme)
|
viper.Set("THEME", config.Theme)
|
||||||
viper.Set("COLOR", config.Color)
|
viper.Set("COLOR", config.Color)
|
||||||
viper.Set("NODEPATH", config.NodePath)
|
viper.Set("NODEPATH", config.NodePath)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ package models
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
|
BasePath string
|
||||||
Theme string
|
Theme string
|
||||||
Color string
|
Color string
|
||||||
DirPath string
|
DirPath string
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,19 @@ package web
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/aceberg/WatchYourLAN/internal/conf"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func indexHandler(c *gin.Context) {
|
func indexHandler(c *gin.Context) {
|
||||||
|
basePath := ""
|
||||||
|
if conf.AppConfig.BasePath != "" {
|
||||||
|
basePath = "/" + conf.AppConfig.BasePath
|
||||||
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "index.html", true)
|
data := gin.H{
|
||||||
|
"BasePath": basePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "index.html", data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5,11 +5,17 @@
|
||||||
<title>WatchYourLAN</title>
|
<title>WatchYourLAN</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!-- Template data injection -->
|
||||||
|
<script>
|
||||||
|
window.appConfig = {
|
||||||
|
basePath: "{{ .BasePath }}",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
<!--Favicon-->
|
<!--Favicon-->
|
||||||
<link rel="icon" type="image/x-icon" href="/fs/public/favicon.png">
|
<link rel="icon" type="image/x-icon" href="{{ .BasePath }}/fs/public/favicon.png">
|
||||||
<!-- JS & CSS -->
|
<!-- JS & CSS -->
|
||||||
<script type="module" crossorigin src="/fs/public/assets/index.js"></script>
|
<script type="module" crossorigin src="{{ .BasePath }}/fs/public/assets/index.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/fs/public/assets/index.css">
|
<link rel="stylesheet" crossorigin href="{{ .BasePath }}/fs/public/assets/index.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func Gui() {
|
||||||
"\n Config dir: " + conf.AppConfig.DirPath +
|
"\n Config dir: " + conf.AppConfig.DirPath +
|
||||||
"\n Default DB: " + conf.AppConfig.UseDB +
|
"\n Default DB: " + conf.AppConfig.UseDB +
|
||||||
"\n Log level: " + conf.AppConfig.LogLevel +
|
"\n Log level: " + conf.AppConfig.LogLevel +
|
||||||
"\n Web GUI: http://" + address +
|
"\n Web GUI: http://" + address + "/" + conf.AppConfig.BasePath +
|
||||||
"\n=================================== " + colorReset)
|
"\n=================================== " + colorReset)
|
||||||
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
@ -51,15 +51,21 @@ func Gui() {
|
||||||
templ := template.Must(template.New("").ParseFS(templFS, "templates/*"))
|
templ := template.Must(template.New("").ParseFS(templFS, "templates/*"))
|
||||||
router.SetHTMLTemplate(templ) // templates
|
router.SetHTMLTemplate(templ) // templates
|
||||||
|
|
||||||
router.StaticFS("/fs/", http.FS(pubFS)) // public
|
var routerGroup gin.IRouter = router
|
||||||
|
|
||||||
router.GET("/", indexHandler) // index.go
|
if conf.AppConfig.BasePath != "" {
|
||||||
router.GET("/config", indexHandler) // index.go
|
routerGroup = router.Group(conf.AppConfig.BasePath)
|
||||||
router.GET("/history", indexHandler) // index.go
|
}
|
||||||
router.GET("/host/*any", indexHandler) // index.go
|
|
||||||
router.GET("/metrics", prometheus.Handler())
|
|
||||||
|
|
||||||
api.Routes(router)
|
routerGroup.StaticFS("/fs/", http.FS(pubFS))
|
||||||
|
|
||||||
|
routerGroup.GET("/", indexHandler)
|
||||||
|
routerGroup.GET("/config", indexHandler)
|
||||||
|
routerGroup.GET("/history", indexHandler)
|
||||||
|
routerGroup.GET("/host/*any", indexHandler)
|
||||||
|
routerGroup.GET("/metrics", prometheus.Handler())
|
||||||
|
|
||||||
|
api.Routes(routerGroup)
|
||||||
|
|
||||||
err = router.Run(address)
|
err = router.Run(address)
|
||||||
check.IfError(err)
|
check.IfError(err)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ services:
|
||||||
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
|
||||||
|
BASE_PATH: "" # optional
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ services:
|
||||||
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
|
||||||
|
# BASE_PATH: "" # optional, default: "" (empty)
|
||||||
# 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
|
||||||
|
|
|
||||||
28
docs/API.md
28
docs/API.md
|
|
@ -1,64 +1,68 @@
|
||||||
## API
|
## API
|
||||||
```http
|
```http
|
||||||
GET /api/all
|
GET {BASE_PATH}/api/all
|
||||||
```
|
```
|
||||||
Returns all hosts in `json`.
|
Returns all hosts in `json`.
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/history
|
GET {BASE_PATH}/api/history
|
||||||
```
|
```
|
||||||
Returns all History. Not recommended, the output can be a lot.
|
Returns all History. Not recommended, the output can be a lot.
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/history/:mac/:date
|
GET {BASE_PATH}/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`.
|
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
|
```http
|
||||||
GET /api/history/:mac?num=20
|
GET {BASE_PATH}/api/history/:mac?num=20
|
||||||
```
|
```
|
||||||
Returns only last 20 lines of history of a device with this `mac`.
|
Returns only last 20 lines of history of a device with this `mac`.
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/host/:id
|
GET {BASE_PATH}/api/host/:id
|
||||||
```
|
```
|
||||||
Returns host with this `id` in `json`.
|
Returns host with this `id` in `json`.
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/port/:addr/:port
|
GET {BASE_PATH}/api/port/:addr/:port
|
||||||
```
|
```
|
||||||
Gets state of one `port` of `addr`. Returns `true` if port is open or `false` otherwise.
|
Gets state of one `port` of `addr`. Returns `true` if port is open or `false` otherwise.
|
||||||
<details>
|
<details>
|
||||||
<summary>Request example</summary>
|
<summary>Request example</summary>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://0.0.0.0:8840/api/port/192.168.2.2/8844
|
# No base path
|
||||||
|
curl "http://0.0.0.0:8840/api/port/192.168.2.2/8844"
|
||||||
|
|
||||||
|
# Generic form
|
||||||
|
curl "http://0.0.0.0:8840/{BASE_PATH}/api/port/192.168.2.2/8844"
|
||||||
```
|
```
|
||||||
</details><br>
|
</details><br>
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/edit/:id/:name/*known
|
GET {BASE_PATH}/api/edit/:id/:name/*known
|
||||||
```
|
```
|
||||||
Edit host with ID `id`. Can change `name`. `known` is optional, when set to `toggle` will change Known state.
|
Edit host with ID `id`. Can change `name`. `known` is optional, when set to `toggle` will change Known state.
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/host/del/:id
|
GET {BASE_PATH}/api/host/del/:id
|
||||||
```
|
```
|
||||||
Remove host with ID `id`.
|
Remove host with ID `id`.
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/notify_test
|
GET {BASE_PATH}/api/notify_test
|
||||||
```
|
```
|
||||||
Send test notification.
|
Send test notification.
|
||||||
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/status/*iface
|
GET {BASE_PATH}/api/status/*iface
|
||||||
```
|
```
|
||||||
Show status (Total number of hosts, online/offline, known/unknown). The `iface` parameter is optional and shows status for one interface only. For all interfaces just call `/api/status/`.
|
Show status (Total number of hosts, online/offline, known/unknown). The `iface` parameter is optional and shows status for one interface only. For all interfaces just call `{BASE_PATH}/api/status/`.
|
||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "watchyourlan",
|
"name": "watchyourlan",
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "watchyourlan",
|
"name": "watchyourlan",
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solid-primitives/scheduled": "^1.5.2",
|
"@solid-primitives/scheduled": "^1.5.2",
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ function App() {
|
||||||
<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 base={window.appConfig?.basePath || ""}>
|
||||||
<Route path="/" component={Body}/>
|
<Route path="/" component={Body}/>
|
||||||
<Route path="/config" component={Config}/>
|
<Route path="/config" component={Config}/>
|
||||||
<Route path="/history" component={History}/>
|
<Route path="/history" component={History}/>
|
||||||
|
|
|
||||||
|
|
@ -35,17 +35,17 @@ function Header() {
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-primary">
|
<nav class="navbar navbar-expand-md navbar-dark bg-primary">
|
||||||
<div class="container-lg">
|
<div class="container-lg">
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
<img src="/fs/public/favicon.png" style="width: 2em"/>
|
<img src={window.appConfig?.basePath + "/fs/public/favicon.png"} style="width: 2em"/>
|
||||||
</a>
|
</a>
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="/" title="Home">Home</a>
|
<a class="nav-link active" href={window.appConfig?.basePath + "/"} title="Home">Home</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="/config/" title="Config">Config</a>
|
<a class="nav-link active" href={window.appConfig?.basePath + "/config/"} title="Config">Config</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" href="/history/" title="History">History</a>
|
<a class="nav-link active" href={window.appConfig?.basePath + "/history/"} title="History">History</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export const apiPath = 'http://0.0.0.0:8840';
|
export const apiPath = 'http://0.0.0.0:8840'+(window.appConfig?.basePath ?? '');
|
||||||
|
|
||||||
export const apiGetAllHosts = async () => {
|
export const apiGetAllHosts = async () => {
|
||||||
const url = apiPath+'/api/all';
|
const url = apiPath+'/api/all';
|
||||||
|
|
|
||||||
9
frontend/src/vite-env.d.ts
vendored
9
frontend/src/vite-env.d.ts
vendored
|
|
@ -1 +1,10 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
appConfig: {
|
||||||
|
basePath: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue