Skip to main content

meow_api/
lib.rs

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    /// Validate the configured external-UI directory. Returns the path only when
83    /// it exists as a directory; otherwise logs a warning and falls back to the
84    /// built-in panel (issue #223).
85    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}