Files
compliance-scanner-agent/compliance-dashboard/src/pages/repositories.rs
Sharang Parnerkar 5da0b5f4df Fix Last Scanned display and add Graph button in repositories page
- Show updated_at as relative time (e.g. "5m ago", "3d ago") instead
  of the last_scanned_commit hex SHA which was not a date
- Add Graph link button next to Scan button for quick navigation
  to the repository's code knowledge graph

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:53:26 +01:00

187 lines
8.9 KiB
Rust

use dioxus::prelude::*;
use crate::app::Route;
use crate::components::page_header::PageHeader;
use crate::components::pagination::Pagination;
use crate::components::toast::{ToastType, Toasts};
#[component]
pub fn RepositoriesPage() -> Element {
let mut page = use_signal(|| 1u64);
let mut show_add_form = use_signal(|| false);
let mut name = use_signal(String::new);
let mut git_url = use_signal(String::new);
let mut branch = use_signal(|| "main".to_string());
let mut toasts = use_context::<Toasts>();
let mut repos = use_resource(move || {
let p = page();
async move {
crate::infrastructure::repositories::fetch_repositories(p)
.await
.ok()
}
});
rsx! {
PageHeader {
title: "Repositories",
description: "Tracked git repositories",
}
div { style: "margin-bottom: 16px;",
button {
class: "btn btn-primary",
onclick: move |_| show_add_form.toggle(),
if show_add_form() { "Cancel" } else { "+ Add Repository" }
}
}
if show_add_form() {
div { class: "card",
div { class: "card-header", "Add Repository" }
div { class: "form-group",
label { "Name" }
input {
r#type: "text",
placeholder: "my-project",
value: "{name}",
oninput: move |e| name.set(e.value()),
}
}
div { class: "form-group",
label { "Git URL" }
input {
r#type: "text",
placeholder: "https://github.com/org/repo.git",
value: "{git_url}",
oninput: move |e| git_url.set(e.value()),
}
}
div { class: "form-group",
label { "Default Branch" }
input {
r#type: "text",
placeholder: "main",
value: "{branch}",
oninput: move |e| branch.set(e.value()),
}
}
button {
class: "btn btn-primary",
onclick: move |_| {
let n = name();
let u = git_url();
let b = branch();
spawn(async move {
match crate::infrastructure::repositories::add_repository(n, u, b).await {
Ok(_) => {
toasts.push(ToastType::Success, "Repository added");
repos.restart();
}
Err(e) => toasts.push(ToastType::Error, e.to_string()),
}
});
show_add_form.set(false);
name.set(String::new());
git_url.set(String::new());
},
"Add"
}
}
}
match &*repos.read() {
Some(Some(resp)) => {
let total_pages = resp.total.unwrap_or(0).div_ceil(20).max(1);
rsx! {
div { class: "card",
div { class: "table-wrapper",
table {
thead {
tr {
th { "Name" }
th { "Git URL" }
th { "Branch" }
th { "Findings" }
th { "Last Scanned" }
th { "Actions" }
}
}
tbody {
for repo in &resp.data {
{
let repo_id = repo.id.as_ref().map(|id| id.to_hex()).unwrap_or_default();
let repo_id_clone = repo_id.clone();
rsx! {
tr {
td { "{repo.name}" }
td {
style: "font-size: 12px; font-family: monospace;",
"{repo.git_url}"
}
td { "{repo.default_branch}" }
td { "{repo.findings_count}" }
td {
{
let now = chrono::Utc::now();
let diff = now.signed_duration_since(repo.updated_at);
let label = if diff.num_minutes() < 1 {
"just now".to_string()
} else if diff.num_hours() < 1 {
format!("{}m ago", diff.num_minutes())
} else if diff.num_days() < 1 {
format!("{}h ago", diff.num_hours())
} else if diff.num_days() < 30 {
format!("{}d ago", diff.num_days())
} else {
repo.updated_at.format("%Y-%m-%d").to_string()
};
rsx! { span { style: "font-size: 12px;", "{label}" } }
}
}
td { style: "display: flex; gap: 4px;",
Link {
to: Route::GraphExplorerPage { repo_id: repo_id.clone() },
class: "btn btn-ghost",
"Graph"
}
button {
class: "btn btn-ghost",
onclick: move |_| {
let id = repo_id_clone.clone();
spawn(async move {
match crate::infrastructure::repositories::trigger_repo_scan(id).await {
Ok(_) => toasts.push(ToastType::Success, "Scan triggered"),
Err(e) => toasts.push(ToastType::Error, e.to_string()),
}
});
},
"Scan"
}
}
}
}
}
}
}
}
}
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 repositories." } }
},
None => rsx! {
div { class: "loading", "Loading repositories..." }
},
}
}
}