use dioxus::prelude::*; use serde::{Deserialize, Serialize}; use compliance_core::models::Finding; #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct FindingsListResponse { pub data: Vec, pub total: Option, pub page: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct FindingsQuery { pub page: u64, pub severity: String, pub scan_type: String, pub status: String, pub repo_id: String, pub q: String, pub sort_by: String, pub sort_order: String, } #[server] pub async fn fetch_findings(query: FindingsQuery) -> Result { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let mut url = format!( "{}/api/v1/findings?page={}&limit=20", state.agent_api_url, query.page ); if !query.severity.is_empty() { url.push_str(&format!("&severity={}", query.severity)); } if !query.scan_type.is_empty() { url.push_str(&format!("&scan_type={}", query.scan_type)); } if !query.status.is_empty() { url.push_str(&format!("&status={}", query.status)); } if !query.repo_id.is_empty() { url.push_str(&format!("&repo_id={}", query.repo_id)); } if !query.q.is_empty() { url.push_str(&format!( "&q={}", url::form_urlencoded::byte_serialize(query.q.as_bytes()).collect::() )); } if !query.sort_by.is_empty() { url.push_str(&format!("&sort_by={}", query.sort_by)); } if !query.sort_order.is_empty() { url.push_str(&format!("&sort_order={}", query.sort_order)); } let resp = reqwest::get(&url) .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: FindingsListResponse = resp .json() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(body) } #[server] pub async fn fetch_finding_detail(id: String) -> Result { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/findings/{id}", state.agent_api_url); let resp = reqwest::get(&url) .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: serde_json::Value = resp .json() .await .map_err(|e| ServerFnError::new(e.to_string()))?; let finding: Finding = serde_json::from_value(body["data"].clone()) .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(finding) } #[server] pub async fn update_finding_status(id: String, status: String) -> Result<(), ServerFnError> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/findings/{id}/status", state.agent_api_url); let client = reqwest::Client::new(); client .patch(&url) .json(&serde_json::json!({ "status": status })) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(()) } #[server] pub async fn bulk_update_finding_status( ids: Vec, status: String, ) -> Result<(), ServerFnError> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/findings/bulk-status", state.agent_api_url); let client = reqwest::Client::new(); client .patch(&url) .json(&serde_json::json!({ "ids": ids, "status": status })) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(()) } #[server] pub async fn update_finding_feedback(id: String, feedback: String) -> Result<(), ServerFnError> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/findings/{id}/feedback", state.agent_api_url); let client = reqwest::Client::new(); client .patch(&url) .json(&serde_json::json!({ "feedback": feedback })) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(()) }