From ac4126b2af2b381f7ccace5727c0bb079e7beb25 Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Tue, 14 Oct 2025 17:59:56 -0700 Subject: [PATCH 01/45] #Added support for action buttons in the table component template. * A default edit and delete button can be included by specifying an "edit_url" or "delete_url" * Custom Column based action buttons can be added in the table header using json object array in the custom_actions column. * Custom action buttons can be defined on the row level by specifying _sqlpage_actions. --- .../sqlpage/migrations/01_documentation.sql | 168 +++++++++++++++++- sqlpage/templates/table.handlebars | 45 +++++ 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index ee2b8b51..c9630fbf 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -810,11 +810,15 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S ('money', 'Name of a numeric column whose values should be displayed as currency amounts, in the currency defined by the `currency` property. This argument can be repeated multiple times.', 'TEXT', TRUE, TRUE), ('currency', 'The ISO 4217 currency code (e.g., USD, EUR, GBP, etc.) to use when formatting monetary values.', 'TEXT', TRUE, TRUE), ('number_format_digits', 'Maximum number of decimal digits to display for numeric values.', 'INTEGER', TRUE, TRUE), + ('edit_url', 'If set, an edit button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the edit button will take the user to that URL.', 'TEXT', TRUE, TRUE), + ('delete_url', 'If set, a delete button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the delete button will take the user to that URL.', 'TEXT', TRUE, TRUE), + ('custom_actions', 'If set, a column of custom action buttons will be added to each row. The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button).', 'JSON', TRUE, TRUE), -- row level ('_sqlpage_css_class', 'For advanced users. Sets a css class on the table row. Added in v0.8.0.', 'TEXT', FALSE, TRUE), ('_sqlpage_color', 'Sets the background color of the row. Added in v0.8.0.', 'COLOR', FALSE, TRUE), ('_sqlpage_footer', 'Sets this row as the table footer. It is recommended that this parameter is applied to the last row. Added in v0.34.0.', 'BOOLEAN', FALSE, TRUE), - ('_sqlpage_id', 'Sets the id of the html tabler row element. Allows you to make links targeting a specific row in a table.', 'TEXT', FALSE, TRUE) + ('_sqlpage_id', 'Sets the id of the html tabler row element. Allows you to make links targeting a specific row in a table.', 'TEXT', FALSE, TRUE), + ('_sqlpage_actions', 'Sets custom action buttons for this specific row in addition to any defined at the table level, The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button).', 'JSON', FALSE, TRUE) ) x; INSERT INTO example(component, description, properties) VALUES @@ -994,9 +998,171 @@ GROUP BY This will generate a table with the stores in the first column, and the items in the following columns, with the quantity sold in each store for each item. ', NULL + ), + ( + 'table', +'# Using row based custom actions in a table + +The table has a column of buttons, each button defined by the `_sqlpage_actions` column at the table level, and by the `_sqlpage_actions` property at the row level. + +```sql + SELECT + name, vendor, product_number, facility_name, + lot_number, status, date_of_expiration, + --Use the unique identifier of the row as the _sqlpage_id property + standard_id AS _sqlpage_id, + --Build an array of objects, each object defining a button with the following properties: name, icon, link, tooltip + json_array(--SQLite specific, refer to your database documentation for the equivalent JSON functions + --The {id} placeholder in the link property will be replaced by the value of the _sqlpage_id property for that row. + json_object(''name'', ''history'', ''tooltip'', ''View Standard History'', ''link'', ''./history.sql?standard_id={id}'', ''icon'', ''history''), + json_object(''name'', ''view_coa'', ''tooltip'', ''View Certificate of Analysis'', ''link'', c_of_a_path, ''icon'', ''file-type-pdf''), + json_object(''name'', ''edit'', ''tooltip'', ''Edit Standard'', ''link'', ''./update.sql?id={id}'', ''icon'', ''pencil''), + --We want different actions based on the status of the standard, so we use a CASE statement to build the appropriate action + CASE + WHEN status = ''Available'' THEN json_object( + ''name'',''Action'', + ''tooltip'',''Set In Use'', + ''link'',''./actions/set_in_use.sql?standard_id='' || standard_id, + ''icon'',''caret-right'' + ) + WHEN status = ''In Use'' THEN json_object( + ''name'',''Action'', + ''tooltip'',''Retire Standard'', + ''link'',''./actions/retire.sql?standard_id='' || standard_id, + ''icon'',''test-pipe-off'' + ) + WHEN status = ''Retired'' THEN json_object( + ''name'',''Action'', + ''tooltip'',''Discard Standard'', + ''link'',''./actions/discard.sql?standard_id='' || standard_id, + ''icon'',''flask-off'' + ) + -- Include an action with no link or icon as a placeholder to keep the buttons aligned and make sure the header is correct. + WHEN status = ''Discarded'' THEN json_object(''name'',''Action'') + + ELSE json_object(''name'',''Action'') + END + ) + AS _sqlpage_actions + FROM standard; + + ``` + + + ' + , + json('[ + { + "component": "table" + }, + { + "name": "CalStd", + "vendor": "PharmaCo", + "product_number": "P1234", + "facility_name": "A Plant", + "lot_number": "T23523", + "status": "Available", + "date_of_expiration": "2026-10-13", + "_sqlpage_id": 32, + "_sqlpage_actions": [ + { + "name": "history", + "tooltip": "View Standard History", + "link": "./history.sql?standard_id={id}", + "icon": "history" + }, + { + "name": "view_coa", + "tooltip": "View Certificate of Analysis", + "link": "/c_of_a\\2025-09-30_22h01m21s_B69baKoz.pdf", + "icon": "file-type-pdf" + }, + { + "name": "edit", + "tooltip": "Edit Standard", + "link": "./update.sql?id={id}", + "icon": "pencil" + }, + { + "name": "Action", + "tooltip": "Set In Use", + "link": "./actions/set_in_use.sql?standard_id=32", + "icon": "caret-right" + } + ] + }, + { + "name": "CalStd", + "vendor": "PharmaCo", + "product_number": "P1234", + "facility_name": "A Plant", + "lot_number": "T2352", + "status": "In Use", + "date_of_expiration": "2026-10-14", + "_sqlpage_id": 33, + "_sqlpage_actions": [ + { + "name": "history", + "tooltip": "View Standard History", + "link": "./history.sql?standard_id={id}", + "icon": "history" + }, + { + "name": "view_coa", + "tooltip": "View Certificate of Analysis", + "link": "/c_of_a\\2025-09-30_22h05m13s_cP7gqMyi.pdf", + "icon": "file-type-pdf" + }, + { + "name": "edit", + "tooltip": "Edit Standard", + "link": "./update.sql?id={id}", + "icon": "pencil" + }, + { + "name": "Action", + "tooltip": "Retire Standard", + "link": "./actions/retire.sql?standard_id=33", + "icon": "test-pipe-off" + } + ] + }, + { + "name": "CalStd", + "vendor": "PharmaCo", + "product_number": "P1234", + "facility_name": "A Plant", + "lot_number": "A123", + "status": "Discarded", + "date_of_expiration": "2026-09-30", + "_sqlpage_id": 31, + "_sqlpage_actions": [ + { + "name": "history", + "tooltip": "View Standard History", + "link": "./history.sql?standard_id={id}", + "icon": "history" + }, + { + "name": "view_coa", + "tooltip": "View Certificate of Analysis", + "link": "#", + "icon": "file-type-pdf" + }, + { + "name": "edit", + "tooltip": "Edit Standard", + "link": "./update.sql?id={id}", + "icon": "pencil" + }, + null + ] + } +]') ); + INSERT INTO component(name, icon, description) VALUES ('csv', 'download', 'Lets the user download data as a CSV file. Each column from the items in the component will map to a column in the resulting CSV. diff --git a/sqlpage/templates/table.handlebars b/sqlpage/templates/table.handlebars index 90958dfe..639f9341 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -3,6 +3,7 @@ {{#if (or search initial_search_value)}}
{{/if}} {{/each}} + {{#if ../edit_url}}Edit{{/if}} + {{#if ../delete_url}}Delete{{/if}} + {{#if ../custom_actions}} + {{#each ../custom_actions}} + {{this.name}} + {{/each}} + {{/if}} + {{#if _sqlpage_actions}} + {{#each _sqlpage_actions}} + {{this.name}} + {{/each}} + {{/if}} {{#delay}}{{/delay}} @@ -78,6 +91,38 @@ {{/if~}} {{~/each~}} + {{#if ../edit_url}} + + + {{~icon_img 'edit'~}} + + + {{/if}} + {{#if ../delete_url}} + + + {{~icon_img 'trash'~}} + + + {{/if}} + {{#if ../custom_actions}} + {{#each ../custom_actions}} + + {{!Title property sets the tooltip text}} + {{~icon_img this.icon~}} + + + {{/each}} + {{/if}} + {{#if _sqlpage_actions}} + {{#each _sqlpage_actions}} + + + {{~icon_img this.icon~}} + + + {{/each}} + {{/if}} {{!~ After this has been rendered, if this was a footer, we need to reopen a new From be67d3e4ae990f7a2efb5b7b8676153391b5c528 Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Tue, 14 Oct 2025 19:17:56 -0700 Subject: [PATCH 02/45] Modified new table example. --- .../sqlpage/migrations/01_documentation.sql | 135 ++++++------------ 1 file changed, 45 insertions(+), 90 deletions(-) diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index c9630fbf..b9fae3d9 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -1001,88 +1001,66 @@ This will generate a table with the stores in the first column, and the items in ), ( 'table', -'# Using row based custom actions in a table +'## Using Action Buttons in a table. -The table has a column of buttons, each button defined by the `_sqlpage_actions` column at the table level, and by the `_sqlpage_actions` property at the row level. +### Preset Actions: `edit_url` & `delete_url` +Since edit and delete are common actions, the `table` component has dedicated `edit_url` and `delete_url` properties to add buttons for these actions. +The value of these properties should be a URL, containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. -```sql - SELECT - name, vendor, product_number, facility_name, - lot_number, status, date_of_expiration, - --Use the unique identifier of the row as the _sqlpage_id property - standard_id AS _sqlpage_id, - --Build an array of objects, each object defining a button with the following properties: name, icon, link, tooltip - json_array(--SQLite specific, refer to your database documentation for the equivalent JSON functions - --The {id} placeholder in the link property will be replaced by the value of the _sqlpage_id property for that row. - json_object(''name'', ''history'', ''tooltip'', ''View Standard History'', ''link'', ''./history.sql?standard_id={id}'', ''icon'', ''history''), - json_object(''name'', ''view_coa'', ''tooltip'', ''View Certificate of Analysis'', ''link'', c_of_a_path, ''icon'', ''file-type-pdf''), - json_object(''name'', ''edit'', ''tooltip'', ''Edit Standard'', ''link'', ''./update.sql?id={id}'', ''icon'', ''pencil''), - --We want different actions based on the status of the standard, so we use a CASE statement to build the appropriate action - CASE - WHEN status = ''Available'' THEN json_object( - ''name'',''Action'', - ''tooltip'',''Set In Use'', - ''link'',''./actions/set_in_use.sql?standard_id='' || standard_id, - ''icon'',''caret-right'' - ) - WHEN status = ''In Use'' THEN json_object( - ''name'',''Action'', - ''tooltip'',''Retire Standard'', - ''link'',''./actions/retire.sql?standard_id='' || standard_id, - ''icon'',''test-pipe-off'' - ) - WHEN status = ''Retired'' THEN json_object( - ''name'',''Action'', - ''tooltip'',''Discard Standard'', - ''link'',''./actions/discard.sql?standard_id='' || standard_id, - ''icon'',''flask-off'' - ) - -- Include an action with no link or icon as a placeholder to keep the buttons aligned and make sure the header is correct. - WHEN status = ''Discarded'' THEN json_object(''name'',''Action'') - - ELSE json_object(''name'',''Action'') - END - ) - AS _sqlpage_actions - FROM standard; - - ``` +### Column with fixed action buttons +You may want to add custom action buttons to your table rows, for instance to view details, download a file, or perform a custom operation. +For this, the `table` component has a `custom_actions` property that lets you define a column of buttons, each button defined by a name, an icon, a link, and an optional tooltip. - ' +### Column with variable action buttons + +The `table` component also supports the row level `_sqlpage_actions` column in your data table. +This is helpful if you want a more complex logic, for instance to disable a button on some rows, or to change the link or icon based on the row data. + +> WARNING! +> If the number of array items in `_sqlpage_actions` is not consistent across all rows, the table may not render correctly. +> You can leave blank spaces by including an object with only the `name` property. + +The table has a column of buttons, each button defined by the `_sqlpage_actions` column at the table level, and by the `_sqlpage_actions` property at the row level. +### `custom_actions` & `_sqlpage_actions` JSON properties. +Each button is defined by the following properties: +* `name`: sets the column header and the tooltip if no tooltip is provided, +* `tooltip`: text to display when hovering over the button, +* `link`: the URL to navigate to when the button is clicked, possibly containing the {id} placeholder that will be replaced by the value of the `_sqlpage_id` property for that row, +* `icon`: the tabler icon name or image link to display on the button + +### Example using all of the above +' , json('[ { - "component": "table" + "component": "table", + "edit_url": "./update.sql?id={id}", + "delete_url": "./delete.sql?id={id}", + "custom_actions": [ + { + "name": "history", + "tooltip": "View Standard History", + "link": "./history.sql?standard_id={id}", + "icon": "history" + } + ] }, { "name": "CalStd", "vendor": "PharmaCo", "product_number": "P1234", - "facility_name": "A Plant", "lot_number": "T23523", "status": "Available", "date_of_expiration": "2026-10-13", "_sqlpage_id": 32, "_sqlpage_actions": [ - { - "name": "history", - "tooltip": "View Standard History", - "link": "./history.sql?standard_id={id}", - "icon": "history" - }, { "name": "view_coa", "tooltip": "View Certificate of Analysis", - "link": "/c_of_a\\2025-09-30_22h01m21s_B69baKoz.pdf", + "link": "/c_of_a/2025-09-30_22h01m21s_B69baKoz.pdf", "icon": "file-type-pdf" }, - { - "name": "edit", - "tooltip": "Edit Standard", - "link": "./update.sql?id={id}", - "icon": "pencil" - }, { "name": "Action", "tooltip": "Set In Use", @@ -1095,30 +1073,17 @@ The table has a column of buttons, each button defined by the `_sqlpage_actions` "name": "CalStd", "vendor": "PharmaCo", "product_number": "P1234", - "facility_name": "A Plant", "lot_number": "T2352", "status": "In Use", "date_of_expiration": "2026-10-14", "_sqlpage_id": 33, "_sqlpage_actions": [ - { - "name": "history", - "tooltip": "View Standard History", - "link": "./history.sql?standard_id={id}", - "icon": "history" - }, { "name": "view_coa", "tooltip": "View Certificate of Analysis", - "link": "/c_of_a\\2025-09-30_22h05m13s_cP7gqMyi.pdf", + "link": "/c_of_a/2025-09-30_22h05m13s_cP7gqMyi.pdf", "icon": "file-type-pdf" }, - { - "name": "edit", - "tooltip": "Edit Standard", - "link": "./update.sql?id={id}", - "icon": "pencil" - }, { "name": "Action", "tooltip": "Retire Standard", @@ -1131,35 +1096,25 @@ The table has a column of buttons, each button defined by the `_sqlpage_actions` "name": "CalStd", "vendor": "PharmaCo", "product_number": "P1234", - "facility_name": "A Plant", "lot_number": "A123", "status": "Discarded", "date_of_expiration": "2026-09-30", "_sqlpage_id": 31, "_sqlpage_actions": [ - { - "name": "history", - "tooltip": "View Standard History", - "link": "./history.sql?standard_id={id}", - "icon": "history" - }, { "name": "view_coa", "tooltip": "View Certificate of Analysis", - "link": "#", + "link": "025-09-30_22h01m21s_B439baKoz.pdf", "icon": "file-type-pdf" }, { - "name": "edit", - "tooltip": "Edit Standard", - "link": "./update.sql?id={id}", - "icon": "pencil" - }, - null + "name": "Action" + } ] } -]') - ); +]' +) +); From 3654d4272bdd1b7ff0d559327b498f026fda3361 Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Tue, 14 Oct 2025 19:43:02 -0700 Subject: [PATCH 03/45] Updated class settings in table.handlebars so that action buttons are centered, and _col_{name} is included in new rows. --- sqlpage/templates/table.handlebars | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sqlpage/templates/table.handlebars b/sqlpage/templates/table.handlebars index 639f9341..bf1cd3e4 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -53,16 +53,16 @@ {{/if}} {{/each}} - {{#if ../edit_url}}Edit{{/if}} - {{#if ../delete_url}}Delete{{/if}} + {{#if ../edit_url}}Edit{{/if}} + {{#if ../delete_url}}Delete{{/if}} {{#if ../custom_actions}} {{#each ../custom_actions}} - {{this.name}} + {{this.name}} {{/each}} {{/if}} {{#if _sqlpage_actions}} {{#each _sqlpage_actions}} - {{this.name}} + {{this.name}} {{/each}} {{/if}} @@ -92,14 +92,14 @@ {{/if~}} {{~/each~}} {{#if ../edit_url}} - + {{~icon_img 'edit'~}} {{/if}} {{#if ../delete_url}} - + {{~icon_img 'trash'~}} @@ -107,8 +107,8 @@ {{/if}} {{#if ../custom_actions}} {{#each ../custom_actions}} - - {{!Title property sets the tooltip text}} + + {{!Title property sets the tooltip text}} {{~icon_img this.icon~}} @@ -116,8 +116,8 @@ {{/if}} {{#if _sqlpage_actions}} {{#each _sqlpage_actions}} - - + + {{~icon_img this.icon~}} From 83be28bd60d5a94940c5357b9e74628ad7bb13a8 Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Tue, 14 Oct 2025 20:23:19 -0700 Subject: [PATCH 04/45] Eliminate clippy redundant else error from src/webserver/http.rs cargo clippy Checking sqlpage v0.38.0 error: redundant else block --> src\webserver\http.rs:493:6 | 493 | } else { | ______^ 494 | | if let Some(domain) = &config.https_domain { 495 | | let mut listen_on_https = listen_on; 496 | | listen_on_https.set_port(443); ... | 511 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else note: the lint level is defined here --> src\lib.rs:1:9 | 1 | #![deny(clippy::pedantic)] | ^^^^^^^^^^^^^^^^ = note: `#[deny(clippy::redundant_else)]` implied by `#[deny(clippy::pedantic)]` help: remove the `else` block and move the contents out | 493 ~ } 494 + if let Some(domain) = &config.https_domain { 495 + let mut listen_on_https = listen_on; 496 + listen_on_https.set_port(443); 497 + log::debug!("Will start HTTPS server on {listen_on_https}"); 498 + let config = make_auto_rustls_config(domain, config); 499 + server = server 500 + .bind_rustls_0_23(listen_on_https, config) 501 + .map_err(|e| bind_error(e, listen_on_https))?; 502 + } else if listen_on.port() == 443 { 503 + bail!("Please specify a value for https_domain in the configuration file. This is required when using HTTPS (port 443)"); 504 + } 505 + if listen_on.port() != 443 { 506 + log::debug!("Will start HTTP server on {listen_on}"); 507 + server = server 508 + .bind(listen_on) 509 + .map_err(|e| bind_error(e, listen_on))?; 510 + } | error: could not compile `sqlpage` (lib) due to 1 previous error --- src/webserver/http.rs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/webserver/http.rs b/src/webserver/http.rs index 88c9966e..6f163a90 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -490,24 +490,23 @@ pub async fn run_server(config: &AppConfig, state: AppState) -> anyhow::Result<( } #[cfg(not(target_family = "unix"))] anyhow::bail!("Unix sockets are not supported on your operating system. Use listen_on instead of unix_socket."); - } else { - if let Some(domain) = &config.https_domain { - let mut listen_on_https = listen_on; - listen_on_https.set_port(443); - log::debug!("Will start HTTPS server on {listen_on_https}"); - let config = make_auto_rustls_config(domain, config); - server = server - .bind_rustls_0_23(listen_on_https, config) - .map_err(|e| bind_error(e, listen_on_https))?; - } else if listen_on.port() == 443 { - bail!("Please specify a value for https_domain in the configuration file. This is required when using HTTPS (port 443)"); - } - if listen_on.port() != 443 { - log::debug!("Will start HTTP server on {listen_on}"); - server = server - .bind(listen_on) - .map_err(|e| bind_error(e, listen_on))?; - } + } + if let Some(domain) = &config.https_domain { + let mut listen_on_https = listen_on; + listen_on_https.set_port(443); + log::debug!("Will start HTTPS server on {listen_on_https}"); + let config = make_auto_rustls_config(domain, config); + server = server + .bind_rustls_0_23(listen_on_https, config) + .map_err(|e| bind_error(e, listen_on_https))?; + } else if listen_on.port() == 443 { + bail!("Please specify a value for https_domain in the configuration file. This is required when using HTTPS (port 443)"); + } + if listen_on.port() != 443 { + log::debug!("Will start HTTP server on {listen_on}"); + server = server + .bind(listen_on) + .map_err(|e| bind_error(e, listen_on))?; } log_welcome_message(config); From 5d5d1f94d78248283d35035f8a5f12dcb852694d Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Sat, 18 Oct 2025 12:19:15 +0200 Subject: [PATCH 05/45] The login component is an authentication form for users of an application. --- .../sqlpage/migrations/68_login.sql | 52 +++++++++++ .../99_shared_id_class_attributes.sql | 6 +- sqlpage/templates/login.handlebars | 89 +++++++++++++++++++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 examples/official-site/sqlpage/migrations/68_login.sql create mode 100644 sqlpage/templates/login.handlebars diff --git a/examples/official-site/sqlpage/migrations/68_login.sql b/examples/official-site/sqlpage/migrations/68_login.sql new file mode 100644 index 00000000..7230f759 --- /dev/null +++ b/examples/official-site/sqlpage/migrations/68_login.sql @@ -0,0 +1,52 @@ +INSERT INTO component(name, icon, description, introduced_in_version) VALUES + ('login', 'password-user', ' +The login component is an authentication form for users of an application. + +It allows the entry of a user account consisting of a username and a password. + +It offers additional features such as the ability to request session persistence or to reset the password.', '0.39.0'); + +INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'login', * FROM (VALUES + ('title','Title of the authentication form.','TEXT',TRUE,TRUE), + ('enctype','Form data encoding.','TEXT',TRUE,TRUE), + ('action','An optional link to a target page that will handle the results of the form. ','TEXT',TRUE,TRUE), + ('username','User account identifier.','TEXT',TRUE,FALSE), + ('password','User password.','TEXT',TRUE,FALSE), + ('image','The URL of an centered image displayed before the title.','URL',TRUE,TRUE), + ('forgot_password_text','A text for the link allowing the user to reset their password. If the text is empty, the link is not displayed.','TEXT',TRUE,TRUE), + ('forgot_password_link','The link to the page allowing the user to reset their password.','TEXT',TRUE,TRUE), + ('remember_me_text','A text for the option allowing the user to request the preservation of their work session. The name of the field is remember. If the text is empty, the option is not displayed.','TEXT',TRUE,TRUE), + ('footer','A text placed at the bottom of the authentication form.','TEXT',TRUE,TRUE), + ('footer_md','A markdown text placed at the bottom of the authentication form. Useful for creating links to other pages (creating a new account, contacting technical support, etc.).','TEXT',TRUE,TRUE), + ('validate','The text to display in the button at the bottom of the form that submits the values.','TEXT',TRUE,FALSE), + ('validate_color','The color of the button at the bottom of the form that submits the values. Omit this property to use the default color.','COLOR',TRUE,TRUE), + ('validate_shape','The shape of the validation button.','TEXT',TRUE,TRUE), + ('validate_outline','A color to outline the validation button.','COLOR',TRUE,TRUE), + ('validate_size','The size of the validation button.','TEXT',TRUE,TRUE) +) x; + +-- Insert example(s) for the component +INSERT INTO example(component, description, properties) +VALUES ( + 'login', + 'Using the main options of the login component', + JSON( + '[ + { + "component": "login", + "action": "login.sql", + "image": "../assets/icon.webp", + "title": "Please login to your account", + "username": "Username", + "password": "Password", + "forgot_password_text": "Forgot your password?", + "forgot_password_link": "reset_password.sql", + "remember_me_text": "Remember me", + "footer_md": "Don''t have an account? [Register here](register.sql)", + "validate": "Sign in" + } + ]' + ) + ); + +-- 265707.png \ No newline at end of file diff --git a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql index 47e66576..df054541 100644 --- a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql +++ b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql @@ -18,7 +18,8 @@ FROM (VALUES ('title', TRUE), ('tracking', TRUE), ('text', TRUE), - ('carousel', TRUE) + ('carousel', TRUE), + ('login', TRUE) ); INSERT INTO parameter(component, top_level, name, description, type, optional) @@ -49,6 +50,7 @@ FROM (VALUES ('timeline', FALSE), ('title', TRUE), ('tracking', TRUE), - ('carousel', TRUE) + ('carousel', TRUE), + ('login', TRUE) ); diff --git a/sqlpage/templates/login.handlebars b/sqlpage/templates/login.handlebars new file mode 100644 index 00000000..f292a18d --- /dev/null +++ b/sqlpage/templates/login.handlebars @@ -0,0 +1,89 @@ +
+
+
+
+ {{#if image}} +
+ +
+ {{/if}} + {{#if title}} +

{{title}}

+ {{/if}} + +
+ + + + + + + + +
+ +
+ + + + + + + + + +
+ {{#if remember_me_text}} + + {{/if}} +
+ +
+ {{#if (or footer footer_md)}} +
+
+ + {{#if footer}} + {{footer}} + {{else}} + {{#if footer_md}} + {{{markdown footer_md}}} + {{/if}} + {{/if}} + +
+ {{/if}} +
+
+
+
From 6729e10a01fc442eb981490440c564c50497f755 Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Mon, 20 Oct 2025 09:31:36 -0700 Subject: [PATCH 06/45] Added sqlpage version number information to the new components. --- .../official-site/sqlpage/migrations/01_documentation.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index b9fae3d9..410dfe2f 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -810,15 +810,15 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S ('money', 'Name of a numeric column whose values should be displayed as currency amounts, in the currency defined by the `currency` property. This argument can be repeated multiple times.', 'TEXT', TRUE, TRUE), ('currency', 'The ISO 4217 currency code (e.g., USD, EUR, GBP, etc.) to use when formatting monetary values.', 'TEXT', TRUE, TRUE), ('number_format_digits', 'Maximum number of decimal digits to display for numeric values.', 'INTEGER', TRUE, TRUE), - ('edit_url', 'If set, an edit button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the edit button will take the user to that URL.', 'TEXT', TRUE, TRUE), - ('delete_url', 'If set, a delete button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the delete button will take the user to that URL.', 'TEXT', TRUE, TRUE), - ('custom_actions', 'If set, a column of custom action buttons will be added to each row. The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button).', 'JSON', TRUE, TRUE), + ('edit_url', 'If set, an edit button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the edit button will take the user to that URL. Added in v0.39.0', 'TEXT', TRUE, TRUE), + ('delete_url', 'If set, a delete button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the delete button will take the user to that URL. Added in v0.39.0', 'TEXT', TRUE, TRUE), + ('custom_actions', 'If set, a column of custom action buttons will be added to each row. The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button). Added in v0.39.0', 'JSON', TRUE, TRUE), -- row level ('_sqlpage_css_class', 'For advanced users. Sets a css class on the table row. Added in v0.8.0.', 'TEXT', FALSE, TRUE), ('_sqlpage_color', 'Sets the background color of the row. Added in v0.8.0.', 'COLOR', FALSE, TRUE), ('_sqlpage_footer', 'Sets this row as the table footer. It is recommended that this parameter is applied to the last row. Added in v0.34.0.', 'BOOLEAN', FALSE, TRUE), ('_sqlpage_id', 'Sets the id of the html tabler row element. Allows you to make links targeting a specific row in a table.', 'TEXT', FALSE, TRUE), - ('_sqlpage_actions', 'Sets custom action buttons for this specific row in addition to any defined at the table level, The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button).', 'JSON', FALSE, TRUE) + ('_sqlpage_actions', 'Sets custom action buttons for this specific row in addition to any defined at the table level, The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button). Added in v0.39.0', 'JSON', FALSE, TRUE) ) x; INSERT INTO example(component, description, properties) VALUES From 5d9df45231ab3a68e847826fcee1c30763b320ca Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Mon, 20 Oct 2025 09:53:10 -0700 Subject: [PATCH 07/45] Undo changes to http.rs since it is not part of the feature. --- src/webserver/http.rs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/webserver/http.rs b/src/webserver/http.rs index 88c9966e..6f163a90 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -490,24 +490,23 @@ pub async fn run_server(config: &AppConfig, state: AppState) -> anyhow::Result<( } #[cfg(not(target_family = "unix"))] anyhow::bail!("Unix sockets are not supported on your operating system. Use listen_on instead of unix_socket."); - } else { - if let Some(domain) = &config.https_domain { - let mut listen_on_https = listen_on; - listen_on_https.set_port(443); - log::debug!("Will start HTTPS server on {listen_on_https}"); - let config = make_auto_rustls_config(domain, config); - server = server - .bind_rustls_0_23(listen_on_https, config) - .map_err(|e| bind_error(e, listen_on_https))?; - } else if listen_on.port() == 443 { - bail!("Please specify a value for https_domain in the configuration file. This is required when using HTTPS (port 443)"); - } - if listen_on.port() != 443 { - log::debug!("Will start HTTP server on {listen_on}"); - server = server - .bind(listen_on) - .map_err(|e| bind_error(e, listen_on))?; - } + } + if let Some(domain) = &config.https_domain { + let mut listen_on_https = listen_on; + listen_on_https.set_port(443); + log::debug!("Will start HTTPS server on {listen_on_https}"); + let config = make_auto_rustls_config(domain, config); + server = server + .bind_rustls_0_23(listen_on_https, config) + .map_err(|e| bind_error(e, listen_on_https))?; + } else if listen_on.port() == 443 { + bail!("Please specify a value for https_domain in the configuration file. This is required when using HTTPS (port 443)"); + } + if listen_on.port() != 443 { + log::debug!("Will start HTTP server on {listen_on}"); + server = server + .bind(listen_on) + .map_err(|e| bind_error(e, listen_on))?; } log_welcome_message(config); From c371b23af2252dba0e8f8ffbe06601d535f26bc5 Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Mon, 20 Oct 2025 10:20:02 -0700 Subject: [PATCH 08/45] Undo changes to http.rs --- src/webserver/http.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/webserver/http.rs b/src/webserver/http.rs index 6f163a90..88c9966e 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -490,23 +490,24 @@ pub async fn run_server(config: &AppConfig, state: AppState) -> anyhow::Result<( } #[cfg(not(target_family = "unix"))] anyhow::bail!("Unix sockets are not supported on your operating system. Use listen_on instead of unix_socket."); - } - if let Some(domain) = &config.https_domain { - let mut listen_on_https = listen_on; - listen_on_https.set_port(443); - log::debug!("Will start HTTPS server on {listen_on_https}"); - let config = make_auto_rustls_config(domain, config); - server = server - .bind_rustls_0_23(listen_on_https, config) - .map_err(|e| bind_error(e, listen_on_https))?; - } else if listen_on.port() == 443 { - bail!("Please specify a value for https_domain in the configuration file. This is required when using HTTPS (port 443)"); - } - if listen_on.port() != 443 { - log::debug!("Will start HTTP server on {listen_on}"); - server = server - .bind(listen_on) - .map_err(|e| bind_error(e, listen_on))?; + } else { + if let Some(domain) = &config.https_domain { + let mut listen_on_https = listen_on; + listen_on_https.set_port(443); + log::debug!("Will start HTTPS server on {listen_on_https}"); + let config = make_auto_rustls_config(domain, config); + server = server + .bind_rustls_0_23(listen_on_https, config) + .map_err(|e| bind_error(e, listen_on_https))?; + } else if listen_on.port() == 443 { + bail!("Please specify a value for https_domain in the configuration file. This is required when using HTTPS (port 443)"); + } + if listen_on.port() != 443 { + log::debug!("Will start HTTP server on {listen_on}"); + server = server + .bind(listen_on) + .map_err(|e| bind_error(e, listen_on))?; + } } log_welcome_message(config); From 32d6863982bfa90f953fc0f13ec706e6d4848b31 Mon Sep 17 00:00:00 2001 From: Spencer Hansen Date: Mon, 20 Oct 2025 15:48:07 -0700 Subject: [PATCH 09/45] Added checks for active status to dropdown items in shell.handlebars. --- sqlpage/templates/shell.handlebars | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlpage/templates/shell.handlebars b/sqlpage/templates/shell.handlebars index 35f31b91..0e1c7a8e 100644 --- a/sqlpage/templates/shell.handlebars +++ b/sqlpage/templates/shell.handlebars @@ -95,7 +95,7 @@ {{~#with (parse_json this)}} {{#if (or (or this.title this.icon) this.image)}}