im not sure what im changing
This commit is contained in:
Generated
+1
@@ -632,6 +632,7 @@ dependencies = [
|
|||||||
"rtnetlink",
|
"rtnetlink",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
"strum",
|
"strum",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ macaddr = { version = "1.0.1", features = ["serde", "serde_std"] }
|
|||||||
strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
|
strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
|
||||||
serde_json = "1.0.143"
|
serde_json = "1.0.143"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_with = { version = "3.14.0", features = ["json"] }
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
tokio = { version = "1.47.1", features = ["fs", "process", "rt-multi-thread", "io-util", "macros"] }
|
tokio = { version = "1.47.1", features = ["fs", "process", "rt-multi-thread", "io-util", "macros"] }
|
||||||
|
|||||||
+2
-2
@@ -1,13 +1,13 @@
|
|||||||
//! low
|
//! low
|
||||||
//!
|
//!
|
||||||
//! # lda-ipj
|
//! # lda-ipjs
|
||||||
//!
|
//!
|
||||||
//! this package will represent all my needs with the all the subcommands of ip -j.
|
//! this package will represent all my needs with the all the subcommands of ip -j.
|
||||||
//!
|
//!
|
||||||
//! ## what i need
|
//! ## what i need
|
||||||
//!
|
//!
|
||||||
//! ```console
|
//! ```console
|
||||||
//! ip -j neigh
|
//! ip -j neigh show
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! i also need to see devices and idk MAYBE maybe not MAYBE UHHHHHH maybe broadcast
|
//! i also need to see devices and idk MAYBE maybe not MAYBE UHHHHHH maybe broadcast
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// ip address [ show [ dev IFNAME ] [ scope SCOPE-ID ] [ master DEVICE ]
|
||||||
|
// [ type TYPE ] [ to PREFIX ] [ FLAG-LIST ]
|
||||||
|
// [ label LABEL ] [up] [ vrf NAME ] ]
|
||||||
|
// fuck is this mean
|
||||||
|
|
||||||
|
// do i need all this? do i need anything but `ip -j a show dev br-lan`?
|
||||||
|
|
||||||
|
// TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap |
|
||||||
|
// bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | lowpan |
|
||||||
|
// gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan | vti |
|
||||||
|
// nlmon | can | bond_slave | ipvlan | geneve | bridge_slave |
|
||||||
|
// hsr | macsec | netdevsim }
|
||||||
|
// FLAG-LIST := [ FLAG-LIST ] FLAG
|
||||||
|
// FLAG := [ permanent | dynamic | secondary | primary |
|
||||||
|
// [-]tentative | [-]deprecated | [-]dadfailed | temporary |
|
||||||
|
// CONFFLAG-LIST ]
|
||||||
|
// CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG
|
||||||
|
// CONFFLAG := [ home | nodad | mngtmpaddr | noprefixroute | autojoin ]
|
||||||
|
|
||||||
|
// prefix seems to be a cidr. both 6 and 4 works. idfk dog
|
||||||
|
|
||||||
|
use super::AddrInfo;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn get() -> Vec<AddrInfo> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
@@ -4,39 +4,38 @@
|
|||||||
//!
|
//!
|
||||||
//! lowk why its free but its indirection and its ass
|
//! lowk why its free but its indirection and its ass
|
||||||
|
|
||||||
use macaddr::MacAddr6;
|
pub mod json;
|
||||||
|
|
||||||
|
use crate::utils::serialize::mac::option_mac;
|
||||||
|
use macaddr::MacAddr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// i dont include what i dont know about (almost all ts)
|
/// i dont include what i dont know about (almost all ts)
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct CommmonOutput {
|
|
||||||
ifindex: u32,
|
|
||||||
ifname: String,
|
|
||||||
/// i imagine UP or DOWN, unknown
|
|
||||||
operstate: String,
|
|
||||||
// 6 has a serde and the enum doesnt? why.
|
|
||||||
address: Option<MacAddr6>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Deserialize)]
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
struct AddrOutput {
|
pub struct AddrOutput {
|
||||||
#[serde(flatten)]
|
pub ifindex: u32,
|
||||||
c: CommmonOutput,
|
pub ifname: String,
|
||||||
addr_info: Vec<AddrInfo>,
|
/// i imagine UP or DOWN, unknown
|
||||||
|
pub operstate: String,
|
||||||
|
// 6 has a serde and the enum doesnt? why. (serializing ts is ass although... im not given an array. they string formatted ts)
|
||||||
|
#[serde(with = "option_mac", default)]
|
||||||
|
pub address: Option<MacAddr>,
|
||||||
|
#[serde(default)] // i wish we have intellisense for this... fuck you metaprogramming
|
||||||
|
pub addr_info: Vec<AddrInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// i be copying
|
// i be copying
|
||||||
// Raw JSON shape from ip -j -4 address show
|
// Raw JSON shape from ip -j -4 address show
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
struct AddrInfo {
|
pub struct AddrInfo {
|
||||||
family: Option<String>,
|
pub family: Option<String>,
|
||||||
|
|
||||||
local: Option<String>,
|
pub local: Option<String>,
|
||||||
prefixlen: Option<u8>,
|
pub prefixlen: Option<u8>,
|
||||||
|
|
||||||
broadcast: Option<String>,
|
pub broadcast: Option<String>,
|
||||||
|
|
||||||
scope: Option<String>,
|
pub scope: Option<String>,
|
||||||
label: Option<String>,
|
pub label: Option<String>,
|
||||||
// many more exist; we only take what we need
|
// many more exist; we only take what we need
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
pub mod json;
|
pub mod json;
|
||||||
pub mod nl;
|
pub mod nl;
|
||||||
|
|
||||||
use crate::utils::serialize::mac::{des_opm, ser_opm};
|
use crate::utils::serialize::mac::option_mac;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use macaddr::MacAddr;
|
use macaddr::MacAddr;
|
||||||
@@ -17,11 +17,11 @@ use strum::{Display, EnumString};
|
|||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
||||||
pub struct NeighborInput {
|
pub struct NeighborInput {
|
||||||
/// supports only the last item (ignore), `to` keyword is optional
|
/// supports only the last item (ignore), `to` keyword is optional
|
||||||
to: Option<IpAddr>,
|
pub to: Option<IpAddr>,
|
||||||
/// supports only one item (it complains if multiple)
|
/// supports only one item (it complains if multiple)
|
||||||
dev: Option<String>, // im all for simplicity
|
pub dev: Option<String>, // im all for simplicity
|
||||||
/// takes multiple, has to have `nud` before bro or it will think you `to`
|
/// takes multiple, has to have `nud` before bro or it will think you `to`
|
||||||
nud: Vec<NUDState>,
|
pub nud: Vec<NUDState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// as input this must be lowercase. as output it is uppercase
|
// as input this must be lowercase. as output it is uppercase
|
||||||
@@ -64,21 +64,18 @@ pub enum NUDState {
|
|||||||
/// success, neighbor validation has ultimately
|
/// success, neighbor validation has ultimately
|
||||||
/// failed.
|
/// failed.
|
||||||
Failed,
|
Failed,
|
||||||
|
|
||||||
|
Other(u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// everything i see
|
/// everything i see
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
||||||
pub struct NeighborItem {
|
pub struct NeighborItem {
|
||||||
#[serde(rename(deserialize = "dst"))]
|
#[serde(rename(deserialize = "dst"))]
|
||||||
ip: IpAddr,
|
pub ip: IpAddr,
|
||||||
dev: String,
|
pub dev: String,
|
||||||
#[serde(
|
#[serde(with = "option_mac", default, rename(deserialize = "lladdr"))]
|
||||||
deserialize_with = "des_opm",
|
pub mac: Option<MacAddr>,
|
||||||
serialize_with = "ser_opm",
|
|
||||||
default,
|
|
||||||
rename(deserialize = "lladdr")
|
|
||||||
)]
|
|
||||||
mac: Option<MacAddr>,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
state: Vec<NUDState>,
|
pub state: Vec<NUDState>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl TryFrom<NeighbourState> for NUDState {
|
|||||||
NeighbourState::Noarp => Ok(Self::Noarp),
|
NeighbourState::Noarp => Ok(Self::Noarp),
|
||||||
NeighbourState::Permanent => Ok(Self::Permanent),
|
NeighbourState::Permanent => Ok(Self::Permanent),
|
||||||
NeighbourState::None => Ok(Self::None),
|
NeighbourState::None => Ok(Self::None),
|
||||||
NeighbourState::Other(e) => Err(e),
|
NeighbourState::Other(e) => Ok(Self::Other(e)),
|
||||||
_ => Err(u16::MAX), // idk
|
_ => Err(u16::MAX), // idk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize a MacAddr as a string
|
/// Serialize a MacAddr as a string
|
||||||
pub fn serialize_mac<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
pub fn serialize<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
@@ -19,7 +19,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize a MacAddr from a string
|
/// Deserialize a MacAddr from a string
|
||||||
pub fn deserialize_mac<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
@@ -27,18 +27,20 @@ where
|
|||||||
s.parse::<MacAddr>().map_err(DeError::custom)
|
s.parse::<MacAddr>().map_err(DeError::custom)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// serialize an [`Option<MacAddr>`]
|
pub mod option_mac {
|
||||||
pub fn ser_opm<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
use super::*;
|
||||||
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
/// serialize an [`Option<MacAddr>`]
|
||||||
}
|
pub fn serialize<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
||||||
|
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
||||||
/// deserialize an [`Option<MacAddr>`]
|
}
|
||||||
pub fn des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
/// deserialize an [`Option<MacAddr>`]
|
||||||
where
|
pub fn deserialize<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
||||||
D: serde::Deserializer<'de>,
|
where
|
||||||
{
|
D: serde::Deserializer<'de>,
|
||||||
Option::<&str>::deserialize(des)?
|
{
|
||||||
.map(str::parse)
|
Option::<&str>::deserialize(des)?
|
||||||
.transpose()
|
.map(str::parse)
|
||||||
.map_err(de::Error::custom)
|
.transpose()
|
||||||
|
.map_err(de::Error::custom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-5
@@ -41,7 +41,7 @@ pub struct IpNeighLine {
|
|||||||
|
|
||||||
// NUDState custom Deserialize now lives in arpparse/impl.rs; use serde_with OneOrMany for Vec
|
// NUDState custom Deserialize now lives in arpparse/impl.rs; use serde_with OneOrMany for Vec
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, serde::Serialize)]
|
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, serde::Serialize, Default)]
|
||||||
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
|
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
pub enum NUDState {
|
pub enum NUDState {
|
||||||
@@ -63,10 +63,7 @@ pub enum NUDState {
|
|||||||
/// neighbour state if it was valid and the
|
/// neighbour state if it was valid and the
|
||||||
/// address is not changed by this command.
|
/// address is not changed by this command.
|
||||||
Stale,
|
Stale,
|
||||||
/// this is a pseudo state used when initially
|
|
||||||
/// creating a neighbour entry or after trying to
|
|
||||||
/// remove it before it becomes free to do so.
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// the neighbour entry has not (yet) been
|
/// the neighbour entry has not (yet) been
|
||||||
/// validated/resolved.
|
/// validated/resolved.
|
||||||
@@ -80,6 +77,13 @@ pub enum NUDState {
|
|||||||
/// success, neighbor validation has ultimately
|
/// success, neighbor validation has ultimately
|
||||||
/// failed.
|
/// failed.
|
||||||
Failed,
|
Failed,
|
||||||
|
|
||||||
|
/// this is a pseudo state used when initially
|
||||||
|
/// creating a neighbour entry or after trying to
|
||||||
|
/// remove it before it becomes free to do so.
|
||||||
|
#[serde(other)]
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NUDState {
|
impl NUDState {
|
||||||
|
|||||||
+5
-12
@@ -49,12 +49,10 @@ pub async fn status_smart_redirect(
|
|||||||
};
|
};
|
||||||
match serde_html_form::to_string(query) {
|
match serde_html_form::to_string(query) {
|
||||||
Ok(e) => Ok(Redirect::to(&format!("/api/status?{e}"))),
|
Ok(e) => Ok(Redirect::to(&format!("/api/status?{e}"))),
|
||||||
Err(e) => Err((
|
Err(e) => Err(ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
error: e.to_string(),
|
||||||
Json(ApiError {
|
code: StatusCode::BAD_GATEWAY,
|
||||||
error: e.to_string(),
|
}),
|
||||||
}),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +65,7 @@ pub async fn status_redirect(Path(NamePath { name }): Path<NamePath>) -> Redirec
|
|||||||
|
|
||||||
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
||||||
get_ips(&name).await.map_or_else(
|
get_ips(&name).await.map_or_else(
|
||||||
|e| {
|
|e| ApiError::ise(e.to_string()).into_response(),
|
||||||
ApiError {
|
|
||||||
error: e.to_string(),
|
|
||||||
}
|
|
||||||
.into_response()
|
|
||||||
},
|
|
||||||
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-7
@@ -26,12 +26,10 @@ pub async fn get_dhcp_leases(Query(raw): Query<DhcpLeasesQueryRaw>) -> impl Into
|
|||||||
let out = enrich_leases_with_nud_state(leases_with_names).await;
|
let out = enrich_leases_with_nud_state(leases_with_names).await;
|
||||||
(StatusCode::OK, Json(out)).into_response()
|
(StatusCode::OK, Json(out)).into_response()
|
||||||
}
|
}
|
||||||
Err(e) => (
|
Err(e) => ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
error: e.to_string(),
|
||||||
Json(ApiError {
|
code: StatusCode::BAD_GATEWAY,
|
||||||
error: e.to_string(),
|
}
|
||||||
}),
|
.into_response(),
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -7,11 +7,23 @@ use serde::Serialize;
|
|||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ApiError {
|
pub struct ApiError {
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub code: StatusCode,
|
||||||
pub error: String,
|
pub error: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ApiError {
|
||||||
|
/// [StatusCode::INTERNAL_SERVER_ERROR]
|
||||||
|
pub const fn ise(error: String) -> Self {
|
||||||
|
Self {
|
||||||
|
code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoResponse for ApiError {
|
impl IntoResponse for ApiError {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
|
(self.code, Json(self)).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-7
@@ -94,13 +94,11 @@ pub async fn get_status_json(
|
|||||||
)
|
)
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
Err(error) => (
|
Err(error) => ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
code: StatusCode::BAD_GATEWAY,
|
||||||
Json(ApiError {
|
error: error.to_string(),
|
||||||
error: error.to_string(),
|
}
|
||||||
}),
|
.into_response(),
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Status endpoints
|
// Status endpoints
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub async fn get_macs(
|
|||||||
let ip_m: HashSet<IpAddr> = futures::future::try_join_all(
|
let ip_m: HashSet<IpAddr> = futures::future::try_join_all(
|
||||||
machine_names
|
machine_names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| async { get_ips(c.as_ref()).await }),
|
.map(|c| get_ips(c.as_ref())),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ impl From<WakeTargetResult> for RouteWakeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RouteWakeTarget {
|
impl RouteWakeTarget {
|
||||||
pub fn to_incomplete(self) -> RouteWakeResult {
|
pub const fn to_incomplete(self) -> RouteWakeResult {
|
||||||
RouteWakeResult {
|
RouteWakeResult {
|
||||||
target: self,
|
target: self,
|
||||||
status: RouteWakeStatus::Incomplete,
|
status: RouteWakeStatus::Incomplete,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_incomplete(&self) -> bool {
|
pub const fn is_incomplete(&self) -> bool {
|
||||||
!matches!(
|
!matches!(
|
||||||
self,
|
self,
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -22,21 +22,21 @@ pub enum WakeStatus {
|
|||||||
WrongSize,
|
WrongSize,
|
||||||
}
|
}
|
||||||
impl WakeTarget {
|
impl WakeTarget {
|
||||||
fn _new(ip: IpAddr, mac: MacAddr) -> Self {
|
const fn _new(ip: IpAddr, mac: MacAddr) -> Self {
|
||||||
Self { ip, mac }
|
Self { ip, mac }
|
||||||
}
|
}
|
||||||
fn good(self) -> WakeTargetResult {
|
const fn good(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::Success)
|
WakeTargetResult::new(self, WakeStatus::Success)
|
||||||
}
|
}
|
||||||
fn bad(self) -> WakeTargetResult {
|
const fn bad(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::WrongSize)
|
WakeTargetResult::new(self, WakeStatus::WrongSize)
|
||||||
}
|
}
|
||||||
fn errored(self) -> WakeTargetResult {
|
const fn errored(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
|
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl WakeTargetResult {
|
impl WakeTargetResult {
|
||||||
fn new(target: WakeTarget, status: WakeStatus) -> Self {
|
const fn new(target: WakeTarget, status: WakeStatus) -> Self {
|
||||||
Self { target, status }
|
Self { target, status }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user