navbar unified

This commit is contained in:
Priec
2026-08-15 14:39:25 +02:00
parent 3dc10e0371
commit f3ba8e73c5
16 changed files with 116 additions and 57 deletions

View File

@@ -50,7 +50,7 @@ pub(crate) async fn load_page(
})
.collect();
Ok(AddLogicPageState {
nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization),
nav: crate::ui::Nav::from_authorization(headers, "admin", &authorization),
tables,
form,
error,

View File

@@ -124,7 +124,7 @@ pub(crate) async fn load_page(
draft.set_available_relation_table_options(table_options);
Ok(AddTablePageState {
nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization),
nav: crate::ui::Nav::from_authorization(headers, "admin", &authorization),
profiles: tree
.profiles
.into_iter()

View File

@@ -43,7 +43,7 @@ pub(crate) async fn load_page(
.flat_map(|profile| profile.tables.iter().map(|table| table.name.clone()))
.collect();
Ok(ValidationPageState {
nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization),
nav: crate::ui::Nav::from_authorization(headers, "admin", &authorization),
profiles,
tables,
form,

View File

@@ -166,7 +166,7 @@ pub(crate) async fn load_admin_page(
};
Ok(AdminPageState {
nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization),
nav: crate::ui::Nav::from_authorization(headers, "admin", &authorization),
profiles,
selected_profile,
tables,

View File

@@ -61,7 +61,7 @@ pub(crate) async fn load_ecb_page(
.into_inner();
Ok(EcbPageState {
nav: crate::ui::Nav::new(headers, "ecb").with_authorization(&authorization),
nav: crate::ui::Nav::from_authorization(headers, "ecb", &authorization),
verified_through_date: status.verified_through_date,
latest_verifiable_date: status.latest_verifiable_date,
healthy: status.healthy,

View File

@@ -274,7 +274,7 @@ pub(crate) async fn load_page(
inputs.columns.table_name = inputs.selection.table.clone();
Ok(TableDefinitionPageState {
nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization),
nav: crate::ui::Nav::from_authorization(headers, "admin", &authorization),
tables,
link_targets,
detail,

View File

@@ -9,7 +9,6 @@ use crate::{
AnalyticsTable, ExecuteAnalyticsQueryRequest, GetAnalyticsCatalogRequest,
GetAnalyticsCatalogResponse, analytics_value,
},
auth::GetAuthorizationRequest,
definitions::common::Empty,
services::authenticated_request,
};
@@ -48,21 +47,6 @@ pub(crate) async fn load_profiles(
.collect())
}
pub(crate) async fn load_navigation(
state: AppState,
headers: &HeaderMap,
) -> crate::ui::Nav {
let Ok(request) = authenticated_request(headers, GetAuthorizationRequest {}) else {
return crate::ui::Nav::new(headers, "analytics");
};
let mut auth = state.auth;
match auth.get_authorization(request).await {
Ok(response) => crate::ui::Nav::new(headers, "analytics")
.with_authorization(response.get_ref()),
Err(_) => crate::ui::Nav::new(headers, "analytics"),
}
}
pub(crate) async fn load_catalog(
state: AppState,
headers: &HeaderMap,

View File

@@ -22,7 +22,9 @@ pub(crate) async fn analytics_page(
State(state): State<AppState>,
headers: HeaderMap,
) -> Html<String> {
Html(ui::render_page(loader::load_navigation(state, &headers).await))
Html(ui::render_page(
crate::ui::Nav::for_request(state, &headers, "analytics").await,
))
}
pub(crate) async fn load_profiles(
@@ -31,15 +33,15 @@ pub(crate) async fn load_profiles(
) -> Html<String> {
match loader::load_profiles(state, &headers).await {
Ok(profiles) => Html(ui::render_profile_options(
crate::ui::Nav::new(&headers, ""),
crate::ui::Nav::without_authorization(&headers, ""),
&profiles,
)),
Err(LoadError::Unauthenticated) => Html(ui::render_profile_options_error(
crate::ui::Nav::new(&headers, ""),
crate::ui::Nav::without_authorization(&headers, ""),
&tr!(Locale::from_headers(&headers), "analytics-sign-in"),
)),
Err(LoadError::Backend(message)) => Html(ui::render_profile_options_error(
crate::ui::Nav::new(&headers, ""),
crate::ui::Nav::without_authorization(&headers, ""),
&message,
)),
}
@@ -64,7 +66,7 @@ pub(crate) async fn load_catalog(
}
match loader::load_catalog(state, &headers, input.profile_name).await {
Ok(catalog) => Html(ui::render_catalog(
crate::ui::Nav::new(&headers, ""),
crate::ui::Nav::without_authorization(&headers, ""),
&catalog,
))
.into_response(),

View File

@@ -10,7 +10,7 @@ pub(crate) async fn load_page(
) -> Result<ExportPageState, LoadError> {
let catalog = load_catalog(state, headers, "read").await?;
Ok(ExportPageState {
nav: crate::ui::Nav::new(headers, "").with_authorization(&catalog.authorization),
nav: crate::ui::Nav::from_authorization(headers, "", &catalog.authorization),
catalog,
})
}

View File

@@ -15,7 +15,7 @@ pub(crate) async fn load_page(
) -> Result<ImportPageState, LoadError> {
let catalog = load_catalog(state, headers, "insert").await?;
Ok(ImportPageState {
nav: crate::ui::Nav::new(headers, "").with_authorization(&catalog.authorization),
nav: crate::ui::Nav::from_authorization(headers, "", &catalog.authorization),
catalog,
form,
error,

View File

@@ -19,14 +19,20 @@ use super::{state::{ChangePasswordInput, LoginInput}, ui};
pub(crate) async fn login_page(
headers: HeaderMap,
) -> Html<String> {
Html(ui::render_page(Nav::new(&headers, "login")))
Html(ui::render_page(Nav::without_authorization(&headers, "login")))
}
pub(crate) async fn password_page(headers: HeaderMap) -> Response {
pub(crate) async fn password_page(
State(state): State<AppState>,
headers: HeaderMap,
) -> Response {
if authenticated_request(&headers, ()).is_err() {
return axum::response::Redirect::to("/login").into_response();
}
Html(ui::render_password_page(Nav::new(&headers, ""))).into_response()
Html(ui::render_password_page(
Nav::for_request(state, &headers, "").await,
))
.into_response()
}
pub(crate) async fn change_password(

View File

@@ -48,3 +48,33 @@ pub(crate) fn render_password_success(locale: Locale) -> String {
&tr!(locale, "password-changed-message"),
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::{AuthorizationSnapshot, Permission};
/// The password page is not itself a nav entry, which is exactly why it
/// once rendered with an unresolved `Nav` and lost every capability link
/// in the navbar. It carries the same navbar as any other page.
#[test]
fn the_password_page_carries_the_navbar_the_signed_in_user_has_earned() {
let authorization = AuthorizationSnapshot {
role: "admin".to_string(),
permissions: vec![Permission {
object: crate::authz::STRUCT_TABLE.to_string(),
action: crate::authz::MANAGE.to_string(),
}],
};
let nav = Nav::from_authorization(
&axum::http::HeaderMap::new(),
"",
&authorization,
);
let rendered = render_password_page(nav);
assert!(rendered.contains("href=\"/admin\""), "{rendered}");
assert!(!rendered.contains("href=\"/login\""), "{rendered}");
}
}

View File

@@ -52,7 +52,7 @@ pub(crate) async fn access(
/// The navbar every permissions page carries. `"permissions"` is a nav entry of
/// its own, so the section is reachable without going through the admin panel.
pub(crate) fn nav(headers: &HeaderMap, access: &Access) -> crate::ui::Nav {
crate::ui::Nav::new(headers, "permissions").with_authorization(&access.authorization)
crate::ui::Nav::from_authorization(headers, "permissions", &access.authorization)
}
pub(crate) async fn roles(

View File

@@ -13,7 +13,7 @@ use crate::{
use super::{state::RegisterInput, ui};
pub(crate) async fn register_page(headers: HeaderMap) -> Html<String> {
Html(ui::render_page(Nav::new(&headers, "register")))
Html(ui::render_page(Nav::without_authorization(&headers, "register")))
}
pub(crate) async fn register(