Add DAST, graph modules, toast notifications, and dashboard enhancements
Add DAST scanning and code knowledge graph features across the stack: - compliance-dast and compliance-graph workspace crates - Agent API handlers and routes for DAST targets/scans and graph builds - Core models and traits for DAST and graph domains - Dashboard pages for DAST targets/findings/overview and graph explorer/impact - Toast notification system with auto-dismiss for async action feedback - Button click animations and disabled states for better UX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,15 +2,18 @@ use dioxus::prelude::*;
|
||||
|
||||
use crate::app::Route;
|
||||
use crate::components::sidebar::Sidebar;
|
||||
use crate::components::toast::{ToastContainer, Toasts};
|
||||
|
||||
#[component]
|
||||
pub fn AppShell() -> Element {
|
||||
use_context_provider(Toasts::new);
|
||||
rsx! {
|
||||
div { class: "app-shell",
|
||||
Sidebar {}
|
||||
main { class: "main-content",
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
ToastContainer {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ pub mod pagination;
|
||||
pub mod severity_badge;
|
||||
pub mod sidebar;
|
||||
pub mod stat_card;
|
||||
pub mod toast;
|
||||
|
||||
@@ -40,6 +40,16 @@ pub fn Sidebar() -> Element {
|
||||
route: Route::IssuesPage {},
|
||||
icon: rsx! { Icon { icon: BsListTask, width: 18, height: 18 } },
|
||||
},
|
||||
NavItem {
|
||||
label: "Code Graph",
|
||||
route: Route::GraphIndexPage {},
|
||||
icon: rsx! { Icon { icon: BsDiagram3, width: 18, height: 18 } },
|
||||
},
|
||||
NavItem {
|
||||
label: "DAST",
|
||||
route: Route::DastOverviewPage {},
|
||||
icon: rsx! { Icon { icon: BsBug, width: 18, height: 18 } },
|
||||
},
|
||||
NavItem {
|
||||
label: "Settings",
|
||||
route: Route::SettingsPage {},
|
||||
@@ -58,6 +68,12 @@ pub fn Sidebar() -> Element {
|
||||
{
|
||||
let is_active = match (¤t_route, &item.route) {
|
||||
(Route::FindingDetailPage { .. }, Route::FindingsPage {}) => true,
|
||||
(Route::GraphIndexPage {}, Route::GraphIndexPage {}) => true,
|
||||
(Route::GraphExplorerPage { .. }, Route::GraphIndexPage {}) => true,
|
||||
(Route::ImpactAnalysisPage { .. }, Route::GraphIndexPage {}) => true,
|
||||
(Route::DastTargetsPage {}, Route::DastOverviewPage {}) => true,
|
||||
(Route::DastFindingsPage {}, Route::DastOverviewPage {}) => true,
|
||||
(Route::DastFindingDetailPage { .. }, Route::DastOverviewPage {}) => true,
|
||||
(a, b) => a == b,
|
||||
};
|
||||
let class = if is_active { "nav-item active" } else { "nav-item" };
|
||||
|
||||
86
compliance-dashboard/src/components/toast.rs
Normal file
86
compliance-dashboard/src/components/toast.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum ToastType {
|
||||
Success,
|
||||
Error,
|
||||
Info,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ToastMessage {
|
||||
pub id: usize,
|
||||
pub message: String,
|
||||
pub toast_type: ToastType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Toasts {
|
||||
items: Signal<Vec<ToastMessage>>,
|
||||
next_id: Signal<usize>,
|
||||
}
|
||||
|
||||
impl Toasts {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
items: Signal::new(vec![]),
|
||||
next_id: Signal::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, toast_type: ToastType, message: impl Into<String>) {
|
||||
let id = *self.next_id.read();
|
||||
*self.next_id.write() = id + 1;
|
||||
self.items.write().push(ToastMessage {
|
||||
id,
|
||||
message: message.into(),
|
||||
toast_type,
|
||||
});
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
{
|
||||
let mut items = self.items;
|
||||
spawn(async move {
|
||||
gloo_timers::future::TimeoutFuture::new(4_000).await;
|
||||
items.write().retain(|t| t.id != id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, id: usize) {
|
||||
self.items.write().retain(|t| t.id != id);
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ToastContainer() -> Element {
|
||||
let mut toasts = use_context::<Toasts>();
|
||||
let items = toasts.items.read();
|
||||
|
||||
rsx! {
|
||||
div { class: "toast-container",
|
||||
for toast in items.iter() {
|
||||
{
|
||||
let toast_id = toast.id;
|
||||
let type_class = match toast.toast_type {
|
||||
ToastType::Success => "toast-success",
|
||||
ToastType::Error => "toast-error",
|
||||
ToastType::Info => "toast-info",
|
||||
};
|
||||
rsx! {
|
||||
div {
|
||||
key: "{toast_id}",
|
||||
class: "toast {type_class}",
|
||||
span { "{toast.message}" }
|
||||
button {
|
||||
class: "toast-dismiss",
|
||||
onclick: move |_| toasts.remove(toast_id),
|
||||
"\u{00d7}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user