dumb shit
This commit is contained in:
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
[build]
|
|
||||||
target = "armv7-unknown-linux-musleabihf"
|
|
||||||
|
|
||||||
[target.armv7-unknown-linux-musleabihf]
|
[target.armv7-unknown-linux-musleabihf]
|
||||||
linker = "rust-lld"
|
linker = "rust-lld"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "armv7-unknown-linux-musleabihf"
|
||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
ldabr = "b -r --target=target.armv7-unknown-linux-musleabihf"
|
ldabr = "b -r --target=target.armv7-unknown-linux-musleabihf"
|
||||||
t = "test -- --nocapture --test-threads=1"
|
t = "test -- --nocapture --test-threads=1"
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.cargo.target": "armv7-unknown-linux-musleabihf",
|
"rust-analyzer.cargo.target": "armv7-unknown-linux-musleabihf",
|
||||||
"rust-analyzer.diagnostics.disabled": ["unlinked-file"]
|
// "rust-analyzer.diagnostics.disabled": ["unlinked-file"]
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+432
-228
File diff suppressed because it is too large
Load Diff
+23
-14
@@ -1,28 +1,31 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wakey"
|
name = "wakey"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = ["gitea"]
|
publish = ["gitea"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.4", features = ["macros"] }
|
anyhow = "1"
|
||||||
axum-extra = { version = "0.10.1", features = ["query"] }
|
axum = { version = "0", features = ["macros"] }
|
||||||
color-eyre = "0.6.5"
|
axum-extra = { version = "0", features = ["query"] }
|
||||||
futures = "0.3.31"
|
color-eyre = "0"
|
||||||
macaddr = { version = "1.0.1", features = ["serde", "serde_std"] }
|
futures = "0"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
macaddr = { version = "1", features = ["serde", "serde_std"] }
|
||||||
serde_html_form = "0.2.7"
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1.0.143"
|
serde_html_form = "0"
|
||||||
serde_with = { version = "3.14.0", features = ["json"] }
|
serde_json = "1"
|
||||||
strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
|
serde_with = { version = "3", features = ["json"] }
|
||||||
thiserror = "2.0.16"
|
strum = { version = "0", features = ["derive", "strum_macros"] }
|
||||||
tokio = { version = "1.47.1", features = [
|
thiserror = "2"
|
||||||
|
tokio = { version = "1", features = [
|
||||||
"fs",
|
"fs",
|
||||||
"process",
|
"process",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"io-util",
|
"io-util",
|
||||||
|
"macros",
|
||||||
] }
|
] }
|
||||||
urlencoding = "2.1.3"
|
tower-http = { version = "0", features = ["fs"] }
|
||||||
|
urlencoding = "2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
@@ -32,3 +35,9 @@ strip = true
|
|||||||
default = ["very-smart-parsing"]
|
default = ["very-smart-parsing"]
|
||||||
very-smart-parsing = [
|
very-smart-parsing = [
|
||||||
] # this is the a-bit-redundant parse thing that copilot made
|
] # this is the a-bit-redundant parse thing that copilot made
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["ipjs"]
|
||||||
|
|
||||||
|
[dependencies.lda-ipjs]
|
||||||
|
path = "ipjs"
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "lda-ipjs"
|
||||||
|
description = "ip -j show schemas"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
macaddr = { version = "1", features = ["serde", "serde_std"] }
|
||||||
|
strum = { version = "0", features = ["derive", "strum_macros"] }
|
||||||
|
serde_json = "1"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_with = { version = "3", features = ["json"] }
|
||||||
|
thiserror = "2"
|
||||||
|
anyhow = "1"
|
||||||
|
tokio = { version = "1", features = ["fs", "process", "rt-multi-thread", "io-util", "macros"] }
|
||||||
|
rtnetlink = "0"
|
||||||
|
futures = "0"
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
trait IpCommand {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//! low
|
||||||
|
//!
|
||||||
|
//! # lda-ipjs
|
||||||
|
//!
|
||||||
|
//! this package will represent all my needs with the all the subcommands of ip -j.
|
||||||
|
//!
|
||||||
|
//! ## what i need
|
||||||
|
//!
|
||||||
|
//! ```console
|
||||||
|
//! ip -j neigh show
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! i also need to see devices and idk MAYBE maybe not MAYBE UHHHHHH maybe broadcast
|
||||||
|
//!
|
||||||
|
//! LOWK if this were to be calls to kernel or some bullshit then PLEASE because doing ts parsing its hell cuh
|
||||||
|
|
||||||
|
pub mod subcommands;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
// i want a generalized way to build and call
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// ip address [ show [ dev IFNAME ] [ scope SCOPE-ID ] [ master DEVICE ]
|
||||||
|
// [ type TYPE ] [ to PREFIX ] [ FLAG-LIST ]
|
||||||
|
// [ label LABEL ] [up] [ vrf NAME ] ]
|
||||||
|
// fuck is this mean
|
||||||
|
|
||||||
|
// do i need all this? do i need anything but `ip -j a show dev br-lan`?
|
||||||
|
|
||||||
|
// TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap |
|
||||||
|
// bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | lowpan |
|
||||||
|
// gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan | vti |
|
||||||
|
// nlmon | can | bond_slave | ipvlan | geneve | bridge_slave |
|
||||||
|
// hsr | macsec | netdevsim }
|
||||||
|
// FLAG-LIST := [ FLAG-LIST ] FLAG
|
||||||
|
// FLAG := [ permanent | dynamic | secondary | primary |
|
||||||
|
// [-]tentative | [-]deprecated | [-]dadfailed | temporary |
|
||||||
|
// CONFFLAG-LIST ]
|
||||||
|
// CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG
|
||||||
|
// CONFFLAG := [ home | nodad | mngtmpaddr | noprefixroute | autojoin ]
|
||||||
|
|
||||||
|
// prefix seems to be a cidr. both 6 and 4 works. idfk dog
|
||||||
|
|
||||||
|
use std::{io, process::Output};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
use super::AddrOutput;
|
||||||
|
|
||||||
|
pub async fn get(dev: Option<&str>) -> anyhow::Result<Vec<AddrOutput>> {
|
||||||
|
let output = _get(dev).await.context("Can not run command")?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
anyhow::bail!(String::from_utf8_lossy(&output.stderr).into_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_slice(&output.stdout).context("Deserialize failed")
|
||||||
|
}
|
||||||
|
pub async fn _get(dev: Option<&str>) -> io::Result<Output> {
|
||||||
|
let mut cmd = tokio::process::Command::new("ip");
|
||||||
|
cmd.args(["-j", "address", "show"]);
|
||||||
|
|
||||||
|
if let Some(d) = dev {
|
||||||
|
cmd.args(["dev", d]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.output().await
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//! ts
|
||||||
|
//!
|
||||||
|
//! deals with both ip a (addroutput) and ip l (commonoutput)
|
||||||
|
//!
|
||||||
|
//! lowk why its free but its indirection and its ass
|
||||||
|
|
||||||
|
pub mod json;
|
||||||
|
pub mod nl;
|
||||||
|
|
||||||
|
use crate::utils::serialize::mac::option_mac;
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// i dont include what i dont know about (almost all ts)
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
pub struct AddrOutput {
|
||||||
|
pub ifindex: u32,
|
||||||
|
pub ifname: String,
|
||||||
|
/// i imagine UP or DOWN, unknown
|
||||||
|
pub operstate: String,
|
||||||
|
// 6 has a serde and the enum doesnt? why. (serializing ts is ass although... im not given an array. they string formatted ts)
|
||||||
|
#[serde(with = "option_mac", default)]
|
||||||
|
pub address: Option<MacAddr>,
|
||||||
|
#[serde(default)] // i wish we have intellisense for this... fuck you metaprogramming
|
||||||
|
pub addr_info: Vec<AddrInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// i be copying
|
||||||
|
// Raw JSON shape from ip -j -4 address show
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct AddrInfo {
|
||||||
|
pub family: Option<String>,
|
||||||
|
|
||||||
|
pub local: Option<String>,
|
||||||
|
pub prefixlen: Option<u8>,
|
||||||
|
|
||||||
|
pub broadcast: Option<String>,
|
||||||
|
|
||||||
|
pub scope: Option<String>,
|
||||||
|
pub label: Option<String>,
|
||||||
|
// many more exist; we only take what we need
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//! i said i aint doing ts no more why am i still here
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
|
use crate::subcommands::address::AddrOutput;
|
||||||
|
|
||||||
|
// shit this one is even worse you needa collect info from two places
|
||||||
|
pub async fn get(dev: Option<&str>) -> anyhow::Result<Vec<AddrOutput>> {
|
||||||
|
let (conn, handle, _) = rtnetlink::new_connection()?;
|
||||||
|
tokio::spawn(conn); // every time?
|
||||||
|
let mut address = handle.address().get();
|
||||||
|
let mut link = handle.link().get();
|
||||||
|
if let Some(dev) = dev {
|
||||||
|
link = link.match_name(dev.to_owned());
|
||||||
|
if let Some(ind) = link.execute().try_next().await?.map(|a| a.header.index) {
|
||||||
|
address = address.set_link_index_filter(ind);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
address.execute().try_next().await?;
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod address;
|
||||||
|
pub mod neighbor;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
//! idk what to put here
|
||||||
|
|
||||||
|
use std::{io, net::IpAddr, process::Output};
|
||||||
|
|
||||||
|
use anyhow::{Context, bail};
|
||||||
|
|
||||||
|
use super::{NUDState, NeighborItem};
|
||||||
|
|
||||||
|
// loose translation of [wakey::utils::query::macs::get_mac]
|
||||||
|
// i think ill write tokio::process every time tho (for this if let thing) because iterate through all ts youll have to as str and all the hooplas.
|
||||||
|
// it all turns to live osstr tho so ts just for my own sanity
|
||||||
|
// thiserror? anyhow
|
||||||
|
|
||||||
|
// what is vro sayin
|
||||||
|
// ahh. instead of using [wakey::utils::cmd::exec_command] which is ass we jus write everything out. so i dont have to .as_str() so often.
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
ip: Option<IpAddr>,
|
||||||
|
dev: Option<&str>,
|
||||||
|
nud: &[NUDState],
|
||||||
|
) -> anyhow::Result<Vec<NeighborItem>> {
|
||||||
|
let output = _get(ip, dev, nud).await.context("Can not run command")?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
bail!(String::from_utf8_lossy(&output.stderr).into_owned())
|
||||||
|
} else {
|
||||||
|
let mut fuckass: Vec<NeighborItem> =
|
||||||
|
serde_json::from_slice(&output.stdout).context("Deserialize failed")?;
|
||||||
|
|
||||||
|
// i hate ts.
|
||||||
|
if let Some(dev) = dev {
|
||||||
|
for item in &mut fuckass {
|
||||||
|
if item.dev.is_none() {
|
||||||
|
item.dev = Some(dev.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(fuckass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn _get(ip: Option<IpAddr>, dev: Option<&str>, nud: &[NUDState]) -> io::Result<Output> {
|
||||||
|
let mut cmd = tokio::process::Command::new("ip");
|
||||||
|
cmd.args(["-j", "neigh", "show"]);
|
||||||
|
|
||||||
|
if let Some(ip) = ip {
|
||||||
|
// cmd.arg("to");
|
||||||
|
cmd.arg(ip.to_canonical().to_string());
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(dev) = dev {
|
||||||
|
cmd.args(["dev", dev]);
|
||||||
|
}
|
||||||
|
for nud in nud {
|
||||||
|
cmd.arg("nud");
|
||||||
|
cmd.arg(nud.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.output().await
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
//! ```bash
|
||||||
|
//! ip -j n s
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! yes. this is a real call.
|
||||||
|
|
||||||
|
pub mod json;
|
||||||
|
pub mod nl;
|
||||||
|
|
||||||
|
use crate::utils::serialize::mac::option_mac;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct NeighborInput {
|
||||||
|
/// supports only the last item (ignore), `to` keyword is optional
|
||||||
|
pub to: Option<IpAddr>,
|
||||||
|
/// supports only one item (it complains if multiple)
|
||||||
|
pub dev: Option<String>, // im all for simplicity
|
||||||
|
/// takes multiple, has to have `nud` before bro or it will think you `to`
|
||||||
|
pub nud: Vec<NUDState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// as input this must be lowercase. as output it is uppercase
|
||||||
|
/// docs for items come from a random ahh man website idk
|
||||||
|
#[derive(
|
||||||
|
Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, Serialize, Deserialize, Default,
|
||||||
|
)]
|
||||||
|
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
|
||||||
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
|
pub enum NUDState {
|
||||||
|
/// the neighbour entry is valid forever and can
|
||||||
|
/// be only be removed administratively.
|
||||||
|
Permanent,
|
||||||
|
/// the neighbour entry is valid. No attempts to
|
||||||
|
/// validate this entry will be made but it can
|
||||||
|
/// be removed when its lifetime expires.
|
||||||
|
Noarp,
|
||||||
|
/// the neighbour entry is valid until the
|
||||||
|
/// reachability timeout expires.
|
||||||
|
Reachable,
|
||||||
|
/// the neighbour entry is valid but suspicious.
|
||||||
|
/// This option to ip neigh does not change the
|
||||||
|
/// neighbour state if it was valid and the
|
||||||
|
/// address is not changed by this command.
|
||||||
|
Stale,
|
||||||
|
/// this is a pseudo state used when initially
|
||||||
|
/// creating a neighbour entry or after trying to
|
||||||
|
/// remove it before it becomes free to do so.
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// the neighbour entry has not (yet) been
|
||||||
|
/// validated/resolved.
|
||||||
|
Incomplete,
|
||||||
|
/// neighbor entry validation is currently
|
||||||
|
/// delayed.
|
||||||
|
Delay,
|
||||||
|
/// neighbor is being probed.
|
||||||
|
Probe,
|
||||||
|
/// max number of probes exceeded without
|
||||||
|
/// success, neighbor validation has ultimately
|
||||||
|
/// failed.
|
||||||
|
Failed,
|
||||||
|
|
||||||
|
Other(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// everything i see
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct NeighborItem {
|
||||||
|
#[serde(rename(deserialize = "dst"))]
|
||||||
|
pub ip: IpAddr,
|
||||||
|
#[serde(default)]
|
||||||
|
pub dev: Option<String>,
|
||||||
|
#[serde(with = "option_mac", default, rename(deserialize = "lladdr"))]
|
||||||
|
pub mac: Option<MacAddr>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub state: Vec<NUDState>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
//! this is purely experimental. im not doing ts no mo
|
||||||
|
|
||||||
|
// hallo
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
net::IpAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
use rtnetlink::packet_route::{
|
||||||
|
AddressFamily,
|
||||||
|
link::LinkAttribute,
|
||||||
|
neighbour::{NeighbourAddress, NeighbourAttribute, NeighbourState},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{NUDState, NeighborItem};
|
||||||
|
|
||||||
|
// dont you love https://github.com/rust-netlink/rtnetlink/blob/main/examples/get_neighbours.rs
|
||||||
|
// NeighborItem.state guarantees to be a single thing.
|
||||||
|
pub async fn get(
|
||||||
|
ip: Option<IpAddr>,
|
||||||
|
dev: Option<&str>,
|
||||||
|
nud: &[NUDState],
|
||||||
|
) -> anyhow::Result<Vec<NeighborItem>> {
|
||||||
|
let (gip, gdev, gnud) = (ip, dev, nud);
|
||||||
|
let (conn, handle, _) = rtnetlink::new_connection()?;
|
||||||
|
tokio::spawn(conn); // every time?
|
||||||
|
let mut neighbor_data = handle.neighbours().get().execute();
|
||||||
|
let nudset: HashSet<&NUDState> = HashSet::from_iter(gnud);
|
||||||
|
|
||||||
|
// map ifindex to name
|
||||||
|
let mut ball: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut result = vec![];
|
||||||
|
'big: while let Some(neighbour_message_item) = neighbor_data.try_next().await? {
|
||||||
|
// Filter by address family
|
||||||
|
if !matches!(
|
||||||
|
neighbour_message_item.header.family,
|
||||||
|
AddressFamily::Inet | AddressFamily::Inet6
|
||||||
|
) || matches!(neighbour_message_item.header.state, NeighbourState::Noarp)
|
||||||
|
// copilot says this to match ip -j n s
|
||||||
|
{
|
||||||
|
continue 'big;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = vec![
|
||||||
|
neighbour_message_item
|
||||||
|
.header
|
||||||
|
.state
|
||||||
|
.try_into()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
]; // ONE ITEM. why tf ts design json.
|
||||||
|
let mut ip = None;
|
||||||
|
let mut mac = None;
|
||||||
|
|
||||||
|
for neigh_attr in neighbour_message_item.attributes {
|
||||||
|
match neigh_attr {
|
||||||
|
NeighbourAttribute::Destination(neighbour_address) => match neighbour_address {
|
||||||
|
NeighbourAddress::Inet(ipv4_addr) => ip = Some(ipv4_addr.into()),
|
||||||
|
NeighbourAddress::Inet6(ipv6_addr) => ip = Some(ipv6_addr.into()),
|
||||||
|
_ => continue 'big,
|
||||||
|
},
|
||||||
|
NeighbourAttribute::LinkLocalAddress(items) => {
|
||||||
|
mac = match items.len() {
|
||||||
|
6 => items.first_chunk::<6>().map(|&e| MacAddr::from(e)),
|
||||||
|
8 => items.first_chunk::<8>().map(|&e| MacAddr::from(e)),
|
||||||
|
_ => continue 'big,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exquisite
|
||||||
|
let dev = if let Some(cached) = ball.get(&neighbour_message_item.header.ifindex) {
|
||||||
|
Some(cached.clone())
|
||||||
|
} else {
|
||||||
|
// Query and cache
|
||||||
|
let name = handle
|
||||||
|
.link()
|
||||||
|
.get()
|
||||||
|
.match_index(neighbour_message_item.header.ifindex)
|
||||||
|
.execute()
|
||||||
|
.try_next()
|
||||||
|
.await?
|
||||||
|
.and_then(|a| {
|
||||||
|
a.attributes.into_iter().find_map(|attr| match attr {
|
||||||
|
LinkAttribute::IfName(name) => Some(name),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(ref n) = name {
|
||||||
|
ball.insert(neighbour_message_item.header.ifindex, n.clone());
|
||||||
|
}
|
||||||
|
name
|
||||||
|
};
|
||||||
|
|
||||||
|
let (Some(ip), Some(dev)) = (ip, dev) else {
|
||||||
|
continue 'big;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
// low block
|
||||||
|
if let Some(fip) = gip
|
||||||
|
&& fip != ip
|
||||||
|
{
|
||||||
|
continue 'big;
|
||||||
|
}
|
||||||
|
if let Some(fdev) = gdev
|
||||||
|
&& dev != fdev
|
||||||
|
{
|
||||||
|
continue 'big;
|
||||||
|
}
|
||||||
|
if !nudset.is_empty() && !nudset.contains(&state[0]) {
|
||||||
|
continue 'big;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(NeighborItem {
|
||||||
|
ip,
|
||||||
|
dev: Some(dev),
|
||||||
|
mac,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(result) // now i need another pass to filter out the uh.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<NeighbourState> for NUDState {
|
||||||
|
fn try_from(value: NeighbourState) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
NeighbourState::Incomplete => Ok(Self::Incomplete),
|
||||||
|
NeighbourState::Reachable => Ok(Self::Reachable),
|
||||||
|
NeighbourState::Stale => Ok(Self::Stale),
|
||||||
|
NeighbourState::Delay => Ok(Self::Delay),
|
||||||
|
NeighbourState::Probe => Ok(Self::Probe),
|
||||||
|
NeighbourState::Failed => Ok(Self::Failed),
|
||||||
|
NeighbourState::Noarp => Ok(Self::Noarp),
|
||||||
|
NeighbourState::Permanent => Ok(Self::Permanent),
|
||||||
|
NeighbourState::None => Ok(Self::None),
|
||||||
|
NeighbourState::Other(e) => Ok(Self::Other(e)),
|
||||||
|
_ => Err(u16::MAX), // idk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error = u16;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use std::iter::Fuse;
|
||||||
|
|
||||||
|
/// wrap ts into a [Fuse][std::iter::Fuse] or something
|
||||||
|
pub struct Real<A: Clone, B: Iterator<Item = A>> {
|
||||||
|
prepend: A,
|
||||||
|
iter: B,
|
||||||
|
my_turn: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Clone, B: Iterator<Item = A>> Real<A, B> {
|
||||||
|
pub fn new(prepend: A, iter: B) -> Self {
|
||||||
|
Self {
|
||||||
|
prepend,
|
||||||
|
iter,
|
||||||
|
my_turn: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fuse(prepend: A, iter: B) -> Fuse<Self> {
|
||||||
|
Self::new(prepend, iter).fuse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Clone, B: Iterator<Item = A>> Iterator for Real<A, B> {
|
||||||
|
type Item = A;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let a = if self.my_turn {
|
||||||
|
Some(self.prepend.clone())
|
||||||
|
} else {
|
||||||
|
self.iter.next()
|
||||||
|
};
|
||||||
|
self.my_turn = !self.my_turn;
|
||||||
|
a
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// pub mod iter;
|
||||||
|
pub mod serialize;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{self, Deserialize, Deserializer, de::Error as DeError};
|
||||||
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
|
||||||
|
serde::Serialize::serialize(&strings, serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a MacAddr as a string
|
||||||
|
pub fn serialize<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&mac.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a MacAddr from a string
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
|
||||||
|
s.parse::<MacAddr>().map_err(DeError::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod option_mac {
|
||||||
|
use super::*;
|
||||||
|
/// serialize an [`Option<MacAddr>`]
|
||||||
|
pub fn serialize<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
||||||
|
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
||||||
|
}
|
||||||
|
/// deserialize an [`Option<MacAddr>`], returns None for invalid input (::, 0.0.0.0)
|
||||||
|
pub fn deserialize<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s: Option<&str> = Option::<&str>::deserialize(des)?;
|
||||||
|
match s {
|
||||||
|
Some(val) => match val.parse::<MacAddr>() {
|
||||||
|
Ok(mac) => Ok(Some(mac)),
|
||||||
|
Err(_) => Ok(None),
|
||||||
|
},
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod mac;
|
||||||
|
// pub mod vec;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use serde::de;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum OneOrMany<T> {
|
||||||
|
One(T),
|
||||||
|
Many(Vec<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D, T>(des: D) -> Result<Vec<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
T: std::str::FromStr,
|
||||||
|
T::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let raw: OneOrMany<String> = OneOrMany::<String>::deserialize(des)?;
|
||||||
|
let mut out = Vec::new();
|
||||||
|
match raw {
|
||||||
|
OneOrMany::One(s) => {
|
||||||
|
let t = s.trim();
|
||||||
|
if !t.is_empty() {
|
||||||
|
out.push(t.parse().map_err(de::Error::custom)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OneOrMany::Many(vs) => {
|
||||||
|
for s in vs {
|
||||||
|
let t = s.trim();
|
||||||
|
if t.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.push(t.parse().map_err(de::Error::custom)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use lda_ipjs::subcommands::{address, neighbor};
|
||||||
|
|
||||||
|
#[tokio::test] // ← Use tokio::test instead of manual #[tokio::main]
|
||||||
|
async fn ball1() -> anyhow::Result<()> {
|
||||||
|
let result = neighbor::nl::get(None, None, &[]).await?;
|
||||||
|
println!("netlink results: {:?}", result);
|
||||||
|
Ok(()) // ← Don't force error, let it succeed
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball2() -> anyhow::Result<()> {
|
||||||
|
let result = neighbor::json::get(None, None, &[]).await?;
|
||||||
|
println!("json results: {:?}", result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this to debug the raw JSON
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball_raw_json() -> anyhow::Result<()> {
|
||||||
|
let output = tokio::process::Command::new("ip")
|
||||||
|
.args(["-j", "neigh", "show"])
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let json = String::from_utf8_lossy(&output.stdout);
|
||||||
|
println!("Raw JSON:\n{}", json);
|
||||||
|
|
||||||
|
// Try to parse it
|
||||||
|
let parsed: Result<Vec<neighbor::NeighborItem>, _> = serde_json::from_slice(&output.stdout);
|
||||||
|
match parsed {
|
||||||
|
Ok(items) => println!("Parsed {} items", items.len()),
|
||||||
|
Err(e) => println!("Parse error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check what fields actually exist in the JSON
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball_field_analysis() -> anyhow::Result<()> {
|
||||||
|
let output = tokio::process::Command::new("ip")
|
||||||
|
.args(["-j", "neigh", "show"])
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let raw: Vec<serde_json::Value> = serde_json::from_slice(&output.stdout)?;
|
||||||
|
|
||||||
|
println!("Found {} neighbor entries", raw.len());
|
||||||
|
|
||||||
|
// Collect all unique field names across all entries
|
||||||
|
let mut all_fields = std::collections::HashSet::new();
|
||||||
|
for (i, entry) in raw.iter().enumerate() {
|
||||||
|
if let Some(obj) = entry.as_object() {
|
||||||
|
println!("\nEntry {}: {} fields", i, obj.len());
|
||||||
|
for (key, value) in obj {
|
||||||
|
all_fields.insert(key.clone());
|
||||||
|
println!(" {}: {} = {:?}", key, value.type_name(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== All unique fields seen ===");
|
||||||
|
for field in &all_fields {
|
||||||
|
println!(" - {}", field);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper trait to get type name for JSON values
|
||||||
|
trait TypeName {
|
||||||
|
fn type_name(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeName for serde_json::Value {
|
||||||
|
fn type_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
serde_json::Value::Null => "null",
|
||||||
|
serde_json::Value::Bool(_) => "bool",
|
||||||
|
serde_json::Value::Number(_) => "number",
|
||||||
|
serde_json::Value::String(_) => "string",
|
||||||
|
serde_json::Value::Array(_) => "array",
|
||||||
|
serde_json::Value::Object(_) => "object",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test filtering logic
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball_compare_backends() -> anyhow::Result<()> {
|
||||||
|
println!("=== JSON Backend ===");
|
||||||
|
let json_result = neighbor::json::get(None, None, &[]).await?;
|
||||||
|
println!("Got {} entries from JSON", json_result.len());
|
||||||
|
|
||||||
|
println!("\n=== Netlink Backend ===");
|
||||||
|
let nl_result = neighbor::nl::get(None, None, &[]).await?;
|
||||||
|
println!("Got {} entries from netlink", nl_result.len());
|
||||||
|
|
||||||
|
// Compare counts
|
||||||
|
if json_result.len() != nl_result.len() {
|
||||||
|
println!(
|
||||||
|
"\n⚠️ Count mismatch! JSON: {}, Netlink: {}",
|
||||||
|
json_result.len(),
|
||||||
|
nl_result.len()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("\n✅ Both backends returned same count");
|
||||||
|
}
|
||||||
|
let a: HashSet<neighbor::NeighborItem> = HashSet::from_iter(json_result);
|
||||||
|
let b: HashSet<neighbor::NeighborItem> = HashSet::from_iter(nl_result);
|
||||||
|
println!(
|
||||||
|
"istg {len1} == {len2} or else",
|
||||||
|
len1 = a.len(),
|
||||||
|
len2 = b.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[tokio::test]
|
||||||
|
// async fn cidr_filter() {
|
||||||
|
// unimplemented!("never. i aint add what i dont need")
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ipjas() -> anyhow::Result<()> {
|
||||||
|
let cuh = address::json::get(None).await?;
|
||||||
|
println!("{cuh:#?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ipjas_raw() -> anyhow::Result<()> {
|
||||||
|
let cuh = address::json::_get(None).await?;
|
||||||
|
println!("{cuh:#?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+19
-2
@@ -1,4 +1,5 @@
|
|||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage('PSAvoidDefaultValueSwitchParameter', 'Default true is intentional for fast dev loop')]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidDefaultValueSwitchParameter', "",
|
||||||
|
Justification = 'Default true is intentional for fast dev loop')]
|
||||||
param(
|
param(
|
||||||
[string]$Pass,
|
[string]$Pass,
|
||||||
[string]$HostName = "192.168.100.1",
|
[string]$HostName = "192.168.100.1",
|
||||||
@@ -47,10 +48,11 @@ sh "`$DEPLOY" $RemoteTmp $RemotePath $RestartFlag
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-Scp {
|
function Invoke-Scp {
|
||||||
param($Local, $Dest, $Pass, $HostKey, [switch]$Quiet)
|
param($Local, $Dest, $Pass, $HostKey, [switch]$Quiet, [switch]$Recurse)
|
||||||
if ($pscp = Get-Command pscp.exe -ErrorAction SilentlyContinue) {
|
if ($pscp = Get-Command pscp.exe -ErrorAction SilentlyContinue) {
|
||||||
$arguments = @('-scp')
|
$arguments = @('-scp')
|
||||||
if ($Quiet) { $arguments += '-q' }
|
if ($Quiet) { $arguments += '-q' }
|
||||||
|
if ($Recurse) { $arguments += '-r' }
|
||||||
if ($HostKey) { $arguments += @('-batch', '-hostkey', $HostKey) }
|
if ($HostKey) { $arguments += @('-batch', '-hostkey', $HostKey) }
|
||||||
if ($Pass) { $arguments += @('-pw', $Pass) }
|
if ($Pass) { $arguments += @('-pw', $Pass) }
|
||||||
$arguments += @($Local, $Dest)
|
$arguments += @($Local, $Dest)
|
||||||
@@ -59,6 +61,7 @@ function Invoke-Scp {
|
|||||||
else {
|
else {
|
||||||
$arguments = @('-O')
|
$arguments = @('-O')
|
||||||
if ($Quiet) { $arguments += '-q' }
|
if ($Quiet) { $arguments += '-q' }
|
||||||
|
if ($Recurse) { $arguments += '-r' }
|
||||||
$arguments += @($Local, $Dest)
|
$arguments += @($Local, $Dest)
|
||||||
Invoke-Ext -Exe 'scp' -Arguments $arguments -Label 'scp'
|
Invoke-Ext -Exe 'scp' -Arguments $arguments -Label 'scp'
|
||||||
}
|
}
|
||||||
@@ -102,6 +105,20 @@ try {
|
|||||||
# Push main binary
|
# Push main binary
|
||||||
Invoke-Scp -Local $localBin -Dest $destTmp -Pass $Pass -HostKey $HostKey -Quiet:$Quiet
|
Invoke-Scp -Local $localBin -Dest $destTmp -Pass $Pass -HostKey $HostKey -Quiet:$Quiet
|
||||||
|
|
||||||
|
# Push static assets
|
||||||
|
$localStatic = Join-Path $repoRoot "static"
|
||||||
|
if (Test-Path $localStatic) {
|
||||||
|
# Assuming RemotePath is like /root/.bin/wakey, we want /root/.bin/static
|
||||||
|
# So we push 'static' directory to /root/.bin/
|
||||||
|
$remoteDir = (Split-Path $RemotePath -Parent) -replace '\\', '/'
|
||||||
|
# Ensure remote dir exists (ssh mkdir -p)
|
||||||
|
Invoke-Ssh -Cmd "mkdir -p $remoteDir" -User $User -Remote $HostName -Pass $Pass -Quiet:$Quiet
|
||||||
|
|
||||||
|
# SCP -r static user@host:/root/.bin/
|
||||||
|
# Note: pscp/scp behavior: if dest is a dir, it copies the source dir INTO it.
|
||||||
|
Invoke-Scp -Local $localStatic -Dest "$User@${HostName}:$remoteDir/" -Pass $Pass -HostKey $HostKey -Quiet:$Quiet -Recurse
|
||||||
|
}
|
||||||
|
|
||||||
# Push deploy helper if exists
|
# Push deploy helper if exists
|
||||||
if (Test-Path $localDeploy) {
|
if (Test-Path $localDeploy) {
|
||||||
Invoke-Scp -Local $localDeploy -Dest "$User@${HostName}:$deployTmp" -Pass $Pass -HostKey $HostKey -Quiet:$Quiet
|
Invoke-Scp -Local $localDeploy -Dest "$User@${HostName}:$deployTmp" -Pass $Pass -HostKey $HostKey -Quiet:$Quiet
|
||||||
|
|||||||
+186
-39
@@ -1,22 +1,25 @@
|
|||||||
"thanks chatgpt"
|
"thanks chatgpt"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import textwrap
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
root = Path(__file__).parent.parent
|
root = Path(__file__).parent.parent
|
||||||
static = root / "static" # change "assets" to your folder
|
static = root / "static"
|
||||||
out_rs = root / "src" / "assets.rs"
|
src = root / "src"
|
||||||
|
out_rs = src / "assets.rs"
|
||||||
|
|
||||||
|
assert 'name = "wakey"' in (root / "Cargo.toml").read_text(encoding="utf-8"), (
|
||||||
|
"uhh how do i explain this"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def sanitize(name: str) -> str:
|
def sanitize(name: str) -> str:
|
||||||
# Valid Rust identifiers: letters, digits, underscores; no starting digit
|
# Valid Rust identifiers: letters, digits, underscores; no starting digit
|
||||||
out = []
|
s = "".join(c if c.isalnum() else "_" for c in name)
|
||||||
for c in name:
|
|
||||||
if c.isalnum() or c == "_":
|
|
||||||
out.append(c)
|
|
||||||
else:
|
|
||||||
out.append("_")
|
|
||||||
s = "".join(out)
|
|
||||||
if s and s[0].isdigit():
|
if s and s[0].isdigit():
|
||||||
s = "_" + s
|
s = "_" + s
|
||||||
return s
|
return s
|
||||||
@@ -24,42 +27,186 @@ def sanitize(name: str) -> str:
|
|||||||
|
|
||||||
def indent(text: str, n: int) -> str:
|
def indent(text: str, n: int) -> str:
|
||||||
pad = " " * n
|
pad = " " * n
|
||||||
return "\n".join(pad + line if line.strip() else line for line in text.splitlines())
|
return textwrap.indent(text, pad)
|
||||||
|
|
||||||
|
|
||||||
class RsAssetFile:
|
class RsAsset(ABC):
|
||||||
def __init__(self, path: Path):
|
path: Path
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetWithName(ABC):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def name_gen(path: Path) -> str:
|
||||||
|
"helper"
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetFile(RsAsset, RsAssetWithName):
|
||||||
|
__match_args__ = ("path", "name")
|
||||||
|
|
||||||
|
def __init__(self, path: Path, name: str | None = None):
|
||||||
|
self.path = path
|
||||||
|
if name is None:
|
||||||
|
self.name: str = RsAssetFile.name_gen(path)
|
||||||
|
else:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def path_relative_to(self, path: Path):
|
||||||
|
"helper"
|
||||||
|
return self.path.relative_to(path, walk_up=True).as_posix()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def plain_template(const_name: str, relative_path: str):
|
||||||
|
return f'pub const {const_name}: &str = include_str!("{relative_path}");'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def macroed_template(const_name: str, relative_path: str):
|
||||||
|
return f'file {const_name} "{relative_path}"'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def name_gen(path: Path) -> str:
|
||||||
|
return sanitize(path.name).upper()
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetModule(RsAsset, RsAssetWithName):
|
||||||
|
__match_args__ = ("path", "name")
|
||||||
|
|
||||||
|
def __init__(self, path: Path, name: str | None = None):
|
||||||
|
self.path = path
|
||||||
|
if name is None:
|
||||||
|
self.name: str = RsAssetModule.name_gen(path)
|
||||||
|
else:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def plain_template(sanitized_name: str, body: str):
|
||||||
|
"braindead"
|
||||||
|
indented_body = indent(body, 4)
|
||||||
|
return f"pub mod {sanitized_name} {{\n{indented_body}\n}}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def macroed_template(sanitized_name: str, body: str):
|
||||||
|
indented_body = indent(body, 4)
|
||||||
|
return f"folder {sanitized_name} {{\n{indented_body}\n}}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_body(path: Path, renderer: Callable[[RsAsset], str]):
|
||||||
|
"here just cuz. Path has to be a folder... so idk"
|
||||||
|
body = RsAssetModule.iterate_assets(path)
|
||||||
|
return "\n".join(map(renderer, body))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def iterate_assets(path: Path) -> "Iterable[RsAssetFile | RsAssetModule]":
|
||||||
|
subs = []
|
||||||
|
for f in path.iterdir():
|
||||||
|
if f.is_file():
|
||||||
|
yield RsAssetFile(f)
|
||||||
|
elif f.is_dir():
|
||||||
|
subs.append(RsAssetModule(f))
|
||||||
|
yield from subs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def name_gen(path: Path) -> str:
|
||||||
|
return sanitize(path.name).lower()
|
||||||
|
|
||||||
|
|
||||||
|
# i meant plain
|
||||||
|
def render_pain(ass: RsAsset) -> str:
|
||||||
|
"""
|
||||||
|
fym i have to deal with all RsAsset subclasses.
|
||||||
|
|
||||||
|
ts like a matrix of ahh.
|
||||||
|
"""
|
||||||
|
match ass:
|
||||||
|
case RsAssetRoot(path):
|
||||||
|
body = RsAssetModule.process_body(path, render_pain)
|
||||||
|
return RsAssetRoot.plain_template(body)
|
||||||
|
case RsAssetModule(path, name):
|
||||||
|
body = RsAssetModule.process_body(path, render_pain)
|
||||||
|
return RsAssetModule.plain_template(name, body)
|
||||||
|
case RsAssetFile(path, name) as file:
|
||||||
|
return RsAssetFile.plain_template(name, file.path_relative_to(src))
|
||||||
|
case _:
|
||||||
|
raise TypeError("who are you?")
|
||||||
|
|
||||||
|
|
||||||
|
def render_macro(ass: RsAsset) -> str:
|
||||||
|
"""
|
||||||
|
ts is ridiculous
|
||||||
|
"""
|
||||||
|
match ass:
|
||||||
|
case RsAssetRoot(path):
|
||||||
|
body = RsAssetModule.process_body(path, render_macro)
|
||||||
|
return RsAssetRoot.macroed_template(body)
|
||||||
|
case RsAssetModule(path, name):
|
||||||
|
body = RsAssetModule.process_body(path, render_macro)
|
||||||
|
return RsAssetModule.macroed_template(name, body)
|
||||||
|
case RsAssetFile(path, name) as file:
|
||||||
|
return RsAssetFile.macroed_template(name, file.path_relative_to(src))
|
||||||
|
case _:
|
||||||
|
raise TypeError("who are you?")
|
||||||
|
|
||||||
|
|
||||||
|
# region shit ass
|
||||||
|
# im deleting this code because its so ass
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
lda_macro = """
|
||||||
|
macro_rules! hehe {
|
||||||
|
// Folder
|
||||||
|
(folder $name:ident { $($children:tt)* } $($rest:tt)*) => {
|
||||||
|
pub mod $name {
|
||||||
|
hehe!{$($children)*}
|
||||||
|
}
|
||||||
|
hehe!{$($rest)*}
|
||||||
|
};
|
||||||
|
// File
|
||||||
|
(file $name:ident $file:literal $($rest:tt)*) => {
|
||||||
|
pub const $name: &str = include_str!($file);
|
||||||
|
hehe!{$($rest)*}
|
||||||
|
};
|
||||||
|
// Base case
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
header = "// generated with ./scripts/map_static.py"
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetRoot(RsAsset):
|
||||||
|
__match_args__ = ("path",)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def plain_template(body: str):
|
||||||
|
return f"""{header}
|
||||||
|
|
||||||
|
{body}
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def macroed_template(body: str):
|
||||||
|
indented_body = indent(body, 4)
|
||||||
|
return f"""{header}
|
||||||
|
{lda_macro}
|
||||||
|
hehe! {{
|
||||||
|
{indented_body}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
os.scandir(path) # is you a dir?
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
const_name = sanitize(self.path.name).upper()
|
|
||||||
return (
|
|
||||||
f"pub const {const_name}: &str = "
|
|
||||||
f'include_str!("{self.path.relative_to(out_rs.parent, walk_up=True).as_posix()}");'
|
|
||||||
)
|
|
||||||
# specify walk_up to have .. in yo path
|
|
||||||
|
|
||||||
|
asset_root = RsAssetRoot(static)
|
||||||
|
|
||||||
class RsAssetModule:
|
# print(render_pain(asset_root))
|
||||||
def __init__(self, folder: Path, body_only: bool = False):
|
# print(render_macro(asset_root))
|
||||||
self.folder = folder
|
|
||||||
self.full = not body_only
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
files = [str(RsAssetFile(s)) for s in self.folder.iterdir() if s.is_file()]
|
|
||||||
subs = [str(RsAssetModule(s)) for s in self.folder.iterdir() if s.is_dir()]
|
|
||||||
|
|
||||||
body = "\n".join(files + subs)
|
|
||||||
return (
|
|
||||||
f"pub mod {sanitize(self.folder.name).lower()} {{" * self.full
|
|
||||||
+ f"\n{indent(body, 4 * self.full)}\n"
|
|
||||||
+ self.full * "}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# rs_code = render_pain(asset_root)
|
||||||
|
rs_code = render_macro(asset_root)
|
||||||
|
|
||||||
# generate
|
# generate
|
||||||
rs_code = "// generated with ./scripts/map_static.py\n" + str(
|
|
||||||
RsAssetModule(static, True)
|
|
||||||
)
|
|
||||||
out_rs.write_text(rs_code, encoding="utf-8")
|
out_rs.write_text(rs_code, encoding="utf-8")
|
||||||
print(f"written to {out_rs}")
|
print(f"written to {out_rs.relative_to(Path.cwd(), walk_up=True)}")
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ if (Test-Path $deploySrc) {
|
|||||||
Set-Content -NoNewline -LiteralPath (Join-Path $rootDir "remote_deploy_wakey.sh") -Value $deployContent -Encoding UTF8
|
Set-Content -NoNewline -LiteralPath (Join-Path $rootDir "remote_deploy_wakey.sh") -Value $deployContent -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Copy static assets
|
||||||
|
$staticSrc = Join-Path $root "static"
|
||||||
|
if (Test-Path $staticSrc) {
|
||||||
|
Copy-Item -Recurse $staticSrc (Join-Path $rootDir "static") -Force
|
||||||
|
}
|
||||||
|
|
||||||
# Copy all OpenWrt init scripts present in repo
|
# Copy all OpenWrt init scripts present in repo
|
||||||
Get-ChildItem (Join-Path $root 'scripts/init/openwrt') -File | ForEach-Object {
|
Get-ChildItem (Join-Path $root 'scripts/init/openwrt') -File | ForEach-Object {
|
||||||
$dest = Join-Path $etcDir $_.Name
|
$dest = Join-Path $etcDir $_.Name
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
# Test ipjs on remote ARM device
|
||||||
|
|
||||||
|
param(
|
||||||
|
[string]$Package = "lda-ipjs",
|
||||||
|
[string]$TestName = "",
|
||||||
|
[ValidateSet("debug", "release")]
|
||||||
|
[string]$BuildProfile = "debug",
|
||||||
|
[string]$password,
|
||||||
|
[switch]$Verbose,
|
||||||
|
[string]$RemoteTestPath = "/root/.bin/test",
|
||||||
|
[string]$RemoteHost = "[email protected]"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Build tests and capture output
|
||||||
|
Write-Host "Building tests for $Package..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Stream cargo output anc convert to text
|
||||||
|
$cargoOutput = cargo test --no-run -p $Package --target armv7-unknown-linux-musleabihf $(if ($BuildProfile -eq "release") { "-r" }) 2>&1 |
|
||||||
|
ForEach-Object {
|
||||||
|
$line = if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.ToString() } else { $_ }
|
||||||
|
if ($Verbose) {
|
||||||
|
Write-Host $line
|
||||||
|
}
|
||||||
|
$line # Pass through to capture
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse test binary paths from cargo output
|
||||||
|
$testBinaries = $cargoOutput |
|
||||||
|
Select-String -Pattern "Executable.*\((.+)\)" |
|
||||||
|
ForEach-Object { $_.Matches.Groups[1].Value } |
|
||||||
|
Get-Item
|
||||||
|
|
||||||
|
if ($testBinaries.Count -eq 0) {
|
||||||
|
Write-Error "No test binaries found! Exiting..."
|
||||||
|
# Write-Host "Cargo output:" -ForegroundColor Yellow
|
||||||
|
# $cargoOutput | ForEach-Object { Write-Host $_ }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Found $($testBinaries.Count) test $($testBinaries.Count -eq 1 ? "binary" : "binaries")" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Run each test binary
|
||||||
|
foreach ($testBinary in $testBinaries) {
|
||||||
|
Write-Host "`nTesting: $($testBinary.Name)" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Copy to target
|
||||||
|
pscp.exe -batch -scp -pw $password $testBinary.FullName ${RemoteHost}:$RemoteTestPath | Out-Null
|
||||||
|
|
||||||
|
# Run on target
|
||||||
|
$testArgs = "$(if ($Verbose) {"--nocapture --show-output"})"
|
||||||
|
if ($TestName) {
|
||||||
|
$testArgs = "$TestName $testArgs"
|
||||||
|
}
|
||||||
|
|
||||||
|
plink -batch -ssh $RemoteHost -pw $password "chmod +x $RemoteTestPath && $RemoteTestPath $testArgs"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nDone!" -ForegroundColor Green
|
||||||
+58
-2
@@ -15,5 +15,61 @@ impl<'de> Deserialize<'de> for NUDState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
use crate::arpparse::IpNeighLine;
|
||||||
pub use crate::utils::parse::mac::{des_opm, ser_opm};
|
|
||||||
|
use lda_ipjs::subcommands::neighbor::{self as ipjs_neigh, NeighborItem};
|
||||||
|
|
||||||
|
impl From<ipjs_neigh::NUDState> for NUDState {
|
||||||
|
fn from(value: ipjs_neigh::NUDState) -> Self {
|
||||||
|
match value {
|
||||||
|
ipjs_neigh::NUDState::Permanent => NUDState::Permanent,
|
||||||
|
ipjs_neigh::NUDState::Noarp => NUDState::Noarp,
|
||||||
|
ipjs_neigh::NUDState::Reachable => NUDState::Reachable,
|
||||||
|
ipjs_neigh::NUDState::Stale => NUDState::Stale,
|
||||||
|
ipjs_neigh::NUDState::None => NUDState::None,
|
||||||
|
ipjs_neigh::NUDState::Incomplete => NUDState::Incomplete,
|
||||||
|
ipjs_neigh::NUDState::Delay => NUDState::Delay,
|
||||||
|
ipjs_neigh::NUDState::Probe => NUDState::Probe,
|
||||||
|
ipjs_neigh::NUDState::Failed => NUDState::Failed,
|
||||||
|
ipjs_neigh::NUDState::Other(_) => NUDState::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NUDState> for ipjs_neigh::NUDState {
|
||||||
|
fn from(value: NUDState) -> Self {
|
||||||
|
match value {
|
||||||
|
NUDState::Permanent => ipjs_neigh::NUDState::Permanent,
|
||||||
|
NUDState::Noarp => ipjs_neigh::NUDState::Noarp,
|
||||||
|
NUDState::Reachable => ipjs_neigh::NUDState::Reachable,
|
||||||
|
NUDState::Stale => ipjs_neigh::NUDState::Stale,
|
||||||
|
NUDState::None => ipjs_neigh::NUDState::None,
|
||||||
|
NUDState::Incomplete => ipjs_neigh::NUDState::Incomplete,
|
||||||
|
NUDState::Delay => ipjs_neigh::NUDState::Delay,
|
||||||
|
NUDState::Probe => ipjs_neigh::NUDState::Probe,
|
||||||
|
NUDState::Failed => ipjs_neigh::NUDState::Failed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NeighborItem> for IpNeighLine {
|
||||||
|
fn from(
|
||||||
|
NeighborItem {
|
||||||
|
ip,
|
||||||
|
dev,
|
||||||
|
mac,
|
||||||
|
state,
|
||||||
|
}: NeighborItem,
|
||||||
|
) -> Self {
|
||||||
|
IpNeighLine {
|
||||||
|
ip,
|
||||||
|
dev,
|
||||||
|
mac,
|
||||||
|
state: state
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(NUDState::None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+13
-8
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
use std::{net::IpAddr, str::FromStr};
|
use std::{net::IpAddr, str::FromStr};
|
||||||
|
|
||||||
use impls::ser_opm;
|
use crate::utils::parse::mac;
|
||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use strum::{Display, EnumString};
|
use strum::{Display, EnumString};
|
||||||
@@ -33,7 +33,7 @@ pub struct IpNeighLine {
|
|||||||
pub ip: IpAddr,
|
pub ip: IpAddr,
|
||||||
pub dev: Option<String>,
|
pub dev: Option<String>,
|
||||||
/// link layer address
|
/// link layer address
|
||||||
#[serde(serialize_with = "ser_opm")]
|
#[serde(with = "mac::option_mac")]
|
||||||
pub mac: Option<MacAddr>,
|
pub mac: Option<MacAddr>,
|
||||||
/// Neighbour Unreachability Detection
|
/// Neighbour Unreachability Detection
|
||||||
pub state: NUDState,
|
pub state: NUDState,
|
||||||
@@ -41,7 +41,9 @@ pub struct IpNeighLine {
|
|||||||
|
|
||||||
// NUDState custom Deserialize now lives in arpparse/impl.rs; use serde_with OneOrMany for Vec
|
// NUDState custom Deserialize now lives in arpparse/impl.rs; use serde_with OneOrMany for Vec
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, serde::Serialize)]
|
#[derive(
|
||||||
|
Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, serde::Serialize, Default,
|
||||||
|
)]
|
||||||
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
|
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
pub enum NUDState {
|
pub enum NUDState {
|
||||||
@@ -63,10 +65,6 @@ pub enum NUDState {
|
|||||||
/// neighbour state if it was valid and the
|
/// neighbour state if it was valid and the
|
||||||
/// address is not changed by this command.
|
/// address is not changed by this command.
|
||||||
Stale,
|
Stale,
|
||||||
/// this is a pseudo state used when initially
|
|
||||||
/// creating a neighbour entry or after trying to
|
|
||||||
/// remove it before it becomes free to do so.
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// the neighbour entry has not (yet) been
|
/// the neighbour entry has not (yet) been
|
||||||
/// validated/resolved.
|
/// validated/resolved.
|
||||||
@@ -80,6 +78,13 @@ pub enum NUDState {
|
|||||||
/// success, neighbor validation has ultimately
|
/// success, neighbor validation has ultimately
|
||||||
/// failed.
|
/// failed.
|
||||||
Failed,
|
Failed,
|
||||||
|
|
||||||
|
/// this is a pseudo state used when initially
|
||||||
|
/// creating a neighbour entry or after trying to
|
||||||
|
/// remove it before it becomes free to do so.
|
||||||
|
#[serde(other)]
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NUDState {
|
impl NUDState {
|
||||||
@@ -178,7 +183,7 @@ impl IpNeighLine {
|
|||||||
Self { state, ..self }
|
Self { state, ..self }
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
pub fn with_dev(dev: impl Into<String>) -> impl FnMut(Self) -> Self {
|
pub fn _with_dev(dev: impl Into<String>) -> impl FnMut(Self) -> Self {
|
||||||
let dev = dev.into();
|
let dev = dev.into();
|
||||||
move |self_| Self {
|
move |self_| Self {
|
||||||
dev: Some(dev.clone()),
|
dev: Some(dev.clone()),
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// generated with ./scripts/map_static.py
|
|
||||||
|
|
||||||
pub const HOME_2_HTML: &str = include_str!("../static/home_2.html");
|
|
||||||
pub mod home_2 {
|
|
||||||
pub const DOM_JS: &str = include_str!("../static/home_2/dom.js");
|
|
||||||
pub const LEASES_JS: &str = include_str!("../static/home_2/leases.js");
|
|
||||||
pub const MAIN_JS: &str = include_str!("../static/home_2/main.js");
|
|
||||||
pub const STATUS_JS: &str = include_str!("../static/home_2/status.js");
|
|
||||||
pub const STYLES_CSS: &str = include_str!("../static/home_2/styles.css");
|
|
||||||
pub const UTILS_JS: &str = include_str!("../static/home_2/utils.js");
|
|
||||||
pub const WAKE_JS: &str = include_str!("../static/home_2/wake.js");
|
|
||||||
}
|
|
||||||
+2
-6
@@ -42,8 +42,8 @@ pub async fn read_dhcp_leases_with_names() -> io::Result<Vec<DhcpLeaseLine>> {
|
|||||||
}
|
}
|
||||||
Ok(leases_with_names)
|
Ok(leases_with_names)
|
||||||
}
|
}
|
||||||
|
use crate::utils::parse::mac;
|
||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
use serde::Serializer;
|
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::{self, ErrorKind};
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
@@ -53,15 +53,11 @@ pub struct DhcpLeaseLine {
|
|||||||
/// Epoch seconds when the lease expires
|
/// Epoch seconds when the lease expires
|
||||||
pub expires_epoch: u64,
|
pub expires_epoch: u64,
|
||||||
pub ip: IpAddr,
|
pub ip: IpAddr,
|
||||||
#[serde(serialize_with = "ser_mac")]
|
#[serde(with = "mac")]
|
||||||
pub mac: MacAddr,
|
pub mac: MacAddr,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ser_mac<S: Serializer>(m: &MacAddr, s: S) -> Result<S::Ok, S::Error> {
|
|
||||||
s.serialize_str(&m.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse one line of /tmp/dhcp.leases
|
/// Parse one line of /tmp/dhcp.leases
|
||||||
pub fn parse_dhcp_lease_line(line: &str) -> Option<DhcpLeaseLine> {
|
pub fn parse_dhcp_lease_line(line: &str) -> Option<DhcpLeaseLine> {
|
||||||
let mut c = line.split_whitespace();
|
let mut c = line.split_whitespace();
|
||||||
|
|||||||
+20
-8
@@ -8,26 +8,37 @@
|
|||||||
//! 2. incorporate ip -j;
|
//! 2. incorporate ip -j;
|
||||||
//! 3. small 1-5 second caching;
|
//! 3. small 1-5 second caching;
|
||||||
|
|
||||||
use axum::{Router, routing::get};
|
use axum::Router;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
mod arpparse;
|
mod arpparse;
|
||||||
pub mod assets;
|
|
||||||
mod dhcpparse;
|
mod dhcpparse;
|
||||||
mod route;
|
mod route;
|
||||||
mod utils;
|
mod utils;
|
||||||
use std::io;
|
use std::{env, io};
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn entry() -> io::Result<()> {
|
async fn entry() -> io::Result<()> {
|
||||||
use crate::route::{api_router, home_2, home_2_route};
|
use crate::route::api_router;
|
||||||
|
use axum::routing::get_service;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
let exe = env::current_exe()?;
|
||||||
|
let root = exe
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| io::Error::other("no parent dir"))?;
|
||||||
|
let static_dir = ServeDir::new(root.join("static"))
|
||||||
|
.append_index_html_on_directories(true)
|
||||||
|
.precompressed_br()
|
||||||
|
.precompressed_deflate()
|
||||||
|
.precompressed_gzip()
|
||||||
|
.precompressed_zstd();
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
// .route("/home", get(home))
|
// .route("/home", get(home))
|
||||||
.route("/", get(home_2))
|
// .route("/", get(home_2))
|
||||||
.merge(home_2_route())
|
// .merge(home_2_route())
|
||||||
// .route("/status", get(get_status_2))
|
// .route("/status", get(get_status_2))
|
||||||
.nest("/api", api_router());
|
.nest("/api", api_router())
|
||||||
|
.fallback_service(get_service(static_dir));
|
||||||
|
|
||||||
let port = TcpListener::bind("0.0.0.0:12012").await?;
|
let port = TcpListener::bind("0.0.0.0:12012").await?;
|
||||||
axum::serve(port, app.into_make_service()).await?;
|
axum::serve(port, app.into_make_service()).await?;
|
||||||
@@ -36,6 +47,7 @@ async fn entry() -> io::Result<()> {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
fn main() -> color_eyre::Result<()> {
|
fn main() -> color_eyre::Result<()> {
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
// use crate::arpparse::NUDState;
|
// use crate::arpparse::NUDState;
|
||||||
// println!("{}", NUDState::Reachable.to_string().to_lowercase());
|
// println!("{}", NUDState::Reachable.to_string().to_lowercase());
|
||||||
|
|||||||
+7
-14
@@ -1,5 +1,5 @@
|
|||||||
use crate::route::error::ApiError;
|
use crate::route::error::ApiError;
|
||||||
use crate::utils::query_parser::{QueryType, parse_query};
|
use crate::utils::query::parser::{QueryType, parse_query};
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
@@ -13,7 +13,7 @@ pub async fn status_smart_redirect(
|
|||||||
Path(q): Path<String>,
|
Path(q): Path<String>,
|
||||||
) -> axum::response::Result<Redirect, impl IntoResponse> {
|
) -> axum::response::Result<Redirect, impl IntoResponse> {
|
||||||
// no less bullshit
|
// no less bullshit
|
||||||
let query = match parse_query(q) {
|
let query = match parse_query(q).await {
|
||||||
QueryType::Ip(ip_addr) => DeviceQuery {
|
QueryType::Ip(ip_addr) => DeviceQuery {
|
||||||
filter: Filters {
|
filter: Filters {
|
||||||
ips: vec![ip_addr],
|
ips: vec![ip_addr],
|
||||||
@@ -49,12 +49,10 @@ pub async fn status_smart_redirect(
|
|||||||
};
|
};
|
||||||
match serde_html_form::to_string(query) {
|
match serde_html_form::to_string(query) {
|
||||||
Ok(e) => Ok(Redirect::to(&format!("/api/status?{e}"))),
|
Ok(e) => Ok(Redirect::to(&format!("/api/status?{e}"))),
|
||||||
Err(e) => Err((
|
Err(e) => Err(ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
error: e.to_string(),
|
||||||
Json(ApiError {
|
code: StatusCode::BAD_GATEWAY,
|
||||||
error: e.to_string(),
|
}),
|
||||||
}),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +65,7 @@ pub async fn status_redirect(Path(NamePath { name }): Path<NamePath>) -> Redirec
|
|||||||
|
|
||||||
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
||||||
get_ips(&name).await.map_or_else(
|
get_ips(&name).await.map_or_else(
|
||||||
|e| {
|
|e| ApiError::ise(e.to_string()).into_response(),
|
||||||
ApiError {
|
|
||||||
error: e.to_string(),
|
|
||||||
}
|
|
||||||
.into_response()
|
|
||||||
},
|
|
||||||
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@ use crate::utils::query::dev;
|
|||||||
use axum::Json;
|
use axum::Json;
|
||||||
|
|
||||||
pub async fn devs_router() -> Json<Vec<String>> {
|
pub async fn devs_router() -> Json<Vec<String>> {
|
||||||
dev::devs_sorted().into()
|
dev::devs_sorted().await.into()
|
||||||
}
|
}
|
||||||
// Device listing endpoints
|
// Device listing endpoints
|
||||||
|
|||||||
+5
-7
@@ -26,12 +26,10 @@ pub async fn get_dhcp_leases(Query(raw): Query<DhcpLeasesQueryRaw>) -> impl Into
|
|||||||
let out = enrich_leases_with_nud_state(leases_with_names).await;
|
let out = enrich_leases_with_nud_state(leases_with_names).await;
|
||||||
(StatusCode::OK, Json(out)).into_response()
|
(StatusCode::OK, Json(out)).into_response()
|
||||||
}
|
}
|
||||||
Err(e) => (
|
Err(e) => ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
error: e.to_string(),
|
||||||
Json(ApiError {
|
code: StatusCode::BAD_GATEWAY,
|
||||||
error: e.to_string(),
|
}
|
||||||
}),
|
.into_response(),
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -7,11 +7,23 @@ use serde::Serialize;
|
|||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ApiError {
|
pub struct ApiError {
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub code: StatusCode,
|
||||||
pub error: String,
|
pub error: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ApiError {
|
||||||
|
/// [StatusCode::INTERNAL_SERVER_ERROR] shortcut
|
||||||
|
pub const fn ise(error: String) -> Self {
|
||||||
|
Self {
|
||||||
|
code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoResponse for ApiError {
|
impl IntoResponse for ApiError {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
|
(self.code, Json(self)).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-32
@@ -5,7 +5,7 @@ pub mod error;
|
|||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod wake;
|
pub mod wake;
|
||||||
|
|
||||||
use crate::assets;
|
// use crate::assets;
|
||||||
use crate::dhcpparse::load_mac_name_cache;
|
use crate::dhcpparse::load_mac_name_cache;
|
||||||
use crate::route::api::ip;
|
use crate::route::api::ip;
|
||||||
use crate::route::api::status_redirect;
|
use crate::route::api::status_redirect;
|
||||||
@@ -16,39 +16,23 @@ use crate::route::status::get_status_json;
|
|||||||
use crate::route::wake::wake_multi;
|
use crate::route::wake::wake_multi;
|
||||||
|
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::response::IntoResponse;
|
use axum::Router;
|
||||||
use axum::routing::post;
|
use axum::body::Body;
|
||||||
use axum::{Router, http::header, response::Html, routing::get};
|
use axum::http::Request;
|
||||||
|
use axum::middleware::{self, Next};
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::utils::route::serve_js;
|
async fn add_performance_header(req: Request<Body>, next: Next) -> Response {
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut response = next.run(req).await;
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
pub async fn home_2() -> Html<&'static str> {
|
if let Ok(val) = format!("work-time={}us", elapsed.as_micros()).parse() {
|
||||||
Html(assets::HOME_2_HTML)
|
response.headers_mut().insert("Lda-Performance", val);
|
||||||
}
|
}
|
||||||
pub fn home_2_route() -> Router {
|
response
|
||||||
use assets::*;
|
|
||||||
Router::new()
|
|
||||||
.route("/home_2", get(|| async { Html(HOME_2_HTML) }))
|
|
||||||
.route("/home_2/", get(|| async { Html(HOME_2_HTML) }))
|
|
||||||
.route("/home_2.html", get(|| async { Html(HOME_2_HTML) }))
|
|
||||||
.route(
|
|
||||||
"/home_2/styles.css",
|
|
||||||
get(|| async {
|
|
||||||
(
|
|
||||||
[
|
|
||||||
(header::CONTENT_TYPE, "text/css; charset=utf-8"),
|
|
||||||
(header::CACHE_CONTROL, "public, max-age=300"),
|
|
||||||
],
|
|
||||||
home_2::STYLES_CSS,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.route("/home_2/main.js", get(|| serve_js(home_2::MAIN_JS)))
|
|
||||||
.route("/home_2/leases.js", get(|| serve_js(home_2::LEASES_JS)))
|
|
||||||
.route("/home_2/status.js", get(|| serve_js(home_2::STATUS_JS)))
|
|
||||||
.route("/home_2/utils.js", get(|| serve_js(home_2::UTILS_JS)))
|
|
||||||
.route("/home_2/wake.js", get(|| serve_js(home_2::WAKE_JS)))
|
|
||||||
.route("/home_2/dom.js", get(|| serve_js(home_2::DOM_JS)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_router() -> Router {
|
pub fn api_router() -> Router {
|
||||||
@@ -69,4 +53,5 @@ pub fn api_router() -> Router {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.layer(middleware::from_fn(add_performance_header))
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-43
@@ -4,11 +4,10 @@ use axum_extra::extract::Query;
|
|||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
|
use serde_with::{DisplayFromStr, OneOrMany, serde_as};
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use crate::arpparse::NUDState;
|
use crate::arpparse::NUDState;
|
||||||
use crate::utils::parse::de_many;
|
|
||||||
use crate::utils::parse::serialize_macs;
|
|
||||||
use crate::utils::query::get_macs;
|
use crate::utils::query::get_macs;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
||||||
@@ -31,68 +30,57 @@ pub struct Status<T> {
|
|||||||
pub filters: Filters,
|
pub filters: Filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize)]
|
||||||
pub struct Filters {
|
pub struct Filters {
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
#[serde_as(as = "OneOrMany<_>")]
|
||||||
|
#[serde(default)]
|
||||||
pub ips: Vec<IpAddr>,
|
pub ips: Vec<IpAddr>,
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
#[serde_as(as = "OneOrMany<_>")]
|
||||||
|
#[serde(default)]
|
||||||
pub devs: Vec<String>,
|
pub devs: Vec<String>,
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
#[serde_as(as = "OneOrMany<_>")]
|
||||||
|
#[serde(default)]
|
||||||
pub nuds: Vec<NUDState>,
|
pub nuds: Vec<NUDState>,
|
||||||
#[serde(
|
#[serde_as(as = "OneOrMany<DisplayFromStr>")]
|
||||||
default,
|
#[serde(default)]
|
||||||
deserialize_with = "de_many::vec_from_strs",
|
|
||||||
serialize_with = "serialize_macs"
|
|
||||||
)]
|
|
||||||
pub macs: Vec<MacAddr>,
|
pub macs: Vec<MacAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_status_json(
|
pub async fn get_status_json(
|
||||||
Query(DeviceQuery {
|
Query(DeviceQuery {
|
||||||
name,
|
name,
|
||||||
filter:
|
filter: filters,
|
||||||
Filters {
|
|
||||||
ips,
|
|
||||||
devs,
|
|
||||||
nuds,
|
|
||||||
macs,
|
|
||||||
},
|
|
||||||
..
|
..
|
||||||
}): Query<DeviceQuery>,
|
}): Query<DeviceQuery>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match get_macs(
|
match get_macs(
|
||||||
&name.iter().collect::<Vec<_>>(),
|
name.as_slice(),
|
||||||
&ips,
|
&filters.ips,
|
||||||
&devs.iter().collect::<Vec<_>>(),
|
&filters.devs,
|
||||||
&nuds,
|
&filters.nuds,
|
||||||
&macs,
|
&filters.macs,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(table) => {
|
Ok(table) => (
|
||||||
let filters = Filters {
|
StatusCode::OK,
|
||||||
ips,
|
Json(Status {
|
||||||
devs,
|
name,
|
||||||
nuds,
|
table,
|
||||||
macs,
|
filters,
|
||||||
};
|
|
||||||
(
|
|
||||||
StatusCode::OK,
|
|
||||||
Json(Status {
|
|
||||||
name,
|
|
||||||
table,
|
|
||||||
filters,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
Err(error) => (
|
|
||||||
StatusCode::BAD_GATEWAY,
|
|
||||||
Json(ApiError {
|
|
||||||
error: error.to_string(),
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
|
Err(error) => ApiError {
|
||||||
|
code: StatusCode::BAD_GATEWAY,
|
||||||
|
error: error
|
||||||
|
.chain()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(": "),
|
||||||
|
}
|
||||||
|
.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Status endpoints
|
// Status endpoints
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ use std::net::IpAddr;
|
|||||||
|
|
||||||
/* use crate::arpparse::IpNeighLine;
|
/* use crate::arpparse::IpNeighLine;
|
||||||
use crate::route::api::Status; */
|
use crate::route::api::Status; */
|
||||||
use crate::utils::parse::mac::{des_opm, ser_opm};
|
use crate::utils::parse::mac;
|
||||||
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};
|
||||||
use futures::TryFutureExt;
|
use futures::TryFutureExt;
|
||||||
@@ -44,7 +44,7 @@ pub enum WakeTargetStatus {
|
|||||||
pub struct WakeTarget {
|
pub struct WakeTarget {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ip: Option<IpAddr>,
|
pub ip: Option<IpAddr>,
|
||||||
#[serde(default, serialize_with = "ser_opm", deserialize_with = "des_opm")]
|
#[serde(default, with = "mac::option_mac")]
|
||||||
pub mac: Option<MacAddr>,
|
pub mac: Option<MacAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
|
|
||||||
pub(crate) async fn exec_command<S: AsRef<std::ffi::OsStr>>(
|
|
||||||
cmd: S,
|
|
||||||
args: impl IntoIterator<Item = S>,
|
|
||||||
) -> io::Result<std::process::Output> {
|
|
||||||
let mut u = tokio::process::Command::new(cmd);
|
|
||||||
u.args(args);
|
|
||||||
u.output().await
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
use std::{fmt, io};
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failure to resolve a host name to IPs.
|
|
||||||
DnsResolve { name: String, source: io::Error },
|
|
||||||
/// External command failed (e.g., ip neigh)
|
|
||||||
CommandFailed {
|
|
||||||
cmd: &'static str,
|
|
||||||
args: Vec<String>,
|
|
||||||
status: Option<i32>,
|
|
||||||
stderr: String,
|
|
||||||
},
|
|
||||||
/// Generic IO error fallback
|
|
||||||
Io(io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::DnsResolve { name, source } => {
|
|
||||||
write!(f, "DNS resolve failed for {name}: {source}")
|
|
||||||
}
|
|
||||||
Error::CommandFailed {
|
|
||||||
cmd,
|
|
||||||
args,
|
|
||||||
status,
|
|
||||||
stderr,
|
|
||||||
} => {
|
|
||||||
let code = status
|
|
||||||
.map(|c| c.to_string())
|
|
||||||
.unwrap_or_else(|| "signal".into());
|
|
||||||
write!(f, "{cmd} {args:?} failed (status: {code}): {stderr}",)
|
|
||||||
}
|
|
||||||
Error::Io(e) => write!(f, "IO error: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
Error::DnsResolve { source, .. } => Some(source),
|
|
||||||
Error::Io(e) => Some(e),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
|
||||||
fn from(e: io::Error) -> Self {
|
|
||||||
Error::Io(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,14 +9,10 @@
|
|||||||
// pub static LDA_MACS_2: LazyLock<[MacAddr; 2]> = LazyLock::new(|| LDA_MACS.map(MacAddr::from));
|
// pub static LDA_MACS_2: LazyLock<[MacAddr; 2]> = LazyLock::new(|| LDA_MACS.map(MacAddr::from));
|
||||||
pub mod wake;
|
pub mod wake;
|
||||||
|
|
||||||
pub mod cmd;
|
|
||||||
pub mod error;
|
|
||||||
/// generic so you can do "123.45.67.89:22" or "lda.lan:22" as an input
|
/// generic so you can do "123.45.67.89:22" or "lda.lan:22" as an input
|
||||||
// this is so bad
|
// this is so bad
|
||||||
pub mod ping;
|
pub mod ping;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
pub mod query_parser;
|
|
||||||
pub mod route;
|
|
||||||
|
|
||||||
// no custom ip deserializer needed when using axum_extra::extract::Query
|
// no custom ip deserializer needed when using axum_extra::extract::Query
|
||||||
// but we add a generic one to ignore blanks and accept OneOrMany
|
// but we add a generic one to ignore blanks and accept OneOrMany
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{self, Deserialize, Deserializer, de::Error as DeError};
|
||||||
|
use serde::{Serialize, Serializer, de};
|
||||||
|
|
||||||
|
pub fn _serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
|
||||||
|
serde::Serialize::serialize(&strings, serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a MacAddr as a string
|
||||||
|
pub fn serialize<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&mac.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a MacAddr from a string
|
||||||
|
pub fn _deserialize<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
|
||||||
|
s.parse::<MacAddr>().map_err(DeError::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod option_mac {
|
||||||
|
use super::*;
|
||||||
|
/// serialize an [`Option<MacAddr>`]
|
||||||
|
pub fn serialize<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
||||||
|
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// deserialize an [`Option<MacAddr>`]
|
||||||
|
pub fn deserialize<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Option::<&str>::deserialize(des)?
|
||||||
|
.map(str::parse)
|
||||||
|
.transpose()
|
||||||
|
.map_err(de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,91 +100,4 @@ pub fn boolish_str(s: &str) -> bool {
|
|||||||
&& t.parse::<u64>().map(|n| n != 0).unwrap_or(false))
|
&& t.parse::<u64>().map(|n| n != 0).unwrap_or(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod de_many {
|
pub mod mac;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::de;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
enum OneOrMany<T> {
|
|
||||||
One(T),
|
|
||||||
Many(Vec<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vec_from_strs<'de, D, T>(des: D) -> Result<Vec<T>, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
T: std::str::FromStr,
|
|
||||||
T::Err: std::fmt::Display,
|
|
||||||
{
|
|
||||||
let raw: OneOrMany<String> = OneOrMany::<String>::deserialize(des)?;
|
|
||||||
let mut out = Vec::new();
|
|
||||||
match raw {
|
|
||||||
OneOrMany::One(s) => {
|
|
||||||
let t = s.trim();
|
|
||||||
if !t.is_empty() {
|
|
||||||
out.push(t.parse().map_err(de::Error::custom)?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OneOrMany::Many(vs) => {
|
|
||||||
for s in vs {
|
|
||||||
let t = s.trim();
|
|
||||||
if t.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
out.push(t.parse().map_err(de::Error::custom)?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod mac {
|
|
||||||
use macaddr::MacAddr;
|
|
||||||
use serde::{self, Deserialize, Deserializer, de::Error as DeError};
|
|
||||||
use serde::{Serialize, Serializer, de};
|
|
||||||
|
|
||||||
pub fn serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
|
|
||||||
serde::Serialize::serialize(&strings, serializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize a MacAddr as a string
|
|
||||||
pub fn _serialize_mac<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(&mac.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize a MacAddr from a string
|
|
||||||
pub fn _deserialize_mac<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
|
|
||||||
s.parse::<MacAddr>().map_err(DeError::custom)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// serialize an [`Option<MacAddr>`]
|
|
||||||
pub fn ser_opm<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
|
||||||
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// deserialize an [`Option<MacAddr>`]
|
|
||||||
pub fn des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
Option::<&str>::deserialize(des)?
|
|
||||||
.map(str::parse)
|
|
||||||
.transpose()
|
|
||||||
.map_err(de::Error::custom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use mac::*;
|
|
||||||
+52
-32
@@ -1,45 +1,65 @@
|
|||||||
use std::{collections::HashSet, fs};
|
use std::collections::HashSet;
|
||||||
|
// /// 50ms
|
||||||
|
// pub async fn get_dev() -> HashSet<String> {
|
||||||
|
// use lda_ipjs::subcommands::address::json as ipjs_json;
|
||||||
|
// let mut devs: HashSet<String> = HashSet::new();
|
||||||
|
// if let Ok(items) = ipjs_json::get(None).await {
|
||||||
|
// for item in items {
|
||||||
|
// if item.ifname != "lo" && !item.ifname.is_empty() {
|
||||||
|
// devs.insert(item.ifname);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// devs
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn get_dev() -> HashSet<String> {
|
/// 3ms
|
||||||
let mut devs: HashSet<String> = HashSet::new();
|
pub async fn get_dev() -> HashSet<String> {
|
||||||
if let Ok(rd) = std::fs::read_dir("/sys/class/net") {
|
use std::fs;
|
||||||
for e in rd.flatten() {
|
fn get_dev() -> HashSet<String> {
|
||||||
if e.file_type()
|
let mut devs: HashSet<String> = HashSet::new();
|
||||||
.map(|ft| {
|
if let Ok(rd) = std::fs::read_dir("/sys/class/net") {
|
||||||
if ft.is_symlink() {
|
for e in rd.flatten() {
|
||||||
// true
|
if e.file_type()
|
||||||
fs::metadata(e.path()).map(|m| m.is_dir()).unwrap_or(false)
|
.map(|ft| {
|
||||||
} else {
|
if ft.is_symlink() {
|
||||||
ft.is_dir()
|
// true
|
||||||
}
|
fs::metadata(e.path()).map(|m| m.is_dir()).unwrap_or(false)
|
||||||
})
|
} else {
|
||||||
.unwrap_or(false)
|
ft.is_dir()
|
||||||
&& let Ok(name) = e.file_name().into_string()
|
}
|
||||||
&& name != "lo"
|
})
|
||||||
&& !name.is_empty()
|
.unwrap_or(false)
|
||||||
{
|
&& let Ok(name) = e.file_name().into_string()
|
||||||
devs.insert(name);
|
&& name != "lo"
|
||||||
|
&& !name.is_empty()
|
||||||
|
{
|
||||||
|
devs.insert(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if let Ok(txt) = std::fs::read_to_string("/proc/net/dev") {
|
||||||
} else if let Ok(txt) = std::fs::read_to_string("/proc/net/dev") {
|
for line in txt.lines().skip(2) {
|
||||||
for line in txt.lines().skip(2) {
|
if let Some((name, _rest)) = line.split_once(':') {
|
||||||
if let Some((name, _rest)) = line.split_once(':') {
|
let n = name.trim().to_string();
|
||||||
let n = name.trim().to_string();
|
if n != "lo" && !n.is_empty() {
|
||||||
if n != "lo" && !n.is_empty() {
|
devs.insert(n);
|
||||||
devs.insert(n);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
devs
|
||||||
}
|
}
|
||||||
devs
|
tokio::task::spawn_blocking(get_dev)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn devs_sorted() -> Vec<String> {
|
pub async fn devs_sorted() -> Vec<String> {
|
||||||
let mut v: Vec<String> = get_dev().into_iter().collect();
|
let mut v: Vec<String> = get_dev().await.into_iter().collect();
|
||||||
v.sort();
|
v.sort();
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_dev(name: &str) -> bool {
|
pub async fn has_dev(name: &str) -> bool {
|
||||||
get_dev().contains(name)
|
get_dev().await.contains(name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,35 +10,33 @@ pub struct DhcpLeaseOut {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub lease_line: DhcpLeaseLine,
|
pub lease_line: DhcpLeaseLine,
|
||||||
pub nud_state: Option<NUDState>,
|
pub nud_state: Option<NUDState>,
|
||||||
pub rank: Option<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enrich DHCP leases with NUD state and rank using get_macs
|
/// Enrich DHCP leases with NUD state and rank using get_macs
|
||||||
pub async fn enrich_leases_with_nud_state(leases: Vec<DhcpLeaseLine>) -> Vec<DhcpLeaseOut> {
|
pub async fn enrich_leases_with_nud_state(leases: Vec<DhcpLeaseLine>) -> Vec<DhcpLeaseOut> {
|
||||||
let ips: Vec<IpAddr> = leases.iter().map(|l| l.ip).collect();
|
let ips: Vec<IpAddr> = leases.iter().map(|l| l.ip).collect();
|
||||||
let mut map: std::collections::HashMap<IpAddr, (NUDState, u8)> =
|
let mut map: std::collections::HashMap<IpAddr, NUDState> = std::collections::HashMap::new();
|
||||||
std::collections::HashMap::new();
|
|
||||||
if let Ok(rows) = get_macs(&[] as &[&str], &ips, &[] as &[&str], &[], &[]).await {
|
if let Ok(rows) = get_macs(&[] as &[&str], &ips, &[] as &[&str], &[], &[]).await {
|
||||||
for row in rows {
|
for row in rows {
|
||||||
let state = row.state;
|
let state = row.state;
|
||||||
let r = state.rank();
|
let r = state.rank();
|
||||||
map.entry(row.ip)
|
map.entry(row.ip)
|
||||||
.and_modify(|e| {
|
.and_modify(|e| {
|
||||||
if r > e.1 {
|
let er = e.rank();
|
||||||
*e = (state, r)
|
if r > er {
|
||||||
|
*e = state
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.or_insert((state, r));
|
.or_insert(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
leases
|
leases
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|lease_line| {
|
.map(|lease_line| {
|
||||||
let (nud_state, rank) = map.get(&lease_line.ip).copied().unzip();
|
let nud_state = map.get(&lease_line.ip).copied();
|
||||||
DhcpLeaseOut {
|
DhcpLeaseOut {
|
||||||
lease_line,
|
lease_line,
|
||||||
nud_state,
|
nud_state,
|
||||||
rank,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
+53
-64
@@ -1,28 +1,18 @@
|
|||||||
|
use lda_ipjs::subcommands::neighbor;
|
||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
|
|
||||||
use crate::arpparse::{self, IpNeighLine, NUDState};
|
use crate::arpparse::{self, IpNeighLine, NUDState};
|
||||||
use crate::utils::{
|
use anyhow::{Context, Result, bail};
|
||||||
cmd::exec_command,
|
|
||||||
error::{self, Result},
|
|
||||||
};
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
pub async fn get_ips(machine_name: &str) -> Result<impl Iterator<Item = IpAddr>> {
|
pub async fn get_ips(machine_name: &str) -> Result<impl Iterator<Item = IpAddr>> {
|
||||||
Ok(tokio::net::lookup_host((machine_name, 0))
|
Ok(tokio::net::lookup_host((machine_name, 0))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| error::Error::DnsResolve {
|
.with_context(|| format!("DNS resolve failed for {machine_name}"))?
|
||||||
name: machine_name.to_string(),
|
|
||||||
source: e,
|
|
||||||
})?
|
|
||||||
.map(|c| c.ip()))
|
.map(|c| c.ip()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[deprecated(
|
|
||||||
// since = "0.1.5",
|
|
||||||
// note = "just call once everything with get mac
|
|
||||||
// and then filter it bro WHY DO YOU EVEN DO TS"
|
|
||||||
// )]
|
|
||||||
// good now
|
// good now
|
||||||
//
|
//
|
||||||
// Current logic: When filtering by exactly 1 dev/mac, exclude entries missing that field.
|
// Current logic: When filtering by exactly 1 dev/mac, exclude entries missing that field.
|
||||||
@@ -45,15 +35,15 @@ pub async fn get_macs(
|
|||||||
macs: &[MacAddr],
|
macs: &[MacAddr],
|
||||||
) -> Result<Vec<IpNeighLine>> {
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
let mut ip_set: HashSet<IpAddr> = ips.iter().map(|ip| ip.to_canonical()).collect();
|
let mut ip_set: HashSet<IpAddr> = ips.iter().map(|ip| ip.to_canonical()).collect();
|
||||||
let ip_m: HashSet<IpAddr> = futures::future::try_join_all(
|
let ip_m: HashSet<IpAddr> = if !machine_names.is_empty() {
|
||||||
machine_names
|
futures::future::try_join_all(machine_names.iter().map(|c| get_ips(c.as_ref())))
|
||||||
.iter()
|
.await?
|
||||||
.map(|c| async { get_ips(c.as_ref()).await }),
|
.into_iter()
|
||||||
)
|
.flatten()
|
||||||
.await?
|
.collect()
|
||||||
.into_iter()
|
} else {
|
||||||
.flatten()
|
Default::default()
|
||||||
.collect();
|
};
|
||||||
let ip_all = if ip_set.is_empty() && ip_m.is_empty() {
|
let ip_all = if ip_set.is_empty() && ip_m.is_empty() {
|
||||||
None
|
None
|
||||||
} else if ip_set.is_empty() {
|
} else if ip_set.is_empty() {
|
||||||
@@ -90,7 +80,7 @@ pub async fn get_macs(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Apply additional filters if any were provided
|
// Apply additional filters if any were provided
|
||||||
if !devs.is_empty() || !macs.is_empty() || !state.is_empty() {
|
if !devs.is_empty() || !macs.is_empty() {
|
||||||
let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect();
|
let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect();
|
||||||
let macset: HashSet<_> = macs.iter().collect();
|
let macset: HashSet<_> = macs.iter().collect();
|
||||||
|
|
||||||
@@ -108,8 +98,28 @@ pub async fn get_macs(
|
|||||||
Ok(ip_filtered)
|
Ok(ip_filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /// the atomic get_macs. handle ONE thing only.
|
||||||
|
// // this one sucks shit
|
||||||
|
// pub async fn get_mac(
|
||||||
|
// ip: Option<IpAddr>,
|
||||||
|
// dev: Option<&str>,
|
||||||
|
// state: &[NUDState],
|
||||||
|
// ) -> Result<Vec<IpNeighLine>> {
|
||||||
|
// use lda_ipjs::subcommands::neighbor as ipjs_neigh;
|
||||||
|
// let ipjs_states: Vec<ipjs_neigh::NUDState> = state.iter().copied().map(Into::into).collect();
|
||||||
|
|
||||||
|
// let items = ipjs_neigh::json::get(ip, dev, &ipjs_states)
|
||||||
|
// .await
|
||||||
|
// .context("Calling ip -j neigh failed")?;
|
||||||
|
|
||||||
|
// let lines = items.into_iter().map(Into::into).collect();
|
||||||
|
|
||||||
|
// Ok(lines)
|
||||||
|
// }
|
||||||
|
|
||||||
/// the atomic get_macs. handle ONE thing only.
|
/// the atomic get_macs. handle ONE thing only.
|
||||||
pub async fn get_mac(
|
// 17 - 25 ms full
|
||||||
|
pub async fn _get_mac(
|
||||||
ip: Option<IpAddr>,
|
ip: Option<IpAddr>,
|
||||||
dev: Option<&str>,
|
dev: Option<&str>,
|
||||||
state: &[NUDState],
|
state: &[NUDState],
|
||||||
@@ -128,57 +138,36 @@ pub async fn get_mac(
|
|||||||
args.push(nud.as_ip_neigh_arg().into());
|
args.push(nud.as_ip_neigh_arg().into());
|
||||||
}
|
}
|
||||||
let cmd = "ip";
|
let cmd = "ip";
|
||||||
let out = exec_command(cmd, args.iter().map(String::as_str).collect::<Vec<_>>()).await?;
|
let mut u = tokio::process::Command::new(cmd);
|
||||||
|
u.args(args);
|
||||||
|
let out = u.output().await?;
|
||||||
|
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
return Err(error::Error::CommandFailed {
|
bail!(String::from_utf8_lossy(&out.stderr).into_owned());
|
||||||
cmd,
|
|
||||||
args,
|
|
||||||
status: out.status.code(),
|
|
||||||
stderr: String::from_utf8_lossy(&out.stderr).into(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let lines = String::from_utf8_lossy(&out.stdout);
|
let lines = String::from_utf8_lossy(&out.stdout);
|
||||||
let parsed = lines.lines().flat_map(arpparse::parse_ip_neigh_line);
|
let parsed = lines.lines().flat_map(arpparse::parse_ip_neigh_line);
|
||||||
let rows: Vec<IpNeighLine> = if let Some(d) = dev {
|
let rows: Vec<IpNeighLine> = if let Some(d) = dev {
|
||||||
parsed.map(IpNeighLine::with_dev(d)).collect()
|
parsed.map(IpNeighLine::_with_dev(d)).collect()
|
||||||
} else {
|
} else {
|
||||||
parsed.collect()
|
parsed.collect()
|
||||||
};
|
};
|
||||||
Ok(rows)
|
Ok(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// 15 - 20 ms full
|
||||||
/// get macs where you just run ip neigh then rust handles the filtering (faster than get mac)
|
pub async fn get_mac(
|
||||||
pub async fn get_macs_rust(
|
ip: Option<IpAddr>,
|
||||||
machine_names: Option<&str>,
|
dev: Option<&str>,
|
||||||
ips: Option<&[IpAddr]>,
|
state: &[NUDState],
|
||||||
devs: Option<&[&str]>,
|
|
||||||
states: Option<&[NUDState]>,
|
|
||||||
) -> Result<Vec<IpNeighLine>> {
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
let mut ip_map: HashSet<IpAddr> = HashSet::new();
|
let state2: Vec<neighbor::NUDState> = state.iter().copied().map(Into::into).collect();
|
||||||
let mut machine_map = HashSet::new();
|
Ok(neighbor::nl::get(ip, dev, &state2)
|
||||||
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
|
.await
|
||||||
|
.context("rtnetlink failed")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|f| f.into_iter().flatten())
|
.map(Into::into)
|
||||||
.collect()
|
.collect())
|
||||||
} */
|
// how did i just do that
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod dev;
|
pub mod dev;
|
||||||
pub mod leases;
|
pub mod leases;
|
||||||
pub mod macs;
|
pub mod macs;
|
||||||
|
pub mod parser;
|
||||||
|
|
||||||
pub use leases::*;
|
pub use leases::*;
|
||||||
pub use macs::*;
|
pub use macs::*;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pub enum QueryType {
|
|||||||
Unknown(String),
|
Unknown(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_query(q: String) -> QueryType {
|
pub async fn parse_query(q: String) -> QueryType {
|
||||||
let s = if cfg!(feature = "very-smart-parsing") {
|
let s = if cfg!(feature = "very-smart-parsing") {
|
||||||
crate::utils::parse::extract_host(&q)
|
crate::utils::parse::extract_host(&q)
|
||||||
} else {
|
} else {
|
||||||
@@ -36,7 +36,7 @@ pub fn parse_query(q: String) -> QueryType {
|
|||||||
return QueryType::Nud(state);
|
return QueryType::Nud(state);
|
||||||
}
|
}
|
||||||
// 4) Known device? prefer dev first
|
// 4) Known device? prefer dev first
|
||||||
if has_dev(s) {
|
if has_dev(s).await {
|
||||||
return QueryType::Dev(s.to_string());
|
return QueryType::Dev(s.to_string());
|
||||||
}
|
}
|
||||||
// Default: name last // it will fail also
|
// Default: name last // it will fail also
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
use axum::{http::header, response::IntoResponse};
|
|
||||||
|
|
||||||
pub async fn serve_js(content: &'static str) -> impl IntoResponse {
|
|
||||||
(
|
|
||||||
[
|
|
||||||
(header::CONTENT_TYPE, "application/javascript"),
|
|
||||||
(header::CACHE_CONTROL, "public, max-age=300"),
|
|
||||||
],
|
|
||||||
content,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -41,13 +41,13 @@ impl From<WakeTargetResult> for RouteWakeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RouteWakeTarget {
|
impl RouteWakeTarget {
|
||||||
pub fn to_incomplete(self) -> RouteWakeResult {
|
pub const fn to_incomplete(self) -> RouteWakeResult {
|
||||||
RouteWakeResult {
|
RouteWakeResult {
|
||||||
target: self,
|
target: self,
|
||||||
status: RouteWakeStatus::Incomplete,
|
status: RouteWakeStatus::Incomplete,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_incomplete(&self) -> bool {
|
pub const fn is_incomplete(&self) -> bool {
|
||||||
!matches!(
|
!matches!(
|
||||||
self,
|
self,
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! why did my Head Ass split these into two.
|
||||||
|
|
||||||
pub mod impls;
|
pub mod impls;
|
||||||
use std::{io, net::IpAddr};
|
use std::{io, net::IpAddr};
|
||||||
|
|
||||||
@@ -22,21 +24,21 @@ pub enum WakeStatus {
|
|||||||
WrongSize,
|
WrongSize,
|
||||||
}
|
}
|
||||||
impl WakeTarget {
|
impl WakeTarget {
|
||||||
fn _new(ip: IpAddr, mac: MacAddr) -> Self {
|
const fn _new(ip: IpAddr, mac: MacAddr) -> Self {
|
||||||
Self { ip, mac }
|
Self { ip, mac }
|
||||||
}
|
}
|
||||||
fn good(self) -> WakeTargetResult {
|
const fn good(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::Success)
|
WakeTargetResult::new(self, WakeStatus::Success)
|
||||||
}
|
}
|
||||||
fn bad(self) -> WakeTargetResult {
|
const fn bad(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::WrongSize)
|
WakeTargetResult::new(self, WakeStatus::WrongSize)
|
||||||
}
|
}
|
||||||
fn errored(self) -> WakeTargetResult {
|
const fn errored(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
|
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl WakeTargetResult {
|
impl WakeTargetResult {
|
||||||
fn new(target: WakeTarget, status: WakeStatus) -> Self {
|
const fn new(target: WakeTarget, status: WakeStatus) -> Self {
|
||||||
Self { target, status }
|
Self { target, status }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { filter_array } from "./status.js";
|
// import { filter_array } from "./status.js";
|
||||||
|
|
||||||
export const qs = new URLSearchParams(location.search);
|
export const qs = new URLSearchParams(location.search);
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export function setPill(kind, text) {
|
|||||||
|
|
||||||
export function setLink(name, clear) {
|
export function setLink(name, clear) {
|
||||||
const url = new URL(location.href);
|
const url = new URL(location.href);
|
||||||
if (clear) url.searchParams.forEach((_, k) => url.searchParams.delete(k)); // FUCK
|
if (clear) url.search = ""; // yo
|
||||||
/* const hasExtraFilters = filter_array.some(
|
/* const hasExtraFilters = filter_array.some(
|
||||||
(k) => url.searchParams.getAll(k).length
|
(k) => url.searchParams.getAll(k).length
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { elLeases } from "./dom.js";
|
import { elLeases } from "./dom.js";
|
||||||
|
import { rankState } from "./utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -36,7 +37,11 @@ export function renderLeases(leases) {
|
|||||||
let dotClass = "dot ok";
|
let dotClass = "dot ok";
|
||||||
if (expired) {
|
if (expired) {
|
||||||
dotClass = "dot bad";
|
dotClass = "dot bad";
|
||||||
} /* rank from NUDState */ else if (typeof l?.rank === "number") {
|
} else if (l?.nud_state) {
|
||||||
|
if (rankState(l.nud_state) >= 5) dotClass = "dot ok";
|
||||||
|
else if (rankState(l.nud_state) >= 2) dotClass = "dot warn";
|
||||||
|
else dotClass = "dot bad";
|
||||||
|
} else if (typeof l?.rank === "number") {
|
||||||
if (l.rank >= 5) dotClass = "dot ok";
|
if (l.rank >= 5) dotClass = "dot ok";
|
||||||
else if (l.rank >= 2) dotClass = "dot warn";
|
else if (l.rank >= 2) dotClass = "dot warn";
|
||||||
else dotClass = "dot bad";
|
else dotClass = "dot bad";
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ function updatePreview() {
|
|||||||
const raw = getName(elName);
|
const raw = getName(elName);
|
||||||
const host = extractHostLikeBackend(raw);
|
const host = extractHostLikeBackend(raw);
|
||||||
elPreview.textContent = host && host !== raw ? `→ ${host}` : "";
|
elPreview.textContent = host && host !== raw ? `→ ${host}` : "";
|
||||||
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickTarget(value) {
|
function pickTarget(value) {
|
||||||
|
|||||||
@@ -30,8 +30,7 @@
|
|||||||
<span title="available keys: name, ips, macs, devs, nuds"
|
<span title="available keys: name, ips, macs, devs, nuds"
|
||||||
>(?name=...)</span
|
>(?name=...)</span
|
||||||
>
|
>
|
||||||
on this page to view the status.<!-- and header (X-Target-Name) so either extractor
|
on this page to view the status.</span
|
||||||
path works. --></span
|
|
||||||
><span id="preview" class="tiny"></span
|
><span id="preview" class="tiny"></span
|
||||||
><a id="permalink" href="#">permalink</a>
|
><a id="permalink" href="#">permalink</a>
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user