History Page

This commit is contained in:
aceberg 2025-03-23 13:42:38 +07:00
parent 113cf7cb18
commit ffc2d2fdbe
22 changed files with 153 additions and 48 deletions

View file

@ -16,6 +16,8 @@ replace:
cp tmp History-*.js && \
cat HostPage-*.js | sed 's/index-.*\.js/index.js/g' > tmp && \
cp tmp HostPage-*.js && \
cat MacHistory-*.js | sed 's/index-.*\.js/index.js/g' > tmp && \
cp tmp MacHistory-*.js && \
rm tmp index-*
all: build replace

View file

@ -1,10 +1,33 @@
/* VARS */
:root {
--transparent-light: #ffffff15;
}
/* Sort button */
.my-btn {
height: 100%;
background-color: #00000000;
color: var(--bs-primary);
text-align: center;
cursor: pointer;
padding: 1px;
border-radius: 15%;
}
.my-btn:hover {
background-color: var(--transparent-light);
}
/* History box */
.my-box-on::before, .my-box-off::before {
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='18'></svg>");
border-left: thin solid black;
}
.my-box-on {
background-color: var(--bs-success);
}
.my-box-off {
background-color: var(--bs-gray-500);
}
.my-box-on:hover, .my-box-off:hover {
background-color: #0000001a;
}

View file

@ -10,7 +10,7 @@ function TableHead() {
return (
<thead>
<tr>
<th style="width: 3em;"></th>
<th style="width: 2em;"></th>
<th>Name <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Name"]}></i></th>
<th>Iface <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "Iface"]}></i></th>
<th>IP <i class="bi bi-sort-down-alt my-btn" onclick={[handleSort, "IP"]}></i></th>

View file

@ -53,7 +53,7 @@ function TableRow(_props: any) {
</td>
<td>{now}</td>
<td>
<a href={edit}><i class="bi bi-pencil-square my-btn"></i></a>
<a href={edit}><i class="bi bi-three-dots-vertical my-btn p-2"></i></a>
</td>
</tr>
)

View file

@ -0,0 +1,18 @@
import MacHistory from "../MacHistory"
function HistCard(_props: any) {
return (
<div class="card border-primary">
<div class="card-header">History</div>
<div class="card-body">
{_props.mac !== ""
? <MacHistory mac={_props.mac}></MacHistory>
: <>Loading...</>
}
</div>
</div>
)
}
export default HistCard

View file

