Merge pull request #1 from yaoresugi/feature/basepath-support
ベースパスを指定できるようにした
This commit is contained in:
commit
71ce54c0ad
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 | |
|
||||
| HOST | Listen address | 0.0.0.0 |
|
||||
| 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 |
|
||||
| COLOR | Background color: light or dark | dark |
|
||||
| NODEPATH | Path to local node modules | |
|
||||
|
|
@ -138,6 +139,7 @@ Config file name is `config_v2.yaml`. Example:
|
|||
|
||||
```yaml
|
||||
arp_args: ""
|
||||
base_path: ""
|
||||
color: dark
|
||||
host: 0.0.0.0
|
||||
ifaces: enp4s0
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
|
@ -20,6 +21,9 @@ func saveConfigHandler(c *gin.Context) {
|
|||
conf.AppConfig.NodePath = c.PostForm("node")
|
||||
conf.AppConfig.ShoutURL = c.PostForm("shout")
|
||||
|
||||
basePath := c.PostForm("basepath")
|
||||
conf.AppConfig.BasePath = strings.TrimRight(basePath, "/")
|
||||
|
||||
conf.Write(conf.AppConfig)
|
||||
|
||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// Routes - start API routes
|
||||
func Routes(router *gin.Engine) {
|
||||
func Routes(router gin.IRouter) {
|
||||
|
||||
r0 := router.Group("/api")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ func read(path string) (config models.Conf) {
|
|||
|
||||
viper.SetDefault("HOST", "0.0.0.0")
|
||||
viper.SetDefault("PORT", "8840")
|
||||
viper.SetDefault("BASE_PATH", "")
|
||||
viper.SetDefault("THEME", "sand")
|
||||
viper.SetDefault("COLOR", "dark")
|
||||
viper.SetDefault("NODEPATH", "")
|
||||
|
|
@ -40,6 +41,7 @@ func read(path string) (config models.Conf) {
|
|||
|
||||
config.Host = viper.Get("HOST").(string)
|
||||
config.Port = viper.Get("PORT").(string)
|
||||
config.BasePath = strings.Trim(viper.GetString("BASE_PATH"), "/")
|
||||
config.Theme = viper.Get("THEME").(string)
|
||||
config.Color = viper.Get("COLOR").(string)
|
||||
config.NodePath = viper.Get("NODEPATH").(string)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ func Write(config models.Conf) {
|
|||
|
||||
viper.Set("HOST", config.Host)
|
||||
viper.Set("PORT", config.Port)
|
||||
viper.Set("BASE_PATH", "") // Can be set only with ENV
|
||||
viper.Set("THEME", config.Theme)
|
||||
viper.Set("COLOR", config.Color)
|
||||
viper.Set("NODEPATH", config.NodePath)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package models
|
|||
type Conf struct {
|
||||
Host string
|
||||
Port string
|
||||
BasePath string
|
||||
Theme string
|
||||
Color string
|
||||
DirPath string
|
||||
|
|
|
|||
|
|
@ -3,10 +3,19 @@ package web
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
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>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Template data injection -->
|
||||
<script>
|
||||
window.appConfig = {
|
||||
basePath: "{{ .BasePath }}",
|
||||
};
|
||||
</script>
|
||||
<!--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 -->
|
||||
<script type="module" crossorigin src="/fs/public/assets/index.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/fs/public/assets/index.css">
|
||||
<script type="module" crossorigin src="{{ .BasePath }}/fs/public/assets/index.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="{{ .BasePath }}/fs/public/assets/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func Gui() {
|
|||
"\n Config dir: " + conf.AppConfig.DirPath +
|
||||
"\n Default DB: " + conf.AppConfig.UseDB +
|
||||
"\n Log level: " + conf.AppConfig.LogLevel +
|
||||
"\n Web GUI: http://" + address +
|
||||
"\n Web GUI: http://" + address + "/" + conf.AppConfig.BasePath +
|
||||
"\n=================================== " + colorReset)
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
|
@ -51,15 +51,21 @@ func Gui() {
|
|||
templ := template.Must(template.New("").ParseFS(templFS, "templates/*"))
|
||||
router.SetHTMLTemplate(templ) // templates
|
||||
|
||||
router.StaticFS("/fs/", http.FS(pubFS)) // public
|
||||
var routerGroup gin.IRouter = router
|
||||
|
||||
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())
|
||||
if conf.AppConfig.BasePath != "" {
|
||||
routerGroup = router.Group(conf.AppConfig.BasePath)
|
||||
}
|
||||
|
||||
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)
|
||||
check.IfError(err)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ version: "3"
|
|||
services:
|
||||
wyl:
|
||||
image: aceberg/watchyourlan
|
||||
network_mode: "host"
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ~/.dockerdata/wyl:/data/WatchYourLAN
|
||||
|
|
@ -11,6 +11,7 @@ services:
|
|||
IFACES: "enp4s0 wlxf4ec3892dd51" # required: 1 or more interface
|
||||
HOST: "0.0.0.0" # optional, default: 0.0.0.0
|
||||
PORT: "8840" # optional, default: 8840
|
||||
BASE_PATH: "" # optional
|
||||
TIMEOUT: "120" # optional, time in seconds, default: 120
|
||||
SHOUTRRR_URL: "" # optional, set url to notify
|
||||
THEME: "sand" # optional
|
||||
|
|
@ -19,7 +20,7 @@ services:
|
|||
# WARNING! WYL needs 'host' network mode to work. So, WYL port will be exposed in this setup. You need to limit access to it with firewall or other measures
|
||||
|
||||
forauth:
|
||||
image: aceberg/forauth
|
||||
image: aceberg/forauth
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8800:8800 # Proxy port
|
||||
|
|
@ -28,10 +29,10 @@ services:
|
|||
- ~/.dockerdata/forauth:/data/ForAuth
|
||||
environment:
|
||||
TZ: Asia/Novosibirsk # required: needs your TZ for correct time
|
||||
FA_TARGET: "YOUR_IP:8840" # optional: path to wyl host:port
|
||||
FA_TARGET: "YOUR_IP:8840" # optional: path to wyl host:port
|
||||
FA_AUTH: "true" # optional: true - enabled, default: false
|
||||
FA_AUTH_EXPIRE: 7d # optional: expiration time, default: 7d
|
||||
FA_AUTH_PASSWORD: "$$2a$$10$$wGLUHXh2cRN1257uGg1s5eZvYgnjw8wB9vAcfcHqqqrxm5hvBqAzK"
|
||||
FA_AUTH_PASSWORD: "$$2a$$10$$wGLUHXh2cRN1257uGg1s5eZvYgnjw8wB9vAcfcHqqqrxm5hvBqAzK"
|
||||
# WARNING! If password is set as environment variable, every '$' character must be escaped with another '$', like this '$$'
|
||||
# optional: password encrypted with bcrypt, how-to: https://github.com/aceberg/ForAuth/blob/main/docs/BCRYPT.md (In this example FA_AUTH_PASSWORD=pw)
|
||||
FA_AUTH_USER: user # optional: username
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ services:
|
|||
wyl:
|
||||
image: aceberg/watchyourlan # dockerhub
|
||||
# image: ghcr.io/aceberg/watchyourlan # or github
|
||||
network_mode: "host"
|
||||
network_mode: "host"
|
||||
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
|
||||
|
|
@ -22,6 +22,7 @@ services:
|
|||
IFACES: "enp4s0 wlxf4ec3892dd51" # required: 1 or more interface
|
||||
# HOST: "0.0.0.0" # optional, default: 0.0.0.0
|
||||
# PORT: "8840" # optional, default: 8840
|
||||
# BASE_PATH: "" # optional, default: "" (empty)
|
||||
# TIMEOUT: "120" # optional, time in seconds, default: 120
|
||||
# SHOUTRRR_URL: "" # optional, set url to notify
|
||||
# THEME: "sand" # optional
|
||||
|
|
|
|||
28
docs/API.md
28
docs/API.md
|
|
@ -1,64 +1,68 @@
|
|||
## API
|
||||
```http
|
||||
GET /api/all
|
||||
GET {BASE_PATH}/api/all
|
||||
```
|
||||
Returns all hosts in `json`.
|
||||
|
||||
|
||||
```http
|
||||
GET /api/history
|
||||
GET {BASE_PATH}/api/history
|
||||
```
|
||||
Returns all History. Not recommended, the output can be a lot.
|
||||
|
||||
```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`.
|
||||
|
||||
```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`.
|
||||
|
||||
|
||||
```http
|
||||
GET /api/host/:id
|
||||
GET {BASE_PATH}/api/host/:id
|
||||
```
|
||||
Returns host with this `id` in `json`.
|
||||
|
||||
|
||||
```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.
|
||||
<details>
|
||||
<summary>Request example</summary>
|
||||
|
||||
```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>
|
||||
|
||||
|
||||
```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.
|
||||
|
||||
|
||||
```http
|
||||
GET /api/host/del/:id
|
||||
GET {BASE_PATH}/api/host/del/:id
|
||||
```
|
||||
Remove host with ID `id`.
|
||||
|
||||
|
||||
```http
|
||||
GET /api/notify_test
|
||||
GET {BASE_PATH}/api/notify_test
|
||||
```
|
||||
Send test notification.
|
||||
|
||||
|
||||
```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",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "watchyourlan",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.4",
|
||||
"dependencies": {
|
||||
"@solid-primitives/scheduled": "^1.5.2",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function App() {
|
|||
<div class="container-lg">
|
||||
<div class="row">
|
||||
<div class="col-md mt-4 mb-4">
|
||||
<Router>
|
||||
<Router base={window.appConfig?.basePath || ""}>
|
||||
<Route path="/" component={Body}/>
|
||||
<Route path="/config" component={Config}/>
|
||||
<Route path="/history" component={History}/>
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ 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");
|
||||
|
|
@ -35,17 +35,17 @@ function Header() {
|
|||
<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"/>
|
||||
<img src={window.appConfig?.basePath + "/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>
|
||||
<a class="nav-link active" href={window.appConfig?.basePath + "/"} title="Home">Home</a>
|
||||
</li>
|
||||
<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 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>
|
||||
</ul>
|
||||
<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 () => {
|
||||
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" />
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
appConfig: {
|
||||
basePath: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue