This commit is contained in:
aceberg 2025-03-24 17:59:48 +07:00
parent 0cd7cc2730
commit 5055cd3d00
19 changed files with 34 additions and 23 deletions

View file

@ -1,6 +1,15 @@
# Change Log
All notable changes to this project will be documented in this file.
## [v2.1.0] - 2025-
### Added
- Rewrited GUI in SolidJS and TypeScript
- Prometheus integration [#181](https://github.com/aceberg/WatchYourLAN/pull/181)
### Fixed
- Vite: file names
- Node Path bug
## [v2.0.4] - 2024-10-21
### Added
- Notification test [#147](https://github.com/aceberg/WatchYourLAN/issues/147)

View file

@ -1,7 +1,7 @@
{
"name": "watchyourlan",
"private": true,
"version": "0.0.0",
"version": "2.1.0",
"type": "module",
"scripts": {
"dev": "vite",

View file

@ -7,7 +7,7 @@ function TableHead() {
const handleSort = (sortBy: string) => {
sortBy === "MAC" ? sortBy = "Mac" : '';
sortBy === "Hardware" ? sortBy = "Hw" : '';
sortBy === "Online" ? sortBy = "Now" : '';
sortBy === "On" ? sortBy = "Now" : '';
sortByAnyField(sortBy as keyof Host);
};
@ -15,7 +15,7 @@ function TableHead() {
<thead>
<tr>
<th style="width: 2em;"></th>
<For each={["Name", "Iface", "IP", "MAC", "Hardware", "Date", "Known", "Online"]}>{(key) =>
<For each={["Name", "Iface", "IP", "MAC", "Hardware", "Date", "Known", "On"]}>{(key) =>
<th>{key} <i class="bi bi-sort-down-alt my-btn"
onClick={[handleSort, key]}
title={"Sort by " + key}

View file

@ -40,7 +40,7 @@ function TableRow(_props: any) {
<td>{_props.host.Iface}</td>
<td><a href={"http://" + _props.host.IP} target="_blank">{_props.host.IP}</a></td>
<td>{_props.host.Mac}</td>
<td>{_props.host.Hw}</td>
<td title={_props.host.Hw}>{_props.host.Hw.slice(0,12)+".."}</td>
<td>{_props.host.Date}</td>
<td>
<div class="form-check form-switch">

View file

@ -28,7 +28,7 @@ function About() {
<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>
<p> Commission you own app (Golang, React/SolidJS). Contact <a href="https://github.com/aceberg" target="_blank">here</a></p>
</div>
</div>
)

View file

@ -25,8 +25,8 @@ function Filter() {
</select>
<select class="form-select" title="Filter by Online">
<option selected disabled>Online</option>
<option onClick={()=>{handleFilter("Now", 1)}}>Online</option>
<option onClick={()=>{handleFilter("Now", 0)}}>Offline</option>
<option onClick={()=>{handleFilter("Now", 1)}}>On</option>
<option onClick={()=>{handleFilter("Now", 0)}}>Off</option>
</select>
<button onClick={()=>{handleFilter("ID", 0)}} class="btn btn-outline-primary" title="Reset filter">Reset filter</button>
</div>

View file

@ -10,7 +10,7 @@ function Header() {
const setCurrentTheme = async () => {
setAppConfig(await apiGetConfig());
const theme = appConfig().Theme?appConfig().Theme:"minty";
const theme = appConfig().Theme?appConfig().Theme:"sand";
const color = appConfig().Color?appConfig().Color:"dark";
if (appConfig().NodePath == '') {

View file

@ -6,7 +6,7 @@ function HistShow(_props: any) {
localStorage.setItem(_props.name, showStr);
setShow(+showStr);
show() == 0 ? setShow(198) : '';
show() == 0 ? setShow(200) : '';
};
return (

View file

@ -6,7 +6,7 @@ function HistCard(_props: any) {
const showStr = localStorage.getItem("hostShow") as string;
setShow(+showStr);
(show() === 0 || isNaN(show())) ? setShow(570) : '';
(show() === 0 || isNaN(show())) ? setShow(500) : '';
return (
<div class="card border-primary">

View file

@ -9,7 +9,7 @@ export const apiGetAllHosts = async () => {
export const apiGetConfig = async () => {
const url = apiPath+'/api/config/';
const url = apiPath+'/api/config';
const res = await (await fetch(url)).json();
return res;

View file

@ -90,4 +90,4 @@ export const [appConfig, setAppConfig] = createSignal<Conf>(emptyConf);
export const [editNames, setEditNames] = createSignal(false);
export const [show, setShow] = createSignal<number>(198);
export const [show, setShow] = createSignal<number>(200);

View file

@ -18,7 +18,7 @@ function Body() {
<TableHead></TableHead>
<tbody>
<For each={allHosts()}>{(host, index) =>
<TableRow host={host} index={index}></TableRow>
<TableRow host={host} index={index() + 1}></TableRow>
}</For>
</tbody>
</table>

View file

@ -8,7 +8,7 @@ function History() {
const showStr = localStorage.getItem("histShow") as string;
setShow(+showStr);
(show() === 0 || isNaN(show())) ? setShow(198) : '';
(show() === 0 || isNaN(show())) ? setShow(200) : '';
return (
<div class="card border-primary">

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
import{c as h,o as v,e as c,S as w,t as u,b as d,s as S,x as g,l as f,F as p,y,k as m,h as b}from"./index.js";var x=u("<i>");function $(o){const[r,t]=h([]);return v(async()=>{let e=[];e=await y(o.mac),e!=null&&(e.sort((s,a)=>s.Date<a.Date?1:-1),t(e))}),c(p,{get each(){return r()},children:(e,s)=>c(w,{get when(){return s()<f()},get children(){var a=x();return d(n=>{var l="Date:"+e.Date+`
Iface:`+e.Iface+`
IP:`+e.IP+`
Known:`+e.Known,i=e.Now===0?"my-box-off":"my-box-on";return l!==n.e&&S(a,"title",n.e=l),i!==n.t&&g(a,n.t=i),n},{e:void 0,t:void 0}),a}})})}var H=u('<input class=form-control placeholder="Show elements"title="Nomber of elements to show"style=max-width:10em;>');function D(o){const r=t=>{localStorage.setItem(o.name,t),m(+t),f()==0&&m(198)};return(()=>{var t=H();return t.$$input=e=>r(e.target.value),t})()}b(["input"]);export{D as H,$ as M};
Known:`+e.Known,i=e.Now===0?"my-box-off":"my-box-on";return l!==n.e&&S(a,"title",n.e=l),i!==n.t&&g(a,n.t=i),n},{e:void 0,t:void 0}),a}})})}var H=u('<input class=form-control placeholder="Show elements"title="Nomber of elements to show"style=max-width:10em;>');function D(o){const r=t=>{localStorage.setItem(o.name,t),m(+t),f()==0&&m(200)};return(()=>{var t=H();return t.$$input=e=>r(e.target.value),t})()}b(["input"]);export{D as H,$ as M};

View file

@ -1 +1 @@
import{k as f,l as m,t as _,i as t,e as a,m as C,b as H,s as v,n as I,F}from"./index.js";import{H as M,M as N}from"./HistShow.js";var P=_('<div class="card border-primary"><div class="card-header d-flex justify-content-between"></div><div class=card-body><table class="table table-striped table-hover"><tbody></tbody>'),j=_("<tr><td class=opacity-50 style=width:2em;>.</td><td><a></a><br><a></a></td><td>");function D(){const $=localStorage.getItem("histShow");return f(+$),(m()===0||isNaN(m()))&&f(198),(()=>{var d=P(),l=d.firstChild,u=l.nextSibling,S=u.firstChild,g=S.firstChild;return t(l,a(C,{}),null),t(l,a(M,{name:"histShow"}),null),t(g,a(F,{get each(){return I()},children:(e,y)=>(()=>{var n=j(),i=n.firstChild,w=i.firstChild,o=i.nextSibling,s=o.firstChild,p=s.nextSibling,c=p.nextSibling,x=o.nextSibling;return t(i,y,w),t(s,()=>e.Name),t(c,()=>e.IP),t(x,a(N,{get mac(){return e.Mac}})),H(r=>{var h="/host/"+e.ID,b="http://"+e.IP;return h!==r.e&&v(s,"href",r.e=h),b!==r.t&&v(c,"href",r.t=b),r},{e:void 0,t:void 0}),n})()})),d})()}export{D as default};
import{k as f,l as m,t as _,i as t,e as a,m as C,b as H,s as v,n as I,F}from"./index.js";import{H as M,M as N}from"./HistShow.js";var P=_('<div class="card border-primary"><div class="card-header d-flex justify-content-between"></div><div class=card-body><table class="table table-striped table-hover"><tbody></tbody>'),j=_("<tr><td class=opacity-50 style=width:2em;>.</td><td><a></a><br><a></a></td><td>");function D(){const $=localStorage.getItem("histShow");return f(+$),(m()===0||isNaN(m()))&&f(200),(()=>{var d=P(),l=d.firstChild,u=l.nextSibling,S=u.firstChild,g=S.firstChild;return t(l,a(C,{}),null),t(l,a(M,{name:"histShow"}),null),t(g,a(F,{get each(){return I()},children:(e,y)=>(()=>{var n=j(),i=n.firstChild,w=i.firstChild,o=i.nextSibling,s=o.firstChild,p=s.nextSibling,c=p.nextSibling,x=o.nextSibling;return t(i,y,w),t(s,()=>e.Name),t(c,()=>e.IP),t(x,a(N,{get mac(){return e.Mac}})),H(r=>{var h="/host/"+e.ID,b="http://"+e.IP;return h!==r.e&&v(s,"href",r.e=h),b!==r.t&&v(c,"href",r.t=b),r},{e:void 0,t:void 0}),n})()})),d})()}export{D as default};

View file

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

File diff suppressed because one or more lines are too long

View file

@ -22,7 +22,9 @@ func Gui(dirPath, nodePath string) {
appConfig.DirPath = dirPath
appConfig.ConfPath = confPath
appConfig.DBPath = dirPath + "/scan.db"
if nodePath != "" {
appConfig.NodePath = nodePath
}
quitScan = make(chan bool)
updateRoutines() // routines-upd.go
@ -37,8 +39,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/*"))
@ -53,7 +55,7 @@ func Gui(dirPath, nodePath string) {
router.GET("/metrics", prometheus.Handler(&appConfig))
router.GET("/api/all", apiAll) // api.go
router.GET("/api/config/", apiGetConfig) // 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