diff --git a/config/settings.default.toml b/config/settings.default.toml index 1df46f5c..53a2c693 100644 --- a/config/settings.default.toml +++ b/config/settings.default.toml @@ -20,7 +20,6 @@ path = "log" prefix = "tracing.log" [webserver] -# Configuración opcional del servidor web. -# Usar bind_address = "" para deshabilitar el servidor web. +# Configuración del servidor web. bind_address = "localhost" bind_port = 8088 diff --git a/src/base/component/chunck.rs b/src/base/component/chunck.rs index 29c62918..8d4e956f 100644 --- a/src/base/component/chunck.rs +++ b/src/base/component/chunck.rs @@ -37,14 +37,12 @@ impl PageComponent for Chunck { impl Chunck { - // Chunck BUILDER. - pub fn markup(markup: Markup) -> Self { - let mut chunck = Chunck::prepare(); - chunck.markup.push(markup); - chunck + Chunck::prepare().add_markup(markup) } + // Chunck BUILDER. + pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self { self.renderable = renderable; self diff --git a/src/base/component/container.rs b/src/base/component/container.rs index 58a0caac..dfb1a010 100644 --- a/src/base/component/container.rs +++ b/src/base/component/container.rs @@ -48,8 +48,6 @@ impl PageComponent for Container { impl Container { - // Container BUILDER. - pub fn row() -> Self { let mut grid = Container::prepare(); grid.container = ContainerType::Row; @@ -62,6 +60,8 @@ impl Container { grid } + // Container BUILDER. + pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self { self.renderable = renderable; self diff --git a/src/base/component/form/button.rs b/src/base/component/form/button.rs new file mode 100644 index 00000000..440ccf93 --- /dev/null +++ b/src/base/component/form/button.rs @@ -0,0 +1,174 @@ +use crate::prelude::*; + +enum ButtonType {Button, Reset, Submit} + +pub struct Button { + renderable : fn() -> bool, + weight : i8, + button_type: ButtonType, + name : Option, + value : Option, + autofocus : Option, + disabled : Option, + template : String, +} + +impl PageComponent for Button { + + fn prepare() -> Self { + Button { + renderable : always, + weight : 0, + button_type: ButtonType::Button, + name : None, + value : None, + autofocus : None, + disabled : None, + template : "default".to_string(), + } + } + + fn is_renderable(&self) -> bool { + (self.renderable)() + } + + fn weight(&self) -> i8 { + self.weight + } + + fn default_render(&self, _: &mut PageAssets) -> Markup { + let (button_type, button_class) = match &self.button_type { + ButtonType::Button => ("button", "btn btn-primary form-button"), + ButtonType::Reset => ("reset", "btn btn-primary form-reset" ), + ButtonType::Submit => ("submit", "btn btn-primary form-submit") + }; + let id_item = match &self.name { + Some(name) => Some(format!("edit-{}", name)), + _ => None + }; + html! { + button + type=(button_type) + id=[&id_item] + class=(button_class) + name=[&self.name] + value=[&self.value] + autofocus=[&self.autofocus] + disabled=[&self.disabled] + { + @match &self.value { + Some(value) => (value), + _ => "" + }; + } + } + } +} + +impl Button { + + pub fn button(value: &str) -> Self { + Button::prepare().with_value(value) + } + + pub fn reset(value: &str) -> Self { + let mut button = Button::prepare().with_value(value); + button.button_type = ButtonType::Reset; + button + } + + pub fn submit(value: &str) -> Self { + let mut button = Button::prepare().with_value(value); + button.button_type = ButtonType::Submit; + button + } + + // Button BUILDER. + + pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self { + self.renderable = renderable; + self + } + + pub fn with_weight(mut self, weight: i8) -> Self { + self.weight = weight; + self + } + + pub fn with_name(mut self, name: &str) -> Self { + self.name = if name.is_empty() { + None + } else { + Some(name.replace(" ", "_")) + }; + self + } + + pub fn with_value(mut self, value: &str) -> Self { + self.value = if value.is_empty() { + None + } else { + Some(value.to_string()) + }; + self + } + + pub fn autofocus(mut self, toggle: bool) -> Self { + self.autofocus = match toggle { + true => Some("autofocus".to_string()), + false => None + }; + self + } + + pub fn disabled(mut self, toggle: bool) -> Self { + self.disabled = match toggle { + true => Some("disabled".to_string()), + false => None + }; + self + } + + pub fn using_template(mut self, template: &str) -> Self { + self.template = template.to_string(); + self + } + + // Button GETTERS. + + pub fn name(&self) -> &str { + match &self.name { + Some(name) => name.as_str(), + _ => "" + } + } + + pub fn value(&self) -> &str { + match &self.value { + Some(value) => value.as_str(), + _ => "" + } + } + + pub fn has_autofocus(&self) -> bool { + match &self.autofocus { + Some(_) => true, + _ => false + } + } + + pub fn is_disabled(&self) -> bool { + match &self.disabled { + Some(_) => true, + _ => false + } + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} + +fn always() -> bool { + true +} diff --git a/src/base/component/form/date.rs b/src/base/component/form/date.rs new file mode 100644 index 00000000..e1e33be0 --- /dev/null +++ b/src/base/component/form/date.rs @@ -0,0 +1,264 @@ +use crate::prelude::*; + +pub struct Date { + renderable : fn() -> bool, + weight : i8, + name : Option, + value : Option, + label : String, + placeholder : Option, + autofocus : Option, + autocomplete: Option, + disabled : Option, + readonly : Option, + required : Option, + help_text : String, + template : String, +} + +impl PageComponent for Date { + + fn prepare() -> Self { + Date { + renderable : always, + weight : 0, + name : None, + value : None, + label : "".to_string(), + placeholder : None, + autofocus : None, + autocomplete: None, + disabled : None, + readonly : None, + required : None, + help_text : "".to_string(), + template : "default".to_string(), + } + } + + fn is_renderable(&self) -> bool { + (self.renderable)() + } + + fn weight(&self) -> i8 { + self.weight + } + + fn default_render(&self, _: &mut PageAssets) -> Markup { + let (class_item, id_item) = match &self.name { + Some(name) => ( + format!("form-item form-item-{} form-type-date", name), + Some(format!("edit-{}", name)) + ), + None => ( + "form-item form-type-date".to_string(), + None + ) + }; + html! { + div class=(class_item) { + @if !self.label.is_empty() { + label class="form-label" for=[&id_item] { + (self.label) " " + @if self.required != None { + span + class="form-required" + title="Este campo es obligatorio." + { + "*" + } " " + } + } + } + input + type="date" + id=[&id_item] + class="form-control" + name=[&self.name] + value=[&self.value] + placeholder=[&self.placeholder] + autofocus=[&self.autofocus] + autocomplete=[&self.autocomplete] + readonly=[&self.readonly] + required=[&self.required] + disabled=[&self.disabled]; + @if !self.help_text.is_empty() { + div class="form-text" { + (self.help_text) + } + } + } + } + } +} + +impl Date { + + // Date BUILDER. + + pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self { + self.renderable = renderable; + self + } + + pub fn with_weight(mut self, weight: i8) -> Self { + self.weight = weight; + self + } + + pub fn with_name(mut self, name: &str) -> Self { + self.name = if name.is_empty() { + None + } else { + Some(name.replace(" ", "_")) + }; + self + } + + pub fn with_value(mut self, value: &str) -> Self { + self.value = if value.is_empty() { + None + } else { + Some(value.to_string()) + }; + self + } + + pub fn with_label(mut self, label: &str) -> Self { + self.label = label.to_string(); + self + } + + pub fn with_placeholder(mut self, placeholder: &str) -> Self { + self.placeholder = if placeholder.is_empty() { + None + } else { + Some(placeholder.to_string()) + }; + self + } + + pub fn autofocus(mut self, toggle: bool) -> Self { + self.autofocus = match toggle { + true => Some("autofocus".to_string()), + false => None + }; + self + } + + pub fn autocomplete(mut self, toggle: bool) -> Self { + self.autocomplete = match toggle { + true => None, + false => Some("off".to_string()) + }; + self + } + + pub fn disabled(mut self, toggle: bool) -> Self { + self.disabled = match toggle { + true => Some("disabled".to_string()), + false => None + }; + self + } + + pub fn readonly(mut self, toggle: bool) -> Self { + self.readonly = match toggle { + true => Some("readonly".to_string()), + false => None + }; + self + } + + pub fn required(mut self, toggle: bool) -> Self { + self.required = match toggle { + true => Some("required".to_string()), + false => None + }; + self + } + + pub fn with_help_text(mut self, help_text: &str) -> Self { + self.help_text = help_text.to_string(); + self + } + + pub fn using_template(mut self, template: &str) -> Self { + self.template = template.to_string(); + self + } + + // Date GETTERS. + + pub fn name(&self) -> &str { + match &self.name { + Some(name) => name.as_str(), + _ => "" + } + } + + pub fn value(&self) -> &str { + match &self.value { + Some(value) => value.as_str(), + _ => "" + } + } + + pub fn label(&self) -> &str { + self.label.as_str() + } + + pub fn placeholder(&self) -> &str { + match &self.placeholder { + Some(placeholder) => placeholder.as_str(), + _ => "" + } + } + + pub fn has_autofocus(&self) -> bool { + match &self.autofocus { + Some(_) => true, + _ => false + } + } + + pub fn has_autocomplete(&self) -> bool { + match &self.autocomplete { + Some(_) => false, + _ => true + } + } + + pub fn is_disabled(&self) -> bool { + match &self.disabled { + Some(_) => true, + _ => false + } + } + + pub fn is_readonly(&self) -> bool { + match &self.readonly { + Some(_) => true, + _ => false + } + } + + pub fn is_required(&self) -> bool { + match &self.required { + Some(_) => true, + _ => false + } + } + + pub fn help_text(&self) -> &str { + self.help_text.as_str() + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} + +fn always() -> bool { + true +} diff --git a/src/base/component/form/form.rs b/src/base/component/form/form.rs new file mode 100644 index 00000000..519587e0 --- /dev/null +++ b/src/base/component/form/form.rs @@ -0,0 +1,152 @@ +use crate::prelude::*; + +pub enum FormMethod {Get, Post} + +pub struct Form { + renderable: fn() -> bool, + weight : i8, + id : Option, + action : Option, + method : FormMethod, + charset : Option, + elements : PageContainer, + template : String, +} + +impl PageComponent for Form { + + fn prepare() -> Self { + Form { + renderable: always, + weight : 0, + id : None, + action : None, + method : FormMethod::Post, + charset : Some("UTF-8".to_string()), + elements : PageContainer::new(), + template : "default".to_string(), + } + } + + fn is_renderable(&self) -> bool { + (self.renderable)() + } + + fn weight(&self) -> i8 { + self.weight + } + + fn default_render(&self, assets: &mut PageAssets) -> Markup { + let method = match self.method { + FormMethod::Get => None, + FormMethod::Post => Some("post".to_string()) + }; + html! { + form + id=[&self.id] + action=[&self.action] + method=[method] + accept-charset=[&self.charset] + { + div { + (self.elements.render(assets)) + } + } + } + } +} + +impl Form { + + // Form BUILDER. + + pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self { + self.renderable = renderable; + self + } + + pub fn with_weight(mut self, weight: i8) -> Self { + self.weight = weight; + self + } + + pub fn with_id(mut self, id: &str) -> Self { + self.id = if id.is_empty() { + None + } else { + Some(id.replace(" ", "_")) + }; + self + } + + pub fn with_action(mut self, action: &str) -> Self { + self.action = if action.is_empty() { + None + } else { + Some(action.to_string()) + }; + self + } + + pub fn with_method(mut self, method: FormMethod) -> Self { + self.method = method; + self + } + + pub fn with_charset(mut self, charset: &str) -> Self { + self.charset = if charset.is_empty() { + None + } else { + Some(charset.to_string()) + }; + self + } + + pub fn add(mut self, element: impl PageComponent) -> Self { + self.elements.add(element); + self + } + + pub fn using_template(mut self, template: &str) -> Self { + self.template = template.to_string(); + self + } + + // Form GETTERS. + + pub fn id(&self) -> &str { + match &self.id { + Some(id) => id.as_str(), + _ => "" + } + } + + pub fn action(&self) -> &str { + match &self.action { + Some(action) => action.as_str(), + _ => "" + } + } + + pub fn method(&self) -> &str { + match &self.method { + FormMethod::Get => "get", + FormMethod::Post => "post" + } + } + + pub fn charset(&self) -> &str { + match &self.charset { + Some(charset) => charset.as_str(), + _ => "" + } + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} + +fn always() -> bool { + true +} diff --git a/src/base/component/form/hidden.rs b/src/base/component/form/hidden.rs new file mode 100644 index 00000000..a95cfc77 --- /dev/null +++ b/src/base/component/form/hidden.rs @@ -0,0 +1,110 @@ +use crate::prelude::*; + +pub struct Hidden { + renderable : fn() -> bool, + weight : i8, + name : Option, + value : Option, + template : String, +} + +impl PageComponent for Hidden { + + fn prepare() -> Self { + Hidden { + renderable : always, + weight : 0, + name : None, + value : None, + template : "default".to_string(), + } + } + + fn is_renderable(&self) -> bool { + (self.renderable)() + } + + fn weight(&self) -> i8 { + self.weight + } + + fn default_render(&self, _: &mut PageAssets) -> Markup { + let id_item = match &self.name { + Some(name) => Some(format!("value-{}", name)), + _ => None + }; + html! { + input + type="hidden" + id=[&id_item] + name=[&self.name] + value=[&self.value]; + } + } +} + +impl Hidden { + + pub fn set(name: &str, value: &str) -> Self { + Hidden::prepare().with_name(name).with_value(value) + } + + // Hidden BUILDER. + + pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self { + self.renderable = renderable; + self + } + + pub fn with_weight(mut self, weight: i8) -> Self { + self.weight = weight; + self + } + + pub fn with_name(mut self, name: &str) -> Self { + self.name = if name.is_empty() { + None + } else { + Some(name.replace(" ", "_")) + }; + self + } + + pub fn with_value(mut self, value: &str) -> Self { + self.value = if value.is_empty() { + None + } else { + Some(value.to_string()) + }; + self + } + + pub fn using_template(mut self, template: &str) -> Self { + self.template = template.to_string(); + self + } + + // Hidden GETTERS. + + pub fn name(&self) -> &str { + match &self.name { + Some(name) => name.as_str(), + _ => "" + } + } + + pub fn value(&self) -> &str { + match &self.value { + Some(value) => value.as_str(), + _ => "" + } + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} + +fn always() -> bool { + true +} diff --git a/src/base/component/form/input.rs b/src/base/component/form/input.rs new file mode 100644 index 00000000..d4ec8930 --- /dev/null +++ b/src/base/component/form/input.rs @@ -0,0 +1,346 @@ +use crate::prelude::*; + +enum InputType {Email, Password, Search, Telephone, Textfield, Url} + +pub struct Input { + renderable : fn() -> bool, + weight : i8, + input_type : InputType, + name : Option, + value : Option, + label : String, + size : Option, + minlength : Option, + maxlength : Option, + placeholder : Option, + autofocus : Option, + autocomplete: Option, + disabled : Option, + readonly : Option, + required : Option, + help_text : String, + template : String, +} + +impl PageComponent for Input { + + fn prepare() -> Self { + Input { + renderable : always, + weight : 0, + input_type : InputType::Textfield, + name : None, + value : None, + label : "".to_string(), + size : Some(60), + minlength : None, + maxlength : Some(128), + placeholder : None, + autofocus : None, + autocomplete: None, + disabled : None, + readonly : None, + required : None, + help_text : "".to_string(), + template : "default".to_string(), + } + } + + fn is_renderable(&self) -> bool { + (self.renderable)() + } + + fn weight(&self) -> i8 { + self.weight + } + + fn default_render(&self, _: &mut PageAssets) -> Markup { + let (input_type, class_type) = match &self.input_type { + InputType::Email => ("email", "form-type-email"), + InputType::Password => ("password", "form-type-password"), + InputType::Search => ("search", "form-type-search"), + InputType::Telephone => ("tel", "form-type-telephone"), + InputType::Textfield => ("text", "form-type-textfield"), + InputType::Url => ("url", "form-type-url") + }; + let (class_item, id_item) = match &self.name { + Some(name) => ( + format!("form-item form-item-{} {}", name, class_type), + Some(format!("edit-{}", name)) + ), + None => ( + format!("form-item {}", class_type), + None + ) + }; + html! { + div class=(class_item) { + @if !self.label.is_empty() { + label class="form-label" for=[&id_item] { + (self.label) " " + @if self.required != None { + span + class="form-required" + title="Este campo es obligatorio." + { + "*" + } " " + } + } + } + input + type=(input_type) + id=[&id_item] + class="form-control" + name=[&self.name] + value=[&self.value] + size=[self.size] + minlength=[self.minlength] + maxlength=[self.maxlength] + placeholder=[&self.placeholder] + autofocus=[&self.autofocus] + autocomplete=[&self.autocomplete] + readonly=[&self.readonly] + required=[&self.required] + disabled=[&self.disabled]; + @if !self.help_text.is_empty() { + div class="form-text" { + (self.help_text) + } + } + } + } + } +} + +impl Input { + + pub fn textfield() -> Self { + Input::prepare() + } + + pub fn password() -> Self { + let mut input = Input::prepare(); + input.input_type = InputType::Password; + input + } + + pub fn search() -> Self { + let mut input = Input::prepare(); + input.input_type = InputType::Search; + input + } + + pub fn email() -> Self { + let mut input = Input::prepare(); + input.input_type = InputType::Email; + input + } + + pub fn telephone() -> Self { + let mut input = Input::prepare(); + input.input_type = InputType::Telephone; + input + } + + pub fn url() -> Self { + let mut input = Input::prepare(); + input.input_type = InputType::Url; + input + } + + // Input BUILDER. + + pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self { + self.renderable = renderable; + self + } + + pub fn with_weight(mut self, weight: i8) -> Self { + self.weight = weight; + self + } + + pub fn with_name(mut self, name: &str) -> Self { + self.name = if name.is_empty() { + None + } else { + Some(name.replace(" ", "_")) + }; + self + } + + pub fn with_value(mut self, value: &str) -> Self { + self.value = if value.is_empty() { + None + } else { + Some(value.to_string()) + }; + self + } + + pub fn with_label(mut self, label: &str) -> Self { + self.label = label.to_string(); + self + } + + pub fn with_size(mut self, size: Option) -> Self { + self.size = size; + self + } + + pub fn with_minlength(mut self, minlength: Option) -> Self { + self.minlength = minlength; + self + } + + pub fn with_maxlength(mut self, maxlength: Option) -> Self { + self.maxlength = maxlength; + self + } + + pub fn with_placeholder(mut self, placeholder: &str) -> Self { + self.placeholder = if placeholder.is_empty() { + None + } else { + Some(placeholder.to_string()) + }; + self + } + + pub fn autofocus(mut self, toggle: bool) -> Self { + self.autofocus = match toggle { + true => Some("autofocus".to_string()), + false => None + }; + self + } + + pub fn autocomplete(mut self, toggle: bool) -> Self { + self.autocomplete = match toggle { + true => None, + false => Some("off".to_string()) + }; + self + } + + pub fn disabled(mut self, toggle: bool) -> Self { + self.disabled = match toggle { + true => Some("disabled".to_string()), + false => None + }; + self + } + + pub fn readonly(mut self, toggle: bool) -> Self { + self.readonly = match toggle { + true => Some("readonly".to_string()), + false => None + }; + self + } + + pub fn required(mut self, toggle: bool) -> Self { + self.required = match toggle { + true => Some("required".to_string()), + false => None + }; + self + } + + pub fn with_help_text(mut self, help_text: &str) -> Self { + self.help_text = help_text.to_string(); + self + } + + pub fn using_template(mut self, template: &str) -> Self { + self.template = template.to_string(); + self + } + + // Input GETTERS. + + pub fn name(&self) -> &str { + match &self.name { + Some(name) => name.as_str(), + _ => "" + } + } + + pub fn value(&self) -> &str { + match &self.value { + Some(value) => value.as_str(), + _ => "" + } + } + + pub fn label(&self) -> &str { + self.label.as_str() + } + + pub fn size(&self) -> Option { + self.size + } + + pub fn minlength(&self) -> Option { + self.minlength + } + + pub fn maxlength(&self) -> Option { + self.maxlength + } + + pub fn placeholder(&self) -> &str { + match &self.placeholder { + Some(placeholder) => placeholder.as_str(), + _ => "" + } + } + + pub fn has_autofocus(&self) -> bool { + match &self.autofocus { + Some(_) => true, + _ => false + } + } + + pub fn has_autocomplete(&self) -> bool { + match &self.autocomplete { + Some(_) => false, + _ => true + } + } + + pub fn is_disabled(&self) -> bool { + match &self.disabled { + Some(_) => true, + _ => false + } + } + + pub fn is_readonly(&self) -> bool { + match &self.readonly { + Some(_) => true, + _ => false + } + } + + pub fn is_required(&self) -> bool { + match &self.required { + Some(_) => true, + _ => false + } + } + + pub fn help_text(&self) -> &str { + self.help_text.as_str() + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} + +fn always() -> bool { + true +} diff --git a/src/base/component/form/mod.rs b/src/base/component/form/mod.rs new file mode 100644 index 00000000..4e10d476 --- /dev/null +++ b/src/base/component/form/mod.rs @@ -0,0 +1,11 @@ +mod form; +pub use form::{Form, FormMethod}; + +mod input; +pub use input::Input; +mod hidden; +pub use hidden::Hidden; +mod date; +pub use date::Date; +mod button; +pub use button::Button; diff --git a/src/base/component/mod.rs b/src/base/component/mod.rs index c12fb175..07c16cf7 100644 --- a/src/base/component/mod.rs +++ b/src/base/component/mod.rs @@ -1,2 +1,7 @@ -pub mod container; -pub mod chunck; +mod container; +pub use container::Container; +mod chunck; +pub use chunck::Chunck; + +pub mod form; +pub use form::{Form, FormMethod}; diff --git a/src/base/module/homepage/locales/en-US/homepage.ftl b/src/base/module/homepage/locales/en-US/homepage.ftl index 7008c35a..ec401e64 100644 --- a/src/base/module/homepage/locales/en-US/homepage.ftl +++ b/src/base/module/homepage/locales/en-US/homepage.ftl @@ -1,4 +1,21 @@ module_name = Default homepage module_desc = Displays a default homepage when none is configured. -greetings = Hello { $name }! +page_title = Hello world! + +text_welcome = This page is used to test the proper operation of { $app } after installation. This web solution is powered by { $pagetop }. If you can read this page, it means that the PageTop server is working properly, but has not yet been configured. + +title_normal_user = Just visiting? +text1_normal_user = If you are a normal user of this web site and don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance. +text2_normal_user = If the problem persists, please contact your system administrator. + +title_about_pagetop = About PageTop +text1_about_pagetop = PageTop defines an interface for the most stable and popular Rust packages to build modular, extensible and configurable web solutions. +text2_about_pagetop = For information on PageTop please visit the "PageTop website". + +title_promo_pagetop = Promoting PageTop +text1_promo_pagetop = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop! + +title_report_problems = Reporting Problems +text1_report_problems = Please use the GitLab tool to report bugs in PageTop. However, check "existing bug reports" before reporting a new bug. +text2_report_problems = Please report bugs specific to modules (such as admin, and others) to respective packages, not to PageTop itself. diff --git a/src/base/module/homepage/locales/es-ES/homepage.ftl b/src/base/module/homepage/locales/es-ES/homepage.ftl index 7af7b370..dee1df47 100644 --- a/src/base/module/homepage/locales/es-ES/homepage.ftl +++ b/src/base/module/homepage/locales/es-ES/homepage.ftl @@ -1,4 +1,21 @@ module_name = Página de inicio predeterminada module_desc = Muestra una página de inicio predeterminada cuando no hay ninguna configurada. -greetings = ¡Hola { $name }! +page_title = ¡Hola mundo! + +text_welcome = Esta página se utiliza para probar el correcto funcionamiento de { $app } después de la instalación. Esta solución web funciona con { $pagetop }. Si puede leer esta página, significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado. + +title_normal_user = ¿Sólo de visita? +text1_normal_user = Si usted es un usuario normal de este sitio web y no sabe de qué trata esta página, probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina. +text2_normal_user = Si el problema persiste, póngase en contacto con el administrador del sistema. + +title_about_pagetop = Sobre PageTop +text1_about_pagetop = PageTop define una interfaz para los paquetes Rust más estables y populares para crear soluciones web modulares, extensibles y configurables. +text2_about_pagetop = Para obtener información sobre PageTop, visite el "sitio web de PageTop". + +title_promo_pagetop = Promociona PageTop +text1_promo_pagetop = Puede usar la siguiente imagen en aplicaciones desarrolladas sobre { $pagetop }. ¡Gracias por usar PageTop! + +title_report_problems = Informando Problemas +text1_report_problems = Utilice la herramienta GitLab para informar errores en PageTop. Sin embargo, verifique los "informes de errores existentes" antes de informar de un nuevo error. +text2_report_problems = Informe los errores específicos de los módulos (como admin y otros) a los paquetes respectivos, no a PageTop en sí. diff --git a/src/base/module/homepage/mod.rs b/src/base/module/homepage/mod.rs index 4133f6ba..2e4164d0 100644 --- a/src/base/module/homepage/mod.rs +++ b/src/base/module/homepage/mod.rs @@ -14,16 +14,57 @@ impl Module for HomepageModule { } fn configure_module(&self, cfg: &mut server::web::ServiceConfig) { - cfg.service(server::web::resource("/").to(home)); - cfg.service(server::web::resource("/{name}").to(home)); + cfg.route("/", server::web::get().to(home)); } } -async fn home(req: server::HttpRequest) -> server::Result { - let name: String = req.match_info().get("name").unwrap_or("World").into(); +async fn home() -> server::Result { Page::prepare() - .add_to("content", Chunck::markup(html! { - h1 { (t("greetings", &args![ "name" => name])) } - })) + .with_title( + l("page_title").as_str() + ) + .add_to("content", Container::prepare() + .with_id("welcome") + .add(Chunck::markup(html! { + h1 { (l("page_title")) } + p { (e("text_welcome", &args![ + "app" => format!("{}", &SETTINGS.app.name), + "pagetop" => "PageTop" + ])) } + })) + ) + .add_to("content", Container::prepare() + .add(Container::row() + .add(Container::column() + .with_id("visitors") + .add(Chunck::markup(html! { + h2 { (l("title_normal_user")) } + p { (l("text1_normal_user")) } + p { (l("text2_normal_user")) } + }))) + .add(Container::column() + .with_id("pagetop") + .add(Chunck::markup(html! { + h2 { (l("title_about_pagetop")) } + p { (l("text1_about_pagetop")) } + p { (l("text2_about_pagetop")) } + + h2 { (l("title_promo_pagetop")) } + p { (e("text1_promo_pagetop", &args![ + "pagetop" => + "PageTop" + ])) } + })) + ) + ) + ) + .add_to("content", Container::prepare() + .with_id("reporting") + .add(Chunck::markup(html! { + h2 { (l("title_report_problems")) } + p { (l("text1_report_problems")) } + p { (l("text2_report_problems")) } + })) + ) .render() } diff --git a/src/base/module/mod.rs b/src/base/module/mod.rs index 070e5b82..dd90a79f 100644 --- a/src/base/module/mod.rs +++ b/src/base/module/mod.rs @@ -1 +1,2 @@ pub mod homepage; +pub mod user; diff --git a/src/base/module/user/locales/en-US/homepage.ftl b/src/base/module/user/locales/en-US/homepage.ftl new file mode 100644 index 00000000..e6ad0e91 --- /dev/null +++ b/src/base/module/user/locales/en-US/homepage.ftl @@ -0,0 +1,8 @@ +module_name = User +module_desc = Manages the user registration and login system. + +username = User name +password = Password +username_help = Enter your { $app } username. +password_help = Enter the password that accompanies your username. +login = Log in diff --git a/src/base/module/user/locales/es-ES/homepage.ftl b/src/base/module/user/locales/es-ES/homepage.ftl new file mode 100644 index 00000000..68f1c889 --- /dev/null +++ b/src/base/module/user/locales/es-ES/homepage.ftl @@ -0,0 +1,8 @@ +module_name = Usuario +module_desc = Gestion el registro de usuarios y el sistema de accesos. + +username = Nombre de usuario +password = Contraseña +username_help = Introduzca su nombre de usuario en { $app }. +password_help = Introduzca la contraseña asociada a su nombre de usuario. +login = Iniciar sesión diff --git a/src/base/module/user/mod.rs b/src/base/module/user/mod.rs new file mode 100644 index 00000000..61d50ca1 --- /dev/null +++ b/src/base/module/user/mod.rs @@ -0,0 +1,47 @@ +use crate::prelude::*; + +localize!("en-US", "src/base/module/user/locales"); + +pub struct UserModule; + +impl Module for UserModule { + fn name(&self) -> String { + l("module_name") + } + + fn description(&self) -> String { + l("module_desc") + } + + fn configure_module(&self, cfg: &mut server::web::ServiceConfig) { + cfg.route("/user/login", server::web::get().to(login)); + } +} + +fn form_login() -> impl PageComponent { + Form::prepare() + .with_id("user-login") + .add(form::Input::textfield() + .with_name("name") + .with_label(l("username").as_str()) + .with_help_text(t("username_help", &args![ + "app" => SETTINGS.app.name.to_string() + ]).as_str()) + .autofocus(true) + ) + .add(form::Input::password() + .with_name("pass") + .with_label(l("password").as_str()) + .with_help_text(l("password_help").as_str()) + ) + .add(form::Button::submit(l("login").as_str())) +} + +async fn login() -> server::Result { + Page::prepare() + .with_title( + "Identificación del usuario" + ) + .add_to("content", form_login()) + .render() +} diff --git a/src/core/server/main.rs b/src/core/server/main.rs index 0884201e..b915743e 100644 --- a/src/core/server/main.rs +++ b/src/core/server/main.rs @@ -9,6 +9,8 @@ use tracing::subscriber::set_global_default; use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; use tracing_actix_web::TracingLogger; +use actix_web::middleware::normalize; + pub fn run(bootstrap: Option) -> Result { // Inicia la traza de ejecución de la aplicación. let env_filter = EnvFilter::try_new(&SETTINGS.log.tracing) @@ -90,6 +92,7 @@ pub fn run(bootstrap: Option) -> Result { let server = server::HttpServer::new(|| { server::App::new() .wrap(TracingLogger) + .wrap(normalize::NormalizePath::new(normalize::TrailingSlash::Trim)) .configure(&all::themes) .configure(&all::modules) }) diff --git a/src/core/state.rs b/src/core/state.rs index 69b6a4de..88936bfd 100644 --- a/src/core/state.rs +++ b/src/core/state.rs @@ -36,7 +36,9 @@ pub fn register_theme(t: &'static (dyn Theme + 'static)) { // ----------------------------------------------------------------------------- pub static MODULES: Lazy>> = Lazy::new(|| { - RwLock::new(vec![]) + RwLock::new(vec![ + &base::module::user::UserModule, + ]) }); pub fn register_module(m: &'static (dyn Module + 'static)) { diff --git a/src/macros/mod.rs b/src/macros/mod.rs index ef3c05d5..88ab3f90 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -16,4 +16,4 @@ macro_rules! args { )* a }}; -} \ No newline at end of file +} diff --git a/src/prelude.rs b/src/prelude.rs index 8ae92907..b4b62b18 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,9 +1,9 @@ //! Re-exporta recursos comunes. pub use crate::args; -pub use crate::localize; - +pub use crate::config::SETTINGS; pub use crate::trace; +pub use crate::localize; pub use crate::core::theme::*; @@ -17,5 +17,4 @@ pub use crate::core::register_theme; pub use crate::core::register_module; pub use crate::core::add_component_to; -pub use crate::base::component::container::Container; -pub use crate::base::component::chunck::Chunck; +pub use crate::base::component::*;