diff --git a/pgml-dashboard/src/api/deployment/deployment_models.rs b/pgml-dashboard/src/api/deployment/deployment_models.rs index 3fe66c8a7..b987cecad 100644 --- a/pgml-dashboard/src/api/deployment/deployment_models.rs +++ b/pgml-dashboard/src/api/deployment/deployment_models.rs @@ -7,6 +7,7 @@ use crate::{ responses::{Error, ResponseOk}, }; +use crate::components::layouts::product::Index as Product; use crate::templates::{components::NavLink, *}; use crate::models; @@ -19,7 +20,7 @@ use std::collections::HashMap; // Returns models page #[get("/models")] pub async fn deployment_models(cluster: &Cluster, _connected: ConnectedCluster<'_>) -> Result { - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![NavLink::new("Models", &urls::deployment_models()).active()]); let tabs = vec![tabs::Tab { @@ -38,7 +39,7 @@ pub async fn model(cluster: &Cluster, model_id: i64, _connected: ConnectedCluste let model = models::Model::get_by_id(cluster.pool(), model_id).await?; let project = models::Project::get_by_id(cluster.pool(), model.project_id).await?; - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![ NavLink::new("Models", &urls::deployment_models()), NavLink::new(&project.name, &urls::deployment_project_by_id(project.id)), diff --git a/pgml-dashboard/src/api/deployment/notebooks.rs b/pgml-dashboard/src/api/deployment/notebooks.rs index 25701c0ca..bb0c7ec95 100644 --- a/pgml-dashboard/src/api/deployment/notebooks.rs +++ b/pgml-dashboard/src/api/deployment/notebooks.rs @@ -11,6 +11,7 @@ use crate::{ responses::{Error, ResponseOk}, }; +use crate::components::layouts::product::Index as Product; use crate::templates::{components::NavLink, *}; use crate::utils::tabs; @@ -21,7 +22,7 @@ use crate::utils::urls; // Returns notebook page #[get("/notebooks")] pub async fn notebooks(cluster: &Cluster, _connected: ConnectedCluster<'_>) -> Result { - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![NavLink::new("Notebooks", &urls::deployment_notebooks()).active()]); let tabs = vec![tabs::Tab { @@ -43,7 +44,7 @@ pub async fn notebook( ) -> Result { let notebook = models::Notebook::get_by_id(cluster.pool(), notebook_id).await?; - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![ NavLink::new("Notebooks", &urls::deployment_notebooks()), NavLink::new(notebook.name.as_str(), &urls::deployment_notebook_by_id(notebook_id)).active(), diff --git a/pgml-dashboard/src/api/deployment/projects.rs b/pgml-dashboard/src/api/deployment/projects.rs index 3a1e060e0..1f8c43788 100644 --- a/pgml-dashboard/src/api/deployment/projects.rs +++ b/pgml-dashboard/src/api/deployment/projects.rs @@ -7,6 +7,7 @@ use crate::{ responses::{Error, ResponseOk}, }; +use crate::components::layouts::product::Index as Product; use crate::templates::{components::NavLink, *}; use crate::models; @@ -17,7 +18,7 @@ use crate::utils::urls; // Returns the deployments projects page. #[get("/projects")] pub async fn projects(cluster: &Cluster, _connected: ConnectedCluster<'_>) -> Result { - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![NavLink::new("Projects", &urls::deployment_projects()).active()]); let tabs = vec![tabs::Tab { @@ -39,7 +40,7 @@ pub async fn project( ) -> Result { let project = models::Project::get_by_id(cluster.pool(), project_id).await?; - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![ NavLink::new("Projects", &urls::deployment_projects()), NavLink::new(project.name.as_str(), &urls::deployment_project_by_id(project_id)).active(), diff --git a/pgml-dashboard/src/api/deployment/snapshots.rs b/pgml-dashboard/src/api/deployment/snapshots.rs index ed87d48e7..3f31d5803 100644 --- a/pgml-dashboard/src/api/deployment/snapshots.rs +++ b/pgml-dashboard/src/api/deployment/snapshots.rs @@ -7,6 +7,7 @@ use crate::{ responses::{Error, ResponseOk}, }; +use crate::components::layouts::product::Index as Product; use crate::templates::{components::NavLink, *}; use crate::models; @@ -18,7 +19,7 @@ use std::collections::HashMap; // Returns snapshots page #[get("/snapshots")] pub async fn snapshots(cluster: &Cluster, _connected: ConnectedCluster<'_>) -> Result { - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![NavLink::new("Snapshots", &urls::deployment_snapshots()).active()]); let tabs = vec![tabs::Tab { @@ -40,7 +41,7 @@ pub async fn snapshot( ) -> Result { let snapshot = models::Snapshot::get_by_id(cluster.pool(), snapshot_id).await?; - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![ NavLink::new("Snapshots", &urls::deployment_snapshots()), NavLink::new(&snapshot.relation_name, &urls::deployment_snapshot_by_id(snapshot.id)).active(), diff --git a/pgml-dashboard/src/api/deployment/uploader.rs b/pgml-dashboard/src/api/deployment/uploader.rs index 41f148007..fccf55e3f 100644 --- a/pgml-dashboard/src/api/deployment/uploader.rs +++ b/pgml-dashboard/src/api/deployment/uploader.rs @@ -4,6 +4,7 @@ use rocket::response::Redirect; use rocket::route::Route; use sailfish::TemplateOnce; +use crate::components::layouts::product::Index as Product; use crate::{ guards::Cluster, guards::ConnectedCluster, @@ -20,7 +21,7 @@ use crate::utils::urls; // Returns the uploader page. #[get("/uploader")] pub async fn uploader(cluster: &Cluster, _connected: ConnectedCluster<'_>) -> Result { - let mut layout = crate::templates::WebAppBase::new("Dashboard", &cluster); + let mut layout = Product::new("Dashboard", &cluster); layout.breadcrumbs(vec![NavLink::new("Upload Data", &urls::deployment_uploader()).active()]); let tabs = vec![tabs::Tab { diff --git a/pgml-dashboard/src/components/layouts/docs/mod.rs b/pgml-dashboard/src/components/layouts/docs/mod.rs index a682072ca..11cb97bf4 100644 --- a/pgml-dashboard/src/components/layouts/docs/mod.rs +++ b/pgml-dashboard/src/components/layouts/docs/mod.rs @@ -2,7 +2,7 @@ use crate::components::cms::IndexLink; use crate::components::layouts::Head; use crate::guards::Cluster; use crate::models::User; -use pgml_components::component; +use pgml_components::{component, Component}; use sailfish::TemplateOnce; #[derive(TemplateOnce, Default, Clone)] @@ -13,23 +13,26 @@ pub struct Docs { user: Option, content: Option, index: Vec, + body_components: Vec, } impl Docs { pub fn new(title: &str, context: Option<&Cluster>) -> Docs { - let (head, footer, user) = match context.as_ref() { + let (head, footer, user, body_components) = match context.as_ref() { Some(context) => ( Head::new().title(&title).context(&context.context.head_items), Some(context.context.marketing_footer.clone()), Some(context.context.user.clone()), + context.context.body_components.clone(), ), - None => (Head::new().title(&title), None, None), + None => (Head::new().title(&title), None, None, Vec::new()), }; Docs { head, footer, user, + body_components, ..Default::default() } } diff --git a/pgml-dashboard/src/components/layouts/docs/template.html b/pgml-dashboard/src/components/layouts/docs/template.html index 85bb6f89c..4c0acc7c5 100644 --- a/pgml-dashboard/src/components/layouts/docs/template.html +++ b/pgml-dashboard/src/components/layouts/docs/template.html @@ -7,6 +7,9 @@ <%+ head %> + <% for component in body_components {%> + <%+ component %> + <% } %>
<%+ MarketingNavbar::new(user).style_alt() %> diff --git a/pgml-dashboard/src/components/layouts/marketing/base/mod.rs b/pgml-dashboard/src/components/layouts/marketing/base/mod.rs index 5d1ee0d36..38de7ba05 100644 --- a/pgml-dashboard/src/components/layouts/marketing/base/mod.rs +++ b/pgml-dashboard/src/components/layouts/marketing/base/mod.rs @@ -3,7 +3,7 @@ use crate::components::notifications::marketing::AlertBanner; use crate::guards::Cluster; use crate::models::User; use crate::Notification; -use pgml_components::component; +use pgml_components::{component, Component}; use sailfish::TemplateOnce; use std::fmt; @@ -35,19 +35,21 @@ pub struct Base { pub user: Option, pub theme: Theme, pub no_transparent_nav: bool, + pub body_components: Vec, } impl Base { pub fn new(title: &str, context: Option<&Cluster>) -> Base { let title = format!("{} - PostgresML", title); - let (head, footer, user) = match context.as_ref() { + let (head, footer, user, body_components) = match context.as_ref() { Some(context) => ( Head::new().title(&title).context(&context.context.head_items), Some(context.context.marketing_footer.clone()), Some(context.context.user.clone()), + context.context.body_components.clone(), ), - None => (Head::new().title(&title), None, None), + None => (Head::new().title(&title), None, None, Vec::new()), }; Base { @@ -56,6 +58,7 @@ impl Base { alert_banner: AlertBanner::from_notification(Notification::next_alert(context)), user, no_transparent_nav: false, + body_components, ..Default::default() } } diff --git a/pgml-dashboard/src/components/layouts/marketing/base/template.html b/pgml-dashboard/src/components/layouts/marketing/base/template.html index e73e656c8..69bdbda77 100644 --- a/pgml-dashboard/src/components/layouts/marketing/base/template.html +++ b/pgml-dashboard/src/components/layouts/marketing/base/template.html @@ -13,6 +13,10 @@ behavior: 'instant' }); + + <% for component in body_components {%> + <%+ component %> + <% } %>
<%+ alert_banner %> diff --git a/pgml-dashboard/src/components/layouts/mod.rs b/pgml-dashboard/src/components/layouts/mod.rs index 4108da56c..5ed0efa41 100644 --- a/pgml-dashboard/src/components/layouts/mod.rs +++ b/pgml-dashboard/src/components/layouts/mod.rs @@ -11,3 +11,6 @@ pub use head::Head; // src/components/layouts/marketing pub mod marketing; + +// src/components/layouts/product +pub mod product; diff --git a/pgml-dashboard/src/components/layouts/product/index/index.scss b/pgml-dashboard/src/components/layouts/product/index/index.scss new file mode 100644 index 000000000..336e2b46c --- /dev/null +++ b/pgml-dashboard/src/components/layouts/product/index/index.scss @@ -0,0 +1 @@ +div[data-controller="layouts-product-index"] {} diff --git a/pgml-dashboard/src/components/layouts/product/index/mod.rs b/pgml-dashboard/src/components/layouts/product/index/mod.rs new file mode 100644 index 000000000..40566663b --- /dev/null +++ b/pgml-dashboard/src/components/layouts/product/index/mod.rs @@ -0,0 +1,103 @@ +use pgml_components::component; +use sailfish::TemplateOnce; + +use pgml_components::Component; + +pub use crate::components::{self, cms::index_link::IndexLink, NavLink, StaticNav, StaticNavLink}; +use crate::{Notification, NotificationLevel}; +use components::notifications::product::ProductBanner; + +use crate::components::layouts::Head; +use crate::models::Cluster; + +#[derive(TemplateOnce, Default, Clone)] +#[template(path = "layouts/product/index/template.html")] +pub struct Index<'a> { + pub content: Option, + pub breadcrumbs: Vec>, + pub head: Head, + pub dropdown_nav: StaticNav, + pub product_left_nav: StaticNav, + pub body_components: Vec, + pub cluster: Cluster, + pub product_banners_high: Vec, + pub product_banner_medium: ProductBanner, + pub product_banner_marketing: ProductBanner, +} + +impl<'a> Index<'a> { + pub fn new(title: &str, context: &crate::guards::Cluster) -> Self { + let head = Head::new().title(title).context(&context.context.head_items); + let cluster = context.context.cluster.clone(); + + let all_product_high_level = context + .notifications + .clone() + .unwrap_or_else(|| vec![]) + .into_iter() + .filter(|n: &Notification| n.level == NotificationLevel::ProductHigh) + .enumerate() + .map(|(i, n)| ProductBanner::from_notification(Some(&n)).set_show_modal_on_load(i == 0)) + .collect::>(); + + Index { + head, + cluster, + dropdown_nav: context.context.dropdown_nav.clone(), + product_left_nav: context.context.product_left_nav.clone(), + product_banners_high: all_product_high_level, + product_banner_medium: ProductBanner::from_notification(Notification::next_product_of_level( + context, + NotificationLevel::ProductMedium, + )), + product_banner_marketing: ProductBanner::from_notification(Notification::next_product_of_level( + context, + NotificationLevel::ProductMarketing, + )), + body_components: context.context.body_components.clone(), + ..Default::default() + } + } + + pub fn breadcrumbs(&mut self, breadcrumbs: Vec>) -> &mut Self { + self.breadcrumbs = breadcrumbs.to_owned(); + self + } + + pub fn disable_upper_nav(&mut self) -> &mut Self { + let links: Vec = self + .product_left_nav + .links + .iter() + .map(|item| item.to_owned().disabled(true)) + .collect(); + self.product_left_nav = StaticNav { links }; + self + } + + pub fn content(&mut self, content: &str) -> &mut Self { + self.content = Some(content.to_owned()); + self + } + + pub fn body_components(&mut self, components: Vec) -> &mut Self { + self.body_components.extend(components); + 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: Index) -> String { + layout.render_once().unwrap() + } +} + +component!(Index, 'a); diff --git a/pgml-dashboard/templates/layout/web_app_base.html b/pgml-dashboard/src/components/layouts/product/index/template.html similarity index 100% rename from pgml-dashboard/templates/layout/web_app_base.html rename to pgml-dashboard/src/components/layouts/product/index/template.html diff --git a/pgml-dashboard/src/components/layouts/product/mod.rs b/pgml-dashboard/src/components/layouts/product/mod.rs new file mode 100644 index 000000000..e751c5bc8 --- /dev/null +++ b/pgml-dashboard/src/components/layouts/product/mod.rs @@ -0,0 +1,6 @@ +// This file is automatically generated. +// You shouldn't modify it manually. + +// src/components/layouts/product/index +pub mod index; +pub use index::Index; diff --git a/pgml-dashboard/src/guards.rs b/pgml-dashboard/src/guards.rs index 3e8d4fb94..9602366ac 100644 --- a/pgml-dashboard/src/guards.rs +++ b/pgml-dashboard/src/guards.rs @@ -62,6 +62,7 @@ impl Cluster { }, marketing_footer: MarketingFooter::new().render_once().unwrap(), head_items: None, + body_components: Vec::new(), }, notifications: None, } diff --git a/pgml-dashboard/src/lib.rs b/pgml-dashboard/src/lib.rs index 0ac7994fd..dac5d9edb 100644 --- a/pgml-dashboard/src/lib.rs +++ b/pgml-dashboard/src/lib.rs @@ -26,11 +26,13 @@ use guards::Cluster; use responses::{Error, Response, ResponseOk}; use templates::{components::StaticNav, *}; +use crate::components::layouts::product::Index as Product; use crate::components::tables::serverless_models::{ServerlessModels, ServerlessModelsTurbo}; use crate::components::tables::serverless_pricing::{ServerlessPricing, ServerlessPricingTurbo}; use crate::utils::cookies::{NotificationCookie, Notifications}; use crate::utils::urls; use chrono; +use pgml_components::Component; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -53,6 +55,7 @@ pub struct Context { pub product_left_nav: StaticNav, pub marketing_footer: String, pub head_items: Option, + pub body_components: Vec, } #[derive(Debug, Clone, Default)] @@ -323,7 +326,7 @@ pub async fn dashboard(tab: Option<&str>, id: Option) -> Redirect { #[get("/playground")] pub async fn playground(cluster: &Cluster) -> Result { - let mut layout = crate::templates::WebAppBase::new("Playground", &cluster); + let mut layout = Product::new("Playground", &cluster); Ok(ResponseOk(layout.render(templates::Playground {}))) } diff --git a/pgml-dashboard/src/templates/mod.rs b/pgml-dashboard/src/templates/mod.rs index 3501350ac..2f7df4c88 100644 --- a/pgml-dashboard/src/templates/mod.rs +++ b/pgml-dashboard/src/templates/mod.rs @@ -2,11 +2,9 @@ use pgml_components::Component; use std::collections::HashMap; pub use crate::components::{self, cms::index_link::IndexLink, NavLink, StaticNav, StaticNavLink}; -use crate::{Notification, NotificationLevel}; +use crate::Notification; use components::notifications::marketing::{AlertBanner, FeatureBanner}; -use components::notifications::product::ProductBanner; -use crate::models::Cluster; use sailfish::TemplateOnce; use sqlx::postgres::types::PgMoney; use sqlx::types::time::PrimitiveDateTime; @@ -39,17 +37,19 @@ pub struct Layout { pub footer: Option, pub alert_banner: AlertBanner, pub feature_banner: FeatureBanner, + pub body_components: Vec, } impl Layout { pub fn new(title: &str, context: Option<&crate::guards::Cluster>) -> Self { - let (head, footer, user) = match context.as_ref() { + let (head, footer, user, body_components) = match context.as_ref() { Some(context) => ( Head::new().title(title).context(&context.context.head_items), Some(context.context.marketing_footer.clone()), Some(context.context.user.clone()), + context.context.body_components.clone(), ), - None => (Head::new().title(title), None, None), + None => (Head::new().title(title), None, None, Vec::new()), }; Layout { @@ -58,6 +58,7 @@ impl Layout { user, alert_banner: AlertBanner::from_notification(Notification::next_alert(context)), feature_banner: FeatureBanner::from_notification(Notification::next_feature(context)), + body_components, ..Default::default() } } @@ -112,95 +113,6 @@ impl From for String { } } -#[derive(TemplateOnce, Clone, Default)] -#[template(path = "layout/web_app_base.html")] -pub struct WebAppBase<'a> { - pub content: Option, - pub breadcrumbs: Vec>, - pub head: Head, - pub dropdown_nav: StaticNav, - pub product_left_nav: StaticNav, - pub body_components: Vec, - pub cluster: Cluster, - pub product_banners_high: Vec, - pub product_banner_medium: ProductBanner, - pub product_banner_marketing: ProductBanner, -} - -impl<'a> WebAppBase<'a> { - pub fn new(title: &str, context: &crate::guards::Cluster) -> Self { - let head = Head::new().title(title).context(&context.context.head_items); - let cluster = context.context.cluster.clone(); - - let all_product_high_level = context - .notifications - .clone() - .unwrap_or_else(|| vec![]) - .into_iter() - .filter(|n: &Notification| n.level == NotificationLevel::ProductHigh) - .enumerate() - .map(|(i, n)| ProductBanner::from_notification(Some(&n)).set_show_modal_on_load(i == 0)) - .collect::>(); - - WebAppBase { - head, - cluster, - dropdown_nav: context.context.dropdown_nav.clone(), - product_left_nav: context.context.product_left_nav.clone(), - product_banners_high: all_product_high_level, - product_banner_medium: ProductBanner::from_notification(Notification::next_product_of_level( - context, - NotificationLevel::ProductMedium, - )), - product_banner_marketing: ProductBanner::from_notification(Notification::next_product_of_level( - context, - NotificationLevel::ProductMarketing, - )), - ..Default::default() - } - } - - pub fn breadcrumbs(&mut self, breadcrumbs: Vec>) -> &mut Self { - self.breadcrumbs = breadcrumbs.to_owned(); - self - } - - pub fn disable_upper_nav(&mut self) -> &mut Self { - let links: Vec = self - .product_left_nav - .links - .iter() - .map(|item| item.to_owned().disabled(true)) - .collect(); - self.product_left_nav = StaticNav { links }; - self - } - - pub fn content(&mut self, content: &str) -> &mut Self { - self.content = Some(content.to_owned()); - self - } - - pub fn body_components(&mut self, components: Vec) -> &mut Self { - self.body_components = components; - 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/static/css/modules.scss b/pgml-dashboard/static/css/modules.scss index c1d3fc2d6..09d3541f0 100644 --- a/pgml-dashboard/static/css/modules.scss +++ b/pgml-dashboard/static/css/modules.scss @@ -40,6 +40,7 @@ @import "../../src/components/layouts/marketing/base/base.scss"; @import "../../src/components/layouts/marketing/sections/three_column/card/card.scss"; @import "../../src/components/layouts/marketing/sections/three_column/index/index.scss"; +@import "../../src/components/layouts/product/index/index.scss"; @import "../../src/components/left_nav_menu/left_nav_menu.scss"; @import "../../src/components/loading/dots/dots.scss"; @import "../../src/components/loading/message/message.scss"; diff --git a/pgml-dashboard/templates/layout/base.html b/pgml-dashboard/templates/layout/base.html index 3fe8cf159..eb10f7d18 100644 --- a/pgml-dashboard/templates/layout/base.html +++ b/pgml-dashboard/templates/layout/base.html @@ -8,6 +8,9 @@ <%+ head %> + <% for component in body_components {%> + <%+ component %> + <% } %>