moga + fmt/clippy
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Kill wakey by PID quietly. Prefer pidof (BusyBox), fallback to ps/awk.
|
||||||
|
|
||||||
|
# 1) Try pidof
|
||||||
|
pids="$(pidof wakey 2>/dev/null)"
|
||||||
|
|
||||||
|
# 2) Fallback: ps | awk (avoid matching awk/grep themselves)
|
||||||
|
if [ -z "$pids" ]; then
|
||||||
|
pids="$(ps w 2>/dev/null | awk '/\/\.bin\/wakey/ && $0 !~ /awk/ {print $1}')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -n "$pids" ] || exit 0
|
||||||
|
|
||||||
|
# Send TERM first
|
||||||
|
kill -TERM $pids 2>/dev/null || true
|
||||||
|
|
||||||
|
# Optional: hard kill if still alive after a short grace
|
||||||
|
sleep 0.2
|
||||||
|
remain=""
|
||||||
|
for p in $pids; do
|
||||||
|
kill -0 "$p" 2>/dev/null && remain="$remain $p"
|
||||||
|
done
|
||||||
|
[ -z "$remain" ] || kill -KILL $remain 2>/dev/null || true
|
||||||
|
|
||||||
|
exit 0
|
||||||
+4
-2
@@ -18,13 +18,15 @@ use strum::{Display, EnumString};
|
|||||||
use crate::arpparse::error::IPNeighParseError;
|
use crate::arpparse::error::IPNeighParseError;
|
||||||
mod error;
|
mod error;
|
||||||
mod r#impl; // custom (de)serialization impls
|
mod r#impl; // custom (de)serialization impls
|
||||||
|
|
||||||
/// ip neigh has some cool shit.
|
/// ip neigh has some cool shit.
|
||||||
|
///
|
||||||
/// IP
|
/// IP
|
||||||
/// dev DEV | None
|
/// dev DEV | None
|
||||||
/// lladdr MAC | None
|
/// lladdr MAC | None
|
||||||
/// status { permanent | noarp | stale | reachable | none | incomplete | delay | probe | failed } (ip neigh help)
|
/// status { permanent | noarp | stale | reachable | none | incomplete | delay | probe | failed } (ip neigh help)
|
||||||
/// so you can see its damn good
|
|
||||||
///
|
///
|
||||||
|
/// so you can see its damn good
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, serde::Serialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, serde::Serialize)]
|
||||||
pub struct IpNeighLine {
|
pub struct IpNeighLine {
|
||||||
@@ -43,7 +45,7 @@ pub fn ser_opm<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// deserialize an [`Option<MacAddr>`]
|
/// deserialize an [`Option<MacAddr>`]
|
||||||
pub fn des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
pub fn _des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
|
|||||||
+17
-7
@@ -7,15 +7,15 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
mod arpparse;
|
mod arpparse;
|
||||||
|
mod route;
|
||||||
mod r#static;
|
mod r#static;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod route;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
route::DeviceQuery,
|
route::DeviceQuery,
|
||||||
utils::{ping::ping_ip, query::get_macs_2_1, wake::wake},
|
utils::{ping::ping_ip, query::get_macs_2_1, wake::wake},
|
||||||
};
|
};
|
||||||
use r#static as st;
|
use r#static as st;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
const MACHINE_NAME: &str = "lda.lan";
|
const MACHINE_NAME: &str = "lda.lan";
|
||||||
|
|
||||||
@@ -49,7 +49,11 @@ async fn wake_handler(
|
|||||||
Query(DeviceQuery { name, .. }): Query<DeviceQuery>,
|
Query(DeviceQuery { name, .. }): Query<DeviceQuery>,
|
||||||
) -> axum::response::Result<impl IntoResponse> {
|
) -> axum::response::Result<impl IntoResponse> {
|
||||||
match wake(name.as_deref().unwrap_or(MACHINE_NAME)).await {
|
match wake(name.as_deref().unwrap_or(MACHINE_NAME)).await {
|
||||||
Ok(x) if x > 0 => Ok((StatusCode::ACCEPTED, format!("{x} packets sent!"))),
|
Ok(0) => Err((StatusCode::NOT_FOUND, "No packets sent!").into()),
|
||||||
|
Ok(x) => Ok((
|
||||||
|
StatusCode::ACCEPTED,
|
||||||
|
format!("{x} packet{s} sent!", s = if x > 1 { "s" } else { "" }),
|
||||||
|
)),
|
||||||
_ => Err((StatusCode::GATEWAY_TIMEOUT, "Wake failed").into()),
|
_ => Err((StatusCode::GATEWAY_TIMEOUT, "Wake failed").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,17 +99,17 @@ pub async fn status_build(machine_name: &str) -> String {
|
|||||||
}
|
}
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> color_eyre::Result<()> {
|
async fn entry() -> io::Result<()> {
|
||||||
use crate::route::{api_status, get_status_2, home_2, home_2_route};
|
use crate::route::{api_status, devs_router, get_status_2, home_2, home_2_route};
|
||||||
|
|
||||||
color_eyre::install()?;
|
|
||||||
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("/wake", post(wake_handler))
|
.route("/wake", post(wake_handler))
|
||||||
.route("/status", get(get_status_2))
|
.route("/status", get(get_status_2))
|
||||||
.merge(api_status());
|
.merge(api_status())
|
||||||
|
.route("/api/devs", get(devs_router));
|
||||||
|
|
||||||
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?;
|
||||||
@@ -124,3 +128,9 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
"OS not supported! run this on your ahh router!"
|
"OS not supported! run this on your ahh router!"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn main() -> color_eyre::Result<()> {
|
||||||
|
color_eyre::install()?;
|
||||||
|
Ok(entry()?)
|
||||||
|
}
|
||||||
|
|||||||
+75
-11
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -9,17 +10,20 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use axum_extra::extract::Query;
|
use axum_extra::extract::Query;
|
||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
|
use serde::ser::Serializer;
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
|
|
||||||
// use serde_with::skip_serializing_none;
|
|
||||||
// use serde::{Deserialize, de};
|
|
||||||
// use serde_with::{OneOrMany, serde_as};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
MACHINE_NAME,
|
MACHINE_NAME,
|
||||||
arpparse::{self, NUDState, des_opm},
|
arpparse::{self, NUDState},
|
||||||
st, status_build,
|
st, status_build,
|
||||||
utils::{de_many, query::get_macs},
|
utils::{
|
||||||
|
de_many,
|
||||||
|
query::{
|
||||||
|
dev::{self, has_dev},
|
||||||
|
get_macs,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn home_2() -> Html<&'static str> {
|
pub async fn home_2() -> Html<&'static str> {
|
||||||
@@ -58,8 +62,8 @@ pub struct DeviceQuery {
|
|||||||
// Accept single or many; ignore blanks
|
// Accept single or many; ignore blanks
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
||||||
ip: Vec<IpAddr>,
|
ip: Vec<IpAddr>,
|
||||||
#[serde(default, deserialize_with = "des_opm")]
|
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
||||||
mac: Option<MacAddr>,
|
mac: Vec<MacAddr>,
|
||||||
/// optional interface filter (e.g., br-lan)
|
/// optional interface filter (e.g., br-lan)
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
||||||
pub dev: Vec<String>,
|
pub dev: Vec<String>,
|
||||||
@@ -87,6 +91,19 @@ pub struct Filters {
|
|||||||
dev: Vec<String>,
|
dev: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
nud: Vec<NUDState>,
|
nud: Vec<NUDState>,
|
||||||
|
#[serde(
|
||||||
|
skip_serializing_if = "Vec::is_empty",
|
||||||
|
serialize_with = "serialize_macs"
|
||||||
|
)]
|
||||||
|
mac: Vec<MacAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
@@ -98,7 +115,12 @@ pub struct StatusError {
|
|||||||
pub async fn get_status_json(
|
pub async fn get_status_json(
|
||||||
// p: Option<Path<NamePath>>,
|
// p: Option<Path<NamePath>>,
|
||||||
Query(DeviceQuery {
|
Query(DeviceQuery {
|
||||||
name, ip, dev, nud, ..
|
name,
|
||||||
|
ip,
|
||||||
|
dev,
|
||||||
|
nud,
|
||||||
|
mac,
|
||||||
|
..
|
||||||
}): Query<DeviceQuery>,
|
}): Query<DeviceQuery>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
fn to_opts<T: Clone>(slice: &[T]) -> Vec<Option<T>> {
|
fn to_opts<T: Clone>(slice: &[T]) -> Vec<Option<T>> {
|
||||||
@@ -122,7 +144,12 @@ pub async fn get_status_json(
|
|||||||
let dev_opts: Vec<Option<String>> = to_opts(&dev);
|
let dev_opts: Vec<Option<String>> = to_opts(&dev);
|
||||||
let nud_opts: Vec<Option<NUDState>> = to_opts(&nud);
|
let nud_opts: Vec<Option<NUDState>> = to_opts(&nud);
|
||||||
|
|
||||||
let filters = Filters { ip, dev, nud };
|
let filters = Filters {
|
||||||
|
ip,
|
||||||
|
dev,
|
||||||
|
nud,
|
||||||
|
mac: mac.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
// Run combinations of dev/nud and merge results
|
// Run combinations of dev/nud and merge results
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
@@ -141,7 +168,12 @@ pub async fn get_status_json(
|
|||||||
.await
|
.await
|
||||||
.map(|v| v.into_iter().flatten().collect::<Vec<_>>())
|
.map(|v| v.into_iter().flatten().collect::<Vec<_>>())
|
||||||
{
|
{
|
||||||
Ok(table) => {
|
Ok(mut table) => {
|
||||||
|
// Optional MAC post-filtering if provided
|
||||||
|
if !mac.is_empty() {
|
||||||
|
let wanted: HashSet<MacAddr> = mac.into_iter().collect();
|
||||||
|
table.retain(|row| row.mac.map(|m| wanted.contains(&m)).unwrap_or(false));
|
||||||
|
}
|
||||||
// let canonical = format!("/api/status?name={name}");
|
// let canonical = format!("/api/status?name={name}");
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
@@ -175,6 +207,7 @@ pub fn api_status() -> Router {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/api/status/{name}", get(status_redirect)) // api/status should be like the entire ip neigh br lan like idk like
|
.route("/api/status/{name}", get(status_redirect)) // api/status should be like the entire ip neigh br lan like idk like
|
||||||
.route("/api/status", get(get_status_json))
|
.route("/api/status", get(get_status_json))
|
||||||
|
.route("/api/smart/{q}", get(status_smart_redirect))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_status_2(q: Query<DeviceQuery>) -> Html<String> {
|
pub async fn get_status_2(q: Query<DeviceQuery>) -> Html<String> {
|
||||||
@@ -186,3 +219,34 @@ pub async fn get_status_2(q: Query<DeviceQuery>) -> Html<String> {
|
|||||||
};
|
};
|
||||||
Html(status_build(&name).await)
|
Html(status_build(&name).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Smart redirect: accept IP, MAC, dev, or NUD state and redirect to /api/status accordingly
|
||||||
|
pub async fn status_smart_redirect(Path(q): Path<String>) -> Redirect {
|
||||||
|
let s = q.trim();
|
||||||
|
// 1) IP
|
||||||
|
if let Ok(ip) = s.parse::<IpAddr>() {
|
||||||
|
return Redirect::to(&format!("/api/status?ip={ip}"));
|
||||||
|
}
|
||||||
|
// 2) MAC
|
||||||
|
if let Ok(mac) = s.parse::<MacAddr>() {
|
||||||
|
return Redirect::to(&format!("/api/status?mac={mac}"));
|
||||||
|
}
|
||||||
|
// 3) NUD state (reachable, stale, ...)
|
||||||
|
if let Ok(state) = s.parse::<NUDState>() {
|
||||||
|
return Redirect::to(&format!("/api/status?nud={state}"));
|
||||||
|
}
|
||||||
|
// 4) Known device? prefer dev first
|
||||||
|
if has_dev(s) {
|
||||||
|
return Redirect::to(&format!("/api/status?dev={}", urlencoding::encode(s)));
|
||||||
|
}
|
||||||
|
// 5) Try DNS: if it resolves, treat as name
|
||||||
|
if tokio::net::lookup_host((s, 0)).await.is_ok() {
|
||||||
|
return Redirect::to(&format!("/api/status?name={}", urlencoding::encode(s)));
|
||||||
|
}
|
||||||
|
// Default: name last
|
||||||
|
Redirect::to(&format!("/api/status?name={}", urlencoding::encode(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn devs_router() -> Json<Vec<String>> {
|
||||||
|
dev::devs_sorted().into()
|
||||||
|
}
|
||||||
|
|||||||
+12
-10
@@ -1,16 +1,15 @@
|
|||||||
pub const LDA_MACS: [[u8; 6]; 2] = [
|
// pub const LDA_MACS: [[u8; 6]; 2] = [
|
||||||
// ether
|
// // ether
|
||||||
[0x04, 0x7c, 0x16, 0x79, 0x6d, 0xee],
|
// [0x04, 0x7c, 0x16, 0x79, 0x6d, 0xee],
|
||||||
// wifi
|
// // wifi
|
||||||
[0xbc, 0x09, 0x1b, 0xec, 0x65, 0xd0],
|
// [0xbc, 0x09, 0x1b, 0xec, 0x65, 0xd0],
|
||||||
];
|
// ];
|
||||||
|
|
||||||
/// is it time to lookup host lda.lan for this...
|
/// is it time to lookup host lda.lan for this...
|
||||||
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;
|
||||||
use std::{net::IpAddr, sync::LazyLock};
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use macaddr::MacAddr;
|
|
||||||
pub mod error;
|
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
|
||||||
@@ -21,7 +20,10 @@ pub mod ping;
|
|||||||
pub async fn get_ips(machine_name: &str) -> error::Result<Vec<IpAddr>> {
|
pub async fn get_ips(machine_name: &str) -> error::Result<Vec<IpAddr>> {
|
||||||
let it = tokio::net::lookup_host((machine_name, 0))
|
let it = tokio::net::lookup_host((machine_name, 0))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| error::Error::DnsResolve { name: machine_name.to_string(), source: e })?;
|
.map_err(|e| error::Error::DnsResolve {
|
||||||
|
name: machine_name.to_string(),
|
||||||
|
source: e,
|
||||||
|
})?;
|
||||||
Ok(it.map(|c| c.ip()).collect())
|
Ok(it.map(|c| c.ip()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-8
@@ -20,13 +20,19 @@ pub enum Error {
|
|||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::DnsResolve { name, source } => write!(f, "DNS resolve failed for {name}: {source}"),
|
Error::DnsResolve { name, source } => {
|
||||||
Error::CommandFailed { cmd, args, status, stderr } => {
|
write!(f, "DNS resolve failed for {name}: {source}")
|
||||||
let code = status.map(|c| c.to_string()).unwrap_or_else(|| "signal".into());
|
}
|
||||||
write!(
|
Error::CommandFailed {
|
||||||
f,
|
cmd,
|
||||||
"{cmd} {args:?} failed (status: {code}): {stderr}",
|
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}"),
|
Error::Io(e) => write!(f, "IO error: {e}"),
|
||||||
}
|
}
|
||||||
@@ -44,5 +50,7 @@ impl std::error::Error for Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
impl From<io::Error> for Error {
|
||||||
fn from(e: io::Error) -> Self { Error::Io(e) }
|
fn from(e: io::Error) -> Self {
|
||||||
|
Error::Io(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+4
-1
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use tokio::{net::{TcpStream, ToSocketAddrs}, time::timeout};
|
use tokio::{
|
||||||
|
net::{TcpStream, ToSocketAddrs},
|
||||||
|
time::timeout,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn ping_ip<T: ToSocketAddrs>(addr: T) -> bool {
|
pub async fn ping_ip<T: ToSocketAddrs>(addr: T) -> bool {
|
||||||
timeout(Duration::from_secs(1), TcpStream::connect(addr))
|
timeout(Duration::from_secs(1), TcpStream::connect(addr))
|
||||||
|
|||||||
+49
-8
@@ -5,7 +5,11 @@ use macaddr::MacAddr;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arpparse::{self, IpNeighLine, NUDState},
|
arpparse::{self, IpNeighLine, NUDState},
|
||||||
utils::{cmd::exec_command, error::{self, Error, Result}, get_ips},
|
utils::{
|
||||||
|
cmd::exec_command,
|
||||||
|
error::{self, Error, Result},
|
||||||
|
get_ips,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
|
pub async fn get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
|
||||||
@@ -76,13 +80,8 @@ pub async fn get_macs(
|
|||||||
state: Option<NUDState>,
|
state: Option<NUDState>,
|
||||||
) -> Result<Vec<IpNeighLine>> {
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
// Collect IPs early (before any await) to avoid holding generics across await points
|
// Collect IPs early (before any await) to avoid holding generics across await points
|
||||||
let ip_list: Option<Vec<IpAddr>> = ips.map(|slice| {
|
let ip_list: Option<Vec<IpAddr>> =
|
||||||
slice
|
ips.map(|slice| slice.iter().copied().map(|ip| ip.to_canonical()).collect());
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.map(|ip| ip.to_canonical())
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Resolve by machine name if no IPs provided but we have a name
|
// Resolve by machine name if no IPs provided but we have a name
|
||||||
let ip_list = match (ip_list, machine_name) {
|
let ip_list = match (ip_list, machine_name) {
|
||||||
@@ -140,3 +139,45 @@ pub async fn get_macs(
|
|||||||
run_one(None).await
|
run_one(None).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod dev {
|
||||||
|
use std::{collections::HashSet, sync::LazyLock};
|
||||||
|
|
||||||
|
pub fn get_dev() -> HashSet<String> {
|
||||||
|
// Prefer /sys/class/net, fallback to /proc/net/dev; filter out loopback
|
||||||
|
let mut devs: HashSet<String> = HashSet::new();
|
||||||
|
if let Ok(rd) = std::fs::read_dir("/sys/class/net") {
|
||||||
|
for e in rd.flatten() {
|
||||||
|
if 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) {
|
||||||
|
// skip headers
|
||||||
|
if let Some((name, _rest)) = line.split_once(':') {
|
||||||
|
let n = name.trim().to_string();
|
||||||
|
if n != "lo" && !n.is_empty() {
|
||||||
|
devs.insert(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
devs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static DEVS: LazyLock<HashSet<String>> = LazyLock::new(get_dev);
|
||||||
|
|
||||||
|
pub fn devs_sorted() -> Vec<String> {
|
||||||
|
let mut v: Vec<String> = DEVS.iter().cloned().collect();
|
||||||
|
v.sort();
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_dev(name: &str) -> bool {
|
||||||
|
DEVS.contains(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -2,13 +2,13 @@ use std::{io, net::IpAddr};
|
|||||||
|
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
use crate::utils::{query::get_macs_2_mac, LDA_MACS_2};
|
use crate::utils::query::get_macs_2_mac;
|
||||||
|
|
||||||
pub async fn wake(machine_name: &str) -> io::Result<u32> {
|
pub async fn wake(machine_name: &str) -> io::Result<u32> {
|
||||||
let suh = UdpSocket::bind("0.0.0.0:0").await?;
|
let suh = UdpSocket::bind("0.0.0.0:0").await?;
|
||||||
suh.set_broadcast(true)?;
|
suh.set_broadcast(true)?;
|
||||||
let mut macs = get_macs_2_mac(machine_name).await.unwrap_or_default();
|
let /* mut */ macs = get_macs_2_mac(machine_name).await.unwrap_or_default();
|
||||||
macs.extend(*LDA_MACS_2);
|
// macs.extend(*LDA_MACS_2);
|
||||||
let mut sent_ok = 0;
|
let mut sent_ok = 0;
|
||||||
for mac in macs {
|
for mac in macs {
|
||||||
let mb = mac.as_bytes();
|
let mb = mac.as_bytes();
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ function buildStatusUrl(name) {
|
|||||||
const u = new URL("/api/status", location.origin);
|
const u = new URL("/api/status", location.origin);
|
||||||
if (name) u.searchParams.set("name", name);
|
if (name) u.searchParams.set("name", name);
|
||||||
// forward multi-value filters from current page URL
|
// forward multi-value filters from current page URL
|
||||||
for (const k of ["ip", "dev", "nud"]) {
|
for (const k of ["ip", "dev", "nud", "mac"]) {
|
||||||
const vals = qs.getAll(k);
|
const vals = qs.getAll(k);
|
||||||
for (const v of vals) u.searchParams.append(k, v);
|
for (const v of vals) u.searchParams.append(k, v);
|
||||||
}
|
}
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {{ name: String, table: Array<{ ip, dev, mac, state }>, filters: {ip, dev, nud}} | { name: String, error }} data from /api/status */
|
/** @param {{ name?: String, table: Array<{ ip, dev, mac, state }>, filters: {ip, dev, nud, mac}} | { name?: String, error }} data from /api/status */
|
||||||
function renderStatus(data) {
|
function renderStatus(data) {
|
||||||
const tbl = document.createElement("table");
|
const tbl = document.createElement("table");
|
||||||
tbl.innerHTML = `<tr><th>IP</th><th>MAC</th><th>State</th><th>IF</th></tr>`;
|
tbl.innerHTML = `<tr><th>IP</th><th>MAC</th><th>State</th><th>IF</th></tr>`;
|
||||||
@@ -82,6 +82,8 @@ function renderStatus(data) {
|
|||||||
parts.push(`dev=[${data.filters.dev.join(", ")}]`);
|
parts.push(`dev=[${data.filters.dev.join(", ")}]`);
|
||||||
if (Array.isArray(data.filters.nud) && data.filters.nud.length)
|
if (Array.isArray(data.filters.nud) && data.filters.nud.length)
|
||||||
parts.push(`nud=[${data.filters.nud.join(", ")}]`);
|
parts.push(`nud=[${data.filters.nud.join(", ")}]`);
|
||||||
|
if (Array.isArray(data.filters.mac) && data.filters.mac.length)
|
||||||
|
parts.push(`mac=[${data.filters.mac.join(", ")}]`);
|
||||||
if (parts.length) {
|
if (parts.length) {
|
||||||
const info = document.createElement("div");
|
const info = document.createElement("div");
|
||||||
info.className = "filters";
|
info.className = "filters";
|
||||||
|
|||||||
Reference in New Issue
Block a user