feat: rag-embedding-ai-chat (#1)
CI / Format (push) Successful in 2s
CI / Clippy (push) Successful in 2m56s
CI / Security Audit (push) Successful in 1m25s
CI / Tests (push) Successful in 3m57s

Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-03-06 21:54:15 +00:00
co-authored by Sharang Parnerkar
parent db454867f3
commit 42cabf0582
61 changed files with 3868 additions and 307 deletions
+622 -47
View File
@@ -2,60 +2,335 @@ use dioxus::prelude::*;
use crate::components::page_header::PageHeader;
use crate::components::pagination::Pagination;
use crate::infrastructure::sbom::*;
#[component]
pub fn SbomPage() -> Element {
// ── Filter signals ──
let mut page = use_signal(|| 1u64);
let mut repo_filter = use_signal(String::new);
let mut pm_filter = use_signal(String::new);
let mut search_q = use_signal(String::new);
let mut vuln_toggle = use_signal(|| Option::<bool>::None);
let mut license_filter = use_signal(String::new);
// ── Active tab: "packages" | "licenses" | "diff" ──
let mut active_tab = use_signal(|| "packages".to_string());
// ── Vuln drill-down: track expanded row by (name, version) ──
let mut expanded_row = use_signal(|| Option::<String>::None);
// ── Export state ──
let mut show_export = use_signal(|| false);
let mut export_format = use_signal(|| "cyclonedx".to_string());
let mut export_result = use_signal(|| Option::<String>::None);
// ── Diff state ──
let mut diff_repo_a = use_signal(String::new);
let mut diff_repo_b = use_signal(String::new);
// ── Repos for dropdowns ──
let repos = use_resource(|| async {
crate::infrastructure::repositories::fetch_repositories(1)
.await
.ok()
});
// ── SBOM list (filtered) ──
let sbom = use_resource(move || {
let p = page();
async move { crate::infrastructure::sbom::fetch_sbom(p).await.ok() }
let repo = repo_filter();
let pm = pm_filter();
let q = search_q();
let hv = vuln_toggle();
let lic = license_filter();
async move {
fetch_sbom_filtered(
if repo.is_empty() { None } else { Some(repo) },
if pm.is_empty() { None } else { Some(pm) },
if q.is_empty() { None } else { Some(q) },
hv,
if lic.is_empty() { None } else { Some(lic) },
p,
)
.await
.ok()
}
});
// ── License summary ──
let license_data = use_resource(move || {
let repo = repo_filter();
async move {
fetch_license_summary(if repo.is_empty() { None } else { Some(repo) })
.await
.ok()
}
});
// ── Diff data ──
let diff_data = use_resource(move || {
let a = diff_repo_a();
let b = diff_repo_b();
async move {
if a.is_empty() || b.is_empty() {
return None;
}
fetch_sbom_diff(a, b).await.ok()
}
});
rsx! {
PageHeader {
title: "SBOM",
description: "Software Bill of Materials - dependency inventory across all repositories",
description: "Software Bill of Materials dependency inventory, license compliance, and vulnerability analysis",
}
match &*sbom.read() {
Some(Some(resp)) => {
let total_pages = resp.total.unwrap_or(0).div_ceil(50).max(1);
rsx! {
div { class: "card",
div { class: "table-wrapper",
table {
thead {
tr {
th { "Package" }
th { "Version" }
th { "Manager" }
th { "License" }
th { "Vulnerabilities" }
// ── Tab bar ──
div { class: "sbom-tab-bar",
button {
class: if active_tab() == "packages" { "sbom-tab active" } else { "sbom-tab" },
onclick: move |_| active_tab.set("packages".to_string()),
"Packages"
}
button {
class: if active_tab() == "licenses" { "sbom-tab active" } else { "sbom-tab" },
onclick: move |_| active_tab.set("licenses".to_string()),
"License Compliance"
}
button {
class: if active_tab() == "diff" { "sbom-tab active" } else { "sbom-tab" },
onclick: move |_| active_tab.set("diff".to_string()),
"Compare"
}
}
// ═══════════════ PACKAGES TAB ═══════════════
if active_tab() == "packages" {
// ── Filter bar ──
div { class: "sbom-filter-bar",
select {
class: "sbom-filter-select",
onchange: move |e| { repo_filter.set(e.value()); page.set(1); },
option { value: "", "All Repositories" }
{
match &*repos.read() {
Some(Some(resp)) => rsx! {
for repo in &resp.data {
{
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! { option { value: "{id}", "{name}" } }
}
}
tbody {
for entry in &resp.data {
},
_ => rsx! {},
}
}
}
select {
class: "sbom-filter-select",
onchange: move |e| { pm_filter.set(e.value()); page.set(1); },
option { value: "", "All Managers" }
option { value: "npm", "npm" }
option { value: "cargo", "Cargo" }
option { value: "pip", "pip" }
option { value: "go", "Go" }
option { value: "maven", "Maven" }
option { value: "nuget", "NuGet" }
option { value: "composer", "Composer" }
option { value: "gem", "RubyGems" }
}
input {
class: "sbom-filter-input",
r#type: "text",
placeholder: "Search packages...",
oninput: move |e| { search_q.set(e.value()); page.set(1); },
}
select {
class: "sbom-filter-select",
onchange: move |e| {
let val = e.value();
vuln_toggle.set(match val.as_str() {
"true" => Some(true),
"false" => Some(false),
_ => None,
});
page.set(1);
},
option { value: "", "All Packages" }
option { value: "true", "With Vulnerabilities" }
option { value: "false", "No Vulnerabilities" }
}
select {
class: "sbom-filter-select",
onchange: move |e| { license_filter.set(e.value()); page.set(1); },
option { value: "", "All Licenses" }
option { value: "MIT", "MIT" }
option { value: "Apache-2.0", "Apache 2.0" }
option { value: "BSD-3-Clause", "BSD 3-Clause" }
option { value: "ISC", "ISC" }
option { value: "GPL-3.0", "GPL 3.0" }
option { value: "GPL-2.0", "GPL 2.0" }
option { value: "LGPL-2.1", "LGPL 2.1" }
option { value: "MPL-2.0", "MPL 2.0" }
}
// ── Export button ──
div { class: "sbom-export-wrapper",
button {
class: "btn btn-secondary sbom-export-btn",
onclick: move |_| show_export.toggle(),
"Export"
}
if show_export() {
div { class: "sbom-export-dropdown",
select {
class: "sbom-filter-select",
value: "{export_format}",
onchange: move |e| export_format.set(e.value()),
option { value: "cyclonedx", "CycloneDX 1.5" }
option { value: "spdx", "SPDX 2.3" }
}
button {
class: "btn btn-primary",
disabled: repo_filter().is_empty(),
onclick: move |_| {
let repo = repo_filter();
let fmt = export_format();
spawn(async move {
match fetch_sbom_export(repo, fmt).await {
Ok(json) => export_result.set(Some(json)),
Err(e) => tracing::error!("Export failed: {e}"),
}
});
},
"Download"
}
if repo_filter().is_empty() {
span { class: "sbom-export-hint", "Select a repo first" }
}
}
}
}
}
// ── Export result display ──
if let Some(json) = export_result() {
div { class: "card sbom-export-result",
div { class: "sbom-export-result-header",
strong { "Exported SBOM" }
button {
class: "btn btn-secondary",
onclick: move |_| export_result.set(None),
"Close"
}
}
pre {
style: "max-height: 400px; overflow: auto; font-size: 12px;",
"{json}"
}
}
}
// ── SBOM table ──
match &*sbom.read() {
Some(Some(resp)) => {
let total_pages = resp.total.unwrap_or(0).div_ceil(50).max(1);
rsx! {
if let Some(total) = resp.total {
div { class: "sbom-result-count",
"{total} package(s) found"
}
}
div { class: "card",
div { class: "table-wrapper",
table {
thead {
tr {
td {
style: "font-weight: 500;",
"{entry.name}"
}
td {
style: "font-family: monospace; font-size: 13px;",
"{entry.version}"
}
td { "{entry.package_manager}" }
td { "{entry.license.as_deref().unwrap_or(\"-\")}" }
td {
if entry.known_vulnerabilities.is_empty() {
span {
style: "color: var(--success);",
"None"
th { "Package" }
th { "Version" }
th { "Manager" }
th { "License" }
th { "Vulnerabilities" }
}
}
tbody {
for entry in &resp.data {
{
let row_key = format!("{}@{}", entry.name, entry.version);
let is_expanded = expanded_row() == Some(row_key.clone());
let has_vulns = !entry.known_vulnerabilities.is_empty();
let license_class = license_css_class(entry.license.as_deref());
let row_key_click = row_key.clone();
rsx! {
tr {
td {
style: "font-weight: 500;",
"{entry.name}"
}
td {
style: "font-family: var(--font-mono, monospace); font-size: 13px;",
"{entry.version}"
}
td { "{entry.package_manager}" }
td {
span { class: "sbom-license-badge {license_class}",
"{entry.license.as_deref().unwrap_or(\"-\")}"
}
}
td {
if has_vulns {
span {
class: "badge badge-high sbom-vuln-toggle",
onclick: move |_| {
let key = row_key_click.clone();
if expanded_row() == Some(key.clone()) {
expanded_row.set(None);
} else {
expanded_row.set(Some(key));
}
},
"{entry.known_vulnerabilities.len()} vuln(s) ▾"
}
} else {
span {
style: "color: var(--success);",
"None"
}
}
}
}
} else {
span { class: "badge badge-high",
"{entry.known_vulnerabilities.len()} vuln(s)"
// ── Vulnerability drill-down row ──
if is_expanded && has_vulns {
tr { class: "sbom-vuln-detail-row",
td { colspan: "5",
div { class: "sbom-vuln-detail",
for vuln in &entry.known_vulnerabilities {
div { class: "sbom-vuln-card",
div { class: "sbom-vuln-card-header",
span { class: "sbom-vuln-id", "{vuln.id}" }
span { class: "sbom-vuln-source", "{vuln.source}" }
if let Some(sev) = &vuln.severity {
span {
class: "badge badge-{sev}",
"{sev}"
}
}
}
if let Some(url) = &vuln.url {
a {
href: "{url}",
target: "_blank",
class: "sbom-vuln-link",
"View Advisory →"
}
}
}
}
}
}
}
}
}
}
@@ -63,21 +338,321 @@ pub fn SbomPage() -> Element {
}
}
}
Pagination {
current_page: page(),
total_pages: total_pages,
on_page_change: move |p| page.set(p),
}
}
Pagination {
current_page: page(),
total_pages: total_pages,
on_page_change: move |p| page.set(p),
}
},
Some(None) => rsx! {
div { class: "card", p { "Failed to load SBOM." } }
},
None => rsx! {
div { class: "loading", "Loading SBOM..." }
},
}
}
// ═══════════════ LICENSE COMPLIANCE TAB ═══════════════
if active_tab() == "licenses" {
match &*license_data.read() {
Some(Some(resp)) => {
let total_pkgs: u64 = resp.data.iter().map(|l| l.count).sum();
let has_copyleft = resp.data.iter().any(|l| l.is_copyleft);
let copyleft_items: Vec<_> = resp.data.iter().filter(|l| l.is_copyleft).collect();
rsx! {
if has_copyleft {
div { class: "license-copyleft-warning",
strong { "⚠ Copyleft Licenses Detected" }
p { "The following copyleft-licensed packages may impose distribution requirements on your software." }
for item in &copyleft_items {
div { class: "license-copyleft-item",
span { class: "sbom-license-badge license-copyleft", "{item.license}" }
span { " — {item.count} package(s): " }
span { class: "license-pkg-list",
"{item.packages.join(\", \")}"
}
}
}
}
}
div { class: "card",
h3 { style: "margin-bottom: 16px;", "License Distribution" }
if total_pkgs > 0 {
div { class: "license-bar-chart",
for item in &resp.data {
{
let pct = (item.count as f64 / total_pkgs as f64 * 100.0).max(2.0);
let bar_class = if item.is_copyleft { "license-bar license-copyleft" } else { "license-bar license-permissive" };
rsx! {
div { class: "license-bar-row",
span { class: "license-bar-label", "{item.license}" }
div { class: "license-bar-track",
div {
class: "{bar_class}",
style: "width: {pct}%;",
}
}
span { class: "license-bar-count", "{item.count}" }
}
}
}
}
}
} else {
p { "No license data available." }
}
}
div { class: "card",
h3 { style: "margin-bottom: 16px;", "All Licenses" }
div { class: "table-wrapper",
table {
thead {
tr {
th { "License" }
th { "Type" }
th { "Packages" }
th { "Count" }
}
}
tbody {
for item in &resp.data {
tr {
td {
span {
class: "sbom-license-badge {license_type_class(item.is_copyleft)}",
"{item.license}"
}
}
td {
if item.is_copyleft {
span { class: "badge badge-high", "Copyleft" }
} else {
span { class: "badge badge-info", "Permissive" }
}
}
td {
style: "max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;",
"{item.packages.join(\", \")}"
}
td { "{item.count}" }
}
}
}
}
}
}
}
},
Some(None) => rsx! {
div { class: "card", p { "Failed to load license summary." } }
},
None => rsx! {
div { class: "loading", "Loading license data..." }
},
}
}
// ═══════════════ DIFF TAB ═══════════════
if active_tab() == "diff" {
div { class: "card",
h3 { style: "margin-bottom: 16px;", "Compare SBOMs Between Repositories" }
div { class: "sbom-diff-controls",
div { class: "sbom-diff-select-group",
label { "Repository A" }
select {
class: "sbom-filter-select",
onchange: move |e| diff_repo_a.set(e.value()),
option { value: "", "Select repository..." }
{
match &*repos.read() {
Some(Some(resp)) => rsx! {
for repo in &resp.data {
{
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! { option { value: "{id}", "{name}" } }
}
}
},
_ => rsx! {},
}
}
}
}
div { class: "sbom-diff-select-group",
label { "Repository B" }
select {
class: "sbom-filter-select",
onchange: move |e| diff_repo_b.set(e.value()),
option { value: "", "Select repository..." }
{
match &*repos.read() {
Some(Some(resp)) => rsx! {
for repo in &resp.data {
{
let id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let name = repo.name.clone();
rsx! { option { value: "{id}", "{name}" } }
}
}
},
_ => rsx! {},
}
}
}
}
}
},
Some(None) => rsx! {
div { class: "card", p { "Failed to load SBOM." } }
},
None => rsx! {
div { class: "loading", "Loading SBOM..." }
},
}
if !diff_repo_a().is_empty() && !diff_repo_b().is_empty() {
match &*diff_data.read() {
Some(Some(resp)) => {
let d = &resp.data;
rsx! {
div { class: "sbom-diff-summary",
div { class: "sbom-diff-stat sbom-diff-added",
span { class: "sbom-diff-stat-num", "{d.only_in_a.len()}" }
span { "Only in A" }
}
div { class: "sbom-diff-stat sbom-diff-removed",
span { class: "sbom-diff-stat-num", "{d.only_in_b.len()}" }
span { "Only in B" }
}
div { class: "sbom-diff-stat sbom-diff-changed",
span { class: "sbom-diff-stat-num", "{d.version_changed.len()}" }
span { "Version Diffs" }
}
div { class: "sbom-diff-stat",
span { class: "sbom-diff-stat-num", "{d.common_count}" }
span { "Common" }
}
}
if !d.only_in_a.is_empty() {
div { class: "card",
h4 { style: "margin-bottom: 12px; color: var(--success);", "Only in Repository A" }
div { class: "table-wrapper",
table {
thead {
tr {
th { "Package" }
th { "Version" }
th { "Manager" }
}
}
tbody {
for e in &d.only_in_a {
tr { class: "sbom-diff-row-added",
td { "{e.name}" }
td { "{e.version}" }
td { "{e.package_manager}" }
}
}
}
}
}
}
}
if !d.only_in_b.is_empty() {
div { class: "card",
h4 { style: "margin-bottom: 12px; color: var(--danger);", "Only in Repository B" }
div { class: "table-wrapper",
table {
thead {
tr {
th { "Package" }
th { "Version" }
th { "Manager" }
}
}
tbody {
for e in &d.only_in_b {
tr { class: "sbom-diff-row-removed",
td { "{e.name}" }
td { "{e.version}" }
td { "{e.package_manager}" }
}
}
}
}
}
}
}
if !d.version_changed.is_empty() {
div { class: "card",
h4 { style: "margin-bottom: 12px; color: var(--warning);", "Version Differences" }
div { class: "table-wrapper",
table {
thead {
tr {
th { "Package" }
th { "Manager" }
th { "Version A" }
th { "Version B" }
}
}
tbody {
for e in &d.version_changed {
tr { class: "sbom-diff-row-changed",
td { "{e.name}" }
td { "{e.package_manager}" }
td { "{e.version_a}" }
td { "{e.version_b}" }
}
}
}
}
}
}
}
if d.only_in_a.is_empty() && d.only_in_b.is_empty() && d.version_changed.is_empty() {
div { class: "card",
p { "Both repositories have identical SBOM entries." }
}
}
}
},
Some(None) => rsx! {
div { class: "card", p { "Failed to load diff." } }
},
None => rsx! {
div { class: "loading", "Computing diff..." }
},
}
}
}
}
}
fn license_css_class(license: Option<&str>) -> &'static str {
match license {
Some(l) => {
let upper = l.to_uppercase();
if upper.contains("GPL") || upper.contains("AGPL") {
"license-copyleft"
} else if upper.contains("LGPL") || upper.contains("MPL") {
"license-weak-copyleft"
} else {
"license-permissive"
}
}
None => "",
}
}
fn license_type_class(is_copyleft: bool) -> &'static str {
if is_copyleft {
"license-copyleft"
} else {
"license-permissive"
}
}