feat(dash): improved frontend dashboard #6
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,3 +18,5 @@ keycloak/*
|
||||
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
||||
searxng/
|
||||
|
||||
@@ -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}" }
|
||||
}
|
||||
|
||||
@@ -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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}" }
|
||||
}
|
||||
|
||||
@@ -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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user