Compare commits
2
Commits
No files matched your search
+2
-2
@@ -89,7 +89,7 @@ pub enum NUDState {
|
|||||||
|
|
||||||
impl NUDState {
|
impl NUDState {
|
||||||
/// Argument form expected by `ip neigh ... nud <state>` (lowercase)
|
/// Argument form expected by `ip neigh ... nud <state>` (lowercase)
|
||||||
pub const fn _as_ip_neigh_arg(self) -> &'static str {
|
pub const fn as_ip_neigh_arg(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
NUDState::Permanent => "permanent",
|
NUDState::Permanent => "permanent",
|
||||||
NUDState::Reachable => "reachable",
|
NUDState::Reachable => "reachable",
|
||||||
@@ -183,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()),
|
||||||
|
|||||||
+19
-3
@@ -16,9 +16,13 @@ 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, header};
|
||||||
|
use axum::middleware::{self, Next};
|
||||||
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::utils::route::serve_js;
|
use crate::utils::route::serve_js;
|
||||||
|
|
||||||
@@ -51,6 +55,17 @@ pub fn home_2_route() -> Router {
|
|||||||
.route("/home_2/dom.js", get(|| serve_js(home_2::DOM_JS)))
|
.route("/home_2/dom.js", get(|| serve_js(home_2::DOM_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();
|
||||||
|
|
||||||
|
if let Ok(val) = format!("work-time={}us", elapsed.as_micros()).parse() {
|
||||||
|
response.headers_mut().insert("Lda-Performance", val);
|
||||||
|
}
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
pub fn api_router() -> Router {
|
pub fn api_router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/status/{name}", get(status_redirect))
|
.route("/status/{name}", get(status_redirect))
|
||||||
@@ -69,4 +84,5 @@ pub fn api_router() -> Router {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.layer(middleware::from_fn(add_performance_header))
|
||||||
}
|
}
|
||||||
+46
-5
@@ -1,16 +1,57 @@
|
|||||||
use lda_ipjs::subcommands::address::json as ipjs_json;
|
|
||||||
use std::collections::HashSet;
|
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
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// 3ms
|
||||||
pub async fn get_dev() -> HashSet<String> {
|
pub async fn get_dev() -> HashSet<String> {
|
||||||
|
use std::fs;
|
||||||
|
fn get_dev() -> HashSet<String> {
|
||||||
let mut devs: HashSet<String> = HashSet::new();
|
let mut devs: HashSet<String> = HashSet::new();
|
||||||
if let Ok(items) = ipjs_json::get(None).await {
|
if let Ok(rd) = std::fs::read_dir("/sys/class/net") {
|
||||||
for item in items {
|
for e in rd.flatten() {
|
||||||
if item.ifname != "lo" && !item.ifname.is_empty() {
|
if e.file_type()
|
||||||
devs.insert(item.ifname);
|
.map(|ft| {
|
||||||
|
if ft.is_symlink() {
|
||||||
|
// true
|
||||||
|
fs::metadata(e.path()).map(|m| m.is_dir()).unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
ft.is_dir()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
&& let Ok(name) = e.file_name().into_string()
|
||||||
|
&& name != "lo"
|
||||||
|
&& !name.is_empty()
|
||||||
|
{
|
||||||
|
devs.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Ok(txt) = std::fs::read_to_string("/proc/net/dev") {
|
||||||
|
for line in txt.lines().skip(2) {
|
||||||
|
if let Some((name, _rest)) = line.split_once(':') {
|
||||||
|
let n = name.trim().to_string();
|
||||||
|
if n != "lo" && !n.is_empty() {
|
||||||
|
devs.insert(n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
devs
|
devs
|
||||||
|
}
|
||||||
|
tokio::task::spawn_blocking(get_dev)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn devs_sorted() -> Vec<String> {
|
pub async fn devs_sorted() -> Vec<String> {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
+48
-10
@@ -1,8 +1,7 @@
|
|||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
|
|
||||||
use crate::arpparse::{IpNeighLine, NUDState};
|
use crate::arpparse::{self, IpNeighLine, NUDState};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use lda_ipjs::subcommands::neighbor as ipjs_neigh;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
@@ -95,21 +94,60 @@ pub async fn get_macs(
|
|||||||
Ok(ip_filtered)
|
Ok(ip_filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /// the atomic get_macs. handle ONE thing only.
|
||||||
|
// 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(
|
pub async fn get_mac(
|
||||||
ip: Option<IpAddr>,
|
ip: Option<IpAddr>,
|
||||||
dev: Option<&str>,
|
dev: Option<&str>,
|
||||||
state: &[NUDState],
|
state: &[NUDState],
|
||||||
) -> Result<Vec<IpNeighLine>> {
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
let ipjs_states: Vec<ipjs_neigh::NUDState> = state.iter().copied().map(Into::into).collect();
|
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
||||||
|
if let Some(ip) = ip {
|
||||||
|
args.push("to".into());
|
||||||
|
args.push(ip.to_string());
|
||||||
|
}
|
||||||
|
if let Some(d) = dev {
|
||||||
|
args.push("dev".into());
|
||||||
|
args.push(d.to_string());
|
||||||
|
}
|
||||||
|
for nud in state {
|
||||||
|
args.push("nud".into());
|
||||||
|
args.push(nud.as_ip_neigh_arg().into());
|
||||||
|
}
|
||||||
|
let cmd = "ip";
|
||||||
|
let mut u = tokio::process::Command::new(cmd);
|
||||||
|
u.args(args);
|
||||||
|
let out = u.output().await?;
|
||||||
|
|
||||||
let items = ipjs_neigh::json::get(ip, dev, &ipjs_states)
|
if !out.status.success() {
|
||||||
.await
|
bail!(String::from_utf8_lossy(&out.stderr).into_owned());
|
||||||
.context("Calling ip -j neigh failed")?;
|
}
|
||||||
|
|
||||||
let lines = items.into_iter().map(Into::into).collect();
|
let lines = String::from_utf8_lossy(&out.stdout);
|
||||||
|
let parsed = lines.lines().flat_map(arpparse::parse_ip_neigh_line);
|
||||||
Ok(lines)
|
let rows: Vec<IpNeighLine> = if let Some(d) = dev {
|
||||||
|
parsed.map(IpNeighLine::with_dev(d)).collect()
|
||||||
|
} else {
|
||||||
|
parsed.collect()
|
||||||
|
};
|
||||||
|
Ok(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
Reference in new issue
Block a user