Compare commits

..
Author SHA1 Message Date
Sharang ParnerkarandClaude Opus 4.8 e3b918b365 fix(dashboard): Findings/SBOM filter by onboarded targets; accurate target findings_count
CI / Check (pull_request) Successful in 7m4s
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 Findings and SBOM pages populated their target dropdown from the legacy
`repositories` collection, so onboarded targets never appeared and their
findings/SBOM couldn't be filtered by name (the data was there, keyed by the
target id). Point both dropdowns at `onboarded_targets` via `fetch_targets`.

Also refresh `OnboardedTarget.findings_count` at the end of `run_target`: the
shared pipeline (Stage 7) increments the legacy `repositories` doc, which the
unified path has none of, so the Targets page always showed 0. Set the accurate
total (count of findings keyed by the target id) on the target itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:52:09 +02:00
sharang bf32b9939a fix(onboarding): targets visibility + unified pipeline by default (#154)
CI / Detect Changes (push) Successful in 3s
CI / Check (push) Has been skipped
CI / Deploy Agent (push) Successful in 3m41s
CI / Deploy Dashboard (push) Successful in 2m46s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 1m46s
2026-07-13 07:29:04 +00:00
3 changed files with 33 additions and 14 deletions
@@ -461,6 +461,25 @@ impl PipelineOrchestrator {
} }, } },
) )
.await?; .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) => { Err(e) => {
tracing::error!(target_id, error = %e, "Unified scan pipeline failed"); 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 mut selected_ids = use_signal(Vec::<String>::new);
let repos = use_resource(|| async { let repos = use_resource(|| async {
crate::infrastructure::repositories::fetch_repositories(1) crate::infrastructure::onboarding::fetch_targets()
.await .await
.ok() .ok()
}); });
@@ -86,14 +86,14 @@ pub fn FindingsPage() -> Element {
} }
select { select {
onchange: move |e| { repo_filter.set(e.value()); page.set(1); }, onchange: move |e| { repo_filter.set(e.value()); page.set(1); },
option { value: "", "All Repositories" } option { value: "", "All Targets" }
{ {
match &*repos.read() { match &*repos.read() {
Some(Some(resp)) => rsx! { Some(Some(resp)) => rsx! {
for repo in &resp.data { for t in &resp.data {
{ {
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default(); let id = t.get("_id").and_then(|o| o.get("$oid")).and_then(|s| s.as_str()).unwrap_or_default().to_string();
let name = repo.name.clone(); let name = t.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
rsx! { rsx! {
option { value: "{id}", "{name}" } 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_a = use_signal(String::new);
let mut diff_repo_b = use_signal(String::new); let mut diff_repo_b = use_signal(String::new);
// ── Repos for dropdowns ── // ── Targets for dropdowns ──
let repos = use_resource(|| async { let repos = use_resource(|| async {
crate::infrastructure::repositories::fetch_repositories(1) crate::infrastructure::onboarding::fetch_targets()
.await .await
.ok() .ok()
}); });
@@ -114,14 +114,14 @@ pub fn SbomPage() -> Element {
select { select {
class: "sbom-filter-select", class: "sbom-filter-select",
onchange: move |e| { repo_filter.set(e.value()); page.set(1); }, onchange: move |e| { repo_filter.set(e.value()); page.set(1); },
option { value: "", "All Repositories" } option { value: "", "All Targets" }
{ {
match &*repos.read() { match &*repos.read() {
Some(Some(resp)) => rsx! { Some(Some(resp)) => rsx! {
for repo in &resp.data { for repo in &resp.data {
{ {
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default(); 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.name.clone(); let name = repo.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
rsx! { option { value: "{id}", "{name}" } } rsx! { option { value: "{id}", "{name}" } }
} }
} }
@@ -476,8 +476,8 @@ pub fn SbomPage() -> Element {
Some(Some(resp)) => rsx! { Some(Some(resp)) => rsx! {
for repo in &resp.data { for repo in &resp.data {
{ {
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default(); 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.name.clone(); let name = repo.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
rsx! { option { value: "{id}", "{name}" } } rsx! { option { value: "{id}", "{name}" } }
} }
} }
@@ -498,8 +498,8 @@ pub fn SbomPage() -> Element {
Some(Some(resp)) => rsx! { Some(Some(resp)) => rsx! {
for repo in &resp.data { for repo in &resp.data {
{ {
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default(); 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.name.clone(); let name = repo.get("name").and_then(|n| n.as_str()).unwrap_or_default().to_string();
rsx! { option { value: "{id}", "{name}" } } rsx! { option { value: "{id}", "{name}" } }
} }
} }