#![allow(clippy::struct_excessive_bools)]
use std::fmt;
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
Option::<T>::deserialize(deserializer).map(Option::unwrap_or_default)
}
#[allow(clippy::ref_option)] fn serialize_optional_secret<S>(
value: &Option<SecretString>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(value) => serializer.serialize_some(value.expose_secret()),
None => serializer.serialize_none(),
}
}
#[allow(clippy::trivially_copy_pass_by_ref)] const fn is_false(value: &bool) -> bool {
!*value
}
macro_rules! impl_string_enum {
($name:ident { $($wire:literal => $variant:ident),+ $(,)? }) => {
impl $name {
pub fn as_str(&self) -> &str {
match self {
$(Self::$variant => $wire,)+
Self::Other(value) => value,
}
}
}
impl Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Ok(match value.as_str() {
$($wire => Self::$variant,)+
_ => Self::Other(value),
})
}
}
};
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum NodeScheme {
Http,
#[default]
Https,
Other(String),
}
impl_string_enum!(NodeScheme {
"http" => Http,
"https" => Https,
});
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum NodeTlsVerifyMode {
#[default]
Verify,
Skip,
Pin,
Mtls,
Other(String),
}
impl_string_enum!(NodeTlsVerifyMode {
"verify" => Verify,
"skip" => Skip,
"pin" => Pin,
"mtls" => Mtls,
});
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum NodeInboundSyncMode {
#[default]
All,
Selected,
Other(String),
}
impl_string_enum!(NodeInboundSyncMode {
"all" => All,
"selected" => Selected,
});
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum NodeStatus {
#[default]
Unknown,
Online,
Offline,
Other(String),
}
impl_string_enum!(NodeStatus {
"unknown" => Unknown,
"online" => Online,
"offline" => Offline,
});
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum RemoteInboundProtocol {
#[default]
Unknown,
Vmess,
Vless,
Trojan,
Shadowsocks,
Wireguard,
Hysteria,
Http,
Mixed,
Tunnel,
Tun,
Mtproto,
Amneziawg,
Tuic,
Other(String),
}
impl_string_enum!(RemoteInboundProtocol {
"amneziawg" => Amneziawg,
"tuic" => Tuic,
"" => Unknown,
"vmess" => Vmess,
"vless" => Vless,
"trojan" => Trojan,
"shadowsocks" => Shadowsocks,
"wireguard" => Wireguard,
"hysteria" => Hysteria,
"http" => Http,
"mixed" => Mixed,
"tunnel" => Tunnel,
"tun" => Tun,
"mtproto" => Mtproto,
});
#[derive(Clone, Default, Deserialize, PartialEq, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct NodeView {
pub id: i64,
pub name: String,
pub remark: String,
pub scheme: NodeScheme,
pub address: String,
pub port: u16,
pub base_path: String,
pub has_api_token: bool,
pub enable: bool,
pub allow_private_address: bool,
pub tls_verify_mode: NodeTlsVerifyMode,
pub pinned_cert_sha256: String,
pub inbound_sync_mode: NodeInboundSyncMode,
#[serde(deserialize_with = "deserialize_null_default")]
pub inbound_tags: Vec<String>,
pub outbound_tag: String,
pub guid: String,
pub status: NodeStatus,
pub last_heartbeat: i64,
pub latency_ms: i64,
pub xray_version: String,
pub panel_version: String,
pub cpu_pct: f64,
pub mem_pct: f64,
pub uptime_secs: u64,
pub net_up: u64,
pub net_down: u64,
pub last_error: String,
pub xray_state: String,
pub xray_error: String,
pub config_dirty: bool,
pub config_dirty_at: i64,
pub inbound_count: i64,
pub client_count: i64,
pub online_count: i64,
pub active_count: i64,
pub disabled_count: i64,
pub depleted_count: i64,
pub parent_guid: String,
pub transitive: bool,
pub created_at: i64,
pub updated_at: i64,
}
impl NodeView {
pub fn to_request(&self) -> NodeRequest {
NodeRequest {
id: self.id,
name: self.name.clone(),
remark: self.remark.clone(),
scheme: self.scheme.clone(),
address: self.address.clone(),
port: self.port,
base_path: self.base_path.clone(),
api_token: None,
clear_api_token: false,
enable: self.enable,
allow_private_address: self.allow_private_address,
tls_verify_mode: self.tls_verify_mode.clone(),
pinned_cert_sha256: self.pinned_cert_sha256.clone(),
inbound_sync_mode: self.inbound_sync_mode.clone(),
inbound_tags: self.inbound_tags.clone(),
outbound_tag: self.outbound_tag.clone(),
}
}
}
impl fmt::Debug for NodeView {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("NodeView")
.field("id", &self.id)
.field("name", &self.name)
.field("remark", &self.remark)
.field("scheme", &self.scheme)
.field("address", &self.address)
.field("port", &self.port)
.field("base_path", &"[REDACTED]")
.field("has_api_token", &self.has_api_token)
.field("enable", &self.enable)
.field("allow_private_address", &self.allow_private_address)
.field("tls_verify_mode", &self.tls_verify_mode)
.field("pinned_cert_sha256", &self.pinned_cert_sha256)
.field("inbound_sync_mode", &self.inbound_sync_mode)
.field("inbound_tags", &self.inbound_tags)
.field("outbound_tag", &"[REDACTED]")
.field("guid", &self.guid)
.field("status", &self.status)
.field("last_heartbeat", &self.last_heartbeat)
.field("latency_ms", &self.latency_ms)
.field("xray_version", &self.xray_version)
.field("panel_version", &self.panel_version)
.field("cpu_pct", &self.cpu_pct)
.field("mem_pct", &self.mem_pct)
.field("uptime_secs", &self.uptime_secs)
.field("net_up", &self.net_up)
.field("net_down", &self.net_down)
.field("last_error", &self.last_error)
.field("xray_state", &self.xray_state)
.field("xray_error", &self.xray_error)
.field("config_dirty", &self.config_dirty)
.field("config_dirty_at", &self.config_dirty_at)
.field("inbound_count", &self.inbound_count)
.field("client_count", &self.client_count)
.field("online_count", &self.online_count)
.field("active_count", &self.active_count)
.field("disabled_count", &self.disabled_count)
.field("depleted_count", &self.depleted_count)
.field("parent_guid", &self.parent_guid)
.field("transitive", &self.transitive)
.field("created_at", &self.created_at)
.field("updated_at", &self.updated_at)
.finish()
}
}
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NodeRequest {
pub id: i64,
pub name: String,
pub remark: String,
pub scheme: NodeScheme,
pub address: String,
pub port: u16,
pub base_path: String,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_secret"
)]
api_token: Option<SecretString>,
#[serde(skip_serializing_if = "is_false")]
clear_api_token: bool,
pub enable: bool,
pub allow_private_address: bool,
pub tls_verify_mode: NodeTlsVerifyMode,
pub pinned_cert_sha256: String,
pub inbound_sync_mode: NodeInboundSyncMode,
pub inbound_tags: Vec<String>,
pub outbound_tag: String,
}
impl NodeRequest {
pub fn new(name: impl Into<String>, address: impl Into<String>, port: u16) -> Self {
Self {
id: 0,
name: name.into(),
remark: String::new(),
scheme: NodeScheme::Https,
address: address.into(),
port,
base_path: "/".to_owned(),
api_token: None,
clear_api_token: false,
enable: true,
allow_private_address: false,
tls_verify_mode: NodeTlsVerifyMode::Verify,
pinned_cert_sha256: String::new(),
inbound_sync_mode: NodeInboundSyncMode::All,
inbound_tags: Vec::new(),
outbound_tag: String::new(),
}
}
#[must_use]
pub fn with_api_token(mut self, token: impl Into<String>) -> Self {
self.set_api_token(token);
self
}
pub fn set_api_token(&mut self, token: impl Into<String>) {
self.api_token = Some(SecretString::from(token.into()));
self.clear_api_token = false;
}
pub fn clear_stored_api_token(&mut self) {
self.api_token = None;
self.clear_api_token = true;
}
pub fn retain_stored_api_token(&mut self) {
self.api_token = None;
self.clear_api_token = false;
}
pub fn api_token(&self) -> Option<&str> {
self.api_token.as_ref().map(ExposeSecret::expose_secret)
}
pub const fn clears_stored_api_token(&self) -> bool {
self.clear_api_token
}
}
impl fmt::Debug for NodeRequest {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("NodeRequest")
.field("id", &self.id)
.field("name", &self.name)
.field("remark", &self.remark)
.field("scheme", &self.scheme)
.field("address", &self.address)
.field("port", &self.port)
.field("base_path", &"[REDACTED]")
.field("api_token", &self.api_token.as_ref().map(|_| "[REDACTED]"))
.field("clear_api_token", &self.clear_api_token)
.field("enable", &self.enable)
.field("allow_private_address", &self.allow_private_address)
.field("tls_verify_mode", &self.tls_verify_mode)
.field("pinned_cert_sha256", &self.pinned_cert_sha256)
.field("inbound_sync_mode", &self.inbound_sync_mode)
.field("inbound_tags", &self.inbound_tags)
.field("outbound_tag", &"[REDACTED]")
.finish()
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct NodeProbeResult {
pub status: NodeStatus,
pub latency_ms: i64,
pub xray_version: String,
pub panel_version: String,
pub cpu_pct: f64,
pub mem_pct: f64,
pub uptime_secs: u64,
pub error: String,
pub xray_state: String,
pub xray_error: String,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct RemoteInboundOption {
pub tag: String,
pub remark: String,
pub protocol: RemoteInboundProtocol,
pub port: u16,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct NodeUpdateResult {
pub id: i64,
pub name: String,
pub ok: bool,
pub error: String,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum NodeUpdateChannel {
#[default]
Stable,
Development,
}
impl NodeUpdateChannel {
pub(crate) const fn is_development(self) -> bool {
matches!(self, Self::Development)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum NodeMetric {
Cpu,
Memory,
NetworkUp,
NetworkDown,
}
impl NodeMetric {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Cpu => "cpu",
Self::Memory => "mem",
Self::NetworkUp => "netUp",
Self::NetworkDown => "netDown",
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct NodeMtlsCa {
pub ca_cert: String,
}