use crate::api::admin::NodeInfo; use crate::api::peers::{AggregatedStats, PeerInfo}; use crate::api::routes::RoutesSnapshot; use crate::api::MyceliumClient; use crate::error::{AppError, AppResult}; use crate::sidecar::SidecarConfig; use crate::state::AppState; use serde::Serialize; use tauri::{AppHandle, State}; #[derive(Debug, Serialize)] pub struct DaemonStatus { pub running: bool, pub api_url: Option, pub key_path: Option, pub config_path: Option, } fn snapshot(state: &AppState) -> DaemonStatus { let sc = &state.sidecar; DaemonStatus { running: sc.is_running(), api_url: sc.client().map(|c| c.base_url().to_string()), key_path: sc.key_path().map(|p| p.display().to_string()), config_path: sc.config_path().map(|p| p.display().to_string()), } } fn require_client(state: &AppState) -> AppResult { state.sidecar.client().ok_or(AppError::DaemonNotRunning) } #[tauri::command] pub fn daemon_status(state: State<'_, AppState>) -> DaemonStatus { snapshot(&state) } #[tauri::command] pub async fn start_daemon( app: AppHandle, state: State<'_, AppState>, config: Option, ) -> AppResult { let cfg = config.unwrap_or_default(); state.sidecar.start(&app, &cfg).await?; state .poller .start(app.clone(), std::sync::Arc::clone(&state.sidecar)); Ok(snapshot(&state)) } #[tauri::command] pub async fn stop_daemon(state: State<'_, AppState>) -> AppResult { state.poller.stop(); state.sidecar.stop().await; Ok(snapshot(&state)) } #[tauri::command] pub async fn node_info(state: State<'_, AppState>) -> AppResult { let client = require_client(&state)?; client.node_info().await } #[tauri::command] pub fn sidecar_logs(state: State<'_, AppState>) -> Vec { state.sidecar.logs_snapshot() } // ─── Peers ─────────────────────────────────────────────────────────────────── #[tauri::command] pub async fn peers_list(state: State<'_, AppState>) -> AppResult> { require_client(&state)?.list_peers().await } #[tauri::command] pub async fn peer_add(state: State<'_, AppState>, endpoint: String) -> AppResult<()> { if endpoint.trim().is_empty() { return Err(AppError::BadInput("endpoint must not be empty".into())); } require_client(&state)?.add_peer(endpoint.trim()).await } #[tauri::command] pub async fn peer_remove(state: State<'_, AppState>, endpoint: String) -> AppResult<()> { require_client(&state)?.remove_peer(&endpoint).await } #[tauri::command] pub async fn peers_stats(state: State<'_, AppState>) -> AppResult { let peers = require_client(&state)?.list_peers().await?; Ok(crate::api::peers::aggregate(&peers)) } // ─── Routes ────────────────────────────────────────────────────────────────── #[tauri::command] pub async fn routes_snapshot(state: State<'_, AppState>) -> AppResult { require_client(&state)?.routes_snapshot().await }