what a plan! what an impl! this is ahh
This commit is contained in:
+10
-30
@@ -2,20 +2,17 @@
|
||||
|
||||
pub mod table;
|
||||
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::net::IpAddr;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{ArgAction, Args, Parser, Subcommand};
|
||||
use tracing::{debug, info};
|
||||
use tracing::debug;
|
||||
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use wakey_core::{DeviceFilters, DeviceQuery, InterfaceSummary, WakeResult};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "wakey")]
|
||||
#[command(version, about = "CLI and temporary HTTP adapter for Wakey")]
|
||||
#[command(
|
||||
long_about = "Wakey can run as a local/operator CLI or serve the legacy HTTP/static interface during the migration to a service-first architecture."
|
||||
)]
|
||||
#[command(version, about = "Operator CLI for Wakey service actions")]
|
||||
pub struct Cli {
|
||||
/// Increase log verbosity. Use `-v` for debug and `-vv` for trace.
|
||||
#[arg(short = 'v', long = "verbose", action = ArgAction::Count, global = true)]
|
||||
@@ -27,8 +24,6 @@ pub struct Cli {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Command {
|
||||
/// Serve the temporary legacy HTTP/static app.
|
||||
Http(HttpArgs),
|
||||
/// Show device status rows from neighbor/device data.
|
||||
Status(StatusArgs),
|
||||
/// Show DHCP leases, optionally enriched with current neighbor state.
|
||||
@@ -39,16 +34,6 @@ pub enum Command {
|
||||
Devs(DevsArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct HttpArgs {
|
||||
/// Host address to bind the HTTP server to.
|
||||
#[arg(long, default_value = "::")]
|
||||
pub host: IpAddr,
|
||||
/// TCP port to bind the HTTP server to.
|
||||
#[arg(long, default_value_t = 12012)]
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct LeasesArgs {
|
||||
/// Include best-known current neighbor state for each lease IP.
|
||||
@@ -143,9 +128,9 @@ pub fn init_tracing(verbose: u8) {
|
||||
|
||||
pub fn default_filter_for_verbosity(verbose: u8) -> &'static str {
|
||||
match verbose {
|
||||
0 => "wakey=info,tower_http=info",
|
||||
1 => "wakey=debug,tower_http=debug",
|
||||
_ => "wakey=trace,tower_http=trace",
|
||||
0 => "wakey=info",
|
||||
1 => "wakey=debug",
|
||||
_ => "wakey=trace",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,11 +138,6 @@ pub async fn run(cli: Cli) -> Result<()> {
|
||||
init_tracing(cli.verbose);
|
||||
|
||||
match cli.command {
|
||||
Command::Http(args) => {
|
||||
let addr = SocketAddr::new(args.host, args.port);
|
||||
info!(%addr, "dispatching http command");
|
||||
wakey::serve_http_from_current_exe(addr).await?;
|
||||
}
|
||||
Command::Status(args) => {
|
||||
let as_json = args.json;
|
||||
let query = status_args_to_query(args);
|
||||
@@ -357,9 +337,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn verbosity_maps_to_expected_default_filters() {
|
||||
assert_eq!(default_filter_for_verbosity(0), "wakey=info,tower_http=info");
|
||||
assert_eq!(default_filter_for_verbosity(1), "wakey=debug,tower_http=debug");
|
||||
assert_eq!(default_filter_for_verbosity(2), "wakey=trace,tower_http=trace");
|
||||
assert_eq!(default_filter_for_verbosity(9), "wakey=trace,tower_http=trace");
|
||||
assert_eq!(default_filter_for_verbosity(0), "wakey=info");
|
||||
assert_eq!(default_filter_for_verbosity(1), "wakey=debug");
|
||||
assert_eq!(default_filter_for_verbosity(2), "wakey=trace");
|
||||
assert_eq!(default_filter_for_verbosity(9), "wakey=trace");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
//! Compatibility types and mappers for the legacy HTTP/static client.
|
||||
//!
|
||||
//! These types intentionally preserve old JSON shapes expected by `/static`
|
||||
//! while the core and service layers evolve underneath them. They do not define
|
||||
//! the long-term domain model of the project.
|
||||
|
||||
use serde::Serialize;
|
||||
use wakey_core::parse::mac;
|
||||
use wakey_core::{
|
||||
Device, DeviceFilters, DeviceInventory, DhcpLeaseWithState, NeighborEntry, Status, WakeResult,
|
||||
WakeTargetResult,
|
||||
};
|
||||
|
||||
/// Legacy status row shape expected by the old `/static` frontend.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct LegacyStatusRow {
|
||||
pub ip: std::net::IpAddr,
|
||||
pub dev: Option<String>,
|
||||
#[serde(with = "mac::option_mac")]
|
||||
pub mac: Option<macaddr::MacAddr>,
|
||||
pub state: wakey_core::NeighborState,
|
||||
}
|
||||
|
||||
/// Legacy status response shape expected by the old `/static` frontend.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct LegacyStatusResponse {
|
||||
pub name: Option<String>,
|
||||
pub table: Vec<LegacyStatusRow>,
|
||||
pub filters: DeviceFilters,
|
||||
}
|
||||
|
||||
/// Legacy DHCP lease row shape expected by the old `/static` frontend.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct LegacyLeaseRow {
|
||||
pub expires_epoch: u64,
|
||||
pub ip: std::net::IpAddr,
|
||||
#[serde(with = "mac")]
|
||||
pub mac: macaddr::MacAddr,
|
||||
pub name: Option<String>,
|
||||
pub nud_state: Option<wakey_core::NeighborState>,
|
||||
}
|
||||
|
||||
/// Legacy wake response wrapper expected by the old `/static` frontend.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct LegacyWakeResult {
|
||||
pub result: Vec<LegacyWakeResultRow>,
|
||||
}
|
||||
|
||||
/// Legacy per-target wake row shape.
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct LegacyWakeResultRow {
|
||||
#[serde(flatten)]
|
||||
pub target: wakey_core::WakeTarget,
|
||||
pub status: wakey_core::WakeStatus,
|
||||
}
|
||||
|
||||
/// Map legacy-style status rows into the old response shape.
|
||||
///
|
||||
/// This helper exists for compatibility with the original frontend contract.
|
||||
pub fn legacy_status_from_domain(status: Status<NeighborEntry>) -> LegacyStatusResponse {
|
||||
LegacyStatusResponse {
|
||||
name: status.name,
|
||||
table: status.table.into_iter().map(legacy_status_row).collect(),
|
||||
filters: status.filters,
|
||||
}
|
||||
}
|
||||
|
||||
/// Project a device inventory into the legacy status response shape.
|
||||
pub fn legacy_status_from_inventory(
|
||||
inventory: DeviceInventory,
|
||||
name: Option<String>,
|
||||
filters: DeviceFilters,
|
||||
) -> LegacyStatusResponse {
|
||||
let table = inventory
|
||||
.devices
|
||||
.into_iter()
|
||||
.flat_map(legacy_status_rows_from_device)
|
||||
.collect();
|
||||
LegacyStatusResponse {
|
||||
name,
|
||||
table,
|
||||
filters,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert one neighbor row to the legacy status row shape.
|
||||
pub fn legacy_status_row(row: NeighborEntry) -> LegacyStatusRow {
|
||||
LegacyStatusRow {
|
||||
ip: row.ip,
|
||||
dev: row.dev,
|
||||
mac: row.mac,
|
||||
state: row.state,
|
||||
}
|
||||
}
|
||||
|
||||
/// Project one merged device back into legacy status rows.
|
||||
pub fn legacy_status_rows_from_device(device: Device) -> Vec<LegacyStatusRow> {
|
||||
if !device.neighbors.is_empty() {
|
||||
return device
|
||||
.neighbors
|
||||
.into_iter()
|
||||
.map(legacy_status_row)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
let fallback_mac = device.macs.first().copied();
|
||||
let fallback_dev = device.interfaces.first().cloned();
|
||||
let fallback_state = match device.presence {
|
||||
wakey_core::Presence::Online => wakey_core::NeighborState::Reachable,
|
||||
wakey_core::Presence::LikelyOnline => wakey_core::NeighborState::Stale,
|
||||
wakey_core::Presence::Offline => wakey_core::NeighborState::Failed,
|
||||
wakey_core::Presence::Unknown => wakey_core::NeighborState::None,
|
||||
};
|
||||
|
||||
device
|
||||
.ips
|
||||
.into_iter()
|
||||
.map(|ip| LegacyStatusRow {
|
||||
ip,
|
||||
dev: fallback_dev.clone(),
|
||||
mac: fallback_mac,
|
||||
state: fallback_state,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert lease rows into the legacy frontend shape.
|
||||
pub fn legacy_leases_from_domain(leases: Vec<DhcpLeaseWithState>) -> Vec<LegacyLeaseRow> {
|
||||
leases.into_iter().map(legacy_lease_row).collect()
|
||||
}
|
||||
|
||||
/// Convert one lease row into the legacy frontend shape.
|
||||
pub fn legacy_lease_row(lease: DhcpLeaseWithState) -> LegacyLeaseRow {
|
||||
LegacyLeaseRow {
|
||||
expires_epoch: lease.lease_line.expires_epoch,
|
||||
ip: lease.lease_line.ip,
|
||||
mac: lease.lease_line.mac,
|
||||
name: lease.lease_line.name,
|
||||
nud_state: lease.nud_state,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert wake results into the legacy frontend shape.
|
||||
pub fn legacy_wake_from_domain(result: WakeResult) -> LegacyWakeResult {
|
||||
LegacyWakeResult {
|
||||
result: result.result.into_iter().map(legacy_wake_row).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert one wake result row into the legacy frontend shape.
|
||||
pub fn legacy_wake_row(row: WakeTargetResult) -> LegacyWakeResultRow {
|
||||
LegacyWakeResultRow {
|
||||
target: row.target,
|
||||
status: row.status,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use wakey_core::{DhcpLease, NeighborState, WakeStatus, WakeTarget};
|
||||
|
||||
#[test]
|
||||
fn maps_status_to_legacy_shape() {
|
||||
let status = Status {
|
||||
name: Some("pc".into()),
|
||||
table: vec![NeighborEntry {
|
||||
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)),
|
||||
dev: Some("br-lan".into()),
|
||||
mac: Some("aa:bb:cc:dd:ee:ff".parse().expect("mac")),
|
||||
state: NeighborState::Reachable,
|
||||
}],
|
||||
filters: DeviceFilters::default(),
|
||||
};
|
||||
let legacy = legacy_status_from_domain(status);
|
||||
assert_eq!(legacy.name.as_deref(), Some("pc"));
|
||||
assert_eq!(legacy.table.len(), 1);
|
||||
assert_eq!(legacy.table[0].state, NeighborState::Reachable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_inventory_to_legacy_status_shape() {
|
||||
let inventory = DeviceInventory {
|
||||
devices: vec![Device::from_parts(
|
||||
vec![NeighborEntry {
|
||||
ip: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)),
|
||||
dev: Some("br-lan".into()),
|
||||
mac: Some("aa:bb:cc:dd:ee:ff".parse().expect("mac")),
|
||||
state: NeighborState::Reachable,
|
||||
}],
|
||||
vec![],
|
||||
)],
|
||||
};
|
||||
let legacy =
|
||||
legacy_status_from_inventory(inventory, Some("pc".into()), DeviceFilters::default());
|
||||
assert_eq!(legacy.name.as_deref(), Some("pc"));
|
||||
assert_eq!(legacy.table.len(), 1);
|
||||
assert_eq!(legacy.table[0].state, NeighborState::Reachable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_leases_to_legacy_shape() {
|
||||
let leases = vec![DhcpLeaseWithState {
|
||||
lease_line: DhcpLease {
|
||||
expires_epoch: 42,
|
||||
ip: IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
mac: "aa:bb:cc:dd:ee:ff".parse().expect("mac"),
|
||||
name: Some("pc".into()),
|
||||
},
|
||||
nud_state: Some(NeighborState::Reachable),
|
||||
}];
|
||||
let legacy = legacy_leases_from_domain(leases);
|
||||
assert_eq!(legacy.len(), 1);
|
||||
assert_eq!(legacy[0].name.as_deref(), Some("pc"));
|
||||
assert_eq!(legacy[0].nud_state, Some(NeighborState::Reachable));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_wake_to_legacy_shape() {
|
||||
let result = WakeResult {
|
||||
result: vec![WakeTargetResult {
|
||||
target: WakeTarget {
|
||||
ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
|
||||
mac: Some("aa:bb:cc:dd:ee:ff".parse().expect("mac")),
|
||||
},
|
||||
status: WakeStatus::Succeed,
|
||||
}],
|
||||
};
|
||||
let legacy = legacy_wake_from_domain(result);
|
||||
assert_eq!(legacy.result.len(), 1);
|
||||
assert_eq!(legacy.result[0].status, WakeStatus::Succeed);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
//! Temporary HTTP adapter for the legacy web/static surface.
|
||||
//!
|
||||
//! This module exists to keep the old `/api` routes and `/static` frontend
|
||||
//! working while the project is migrated toward a service-first architecture.
|
||||
//! New product logic should live in [`crate::service`], not here.
|
||||
|
||||
pub mod compat;
|
||||
pub mod route;
|
||||
|
||||
use std::{io, net::SocketAddr};
|
||||
|
||||
use axum::Router;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
||||
use tracing::info;
|
||||
|
||||
/// Build the temporary HTTP app that serves the legacy API and static frontend.
|
||||
///
|
||||
/// This is a compatibility surface. It should stay thin and delegate actual
|
||||
/// product behavior to the service layer.
|
||||
pub fn http_app(static_root: std::path::PathBuf) -> Router {
|
||||
Router::new()
|
||||
.nest("/api", route::api_router())
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.fallback_service(axum::routing::get_service(
|
||||
ServeDir::new(static_root)
|
||||
.append_index_html_on_directories(true)
|
||||
.precompressed_br()
|
||||
.precompressed_deflate()
|
||||
.precompressed_gzip()
|
||||
.precompressed_zstd(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Serve the temporary HTTP app on the provided socket address.
|
||||
///
|
||||
/// This is intended for transition and compatibility, not as the long-term
|
||||
/// architecture boundary of the project.
|
||||
pub async fn serve_http(addr: SocketAddr, static_root: std::path::PathBuf) -> io::Result<()> {
|
||||
info!(%addr, static_root = %static_root.display(), "starting legacy http adapter");
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
axum::serve(listener, http_app(static_root).into_make_service()).await
|
||||
}
|
||||
|
||||
/// Serve the HTTP app using the `static/` directory next to the current
|
||||
/// executable.
|
||||
pub async fn serve_http_from_current_exe(addr: SocketAddr) -> io::Result<()> {
|
||||
let exe = std::env::current_exe()?;
|
||||
let root = exe
|
||||
.parent()
|
||||
.ok_or_else(|| io::Error::other("no parent dir"))?;
|
||||
serve_http(addr, root.join("static")).await
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
use crate::http::route::error::ApiError;
|
||||
use axum::Json;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{extract::Path, response::Redirect};
|
||||
use wakey_core::DeviceQuery;
|
||||
|
||||
use crate::http::route::status::NamePath;
|
||||
|
||||
// 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>,
|
||||
) -> axum::response::Result<Redirect, impl IntoResponse> {
|
||||
let query: DeviceQuery = match crate::service::resolve_query(q).await {
|
||||
Ok(query) => query,
|
||||
Err(e) => {
|
||||
return Err(ApiError {
|
||||
error: e.to_string(),
|
||||
code: StatusCode::BAD_GATEWAY,
|
||||
});
|
||||
}
|
||||
};
|
||||
match serde_html_form::to_string(query) {
|
||||
Ok(e) => Ok(Redirect::to(&format!("/api/status?{e}"))),
|
||||
Err(e) => Err(ApiError {
|
||||
error: e.to_string(),
|
||||
code: StatusCode::BAD_GATEWAY,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn status_redirect(Path(NamePath { name }): Path<NamePath>) -> Redirect {
|
||||
Redirect::permanent(&format!(
|
||||
"/api/status?name={name}",
|
||||
name = urlencoding::encode(&name) // just for
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
||||
crate::service::get_ips(&name).await.map_or_else(
|
||||
|e| ApiError::ise(e.to_string()).into_response(),
|
||||
|ips| Json(ips).into_response(),
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
use crate::http::route::error::ApiError;
|
||||
use axum::{Json, response::IntoResponse};
|
||||
|
||||
pub async fn devs_router() -> impl IntoResponse {
|
||||
match crate::service::list_interfaces().await {
|
||||
Ok(devs) => Json(devs).into_response(),
|
||||
Err(e) => ApiError::ise(e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
// Device listing endpoints
|
||||
@@ -1,27 +0,0 @@
|
||||
use crate::{http::route::error::ApiError, utils::parse::boolish_str};
|
||||
use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
|
||||
|
||||
// DHCP lease endpoints
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
pub struct DhcpLeasesQueryRaw {
|
||||
include_state: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_dhcp_leases(
|
||||
Query(DhcpLeasesQueryRaw { include_state }): Query<DhcpLeasesQueryRaw>,
|
||||
) -> impl IntoResponse {
|
||||
let include_state = include_state.as_deref().map(boolish_str).unwrap_or(false);
|
||||
|
||||
match crate::service::get_leases(wakey_core::LeaseQuery { include_state }).await {
|
||||
Ok(leases) => (
|
||||
StatusCode::OK,
|
||||
Json(crate::http::compat::legacy_leases_from_domain(leases)),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => ApiError {
|
||||
error: e.to_string(),
|
||||
code: StatusCode::BAD_GATEWAY,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use axum::{
|
||||
Json,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ApiError<T: Serialize> {
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub code: StatusCode,
|
||||
pub error: T,
|
||||
}
|
||||
|
||||
impl<T: Serialize> ApiError<T> {
|
||||
/// [StatusCode::INTERNAL_SERVER_ERROR] shortcut
|
||||
pub const fn ise(error: T) -> Self {
|
||||
Self {
|
||||
code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> IntoResponse for ApiError<T> {
|
||||
fn into_response(self) -> Response {
|
||||
(self.code, Json(self)).into_response()
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
pub mod api;
|
||||
pub mod devs;
|
||||
pub mod dhcp;
|
||||
pub mod error;
|
||||
pub mod status;
|
||||
pub mod wake;
|
||||
|
||||
use crate::http::route::api::ip;
|
||||
use crate::http::route::api::status_redirect;
|
||||
use crate::http::route::api::status_smart_redirect;
|
||||
use crate::http::route::devs::devs_router;
|
||||
use crate::http::route::dhcp::get_dhcp_leases;
|
||||
use crate::http::route::error::ApiError;
|
||||
use crate::http::route::status::get_status_json;
|
||||
use crate::http::route::wake::wake_multi;
|
||||
use crate::legacy::dhcpparse::load_mac_name_cache;
|
||||
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use axum::body::Body;
|
||||
use axum::http::Request;
|
||||
use axum::middleware::{self, Next};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::{get, post};
|
||||
use std::time::Instant;
|
||||
|
||||
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 {
|
||||
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))
|
||||
.route(
|
||||
"/mac-cache",
|
||||
get(async || match load_mac_name_cache().await {
|
||||
Ok(h) => Json(h).into_response(),
|
||||
Err(e) => ApiError::ise(e.to_string()).into_response(),
|
||||
}),
|
||||
)
|
||||
.layer(middleware::from_fn(add_performance_header))
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use crate::http::route::error::ApiError;
|
||||
use axum::{Json, http::StatusCode, response::IntoResponse};
|
||||
use axum_extra::extract::Query;
|
||||
pub use wakey_core::{DeviceQuery, NamePath};
|
||||
|
||||
pub async fn get_status_json(Query(query): Query<DeviceQuery>) -> impl IntoResponse {
|
||||
match crate::service::inventory(query.clone()).await {
|
||||
Ok(inventory) => (
|
||||
StatusCode::OK,
|
||||
Json(crate::http::compat::legacy_status_from_inventory(
|
||||
inventory,
|
||||
query.name,
|
||||
query.filter,
|
||||
)),
|
||||
)
|
||||
.into_response(),
|
||||
Err(error) => ApiError {
|
||||
code: StatusCode::BAD_GATEWAY,
|
||||
error: error
|
||||
.chain()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(": "),
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
// Status endpoints
|
||||
@@ -1,17 +0,0 @@
|
||||
use crate::http::route::error::ApiError;
|
||||
use axum::{extract::Json, http::StatusCode, response::IntoResponse};
|
||||
pub use wakey_core::WakeTarget;
|
||||
|
||||
pub async fn wake_multi(Json(req): Json<Vec<WakeTarget>>) -> impl IntoResponse {
|
||||
match crate::service::wake_targets(req).await {
|
||||
Ok(result) => (
|
||||
StatusCode::OK,
|
||||
Json(crate::http::compat::legacy_wake_from_domain(result)),
|
||||
)
|
||||
.into_response(),
|
||||
Err(error) => {
|
||||
let error = format!("Error: {}", error);
|
||||
ApiError::ise(error).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub use wakey_core::NeighborEntry as IpNeighLine;
|
||||
pub use wakey_core::NeighborState as NUDState;
|
||||
@@ -1 +0,0 @@
|
||||
pub use wakey_linux::dhcp::{load_mac_name_cache, read_dhcp_leases_with_names};
|
||||
@@ -1,8 +0,0 @@
|
||||
//! Transitional compatibility wrappers preserved during the migration.
|
||||
//!
|
||||
//! Items in this module exist so the codebase can keep working while older
|
||||
//! parsing paths and adapter surfaces are being retired or replaced. New logic
|
||||
//! should prefer the service layer and the dedicated crate boundaries instead.
|
||||
|
||||
pub mod arpparse;
|
||||
pub mod dhcpparse;
|
||||
@@ -1,9 +1,6 @@
|
||||
pub mod http;
|
||||
pub mod legacy;
|
||||
pub mod service;
|
||||
pub mod utils;
|
||||
|
||||
pub use http::{http_app, serve_http, serve_http_from_current_exe};
|
||||
pub use service::{
|
||||
StatusResponse, broadcast_wake_targets, device_to_status_rows, get_interface_summaries,
|
||||
get_interface_summary, get_ips, get_leases, get_status, get_status_for_input, inventory,
|
||||
|
||||
@@ -12,7 +12,3 @@
|
||||
// this is so bad
|
||||
pub mod ping;
|
||||
pub mod query;
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/// key for yes: "1" | "true" | "yes" | "on" | "y"
|
||||
///
|
||||
/// frfr
|
||||
pub fn _de_boolish<'de, D>(des: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Boolish {
|
||||
B(bool),
|
||||
I(u8),
|
||||
S(String),
|
||||
}
|
||||
Ok(match Boolish::deserialize(des)? {
|
||||
Boolish::B(b) => b,
|
||||
Boolish::I(i) => i != 0,
|
||||
Boolish::S(s) => {
|
||||
let t = s.trim().to_ascii_lowercase();
|
||||
if t.is_empty() {
|
||||
true // presence implies true
|
||||
} else {
|
||||
matches!(t.as_str(), "1" | "true" | "yes" | "on" | "y")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a tolerant boolean value from a string.
|
||||
/// Accepts: "1", "true", "yes", "on", "y" as true; "0", "false", "no", "off", "n" as false.
|
||||
/// Empty string means true (presence-only query flag).
|
||||
pub fn boolish_str(s: &str) -> bool {
|
||||
let t = s.trim().to_ascii_lowercase();
|
||||
if t.is_empty() {
|
||||
return true;
|
||||
}
|
||||
matches!(t.as_str(), "1" | "true" | "yes" | "on" | "y")
|
||||
|| (!matches!(t.as_str(), "0" | "false" | "no" | "off" | "n")
|
||||
&& t.parse::<u64>().map(|n| n != 0).unwrap_or(false))
|
||||
}
|
||||
+4
-3
@@ -6,8 +6,9 @@ use tokio::{
|
||||
net::{TcpStream, ToSocketAddrs},
|
||||
time::timeout,
|
||||
};
|
||||
use wakey_core::NeighborState;
|
||||
|
||||
use crate::{legacy::arpparse::NUDState, utils::query::get_mac};
|
||||
use crate::utils::query::get_mac;
|
||||
|
||||
pub async fn _ping_ip<T: ToSocketAddrs>(addr: T) -> bool {
|
||||
timeout(Duration::from_secs(1), TcpStream::connect(addr))
|
||||
@@ -19,13 +20,13 @@ pub async fn _ping_ip_2<T: ToSocketAddrs>(_addr: T) -> bool {
|
||||
}
|
||||
|
||||
pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
|
||||
match get_mac(Some(addr.into()), None, &[] as &[NUDState]).await {
|
||||
match get_mac(Some(addr.into()), None, &[] as &[NeighborState]).await {
|
||||
Err(_) => 0,
|
||||
Ok(l) => l
|
||||
.into_iter()
|
||||
.map(|e| e.state)
|
||||
.max()
|
||||
.map(NUDState::rank)
|
||||
.map(NeighborState::rank)
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -16,8 +16,7 @@ pub use macs::*;
|
||||
pub mod macs {
|
||||
use anyhow::Result;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::legacy::arpparse::{IpNeighLine, NUDState};
|
||||
use wakey_core::{NeighborEntry, NeighborState};
|
||||
|
||||
pub async fn get_ips(machine_name: &str) -> Result<impl Iterator<Item = IpAddr>> {
|
||||
wakey_linux::devices::get_ips(machine_name).await
|
||||
@@ -27,17 +26,17 @@ pub mod macs {
|
||||
machine_names: &[impl AsRef<str>],
|
||||
ips: &[IpAddr],
|
||||
devs: &[impl AsRef<str>],
|
||||
state: &[NUDState],
|
||||
state: &[NeighborState],
|
||||
macs: &[macaddr::MacAddr],
|
||||
) -> Result<Vec<IpNeighLine>> {
|
||||
) -> Result<Vec<NeighborEntry>> {
|
||||
wakey_linux::devices::get_neighbors(machine_names, ips, devs, state, macs).await
|
||||
}
|
||||
|
||||
pub async fn get_mac(
|
||||
ip: Option<IpAddr>,
|
||||
dev: Option<&str>,
|
||||
state: &[NUDState],
|
||||
) -> Result<Vec<IpNeighLine>> {
|
||||
state: &[NeighborState],
|
||||
) -> Result<Vec<NeighborEntry>> {
|
||||
let ips: Vec<IpAddr> = ip.into_iter().collect();
|
||||
let devs: Vec<&str> = dev.into_iter().collect();
|
||||
get_macs(&[] as &[&str], &ips, &devs, state, &[]).await
|
||||
|
||||
Reference in New Issue
Block a user