diff --git a/client b/client index 1e3c9de4..a330f747 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 1e3c9de431a28de635ef4caac5c7ea69835b0822 +Subproject commit a330f747090baf32825ae6a7caf5f8d95de6d863 diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index 3cd4b0a6..35a71436 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -83,6 +83,11 @@ message PostTableDefinitionRequest { // to individual MONEY-column currencies: tables may hold money in any currency, and amounts convert // to this one when they reach the ledger. string accounting_currency = 8; + + // When true, the table is stored once in the global physical schema and is + // visible from every profile. profile_name and accounting_currency are ignored. + bool global = 9; + } // Defines the input for explicitly creating tables backed by one invoice @@ -237,6 +242,9 @@ message ProfileTreeResponse { // "dynamic" for user-defined tables, "system" for backend-managed tables. string table_kind = 5; + + // True when this table is shared by every profile. + bool global = 6; } // Profile (schema) entry. @@ -313,6 +321,7 @@ message TableDetail { repeated string row_display_columns = 6; map column_behaviors = 7; string table_kind = 8; + bool global = 9; } // Server-owned behavior for one logical column returned in table details. diff --git a/common/proto/tables_data.proto b/common/proto/tables_data.proto index 128970b2..eb855980 100644 --- a/common/proto/tables_data.proto +++ b/common/proto/tables_data.proto @@ -47,6 +47,18 @@ service TablesData { // - Updates the row and returns the id; queues search indexing (best effort) rpc PutTableData(PutTableDataRequest) returns (PutTableDataResponse); + // Performs a PUT after the user explicitly accepted its cross-profile impact. + // This is meaningful only for global tables; ordinary tables behave exactly + // like PutTableData. + rpc PutTableDataConfirmed(PutTableDataRequest) returns (PutTableDataResponse); + + // Lists the other profiles whose rows currently point at this global row. + rpc GetGlobalTableUpdateImpact(GlobalTableUpdateImpactRequest) returns (GlobalTableUpdateImpactResponse); + + // Snapshots the current version of a global row and advances its version. + // Existing references remain immutable; new references use the new version. + rpc ArchiveGlobalTableData(ArchiveGlobalTableDataRequest) returns (ArchiveGlobalTableDataResponse); + // Soft-delete a single record (sets deleted = true) if it exists and is not already deleted. // // Behavior: @@ -68,6 +80,9 @@ service TablesData { // - If the physical table is missing but the definition exists, returns INTERNAL rpc GetTableData(GetTableDataRequest) returns (GetTableDataResponse); + // Fetches one exact version of a global row. + rpc GetGlobalTableDataVersion(GetGlobalTableDataVersionRequest) returns (GetTableDataResponse); + // Count non-deleted rows in a table. // // Behavior: @@ -214,6 +229,32 @@ message PutTableDataResponse { int64 row_revision = 4; } +message GlobalTableUpdateImpactRequest { + string profile_name = 1; + string table_name = 2; + int64 id = 3; +} + +message GlobalTableUpdateImpactResponse { + repeated string affected_profiles = 1; +} + +message ArchiveGlobalTableDataRequest { + string profile_name = 1; + string table_name = 2; + int64 id = 3; + int64 expected_revision = 4; +} + +message ArchiveGlobalTableDataResponse { + bool success = 1; + int64 archived_version = 2; + string archived_at = 3; + repeated string affected_profiles = 4; + int64 current_version = 5; + int64 row_revision = 6; +} + // Soft-delete a single row. message DeleteTableDataRequest { // Required. Profile (schema) name. @@ -245,6 +286,14 @@ message GetTableDataRequest { // Required. Id of the row to fetch. int64 id = 3; + +} + +message GetGlobalTableDataVersionRequest { + string profile_name = 1; + string table_name = 2; + int64 id = 3; + int64 version = 4; } // Row payload: all columns returned as strings. @@ -264,6 +313,9 @@ message GetTableDataResponse { // identified by its id alone. repeated string row_display_values = 2; repeated string row_display_columns = 3; + + // Version paired with each global-link column in this row. + map link_versions = 4; } // Count non-deleted rows. diff --git a/common/src/grpc_error.rs b/common/src/grpc_error.rs index 0a905dfa..80245001 100644 --- a/common/src/grpc_error.rs +++ b/common/src/grpc_error.rs @@ -2,3 +2,4 @@ pub const ERROR_REASON_METADATA_KEY: &str = "komp-ac-error-reason"; pub const COMPUTED_VALUE_MISMATCH_REASON: &str = "computed-value-mismatch"; pub const ROW_STALE_REASON: &str = "row-stale"; pub const ROW_ID_CONFLICT_REASON: &str = "row-id-conflict"; +pub const GLOBAL_UPDATE_CONFIRMATION_REASON: &str = "global-update-confirmation"; diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 41529625..b102d933 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index 257ebf27..c8c6d77f 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -37,6 +37,10 @@ pub struct PostTableDefinitionRequest { /// to this one when they reach the ledger. #[prost(string, tag = "8")] pub accounting_currency: ::prost::alloc::string::String, + /// When true, the table is stored once in the global physical schema and is + /// visible from every profile. profile_name and accounting_currency are ignored. + #[prost(bool, tag = "9")] + pub global: bool, } /// Defines the input for explicitly creating tables backed by one invoice /// template. typst_source must contain exactly one field declaration: @@ -215,6 +219,9 @@ pub mod profile_tree_response { /// "dynamic" for user-defined tables, "system" for backend-managed tables. #[prost(string, tag = "5")] pub table_kind: ::prost::alloc::string::String, + /// True when this table is shared by every profile. + #[prost(bool, tag = "6")] + pub global: bool, } /// Profile (schema) entry. #[derive(Clone, PartialEq, ::prost::Message)] @@ -324,6 +331,8 @@ pub struct TableDetail { >, #[prost(string, tag = "8")] pub table_kind: ::prost::alloc::string::String, + #[prost(bool, tag = "9")] + pub global: bool, } /// Server-owned behavior for one logical column returned in table details. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] diff --git a/common/src/proto/komp_ac.tables_data.rs b/common/src/proto/komp_ac.tables_data.rs index 627c388a..161d1d8b 100644 --- a/common/src/proto/komp_ac.tables_data.rs +++ b/common/src/proto/komp_ac.tables_data.rs @@ -149,6 +149,46 @@ pub struct PutTableDataResponse { #[prost(int64, tag = "4")] pub row_revision: i64, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GlobalTableUpdateImpactRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + #[prost(int64, tag = "3")] + pub id: i64, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GlobalTableUpdateImpactResponse { + #[prost(string, repeated, tag = "1")] + pub affected_profiles: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ArchiveGlobalTableDataRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + #[prost(int64, tag = "3")] + pub id: i64, + #[prost(int64, tag = "4")] + pub expected_revision: i64, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ArchiveGlobalTableDataResponse { + #[prost(bool, tag = "1")] + pub success: bool, + #[prost(int64, tag = "2")] + pub archived_version: i64, + #[prost(string, tag = "3")] + pub archived_at: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "4")] + pub affected_profiles: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(int64, tag = "5")] + pub current_version: i64, + #[prost(int64, tag = "6")] + pub row_revision: i64, +} /// Soft-delete a single row. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct DeleteTableDataRequest { @@ -185,6 +225,17 @@ pub struct GetTableDataRequest { #[prost(int64, tag = "3")] pub id: i64, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetGlobalTableDataVersionRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + #[prost(int64, tag = "3")] + pub id: i64, + #[prost(int64, tag = "4")] + pub version: i64, +} /// Row payload: all columns returned as strings. #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetTableDataResponse { @@ -209,6 +260,9 @@ pub struct GetTableDataResponse { pub row_display_values: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(string, repeated, tag = "3")] pub row_display_columns: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Version paired with each global-link column in this row. + #[prost(map = "string, int64", tag = "4")] + pub link_versions: ::std::collections::HashMap<::prost::alloc::string::String, i64>, } /// Count non-deleted rows. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -443,6 +497,99 @@ pub mod tables_data_client { ); self.inner.unary(req, path, codec).await } + /// Performs a PUT after the user explicitly accepted its cross-profile impact. + /// This is meaningful only for global tables; ordinary tables behave exactly + /// like PutTableData. + pub async fn put_table_data_confirmed( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.tables_data.TablesData/PutTableDataConfirmed", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.tables_data.TablesData", + "PutTableDataConfirmed", + ), + ); + self.inner.unary(req, path, codec).await + } + /// Lists the other profiles whose rows currently point at this global row. + pub async fn get_global_table_update_impact( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.tables_data.TablesData/GetGlobalTableUpdateImpact", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.tables_data.TablesData", + "GetGlobalTableUpdateImpact", + ), + ); + self.inner.unary(req, path, codec).await + } + /// Snapshots the current version of a global row and advances its version. + /// Existing references remain immutable; new references use the new version. + pub async fn archive_global_table_data( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.tables_data.TablesData/ArchiveGlobalTableData", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.tables_data.TablesData", + "ArchiveGlobalTableData", + ), + ); + self.inner.unary(req, path, codec).await + } /// Soft-delete a single record (sets deleted = true) if it exists and is not already deleted. /// /// Behavior: @@ -514,6 +661,36 @@ pub mod tables_data_client { ); self.inner.unary(req, path, codec).await } + /// Fetches one exact version of a global row. + pub async fn get_global_table_data_version( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.tables_data.TablesData/GetGlobalTableDataVersion", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.tables_data.TablesData", + "GetGlobalTableDataVersion", + ), + ); + self.inner.unary(req, path, codec).await + } /// Count non-deleted rows in a table. /// /// Behavior: @@ -655,6 +832,33 @@ pub mod tables_data_server { tonic::Response, tonic::Status, >; + /// Performs a PUT after the user explicitly accepted its cross-profile impact. + /// This is meaningful only for global tables; ordinary tables behave exactly + /// like PutTableData. + async fn put_table_data_confirmed( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Lists the other profiles whose rows currently point at this global row. + async fn get_global_table_update_impact( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Snapshots the current version of a global row and advances its version. + /// Existing references remain immutable; new references use the new version. + async fn archive_global_table_data( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Soft-delete a single record (sets deleted = true) if it exists and is not already deleted. /// /// Behavior: @@ -688,6 +892,14 @@ pub mod tables_data_server { tonic::Response, tonic::Status, >; + /// Fetches one exact version of a global row. + async fn get_global_table_data_version( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Count non-deleted rows in a table. /// /// Behavior: @@ -934,6 +1146,152 @@ pub mod tables_data_server { }; Box::pin(fut) } + "/komp_ac.tables_data.TablesData/PutTableDataConfirmed" => { + #[allow(non_camel_case_types)] + struct PutTableDataConfirmedSvc(pub Arc); + impl< + T: TablesData, + > tonic::server::UnaryService + for PutTableDataConfirmedSvc { + type Response = super::PutTableDataResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::put_table_data_confirmed(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = PutTableDataConfirmedSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.tables_data.TablesData/GetGlobalTableUpdateImpact" => { + #[allow(non_camel_case_types)] + struct GetGlobalTableUpdateImpactSvc(pub Arc); + impl< + T: TablesData, + > tonic::server::UnaryService + for GetGlobalTableUpdateImpactSvc { + type Response = super::GlobalTableUpdateImpactResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::GlobalTableUpdateImpactRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_global_table_update_impact( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetGlobalTableUpdateImpactSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.tables_data.TablesData/ArchiveGlobalTableData" => { + #[allow(non_camel_case_types)] + struct ArchiveGlobalTableDataSvc(pub Arc); + impl< + T: TablesData, + > tonic::server::UnaryService + for ArchiveGlobalTableDataSvc { + type Response = super::ArchiveGlobalTableDataResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::archive_global_table_data( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ArchiveGlobalTableDataSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/komp_ac.tables_data.TablesData/DeleteTableData" => { #[allow(non_camel_case_types)] struct DeleteTableDataSvc(pub Arc); @@ -1024,6 +1382,58 @@ pub mod tables_data_server { }; Box::pin(fut) } + "/komp_ac.tables_data.TablesData/GetGlobalTableDataVersion" => { + #[allow(non_camel_case_types)] + struct GetGlobalTableDataVersionSvc(pub Arc); + impl< + T: TablesData, + > tonic::server::UnaryService< + super::GetGlobalTableDataVersionRequest, + > for GetGlobalTableDataVersionSvc { + type Response = super::GetTableDataResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::GetGlobalTableDataVersionRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_global_table_data_version( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetGlobalTableDataVersionSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/komp_ac.tables_data.TablesData/GetTableDataCount" => { #[allow(non_camel_case_types)] struct GetTableDataCountSvc(pub Arc); diff --git a/server b/server index 3bf72f51..ac0c12a7 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 3bf72f51a61614837c1a1703afca42e334c78511 +Subproject commit ac0c12a7ac86887fbaaefb2915d0b08ddd4493bb diff --git a/web/src/pages/add_table/draft.rs b/web/src/pages/add_table/draft.rs index 863ffc52..84320c73 100644 --- a/web/src/pages/add_table/draft.rs +++ b/web/src/pages/add_table/draft.rs @@ -35,6 +35,7 @@ pub(crate) struct TableDraft { /// Profile name typed in when creating a new profile. pub profile_name_input: String, pub creating_new_profile: bool, + pub global: bool, pub accounting_currency: String, pub table_name: String, @@ -69,11 +70,11 @@ impl TableDraft { // ---- field visibility (the same rules the TUI canvas applies) -------- pub(crate) fn show_profile_name_input(&self) -> bool { - self.creating_new_profile + self.creating_new_profile && !self.global } pub(crate) fn show_accounting_currency(&self) -> bool { - self.creating_new_profile + self.creating_new_profile && !self.global } // ---- mutations ------------------------------------------------------- @@ -211,10 +212,10 @@ impl TableDraft { /// Every check the client runs before it will save. pub(crate) fn validate(&self) -> Result<(), String> { let profile_name = self.effective_profile_name(); - if self.creating_new_profile && profile_name.is_empty() { + if !self.global && self.creating_new_profile && profile_name.is_empty() { return Err("Enter a name for the new profile.".to_string()); } - if let Some(error) = validate_identifier(&profile_name, "Profile name", false) { + if !self.global && let Some(error) = validate_identifier(&profile_name, "Profile name", false) { return Err(error); } if let Some(error) = validate_accounting_currency(self) { @@ -244,18 +245,19 @@ impl TableDraft { profile_name: self.effective_profile_name(), columns: proto_columns(&self.columns.added), indexes: self.columns.selected_index_names(), - accounting_currency: if self.creating_new_profile { + accounting_currency: if self.creating_new_profile && !self.global { self.accounting_currency.trim().to_ascii_uppercase() } else { String::new() }, row_display_columns: self.row_display_columns.clone(), + global: self.global, }) } } pub(crate) fn validate_accounting_currency(draft: &TableDraft) -> Option { - if !draft.creating_new_profile { + if !draft.creating_new_profile || draft.global { return None; } let currency = draft.accounting_currency.to_ascii_uppercase(); diff --git a/web/src/pages/add_table/loader.rs b/web/src/pages/add_table/loader.rs index 2f3fe748..5d348f50 100644 --- a/web/src/pages/add_table/loader.rs +++ b/web/src/pages/add_table/loader.rs @@ -60,7 +60,26 @@ pub(crate) async fn load_page( .into_inner(); let effective_profile = draft.effective_profile_name(); - match tree + if draft.global { + let global_tables = tree + .profiles + .iter() + .flat_map(|profile| profile.tables.iter()) + .filter(|table| table.global) + .map(|table| table.name.clone()) + .collect::>() + .into_iter() + .collect::>(); + draft.existing_profile_tables = tree + .profiles + .iter() + .flat_map(|profile| profile.tables.iter()) + .map(|table| table.name.clone()) + .collect::>() + .into_iter() + .collect(); + draft.set_available_relation_tables(global_tables); + } else { match tree .profiles .iter() .find(|profile| profile.name == effective_profile) @@ -82,7 +101,7 @@ pub(crate) async fn load_page( draft.existing_profile_tables.clear(); draft.relation_tables.clear(); } - } + }} Ok(AddTablePageState { nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization), diff --git a/web/src/pages/add_table/logic.rs b/web/src/pages/add_table/logic.rs index 3a08caa3..1ab99ef9 100644 --- a/web/src/pages/add_table/logic.rs +++ b/web/src/pages/add_table/logic.rs @@ -98,7 +98,8 @@ pub(crate) async fn create_table( }; let mut definitions = state.definitions; - match definitions.post_table_definition(request).await { + let result = definitions.post_table_definition(request).await; + match result { Ok(response) if response.get_ref().success => { let location = format!( "/admin/table-definition?profile={profile_name}&table={}", diff --git a/web/src/pages/add_table/state.rs b/web/src/pages/add_table/state.rs index 7e5d1aa6..1ec2e174 100644 --- a/web/src/pages/add_table/state.rs +++ b/web/src/pages/add_table/state.rs @@ -34,6 +34,8 @@ pub(crate) struct BuilderForm { #[serde(default)] pub accounting_currency: String, #[serde(default)] + pub global: bool, + #[serde(default)] pub table_name: String, // The pending column being described in the input panel. @@ -134,6 +136,7 @@ impl BuilderForm { profile_name_input: self.profile_name_input.clone(), creating_new_profile, accounting_currency: self.accounting_currency.clone(), + global: self.global, table_name: self.table_name.clone(), columns, relation_tables: self.relation_tables.clone(), diff --git a/web/templates/pages/add_table/builder.html b/web/templates/pages/add_table/builder.html index 235b3ee8..b7c6855a 100644 --- a/web/templates/pages/add_table/builder.html +++ b/web/templates/pages/add_table/builder.html @@ -24,6 +24,15 @@

Table

+ + + {% if !page.draft.global %} + {% else %} + + {% endif %} {% if page.draft.show_profile_name_input() %}