1#![allow(clippy::struct_excessive_bools)]
2
3use std::fmt;
4
5use secrecy::{ExposeSecret, SecretString};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
9where
10 D: Deserializer<'de>,
11 T: Deserialize<'de> + Default,
12{
13 Option::<T>::deserialize(deserializer).map(Option::unwrap_or_default)
14}
15
16#[allow(clippy::ref_option)] fn serialize_optional_secret<S>(
18 value: &Option<SecretString>,
19 serializer: S,
20) -> Result<S::Ok, S::Error>
21where
22 S: Serializer,
23{
24 match value {
25 Some(value) => serializer.serialize_some(value.expose_secret()),
26 None => serializer.serialize_none(),
27 }
28}
29
30#[allow(clippy::trivially_copy_pass_by_ref)] const fn is_false(value: &bool) -> bool {
32 !*value
33}
34
35macro_rules! impl_string_enum {
36 ($name:ident { $($wire:literal => $variant:ident),+ $(,)? }) => {
37 impl $name {
38 pub fn as_str(&self) -> &str {
40 match self {
41 $(Self::$variant => $wire,)+
42 Self::Other(value) => value,
43 }
44 }
45 }
46
47 impl Serialize for $name {
48 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49 where
50 S: Serializer,
51 {
52 serializer.serialize_str(self.as_str())
53 }
54 }
55
56 impl<'de> Deserialize<'de> for $name {
57 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58 where
59 D: Deserializer<'de>,
60 {
61 let value = String::deserialize(deserializer)?;
62 Ok(match value.as_str() {
63 $($wire => Self::$variant,)+
64 _ => Self::Other(value),
65 })
66 }
67 }
68 };
69}
70
71#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
73#[non_exhaustive]
74pub enum NodeScheme {
75 Http,
77 #[default]
79 Https,
80 Other(String),
82}
83
84impl_string_enum!(NodeScheme {
85 "http" => Http,
86 "https" => Https,
87});
88
89#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
91#[non_exhaustive]
92pub enum NodeTlsVerifyMode {
93 #[default]
95 Verify,
96 Skip,
98 Pin,
100 Mtls,
102 Other(String),
104}
105
106impl_string_enum!(NodeTlsVerifyMode {
107 "verify" => Verify,
108 "skip" => Skip,
109 "pin" => Pin,
110 "mtls" => Mtls,
111});
112
113#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
115#[non_exhaustive]
116pub enum NodeInboundSyncMode {
117 #[default]
119 All,
120 Selected,
122 Other(String),
124}
125
126impl_string_enum!(NodeInboundSyncMode {
127 "all" => All,
128 "selected" => Selected,
129});
130
131#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
133#[non_exhaustive]
134pub enum NodeStatus {
135 #[default]
137 Unknown,
138 Online,
140 Offline,
142 Other(String),
144}
145
146impl_string_enum!(NodeStatus {
147 "unknown" => Unknown,
148 "online" => Online,
149 "offline" => Offline,
150});
151
152#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
154#[non_exhaustive]
155pub enum RemoteInboundProtocol {
156 #[default]
158 Unknown,
159 Vmess,
161 Vless,
163 Trojan,
165 Shadowsocks,
167 Wireguard,
169 Hysteria,
171 Http,
173 Mixed,
175 Tunnel,
177 Tun,
179 Mtproto,
181 Amneziawg,
183 Tuic,
185 Other(String),
187}
188
189impl_string_enum!(RemoteInboundProtocol {
190 "amneziawg" => Amneziawg,
191 "tuic" => Tuic,
192 "" => Unknown,
193 "vmess" => Vmess,
194 "vless" => Vless,
195 "trojan" => Trojan,
196 "shadowsocks" => Shadowsocks,
197 "wireguard" => Wireguard,
198 "hysteria" => Hysteria,
199 "http" => Http,
200 "mixed" => Mixed,
201 "tunnel" => Tunnel,
202 "tun" => Tun,
203 "mtproto" => Mtproto,
204});
205
206#[derive(Clone, Default, Deserialize, PartialEq, Serialize)]
211#[serde(default, rename_all = "camelCase")]
212pub struct NodeView {
213 pub id: i64,
215 pub name: String,
217 pub remark: String,
219 pub scheme: NodeScheme,
221 pub address: String,
223 pub port: u16,
225 pub base_path: String,
227 pub has_api_token: bool,
229 pub enable: bool,
231 pub allow_private_address: bool,
233 pub tls_verify_mode: NodeTlsVerifyMode,
235 pub pinned_cert_sha256: String,
237 pub inbound_sync_mode: NodeInboundSyncMode,
239 #[serde(deserialize_with = "deserialize_null_default")]
241 pub inbound_tags: Vec<String>,
242 pub outbound_tag: String,
244 pub guid: String,
246 pub status: NodeStatus,
248 pub last_heartbeat: i64,
250 pub latency_ms: i64,
252 pub xray_version: String,
254 pub panel_version: String,
256 pub cpu_pct: f64,
258 pub mem_pct: f64,
260 pub uptime_secs: u64,
262 pub net_up: u64,
264 pub net_down: u64,
266 pub last_error: String,
268 pub xray_state: String,
270 pub xray_error: String,
272 pub config_dirty: bool,
274 pub config_dirty_at: i64,
276 pub inbound_count: i64,
278 pub client_count: i64,
280 pub online_count: i64,
282 pub active_count: i64,
284 pub disabled_count: i64,
286 pub depleted_count: i64,
288 pub parent_guid: String,
290 pub transitive: bool,
292 pub created_at: i64,
294 pub updated_at: i64,
296}
297
298impl NodeView {
299 pub fn to_request(&self) -> NodeRequest {
301 NodeRequest {
302 id: self.id,
303 name: self.name.clone(),
304 remark: self.remark.clone(),
305 scheme: self.scheme.clone(),
306 address: self.address.clone(),
307 port: self.port,
308 base_path: self.base_path.clone(),
309 api_token: None,
310 clear_api_token: false,
311 enable: self.enable,
312 allow_private_address: self.allow_private_address,
313 tls_verify_mode: self.tls_verify_mode.clone(),
314 pinned_cert_sha256: self.pinned_cert_sha256.clone(),
315 inbound_sync_mode: self.inbound_sync_mode.clone(),
316 inbound_tags: self.inbound_tags.clone(),
317 outbound_tag: self.outbound_tag.clone(),
318 }
319 }
320}
321
322impl fmt::Debug for NodeView {
323 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
324 formatter
325 .debug_struct("NodeView")
326 .field("id", &self.id)
327 .field("name", &self.name)
328 .field("remark", &self.remark)
329 .field("scheme", &self.scheme)
330 .field("address", &self.address)
331 .field("port", &self.port)
332 .field("base_path", &"[REDACTED]")
333 .field("has_api_token", &self.has_api_token)
334 .field("enable", &self.enable)
335 .field("allow_private_address", &self.allow_private_address)
336 .field("tls_verify_mode", &self.tls_verify_mode)
337 .field("pinned_cert_sha256", &self.pinned_cert_sha256)
338 .field("inbound_sync_mode", &self.inbound_sync_mode)
339 .field("inbound_tags", &self.inbound_tags)
340 .field("outbound_tag", &"[REDACTED]")
341 .field("guid", &self.guid)
342 .field("status", &self.status)
343 .field("last_heartbeat", &self.last_heartbeat)
344 .field("latency_ms", &self.latency_ms)
345 .field("xray_version", &self.xray_version)
346 .field("panel_version", &self.panel_version)
347 .field("cpu_pct", &self.cpu_pct)
348 .field("mem_pct", &self.mem_pct)
349 .field("uptime_secs", &self.uptime_secs)
350 .field("net_up", &self.net_up)
351 .field("net_down", &self.net_down)
352 .field("last_error", &self.last_error)
353 .field("xray_state", &self.xray_state)
354 .field("xray_error", &self.xray_error)
355 .field("config_dirty", &self.config_dirty)
356 .field("config_dirty_at", &self.config_dirty_at)
357 .field("inbound_count", &self.inbound_count)
358 .field("client_count", &self.client_count)
359 .field("online_count", &self.online_count)
360 .field("active_count", &self.active_count)
361 .field("disabled_count", &self.disabled_count)
362 .field("depleted_count", &self.depleted_count)
363 .field("parent_guid", &self.parent_guid)
364 .field("transitive", &self.transitive)
365 .field("created_at", &self.created_at)
366 .field("updated_at", &self.updated_at)
367 .finish()
368 }
369}
370
371#[derive(Clone, Serialize)]
377#[serde(rename_all = "camelCase")]
378pub struct NodeRequest {
379 pub id: i64,
381 pub name: String,
383 pub remark: String,
385 pub scheme: NodeScheme,
387 pub address: String,
389 pub port: u16,
391 pub base_path: String,
393 #[serde(
394 skip_serializing_if = "Option::is_none",
395 serialize_with = "serialize_optional_secret"
396 )]
397 api_token: Option<SecretString>,
398 #[serde(skip_serializing_if = "is_false")]
399 clear_api_token: bool,
400 pub enable: bool,
402 pub allow_private_address: bool,
404 pub tls_verify_mode: NodeTlsVerifyMode,
406 pub pinned_cert_sha256: String,
408 pub inbound_sync_mode: NodeInboundSyncMode,
410 pub inbound_tags: Vec<String>,
412 pub outbound_tag: String,
414}
415
416impl NodeRequest {
417 pub fn new(name: impl Into<String>, address: impl Into<String>, port: u16) -> Self {
419 Self {
420 id: 0,
421 name: name.into(),
422 remark: String::new(),
423 scheme: NodeScheme::Https,
424 address: address.into(),
425 port,
426 base_path: "/".to_owned(),
427 api_token: None,
428 clear_api_token: false,
429 enable: true,
430 allow_private_address: false,
431 tls_verify_mode: NodeTlsVerifyMode::Verify,
432 pinned_cert_sha256: String::new(),
433 inbound_sync_mode: NodeInboundSyncMode::All,
434 inbound_tags: Vec::new(),
435 outbound_tag: String::new(),
436 }
437 }
438
439 #[must_use]
441 pub fn with_api_token(mut self, token: impl Into<String>) -> Self {
442 self.set_api_token(token);
443 self
444 }
445
446 pub fn set_api_token(&mut self, token: impl Into<String>) {
448 self.api_token = Some(SecretString::from(token.into()));
449 self.clear_api_token = false;
450 }
451
452 pub fn clear_stored_api_token(&mut self) {
456 self.api_token = None;
457 self.clear_api_token = true;
458 }
459
460 pub fn retain_stored_api_token(&mut self) {
462 self.api_token = None;
463 self.clear_api_token = false;
464 }
465
466 pub fn api_token(&self) -> Option<&str> {
468 self.api_token.as_ref().map(ExposeSecret::expose_secret)
469 }
470
471 pub const fn clears_stored_api_token(&self) -> bool {
473 self.clear_api_token
474 }
475}
476
477impl fmt::Debug for NodeRequest {
478 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
479 formatter
480 .debug_struct("NodeRequest")
481 .field("id", &self.id)
482 .field("name", &self.name)
483 .field("remark", &self.remark)
484 .field("scheme", &self.scheme)
485 .field("address", &self.address)
486 .field("port", &self.port)
487 .field("base_path", &"[REDACTED]")
488 .field("api_token", &self.api_token.as_ref().map(|_| "[REDACTED]"))
489 .field("clear_api_token", &self.clear_api_token)
490 .field("enable", &self.enable)
491 .field("allow_private_address", &self.allow_private_address)
492 .field("tls_verify_mode", &self.tls_verify_mode)
493 .field("pinned_cert_sha256", &self.pinned_cert_sha256)
494 .field("inbound_sync_mode", &self.inbound_sync_mode)
495 .field("inbound_tags", &self.inbound_tags)
496 .field("outbound_tag", &"[REDACTED]")
497 .finish()
498 }
499}
500
501#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
506#[serde(default, rename_all = "camelCase")]
507pub struct NodeProbeResult {
508 pub status: NodeStatus,
510 pub latency_ms: i64,
512 pub xray_version: String,
514 pub panel_version: String,
516 pub cpu_pct: f64,
518 pub mem_pct: f64,
520 pub uptime_secs: u64,
522 pub error: String,
524 pub xray_state: String,
526 pub xray_error: String,
528}
529
530#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
532#[serde(default, rename_all = "camelCase")]
533pub struct RemoteInboundOption {
534 pub tag: String,
536 pub remark: String,
538 pub protocol: RemoteInboundProtocol,
540 pub port: u16,
542}
543
544#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
546#[serde(default, rename_all = "camelCase")]
547pub struct NodeUpdateResult {
548 pub id: i64,
550 pub name: String,
552 pub ok: bool,
554 pub error: String,
556}
557
558#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
560#[non_exhaustive]
561pub enum NodeUpdateChannel {
562 #[default]
564 Stable,
565 Development,
567}
568
569impl NodeUpdateChannel {
570 pub(crate) const fn is_development(self) -> bool {
571 matches!(self, Self::Development)
572 }
573}
574
575#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
577#[non_exhaustive]
578pub enum NodeMetric {
579 Cpu,
581 Memory,
583 NetworkUp,
585 NetworkDown,
587}
588
589impl NodeMetric {
590 pub(crate) const fn as_str(self) -> &'static str {
591 match self {
592 Self::Cpu => "cpu",
593 Self::Memory => "mem",
594 Self::NetworkUp => "netUp",
595 Self::NetworkDown => "netDown",
596 }
597 }
598}
599
600#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
602#[serde(default, rename_all = "camelCase")]
603pub struct NodeMtlsCa {
604 pub ca_cert: String,
606}