1pub mod log_stream;
2pub mod routes;
3pub mod ui;
4
5use dashmap::DashMap;
6use log_stream::LogMessage;
7use meow_config::{
8 proxy_provider::ProxyProvider, raw::RawConfig, rule_provider::RuleProvider, NamedListener,
9};
10use meow_tunnel::Tunnel;
11use parking_lot::RwLock;
12use std::collections::HashMap;
13use std::net::SocketAddr;
14use std::path::PathBuf;
15use std::sync::Arc;
16use tokio::sync::broadcast;
17use tracing::{info, warn};
18
19pub struct ApiServer {
20 tunnel: Tunnel,
21 listen_addr: SocketAddr,
22 secret: Option<String>,
23 config_path: String,
24 raw_config: Arc<RwLock<RawConfig>>,
25 log_tx: broadcast::Sender<LogMessage>,
26 proxy_providers: Arc<DashMap<String, Arc<ProxyProvider>>>,
27 rule_providers: Arc<RwLock<HashMap<String, Arc<RuleProvider>>>>,
28 listeners: Vec<NamedListener>,
29 external_ui: Option<PathBuf>,
30}
31
32impl ApiServer {
33 #[allow(clippy::too_many_arguments)]
34 pub fn new(
35 tunnel: Tunnel,
36 listen_addr: SocketAddr,
37 secret: Option<String>,
38 config_path: String,
39 raw_config: Arc<RwLock<RawConfig>>,
40 log_tx: broadcast::Sender<LogMessage>,
41 proxy_providers: Arc<DashMap<String, Arc<ProxyProvider>>>,
42 rule_providers: Arc<RwLock<HashMap<String, Arc<RuleProvider>>>>,
43 listeners: Vec<NamedListener>,
44 external_ui: Option<PathBuf>,
45 ) -> Self {
46 Self {
47 tunnel,
48 listen_addr,
49 secret,
50 config_path,
51 raw_config,
52 log_tx,
53 proxy_providers,
54 rule_providers,
55 listeners,
56 external_ui,
57 }
58 }
59
60 pub async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
61 let state = Arc::new(routes::AppState {
62 tunnel: self.tunnel.clone(),
63 secret: self.secret.clone(),
64 config_path: self.config_path.clone(),
65 raw_config: Arc::clone(&self.raw_config),
66 log_tx: self.log_tx.clone(),
67 proxy_providers: Arc::clone(&self.proxy_providers),
68 rule_providers: Arc::clone(&self.rule_providers),
69 listeners: self.listeners.clone(),
70 external_ui: self.resolve_external_ui(),
71 });
72
73 let app = routes::create_router(state);
74
75 let listener = tokio::net::TcpListener::bind(self.listen_addr).await?;
76 info!("REST API listening on {}", self.listen_addr);
77 info!("Web UI available at http://{}/ui", self.listen_addr);
78 axum::serve(listener, app).await?;
79 Ok(())
80 }
81
82 fn resolve_external_ui(&self) -> Option<PathBuf> {
86 let dir = self.external_ui.as_ref()?;
87 if dir.is_dir() {
88 info!("Serving external Web UI from {}", dir.display());
89 Some(dir.clone())
90 } else {
91 warn!(
92 "external-ui directory {} not found; serving the built-in panel instead",
93 dir.display()
94 );
95 None
96 }
97 }
98}