feat(onboarding): scan-trigger endpoint + wizard Run-Scan button
CI / Check (pull_request) Successful in 5m40s
CI / Detect Changes (pull_request) 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

Complete the onboard -> scan -> results loop in-UI:
- POST /api/v1/targets/{id}/scan (404s unknown targets) spawns run_scan, which
  dispatches to the unified pipeline under UNIFIED_PIPELINE.
- Wizard Done step gains a "Run scan" button (server fn trigger_target_scan)
  with inline status, so a user can onboard and kick off a scan without curl.

Passes fmt + clippy -D warnings (agent, dashboard server + web).

Closes #126, #127. Refs #131, #133, #118.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-07-13 00:13:26 +02:00
co-authored by Claude Fable 5
parent 9ec07ff7a1
commit 9919afa0ee
4 changed files with 87 additions and 2 deletions
@@ -348,3 +348,43 @@ pub async fn detect_target(
page: None,
}))
}
/// POST /api/v1/targets/{id}/scan — trigger a scan for the target.
///
/// Dispatches to the unified pipeline when `UNIFIED_PIPELINE` is set (else the
/// legacy path). Runs in the background and returns immediately.
#[tracing::instrument(skip_all, fields(target_id = %id))]
pub async fn trigger_target_scan(
Extension(agent): AgentExt,
tenant: TenantCtx,
Path(id): Path<String>,
) -> Result<Json<serde_json::Value>, StatusCode> {
let oid = parse_oid(&id)?;
let db = tenant_db(&agent, &tenant).await?;
// 404 if the target doesn't exist for this tenant.
if db
.onboarded_targets()
.find_one(doc! { "_id": oid })
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.is_none()
{
return Err(StatusCode::NOT_FOUND);
}
let agent_clone = (*agent).clone();
let tenant_id = tenant.0.tenant_id.clone();
tokio::spawn(async move {
if let Err(e) = agent_clone
.run_scan(
&tenant_id,
&id,
compliance_core::models::ScanTrigger::Manual,
)
.await
{
tracing::error!("Manual target scan failed for {id}: {e}");
}
});
Ok(Json(serde_json::json!({ "status": "scan_triggered" })))
}
+4
View File
@@ -48,6 +48,10 @@ pub fn build_router() -> Router {
"/api/v1/targets/{id}/detect",
post(handlers::onboarding::detect_target),
)
.route(
"/api/v1/targets/{id}/scan",
post(handlers::onboarding::trigger_target_scan),
)
.route("/api/v1/findings", get(handlers::list_findings))
.route("/api/v1/findings/{id}", get(handlers::get_finding))
.route(