@ -1,28 +1,27 @@
import { apiDelHost, apiEditHost } from "../../functions/api";
import { currentHost } from "../../functions/exports";
function HostCard() {
function HostCard(_props: any) {
let name:string = "";
const handleInput = async (n: string) => {
name = n;
await apiEditHost(currentHost().ID, name, '');
await apiEditHost(_props.host.ID, name, '');
};
const handleToggle = async () => {
if (name == "") {
name = currentHost().Name;
name = _props.host.Name;
}
await apiEditHost(currentHost().ID, name, 'toggle');
await apiEditHost(_props.host.ID, name, 'toggle');
};
const handleDel = async () => {
await apiDelHost(currentHost().ID);
await apiDelHost(_props.host.ID);
window.location.href = '/';
};
@ -34,40 +33,40 @@ function HostCard() {
<tbody>
<tr>
<td>ID</td>
<td>{currentHost().ID}</td>
<td>{_props.host.ID}</td>
</tr>
<tr>
<td>Name</td>
<td>
<input type="text" class="form-control" value={currentHost().Name}
<input type="text" class="form-control" value={_props.host.Name}
onInput={e => handleInput(e.target.value)}></input>
</td>
</tr>
<tr>
<td>DNS name</td>
<td>{currentHost().DNS}</td>
<td>{_props.host.DNS}</td>
</tr>
<tr>
<td>Iface</td>
<td>{currentHost().Iface}</td>
<td>{_props.host.Iface}</td>
</tr>
<tr>
<td>IP</td>
<td>
<a href={"http://" + currentHost().IP} target="_blank">{currentHost().IP}</a>
<a href={"http://" + _props.host.IP} target="_blank">{_props.host.IP}</a>
</td>
</tr>
<tr>
<td>MAC</td>
<td>{currentHost().Mac}</td>
<td>{_props.host.Mac}</td>
</tr>
<tr>
<td>Hardware</td>
<td>{currentHost().Hw}</td>
<td>{_props.host.Hw}</td>
</tr>
<tr>
<td>Date</td>
<td>{currentHost().Date}</td>
<td>{_props.host.Date}</td>
</tr>
<tr>
<td>Known</td>
@ -75,7 +74,7 @@ function HostCard() {
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
onClick={handleToggle}
checked={currentHost().Known == 1
checked={_props.host.Known == 1
? true
: false
}
@ -85,7 +84,7 @@ function HostCard() {
</tr>
<tr>
<td>Online</td>
<td>{currentHost().Now == 1
<td>{_props.host.Now == 1
? <i class="bi bi-check-circle-fill" style="color:var(--bs-success);"></i>
: <i class="bi bi-circle-fill" style="color:var(--bs-gray-500);"></i>
}</td>

View file

@ -1,9 +1,7 @@
import { createSignal, For } from "solid-js";
import { apiPortScan } from "../../functions/api";
import { currentHost } from "../../functions/exports";
function Ping() {
function Ping(_props: any) {
let stop = false;
@ -31,7 +29,7 @@ function Ping() {
break;
}
setCurPort(i.toString());
portOpened = await apiPortScan(currentHost().IP, i);
portOpened = await apiPortScan(_props.IP, i);
if (portOpened) {
setFoundPorts([...foundPorts(), i]);
}
@ -67,7 +65,7 @@ function Ping() {
}
<div class="mt-2">
<For each={foundPorts()}>{(port) =>
<a class="me-4" href={"http://" + currentHost().IP + ":" + port} target="_blank">{port}</a>
<a class="me-4" href={"http://" + _props.IP + ":" + port} target="_blank">{port}</a>
}</For>
</div>
</div>

View file

@ -0,0 +1,29 @@
import { createSignal, For, onMount } from "solid-js";
import { apiGetHistory } from "../functions/api";
import { Host } from "../functions/exports";
function MacHistory(_props: any) {
const [hist, setHist] = createSignal<Host[]>([]);
onMount(async () => {
let h:Host[] = [];
h = await apiGetHistory(_props.mac);
if (h != null) {
h.sort((a:Host, b:Host) => (a.Date < b.Date ? 1 : -1));
if (_props.show > 0) {
h = h.slice(0, _props.show);
}
setHist(h);
}
});
return (
<For each={hist()}>{h =>
<i title={"Date:"+h.Date+"\nIface:"+h.Iface+"\nIP:"+h.IP+"\nKnown:"+h.Known}
class={h.Now === 0?"my-box-off":"my-box-on"}></i>
}</For>
)
}
export default MacHistory

View file

@ -60,3 +60,10 @@ export const apiPortScan = async (ip:string, port:number) => {
return res;
};
export const apiGetHistory = async (mac:string) => {
const url = apiPath+'/api/history/'+mac;
const hosts = await (await fetch(url)).json();
return hosts;
};

View file

@ -89,5 +89,3 @@ export const [ifaces, setIfaces] = createSignal<string[]>([]);
export const [appConfig, setAppConfig] = createSignal<Conf>(emptyConf);
export const [editNames, setEditNames] = createSignal(false);
export const [currentHost, setCurrentHost] = createSignal<Host>(emptyHost);

View file

@ -1,10 +1,32 @@
import { For } from "solid-js"
import Filter from "../components/Filter"
import { allHosts } from "../functions/exports"
import MacHistory from "../components/MacHistory"
function History() {
return (
<div class="row">
<div class="col-md">
History page
<div class="card border-primary">
<div class="card-header d-flex justify-left">
<Filter></Filter>
</div>
<div class="card-body">
<table class="table table-striped table-hover">
<tbody>
<For each={allHosts()}>{(host, index) =>
<tr>
<td class="opacity-50" style="width: 2em;">{index()}.</td>
<td>
<a href={"/host/"+host.ID}>{host.Name}</a><br></br>
<a href={"http://"+host.IP}>{host.IP}</a>
</td>
<td>
<MacHistory mac={host.Mac} show={200}></MacHistory>
</td>
</tr>
}</For>
</tbody>
</table>
</div>
</div>
)

View file

@ -1,14 +1,17 @@
import { useParams } from "@solidjs/router";
import { onMount } from "solid-js";
import { createSignal, onMount } from "solid-js";
import { apiGetHost } from "../functions/api";
import { setCurrentHost } from "../functions/exports";
import HostCard from "../components/HostPage/HostCard";
import Ping from "../components/HostPage/Ping";
import HistCard from "../components/HostPage/HistCard";
import { emptyHost, Host } from "../functions/exports";
function HostPage() {
const [currentHost, setCurrentHost] = createSignal<Host>(emptyHost);
onMount(async () => {
const params = useParams();
const host = await apiGetHost(params.id);
@ -17,14 +20,21 @@ function HostPage() {
});
return (
<>
<div class="row">
<div class="col-md">
<HostCard></HostCard>
<HostCard host={currentHost()}></HostCard>
</div>
<div class="col-md">
<Ping></Ping>
<Ping IP={currentHost().IP}></Ping>
</div>
</div>
<div class="row mt-4">
<div class="col-md">
<HistCard mac={currentHost().Mac}></HistCard>
</div>
</div>
</>
)
}

View file

@ -54,11 +54,3 @@ type Stat struct {
Known int
Unknown int
}
// GuiData - all data sent to html page
type GuiData struct {
Config Conf
Host Host
Themes []string
Version string
}

View file

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

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

View file

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

View file

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

View file

@ -0,0 +1,4 @@
import{c,o as l,e as f,t as m,b as u,s as v,v as w,F as y,w as b}from"./index.js";var d=m("<i>");function I(n){const[r,i]=c([]);return l(async()=>{let t=[];t=await b(n.mac),t!=null&&(t.sort((a,e)=>a.Date<e.Date?1:-1),n.show>0&&(t=t.slice(0,n.show)),i(t))}),f(y,{get each(){return r()},children:t=>(()=>{var a=d();return u(e=>{var o="Date:"+t.Date+`
Iface:`+t.Iface+`
IP:`+t.IP+`
Known:`+t.Known,s=t.Now===0?"my-box-off":"my-box-on";return o!==e.e&&v(a,"title",e.e=o),s!==e.t&&w(a,e.t=s),e},{e:void 0,t:void 0}),a})()})}export{I as M};

View file

@ -1 +1 @@
.my-btn{height:100%;background-color:#0000;color:var(--bs-primary);text-align:center}.my-btn:hover{background-color:#0000001a}
:root{--transparent-light: #ffffff15}.my-btn{height:100%;background-color:#0000;color:var(--bs-primary);text-align:center;cursor:pointer;padding:1px;border-radius:15%}.my-btn:hover{background-color:var(--transparent-light)}.my-box-on:before,.my-box-off:before{content:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='18'></svg>");border-left:thin solid black}.my-box-on{background-color:var(--bs-success)}.my-box-off{background-color:var(--bs-gray-500)}.my-box-on:hover,.my-box-off:hover{background-color:#0000001a}

File diff suppressed because one or more lines are too long

View file

@ -48,6 +48,8 @@ func Gui(dirPath, nodePath string) {
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(&appConfig))
router.GET("/api/all", apiAll) // api.go