use dioxus::prelude::*; use serde::{Deserialize, Serialize}; // ── Local types (no bson dependency, WASM-safe) ── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct VulnRefData { pub id: String, pub source: String, pub severity: Option, pub url: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SbomEntryData { #[serde(rename = "_id", default)] pub id: Option, pub repo_id: String, pub name: String, pub version: String, pub package_manager: String, pub license: Option, pub purl: Option, #[serde(default)] pub known_vulnerabilities: Vec, #[serde(default)] pub created_at: Option, #[serde(default)] pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SbomListResponse { pub data: Vec, pub total: Option, pub page: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LicenseSummaryData { pub license: String, pub count: u64, pub is_copyleft: bool, pub packages: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LicenseSummaryResponse { pub data: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SbomDiffEntryData { pub name: String, pub version: String, pub package_manager: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SbomVersionDiffData { pub name: String, pub package_manager: String, pub version_a: String, pub version_b: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SbomDiffResultData { pub only_in_a: Vec, pub only_in_b: Vec, pub version_changed: Vec, pub common_count: u64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SbomDiffResponse { pub data: SbomDiffResultData, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SbomFiltersResponse { pub package_managers: Vec, pub licenses: Vec, } // ── Server functions ── #[server] pub async fn fetch_sbom_filters() -> Result { let resp = super::agent_client::agent_get("/api/v1/sbom/filters") .await? .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let text = resp .text() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: SbomFiltersResponse = serde_json::from_str(&text) .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?; Ok(body) } #[server] pub async fn fetch_sbom_filtered( repo_id: Option, package_manager: Option, q: Option, has_vulns: Option, license: Option, page: u64, ) -> Result { let mut params = vec![format!("page={page}"), "limit=50".to_string()]; if let Some(r) = &repo_id { if !r.is_empty() { params.push(format!("repo_id={r}")); } } if let Some(pm) = &package_manager { if !pm.is_empty() { params.push(format!("package_manager={pm}")); } } if let Some(q) = &q { if !q.is_empty() { params.push(format!("q={}", q.replace(' ', "%20"))); } } if let Some(hv) = has_vulns { params.push(format!("has_vulns={hv}")); } if let Some(l) = &license { if !l.is_empty() { params.push(format!("license={}", l.replace(' ', "%20"))); } } let path = format!("/api/v1/sbom?{}", params.join("&")); let resp = super::agent_client::agent_get(&path) .await? .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let text = resp .text() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: SbomListResponse = serde_json::from_str(&text) .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?; Ok(body) } #[server] pub async fn fetch_sbom_export(repo_id: String, format: String) -> Result { let path = format!("/api/v1/sbom/export?repo_id={repo_id}&format={format}"); let resp = super::agent_client::agent_get(&path) .await? .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let text = resp .text() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(text) } #[server] pub async fn fetch_license_summary( repo_id: Option, ) -> Result { let mut path = "/api/v1/sbom/licenses".to_string(); if let Some(r) = &repo_id { if !r.is_empty() { path = format!("{path}?repo_id={r}"); } } let resp = super::agent_client::agent_get(&path) .await? .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let text = resp .text() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: LicenseSummaryResponse = serde_json::from_str(&text) .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?; Ok(body) } #[server] pub async fn fetch_sbom_diff( repo_a: String, repo_b: String, ) -> Result { let path = format!("/api/v1/sbom/diff?repo_a={repo_a}&repo_b={repo_b}"); let resp = super::agent_client::agent_get(&path) .await? .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let text = resp .text() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: SbomDiffResponse = serde_json::from_str(&text) .map_err(|e| ServerFnError::new(format!("Parse error: {e} — body: {text}")))?; Ok(body) }