From c7e6e30008d964d1ba6d5e0b87bd7c4c44b36bd1 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:49:35 -0600 Subject: [PATCH 1/4] add webapp layout, breadcrumbs, left nav to opensource --- pgml-dashboard/src/templates/components.rs | 12 ++ pgml-dashboard/src/templates/mod.rs | 120 ++++++++++++++++++ .../templates/components/breadcrumbs.html | 36 ++++-- .../templates/layout/web_app_base.html | 29 +++++ 4 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 pgml-dashboard/templates/layout/web_app_base.html diff --git a/pgml-dashboard/src/templates/components.rs b/pgml-dashboard/src/templates/components.rs index c649cd563..ebf191804 100644 --- a/pgml-dashboard/src/templates/components.rs +++ b/pgml-dashboard/src/templates/components.rs @@ -62,12 +62,24 @@ pub struct Nav<'a> { pub links: Vec>, } +impl<'a> Nav<'a> { + pub fn render(links: Vec>) -> String { + Nav { links }.render_once().unwrap() + } +} + #[derive(TemplateOnce)] #[template(path = "components/breadcrumbs.html")] pub struct Breadcrumbs<'a> { pub links: Vec>, } +impl<'a> Breadcrumbs<'a> { + pub fn render(links: Vec>) -> String { + Breadcrumbs { links }.render_once().unwrap() + } +} + #[derive(TemplateOnce)] #[template(path = "components/boxes.html")] pub struct Boxes<'a> { diff --git a/pgml-dashboard/src/templates/mod.rs b/pgml-dashboard/src/templates/mod.rs index bed1f2b32..592d52a51 100644 --- a/pgml-dashboard/src/templates/mod.rs +++ b/pgml-dashboard/src/templates/mod.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use components::{Nav, NavLink}; + use sailfish::TemplateOnce; use sqlx::postgres::types::PgMoney; use sqlx::types::time::PrimitiveDateTime; @@ -93,6 +95,124 @@ impl From for String { } } +#[derive(TemplateOnce, Clone, Default)] +#[template(path = "layout/web_app_base.html")] +pub struct WebAppBase<'a> { + pub head: Head, + pub preloads: Vec, + pub content: Option, + pub visible_clusters: HashMap, + pub breadcrumbs: Vec>, + pub nav: Vec>, +} + +impl<'a> WebAppBase<'a> { + pub fn new(title: &str) -> Self { + WebAppBase { + head: Head::new().title(title), + ..Default::default() + } + } + + pub fn clusters(&mut self, clusters: HashMap) -> &mut Self { + self.visible_clusters = clusters.to_owned(); + self + } + + pub fn breadcrumbs(&mut self, breadcrumbs: Vec>) -> &mut Self { + self.breadcrumbs = breadcrumbs.to_owned(); + self + } + + pub fn description(&mut self, description: &str) -> &mut Self { + self.head.description = Some(description.to_owned()); + self + } + + pub fn nav(&mut self, active: &str) -> &mut Self { + let mut nav_links = vec![NavLink::new("Create new cluster", "/clusters/new").icon("add")]; + + println!("{:?}", self.visible_clusters); + + // Adds the spesific cluster to a sublist. + if self.visible_clusters.len() > 0 { + let cluster_links = self + .visible_clusters + .iter() + .map(|(name, id)| { + NavLink::new(name, &format!("/clusters/{}", id)).icon("developer_board") + }) + .collect(); + + let cluster_nav = Nav { + links: cluster_links, + }; + + nav_links.push( + NavLink::new("Clusters", "/clusters") + .icon("lan") + .nav(cluster_nav), + ) + } else { + nav_links.push(NavLink::new("Clusters", "/clusters").icon("lan")) + } + + nav_links.push(NavLink::new("Payments", "/payments").icon("payments")); + + // Sets the active left nav item. + let nav_with_active: Vec = nav_links + .into_iter() + .map(|item| { + if item.name.eq(active) { + return item.active(); + } + match item.nav { + Some(sub_nav) => { + let sub_links: Vec = sub_nav + .links + .into_iter() + .map(|sub_item| { + if sub_item.name.eq(active) { + sub_item.active() + } else { + sub_item + } + }) + .collect(); + NavLink { + nav: Some(Nav { links: sub_links }), + ..item + } + } + None => item, + } + }) + .collect(); + + self.nav = nav_with_active; + self + } + + pub fn content(&mut self, content: &str) -> &mut Self { + self.content = Some(content.to_owned()); + self + } + + pub fn render(&mut self, template: T) -> String + where + T: sailfish::TemplateOnce, + { + self.content = Some(template.render_once().unwrap()); + (*self).clone().into() + } +} + +impl<'a> From> for String { + fn from(layout: WebAppBase) -> String { + layout.render_once().unwrap() + } +} + #[derive(TemplateOnce)] #[template(path = "content/article.html")] pub struct Article { diff --git a/pgml-dashboard/templates/components/breadcrumbs.html b/pgml-dashboard/templates/components/breadcrumbs.html index fe1c35387..5b92ff3d6 100644 --- a/pgml-dashboard/templates/components/breadcrumbs.html +++ b/pgml-dashboard/templates/components/breadcrumbs.html @@ -24,17 +24,31 @@ - diff --git a/pgml-dashboard/templates/layout/web_app_base.html b/pgml-dashboard/templates/layout/web_app_base.html new file mode 100644 index 000000000..f453c918a --- /dev/null +++ b/pgml-dashboard/templates/layout/web_app_base.html @@ -0,0 +1,29 @@ +<% use crate::templates::components::{Nav, Breadcrumbs}; %> + + + + <% include!("head.html"); %> + +
+
+
+
+ <%- Nav::render( nav ) %> +
+ +
+
+ <%- Breadcrumbs::render( breadcrumbs ) %> +
+ +
+ <%- content.unwrap_or_default() %> +
+
+
+
+
+ +
+ + From 5923d7366cbf8425622bfad1ff5845e326aa2bf9 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:05:06 -0600 Subject: [PATCH 2/4] add ability to change head html file for webappbase layout --- pgml-dashboard/src/templates/head.rs | 26 ++++++++++++++ pgml-dashboard/src/templates/mod.rs | 35 +++++++++++++------ .../templates/layout/web_app_base.html | 4 +-- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/pgml-dashboard/src/templates/head.rs b/pgml-dashboard/src/templates/head.rs index 1082bad37..2f9f12f3b 100644 --- a/pgml-dashboard/src/templates/head.rs +++ b/pgml-dashboard/src/templates/head.rs @@ -1,8 +1,11 @@ +use sailfish::TemplateOnce; + #[derive(Clone, Default)] pub struct Head { pub title: String, pub description: Option, pub image: Option, + pub preloads: Vec, } impl Head { @@ -29,3 +32,26 @@ impl Head { Head::new().title("404 - Not Found") } } + +#[derive(TemplateOnce, Default, Clone)] +#[template(path = "layout/head.html")] +pub struct DefaultHeadTemplate { + pub head: Head, +} + +impl DefaultHeadTemplate { + pub fn new(head: Option) -> DefaultHeadTemplate { + let head = match head { + Some(head) => head, + None => Head::new(), + }; + + DefaultHeadTemplate { head } + } +} + +impl From for String { + fn from(layout: DefaultHeadTemplate) -> String { + layout.render_once().unwrap() + } +} diff --git a/pgml-dashboard/src/templates/mod.rs b/pgml-dashboard/src/templates/mod.rs index 592d52a51..aa43f8087 100644 --- a/pgml-dashboard/src/templates/mod.rs +++ b/pgml-dashboard/src/templates/mod.rs @@ -98,22 +98,36 @@ impl From for String { #[derive(TemplateOnce, Clone, Default)] #[template(path = "layout/web_app_base.html")] pub struct WebAppBase<'a> { - pub head: Head, - pub preloads: Vec, pub content: Option, pub visible_clusters: HashMap, pub breadcrumbs: Vec>, pub nav: Vec>, + pub head: String, } impl<'a> WebAppBase<'a> { pub fn new(title: &str) -> Self { WebAppBase { - head: Head::new().title(title), + head: crate::templates::head::DefaultHeadTemplate::new(Some( + crate::templates::head::Head { + title: title.to_owned(), + description: None, + image: None, + preloads: vec![], + }, + )) + .render_once() + .unwrap() + .to_owned(), ..Default::default() } } + pub fn head(&mut self, head: String) -> &mut Self { + self.head = head.to_owned(); + self + } + pub fn clusters(&mut self, clusters: HashMap) -> &mut Self { self.visible_clusters = clusters.to_owned(); self @@ -124,20 +138,19 @@ impl<'a> WebAppBase<'a> { self } - pub fn description(&mut self, description: &str) -> &mut Self { - self.head.description = Some(description.to_owned()); - self - } - pub fn nav(&mut self, active: &str) -> &mut Self { let mut nav_links = vec![NavLink::new("Create new cluster", "/clusters/new").icon("add")]; - println!("{:?}", self.visible_clusters); - // Adds the spesific cluster to a sublist. if self.visible_clusters.len() > 0 { - let cluster_links = self + let mut sorted_clusters: Vec<(String, String)> = self .visible_clusters + .iter() + .map(|(name, id)| (name.to_string(), id.to_string())) + .collect(); + sorted_clusters.sort_by_key(|k| k.1.to_owned()); + + let cluster_links = sorted_clusters .iter() .map(|(name, id)| { NavLink::new(name, &format!("/clusters/{}", id)).icon("developer_board") diff --git a/pgml-dashboard/templates/layout/web_app_base.html b/pgml-dashboard/templates/layout/web_app_base.html index f453c918a..c29df7dad 100644 --- a/pgml-dashboard/templates/layout/web_app_base.html +++ b/pgml-dashboard/templates/layout/web_app_base.html @@ -2,7 +2,7 @@ - <% include!("head.html"); %> + <%- head %>
@@ -13,7 +13,7 @@
- <%- Breadcrumbs::render( breadcrumbs ) %> + <%- Breadcrumbs::render( breadcrumbs ) %>
From 81b73619137cf9d15a9aa6cecd2e75122532bf41 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:31:48 -0600 Subject: [PATCH 3/4] add method to add preloads to head --- pgml-dashboard/src/templates/head.rs | 5 +++++ pgml-dashboard/src/templates/mod.rs | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pgml-dashboard/src/templates/head.rs b/pgml-dashboard/src/templates/head.rs index 2f9f12f3b..20a84b786 100644 --- a/pgml-dashboard/src/templates/head.rs +++ b/pgml-dashboard/src/templates/head.rs @@ -13,6 +13,11 @@ impl Head { Head::default() } + pub fn add_preload(&mut self, preload: &str) -> &mut Self { + self.preloads.push(preload.to_owned()); + self + } + pub fn title(mut self, title: &str) -> Head { self.title = title.to_owned(); self diff --git a/pgml-dashboard/src/templates/mod.rs b/pgml-dashboard/src/templates/mod.rs index aa43f8087..926e16740 100644 --- a/pgml-dashboard/src/templates/mod.rs +++ b/pgml-dashboard/src/templates/mod.rs @@ -117,8 +117,7 @@ impl<'a> WebAppBase<'a> { }, )) .render_once() - .unwrap() - .to_owned(), + .unwrap(), ..Default::default() } } From 940cd1b27465676620f21c2764f3fc0c1c24a055 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:23:36 -0600 Subject: [PATCH 4/4] add tests for head --- pgml-dashboard/src/templates/head.rs | 89 ++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/pgml-dashboard/src/templates/head.rs b/pgml-dashboard/src/templates/head.rs index 20a84b786..bc0df7def 100644 --- a/pgml-dashboard/src/templates/head.rs +++ b/pgml-dashboard/src/templates/head.rs @@ -60,3 +60,92 @@ impl From for String { layout.render_once().unwrap() } } + +#[cfg(test)] +mod head_tests { + use crate::templates::Head; + + #[test] + fn new_head() { + let head = Head::new(); + assert_eq!( + (head.title, head.description, head.image, head.preloads), + ("".to_string(), None, None, vec![]) + ); + } + + #[test] + fn add_preload() { + let mut head = Head::new(); + let mut preloads: Vec = vec![]; + for i in 0..5 { + preloads.push(format!("image/test_preload_{}.test", i).to_string()); + } + for preload in preloads.clone() { + head.add_preload(&preload); + } + assert!(head.preloads.eq(&preloads)); + } + + #[test] + fn add_title() { + let head = Head::new().title("test title"); + assert_eq!(head.title, "test title"); + } + + #[test] + fn add_description() { + let head = Head::new().description("test description"); + assert_eq!(head.description, Some("test description".to_string())); + } + + #[test] + fn add_image() { + let head = Head::new().image("images/image_file_path.jpg"); + assert_eq!(head.image, Some("images/image_file_path.jpg".to_string())); + } + + #[test] + fn not_found() { + let head = Head::not_found(); + assert_eq!(head.title, "404 - Not Found") + } +} + +#[cfg(test)] +mod default_head_template_test { + use super::{DefaultHeadTemplate, Head}; + use sailfish::TemplateOnce; + + #[test] + fn default() { + let head = DefaultHeadTemplate::new(None); + let rendered = head.render_once().unwrap(); + assert!( + rendered.contains(r#""#) && + rendered.contains(r#" – PostgresML"#) && + rendered.contains(r#""#) && + !rendered.contains("preload") && + rendered.contains(r#"