P1: sidecar lifecycle and HTTP bridge
Backend - sidecar.rs supervises the bundled `mycelium` binary launched via pkexec; locates it in resource_dir or CARGO_MANIFEST_DIR/binaries matching $TAURI_ENV_TARGET_TRIPLE - ephemeral port via portpicker, key + config persisted in app_data_dir, kill_on_drop with explicit start_kill on stop - health-check loop calls /api/v1/admin until 2xx (timeout 20s); emits sidecar://ready and sidecar://exited - 500-line ring buffer of stdout/stderr surfaced via sidecar_logs command for the upcoming Settings page - elevation::is_auth_failure(126|127) maps pkexec cancel to a dedicated AppError variant - AppError uses thiserror, Serialize impl renders messages as plain strings for the JS side Frontend - typed `api` wrapper around invoke() in src/lib/api.ts - node store (Pinia) bootstraps on mount, listens on sidecar://ready and sidecar://exited - StartupOverlay covers the whole window for idle/starting/error phases; sidebar status dot + start/stop button - Status view renders subnet, pubkey, api endpoint and key path with one-click clipboard copy
This commit is contained in:
68
src-tauri/src/api/mod.rs
Normal file
68
src-tauri/src/api/mod.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
pub mod admin;
|
||||
|
||||
use crate::error::{AppError, AppResult};
|
||||
use reqwest::{Client, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Thin REST client for the mycelium daemon's HTTP API.
|
||||
///
|
||||
/// The base URL is set after the sidecar reports ready; clients are cheap
|
||||
/// to clone (the inner `reqwest::Client` keeps a shared connection pool).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MyceliumClient {
|
||||
base: String,
|
||||
http: Client,
|
||||
}
|
||||
|
||||
impl MyceliumClient {
|
||||
pub fn new(base: impl Into<String>) -> Self {
|
||||
let http = Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.expect("reqwest client build");
|
||||
Self {
|
||||
base: base.into(),
|
||||
http,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_url(&self) -> &str {
|
||||
&self.base
|
||||
}
|
||||
|
||||
pub(crate) fn url(&self, path: &str) -> String {
|
||||
format!("{}/api/v1{}", self.base, path)
|
||||
}
|
||||
|
||||
pub(crate) async fn parse<T: DeserializeOwned>(resp: Response) -> AppResult<T> {
|
||||
let status = resp.status();
|
||||
if status.is_success() {
|
||||
resp.json::<T>().await.map_err(AppError::from)
|
||||
} else {
|
||||
let body = resp.text().await.unwrap_or_default();
|
||||
Err(AppError::DaemonStatus {
|
||||
status: status.as_u16(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // wired in P2 (peers add/remove)
|
||||
pub(crate) async fn check_status(resp: Response) -> AppResult<()> {
|
||||
let status = resp.status();
|
||||
if status.is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
let body = resp.text().await.unwrap_or_default();
|
||||
Err(AppError::DaemonStatus {
|
||||
status: status.as_u16(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn http(&self) -> &Client {
|
||||
&self.http
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user