Host page

This commit is contained in:
aceberg 2025-03-18 21:29:58 +07:00
parent a9972ac0ce
commit 745dd0c2b1
22 changed files with 499 additions and 50 deletions

View file

@ -0,0 +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,P=f.firstChild,n=P.nextSibling,r=f.nextSibling,c=r.firstChild,a=c.nextSibling,g=a.firstChild,h=r.nextSibling,k=h.firstChild,S=k.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=N=>s(N.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 N=K(()=>t().Now==1);return()=>N()?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])},P=()=>{e?($(_()),f()):e=!0};return(()=>{var n=bt(),r=n.firstChild,c=r.nextSibling,a=c.firstChild,g=a.firstChild,h=g.nextSibling,k=h.nextSibling,S=a.nextSibling;return g.$$input=i=>$(i.target.value),h.$$input=i=>b(i.target.value),k.$$click=f,l(c,(()=>{var i=K(()=>_()!="");return()=>i()?(()=>{var d=_t(),m=d.firstChild,p=m.nextSibling;return p.firstChild,m.$$click=P,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};

File diff suppressed because one or more lines are too long

View file

@ -40,10 +40,10 @@
<a class="nav-link active" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/config/">Config</a>
<a class="nav-link active" target="_self" href="/config/">Config</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/history/">History</a>
<a class="nav-link active" target="_self" href="/history/">History</a>
</li>
</ul>
</div>

View file

@ -57,8 +57,8 @@ func Gui(dirPath, nodePath string) {
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.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

View file

@ -8,6 +8,7 @@
"name": "watchyourlan",
"version": "0.0.0",
"dependencies": {
"@solidjs/router": "^0.15.3",
"solid-js": "^1.9.5"
},
"devDependencies": {
@ -963,6 +964,14 @@
"win32"
]
},
"node_modules/@solidjs/router": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.15.3.tgz",
"integrity": "sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==",
"peerDependencies": {
"solid-js": "^1.8.6"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",

View file

@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@solidjs/router": "^0.15.3",
"solid-js": "^1.9.5"
},
"devDependencies": {

View file

@ -1,22 +1,26 @@
import { onMount } from 'solid-js';
import './App.css'
import { apiGetAllHosts } from './functions/api';
import { setAllHosts } from './functions/exports';
import Body from './pages/Body'
import { lazy, onMount } from 'solid-js';
import { Router, Route } from "@solidjs/router";
import './App.css';
import { runAtStart } from './functions/atstart';
import Body from './pages/Body';
function App() {
onMount(async () => {
const hosts = await apiGetAllHosts();
setAllHosts(hosts);
onMount(() => {
runAtStart();
});
const HostPage = lazy(() => import("./pages/HostPage"));
return (
<div class="container-lg">
<div class="row">
<div class="col-md mt-4 mb-4">
<Body></Body>
<Router>
<Route path="/" component={Body}/>
<Route path="/host/:id" component={HostPage}/>
</Router>
</div>
</div>
</div>

View file

@ -0,0 +1,28 @@
import { Show } from "solid-js";
import { editNames, setEditNames } from "../functions/exports";
import Filter from "./Filter";
import Search from "./Search";
function BodyCardHead() {
return (
<div class="row">
<div class="col-md mt-1 mb-1">
<Filter></Filter>
</div>
<div class="col-md mt-1 mb-1">
<div class="d-flex justify-content-between">
<Search></Search>
<Show
when={editNames()}
fallback={<button class="btn btn-outline-primary" onClick={[setEditNames, true]}>Edit names</button>}
>
<button class="btn btn-primary" onClick={[setEditNames, false]}>Edit names</button>
</Show>
</div>
</div>
</div>
)
}
export default BodyCardHead

View file

@ -1,20 +0,0 @@
import { Show } from "solid-js";
import { editNames, setEditNames } from "../functions/exports";
function CardHead() {
return (
<div class="row">
<div class="col-md mt-1 mb-1">
<Show
when={editNames()}
fallback={<button class="btn btn-outline-primary" onClick={[setEditNames, true]}>Edit names</button>}
>
<button class="btn btn-primary" onClick={[setEditNames, false]}>Edit names</button>
</Show>
</div>
</div>
)
}
export default CardHead

View file

@ -0,0 +1,38 @@
import { For } from "solid-js";
import { Host, ifaces } from "../functions/exports";
import { filterFunc } from "../functions/filter";
function Filter() {
const handleFilter = (field: keyof Host, value: any) => {
filterFunc(field, value);
};
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) =>
<option onClick={()=>{handleFilter("Iface", iface)}}>{iface}</option>
}</For>
</select>
<select class="form-select">
<option selected disabled>Known</option>
<option onClick={()=>{handleFilter("Known", 1)}}>Known</option>
<option onClick={()=>{handleFilter("Known", 0)}}>Unknown</option>
</select>
<select class="form-select">
<option selected disabled>Online</option>
<option onClick={()=>{handleFilter("Now", 1)}}>Online</option>
<option onClick={()=>{handleFilter("Now", 0)}}>Offline</option>
</select>
<button onClick={()=>{handleFilter("ID", 0)}} class="btn btn-outline-primary">Reset filter</button>
</div>
</div>
)
}
export default Filter

View file

@ -0,0 +1,101 @@
import { apiDelHost, apiEditHost } from "../functions/api";
import { currentHost } from "../functions/exports";
function HostPageHost() {
let name:string = "";
const handleInput = async (n: string) => {
name = n;
await apiEditHost(currentHost().ID, name, '');
};
const handleToggle = async () => {
if (name == "") {
name = currentHost().Name;
}
await apiEditHost(currentHost().ID, name, 'toggle');
};
const handleDel = async () => {
await apiDelHost(currentHost().ID);
window.location.href = '/';
};
return (
<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>{currentHost().ID}</td>
</tr>
<tr>
<td>Name</td>
<td>
<input type="text" class="form-control" value={currentHost().Name}
onInput={e => handleInput(e.target.value)}></input>
</td>
</tr>
<tr>
<td>DNS name</td>
<td>{currentHost().DNS}</td>
</tr>
<tr>
<td>Iface</td>
<td>{currentHost().Iface}</td>
</tr>
<tr>
<td>IP</td>
<td>
<a href={"http://" + currentHost().IP} target="_blank">{currentHost().IP}</a>
</td>
</tr>
<tr>
<td>MAC</td>
<td>{currentHost().Mac}</td>
</tr>
<tr>
<td>Hardware</td>
<td>{currentHost().Hw}</td>
</tr>
<tr>
<td>Date</td>
<td>{currentHost().Date}</td>
</tr>
<tr>
<td>Known</td>
<td>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
onClick={handleToggle}
checked={currentHost().Known == 1
? true
: false
}
></input>
</div>
</td>
</tr>
<tr>
<td>Online</td>
<td>{currentHost().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>
</tr>
</tbody>
</table>
<button type="button" onClick={handleDel} class="btn btn-outline-danger">Delete host</button>
</div>
</div>
)
}
export default HostPageHost

View file

@ -0,0 +1,78 @@
import { createSignal, For } from "solid-js";
import { apiPortScan } from "../functions/api";
import { currentHost } from "../functions/exports";
function HostPagePing() {
let stop = false;
const [beginStr, setBegin] = createSignal("");
const [endStr, setEnd] = createSignal("");
const [curPort, setCurPort] = createSignal("");
const [foundPorts, setFoundPorts] = createSignal<number[]>([]);
const handleScan = async () => {
stop = false;
let begin = Number(beginStr());
if (Number.isNaN(begin) || begin < 1 || begin > 65535) {
begin = 1;
}
let end = Number(endStr());
if (Number.isNaN(end) || end < 1 || end > 65535) {
end = 65535;
}
let portOpened:boolean;
for (let i = begin ; i <= end; i++) {
if (stop) {
break;
}
setCurPort(i.toString());
portOpened = await apiPortScan(currentHost().IP, i);
if (portOpened) {
setFoundPorts([...foundPorts(), i]);
}
}
};
const handleStop = () => {
if (stop) {
setBegin(curPort());
handleScan();
} else {
stop = true;
}
}
return (
<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"
onInput={e => setBegin(e.target.value)}></input>
<input type="text" class="form-control" placeholder="65535"
onInput={e => setEnd(e.target.value)}></input>
<button type="button" onClick={handleScan} class="btn btn-primary">Scan</button>
</form>
{curPort() != ""
? <div class="d-flex justify-content-between mt-2">
<button type="button" onClick={handleStop} class="btn btn-warning">Stop/Continue</button>
<div>Scanning port: {curPort()}</div>
</div>
: <></>
}
<div class="mt-2">
<For each={foundPorts()}>{(port) =>
<a class="me-4" href={"http://" + currentHost().IP + ":" + port} target="_blank">{port}</a>
}</For>
</div>
</div>
</div>
)
}
export default HostPagePing

View file

@ -0,0 +1,14 @@
import { searchFunc } from "../functions/search";
function Search() {
const handleSearch = (s: string) => {
searchFunc(s);
};
return (
<input onInput={e => handleSearch(e.target.value)} class="form-control" placeholder="Search" style="max-width: 10em;"></input>
)
}
export default Search

View file

@ -7,6 +7,7 @@ function TableRow(_props: any) {
const [name, setName] = createSignal(_props.host.Name);
const link = "http://" + _props.host.IP;
const edit = "/host/" + _props.host.ID;
let now = <i class="bi bi-circle-fill" style="color:var(--bs-gray-500);"></i>;
if (_props.host.Now == 1) {
@ -51,7 +52,9 @@ function TableRow(_props: any) {
</div>
</td>
<td>{now}</td>
<td><i class="bi bi-pencil-square my-btn"></i></td>
<td>
<a href={edit}><i class="bi bi-pencil-square my-btn"></i></a>
</td>
</tr>
)
}

View file

@ -12,5 +12,29 @@ export const apiEditHost = async (id:number, name:string, known:string) => {
const url = api+'/api/edit/'+id+'/'+name+'/'+known;
const res = await (await fetch(url)).json();
return res;
};
export const apiGetHost = async (id:string) => {
const url = api+'/api/host/'+id;
const res = await (await fetch(url)).json();
return res;
};
export const apiDelHost = async (id:number) => {
const url = api+'/api/host/del/'+id;
const res = await (await fetch(url)).json();
return res;
};
export const apiPortScan = async (ip:string, port:number) => {
const url = api+'/api/port/'+ip+'/'+port;
const res = await (await fetch(url)).json();
return res;
};

View file

@ -0,0 +1,37 @@
import { apiGetAllHosts } from "./api";
import { allHosts, setAllHosts, setBkpHosts, setIfaces } from "./exports";
import { filterAtStart, filterFunc } from "./filter";
import { sortAtStart } from "./sort";
export function runAtStart() {
getHosts();
filterFunc("ID", 0); // reset filter
setInterval(() => {
getHosts();
}, 60000); // 60000 ms = 1 minute
}
async function getHosts() {
const hosts = await apiGetAllHosts();
setAllHosts(hosts);
setBkpHosts(hosts);
sortAtStart();
filterAtStart();
listIfaces();
// console.log("UPD");
}
function listIfaces() {
let ifaces:string[] = [];
for (let host of allHosts()) {
if (!ifaces.includes(host.Iface)) {
ifaces.push(host.Iface);
}
}
setIfaces(ifaces);
}

View file

@ -13,17 +13,32 @@ export interface Host {
Now: number;
};
export interface Conf {
Color: string;
Timeout: number;
export const emptyHost:Host = {
ID: 0,
Name: "",
DNS: "",
Iface: "",
IP: "",
Mac: "",
Hw: "",
Date: "",
Known: 0,
Now: 0,
};
export interface Conf {
Color: string;
Timeout: number;
};
export const [allHosts, setAllHosts] = createSignal<Host[]>([]);
export const [bkpHosts, setBkpHosts] = createSignal<Host[]>([]);
export const [ifaces, setIfaces] = createSignal<string[]>([]);
export const [appConfig, setAppConfig] = createSignal<Conf>();
export const [editNames, setEditNames] = createSignal(false);
// export const [store, setStore] = createStore({ hosts: allHosts() });
// export const [updBody, setUpdBody] = createSignal(false);
export const [currentHost, setCurrentHost] = createSignal<Host>(emptyHost);

View file

@ -0,0 +1,40 @@
import { allHosts, bkpHosts, Host, setAllHosts } from "./exports";
let oldFilter = 'ID';
export function filterAtStart() {
const field = localStorage.getItem("filterField") as keyof Host;
const value = localStorage.getItem("filterValue");
filterFunc(field, value);
}
export function filterFunc(field: keyof Host, value: any) {
let addrsArray = allHosts();
if (oldFilter == field) {
addrsArray = bkpHosts();
}
oldFilter = field;
localStorage.setItem("filterField", field);
localStorage.setItem("filterValue", value);
switch (field) {
case 'Iface':
addrsArray = addrsArray.filter((item) => item.Iface == value);
break;
case 'Known':
addrsArray = addrsArray.filter((item) => item.Known == value);
break;
case 'Now':
addrsArray = addrsArray.filter((item) => item.Now == value);
break;
default:
addrsArray = bkpHosts();
}
setAllHosts([]);
setAllHosts(addrsArray);
}

View file

@ -0,0 +1,37 @@
import { allHosts, bkpHosts, Host, setAllHosts } from "./exports";
export function searchFunc(s: string) {
if (s != "") {
const sl = s.toLowerCase();
let newArray:Host[] = [];
for (let item of allHosts()) {
if (searchItem(item, sl)) {
newArray.push(item);
}
}
setAllHosts([]);
setAllHosts(newArray);
} else {
setAllHosts([]);
setAllHosts(bkpHosts());
}
}
function searchItem(item:Host, sl:string) {
const name = item.Name.toLowerCase();
const hw = item.Hw.toLowerCase();
const mac = item.Mac.toLowerCase();
if ((name.includes(sl)) || (item.Iface.includes(sl)) || (item.IP.includes(sl)) || (mac.includes(sl)) || (hw.includes(sl)) || (item.Date.includes(sl))) {
return true;
} else {
return false;
}
}

View file

@ -1,7 +1,15 @@
import { allHosts, Host, setAllHosts } from "./exports";
let down = false;
let oldField = "Name";
let oldField = '';
export function sortAtStart() {
const field = localStorage.getItem("sortField") as keyof Host;
down = JSON.parse(localStorage.getItem("sortDown") as string);
down = !down;
sortByAnyField(field);
}
export function sortByAnyField(field: keyof Host) {
@ -13,8 +21,8 @@ export function sortByAnyField(field: keyof Host) {
down = !down;
}
// localStorage.setItem("sortDown", down);
// localStorage.setItem("sortField", field);
localStorage.setItem("sortDown", down.toString());
localStorage.setItem("sortField", field);
// checkNotEmpty(addrsArray);
let someArray = allHosts();

View file

@ -1,16 +1,17 @@
import { allHosts } from "../functions/exports";
import { For } from "solid-js";
import TableRow from "../components/TableRow";
import CardHead from "../components/CardHead";
import TableHead from "../components/TableHead";
import { allHosts } from "../functions/exports";
import TableRow from "../components/TableRow";
import TableHead from "../components/TableHead";
import BodyCardHead from "../components/BodyCardHead";
function Body() {
return (
<div class="card border-primary">
<div class="card-header">
<CardHead></CardHead>
<BodyCardHead></BodyCardHead>
</div>
<div class="card-body table-responsive">
<table class="table table-striped table-hover">

View file

@ -0,0 +1,30 @@
import { useParams } from "@solidjs/router";
import { apiGetHost } from "../functions/api";
import HostPageHost from "../components/HostPageHost";
import { setCurrentHost } from "../functions/exports";
import { onMount } from "solid-js";
import HostPagePing from "../components/HostPagePing";
function HostPage() {
onMount(async () => {
const params = useParams();
const host = await apiGetHost(params.id);
setCurrentHost(host);
});
return (
<div class="row">
<div class="col-md">
<HostPageHost></HostPageHost>
</div>
<div class="col-md">
<HostPagePing></HostPagePing>
</div>
</div>
)
}
export default HostPage