From 78e3f1ae08df60f1d5ae9fd5d30d298837c2434e Mon Sep 17 00:00:00 2001 From: Priec Date: Wed, 12 Aug 2026 14:10:20 +0200 Subject: [PATCH] global table content --- server | 2 +- web/src/pages/add_table/logic.rs | 9 +++++++- .../pages/admin/table_definition/loader.rs | 23 +++++++++++-------- web/src/pages/admin/table_definition/state.rs | 6 +++++ web/src/pages/admin/table_definition/ui.rs | 16 ++++++++++++- .../table_definition/table_definition.html | 2 +- .../admin/table_definition/workspace.html | 13 +++++++---- 7 files changed, 54 insertions(+), 17 deletions(-) diff --git a/server b/server index d8c8b352..2bfe955a 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit d8c8b3521c5d3006bce497edb6fe4115c40b4c03 +Subproject commit 2bfe955a8d4a65736ecbc0ea7513689819812c3d diff --git a/web/src/pages/add_table/logic.rs b/web/src/pages/add_table/logic.rs index 1ab99ef9..c10aa8e3 100644 --- a/web/src/pages/add_table/logic.rs +++ b/web/src/pages/add_table/logic.rs @@ -29,6 +29,8 @@ use super::{ pub(crate) struct NewTableQuery { #[serde(default)] profile: String, + #[serde(default)] + global: bool, } /// GET /admin/tables/new @@ -39,6 +41,7 @@ pub(crate) async fn new_table_page( ) -> Response { let mut draft = TableDraft::new(); draft.profile_name = query.profile.trim().to_string(); + draft.global = query.global; match load_page(state, &headers, draft, None, None).await { Ok(page) => Html(ui::render_page(&page)).into_response(), @@ -91,7 +94,11 @@ pub(crate) async fn create_table( } }; - let profile_name = request.profile_name.clone(); + let profile_name = if request.global { + "__global".to_string() + } else { + request.profile_name.clone() + }; let request = match authenticated_request(&headers, request) { Ok(request) => request, Err(_) => return Redirect::to("/login").into_response(), diff --git a/web/src/pages/admin/table_definition/loader.rs b/web/src/pages/admin/table_definition/loader.rs index 3e7389d5..d3a80653 100644 --- a/web/src/pages/admin/table_definition/loader.rs +++ b/web/src/pages/admin/table_definition/loader.rs @@ -26,7 +26,7 @@ use crate::{ }; use super::state::{ - DetailColumn, LoadError, PageInputs, RenameEntry, ScriptView, TableDefinitionPageState, + DetailColumn, GLOBAL_SCOPE, LoadError, PageInputs, RenameEntry, ScriptView, TableDefinitionPageState, TableDetailView, TablePermissionAction, TableRolePermissions, TableSummary, }; @@ -96,19 +96,24 @@ pub(crate) async fn load_page( .collect::>(); // A profile that no longer exists takes the table selection with it. - if !profiles.contains(&inputs.selection.profile) { + if inputs.selection.profile != GLOBAL_SCOPE && !profiles.contains(&inputs.selection.profile) { inputs.selection.profile.clear(); inputs.selection.table.clear(); } - let tables = tree - .profiles - .iter() - .find(|profile| profile.name == inputs.selection.profile) - .map(|profile| { - profile - .tables + let selected_tables = if inputs.selection.is_global() { + tree.profiles.first().map(|profile| profile.tables.as_slice()) + } else { + tree.profiles + .iter() + .find(|profile| profile.name == inputs.selection.profile) + .map(|profile| profile.tables.as_slice()) + }; + let tables = selected_tables + .map(|tables| { + tables .iter() + .filter(|table| table.global == inputs.selection.is_global()) .map(|table| TableSummary { name: table.name.clone(), table_kind: table.table_kind.clone(), diff --git a/web/src/pages/admin/table_definition/state.rs b/web/src/pages/admin/table_definition/state.rs index d0d618f1..3b8d1400 100644 --- a/web/src/pages/admin/table_definition/state.rs +++ b/web/src/pages/admin/table_definition/state.rs @@ -7,6 +7,8 @@ use crate::schema::{ColumnCatalog, ColumnDraft}; +pub(crate) const GLOBAL_SCOPE: &str = "__global"; + /// The profile and table the workspace is pointed at. Arrives as a query /// string on the selector and on the column-panel endpoints, and as hidden /// fields on the panels that write. @@ -27,6 +29,10 @@ impl Selection { !self.table.is_empty() } + pub(crate) fn is_global(&self) -> bool { + self.profile == GLOBAL_SCOPE + } + /// The query string the column panel posts back to, so the panel's own /// form does not have to carry the selection among its column fields. pub(crate) fn query(&self) -> String { diff --git a/web/src/pages/admin/table_definition/ui.rs b/web/src/pages/admin/table_definition/ui.rs index 3e93a2a1..faf1bc0f 100644 --- a/web/src/pages/admin/table_definition/ui.rs +++ b/web/src/pages/admin/table_definition/ui.rs @@ -113,6 +113,20 @@ mod tests { } } + #[test] + fn global_tables_have_their_own_scope() { + let mut state = page(); + state.selection.profile = "__global".to_string(); + + let html = render_workspace(&state); + + assert!(html.contains(r#"value="__global" selected"#)); + assert!(html.contains("Global — all profiles")); + assert!(html.contains("/admin/tables/new?global=true")); + assert!(!html.contains("Copy __global")); + assert!(!html.contains("Create tables from an invoice template")); + } + fn page() -> TableDefinitionPageState { TableDefinitionPageState { nav: Nav::default(), @@ -254,7 +268,7 @@ mod tests { state.tables.clear(); let html = render_workspace(&state); assert!(!html.contains("/admin/table-definition/copy")); - assert!(html.contains("Choose a profile")); + assert!(html.contains("Choose a scope")); } #[test] diff --git a/web/templates/pages/admin/table_definition/table_definition.html b/web/templates/pages/admin/table_definition/table_definition.html index 567a6651..f9a91440 100644 --- a/web/templates/pages/admin/table_definition/table_definition.html +++ b/web/templates/pages/admin/table_definition/table_definition.html @@ -9,7 +9,7 @@

Table definition

Table definition

-

Pick a profile and a table, then change its definition.

+

Choose Global to manage tables shared by every profile, or choose a profile to manage only its own tables.

← Admin panel diff --git a/web/templates/pages/admin/table_definition/workspace.html b/web/templates/pages/admin/table_definition/workspace.html index a335a876..7fa02c1a 100644 --- a/web/templates/pages/admin/table_definition/workspace.html +++ b/web/templates/pages/admin/table_definition/workspace.html @@ -45,10 +45,11 @@

Selection

-