feat(sidebar): add compliance scanner link from env config #19

Merged
sharang merged 2 commits from feat/compliance-scanner-link into main 2026-03-09 08:39:01 +00:00
11 changed files with 39 additions and 7 deletions
Showing only changes of commit dc4cb40171 - Show all commits

View File

@@ -74,6 +74,11 @@ LANGGRAPH_URL=
LANGFLOW_URL= LANGFLOW_URL=
LANGFUSE_URL= LANGFUSE_URL=
# ---------------------------------------------------------------------------
# Compliance scanner (external tool, opens in new tab) [OPTIONAL]
# ---------------------------------------------------------------------------
COMPLIANCE_SCANNER_URL=
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Vector database [OPTIONAL] # Vector database [OPTIONAL]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -46,7 +46,8 @@
"agents": "Agenten", "agents": "Agenten",
"flow": "Flow", "flow": "Flow",
"analytics": "Analytics", "analytics": "Analytics",
"pricing": "Preise" "pricing": "Preise",
"compliance": "Compliance"
}, },
"auth": { "auth": {
"redirecting_login": "Weiterleitung zur Anmeldung...", "redirecting_login": "Weiterleitung zur Anmeldung...",

View File

@@ -46,7 +46,8 @@
"agents": "Agents", "agents": "Agents",
"flow": "Flow", "flow": "Flow",
"analytics": "Analytics", "analytics": "Analytics",
"pricing": "Pricing" "pricing": "Pricing",
"compliance": "Compliance"
}, },
"auth": { "auth": {
"redirecting_login": "Redirecting to login...", "redirecting_login": "Redirecting to login...",

View File

@@ -46,7 +46,8 @@
"agents": "Agentes", "agents": "Agentes",
"flow": "Flujo", "flow": "Flujo",
"analytics": "Estadisticas", "analytics": "Estadisticas",
"pricing": "Precios" "pricing": "Precios",
"compliance": "Cumplimiento"
}, },
"auth": { "auth": {
"redirecting_login": "Redirigiendo al inicio de sesion...", "redirecting_login": "Redirigiendo al inicio de sesion...",

View File

@@ -46,7 +46,8 @@
"agents": "Agents", "agents": "Agents",
"flow": "Flux", "flow": "Flux",
"analytics": "Analytique", "analytics": "Analytique",
"pricing": "Tarifs" "pricing": "Tarifs",
"compliance": "Conformite"
}, },
"auth": { "auth": {
"redirecting_login": "Redirection vers la connexion...", "redirecting_login": "Redirection vers la connexion...",

View File

@@ -46,7 +46,8 @@
"agents": "Agentes", "agents": "Agentes",
"flow": "Fluxo", "flow": "Fluxo",
"analytics": "Analise", "analytics": "Analise",
"pricing": "Precos" "pricing": "Precos",
"compliance": "Conformidade"
}, },
"auth": { "auth": {
"redirecting_login": "A redirecionar para o inicio de sessao...", "redirecting_login": "A redirecionar para o inicio de sessao...",

View File

@@ -76,6 +76,7 @@ pub fn AppShell() -> Element {
name: info.name, name: info.name,
avatar_url: info.avatar_url, avatar_url: info.avatar_url,
librechat_url: info.librechat_url, librechat_url: info.librechat_url,
compliance_scanner_url: info.compliance_scanner_url,
class: sidebar_cls, class: sidebar_cls,
on_nav: move |_| mobile_menu_open.set(false), on_nav: move |_| mobile_menu_open.set(false),
} }

View File

@@ -1,7 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::{ use dioxus_free_icons::icons::bs_icons::{
BsBoxArrowRight, BsBuilding, BsChatDots, BsCloudArrowUp, BsCodeSlash, BsGithub, BsGlobe2, BsBoxArrowRight, BsBuilding, BsChatDots, BsCloudArrowUp, BsCodeSlash, BsGithub, BsGlobe2,
BsGrid, BsHouseDoor, BsMoonFill, BsSunFill, BsGrid, BsHouseDoor, BsMoonFill, BsShieldCheck, BsSunFill,
}; };
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
@@ -44,13 +44,14 @@ pub fn Sidebar(
email: String, email: String,
avatar_url: String, avatar_url: String,
#[props(default = "http://localhost:3080".to_string())] librechat_url: String, #[props(default = "http://localhost:3080".to_string())] librechat_url: String,
#[props(default)] compliance_scanner_url: String,
#[props(default = "sidebar".to_string())] class: String, #[props(default = "sidebar".to_string())] class: String,
#[props(default)] on_nav: EventHandler<()>, #[props(default)] on_nav: EventHandler<()>,
) -> Element { ) -> Element {
let locale = use_context::<Signal<Locale>>(); let locale = use_context::<Signal<Locale>>();
let locale_val = *locale.read(); let locale_val = *locale.read();
let nav_items: Vec<NavItem> = vec![ let mut nav_items: Vec<NavItem> = vec![
NavItem { NavItem {
key: "dashboard", key: "dashboard",
label: t(locale_val, "nav.dashboard"), label: t(locale_val, "nav.dashboard"),
@@ -84,6 +85,16 @@ pub fn Sidebar(
}, },
]; ];
// Only show the compliance scanner link when a URL is configured.
if !compliance_scanner_url.is_empty() {
nav_items.push(NavItem {
key: "compliance",
label: t(locale_val, "nav.compliance"),
target: NavTarget::External(compliance_scanner_url.clone()),
icon: rsx! { Icon { icon: BsShieldCheck, width: 18, height: 18 } },
});
}
// Determine current path to highlight the active nav link. // Determine current path to highlight the active nav link.
let current_route = use_route::<Route>(); let current_route = use_route::<Route>();
let logout_label = t(locale_val, "common.logout"); let logout_label = t(locale_val, "common.logout");

View File

@@ -35,6 +35,8 @@ pub async fn check_auth() -> Result<AuthInfo, ServerFnError> {
let langgraph_url = state.services.langgraph_url.clone(); let langgraph_url = state.services.langgraph_url.clone();
let langflow_url = state.services.langflow_url.clone(); let langflow_url = state.services.langflow_url.clone();
let langfuse_url = state.services.langfuse_url.clone(); let langfuse_url = state.services.langfuse_url.clone();
let compliance_scanner_url =
state.services.compliance_scanner_url.clone();
Ok(AuthInfo { Ok(AuthInfo {
authenticated: true, authenticated: true,
@@ -46,6 +48,7 @@ pub async fn check_auth() -> Result<AuthInfo, ServerFnError> {
langgraph_url, langgraph_url,
langflow_url, langflow_url,
langfuse_url, langfuse_url,
compliance_scanner_url,
}) })
} }
None => Ok(AuthInfo::default()), None => Ok(AuthInfo::default()),

View File

@@ -168,6 +168,8 @@ pub struct ServiceUrls {
pub s3_access_key: String, pub s3_access_key: String,
/// S3 secret key (wrapped for debug safety). /// S3 secret key (wrapped for debug safety).
pub s3_secret_key: SecretString, pub s3_secret_key: SecretString,
/// Compliance scanner URL (external tool opened in a new tab).
pub compliance_scanner_url: String,
} }
impl ServiceUrls { impl ServiceUrls {
@@ -194,6 +196,7 @@ impl ServiceUrls {
s3_url: optional_env("S3_URL"), s3_url: optional_env("S3_URL"),
s3_access_key: optional_env("S3_ACCESS_KEY"), s3_access_key: optional_env("S3_ACCESS_KEY"),
s3_secret_key: SecretString::from(optional_env("S3_SECRET_KEY")), s3_secret_key: SecretString::from(optional_env("S3_SECRET_KEY")),
compliance_scanner_url: optional_env("COMPLIANCE_SCANNER_URL"),
}) })
} }
} }

