Files
Mycell-UI/src-tauri/src/commands.rs
syoul 95e7cb4bd3 P3: routes (selected, fallback, queried)
Backend
- api/routes.rs models the Babel-style route shape; metric uses an
  untagged enum to round-trip both numeric hop counts and the
  literal "infinite" string the daemon emits for poisoned routes
- routes_snapshot() runs the three GETs concurrently with try_join
  so the snapshot is internally consistent
- poller spawns a second 5s loop emitting routes://updated; both
  loops are owned by the Poller and aborted together on stop_daemon

Frontend
- routes store mirrors the snapshot shape; tabbed view (radix-vue)
  with selected, fallback and queried lists
- RouteTable component shared by selected/fallback; metric column
  is colour-coded (0 green, low neutral, high yellow, infinite red)
- Queried subnets show a live `expires in 12s` countdown driven by
  a 1Hz tick ref instead of mutating the store
2026-04-25 23:02:32 +02:00

102 lines
3.3 KiB
Rust

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<String>,
pub key_path: Option<String>,
pub config_path: Option<String>,
}
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<MyceliumClient> {
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<SidecarConfig>,
) -> AppResult<DaemonStatus> {
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<DaemonStatus> {
state.poller.stop();
state.sidecar.stop().await;
Ok(snapshot(&state))
}
#[tauri::command]
pub async fn node_info(state: State<'_, AppState>) -> AppResult<NodeInfo> {
let client = require_client(&state)?;
client.node_info().await
}
#[tauri::command]
pub fn sidecar_logs(state: State<'_, AppState>) -> Vec<String> {
state.sidecar.logs_snapshot()
}
// ─── Peers ───────────────────────────────────────────────────────────────────
#[tauri::command]
pub async fn peers_list(state: State<'_, AppState>) -> AppResult<Vec<PeerInfo>> {
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<AggregatedStats> {
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<RoutesSnapshot> {
require_client(&state)?.routes_snapshot().await
}