From 04ee2257320b1ce68ec658471789a893152369ea Mon Sep 17 00:00:00 2001 From: Per Stark Date: Tue, 4 Nov 2025 20:42:24 +0100 Subject: [PATCH] design: improved admin page, new structure --- CHANGELOG.md | 1 + html-router/src/routes/admin/handlers.rs | 97 +++++++--- html-router/templates/admin/base.html | 183 ++++-------------- .../templates/admin/sections/models.html | 130 +++++++++++++ .../templates/admin/sections/overview.html | 57 ++++++ 5 files changed, 297 insertions(+), 171 deletions(-) create mode 100644 html-router/templates/admin/sections/models.html create mode 100644 html-router/templates/admin/sections/overview.html diff --git a/CHANGELOG.md b/CHANGELOG.md index ede988a..b0aa01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog ## Unreleased +- Improved admin page, now only loads models when specifically requested ## Version 0.2.6 (2025-10-29) - Added an opt-in FastEmbed-based reranking stage behind `reranking_enabled`. It improves retrieval accuracy by re-scoring hybrid results. diff --git a/html-router/src/routes/admin/handlers.rs b/html-router/src/routes/admin/handlers.rs index 8744bce..80e08b2 100644 --- a/html-router/src/routes/admin/handlers.rs +++ b/html-router/src/routes/admin/handlers.rs @@ -1,5 +1,9 @@ use async_openai::types::ListModelResponse; -use axum::{extract::State, response::IntoResponse, Form}; +use axum::{ + extract::{Query, State}, + response::IntoResponse, + Form, +}; use serde::{Deserialize, Serialize}; use common::{ @@ -31,44 +35,83 @@ use crate::{ pub struct AdminPanelData { user: User, settings: SystemSettings, - analytics: Analytics, - users: i64, + analytics: Option, + users: Option, default_query_prompt: String, default_image_prompt: String, conversation_archive: Vec, - available_models: ListModelResponse, + available_models: Option, + current_section: AdminSection, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AdminSection { + Overview, + Models, +} + +impl Default for AdminSection { + fn default() -> Self { + Self::Overview + } +} + +#[derive(Deserialize)] +pub struct AdminPanelQuery { + section: Option, } pub async fn show_admin_panel( State(state): State, RequireUser(user): RequireUser, + Query(query): Query, ) -> Result { - let ( - settings_res, - analytics_res, - user_count_res, - conversation_archive_res, - available_models_res, - ) = tokio::join!( + let section = match query.section.as_deref() { + Some("models") => AdminSection::Models, + _ => AdminSection::Overview, + }; + + let (settings, conversation_archive) = tokio::try_join!( SystemSettings::get_current(&state.db), - Analytics::get_current(&state.db), - Analytics::get_users_amount(&state.db), - User::get_user_conversations(&user.id, &state.db), - async { state.openai_client.models().list().await } - ); + User::get_user_conversations(&user.id, &state.db) + )?; + + let (analytics, users) = if section == AdminSection::Overview { + let (analytics, users) = tokio::try_join!( + Analytics::get_current(&state.db), + Analytics::get_users_amount(&state.db) + )?; + (Some(analytics), Some(users)) + } else { + (None, None) + }; + + let available_models = if section == AdminSection::Models { + Some( + state + .openai_client + .models() + .list() + .await + .map_err(|e| AppError::InternalError(e.to_string()))?, + ) + } else { + None + }; Ok(TemplateResponse::new_template( "admin/base.html", AdminPanelData { user, - settings: settings_res?, - analytics: analytics_res?, - available_models: available_models_res - .map_err(|e| AppError::InternalError(e.to_string()))?, - users: user_count_res?, + settings, + analytics, + available_models, + users, default_query_prompt: DEFAULT_QUERY_SYSTEM_PROMPT.to_string(), default_image_prompt: DEFAULT_IMAGE_PROCESSING_PROMPT.to_string(), - conversation_archive: conversation_archive_res?, + conversation_archive, + current_section: section, }, )) } @@ -115,7 +158,7 @@ pub async fn toggle_registration_status( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "registration_status_input", RegistrationToggleData { settings: new_settings, @@ -217,7 +260,7 @@ pub async fn update_model_settings( .map_err(|_e| AppError::InternalError("Failed to get models".to_string()))?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/models.html", "model_settings_form", ModelSettingsData { settings: new_settings, @@ -282,7 +325,7 @@ pub async fn patch_query_prompt( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "system_prompt_section", SystemPromptSectionData { settings: new_settings, @@ -341,7 +384,7 @@ pub async fn patch_ingestion_prompt( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "system_prompt_section", SystemPromptSectionData { settings: new_settings, @@ -400,7 +443,7 @@ pub async fn patch_image_prompt( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "system_prompt_section", SystemPromptSectionData { settings: new_settings, diff --git a/html-router/templates/admin/base.html b/html-router/templates/admin/base.html index 6d10cfc..119680b 100644 --- a/html-router/templates/admin/base.html +++ b/html-router/templates/admin/base.html @@ -3,154 +3,49 @@ {% block title %}Minne - Admin{% endblock %} {% block main %} -
-
-
-
-

