Compare commits

..
Author SHA1 Message Date
Sharang ParnerkarandClaude Opus 4.8 9717a1efda fix(onboarding): targets visibility + unified pipeline by default
CI / Check (pull_request) Successful in 5m36s
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
The onboarding wizard created OnboardedTargets that were invisible in the
dashboard, and triggering a scan failed with "Repository <id> not found":
`/targets/{id}/scan` went through `run_scan`, which consulted the global
`unified_pipeline` flag and fell to the legacy repository pipeline (reads
`repositories`, not `onboarded_targets`).

Agent
- Add `ComplianceAgent::run_target_scan`, always dispatching to the unified
  `run_target` pipeline. The target-scan endpoint operates on
  `onboarded_targets` by construction, so it must not depend on the
  transition flag. `trigger_target_scan` now calls it.
- Default `UNIFIED_PIPELINE` to on (no legacy `repositories` data in prod);
  set `UNIFIED_PIPELINE=0` to opt back to the legacy pipeline.
- Scheduler now scans `onboarded_targets` (via `run_target_scan`) instead of
  the legacy `repositories` collection.

Dashboard
- New Targets page (`/targets`): lists onboarded targets with detected type,
  artifacts, findings count, applicable-scans matrix (on expand), plus Run
  scan and Delete. Sidebar "Repositories" nav becomes "Targets".
- Remove the "Add Repository" form from the Repositories page — onboarding
  is the single entry point (private-repo auth + issue tracker move into the
  onboarding flow, revisable on the target).
- Add `delete_target` server fn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:22:03 +02:00
3 changed files with 14 additions and 33 deletions
@@ -461,25 +461,6 @@ impl PipelineOrchestrator {
} },
)
.await?;
// Refresh the target's cached findings count. The shared pipeline
// (Stage 7) increments `repositories`, which the unified path does
// not use, so set the accurate total on the target itself.
let total = self
.db
.findings()
.count_documents(doc! { "repo_id": target_id })
.await
.unwrap_or(*count as u64);
self.db
.onboarded_targets()
.update_one(
doc! { "_id": oid },
doc! { "$set": {
"findings_count": total as i64,
"updated_at": mongodb::bson::DateTime::now(),
} },
)
.await?;
}
Err(e) => {
tracing::error!(target_id, error = %e, "Unified scan pipeline failed");
+5 -5
View File
@@ -20,7 +20,7 @@ pub fn FindingsPage() -> Element {
let mut selected_ids = use_signal(Vec::<String>::new);
let repos = use_resource(|| async {
crate::infrastructure::onboarding::fetch_targets()
crate::infrastructure::repositories::fetch_repositories(1)
.await
.ok()
});
@@ -86,14 +86,14 @@ pub fn FindingsPage() -> Element {
}
select {
onchange: move |e| { repo_filter.set(e.value()); page.set(1); },
option { value: "", "All Targets" }
option { value: "", "All Repositories" }
{
match &*repos.read() {
Some(Some(resp)) => rsx! {
for t in &resp.data {
for repo in &resp.data {
{
let id = t.get("_id").and_then(|o| o.get("$oid")).and_then(|s| s.as_str()).unwrap_or_default().to_string();
let name = t.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! {
option { value: "{id}", "{name}" }
}
+9 -9
View File
@@ -28,9 +28,9 @@ pub fn SbomPage() -> Element {
let mut diff_repo_a = use_signal(String::new);
let mut diff_repo_b = use_signal(String::new);
// ── Targets for dropdowns ──
// ── Repos for dropdowns ──
let repos = use_resource(|| async {
crate::infrastructure::onboarding::fetch_targets()
crate::infrastructure::repositories::fetch_repositories(1)
.await
.ok()
});
@@ -114,14 +114,14 @@ pub fn SbomPage() -> Element {
select {
class: "sbom-filter-select",
onchange: move |e| { repo_filter.set(e.value()); page.set(1); },
option { value: "", "All Targets" }
option { value: "", "All Repositories" }
{
match &*repos.read() {
Some(Some(resp)) => rsx! {
for repo in &resp.data {
{
let id = repo.get("_id").and_then(|o| o.get("$oid")).and_then(|s| s.as_str()).unwrap_or_default().to_string();
let name = repo.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! { option { value: "{id}", "{name}" } }
}
}
@@ -476,8 +476,8 @@ pub fn SbomPage() -> Element {
Some(Some(resp)) => rsx! {
for repo in &resp.data {
{
let id = repo.get("_id").and_then(|o| o.get("$oid")).and_then(|s| s.as_str()).unwrap_or_default().to_string();
let name = repo.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! { option { value: "{id}", "{name}" } }
}
}
@@ -498,8 +498,8 @@ pub fn SbomPage() -> Element {
Some(Some(resp)) => rsx! {
for repo in &resp.data {
{
let id = repo.get("_id").and_then(|o| o.get("$oid")).and_then(|s| s.as_str()).unwrap_or_default().to_string();
let name = repo.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! { option { value: "{id}", "{name}" } }
}
}