Config Page

This commit is contained in:
aceberg 2025-03-22 19:28:31 +07:00
parent 8f122db640
commit 113cf7cb18
25 changed files with 533 additions and 146 deletions

View file

@ -10,6 +10,10 @@ replace:
cd ../internal/web/public/assets/ && \
cat index-*.js | sed 's/assets/fs\/public\/assets/g;s/http:\/\/0.0.0.0:8840//' > index.js && \
cat index-*.css | sed 's/assets/fs\/public\/assets/g' > index.css && \
cat Config-*.js | sed 's/index-.*\.js/index.js/g' > tmp && \
cp tmp Config-*.js && \
cat History-*.js | sed 's/index-.*\.js/index.js/g' > tmp && \
cp tmp History-*.js && \
cat HostPage-*.js | sed 's/index-.*\.js/index.js/g' > tmp && \
cp tmp HostPage-*.js && \
rm tmp index-*

View file

@ -12,6 +12,8 @@ function App() {
runAtStart();
});
const Config = lazy(() => import("./pages/Config"));
const History = lazy(() => import("./pages/History"));
const HostPage = lazy(() => import("./pages/HostPage"));
return (
@ -22,6 +24,8 @@ function App() {
<div class="col-md mt-4 mb-4">
<Router>
<Route path="/" component={Body}/>
<Route path="/config" component={Config}/>
<Route path="/history" component={History}/>
<Route path="/host/:id" component={HostPage}/>
</Router>
</div>

View file

@ -0,0 +1,37 @@
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">
<p> After changing <b>Host</b> or <b>Port</b> the app must be restarted</p>
<p> <b>Shoutrrr URL</b> provides notifications to Discord, Email, Gotify, Telegram and other services. <a href="https://containrrr.dev/shoutrrr/v0.8/" target="_blank">Link to documentation</a></p>
<p> <b>Interfaces</b> - one or more, space separated</p>
<p> <b>Timeout (seconds)</b> - time between scans</p>
<p> <b>Args for arp-scan</b> - 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.</p>
<p> <b>Arp Strings</b> - 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.</p>
<p> <b>Trim History</b> - remove history after (hours)</p>
<p> <b>Store History in DB</b> - if off, 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</p>
<p> <b>PG Connect URL</b> - 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></p>
<p> If you find this app useful, please, <a href="https://github.com/aceberg#donate" target="_blank">donate</a></p>
<p> Commission you own app (Golang, HTML/JS, Flutter). Contact <a href="https://github.com/aceberg" target="_blank">here</a></p>
</div>
</div>
)
}
export default About

View file

@ -0,0 +1,83 @@
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

View file

@ -0,0 +1,61 @@
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

View file

@ -0,0 +1,36 @@
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="_self">/metrics</a>
</td>
</tr>
</tbody></table>
</form>
</div>
</div>
)
}
export default Prometheus

View file

@ -0,0 +1,94 @@
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>Store History in DB</td>
<td>
<div class="form-check form-switch">
{appConfig().HistInDB
? <input class="form-check-input" type="checkbox" name="histdb" checked></input>
: <input class="form-check-input" type="checkbox" name="histdb"></input>
}
</div>
</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></td>
</tr>
</tbody></table>
</form>
</div>
</div>
)
}
export default Scan

View file

