THE ABSOLUTE CRIME of not committing frequently
This commit is contained in:
Vendored
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.cargo.target": "armv7-unknown-linux-musleabihf"
|
"rust-analyzer.cargo.target": "armv7-unknown-linux-musleabihf",
|
||||||
|
"rust-analyzer.diagnostics.disabled": ["unlinked-file"]
|
||||||
}
|
}
|
||||||
Generated
+1
-1
@@ -1179,7 +1179,7 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wakey"
|
name = "wakey"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wakey"
|
name = "wakey"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = ["gitea"]
|
publish = ["gitea"]
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ start() {
|
|||||||
RETRY_COUNT=0
|
RETRY_COUNT=0
|
||||||
|
|
||||||
# shellcheck disable=SC2016
|
# shellcheck disable=SC2016
|
||||||
nohup sh -c '
|
sh -c '
|
||||||
while [ "$RETRY_COUNT" -lt "$MAX_RETRIES" ]; do
|
while [ "$RETRY_COUNT" -lt "$MAX_RETRIES" ]; do
|
||||||
if fn; then
|
if fn; then
|
||||||
echo "[update_tailscale] Success at $(date)" >>"$LOGFILE"
|
echo "[update_tailscale] Success at $(date)" >>"$LOGFILE"
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,8 @@
|
|||||||
const MAC_NAME_CACHE: &str = "/tmp/wakey_mac_names.json";
|
const MAC_NAME_CACHE: &str = "/tmp/wakey_mac_names.json";
|
||||||
|
|
||||||
/// Load MAC->name cache from disk
|
/// Load MAC->name cache from disk
|
||||||
async fn load_mac_name_cache() -> io::Result<std::collections::BTreeMap<String, String>> {
|
pub(crate) async fn load_mac_name_cache() -> io::Result<std::collections::BTreeMap<String, String>>
|
||||||
|
{
|
||||||
match tokio::fs::read_to_string(MAC_NAME_CACHE).await {
|
match tokio::fs::read_to_string(MAC_NAME_CACHE).await {
|
||||||
Ok(s) => serde_json::from_str(&s).map_err(io::Error::other),
|
Ok(s) => serde_json::from_str(&s).map_err(io::Error::other),
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => Ok(Default::default()),
|
Err(e) if e.kind() == ErrorKind::NotFound => Ok(Default::default()),
|
||||||
|
|||||||
+12
-8
@@ -1,7 +1,14 @@
|
|||||||
use axum::{
|
//! braindead version v0.1.x
|
||||||
Router,
|
//!
|
||||||
routing::{get, post},
|
//! # whats next
|
||||||
};
|
//!
|
||||||
|
//! for version 2 i hope to have:
|
||||||
|
//!
|
||||||
|
//! 1. idk reworked frontend;
|
||||||
|
//! 2. incorporate ip -j;
|
||||||
|
//! 3. small 1-5 second caching;
|
||||||
|
|
||||||
|
use axum::{Router, routing::get};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
mod arpparse;
|
mod arpparse;
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
@@ -10,18 +17,15 @@ mod route;
|
|||||||
mod utils;
|
mod utils;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
const MACHINE_NAME: &str = "lda.lan";
|
|
||||||
|
|
||||||
#[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, wake_handler};
|
use crate::route::{api_router, home_2, home_2_route};
|
||||||
|
|
||||||
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("/status", get(get_status_2))
|
// .route("/status", get(get_status_2))
|
||||||
.nest("/api", api_router());
|
.nest("/api", api_router());
|
||||||
|
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
pub mod api;
|
|
||||||
pub mod devs;
|
|
||||||
pub mod dhcp;
|
|
||||||
pub mod status;
|
|
||||||
pub mod wake;
|
|
||||||
|
|
||||||
pub use crate::route::api::{DeviceQuery, api_router};
|
|
||||||
use crate::{
|
|
||||||
assets::{self},
|
|
||||||
utils::{ping::_ping_ip, wake::wake},
|
|
||||||
};
|
|
||||||
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
http::{StatusCode, header},
|
|
||||||
response::{Html, IntoResponse},
|
|
||||||
routing::get,
|
|
||||||
};
|
|
||||||
use axum_extra::extract::Query;
|
|
||||||
|
|
||||||
use crate::utils::route::serve_js;
|
|
||||||
use crate::{MACHINE_NAME, utils::_status_build};
|
|
||||||
|
|
||||||
pub async fn wake_handler(
|
|
||||||
Query(DeviceQuery { name, .. }): Query<DeviceQuery>,
|
|
||||||
) -> axum::response::Result<impl IntoResponse> {
|
|
||||||
match wake(name.as_deref().unwrap_or(MACHINE_NAME)).await {
|
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn _get_status_2(q: Query<DeviceQuery>) -> Html<String> {
|
|
||||||
let name = match q {
|
|
||||||
Query(DeviceQuery {
|
|
||||||
name: Some(name), ..
|
|
||||||
}) => name,
|
|
||||||
_ => MACHINE_NAME.to_string(),
|
|
||||||
};
|
|
||||||
Html(_status_build(&name).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn _home() -> Html<String> {
|
|
||||||
Html(format!(
|
|
||||||
r#"
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<p><a href="/home_2">Alternate UI</a></p>
|
|
||||||
<p>the machine is {}! <a href="/status">Status</a></p>
|
|
||||||
<form method="POST" action="/wake">
|
|
||||||
<button type="submit">Wake LDA</button>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"#,
|
|
||||||
if _ping_ip((MACHINE_NAME, 22)).await {
|
|
||||||
"on"
|
|
||||||
} else {
|
|
||||||
"off"
|
|
||||||
} // match get_ips(MACHINE_NAME).await {
|
|
||||||
// Ok(ips) => {
|
|
||||||
// // let addrs: Vec<SocketAddr> = ips.into_iter().map(|ip|(ip, 22).into()).collect();
|
|
||||||
// }
|
|
||||||
// Err(_) => "off",
|
|
||||||
// }
|
|
||||||
))
|
|
||||||
}
|
|
||||||
pub async fn home_2() -> Html<&'static str> {
|
|
||||||
Html(assets::HOME_2_HTML)
|
|
||||||
}
|
|
||||||
pub fn home_2_route() -> Router {
|
|
||||||
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)))
|
|
||||||
}
|
|
||||||
+52
-19
@@ -1,32 +1,63 @@
|
|||||||
|
use crate::route::error::ApiError;
|
||||||
|
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;
|
||||||
use axum::routing::post;
|
use axum::{extract::Path, response::Redirect};
|
||||||
use axum::{Router, extract::Path, response::Redirect, routing::get};
|
|
||||||
|
|
||||||
use crate::utils::route;
|
use crate::route::status::{DeviceQuery, Filters, NamePath};
|
||||||
|
use crate::utils::query::get_ips;
|
||||||
|
|
||||||
// Smart redirect: accept IP, MAC, dev, or NUD state and redirect to /api/status accordingly
|
// Smart redirect: accept IP, MAC, dev, or NUD state and redirect to /api/status accordingly
|
||||||
pub async fn status_smart_redirect(
|
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> {
|
||||||
match serde_html_form::to_string(route::status_smart_redirect(q).await) {
|
// no less bullshit
|
||||||
|
let query = match parse_query(q) {
|
||||||
|
QueryType::Ip(ip_addr) => DeviceQuery {
|
||||||
|
filter: Filters {
|
||||||
|
ips: vec![ip_addr],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryType::Mac(mac_addr) => DeviceQuery {
|
||||||
|
filter: Filters {
|
||||||
|
macs: vec![mac_addr],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryType::Dev(s) => DeviceQuery {
|
||||||
|
filter: Filters {
|
||||||
|
devs: vec![s],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryType::Nud(nudstate) => DeviceQuery {
|
||||||
|
filter: Filters {
|
||||||
|
nuds: vec![nudstate],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryType::Unknown(n) => DeviceQuery {
|
||||||
|
name: Some(n),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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((
|
||||||
StatusCode::BAD_GATEWAY,
|
StatusCode::BAD_GATEWAY,
|
||||||
Json(StatusError {
|
Json(ApiError {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
..Default::default()
|
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use crate::route::devs::*;
|
|
||||||
pub use crate::route::dhcp::*;
|
|
||||||
pub use crate::route::status::*;
|
|
||||||
pub use crate::route::wake::*;
|
|
||||||
|
|
||||||
pub async fn status_redirect(Path(NamePath { name }): Path<NamePath>) -> Redirect {
|
pub async fn status_redirect(Path(NamePath { name }): Path<NamePath>) -> Redirect {
|
||||||
Redirect::permanent(&format!(
|
Redirect::permanent(&format!(
|
||||||
"/api/status?name={name}",
|
"/api/status?name={name}",
|
||||||
@@ -34,12 +65,14 @@ pub async fn status_redirect(Path(NamePath { name }): Path<NamePath>) -> Redirec
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_router() -> Router {
|
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
||||||
Router::new()
|
get_ips(&name).await.map_or_else(
|
||||||
.route("/status/{name}", get(status_redirect))
|
|e| {
|
||||||
.route("/status", get(get_status_json))
|
ApiError {
|
||||||
.route("/dhcp_leases", get(get_dhcp_leases))
|
error: e.to_string(),
|
||||||
.route("/smart/{q}", get(status_smart_redirect))
|
}
|
||||||
.route("/devs", get(devs_router))
|
.into_response()
|
||||||
.route("/wake", post(wake_multi))
|
},
|
||||||
|
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-5
@@ -1,4 +1,8 @@
|
|||||||
use crate::{dhcpparse, route::api::StatusError, utils::parse::boolish_str};
|
use crate::{
|
||||||
|
dhcpparse::read_dhcp_leases_with_names,
|
||||||
|
route::error::ApiError,
|
||||||
|
utils::{parse::boolish_str, query::enrich_leases_with_nud_state},
|
||||||
|
};
|
||||||
use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
|
use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
|
||||||
|
|
||||||
// DHCP lease endpoints
|
// DHCP lease endpoints
|
||||||
@@ -14,19 +18,18 @@ pub async fn get_dhcp_leases(Query(raw): Query<DhcpLeasesQueryRaw>) -> impl Into
|
|||||||
.map(boolish_str)
|
.map(boolish_str)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
match dhcpparse::read_dhcp_leases_with_names().await {
|
match read_dhcp_leases_with_names().await {
|
||||||
Ok(leases_with_names) => {
|
Ok(leases_with_names) => {
|
||||||
if !include_state {
|
if !include_state {
|
||||||
return (StatusCode::OK, Json(leases_with_names)).into_response();
|
return (StatusCode::OK, Json(leases_with_names)).into_response();
|
||||||
}
|
}
|
||||||
let out = crate::utils::query::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) => (
|
||||||
StatusCode::BAD_GATEWAY,
|
StatusCode::BAD_GATEWAY,
|
||||||
Json(StatusError {
|
Json(ApiError {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
..Default::default()
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use axum::{
|
||||||
|
Json,
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct ApiError {
|
||||||
|
pub error: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for ApiError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
pub mod api;
|
||||||
|
pub mod devs;
|
||||||
|
pub mod dhcp;
|
||||||
|
pub mod error;
|
||||||
|
pub mod status;
|
||||||
|
pub mod wake;
|
||||||
|
|
||||||
|
use crate::assets;
|
||||||
|
use crate::route::api::ip;
|
||||||
|
use crate::route::api::status_redirect;
|
||||||
|
use crate::route::api::status_smart_redirect;
|
||||||
|
use crate::route::devs::devs_router;
|
||||||
|
use crate::route::dhcp::get_dhcp_leases;
|
||||||
|
use crate::route::status::get_status_json;
|
||||||
|
use crate::route::wake::wake_multi;
|
||||||
|
|
||||||
|
use axum::routing::post;
|
||||||
|
use axum::{Router, http::header, response::Html, routing::get};
|
||||||
|
|
||||||
|
use crate::utils::route::serve_js;
|
||||||
|
|
||||||
|
pub async fn home_2() -> Html<&'static str> {
|
||||||
|
Html(assets::HOME_2_HTML)
|
||||||
|
}
|
||||||
|
pub fn home_2_route() -> Router {
|
||||||
|
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 {
|
||||||
|
Router::new()
|
||||||
|
.route("/status/{name}", get(status_redirect))
|
||||||
|
.route("/status", get(get_status_json))
|
||||||
|
.route("/dhcp_leases", get(get_dhcp_leases))
|
||||||
|
.route("/smart/{q}", get(status_smart_redirect))
|
||||||
|
.route("/devs", get(devs_router))
|
||||||
|
.route("/wake", post(wake_multi))
|
||||||
|
.route("/ips/{name}", get(ip))
|
||||||
|
}
|
||||||
+42
-78
@@ -1,33 +1,21 @@
|
|||||||
use crate::{
|
use crate::route::error::ApiError;
|
||||||
arpparse::NUDState,
|
|
||||||
utils::{
|
|
||||||
parse::{de_many, serialize_macs},
|
|
||||||
query::get_macs,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use axum::{Json, http::StatusCode, response::IntoResponse};
|
use axum::{Json, http::StatusCode, response::IntoResponse};
|
||||||
use axum_extra::extract::Query;
|
use axum_extra::extract::Query;
|
||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use crate::arpparse::NUDState;
|
||||||
|
use crate::utils::parse::de_many;
|
||||||
|
use crate::utils::parse::serialize_macs;
|
||||||
|
use crate::utils::query::get_macs;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
|
||||||
pub struct DeviceQuery {
|
pub struct DeviceQuery {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
#[serde(flatten)]
|
||||||
pub ip: Vec<IpAddr>,
|
pub filter: Filters,
|
||||||
#[serde(
|
|
||||||
default,
|
|
||||||
deserialize_with = "de_many::vec_from_strs",
|
|
||||||
serialize_with = "serialize_macs"
|
|
||||||
)]
|
|
||||||
pub mac: Vec<MacAddr>,
|
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
|
||||||
pub dev: Vec<String>,
|
|
||||||
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
|
||||||
pub nud: Vec<NUDState>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Hash, Deserialize)]
|
#[derive(Debug, Default, Clone, Hash, Deserialize)]
|
||||||
@@ -43,21 +31,23 @@ pub struct Status<T> {
|
|||||||
pub filters: Filters,
|
pub filters: Filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize)]
|
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize)]
|
||||||
pub struct Filters {
|
pub struct Filters {
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
||||||
pub ip: Vec<IpAddr>,
|
pub ips: Vec<IpAddr>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
||||||
pub dev: Vec<String>,
|
pub devs: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, deserialize_with = "de_many::vec_from_strs")]
|
||||||
pub nud: Vec<NUDState>,
|
pub nuds: Vec<NUDState>,
|
||||||
#[serde(
|
#[serde(
|
||||||
skip_serializing_if = "Vec::is_empty",
|
default,
|
||||||
|
deserialize_with = "de_many::vec_from_strs",
|
||||||
serialize_with = "serialize_macs"
|
serialize_with = "serialize_macs"
|
||||||
)]
|
)]
|
||||||
pub mac: Vec<MacAddr>,
|
pub macs: Vec<MacAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated = "use ApiError"]
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Default)]
|
#[derive(Debug, Serialize, Default)]
|
||||||
pub struct StatusError {
|
pub struct StatusError {
|
||||||
@@ -68,57 +58,32 @@ pub struct StatusError {
|
|||||||
pub async fn get_status_json(
|
pub async fn get_status_json(
|
||||||
Query(DeviceQuery {
|
Query(DeviceQuery {
|
||||||
name,
|
name,
|
||||||
ip,
|
filter:
|
||||||
dev,
|
Filters {
|
||||||
nud,
|
ips,
|
||||||
mac,
|
devs,
|
||||||
|
nuds,
|
||||||
|
macs,
|
||||||
|
},
|
||||||
..
|
..
|
||||||
}): Query<DeviceQuery>,
|
}): Query<DeviceQuery>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
fn to_opts<T: Clone>(slice: &[T]) -> Vec<Option<T>> {
|
match get_macs(
|
||||||
if slice.is_empty() {
|
&name.iter().collect::<Vec<_>>(),
|
||||||
vec![None]
|
&ips,
|
||||||
} else {
|
&devs.iter().collect::<Vec<_>>(),
|
||||||
slice.iter().cloned().map(Some).collect()
|
&nuds,
|
||||||
}
|
&macs,
|
||||||
}
|
)
|
||||||
let ips_opt = if ip.is_empty() {
|
.await
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ip.clone())
|
|
||||||
};
|
|
||||||
let dev_opts: Vec<Option<String>> = to_opts(&dev);
|
|
||||||
let nud_opts: Vec<Option<NUDState>> = to_opts(&nud);
|
|
||||||
let filters = Filters {
|
|
||||||
ip,
|
|
||||||
dev,
|
|
||||||
nud,
|
|
||||||
mac: mac.clone(),
|
|
||||||
};
|
|
||||||
let mut tasks = Vec::new();
|
|
||||||
// can we jus collect the whole table instead of doing this.
|
|
||||||
for d in &dev_opts {
|
|
||||||
for n in &nud_opts {
|
|
||||||
tasks.push(get_macs(
|
|
||||||
// this i hate sm
|
|
||||||
name.as_deref(),
|
|
||||||
ips_opt.as_deref(),
|
|
||||||
d.as_deref(),
|
|
||||||
*n,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// why try join all?
|
|
||||||
match futures::future::try_join_all(tasks)
|
|
||||||
.await
|
|
||||||
.map(|v| v.into_iter().flatten().collect::<Vec<_>>())
|
|
||||||
{
|
{
|
||||||
Ok(mut table) => {
|
Ok(table) => {
|
||||||
if !mac.is_empty() {
|
let filters = Filters {
|
||||||
// mac here
|
ips,
|
||||||
let wanted: HashSet<MacAddr> = mac.into_iter().collect();
|
devs,
|
||||||
table.retain(|row| row.mac.map(|m| wanted.contains(&m)).unwrap_or(false));
|
nuds,
|
||||||
}
|
macs,
|
||||||
|
};
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
Json(Status {
|
Json(Status {
|
||||||
@@ -131,8 +96,7 @@ pub async fn get_status_json(
|
|||||||
}
|
}
|
||||||
Err(error) => (
|
Err(error) => (
|
||||||
StatusCode::BAD_GATEWAY,
|
StatusCode::BAD_GATEWAY,
|
||||||
Json(StatusError {
|
Json(ApiError {
|
||||||
name,
|
|
||||||
error: error.to_string(),
|
error: error.to_string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-20
@@ -23,9 +23,8 @@ pub struct WakeResult {
|
|||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Clone, Copy)]
|
#[derive(Debug, Serialize, Clone, Copy)]
|
||||||
pub struct WakeTargetResult {
|
pub struct WakeTargetResult {
|
||||||
pub ip: Option<IpAddr>,
|
#[serde(flatten)]
|
||||||
#[serde(serialize_with = "ser_opm")]
|
pub target: WakeTarget,
|
||||||
pub mac: Option<MacAddr>,
|
|
||||||
pub status: WakeTargetStatus,
|
pub status: WakeTargetStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,11 +40,11 @@ pub enum WakeTargetStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
pub struct WakeTarget {
|
pub struct WakeTarget {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ip: Option<IpAddr>,
|
pub ip: Option<IpAddr>,
|
||||||
#[serde(default, deserialize_with = "des_opm")]
|
#[serde(default, serialize_with = "ser_opm", deserialize_with = "des_opm")]
|
||||||
pub mac: Option<MacAddr>,
|
pub mac: Option<MacAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,21 +81,15 @@ pub async fn wake_multi_split(
|
|||||||
.await?;
|
.await?;
|
||||||
sock.set_broadcast(true)?;
|
sock.set_broadcast(true)?;
|
||||||
|
|
||||||
Ok(
|
let iter = targets.into_iter().map(async |c| {
|
||||||
futures::future::join_all(targets.into_iter().map(async |c| {
|
if c.is_incomplete() {
|
||||||
if c.is_incomplete() {
|
c.to_incomplete()
|
||||||
c.to_incomplete()
|
} else {
|
||||||
} else {
|
let t = c.try_into().expect("complete struct failed to try_into");
|
||||||
wake_one(
|
wake_one(&sock, t).await.into()
|
||||||
&sock,
|
}
|
||||||
c.try_into().expect("complete struct failed to try_into"), // type state pattern?
|
});
|
||||||
)
|
Ok(futures::future::join_all(iter).await)
|
||||||
.await
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.await,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/* #[derive(Debug, Serialize)]
|
/* #[derive(Debug, Serialize)]
|
||||||
pub struct WakeStatusLine {
|
pub struct WakeStatusLine {
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
// pub const LDA_MACS: [[u8; 6]; 2] = [
|
|
||||||
// // ether
|
|
||||||
// [0x04, 0x7c, 0x16, 0x79, 0x6d, 0xee],
|
|
||||||
// // wifi
|
|
||||||
// [0xbc, 0x09, 0x1b, 0xec, 0x65, 0xd0],
|
|
||||||
// ];
|
|
||||||
|
|
||||||
/// 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 mod wake;
|
|
||||||
|
|
||||||
use crate::utils::query::_get_macs_2_1;
|
|
||||||
|
|
||||||
pub mod cmd;
|
|
||||||
pub mod error;
|
|
||||||
/// generic so you can do "123.45.67.89:22" or "lda.lan:22" as an input
|
|
||||||
// this is so bad
|
|
||||||
pub mod ping;
|
|
||||||
pub mod query;
|
|
||||||
pub mod route;
|
|
||||||
|
|
||||||
// no custom ip deserializer needed when using axum_extra::extract::Query
|
|
||||||
// but we add a generic one to ignore blanks and accept OneOrMany
|
|
||||||
pub(crate) mod parse;
|
|
||||||
|
|
||||||
pub async fn _status_build(machine_name: &str) -> String {
|
|
||||||
let formatted_macs = match _get_macs_2_1(machine_name).await {
|
|
||||||
Ok(table) => {
|
|
||||||
let the: String = table
|
|
||||||
.iter()
|
|
||||||
.map(|(ip, mac, state)| {
|
|
||||||
let mac_str = // if let Some(mac) = mac {
|
|
||||||
mac.to_string()
|
|
||||||
// } else {
|
|
||||||
// "None".into()
|
|
||||||
// }
|
|
||||||
;
|
|
||||||
format!(
|
|
||||||
"<tr><td>{ip}</td><td>{mac_str}</td><td>{state}</td></tr>",
|
|
||||||
state = state._dumber_state()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
format!(
|
|
||||||
r#"<p>info of {machine_name}:</p>
|
|
||||||
<table>
|
|
||||||
<tr><th>IP</th><th>MAC</th><th>State</th></tr>
|
|
||||||
{the}
|
|
||||||
</table>"#
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Err(e) => format!("<p>errors getting table for {machine_name}: {e}</p>"),
|
|
||||||
};
|
|
||||||
|
|
||||||
format!(
|
|
||||||
r#"
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
{formatted_macs}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// pub const LDA_MACS: [[u8; 6]; 2] = [
|
||||||
|
// // ether
|
||||||
|
// [0x04, 0x7c, 0x16, 0x79, 0x6d, 0xee],
|
||||||
|
// // wifi
|
||||||
|
// [0xbc, 0x09, 0x1b, 0xec, 0x65, 0xd0],
|
||||||
|
// ];
|
||||||
|
|
||||||
|
/// 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 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
|
||||||
|
// this is so bad
|
||||||
|
pub mod ping;
|
||||||
|
pub mod query;
|
||||||
|
pub mod query_parser;
|
||||||
|
pub mod route;
|
||||||
|
|
||||||
|
// no custom ip deserializer needed when using axum_extra::extract::Query
|
||||||
|
// but we add a generic one to ignore blanks and accept OneOrMany
|
||||||
|
pub(crate) mod parse;
|
||||||
+2
-2
@@ -7,7 +7,7 @@ use tokio::{
|
|||||||
time::timeout,
|
time::timeout,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{arpparse::NUDState, utils::query::get_macs};
|
use crate::{arpparse::NUDState, utils::query::get_mac};
|
||||||
|
|
||||||
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))
|
||||||
@@ -19,7 +19,7 @@ pub async fn _ping_ip_2<T: ToSocketAddrs>(_addr: T) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
|
pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
|
||||||
match get_macs(None, Some(&[addr.into()]), None, None).await {
|
match get_mac(Some(addr.into()), None, None).await {
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
Ok(l) => l
|
Ok(l) => l
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::arpparse::NUDState;
|
use crate::arpparse::NUDState;
|
||||||
use crate::dhcpparse::DhcpLeaseLine;
|
use crate::dhcpparse::DhcpLeaseLine;
|
||||||
|
use crate::utils::query::get_macs;
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
@@ -14,11 +15,10 @@ pub struct DhcpLeaseOut {
|
|||||||
|
|
||||||
/// 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> {
|
||||||
use crate::utils::query::macs::get_macs;
|
|
||||||
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, u8)> =
|
||||||
std::collections::HashMap::new();
|
std::collections::HashMap::new();
|
||||||
if let Ok(rows) = get_macs(None, Some(&ips), None, None).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();
|
||||||
|
|||||||
+99
-79
@@ -1,13 +1,14 @@
|
|||||||
|
use macaddr::MacAddr;
|
||||||
|
|
||||||
use crate::arpparse::{self, IpNeighLine, NUDState};
|
use crate::arpparse::{self, IpNeighLine, NUDState};
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
cmd::exec_command,
|
cmd::exec_command,
|
||||||
error::{self, Error, Result},
|
error::{self, Result},
|
||||||
};
|
};
|
||||||
use macaddr::MacAddr;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
pub async fn get_ips(machine_name: &str) -> error::Result<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 {
|
.map_err(|e| error::Error::DnsResolve {
|
||||||
@@ -17,84 +18,103 @@ pub async fn get_ips(machine_name: &str) -> error::Result<impl Iterator<Item = I
|
|||||||
.map(|c| c.ip()))
|
.map(|c| c.ip()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn _get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
|
// #[deprecated(
|
||||||
Ok(get_macs_1(machine_name)
|
// since = "0.1.5",
|
||||||
.await?
|
// note = "just call once everything with get mac
|
||||||
.into_iter()
|
// and then filter it bro WHY DO YOU EVEN DO TS"
|
||||||
.filter_map(
|
// )]
|
||||||
|IpNeighLine {
|
// good now
|
||||||
ip,
|
//
|
||||||
dev: _,
|
// Current logic: When filtering by exactly 1 dev/mac, exclude entries missing that field.
|
||||||
mac,
|
// This is because missing dev/mac usually means the entry is incomplete/transient.
|
||||||
state,
|
//
|
||||||
}| mac.map(|mac| (ip, mac, state)),
|
// when there is only one MACs (getmac got some), the result will not have them fields.
|
||||||
)
|
// so there are three cases:
|
||||||
.collect())
|
//
|
||||||
}
|
// 1. dont got nothing: take all of them (macset.is_empty())
|
||||||
|
// 2. exactly one: pre-filtered by ip, everything matches,
|
||||||
pub async fn get_macs_2_mac(machine_name: &str) -> Result<HashSet<MacAddr>> {
|
// devset.len() != 1 returns false, but then it works????
|
||||||
Ok(get_macs_1(machine_name)
|
// OH THIS fuckass code i added it in the get_mac
|
||||||
.await?
|
// 3. devset.len() > 1. if none then absolutely not match,
|
||||||
.into_iter()
|
// if some then check with the set; thats normal
|
||||||
.filter_map(
|
|
||||||
|IpNeighLine {
|
|
||||||
ip: _,
|
|
||||||
dev: _,
|
|
||||||
mac,
|
|
||||||
state: _,
|
|
||||||
}| mac,
|
|
||||||
)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_macs_1(machine_name: &str) -> Result<Vec<arpparse::IpNeighLine>> {
|
|
||||||
let dev = "br-lan";
|
|
||||||
let ips = get_ips(machine_name).await?;
|
|
||||||
let futures = ips.map(|ip| {
|
|
||||||
let ip = ip.to_canonical();
|
|
||||||
async move {
|
|
||||||
let cmd = "ip";
|
|
||||||
let args = ["neigh", "show", "to", &ip.to_string(), "dev", dev];
|
|
||||||
let o = exec_command(cmd, args).await?;
|
|
||||||
if !o.status.success() {
|
|
||||||
return Err(Error::CommandFailed {
|
|
||||||
cmd,
|
|
||||||
args: args.iter().map(ToString::to_string).collect(),
|
|
||||||
status: o.status.code(),
|
|
||||||
stderr: String::from_utf8_lossy(&o.stderr).into(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Ok(String::from_utf8_lossy(&o.stdout)
|
|
||||||
.lines()
|
|
||||||
.flat_map(arpparse::parse_ip_neigh_line)
|
|
||||||
.collect::<Vec<_>>())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let res = futures::future::try_join_all(futures).await?;
|
|
||||||
Ok(res.into_iter().flatten().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_macs(
|
pub async fn get_macs(
|
||||||
machine_name: Option<&str>,
|
machine_names: &[impl AsRef<str>],
|
||||||
ips: Option<&[IpAddr]>,
|
ips: &[IpAddr],
|
||||||
dev: Option<&str>,
|
devs: &[impl AsRef<str>],
|
||||||
state: Option<NUDState>,
|
state: &[NUDState],
|
||||||
|
macs: &[MacAddr],
|
||||||
) -> Result<Vec<IpNeighLine>> {
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
let ip_list: Option<Vec<IpAddr>> =
|
let mut ip_set: HashSet<IpAddr> = ips.iter().map(|ip| ip.to_canonical()).collect();
|
||||||
ips.map(|slice| slice.iter().map(|ip| ip.to_canonical()).collect());
|
let ip_m: HashSet<IpAddr> = futures::future::try_join_all(
|
||||||
let ip_list = match (ip_list, machine_name) {
|
machine_names
|
||||||
(Some(list), _) => list,
|
.iter()
|
||||||
(None, Some(name)) => get_ips(name).await?.collect(),
|
.map(|c| async { get_ips(c.as_ref()).await }),
|
||||||
(None, None) => Vec::new(),
|
)
|
||||||
};
|
.await?
|
||||||
let run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, dev, state);
|
.into_iter()
|
||||||
if !ip_list.is_empty() {
|
.flatten()
|
||||||
let futures = ip_list.into_iter().map(|ip| run_one(Some(ip)));
|
.collect();
|
||||||
let res = futures::future::try_join_all(futures).await?;
|
let ip_all = if ip_set.is_empty() && ip_m.is_empty() {
|
||||||
Ok(res.into_iter().flatten().collect())
|
None
|
||||||
|
} else if ip_set.is_empty() {
|
||||||
|
Some(ip_m)
|
||||||
|
} else if ip_m.is_empty() {
|
||||||
|
Some(ip_set)
|
||||||
} else {
|
} else {
|
||||||
run_one(None).await
|
Some({
|
||||||
}
|
ip_set.retain(|c| ip_m.contains(c)); // inline AHHH
|
||||||
|
ip_set
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let opt_dev = if devs.len() > 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
devs.iter().next().map(AsRef::as_ref)
|
||||||
|
};
|
||||||
|
let opt_state = if state.len() > 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
state.iter().next().copied()
|
||||||
|
};
|
||||||
|
|
||||||
|
let run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, opt_dev, opt_state);
|
||||||
|
|
||||||
|
let mut ip_filtered = if let Some(something) = ip_all {
|
||||||
|
if something.len() == 1 {
|
||||||
|
run_one(something.into_iter().next()).await?
|
||||||
|
} else {
|
||||||
|
run_one(None)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| something.contains(&c.ip))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
run_one(None).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply additional filters if any were provided
|
||||||
|
if !devs.is_empty() || !macs.is_empty() || !state.is_empty() {
|
||||||
|
let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect();
|
||||||
|
let nudset: HashSet<_> = state.iter().collect();
|
||||||
|
let macset: HashSet<_> = macs.iter().collect();
|
||||||
|
|
||||||
|
ip_filtered.retain(|entry| {
|
||||||
|
// Dev filter: if we're filtering by dev, entry must have a dev AND it must be in the set
|
||||||
|
let dev_ok =
|
||||||
|
devset.is_empty() || entry.dev.as_deref().is_some_and(|d| devset.contains(d));
|
||||||
|
|
||||||
|
// NUD filter: always present, simple check
|
||||||
|
let nud_ok = nudset.is_empty() || nudset.contains(&entry.state);
|
||||||
|
|
||||||
|
// MAC filter: if we're filtering by MAC, entry must have a MAC AND it must be in the set
|
||||||
|
let mac_ok = macset.is_empty() || entry.mac.is_some_and(|m| macset.contains(&m));
|
||||||
|
|
||||||
|
dev_ok && nud_ok && mac_ok
|
||||||
|
})
|
||||||
|
};
|
||||||
|
Ok(ip_filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the atomic get_macs. handle ONE thing only.
|
/// the atomic get_macs. handle ONE thing only.
|
||||||
@@ -102,7 +122,7 @@ pub async fn get_mac(
|
|||||||
ip: Option<IpAddr>,
|
ip: Option<IpAddr>,
|
||||||
dev: Option<&str>,
|
dev: Option<&str>,
|
||||||
state: Option<NUDState>,
|
state: Option<NUDState>,
|
||||||
) -> error::Result<Vec<IpNeighLine>> {
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
||||||
if let Some(ip) = ip {
|
if let Some(ip) = ip {
|
||||||
args.push("to".into());
|
args.push("to".into());
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
|
||||||
|
use crate::{arpparse::NUDState, utils::query::dev::has_dev};
|
||||||
|
|
||||||
|
pub enum QueryType {
|
||||||
|
Ip(IpAddr),
|
||||||
|
Mac(MacAddr),
|
||||||
|
Dev(String),
|
||||||
|
Nud(NUDState),
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_query(q: String) -> QueryType {
|
||||||
|
let s = if cfg!(feature = "very-smart-parsing") {
|
||||||
|
crate::utils::parse::extract_host(&q)
|
||||||
|
} else {
|
||||||
|
q.trim()
|
||||||
|
};
|
||||||
|
// 1) IP
|
||||||
|
let ip = if cfg!(feature = "very-smart-parsing") {
|
||||||
|
crate::utils::parse::parse_numeric_ipv4(s).or_else(|| s.parse::<IpAddr>().ok())
|
||||||
|
} else {
|
||||||
|
s.parse::<IpAddr>().ok()
|
||||||
|
};
|
||||||
|
if let Some(ip) = ip {
|
||||||
|
return QueryType::Ip(ip);
|
||||||
|
}
|
||||||
|
// 2) MAC
|
||||||
|
if let Ok(mac) = s.parse::<MacAddr>() {
|
||||||
|
return QueryType::Mac(mac);
|
||||||
|
}
|
||||||
|
// 3) NUD state (reachable, stale, ...)
|
||||||
|
if let Ok(state) = s.parse::<NUDState>() {
|
||||||
|
return QueryType::Nud(state);
|
||||||
|
}
|
||||||
|
// 4) Known device? prefer dev first
|
||||||
|
if has_dev(s) {
|
||||||
|
return QueryType::Dev(s.to_string());
|
||||||
|
}
|
||||||
|
// Default: name last // it will fail also
|
||||||
|
QueryType::Unknown(s.to_string())
|
||||||
|
}
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use axum::{http::header, response::IntoResponse};
|
use axum::{http::header, response::IntoResponse};
|
||||||
use macaddr::MacAddr;
|
|
||||||
|
|
||||||
use crate::{arpparse::NUDState, route::DeviceQuery, utils::query::dev::has_dev};
|
|
||||||
|
|
||||||
pub async fn serve_js(content: &'static str) -> impl IntoResponse {
|
pub async fn serve_js(content: &'static str) -> impl IntoResponse {
|
||||||
(
|
(
|
||||||
@@ -14,59 +9,3 @@ pub async fn serve_js(content: &'static str) -> impl IntoResponse {
|
|||||||
content,
|
content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn status_smart_redirect(q: String) -> DeviceQuery {
|
|
||||||
let s = if cfg!(feature = "very-smart-parsing") {
|
|
||||||
crate::utils::parse::extract_host(&q)
|
|
||||||
} else {
|
|
||||||
q.trim()
|
|
||||||
};
|
|
||||||
// 1) IP
|
|
||||||
let ip = if cfg!(feature = "very-smart-parsing") {
|
|
||||||
crate::utils::parse::parse_numeric_ipv4(s).or_else(|| s.parse::<IpAddr>().ok())
|
|
||||||
} else {
|
|
||||||
s.parse::<IpAddr>().ok()
|
|
||||||
};
|
|
||||||
if let Some(ip) = ip {
|
|
||||||
let ip = vec![ip];
|
|
||||||
return DeviceQuery {
|
|
||||||
ip,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// 2) MAC
|
|
||||||
if let Ok(mac) = s.parse::<MacAddr>() {
|
|
||||||
let mac = vec![mac];
|
|
||||||
return DeviceQuery {
|
|
||||||
mac,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// 3) NUD state (reachable, stale, ...)
|
|
||||||
if let Ok(state) = s.parse::<NUDState>() {
|
|
||||||
let nud = vec![state];
|
|
||||||
return DeviceQuery {
|
|
||||||
nud,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// 4) Known device? prefer dev first
|
|
||||||
if has_dev(s) {
|
|
||||||
return DeviceQuery {
|
|
||||||
dev: vec![s.to_string()],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// 5) Try DNS: if it resolves, treat as name
|
|
||||||
/* if tokio::net::lookup_host((s, 0)).await.is_ok() {
|
|
||||||
return DeviceQuery {
|
|
||||||
name: Some(s.to_string()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
} */
|
|
||||||
// Default: name last // it will fail also
|
|
||||||
DeviceQuery {
|
|
||||||
name: Some(s.to_string()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+11
-4
@@ -22,11 +22,19 @@ impl TryFrom<RouteWakeTarget> for WakeTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<WakeTargetResult> for RouteWakeResult {
|
impl From<WakeTarget> for RouteWakeTarget {
|
||||||
fn from(WakeTargetResult { ip, mac, status }: WakeTargetResult) -> Self {
|
fn from(WakeTarget { ip, mac }: WakeTarget) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ip: Some(ip),
|
ip: Some(ip),
|
||||||
mac: Some(mac),
|
mac: Some(mac),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WakeTargetResult> for RouteWakeResult {
|
||||||
|
fn from(WakeTargetResult { target, status }: WakeTargetResult) -> Self {
|
||||||
|
Self {
|
||||||
|
target: target.into(),
|
||||||
status: status.into(),
|
status: status.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,8 +43,7 @@ impl From<WakeTargetResult> for RouteWakeResult {
|
|||||||
impl RouteWakeTarget {
|
impl RouteWakeTarget {
|
||||||
pub fn to_incomplete(self) -> RouteWakeResult {
|
pub fn to_incomplete(self) -> RouteWakeResult {
|
||||||
RouteWakeResult {
|
RouteWakeResult {
|
||||||
ip: self.ip,
|
target: self,
|
||||||
mac: self.mac,
|
|
||||||
status: RouteWakeStatus::Incomplete,
|
status: RouteWakeStatus::Incomplete,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,35 +5,6 @@ use futures::TryFutureExt;
|
|||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
use crate::utils::query::get_macs_2_mac;
|
|
||||||
|
|
||||||
pub async fn wake(machine_name: &str) -> io::Result<u32> {
|
|
||||||
let suh = UdpSocket::bind("0.0.0.0:0").await?;
|
|
||||||
suh.set_broadcast(true)?;
|
|
||||||
let /* mut */ macs = get_macs_2_mac(machine_name).await.unwrap_or_default();
|
|
||||||
// macs.extend(*LDA_MACS_2);
|
|
||||||
let mut sent_ok = 0;
|
|
||||||
for mac in macs {
|
|
||||||
let mb = mac.as_bytes();
|
|
||||||
|
|
||||||
let mut pac = [0; 6 + 6 * 16]; // 6x FF + 16x mac6
|
|
||||||
pac[..6].fill(0xff);
|
|
||||||
for i in 1..=16 {
|
|
||||||
pac[i * 6..(i + 1) * 6].copy_from_slice(mb);
|
|
||||||
}
|
|
||||||
|
|
||||||
match suh
|
|
||||||
.send_to(&pac, (IpAddr::from([192, 168, 100, 255]), 9))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(n) if n == pac.len() => sent_ok += 1,
|
|
||||||
Ok(n) => eprintln!("partial send ({n}/{})", pac.len()),
|
|
||||||
Err(e) => eprintln!("send error: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(sent_ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Hash)]
|
#[derive(Debug, Clone, Copy, Hash)]
|
||||||
pub struct WakeTarget {
|
pub struct WakeTarget {
|
||||||
pub ip: IpAddr,
|
pub ip: IpAddr,
|
||||||
@@ -41,8 +12,7 @@ pub struct WakeTarget {
|
|||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy, Hash)]
|
#[derive(Debug, Clone, Copy, Hash)]
|
||||||
pub struct WakeTargetResult {
|
pub struct WakeTargetResult {
|
||||||
pub ip: IpAddr,
|
pub target: WakeTarget,
|
||||||
pub mac: MacAddr,
|
|
||||||
pub status: WakeStatus,
|
pub status: WakeStatus,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy, Hash)]
|
#[derive(Debug, Clone, Copy, Hash)]
|
||||||
@@ -56,18 +26,18 @@ impl WakeTarget {
|
|||||||
Self { ip, mac }
|
Self { ip, mac }
|
||||||
}
|
}
|
||||||
fn good(self) -> WakeTargetResult {
|
fn good(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self.ip, self.mac, WakeStatus::Success)
|
WakeTargetResult::new(self, WakeStatus::Success)
|
||||||
}
|
}
|
||||||
fn bad(self) -> WakeTargetResult {
|
fn bad(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self.ip, self.mac, WakeStatus::WrongSize)
|
WakeTargetResult::new(self, WakeStatus::WrongSize)
|
||||||
}
|
}
|
||||||
fn errored(self) -> WakeTargetResult {
|
fn errored(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self.ip, self.mac, WakeStatus::NonexistentAddress)
|
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl WakeTargetResult {
|
impl WakeTargetResult {
|
||||||
fn new(ip: IpAddr, mac: MacAddr, status: WakeStatus) -> Self {
|
fn new(target: WakeTarget, status: WakeStatus) -> Self {
|
||||||
Self { ip, mac, status }
|
Self { target, status }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
<div class="row muted" style="gap: 16px">
|
<div class="row muted" style="gap: 16px">
|
||||||
<span
|
<span
|
||||||
>Uses query
|
>Uses query
|
||||||
<span title="available keys: name, ip, mac, dev, nud"
|
<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.<!-- and header (X-Target-Name) so either extractor
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import { elLeases } from "./dom.js";
|
import { elLeases } from "./dom.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {{
|
||||||
|
* expires_epoch: Number
|
||||||
|
* rank?: Number
|
||||||
|
* ip: String
|
||||||
|
* mac: String
|
||||||
|
* nud_state?: String
|
||||||
|
* name?: String
|
||||||
|
* }[]} leases
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export function renderLeases(leases) {
|
export function renderLeases(leases) {
|
||||||
if (!elLeases) return;
|
if (!elLeases) return;
|
||||||
if (!Array.isArray(leases) || leases.length === 0) {
|
if (!Array.isArray(leases) || leases.length === 0) {
|
||||||
|
|||||||
+89
-3
@@ -2,6 +2,7 @@ import { elHtml, elLog, setPill, qs, pill } from "./dom.js";
|
|||||||
import { rankState } from "./utils.js";
|
import { rankState } from "./utils.js";
|
||||||
import { merge_wake_data, translate_wake_message } from "./wake.js";
|
import { merge_wake_data, translate_wake_message } from "./wake.js";
|
||||||
|
|
||||||
|
// IpNeighLine
|
||||||
const status_map = {
|
const status_map = {
|
||||||
ip: "ip",
|
ip: "ip",
|
||||||
mac: "mac",
|
mac: "mac",
|
||||||
@@ -9,8 +10,14 @@ const status_map = {
|
|||||||
dev: "interface",
|
dev: "interface",
|
||||||
};
|
};
|
||||||
export const status_array = Object.keys(status_map);
|
export const status_array = Object.keys(status_map);
|
||||||
export const filter_array = ["ip", "dev", "nud", "mac"];
|
// Filters
|
||||||
|
export const filter_array = ["ips", "devs", "nuds", "macs"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {URL}
|
||||||
|
*/
|
||||||
function buildStatusUrl(name) {
|
function buildStatusUrl(name) {
|
||||||
const hasExtraFilters = filter_array.some((k) => qs.getAll(k).length);
|
const hasExtraFilters = filter_array.some((k) => qs.getAll(k).length);
|
||||||
if (name && !hasExtraFilters) {
|
if (name && !hasExtraFilters) {
|
||||||
@@ -25,6 +32,25 @@ function buildStatusUrl(name) {
|
|||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {{
|
||||||
|
* has_wake?: true
|
||||||
|
* table: {
|
||||||
|
* wake_status?: Boolean
|
||||||
|
* ips: String
|
||||||
|
* dev: String
|
||||||
|
* mac: String
|
||||||
|
* state: String
|
||||||
|
* }[]
|
||||||
|
* filters: {
|
||||||
|
* ips?: String[]
|
||||||
|
* devs?: String[]
|
||||||
|
* nuds?: String[]
|
||||||
|
* macs?: String[]
|
||||||
|
* }
|
||||||
|
* }} data
|
||||||
|
*/
|
||||||
export function renderStatus(data) {
|
export function renderStatus(data) {
|
||||||
const tbl = document.createElement("table");
|
const tbl = document.createElement("table");
|
||||||
tbl.className = "table";
|
tbl.className = "table";
|
||||||
@@ -82,12 +108,51 @@ export function renderStatus(data) {
|
|||||||
if (r >= 5) setPill("ok", "online");
|
if (r >= 5) setPill("ok", "online");
|
||||||
else if (r >= 2) setPill("warn", "maybe");
|
else if (r >= 2) setPill("warn", "maybe");
|
||||||
else setPill("bad", "offline");
|
else setPill("bad", "offline");
|
||||||
if (data.filters.nud?.length > 0) pill.textContent += " (filtered)";
|
if (data.filters.nuds?.length > 0) pill.textContent += " (filtered)";
|
||||||
} else {
|
} else {
|
||||||
setPill("warn", "unknown");
|
setPill("warn", "unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param {String} name
|
||||||
|
* @param {Boolean} render
|
||||||
|
* @param {{
|
||||||
|
* ip: String,
|
||||||
|
* mac: String,
|
||||||
|
* status:
|
||||||
|
* "incomplete" | "succeed" | "nonexistent_address" | "wrong_size"
|
||||||
|
* }} data_wake
|
||||||
|
* @returns {{
|
||||||
|
* has_wake: false
|
||||||
|
* table: {
|
||||||
|
* ip: String
|
||||||
|
* dev: String
|
||||||
|
* mac: String
|
||||||
|
* state: String
|
||||||
|
* }[]
|
||||||
|
* filters: {
|
||||||
|
* ips?: String[]
|
||||||
|
* devs?: String[]
|
||||||
|
* nuds?: String[]
|
||||||
|
* macs?: String[]
|
||||||
|
* }
|
||||||
|
* } | {
|
||||||
|
* has_wake: true
|
||||||
|
* table: {
|
||||||
|
* wake_status: Boolean
|
||||||
|
* ips: String
|
||||||
|
* dev: String
|
||||||
|
* mac: String
|
||||||
|
* state: String
|
||||||
|
* }[]
|
||||||
|
* filters: {
|
||||||
|
* ips?: String[]
|
||||||
|
* devs?: String[]
|
||||||
|
* nuds?: String[]
|
||||||
|
* macs?: String[]
|
||||||
|
* }
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
export async function fetchStatus(name, render = true, data_wake) {
|
export async function fetchStatus(name, render = true, data_wake) {
|
||||||
setPill("warn", "checking…");
|
setPill("warn", "checking…");
|
||||||
const u = buildStatusUrl(name);
|
const u = buildStatusUrl(name);
|
||||||
@@ -97,6 +162,7 @@ export async function fetchStatus(name, render = true, data_wake) {
|
|||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
let msg = String(r.status);
|
let msg = String(r.status);
|
||||||
try {
|
try {
|
||||||
|
/** @type {{error: string}} */
|
||||||
const err = await r.clone().json();
|
const err = await r.clone().json();
|
||||||
msg = err.error || JSON.stringify(err);
|
msg = err.error || JSON.stringify(err);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -106,6 +172,26 @@ export async function fetchStatus(name, render = true, data_wake) {
|
|||||||
setPill("bad", "error");
|
setPill("bad", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this one does not have data wake
|
||||||
|
* @type {{
|
||||||
|
* table: {
|
||||||
|
* ip: String
|
||||||
|
* dev: String
|
||||||
|
* mac: String
|
||||||
|
* state: String
|
||||||
|
* wake_status?: String
|
||||||
|
* }[]
|
||||||
|
* filters: {
|
||||||
|
* ips?: String[]
|
||||||
|
* devs?: String[]
|
||||||
|
* nuds?: String[]
|
||||||
|
* macs?: String[]
|
||||||
|
* }
|
||||||
|
* has_wake?: true
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
|
|
||||||
if (data_wake) {
|
if (data_wake) {
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
font-family: system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
wrap {
|
||||||
|
margin-inline: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
|
|||||||
+31
-3
@@ -1,6 +1,9 @@
|
|||||||
import { elHtml, elLog, setPill } from "./dom.js";
|
import { elHtml, elLog, setPill } from "./dom.js";
|
||||||
import { fetchStatus, renderStatus } from "./status.js";
|
import { fetchStatus, renderStatus } from "./status.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} name just plain name
|
||||||
|
*/
|
||||||
export async function sendWake(name) {
|
export async function sendWake(name) {
|
||||||
const data = await fetchStatus(name, false);
|
const data = await fetchStatus(name, false);
|
||||||
if (!data) return; // can not proceed; theres nothing.
|
if (!data) return; // can not proceed; theres nothing.
|
||||||
@@ -51,8 +54,29 @@ export async function sendWake(name) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Array} table
|
* @param {{
|
||||||
* @param {Array} wake
|
* ip: String
|
||||||
|
* dev: String
|
||||||
|
* mac: String
|
||||||
|
* state: String
|
||||||
|
* }[]} table
|
||||||
|
* @param {{
|
||||||
|
* ip: String,
|
||||||
|
* mac: String,
|
||||||
|
* status: "incomplete" | "succeed" | "nonexistent_address" | "wrong_size"
|
||||||
|
* }[]} wake
|
||||||
|
* @returns {{
|
||||||
|
* ip: String
|
||||||
|
* dev: String
|
||||||
|
* mac: String
|
||||||
|
* state: String
|
||||||
|
* }[] | {
|
||||||
|
* ip: String
|
||||||
|
* dev: String
|
||||||
|
* mac: String
|
||||||
|
* state: String
|
||||||
|
* wake_status: boolean
|
||||||
|
* }[]}
|
||||||
*/
|
*/
|
||||||
export function merge_wake_data(table, wake) {
|
export function merge_wake_data(table, wake) {
|
||||||
if (!wake) return table;
|
if (!wake) return table;
|
||||||
@@ -90,7 +114,11 @@ export function merge_wake_data(table, wake) {
|
|||||||
return return_array;
|
return return_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {"incomplete" | "succeed" | "nonexistent_address" | "wrong_size" | any} wake_msg
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
export function translate_wake_message(wake_msg) {
|
export function translate_wake_message(wake_msg) {
|
||||||
switch (wake_msg) {
|
switch (wake_msg) {
|
||||||
case "incomplete":
|
case "incomplete":
|
||||||
|
|||||||
Reference in New Issue
Block a user