Some checks failed
CI / Format (push) Successful in 4s
CI / Clippy (pull_request) Failing after 3m10s
CI / Detect Changes (push) Has been skipped
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (push) Has been skipped
CI / Clippy (push) Failing after 2m58s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Format (pull_request) Successful in 3s
CI / Security Audit (pull_request) Has been skipped
CI / Tests (pull_request) Has been skipped
CI / Deploy Dashboard (push) Has been skipped
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
Replace expect() calls with let-else returns in SBOM download, use strip_prefix() instead of manual slicing in extract_base_url, and suppress too_many_arguments on server function. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
273 lines
8.5 KiB
Rust
273 lines
8.5 KiB
Rust
use dioxus::prelude::*;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use compliance_core::models::TrackedRepository;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct RepositoryListResponse {
|
|
pub data: Vec<TrackedRepository>,
|
|
pub total: Option<u64>,
|
|
pub page: Option<u64>,
|
|
}
|
|
|
|
#[server]
|
|
pub async fn fetch_repositories(page: u64) -> Result<RepositoryListResponse, ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!(
|
|
"{}/api/v1/repositories?page={page}&limit=20",
|
|
state.agent_api_url
|
|
);
|
|
|
|
let resp = reqwest::get(&url)
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
let body: RepositoryListResponse = resp
|
|
.json()
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
Ok(body)
|
|
}
|
|
|
|
#[server]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn add_repository(
|
|
name: String,
|
|
git_url: String,
|
|
default_branch: String,
|
|
auth_token: Option<String>,
|
|
auth_username: Option<String>,
|
|
tracker_type: Option<String>,
|
|
tracker_owner: Option<String>,
|
|
tracker_repo: Option<String>,
|
|
tracker_token: Option<String>,
|
|
) -> Result<(), ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!("{}/api/v1/repositories", state.agent_api_url);
|
|
|
|
let mut body = serde_json::json!({
|
|
"name": name,
|
|
"git_url": git_url,
|
|
"default_branch": default_branch,
|
|
});
|
|
if let Some(token) = auth_token.filter(|t| !t.is_empty()) {
|
|
body["auth_token"] = serde_json::Value::String(token);
|
|
}
|
|
if let Some(username) = auth_username.filter(|u| !u.is_empty()) {
|
|
body["auth_username"] = serde_json::Value::String(username);
|
|
}
|
|
if let Some(tt) = tracker_type.filter(|t| !t.is_empty()) {
|
|
body["tracker_type"] = serde_json::Value::String(tt);
|
|
}
|
|
if let Some(to) = tracker_owner.filter(|t| !t.is_empty()) {
|
|
body["tracker_owner"] = serde_json::Value::String(to);
|
|
}
|
|
if let Some(tr) = tracker_repo.filter(|t| !t.is_empty()) {
|
|
body["tracker_repo"] = serde_json::Value::String(tr);
|
|
}
|
|
if let Some(tk) = tracker_token.filter(|t| !t.is_empty()) {
|
|
body["tracker_token"] = serde_json::Value::String(tk);
|
|
}
|
|
|
|
let client = reqwest::Client::new();
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
|
|
if !resp.status().is_success() {
|
|
let body = resp.text().await.unwrap_or_default();
|
|
return Err(ServerFnError::new(format!(
|
|
"Failed to add repository: {body}"
|
|
)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[server]
|
|
pub async fn update_repository(
|
|
repo_id: String,
|
|
name: Option<String>,
|
|
default_branch: Option<String>,
|
|
auth_token: Option<String>,
|
|
auth_username: Option<String>,
|
|
tracker_type: Option<String>,
|
|
tracker_owner: Option<String>,
|
|
tracker_repo: Option<String>,
|
|
tracker_token: Option<String>,
|
|
scan_schedule: Option<String>,
|
|
) -> Result<(), ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!("{}/api/v1/repositories/{repo_id}", state.agent_api_url);
|
|
|
|
let mut body = serde_json::Map::new();
|
|
if let Some(v) = name.filter(|s| !s.is_empty()) {
|
|
body.insert("name".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = default_branch.filter(|s| !s.is_empty()) {
|
|
body.insert("default_branch".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = auth_token {
|
|
body.insert("auth_token".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = auth_username {
|
|
body.insert("auth_username".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = tracker_type {
|
|
body.insert("tracker_type".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = tracker_owner {
|
|
body.insert("tracker_owner".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = tracker_repo {
|
|
body.insert("tracker_repo".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = tracker_token {
|
|
body.insert("tracker_token".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Some(v) = scan_schedule {
|
|
body.insert("scan_schedule".into(), serde_json::Value::String(v));
|
|
}
|
|
|
|
let client = reqwest::Client::new();
|
|
let resp = client
|
|
.patch(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
|
|
if !resp.status().is_success() {
|
|
let text = resp.text().await.unwrap_or_default();
|
|
return Err(ServerFnError::new(format!(
|
|
"Failed to update repository: {text}"
|
|
)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[server]
|
|
pub async fn fetch_ssh_public_key() -> Result<String, ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!("{}/api/v1/settings/ssh-public-key", state.agent_api_url);
|
|
|
|
let resp = reqwest::get(&url)
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
|
|
if !resp.status().is_success() {
|
|
return Err(ServerFnError::new("SSH key not available".to_string()));
|
|
}
|
|
|
|
let body: serde_json::Value = resp
|
|
.json()
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
|
|
Ok(body
|
|
.get("public_key")
|
|
.and_then(|v| v.as_str())
|
|
.unwrap_or("")
|
|
.to_string())
|
|
}
|
|
|
|
#[server]
|
|
pub async fn delete_repository(repo_id: String) -> Result<(), ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!("{}/api/v1/repositories/{repo_id}", state.agent_api_url);
|
|
|
|
let client = reqwest::Client::new();
|
|
let resp = client
|
|
.delete(&url)
|
|
.send()
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
|
|
if !resp.status().is_success() {
|
|
let body = resp.text().await.unwrap_or_default();
|
|
return Err(ServerFnError::new(format!(
|
|
"Failed to delete repository: {body}"
|
|
)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[server]
|
|
pub async fn trigger_repo_scan(repo_id: String) -> Result<(), ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!("{}/api/v1/repositories/{repo_id}/scan", state.agent_api_url);
|
|
|
|
let client = reqwest::Client::new();
|
|
client
|
|
.post(&url)
|
|
.send()
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct WebhookConfigResponse {
|
|
pub webhook_secret: Option<String>,
|
|
pub tracker_type: String,
|
|
}
|
|
|
|
#[server]
|
|
pub async fn fetch_webhook_config(repo_id: String) -> Result<WebhookConfigResponse, ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!(
|
|
"{}/api/v1/repositories/{repo_id}/webhook-config",
|
|
state.agent_api_url
|
|
);
|
|
|
|
let resp = reqwest::get(&url)
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
let body: WebhookConfigResponse = resp
|
|
.json()
|
|
.await
|
|
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
|
Ok(body)
|
|
}
|
|
|
|
/// Check if a repository has any running scans
|
|
#[server]
|
|
pub async fn check_repo_scanning(repo_id: String) -> Result<bool, ServerFnError> {
|
|
let state: super::server_state::ServerState =
|
|
dioxus_fullstack::FullstackContext::extract().await?;
|
|
let url = format!("{}/api/v1/scan-runs?page=1&limit=1", 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()))?;
|
|
|
|
// Check if the most recent scan for this repo is still running
|
|
if let Some(scans) = body.get("data").and_then(|d| d.as_array()) {
|
|
for scan in scans {
|
|
let scan_repo = scan.get("repo_id").and_then(|v| v.as_str()).unwrap_or("");
|
|
let status = scan.get("status").and_then(|v| v.as_str()).unwrap_or("");
|
|
if scan_repo == repo_id && status == "running" {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|