feat(dash): improved frontend dashboard #6

Merged
sharang merged 8 commits from feat/CAI-4-next into main 2026-02-19 11:52:41 +00:00
10 changed files with 44 additions and 57 deletions
Showing only changes of commit 83772cc256 - Show all commits

2
.gitignore vendored
View File

@@ -18,3 +18,5 @@ keycloak/*
# Node modules
node_modules/
searxng/

View File

@@ -14,7 +14,11 @@ pub fn NewsCardView(card: NewsCardModel) -> Element {
article { class: "news-card",
if let Some(ref thumb) = card.thumbnail_url {
div { class: "news-card-thumb",
img { src: "{thumb}", alt: "{card.title}", loading: "lazy" }
img {
src: "{thumb}",
alt: "{card.title}",
loading: "lazy",
}
}
}
div { class: "news-card-body",
@@ -24,7 +28,12 @@ pub fn NewsCardView(card: NewsCardModel) -> Element {
span { class: "news-card-date", "{card.published_at}" }
}
h3 { class: "news-card-title",
a { href: "{card.url}", target: "_blank", rel: "noopener", "{card.title}" }
a {
href: "{card.url}",
target: "_blank",
rel: "noopener",
"{card.title}"
}
}
p { class: "news-card-summary", "{card.summary}" }
}

View File

@@ -29,13 +29,13 @@ pub fn SubNav(items: Vec<SubNavItem>) -> Element {
for item in &items {
{
let is_active = item.route == current_route;
let class = if is_active { "sub-nav-item sub-nav-item--active" } else { "sub-nav-item" };
let class = if is_active {
"sub-nav-item sub-nav-item--active"
} else {
"sub-nav-item"
};
rsx! {
Link {
class: "{class}",
to: item.route.clone(),
"{item.label}"
}
Link { class: "{class}", to: item.route.clone(), "{item.label}" }
}
}
}

View File

@@ -32,7 +32,11 @@ pub fn ToolCard(tool: McpTool, on_toggle: EventHandler<String>) -> Element {
let id = tool.id.clone();
move |_| on_toggle.call(id.clone())
},
if tool.enabled { "ON" } else { "OFF" }
if tool.enabled {
"ON"
} else {
"OFF"
}
}
}
}

View File

@@ -37,9 +37,7 @@ pub fn ChatPage() -> Element {
};
let id = session.id.clone();
rsx! {
button {
class: "{class}",
onclick: move |_| active_session_id.set(id.clone()),
button { class: "{class}", onclick: move |_| active_session_id.set(id.clone()),
div { class: "chat-session-title", "{session.title}" }
div { class: "chat-session-date", "{session.created_at}" }
}

View File

@@ -43,7 +43,7 @@ pub fn DashboardPage() -> Element {
subtitle: "AI news and updates".to_string(),
}
div { class: "dashboard-filters",
for (label, cat) in categories {
for (label , cat) in categories {
{
let is_active = *active_filter.read() == cat;
let class = if is_active {
@@ -52,11 +52,7 @@ pub fn DashboardPage() -> Element {
"filter-tab"
};
rsx! {
button {
class: "{class}",
onclick: move |_| active_filter.set(cat.clone()),
"{label}"
}
button { class: "{class}", onclick: move |_| active_filter.set(cat.clone()), "{label}" }
}
}
}

View File

@@ -17,12 +17,7 @@ pub fn AnalyticsPage() -> Element {
div { class: "analytics-stat",
span { class: "analytics-stat-value", "{metric.value}" }
span { class: "analytics-stat-label", "{metric.label}" }
span {
class: if metric.change_pct >= 0.0 {
"analytics-stat-change analytics-stat-change--up"
} else {
"analytics-stat-change analytics-stat-change--down"
},
span { class: if metric.change_pct >= 0.0 { "analytics-stat-change analytics-stat-change--up" } else { "analytics-stat-change analytics-stat-change--down" },
"{metric.change_pct:+.1}%"
}
}

View File

@@ -26,27 +26,19 @@ pub fn OrgDashboardPage() -> Element {
title: "Organization".to_string(),
subtitle: "Manage members and billing".to_string(),
actions: rsx! {
button {
class: "btn-primary",
onclick: move |_| show_invite.set(true),
"Invite Member"
}
button { class: "btn-primary", onclick: move |_| show_invite.set(true), "Invite Member" }
},
}
// Stats bar
div { class: "org-stats-bar",
div { class: "org-stat",
span { class: "org-stat-value",
"{usage.seats_used}/{usage.seats_total}"
}
span { class: "org-stat-value", "{usage.seats_used}/{usage.seats_total}" }
span { class: "org-stat-label", "Seats Used" }
}
div { class: "org-stat",
span { class: "org-stat-value", "{tokens_display}" }
span { class: "org-stat-label",
"of {tokens_limit_display} tokens"
}
span { class: "org-stat-label", "of {tokens_limit_display} tokens" }
}
div { class: "org-stat",
span { class: "org-stat-value", "{usage.billing_cycle_end}" }
@@ -79,7 +71,8 @@ pub fn OrgDashboardPage() -> Element {
// Invite modal
if *show_invite.read() {
div { class: "modal-overlay",
div {
class: "modal-overlay",
onclick: move |_| show_invite.set(false),
div {
class: "modal-content",

View File

@@ -76,9 +76,7 @@ pub fn ProvidersPage() -> Element {
saved.set(false);
},
for m in &available_models {
option { value: "{m.id}",
"{m.name} ({m.context_window}k ctx)"
}
option { value: "{m.id}", "{m.name} ({m.context_window}k ctx)" }
}
}
}
@@ -92,9 +90,7 @@ pub fn ProvidersPage() -> Element {
saved.set(false);
},
for e in &available_embeddings {
option { value: "{e.id}",
"{e.name} ({e.dimensions}d)"
}
option { value: "{e.id}", "{e.name} ({e.dimensions}d)" }
}
}
}
@@ -125,26 +121,24 @@ pub fn ProvidersPage() -> Element {
div { class: "status-card",
div { class: "status-row",
span { class: "status-label", "Provider" }
span { class: "status-value",
"{active_config.provider.label()}"
}
span { class: "status-value", "{active_config.provider.label()}" }
}
div { class: "status-row",
span { class: "status-label", "Model" }
span { class: "status-value",
"{active_config.selected_model}"
}
span { class: "status-value", "{active_config.selected_model}" }
}
div { class: "status-row",
span { class: "status-label", "Embedding" }
span { class: "status-value",
"{active_config.selected_embedding}"
}
span { class: "status-value", "{active_config.selected_embedding}" }
}
div { class: "status-row",
span { class: "status-label", "API Key" }
span { class: "status-value",
if active_config.api_key_set { "Set" } else { "Not set" }
if active_config.api_key_set {
"Set"
} else {
"Not set"
}
}
}
}

View File

@@ -30,11 +30,7 @@ pub fn ToolsPage() -> Element {
}
div { class: "tools-grid",
for tool in tool_list {
ToolCard {
key: "{tool.id}",
tool,
on_toggle,
}
ToolCard { key: "{tool.id}", tool, on_toggle }
}
}
}