View File

@@ -30,6 +30,8 @@ pub struct AuthInfo {
pub langflow_url: String, pub langflow_url: String,
/// Langfuse observability URL (empty if not configured) /// Langfuse observability URL (empty if not configured)
pub langfuse_url: String, pub langfuse_url: String,
/// Compliance scanner URL (empty if not configured)
pub compliance_scanner_url: String,
} }
/// Per-user LLM provider configuration stored in MongoDB. /// Per-user LLM provider configuration stored in MongoDB.
@@ -100,6 +102,7 @@ mod tests {
assert_eq!(info.langgraph_url, ""); assert_eq!(info.langgraph_url, "");
assert_eq!(info.langflow_url, ""); assert_eq!(info.langflow_url, "");
assert_eq!(info.langfuse_url, ""); assert_eq!(info.langfuse_url, "");
assert_eq!(info.compliance_scanner_url, "");
} }
#[test] #[test]
@@ -114,6 +117,7 @@ mod tests {
langgraph_url: "http://localhost:8123".into(), langgraph_url: "http://localhost:8123".into(),
langflow_url: "http://localhost:7860".into(), langflow_url: "http://localhost:7860".into(),
langfuse_url: "http://localhost:3000".into(), langfuse_url: "http://localhost:3000".into(),
compliance_scanner_url: "http://localhost:9090".into(),
}; };
let json = serde_json::to_string(&info).expect("serialize AuthInfo"); let json = serde_json::to_string(&info).expect("serialize AuthInfo");
let back: AuthInfo = serde_json::from_str(&json).expect("deserialize AuthInfo"); let back: AuthInfo = serde_json::from_str(&json).expect("deserialize AuthInfo");