@ -12,7 +12,6 @@ function Filter() {
return (
<div class="row">
<div class="col input-group">
<span class="input-group-text">Filter by</span>
<select class="form-select">
<option selected disabled>Iface</option>
<For each={ifaces()}>{(iface) =>

View file

@ -1,21 +1,24 @@
import { createSignal } from "solid-js";
import { Conf } from "../functions/exports";
import { appConfig, setAppConfig } from "../functions/exports";
import { apiGetConfig } from "../functions/api";
function Header() {
const [themePath, setThemePath] = createSignal('');
const [iconsPath, setIconsPath] = createSignal('');
const setCurrentTheme = (appConfig:Conf) => {
const theme = appConfig.Theme?appConfig.Theme:"sand";
const color = appConfig.Color?appConfig.Color:"dark";
const setCurrentTheme = async () => {
setAppConfig(await apiGetConfig());
if (appConfig.NodePath == '') {
const theme = appConfig().Theme?appConfig().Theme:"minty";
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");
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);
@ -23,12 +26,7 @@ function Header() {
? document.documentElement.style.setProperty('--transparent-light', '#ffffff15')
: document.documentElement.style.setProperty('--transparent-light', '#00000015');
}
setCurrentTheme({
Theme: "sand",
Color: "dark",
Timeout: 120,
NodePath: "",
});
setCurrentTheme();
return (
<>
@ -52,7 +50,7 @@ function Header() {
<a class="nav-link active" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" target="_self" href="/config/">Config</a>
<a class="nav-link active" href="/config/">Config</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/history/">History</a>

View file

@ -1,15 +1,37 @@
const api = 'http://0.0.0.0:8840';
export const apiPath = 'http://0.0.0.0:8840';
export const apiGetAllHosts = async () => {
const url = api+'/api/all';
const url = apiPath+'/api/all';
const hosts = await (await fetch(url)).json();
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) => {
const url = api+'/api/edit/'+id+'/'+name+'/'+known;
const url = apiPath+'/api/edit/'+id+'/'+name+'/'+known;
const res = await (await fetch(url)).json();
return res;
@ -17,7 +39,7 @@ export const apiEditHost = async (id:number, name:string, known:string) => {
export const apiGetHost = async (id:string) => {
const url = api+'/api/host/'+id;
const url = apiPath+'/api/host/'+id;
const res = await (await fetch(url)).json();
return res;
@ -25,7 +47,7 @@ export const apiGetHost = async (id:string) => {
export const apiDelHost = async (id:number) => {
const url = api+'/api/host/del/'+id;
const url = apiPath+'/api/host/del/'+id;
const res = await (await fetch(url)).json();
return res;
@ -33,7 +55,7 @@ export const apiDelHost = async (id:number) => {
export const apiPortScan = async (ip:string, port:number) => {
const url = api+'/api/port/'+ip+'/'+port;
const url = apiPath+'/api/port/'+ip+'/'+port;
const res = await (await fetch(url)).json();
return res;

View file

@ -13,6 +13,34 @@ export interface Host {
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;
HistInDB: boolean;
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 = {
ID: 0,
Name: "",
@ -26,11 +54,30 @@ export const emptyHost:Host = {
Now: 0,
};
export interface Conf {
Theme: string;
Color: string;
Timeout: number;
NodePath: string;
export const emptyConf:Conf = {
Host: "",
Port: "",
Theme: "",
Color: "",
DirPath: "",
Timeout: 120,
NodePath: "",
LogLevel: "",
Ifaces: "",
ArpArgs: "",
ArpStrs: [],
TrimHist: 48,
HistInDB: false,
ShoutURL: "",
UseDB: "",
PGConnect: "",
InfluxEnable: false,
InfluxAddr: "",
InfluxToken: "",
InfluxOrg: "",
InfluxBucket: "",
InfluxSkipTLS: false,
PrometheusEnable: false,
};
@ -39,7 +86,7 @@ export const [bkpHosts, setBkpHosts] = createSignal<Host[]>([]);
export const [ifaces, setIfaces] = createSignal<string[]>([]);
export const [appConfig, setAppConfig] = createSignal<Conf>();
export const [appConfig, setAppConfig] = createSignal<Conf>(emptyConf);
export const [editNames, setEditNames] = createSignal(false);

View file

@ -0,0 +1,30 @@
import About from "../components/Config/About"
import Basic from "../components/Config/Basic"
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">
<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

View file

@ -0,0 +1,13 @@
function History() {
return (
<div class="row">
<div class="col-md">
History page
</div>
</div>
)
}
export default History

26
go.mod
View file

@ -9,7 +9,7 @@ require (
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
github.com/prometheus/client_golang v1.21.1
github.com/spf13/viper v1.19.0
github.com/spf13/viper v1.20.0
modernc.org/sqlite v1.36.1
)
@ -23,53 +23,49 @@ require (
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/fsnotify/fsnotify v1.8.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/go-viper/mapstructure/v2 v2.2.1 // 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/compress v1.17.11 // 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/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.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // 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/sagikazarmark/locafero v0.7.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/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // 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.31.0 // indirect
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.36.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

73
go.sum
View file

@ -19,25 +19,24 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.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/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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/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=
@ -50,6 +49,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
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/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/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/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
@ -61,8 +62,6 @@ github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlG
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=
@ -90,8 +89,6 @@ 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=
@ -99,8 +96,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
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=
@ -116,11 +111,10 @@ 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/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
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/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/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
@ -133,32 +127,27 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
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.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/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
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/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
github.com/spf13/viper v1.20.0/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/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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@ -174,12 +163,12 @@ go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTV
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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
@ -187,19 +176,17 @@ golang.org/x/sync v0.10.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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/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=

View file

@ -11,6 +11,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Handler - display Prometheus metrics
func Handler(appConfig *models.Conf) func(c *gin.Context) {
h := promhttp.Handler()
return func(c *gin.Context) {

View file

@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/aceberg/WatchYourLAN/internal/check"
"github.com/aceberg/WatchYourLAN/internal/db"
"github.com/aceberg/WatchYourLAN/internal/models"
"github.com/aceberg/WatchYourLAN/internal/notify"
@ -19,6 +20,20 @@ func apiAll(c *gin.Context) {
c.IndentedJSON(http.StatusOK, allHosts)
}
func apiVersion(c *gin.Context) {
file, err := pubFS.ReadFile("public/version")
check.IfError(err)
version := string(file)[8:]
c.IndentedJSON(http.StatusOK, version)
}
func apiGetConfig(c *gin.Context) {
c.IndentedJSON(http.StatusOK, appConfig)
}
func apiHistory(c *gin.Context) {
var hosts []models.Host

View file

@ -7,27 +7,9 @@ import (
"github.com/gin-gonic/gin"
"github.com/aceberg/WatchYourLAN/internal/check"
"github.com/aceberg/WatchYourLAN/internal/conf"
"github.com/aceberg/WatchYourLAN/internal/models"
)
func configHandler(c *gin.Context) {
var guiData models.GuiData
guiData.Config = appConfig
guiData.Themes = []string{"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"}
file, err := pubFS.ReadFile("public/version")
check.IfError(err)
version := string(file)
guiData.Version = version[8:]
c.HTML(http.StatusOK, "header.html", guiData)
c.HTML(http.StatusOK, "config.html", guiData)
}
func saveConfigHandler(c *gin.Context) {
appConfig.Host = c.PostForm("host")
@ -41,7 +23,7 @@ func saveConfigHandler(c *gin.Context) {
slog.Info("Writing new config to " + appConfig.ConfPath)
c.Redirect(http.StatusFound, "/config")
c.Redirect(http.StatusFound, c.Request.Referer())
}
func saveSettingsHandler(c *gin.Context) {
@ -80,7 +62,7 @@ func saveSettingsHandler(c *gin.Context) {
updateRoutines() // routines-upd.go
c.Redirect(http.StatusFound, "/config")
c.Redirect(http.StatusFound, c.Request.Referer())
}
func saveInfluxHandler(c *gin.Context) {
@ -107,7 +89,7 @@ func saveInfluxHandler(c *gin.Context) {
slog.Info("Writing new config to " + appConfig.ConfPath)
c.Redirect(http.StatusFound, "/config")
c.Redirect(http.StatusFound, c.Request.Referer())
}
func savePrometheusHandler(c *gin.Context) {
@ -119,5 +101,5 @@ func savePrometheusHandler(c *gin.Context) {
slog.Info("Writing new config to " + appConfig.ConfPath)
c.Redirect(http.StatusFound, "/config")
c.Redirect(http.StatusFound, c.Request.Referer())
}

View file

@ -2,7 +2,6 @@ package web
import (
"net"
"slices"
"strconv"
"strings"
@ -46,13 +45,3 @@ func getHostsByMAC(mac string, hosts []models.Host) (foundHosts []models.Host) {
return foundHosts
}
func getAllIfaces(hosts []models.Host) (ifaces []string) {
for _, host := range hosts {
if !slices.Contains(ifaces, host.Iface) {
ifaces = append(ifaces, host.Iface)
}
}
return ifaces
}

View file

@ -4,21 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/aceberg/WatchYourLAN/internal/models"
)
func indexHandler(c *gin.Context) {
var guiData models.GuiData
guiData.Config = appConfig
guiData.Themes = getAllIfaces(allHosts)
c.HTML(http.StatusOK, "index.html", guiData)
}
func historyHandler(c *gin.Context) {
var guiData models.GuiData
guiData.Config = appConfig
c.HTML(http.StatusOK, "history.html", guiData)
c.HTML(http.StatusOK, "index.html", true)
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{t}from"./index.js";var r=t("<div class=row><div class=col-md>History page");function o(){return r()}export{o as default};

View file

@ -1 +1 @@
import{t as o,i as l,c as t,a as K,b as x,s as O,d as A,e as et,f as B,g as v,h as I,F as lt,j as it,o as nt,u as rt,k as at,l as st}from"./index.js";var dt=o('<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></td></tr></tbody></table><button type=button class="btn btn-outline-danger">Delete host'),ct=o('<i class="bi bi-check-circle-fill"style=color:var(--bs-success);>'),ot=o('<i class="bi bi-circle-fill"style=color:var(--bs-gray-500);>');function $t(){let e="";const s=async b=>{e=b,await A(t().ID,e,"")},$=async()=>{e==""&&(e=t().Name),await A(t().ID,e,"toggle")},C=async()=>{await et(t().ID),window.location.href="/"};return(()=>{var b=dt(),_=b.firstChild,y=_.nextSibling,u=y.firstChild,w=u.firstChild,f=w.firstChild,k=f.firstChild,n=k.nextSibling,r=f.nextSibling,c=r.firstChild,a=c.nextSibling,g=a.firstChild,h=r.nextSibling,N=h.firstChild,S=N.nextSibling,i=h.nextSibling,d=i.firstChild,m=d.nextSibling,p=i.nextSibling,G=p.firstChild,R=G.nextSibling,D=R.firstChild,H=p.nextSibling,T=H.firstChild,q=T.nextSibling,E=H.nextSibling,z=E.firstChild,J=z.nextSibling,M=E.nextSibling,L=M.firstChild,Q=L.nextSibling,F=M.nextSibling,U=F.firstChild,V=U.nextSibling,W=V.firstChild,j=W.firstChild,X=F.nextSibling,Y=X.firstChild,Z=Y.nextSibling,tt=u.nextSibling;return l(n,()=>t().ID),g.$$input=P=>s(P.target.value),l(S,()=>t().DNS),l(m,()=>t().Iface),l(D,()=>t().IP),l(q,()=>t().Mac),l(J,()=>t().Hw),l(Q,()=>t().Date),j.$$click=$,l(Z,(()=>{var P=K(()=>t().Now==1);return()=>P()?ct():ot()})()),tt.$$click=C,x(()=>O(D,"href","http://"+t().IP)),x(()=>g.value=t().Name),x(()=>j.checked=t().Known==1),b})()}B(["input","click"]);var bt=o('<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>'),_t=o('<div class="d-flex justify-content-between mt-2"><button type=button class="btn btn-warning">Stop/Continue</button><div>Scanning port: '),ut=o("<a class=me-4 target=_blank>");function ft(){let e=!1;const[s,$]=v(""),[C,b]=v(""),[_,y]=v(""),[u,w]=v([]),f=async()=>{e=!1;let n=Number(s());(Number.isNaN(n)||n<1||n>65535)&&(n=1);let r=Number(C());(Number.isNaN(r)||r<1||r>65535)&&(r=65535);let c;for(let a=n;a<=r&&!e;a++)y(a.toString()),c=await it(t().IP,a),c&&w([...u(),a])},k=()=>{e?($(_()),f()):e=!0};return(()=>{var n=bt(),r=n.firstChild,c=r.nextSibling,a=c.firstChild,g=a.firstChild,h=g.nextSibling,N=h.nextSibling,S=a.nextSibling;return g.$$input=i=>$(i.target.value),h.$$input=i=>b(i.target.value),N.$$click=f,l(c,(()=>{var i=K(()=>_()!="");return()=>i()?(()=>{var d=_t(),m=d.firstChild,p=m.nextSibling;return p.firstChild,m.$$click=k,l(p,_,null),d})():[]})(),S),l(S,I(lt,{get each(){return u()},children:i=>(()=>{var d=ut();return l(d,i),x(()=>O(d,"href","http://"+t().IP+":"+i)),d})()})),n})()}B(["input","click"]);var gt=o("<div class=row><div class=col-md></div><div class=col-md>");function pt(){return nt(async()=>{const e=rt(),s=await at(e.id);st(s)}),(()=>{var e=gt(),s=e.firstChild,$=s.nextSibling;return l(s,I($t,{})),l($,I(ft,{})),e})()}export{pt as default};
import{t as o,i as l,k as t,j as K,b as x,s as O,l as A,m as et,h as q,c as v,e as I,F as lt,n as it,o as nt,u as rt,p as at,q as st}from"./index.js";var dt=o('<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></td></tr></tbody></table><button type=button class="btn btn-outline-danger">Delete host'),ct=o('<i class="bi bi-check-circle-fill"style=color:var(--bs-success);>'),ot=o('<i class="bi bi-circle-fill"style=color:var(--bs-gray-500);>');function $t(){let e="";const s=async b=>{e=b,await A(t().ID,e,"")},$=async()=>{e==""&&(e=t().Name),await A(t().ID,e,"toggle")},C=async()=>{await et(t().ID),window.location.href="/"};return(()=>{var b=dt(),_=b.firstChild,y=_.nextSibling,u=y.firstChild,w=u.firstChild,f=w.firstChild,k=f.firstChild,n=k.nextSibling,r=f.nextSibling,c=r.firstChild,a=c.nextSibling,p=a.firstChild,g=r.nextSibling,N=g.firstChild,m=N.nextSibling,i=g.nextSibling,d=i.firstChild,S=d.nextSibling,h=i.nextSibling,B=h.firstChild,G=B.nextSibling,D=G.firstChild,H=h.nextSibling,R=H.firstChild,T=R.nextSibling,E=H.nextSibling,z=E.firstChild,J=z.nextSibling,M=E.nextSibling,L=M.firstChild,Q=L.nextSibling,F=M.nextSibling,U=F.firstChild,V=U.nextSibling,W=V.firstChild,j=W.firstChild,X=F.nextSibling,Y=X.firstChild,Z=Y.nextSibling,tt=u.nextSibling;return l(n,()=>t().ID),p.$$input=P=>s(P.target.value),l(m,()=>t().DNS),l(S,()=>t().Iface),l(D,()=>t().IP),l(T,()=>t().Mac),l(J,()=>t().Hw),l(Q,()=>t().Date),j.$$click=$,l(Z,(()=>{var P=K(()=>t().Now==1);return()=>P()?ct():ot()})()),tt.$$click=C,x(()=>O(D,"href","http://"+t().IP)),x(()=>p.value=t().Name),x(()=>j.checked=t().Known==1),b})()}q(["input","click"]);var bt=o('<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>'),_t=o('<div class="d-flex justify-content-between mt-2"><button type=button class="btn btn-warning">Stop/Continue</button><div>Scanning port: '),ut=o("<a class=me-4 target=_blank>");function ft(){let e=!1;const[s,$]=v(""),[C,b]=v(""),[_,y]=v(""),[u,w]=v([]),f=async()=>{e=!1;let n=Number(s());(Number.isNaN(n)||n<1||n>65535)&&(n=1);let r=Number(C());(Number.isNaN(r)||r<1||r>65535)&&(r=65535);let c;for(let a=n;a<=r&&!e;a++)y(a.toString()),c=await it(t().IP,a),c&&w([...u(),a])},k=()=>{e?($(_()),f()):e=!0};return(()=>{var n=bt(),r=n.firstChild,c=r.nextSibling,a=c.firstChild,p=a.firstChild,g=p.nextSibling,N=g.nextSibling,m=a.nextSibling;return p.$$input=i=>$(i.target.value),g.$$input=i=>b(i.target.value),N.$$click=f,l(c,(()=>{var i=K(()=>_()!="");return()=>i()?(()=>{var d=_t(),S=d.firstChild,h=S.nextSibling;return h.firstChild,S.$$click=k,l(h,_,null),d})():[]})(),m),l(m,I(lt,{get each(){return u()},children:i=>(()=>{var d=ut();return l(d,i),x(()=>O(d,"href","http://"+t().IP+":"+i)),d})()})),n})()}q(["input","click"]);var pt=o("<div class=row><div class=col-md></div><div class=col-md>");function ht(){return nt(async()=>{const e=rt(),s=await at(e.id);st(s)}),(()=>{var e=pt(),s=e.firstChild,$=s.nextSibling;return l(s,I($t,{})),l($,I(ft,{})),e})()}export{ht as default};

File diff suppressed because one or more lines are too long

View file

@ -37,8 +37,8 @@ func Gui(dirPath, nodePath string) {
slog.Info("=================================== ")
gin.SetMode(gin.ReleaseMode)
// router := gin.Default()
router := gin.New()
router := gin.Default()
// router := gin.New()
router.Use(gin.Recovery())
templ := template.Must(template.New("").ParseFS(templFS, "templates/*"))
@ -46,7 +46,12 @@ func Gui(dirPath, nodePath string) {
router.StaticFS("/fs/", http.FS(pubFS)) // public
router.GET("/", indexHandler) // index.go
router.GET("/config", indexHandler) // index.go
router.GET("/metrics", prometheus.Handler(&appConfig))
router.GET("/api/all", apiAll) // api.go
router.GET("/api/config/", apiGetConfig) // api.go
router.GET("/api/edit/:id/:name/*known", apiEdit) // api.go
router.GET("/api/history/*mac", apiHistory) // api.go
router.GET("/api/host/:id", apiHost) // api.go
@ -54,18 +59,12 @@ func Gui(dirPath, nodePath string) {
router.GET("/api/notify_test", apiNotifyTest) // api.go
router.GET("/api/port/:addr/:port", apiPort) // api.go
router.GET("/api/status/*iface", apiStatus) // api.go
router.GET("/api/version", apiVersion) // api.go
router.GET("/", indexHandler) // index.go
router.GET("/history/", historyHandler) // index.go
// router.GET("/host/:id", hostHandler) // host.go
router.GET("/config/", configHandler) // config.go
router.POST("/config/", saveConfigHandler) // config.go
router.POST("/config_settings/", saveSettingsHandler) // config.go
router.POST("/config_influx/", saveInfluxHandler) // config.go
router.POST("/config_prometheus/", savePrometheusHandler) // config.go
router.GET("/metrics", prometheus.Handler(&appConfig))
router.POST("/api/config/", saveConfigHandler) // config.go
router.POST("/api/config_settings/", saveSettingsHandler) // config.go
router.POST("/api/config_influx/", saveInfluxHandler) // config.go
router.POST("/api/config_prometheus/", savePrometheusHandler) // config.go
err := router.Run(address)
check.IfError(err)