Added wake indicator. moved to new wake API endpoint. commented out some things. we dont need none of ts.
This commit is contained in:
@@ -26,6 +26,7 @@ pub async fn read_dhcp_leases_with_names() -> io::Result<Vec<DhcpLeaseLine>> {
|
|||||||
for mut l in leases {
|
for mut l in leases {
|
||||||
let mac_s = l.mac.to_string();
|
let mac_s = l.mac.to_string();
|
||||||
if let Some(ref name) = l.name {
|
if let Some(ref name) = l.name {
|
||||||
|
// if no name in cache file or it changed
|
||||||
if cache.get(&mac_s).map(|v| v != name).unwrap_or(true) {
|
if cache.get(&mac_s).map(|v| v != name).unwrap_or(true) {
|
||||||
cache.insert(mac_s, name.clone());
|
cache.insert(mac_s, name.clone());
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|||||||
+10
-9
@@ -1,16 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
arpparse::{IpNeighLine, NUDState},
|
arpparse::{NUDState},
|
||||||
utils::parse::{de_many, serialize_macs},
|
utils::parse::{de_many, serialize_macs},
|
||||||
};
|
};
|
||||||
use axum::{Json, http::StatusCode, response::IntoResponse};
|
use axum::{Json, http::StatusCode, response::IntoResponse};
|
||||||
use axum_extra::extract::Query;
|
use axum_extra::extract::Query;
|
||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
use serde::Serialize;
|
use serde::{Serialize, Deserialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Hash, serde::Deserialize, Serialize)]
|
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
||||||
pub struct DeviceQuery {
|
pub struct DeviceQuery {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
||||||
@@ -27,20 +27,20 @@ pub struct DeviceQuery {
|
|||||||
pub nud: Vec<NUDState>,
|
pub nud: Vec<NUDState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Hash, serde::Deserialize)]
|
#[derive(Debug, Default, Clone, Hash, Deserialize)]
|
||||||
pub struct NamePath {
|
pub struct NamePath {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Default, serde::Serialize)]
|
#[derive(Debug, Default, Serialize)]
|
||||||
pub struct Status {
|
pub struct Status<T> {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub table: Vec<IpNeighLine>,
|
pub table: Vec<T>,
|
||||||
pub filters: Filters,
|
pub filters: Filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, serde::Serialize)]
|
#[derive(Debug, Default, Serialize)]
|
||||||
pub struct Filters {
|
pub struct Filters {
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
pub ip: Vec<IpAddr>,
|
pub ip: Vec<IpAddr>,
|
||||||
@@ -56,7 +56,7 @@ pub struct Filters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, serde::Serialize, Default)]
|
#[derive(Debug, Serialize, Default)]
|
||||||
pub struct StatusError {
|
pub struct StatusError {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub error: String,
|
pub error: String,
|
||||||
@@ -103,6 +103,7 @@ pub async fn get_status_json(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// why try join all?
|
||||||
match futures::future::try_join_all(tasks)
|
match futures::future::try_join_all(tasks)
|
||||||
.await
|
.await
|
||||||
.map(|v| v.into_iter().flatten().collect::<Vec<_>>())
|
.map(|v| v.into_iter().flatten().collect::<Vec<_>>())
|
||||||
|
|||||||
+24
-2
@@ -2,6 +2,8 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
/* use crate::arpparse::IpNeighLine;
|
||||||
|
use crate::route::api::Status; */
|
||||||
use crate::utils::parse::mac::{des_opm, ser_opm};
|
use crate::utils::parse::mac::{des_opm, ser_opm};
|
||||||
use crate::utils::wake::wake_one;
|
use crate::utils::wake::wake_one;
|
||||||
use axum::{extract::Json, http::StatusCode, response::IntoResponse};
|
use axum::{extract::Json, http::StatusCode, response::IntoResponse};
|
||||||
@@ -26,7 +28,7 @@ pub struct WakeTargetResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone, Copy, Hash)]
|
#[derive(Debug, Serialize, Clone, Copy, Hash)]
|
||||||
#[serde(rename_all="snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum WakeTargetStatus {
|
pub enum WakeTargetStatus {
|
||||||
Succeed,
|
Succeed,
|
||||||
/// not a real address...
|
/// not a real address...
|
||||||
@@ -39,8 +41,9 @@ pub enum WakeTargetStatus {
|
|||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||||
pub struct WakeTarget {
|
pub struct WakeTarget {
|
||||||
|
#[serde(default)]
|
||||||
pub ip: Option<IpAddr>,
|
pub ip: Option<IpAddr>,
|
||||||
#[serde(deserialize_with = "des_opm")]
|
#[serde(default, deserialize_with = "des_opm")]
|
||||||
pub mac: Option<MacAddr>,
|
pub mac: Option<MacAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,3 +94,22 @@ pub async fn wake_multi_split(
|
|||||||
.await,
|
.await,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/* #[derive(Debug, Serialize)]
|
||||||
|
pub struct WakeStatusLine {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub status: IpNeighLine, // most powerful find of the century
|
||||||
|
pub wake_status: WakeTargetStatus,
|
||||||
|
}
|
||||||
|
pub type WakeStatus = Status<WakeStatusLine>; */
|
||||||
|
// /// return status BUT plus a indicator of i sent a wake.
|
||||||
|
// pub async fn wake_status(
|
||||||
|
// Query(DeviceQuery {
|
||||||
|
// name,
|
||||||
|
// ip,
|
||||||
|
// dev,
|
||||||
|
// nud,
|
||||||
|
// mac,
|
||||||
|
// ..
|
||||||
|
// }): Query<DeviceQuery>,
|
||||||
|
// ) -> impl IntoResponse {
|
||||||
|
// }
|
||||||
|
|||||||
+1
-1
@@ -154,7 +154,7 @@ pub mod mac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize a MacAddr as a string
|
/// Serialize a MacAddr as a string
|
||||||
pub fn serialize_mac<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
pub fn _serialize_mac<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
use crate::arpparse::NUDState;
|
use crate::arpparse::NUDState;
|
||||||
use crate::dhcpparse::DhcpLeaseLine;
|
use crate::dhcpparse::DhcpLeaseLine;
|
||||||
use crate::utils::parse::serialize_mac;
|
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
pub struct DhcpLeaseOut {
|
pub struct DhcpLeaseOut {
|
||||||
pub expires_epoch: u64,
|
#[serde(flatten)]
|
||||||
pub ip: IpAddr,
|
pub lease_line: DhcpLeaseLine,
|
||||||
#[serde(serialize_with = "serialize_mac")]
|
|
||||||
pub mac: macaddr::MacAddr,
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub nud_state: Option<NUDState>,
|
pub nud_state: Option<NUDState>,
|
||||||
pub rank: Option<u8>,
|
pub rank: Option<u8>,
|
||||||
}
|
}
|
||||||
@@ -37,13 +33,13 @@ pub async fn enrich_leases_with_nud_state(leases: Vec<DhcpLeaseLine>) -> Vec<Dhc
|
|||||||
}
|
}
|
||||||
leases
|
leases
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|l| DhcpLeaseOut {
|
.map(|lease_line| {
|
||||||
expires_epoch: l.expires_epoch,
|
let (nud_state, rank) = map.get(&lease_line.ip).copied().unzip();
|
||||||
ip: l.ip,
|
DhcpLeaseOut {
|
||||||
mac: l.mac,
|
lease_line,
|
||||||
name: l.name,
|
nud_state,
|
||||||
nud_state: map.get(&l.ip).map(|(s, _)| *s),
|
rank,
|
||||||
rank: map.get(&l.ip).map(|(_, r)| *r),
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-39
@@ -7,14 +7,14 @@ use macaddr::MacAddr;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
pub async fn get_ips(machine_name: &str) -> error::Result<Vec<IpAddr>> {
|
pub async fn get_ips(machine_name: &str) -> error::Result<impl Iterator<Item = IpAddr>> {
|
||||||
let it = tokio::net::lookup_host((machine_name, 0))
|
Ok(tokio::net::lookup_host((machine_name, 0))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| error::Error::DnsResolve {
|
.map_err(|e| error::Error::DnsResolve {
|
||||||
name: machine_name.to_string(),
|
name: machine_name.to_string(),
|
||||||
source: e,
|
source: e,
|
||||||
})?;
|
})?
|
||||||
Ok(it.map(|c| c.ip()).collect())
|
.map(|c| c.ip()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn _get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
|
pub async fn _get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
|
||||||
@@ -50,7 +50,7 @@ pub async fn get_macs_2_mac(machine_name: &str) -> Result<HashSet<MacAddr>> {
|
|||||||
pub async fn get_macs_1(machine_name: &str) -> Result<Vec<arpparse::IpNeighLine>> {
|
pub async fn get_macs_1(machine_name: &str) -> Result<Vec<arpparse::IpNeighLine>> {
|
||||||
let dev = "br-lan";
|
let dev = "br-lan";
|
||||||
let ips = get_ips(machine_name).await?;
|
let ips = get_ips(machine_name).await?;
|
||||||
let futures = ips.iter().map(|ip| {
|
let futures = ips.map(|ip| {
|
||||||
let ip = ip.to_canonical();
|
let ip = ip.to_canonical();
|
||||||
async move {
|
async move {
|
||||||
let cmd = "ip";
|
let cmd = "ip";
|
||||||
@@ -84,42 +84,10 @@ pub async fn get_macs(
|
|||||||
ips.map(|slice| slice.iter().copied().map(|ip| ip.to_canonical()).collect());
|
ips.map(|slice| slice.iter().copied().map(|ip| ip.to_canonical()).collect());
|
||||||
let ip_list = match (ip_list, machine_name) {
|
let ip_list = match (ip_list, machine_name) {
|
||||||
(Some(list), _) => list,
|
(Some(list), _) => list,
|
||||||
(None, Some(name)) => get_ips(name).await?.into_iter().collect(),
|
(None, Some(name)) => get_ips(name).await?.collect(),
|
||||||
(None, None) => Vec::new(),
|
(None, None) => Vec::new(),
|
||||||
};
|
};
|
||||||
let nud_arg = state.map(NUDState::as_ip_neigh_arg);
|
let run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, dev, state);
|
||||||
let run_one = |to_ip: Option<IpAddr>| async move {
|
|
||||||
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
|
||||||
if let Some(ip) = to_ip {
|
|
||||||
args.push("to".into());
|
|
||||||
args.push(ip.to_string());
|
|
||||||
}
|
|
||||||
if let Some(d) = dev {
|
|
||||||
args.push("dev".into());
|
|
||||||
args.push(d.to_string());
|
|
||||||
}
|
|
||||||
if let Some(nud) = nud_arg {
|
|
||||||
args.push("nud".into());
|
|
||||||
args.push(nud.to_string());
|
|
||||||
}
|
|
||||||
let o = exec_command("ip", args.iter().map(String::as_str).collect::<Vec<_>>()).await?;
|
|
||||||
if !o.status.success() {
|
|
||||||
return Err(Error::CommandFailed {
|
|
||||||
cmd: "ip",
|
|
||||||
args,
|
|
||||||
status: o.status.code(),
|
|
||||||
stderr: String::from_utf8_lossy(&o.stderr).into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let lines = String::from_utf8_lossy(&o.stdout);
|
|
||||||
let parsed = lines.lines().flat_map(arpparse::parse_ip_neigh_line);
|
|
||||||
let rows: Vec<IpNeighLine> = if let Some(d) = dev {
|
|
||||||
parsed.map(IpNeighLine::with_dev(d)).collect()
|
|
||||||
} else {
|
|
||||||
parsed.collect()
|
|
||||||
};
|
|
||||||
Ok::<Vec<IpNeighLine>, error::Error>(rows)
|
|
||||||
};
|
|
||||||
if !ip_list.is_empty() {
|
if !ip_list.is_empty() {
|
||||||
let futures = ip_list.into_iter().map(|ip| run_one(Some(ip)));
|
let futures = ip_list.into_iter().map(|ip| run_one(Some(ip)));
|
||||||
let res = futures::future::try_join_all(futures).await?;
|
let res = futures::future::try_join_all(futures).await?;
|
||||||
@@ -128,3 +96,76 @@ pub async fn get_macs(
|
|||||||
run_one(None).await
|
run_one(None).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the atomic get_macs. handle ONE thing only.
|
||||||
|
pub async fn get_mac(
|
||||||
|
ip: Option<IpAddr>,
|
||||||
|
dev: Option<&str>,
|
||||||
|
state: Option<NUDState>,
|
||||||
|
) -> error::Result<Vec<IpNeighLine>> {
|
||||||
|
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
||||||
|
if let Some(ip) = ip {
|
||||||
|
args.push("to".into());
|
||||||
|
args.push(ip.to_string());
|
||||||
|
}
|
||||||
|
if let Some(d) = dev {
|
||||||
|
args.push("dev".into());
|
||||||
|
args.push(d.to_string());
|
||||||
|
}
|
||||||
|
if let Some(nud) = state {
|
||||||
|
args.push("nud".into());
|
||||||
|
args.push(nud.as_ip_neigh_arg().into());
|
||||||
|
}
|
||||||
|
let cmd = "ip";
|
||||||
|
let out = exec_command(cmd, args.iter().map(String::as_str).collect::<Vec<_>>()).await?;
|
||||||
|
if !out.status.success() {
|
||||||
|
return Err(error::Error::CommandFailed {
|
||||||
|
cmd,
|
||||||
|
args,
|
||||||
|
status: out.status.code(),
|
||||||
|
stderr: String::from_utf8_lossy(&out.stderr).into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let lines = String::from_utf8_lossy(&out.stdout);
|
||||||
|
let parsed = lines.lines().flat_map(arpparse::parse_ip_neigh_line);
|
||||||
|
let rows: Vec<IpNeighLine> = if let Some(d) = dev {
|
||||||
|
parsed.map(IpNeighLine::with_dev(d)).collect()
|
||||||
|
} else {
|
||||||
|
parsed.collect()
|
||||||
|
};
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pub async fn _get_macs_good(
|
||||||
|
machine_names: Option<&str>,
|
||||||
|
ips: Option<&[IpAddr]>,
|
||||||
|
devs: Option<&[&str]>,
|
||||||
|
states: Option<&[NUDState]>,
|
||||||
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
|
let mut ip_map: HashSet<IpAddr> = HashSet::new();
|
||||||
|
let mut machine_map = HashSet::new();
|
||||||
|
if let Some(ips) = ips {
|
||||||
|
ip_map.extend(ips);
|
||||||
|
}
|
||||||
|
if let Some(m) = machine_names {
|
||||||
|
machine_map.extend(get_ips(m).await.into_iter().flatten());
|
||||||
|
}
|
||||||
|
|
||||||
|
let real = match (ip_map.is_empty(), machine_map.is_empty()) {
|
||||||
|
(true, true) => HashSet::new(),
|
||||||
|
(true, false) => machine_map,
|
||||||
|
(false, true) => ip_map,
|
||||||
|
(false, false) => ip_map.intersection(&machine_map).copied().collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn _get_machines(m: &[&str]) -> Vec<IpAddr> {
|
||||||
|
let futs = m.iter().map(|c| get_ips(c));
|
||||||
|
futures::future::join_all(futs)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|f| f.into_iter().flatten())
|
||||||
|
.collect()
|
||||||
|
} */
|
||||||
|
|||||||
+3
-1
@@ -74,7 +74,7 @@ impl WakeTargetResult {
|
|||||||
pub async fn _wake_multi(
|
pub async fn _wake_multi(
|
||||||
targets: impl IntoIterator<Item = WakeTarget>,
|
targets: impl IntoIterator<Item = WakeTarget>,
|
||||||
) -> io::Result<Vec<WakeTargetResult>> {
|
) -> io::Result<Vec<WakeTargetResult>> {
|
||||||
let sock = UdpSocket::bind("0.0.0.0:0").await?;
|
let sock = UdpSocket::bind(":0").await?;
|
||||||
sock.set_broadcast(true)?;
|
sock.set_broadcast(true)?;
|
||||||
let fs = targets.into_iter().map(|t| wake_one(&sock, t));
|
let fs = targets.into_iter().map(|t| wake_one(&sock, t));
|
||||||
Ok(futures::future::join_all(fs).await)
|
Ok(futures::future::join_all(fs).await)
|
||||||
@@ -96,3 +96,5 @@ pub async fn wake_one(sock: &UdpSocket, t: WakeTarget) -> WakeTargetResult {
|
|||||||
Err(_) => t.errored(),
|
Err(_) => t.errored(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub async fn wake_query();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const qs = new URLSearchParams(location.search);
|
export const qs = new URLSearchParams(location.search);
|
||||||
|
|
||||||
|
// not real cuh
|
||||||
export const $ = (id) => document.getElementById(id);
|
export const $ = (id) => document.getElementById(id);
|
||||||
export const elName = $("name");
|
export const elName = $("name");
|
||||||
export const elCheck = $("check");
|
export const elCheck = $("check");
|
||||||
|
|||||||
+11
-6
@@ -8,7 +8,12 @@ export function renderLeases(leases) {
|
|||||||
}
|
}
|
||||||
const tbl = document.createElement("table");
|
const tbl = document.createElement("table");
|
||||||
tbl.className = "table";
|
tbl.className = "table";
|
||||||
tbl.innerHTML = `<tr><th></th><th>IP</th><th>MAC</th><th>Name</th><th>Expires</th></tr>`;
|
|
||||||
|
(
|
||||||
|
tbl.tHead || tbl.createTHead()
|
||||||
|
).innerHTML = `<tr><th></th><th>IP</th><th>MAC</th><th>Name</th><th>Expires</th></tr>`;
|
||||||
|
|
||||||
|
const tbd = tbl.tBodies.item(0) || tbl.createTBody();
|
||||||
const nowSec = Math.floor(Date.now() / 1000);
|
const nowSec = Math.floor(Date.now() / 1000);
|
||||||
for (const l of leases) {
|
for (const l of leases) {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
@@ -34,17 +39,17 @@ export function renderLeases(leases) {
|
|||||||
const name = l.name || "";
|
const name = l.name || "";
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td><span class="${dotClass}" title="${title}"></span></td>
|
<td><span class="${dotClass}" title="${title}"></span></td>
|
||||||
${[ip, mac, name]
|
${Object.entries({ ip, mac, name })
|
||||||
.map(
|
.map(
|
||||||
(f) =>
|
([name, value]) =>
|
||||||
`<td>${
|
`<td>${
|
||||||
f &&
|
value &&
|
||||||
`<a href="#" class="pick" data-value="${f}" title="filter by ip">${f}</a>`
|
`<a href="#" class="pick" data-value="${value}" title="filter by ${name}">${value}</a>`
|
||||||
}</td>`
|
}</td>`
|
||||||
)
|
)
|
||||||
.join("\n")}
|
.join("\n")}
|
||||||
<td><span class="tiny">${whenText}</span></td>`;
|
<td><span class="tiny">${whenText}</span></td>`;
|
||||||
tbl.appendChild(tr);
|
tbd.appendChild(tr);
|
||||||
}
|
}
|
||||||
elLeases.innerHTML = "";
|
elLeases.innerHTML = "";
|
||||||
elLeases.appendChild(tbl);
|
elLeases.appendChild(tbl);
|
||||||
|
|||||||
+36
-16
@@ -1,5 +1,6 @@
|
|||||||
import { elHtml, elLog, setPill, qs } from "./dom.js";
|
import { elHtml, elLog, setPill, qs, pill } from "./dom.js";
|
||||||
import { rankState } from "./utils.js";
|
import { rankState } from "./utils.js";
|
||||||
|
import { merge_wake_data, translate_wake_message } from "./wake.js";
|
||||||
|
|
||||||
const status_map = {
|
const status_map = {
|
||||||
ip: "ip",
|
ip: "ip",
|
||||||
@@ -11,7 +12,7 @@ const status_map = {
|
|||||||
const filter_array = ["ip", "dev", "nud", "mac"];
|
const filter_array = ["ip", "dev", "nud", "mac"];
|
||||||
|
|
||||||
function buildStatusUrl(name) {
|
function buildStatusUrl(name) {
|
||||||
const hasExtraFilters = f_array.some((k) => qs.getAll(k).length);
|
const hasExtraFilters = filter_array.some((k) => qs.getAll(k).length);
|
||||||
if (name && !hasExtraFilters) {
|
if (name && !hasExtraFilters) {
|
||||||
return new URL(`/api/smart/${encodeURIComponent(name)}`, location.origin);
|
return new URL(`/api/smart/${encodeURIComponent(name)}`, location.origin);
|
||||||
}
|
}
|
||||||
@@ -27,21 +28,33 @@ function buildStatusUrl(name) {
|
|||||||
export function renderStatus(data) {
|
export function renderStatus(data) {
|
||||||
const tbl = document.createElement("table");
|
const tbl = document.createElement("table");
|
||||||
tbl.className = "table";
|
tbl.className = "table";
|
||||||
tbl.innerHTML = `<tr><th>IP</th><th>MAC</th><th>State</th><th>IF</th></tr>`;
|
tbl.innerHTML = `<thead><tr>${
|
||||||
|
data.has_wake ? "<th>Wake status</th>" : ""
|
||||||
|
}<th>IP</th><th>MAC</th><th>State</th><th>IF</th></tr></thead>`;
|
||||||
|
|
||||||
for (const row of data.table || []) {
|
// sum hax
|
||||||
|
const tbd = tbl.tBodies.item(0) || tbl.createTBody();
|
||||||
|
for (const row of data.table) {
|
||||||
|
if (data.has_wake && !row.wake_status)
|
||||||
|
throw TypeError("specified has wake but no wake stats");
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.innerHTML = Object.entries(status_map)
|
tr.innerHTML = `${
|
||||||
.map(
|
row.wake_status
|
||||||
(field, description) =>
|
? `<td><span class="dot ${
|
||||||
`<td>${
|
row.wake_status == "succeed" ? "ok" : "bad"
|
||||||
row[field]
|
}" title="${translate_wake_message(row.wake_status)}"></span></td>`
|
||||||
? `<a href="#" class="pick" data-value="${row[field]}" title="filter by ${description}">${row[field]}</a>`
|
|
||||||
: ""
|
: ""
|
||||||
}</td>`
|
}${Object.entries(status_map)
|
||||||
)
|
.map(([field, description]) => {
|
||||||
.join();
|
const value = row[field];
|
||||||
tbl.appendChild(tr);
|
return `<td>${
|
||||||
|
value
|
||||||
|
? `<a href="#" class="pick" data-value="${value}" title="filter by ${description}">${value}</a>`
|
||||||
|
: ""
|
||||||
|
}</td>`;
|
||||||
|
})
|
||||||
|
.join("")}`;
|
||||||
|
tbd.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
elHtml.innerHTML = "";
|
elHtml.innerHTML = "";
|
||||||
@@ -69,12 +82,13 @@ export function renderStatus(data) {
|
|||||||
if (r >= 5) setPill("ok", "online");
|
if (r >= 5) setPill("ok", "online");
|
||||||
else if (r >= 2) setPill("warn", "maybe");
|
else if (r >= 2) setPill("warn", "maybe");
|
||||||
else setPill("bad", "offline");
|
else setPill("bad", "offline");
|
||||||
|
if (data.filters.nud?.length > 0) pill.textContent += " (filtered)";
|
||||||
} else {
|
} else {
|
||||||
setPill("warn", "unknown");
|
setPill("warn", "unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchStatus(name) {
|
export async function fetchStatus(name, render = true, data_wake) {
|
||||||
setPill("warn", "checking…");
|
setPill("warn", "checking…");
|
||||||
const u = buildStatusUrl(name);
|
const u = buildStatusUrl(name);
|
||||||
elLog.textContent = "GET " + u.pathname + u.search;
|
elLog.textContent = "GET " + u.pathname + u.search;
|
||||||
@@ -93,7 +107,13 @@ export async function fetchStatus(name) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
renderStatus(data);
|
|
||||||
|
if (data_wake) {
|
||||||
|
data.table = merge_wake_data(data.table, data_wake);
|
||||||
|
data.has_wake = true;
|
||||||
|
}
|
||||||
|
if (render) renderStatus(data);
|
||||||
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
elLog.textContent = "status error: " + e;
|
elLog.textContent = "status error: " + e;
|
||||||
setPill("bad", "error");
|
setPill("bad", "error");
|
||||||
|
|||||||
+99
-10
@@ -1,18 +1,107 @@
|
|||||||
import { elLog, setPill } from "./dom.js";
|
import { elHtml, elLog, setPill } from "./dom.js";
|
||||||
import { fetchStatus } from "./status.js";
|
import { fetchStatus, renderStatus } from "./status.js";
|
||||||
|
|
||||||
export async function sendWake(name) {
|
export async function sendWake(name) {
|
||||||
setPill("warn", "waking…");
|
const data = await fetchStatus(name, false);
|
||||||
elLog.textContent = "POST /wake?name=" + name;
|
if (!data) return; // can not proceed; theres nothing.
|
||||||
try {
|
const wake_targets = data.table.map(({ ip, mac }) => {
|
||||||
const r = await fetch(`/wake?name=${encodeURIComponent(name)}`, {
|
return { ip, mac };
|
||||||
method: "POST",
|
|
||||||
});
|
});
|
||||||
const t = await r.text();
|
setPill("warn", "waking…");
|
||||||
elLog.textContent = t || "ok";
|
elLog.textContent = "POST /api/wake";
|
||||||
setTimeout(() => fetchStatus(name), 800);
|
try {
|
||||||
|
const r = await fetch(`/api/wake`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(wake_targets),
|
||||||
|
});
|
||||||
|
if (!r.ok) {
|
||||||
|
let msg = String(r.status);
|
||||||
|
try {
|
||||||
|
const err = await r.clone().json();
|
||||||
|
msg = err.error || JSON.stringify(err);
|
||||||
|
} catch {
|
||||||
|
msg = await r.text();
|
||||||
|
}
|
||||||
|
elLog.textContent = `wake error: ${msg}`;
|
||||||
|
setPill("bad", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const j = await r.json();
|
||||||
|
|
||||||
|
if (!j.success) {
|
||||||
|
elLog.textContent = `wake error: ${j.error}`;
|
||||||
|
setPill("bad", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = j.result;
|
||||||
|
data.table = merge_wake_data(data.table, result);
|
||||||
|
data.has_wake = true;
|
||||||
|
|
||||||
|
renderStatus(data);
|
||||||
|
|
||||||
|
setTimeout(() => fetchStatus(name, undefined, result), 2000); // long ass timeout
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
elLog.textContent = "wake error: " + e;
|
elLog.textContent = "wake error: " + e;
|
||||||
setPill("bad", "error");
|
setPill("bad", "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Array} table
|
||||||
|
* @param {Array} wake
|
||||||
|
*/
|
||||||
|
export function merge_wake_data(table, wake) {
|
||||||
|
if (!wake) return table;
|
||||||
|
if (wake.length != table.length)
|
||||||
|
throw TypeError(
|
||||||
|
"wake status table and status table not of the same length"
|
||||||
|
);
|
||||||
|
const return_array = [];
|
||||||
|
let linear_failed = false;
|
||||||
|
for (const [index, entry] of table.entries()) {
|
||||||
|
if (wake[index].ip != entry.ip || wake[index].mac != entry.mac) {
|
||||||
|
linear_failed = true;
|
||||||
|
break;
|
||||||
|
} // use alternative method
|
||||||
|
|
||||||
|
return_array.push({ wake_status: wake[index].status, ...entry });
|
||||||
|
}
|
||||||
|
|
||||||
|
// never happening AHH
|
||||||
|
if (linear_failed) {
|
||||||
|
return_array = [];
|
||||||
|
const wake_map = new Map();
|
||||||
|
wake.forEach(({ ip, mac, status }) => {
|
||||||
|
wake_map.set(JSON.stringify({ ip, mac }), status);
|
||||||
|
});
|
||||||
|
return_array = table.map((entry) => {
|
||||||
|
const { ip, mac } = entry;
|
||||||
|
return {
|
||||||
|
wake_status: wake_map.get(JSON.stringify({ ip, mac })),
|
||||||
|
...entry,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//
|
||||||
|
return return_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function translate_wake_message(wake_msg) {
|
||||||
|
switch (wake_msg) {
|
||||||
|
case "incomplete":
|
||||||
|
return "Incomplete address (both ip and MAC required)"
|
||||||
|
case "succeed":
|
||||||
|
return "Wake request sent successfully"
|
||||||
|
case "nonexistent_address":
|
||||||
|
return "Errored pinging this address (nonexistent address)"
|
||||||
|
case "wrong_size":
|
||||||
|
return "Wake request malformed"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user