Admin Dashboard

+
+
+
+
+

Admin Controls

+

+ Stay on top of analytics and manage AI integrations without waiting on long-running model calls. +

+
+
+ Signed in as {{ user.email }}
-
-
-
-
Page Loads
-
{{analytics.page_loads}}
-
Total page load events
-
-
-
Unique Visitors
-
{{analytics.visitors}}
-
Distinct users by fingerprint
-
-
-
Users
-
{{users}}
-
Registered accounts
-
-
-
+ -
- {% block system_prompt_section %} -
-
System Prompts
-
- - - -
-
- {% endblock %} - -
-
AI Models
- {% block model_settings_form %} -
- -
-
Query Model
- -

Current: {{settings.query_model}}

-
- - -
-
Processing Model
- -

Current: {{settings.processing_model}}

-
- - -
-
Image Processing Model
- -

Current: {{settings.image_processing_model}}

-
- - -
-
Voice Processing Model
- -

Current: {{settings.voice_processing_model}}

-
- - -
-
Embedding Model
- -

Current: {{settings.embedding_model}} ({{settings.embedding_dimensions}} dims)

-
- - -
-
Embedding Dimensions
- -
- - - - -
- -
-
- - - {% endblock %} -
- -
-
Registration
- -
-
-
+
+ {% if current_section == 'models' %} + {% include 'admin/sections/models.html' %} + {% else %} + {% include 'admin/sections/overview.html' %} + {% endif %} +
{% endblock %} diff --git a/html-router/templates/admin/sections/models.html b/html-router/templates/admin/sections/models.html new file mode 100644 index 0000000..9ff3b42 --- /dev/null +++ b/html-router/templates/admin/sections/models.html @@ -0,0 +1,130 @@ +
+
+
+
AI Models
+

Model configuration

+

+ Choose which models power conversational search, ingestion analysis, and embeddings. Adjusting embeddings may trigger a full reprocess. +

+
+ + ← Back to Admin + +
+ + {% if available_models %} + {% block model_settings_form %} +
+
+
+
Query Model
+ +

Current: {{ settings.query_model }}

+
+ +
+
Processing Model
+ +

Current: {{ settings.processing_model }}

+
+
+ +
+
+
Image Processing Model
+ +

Current: {{ settings.image_processing_model }}

+
+ +
+
Voice Processing Model
+ +

Current: {{ settings.voice_processing_model }}

+
+
+ +
+
+
Embedding Model
+ +

Current: {{ settings.embedding_model }}

+
+ +
+
Embedding Dimensions
+ +

Changing dimensions will trigger a background re-embedding.

+
+
+ + + +
+ +
+
+ + + {% endblock %} + {% else %} +
+
Unable to load models
+

We could not reach the model provider. Check the API key and retry.

+
+ {% endif %} +
diff --git a/html-router/templates/admin/sections/overview.html b/html-router/templates/admin/sections/overview.html new file mode 100644 index 0000000..59a1a5c --- /dev/null +++ b/html-router/templates/admin/sections/overview.html @@ -0,0 +1,57 @@ +{% if analytics %} +
+
+
Page Loads
+
{{ analytics.page_loads }}
+
Total load events seen by Minne
+
+
+
Unique Visitors
+
{{ analytics.visitors }}
+
Distinct users by fingerprint
+
+
+
Users
+
{{ users or 0 }}
+
Registered accounts
+
+
+{% else %} +
+
Analytics unavailable
+

We could not fetch analytics for this view. Reload or check the monitoring pipeline.

+
+{% endif %} + +
+ {% block system_prompt_section %} +
+
+
+
System Prompts
+

Adjust the prompts that power retrieval, ingestion analysis, and image processing flows.

+
+ LLM +
+
+ + + +
+
+ {% endblock %} + +
+
Registration
+

Toggle whether new people can sign up without an invite.

+ +
+
+