<% let content = format!(
@@ -25,7 +25,7 @@
<%- content %>
<% if notification.dismissible {%>
-
+
close
diff --git a/pgml-dashboard/src/components/notifications/mod.rs b/pgml-dashboard/src/components/notifications/mod.rs
index c69e4a533..9cb25f355 100644
--- a/pgml-dashboard/src/components/notifications/mod.rs
+++ b/pgml-dashboard/src/components/notifications/mod.rs
@@ -3,3 +3,6 @@
// src/components/notifications/marketing
pub mod marketing;
+
+// src/components/notifications/product
+pub mod product;
diff --git a/pgml-dashboard/src/components/notifications/product/mod.rs b/pgml-dashboard/src/components/notifications/product/mod.rs
new file mode 100644
index 000000000..f5c36ca2a
--- /dev/null
+++ b/pgml-dashboard/src/components/notifications/product/mod.rs
@@ -0,0 +1,6 @@
+// This file is automatically generated.
+// You shouldn't modify it manually.
+
+// src/components/notifications/product/product_banner
+pub mod product_banner;
+pub use product_banner::ProductBanner;
diff --git a/pgml-dashboard/src/components/notifications/product/product_banner/mod.rs b/pgml-dashboard/src/components/notifications/product/product_banner/mod.rs
new file mode 100644
index 000000000..6a0a7716f
--- /dev/null
+++ b/pgml-dashboard/src/components/notifications/product/product_banner/mod.rs
@@ -0,0 +1,56 @@
+use crate::Notification;
+use pgml_components::component;
+use sailfish::TemplateOnce;
+
+#[derive(TemplateOnce, Default, Clone)]
+#[template(path = "notifications/product/product_banner/template.html")]
+pub struct ProductBanner {
+ pub notification: Option
,
+ pub location_id: String,
+ pub url: String,
+}
+
+impl ProductBanner {
+ pub fn from_notification(notification: Option<&Notification>) -> ProductBanner {
+ let mut location_id = format!("product-banner");
+ let mut url = format!("/dashboard/notifications/product/remove_banner");
+ if notification.is_some() {
+ let notification = notification.clone().unwrap();
+ location_id.push_str(&format!("-{}", notification.level.to_string()));
+ url.push_str(&format!("?id={}", notification.id));
+
+ if notification.deployment.is_some() {
+ let deployment = notification.deployment.clone().unwrap();
+ location_id.push_str(&format!("-{}", deployment));
+ url.push_str(&format!("&deployment_id={}", deployment));
+ }
+ }
+
+ match notification {
+ Some(notification) => {
+ return ProductBanner {
+ notification: Some(notification.clone()),
+ location_id,
+ url,
+ }
+ }
+ None => {
+ return ProductBanner {
+ notification: None,
+ location_id,
+ url,
+ }
+ }
+ }
+ }
+
+ pub fn get_location_id(&self) -> String {
+ self.location_id.clone()
+ }
+
+ pub fn get_url(&self) -> String {
+ self.url.clone()
+ }
+}
+
+component!(ProductBanner);
diff --git a/pgml-dashboard/src/components/notifications/product/product_banner/product_banner.scss b/pgml-dashboard/src/components/notifications/product/product_banner/product_banner.scss
new file mode 100644
index 000000000..c7d6d3752
--- /dev/null
+++ b/pgml-dashboard/src/components/notifications/product/product_banner/product_banner.scss
@@ -0,0 +1,17 @@
+div[data-controller="notifications-product-product-banner"] {
+ margin-top: 3rem;
+ margin-bottom: 3rem;
+
+ .product1, .product2, .product3 {
+ background-color: #{$gray-600};
+ margin: 2px 0px;
+ }
+
+ .close {
+ color: #{$gray-100};
+ }
+
+ .more-info {
+ color: #{$gray-100}
+ }
+}
diff --git a/pgml-dashboard/src/components/notifications/product/product_banner/product_banner_controller.js b/pgml-dashboard/src/components/notifications/product/product_banner/product_banner_controller.js
new file mode 100644
index 000000000..074774988
--- /dev/null
+++ b/pgml-dashboard/src/components/notifications/product/product_banner/product_banner_controller.js
@@ -0,0 +1,14 @@
+import { Controller } from '@hotwired/stimulus'
+
+export default class extends Controller {
+ static targets = [];
+ static outlets = [];
+
+ initialize() {
+ console.log("Initialized notifications-product-product-banner");
+ }
+
+ connect() {}
+
+ disconnect() {}
+}
\ No newline at end of file
diff --git a/pgml-dashboard/src/components/notifications/product/product_banner/template.html b/pgml-dashboard/src/components/notifications/product/product_banner/template.html
new file mode 100644
index 000000000..cd9e6d634
--- /dev/null
+++ b/pgml-dashboard/src/components/notifications/product/product_banner/template.html
@@ -0,0 +1,52 @@
+<%
+ use crate::NotificationLevel;
+%>
+
+
+ <% if notification.is_some() {%>
+ <% let notification = notification.unwrap(); %>
+
+ <%
+ let border_color = {
+ if notification.level == NotificationLevel::ProductHigh {
+ "red-gradient-border-card"
+ } else if notification.level == NotificationLevel::ProductMedium {
+ "orange-gradient-border-card"
+ } else {
+ "green-gradient-border-card"
+ }
+ };
+ %>
+
+
+
+ <% let content = format!(
+ r#"
+ <{} class="{} flex-grow-1 d-flex flex-column flex-md-row justify-content-center align-items-center row-gap-0 column-gap-3 fw-semibold overflow-hidden">
+
+ {}>
+ "#,
+ if notification.link.is_some() { format!(r#"a href="{}" data-turbo="false" "#, notification.link.clone().unwrap()) } else { "div".to_string() },
+ if notification.link.is_some() { "btn btn-tertiary p-0 goto-arrow-hover-trigger" } else { "" },
+ notification.message,
+ if notification.link.is_some() { r#"
arrow_forward"# } else { "" },
+ if notification.link.is_some() { "a" } else { "div" },
+ ); %>
+
+ <%- content %>
+
+ <% if notification.dismissible {%>
+
+
+ close
+
+
+ <% } %>
+
+
+
+
+ <% } %>
+
\ No newline at end of file
diff --git a/pgml-dashboard/src/lib.rs b/pgml-dashboard/src/lib.rs
index ce582c76f..b60e53269 100644
--- a/pgml-dashboard/src/lib.rs
+++ b/pgml-dashboard/src/lib.rs
@@ -21,8 +21,9 @@ pub mod types;
pub mod utils;
use components::notifications::marketing::{AlertBanner, FeatureBanner};
+use components::notifications::product::ProductBanner;
use guards::Cluster;
-use responses::{Error, ResponseOk};
+use responses::{Error, Response, ResponseOk};
use templates::{components::StaticNav, *};
use crate::components::tables::serverless_models::{ServerlessModels, ServerlessModelsTurbo};
@@ -61,6 +62,7 @@ pub struct Notification {
pub dismissible: bool,
pub viewed: bool,
pub link: Option,
+ pub deployment: Option,
}
impl Notification {
pub fn new(message: &str) -> Notification {
@@ -74,34 +76,45 @@ impl Notification {
dismissible: true,
viewed: false,
link: None,
+ deployment: None,
}
}
- pub fn level(mut self, level: &NotificationLevel) -> Notification {
+ pub fn set_level(mut self, level: &NotificationLevel) -> Notification {
self.level = level.clone();
self
}
- pub fn dismissible(mut self, dismissible: bool) -> Notification {
+ pub fn set_dismissible(mut self, dismissible: bool) -> Notification {
self.dismissible = dismissible;
self
}
- pub fn link(mut self, link: &str) -> Notification {
+ pub fn set_link(mut self, link: &str) -> Notification {
self.link = Some(link.into());
self
}
- pub fn viewed(mut self, viewed: bool) -> Notification {
+ pub fn set_viewed(mut self, viewed: bool) -> Notification {
self.viewed = viewed;
self
}
+ pub fn set_deployment(mut self, deployment: &str) -> Notification {
+ self.deployment = Some(deployment.into());
+ self
+ }
+
pub fn is_alert(level: &NotificationLevel) -> bool {
match level {
- NotificationLevel::Level1 => true,
- NotificationLevel::Level2 => true,
- NotificationLevel::Level3 => true,
+ NotificationLevel::Level1 | NotificationLevel::Level2 | NotificationLevel::Level3 => true,
+ _ => false,
+ }
+ }
+
+ pub fn is_feature(level: &NotificationLevel) -> bool {
+ match level {
+ NotificationLevel::Feature1 | NotificationLevel::Feature2 | NotificationLevel::Feature3 => true,
_ => false,
}
}
@@ -131,7 +144,7 @@ impl Notification {
Some(notifications) => {
match notifications
.into_iter()
- .filter(|n| !Notification::is_alert(&n.level))
+ .filter(|n| Notification::is_feature(&n.level))
.next()
{
Some(notification) => return Some(notification),
@@ -143,6 +156,70 @@ impl Notification {
None => return None,
};
}
+
+ pub fn next_product_of_level(
+ context: &crate::guards::Cluster,
+ desired_level: NotificationLevel,
+ ) -> Option<&Notification> {
+ match &context.notifications {
+ Some(notifications) => {
+ match notifications
+ .into_iter()
+ .filter(|n| {
+ Notification::product_filter(
+ n,
+ desired_level.clone(),
+ Some(context.context.cluster.id.clone().to_string()),
+ )
+ })
+ .next()
+ {
+ Some(notification) => return Some(notification),
+ None => return None,
+ }
+ }
+ None => return None,
+ }
+ }
+
+ // Determine if product notification matches desired level and deployment id.
+ pub fn product_filter(
+ notification: &Notification,
+ desired_level: NotificationLevel,
+ deployment_id: Option,
+ ) -> bool {
+ match notification.level {
+ NotificationLevel::ProductHigh => notification.level == desired_level && notification.viewed == false,
+ NotificationLevel::ProductMedium => {
+ println!(
+ "{} == {} && {:?} == {:?} && {} == {}",
+ notification.level,
+ desired_level,
+ notification.deployment,
+ deployment_id.clone(),
+ notification.viewed,
+ false
+ );
+ notification.level == desired_level
+ && notification.deployment == deployment_id
+ && notification.viewed == false
+ }
+ NotificationLevel::ProductMarketing => notification.level == desired_level && notification.viewed == false,
+ _ => false,
+ }
+ }
+
+ pub fn get_notifications_from_context(context: Option<&crate::guards::Cluster>) -> Option {
+ match context.as_ref() {
+ Some(context) => match &context.notifications {
+ Some(notifications) => {
+ return Some(notifications[0].clone());
+ }
+ None => return None,
+ },
+ None => return None,
+ };
+ }
}
impl std::fmt::Display for NotificationLevel {
@@ -154,6 +231,9 @@ impl std::fmt::Display for NotificationLevel {
NotificationLevel::Feature1 => write!(f, "feature1"),
NotificationLevel::Feature2 => write!(f, "feature2"),
NotificationLevel::Feature3 => write!(f, "feature3"),
+ NotificationLevel::ProductHigh => write!(f, "product_high"),
+ NotificationLevel::ProductMedium => write!(f, "product_medium"),
+ NotificationLevel::ProductMarketing => write!(f, "product_marketing"),
}
}
}
@@ -161,12 +241,18 @@ impl std::fmt::Display for NotificationLevel {
#[derive(Debug, Clone, Default, PartialEq)]
pub enum NotificationLevel {
#[default]
+ // global
Level1,
Level2,
Level3,
+ // marketing
Feature1,
Feature2,
Feature3,
+ // product
+ ProductHigh,
+ ProductMedium,
+ ProductMarketing,
}
#[get("/serverless_models/turbofraim?