all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v4 proxmox 04/69] notify: preparation for the first endpoint plugin
Date: Thu, 20 Jul 2023 16:31:31 +0200	[thread overview]
Message-ID: <20230720143236.652292-5-l.wagner@proxmox.com> (raw)
In-Reply-To: <20230720143236.652292-1-l.wagner@proxmox.com>

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---

Notes:
    Changes since v3:
      - Derive Clone for Config
      - Remove private_digest from Config
      - Add explanatory comment on why Rc<RecCell<Vec<Notification>>> is
        needed in the test code
      - Avoid linear search in Vec for private_configs
      - Minor stylistic touchups

 Cargo.toml                          |   1 +
 proxmox-notify/Cargo.toml           |   9 +
 proxmox-notify/src/config.rs        |  51 +++++
 proxmox-notify/src/endpoints/mod.rs |   0
 proxmox-notify/src/lib.rs           | 301 ++++++++++++++++++++++++++++
 proxmox-notify/src/schema.rs        |  21 ++
 6 files changed, 383 insertions(+)
 create mode 100644 proxmox-notify/src/config.rs
 create mode 100644 proxmox-notify/src/endpoints/mod.rs
 create mode 100644 proxmox-notify/src/schema.rs

diff --git a/Cargo.toml b/Cargo.toml
index 317593f0..ef8a050a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -93,6 +93,7 @@ proxmox-lang = { version = "1.1", path = "proxmox-lang" }
 proxmox-rest-server = { version = "0.4.0", path = "proxmox-rest-server" }
 proxmox-router = { version = "1.3.1", path = "proxmox-router" }
 proxmox-schema = { version = "1.3.7", path = "proxmox-schema" }
+proxmox-section-config = { version = "1.0.2", path = "proxmox-section-config" }
 proxmox-serde = { version = "0.1.1", path = "proxmox-serde", features = [ "serde_json" ] }
 proxmox-sortable-macro = { version = "0.1.2", path = "proxmox-sortable-macro" }
 proxmox-sys = { version = "0.5.0", path = "proxmox-sys" }
diff --git a/proxmox-notify/Cargo.toml b/proxmox-notify/Cargo.toml
index 2e69d5b0..2cd6278a 100644
--- a/proxmox-notify/Cargo.toml
+++ b/proxmox-notify/Cargo.toml
@@ -8,3 +8,12 @@ repository.workspace = true
 exclude.workspace = true
 
 [dependencies]
+lazy_static.workspace = true
+log.workspace = true
+openssl.workspace = true
+proxmox-schema = { workspace = true, features = ["api-macro", "api-types"]}
+proxmox-section-config = { workspace = true }
+proxmox-sys.workspace = true
+regex.workspace = true
+serde.workspace = true
+serde_json.workspace = true
diff --git a/proxmox-notify/src/config.rs b/proxmox-notify/src/config.rs
new file mode 100644
index 00000000..362ca0fc
--- /dev/null
+++ b/proxmox-notify/src/config.rs
@@ -0,0 +1,51 @@
+use lazy_static::lazy_static;
+use proxmox_schema::{ApiType, ObjectSchema};
+use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin};
+
+use crate::schema::BACKEND_NAME_SCHEMA;
+use crate::Error;
+
+lazy_static! {
+    pub static ref CONFIG: SectionConfig = config_init();
+    pub static ref PRIVATE_CONFIG: SectionConfig = private_config_init();
+}
+
+fn config_init() -> SectionConfig {
+    let mut config = SectionConfig::new(&BACKEND_NAME_SCHEMA);
+
+    config
+}
+
+fn private_config_init() -> SectionConfig {
+    let mut config = SectionConfig::new(&BACKEND_NAME_SCHEMA);
+
+    config
+}
+
+pub fn config(raw_config: &str) -> Result<(SectionConfigData, [u8; 32]), Error> {
+    let digest = openssl::sha::sha256(raw_config.as_bytes());
+    let data = CONFIG
+        .parse("notifications.cfg", raw_config)
+        .map_err(|err| Error::ConfigDeserialization(err.into()))?;
+    Ok((data, digest))
+}
+
+pub fn private_config(raw_config: &str) -> Result<(SectionConfigData, [u8; 32]), Error> {
+    let digest = openssl::sha::sha256(raw_config.as_bytes());
+    let data = PRIVATE_CONFIG
+        .parse("priv/notifications.cfg", raw_config)
+        .map_err(|err| Error::ConfigDeserialization(err.into()))?;
+    Ok((data, digest))
+}
+
+pub fn write(config: &SectionConfigData) -> Result<String, Error> {
+    CONFIG
+        .write("notifications.cfg", config)
+        .map_err(|err| Error::ConfigSerialization(err.into()))
+}
+
+pub fn write_private(config: &SectionConfigData) -> Result<String, Error> {
+    PRIVATE_CONFIG
+        .write("priv/notifications.cfg", config)
+        .map_err(|err| Error::ConfigSerialization(err.into()))
+}
diff --git a/proxmox-notify/src/endpoints/mod.rs b/proxmox-notify/src/endpoints/mod.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/proxmox-notify/src/lib.rs b/proxmox-notify/src/lib.rs
index e69de29b..deebe046 100644
--- a/proxmox-notify/src/lib.rs
+++ b/proxmox-notify/src/lib.rs
@@ -0,0 +1,301 @@
+use std::collections::HashMap;
+use std::fmt::Display;
+
+use proxmox_schema::api;
+use proxmox_section_config::SectionConfigData;
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+use serde_json::Value;
+
+use std::error::Error as StdError;
+
+mod config;
+pub mod endpoints;
+pub mod schema;
+
+#[derive(Debug)]
+pub enum Error {
+    ConfigSerialization(Box<dyn StdError + Send + Sync>),
+    ConfigDeserialization(Box<dyn StdError + Send + Sync>),
+    NotifyFailed(String, Box<dyn StdError + Send + Sync>),
+    TargetDoesNotExist(String),
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Error::ConfigSerialization(err) => {
+                write!(f, "could not serialize configuration: {err}")
+            }
+            Error::ConfigDeserialization(err) => {
+                write!(f, "could not deserialize configuration: {err}")
+            }
+            Error::NotifyFailed(endpoint, err) => {
+                write!(f, "could not notify via endpoint(s): {endpoint}: {err}")
+            }
+            Error::TargetDoesNotExist(target) => {
+                write!(f, "notification target '{target}' does not exist")
+            }
+        }
+    }
+}
+
+impl StdError for Error {
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        match self {
+            Error::ConfigSerialization(err) => Some(&**err),
+            Error::ConfigDeserialization(err) => Some(&**err),
+            Error::NotifyFailed(_, err) => Some(&**err),
+            Error::TargetDoesNotExist(_) => None,
+        }
+    }
+}
+
+#[api()]
+#[derive(Clone, Debug, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd)]
+#[serde(rename_all = "kebab-case")]
+/// Severity of a notification
+pub enum Severity {
+    /// General information
+    Info,
+    /// A noteworthy event
+    Notice,
+    /// Warning
+    Warning,
+    /// Error
+    Error,
+}
+
+/// Notification endpoint trait, implemented by all endpoint plugins
+pub trait Endpoint {
+    /// Send a documentation
+    fn send(&self, notification: &Notification) -> Result<(), Error>;
+
+    /// The name/identifier for this endpoint
+    fn name(&self) -> &str;
+}
+
+#[derive(Debug, Clone)]
+/// Notification which can be sent
+pub struct Notification {
+    /// Notification severity
+    pub severity: Severity,
+    /// The title of the notification
+    pub title: String,
+    /// Notification text
+    pub body: String,
+    /// Additional metadata for the notification
+    pub properties: Option<Value>,
+}
+
+/// Notification configuration
+#[derive(Debug, Clone)]
+pub struct Config {
+    config: SectionConfigData,
+    private_config: SectionConfigData,
+    digest: [u8; 32],
+}
+
+impl Config {
+    /// Parse raw config
+    pub fn new(raw_config: &str, raw_private_config: &str) -> Result<Self, Error> {
+        let (config, digest) = config::config(raw_config)?;
+        let (private_config, _) = config::private_config(raw_private_config)?;
+
+        Ok(Self {
+            config,
+            digest,
+            private_config,
+        })
+    }
+
+    /// Serialize config
+    pub fn write(&self) -> Result<(String, String), Error> {
+        Ok((
+            config::write(&self.config)?,
+            config::write_private(&self.private_config)?,
+        ))
+    }
+
+    /// Returns the SHA256 digest of the configuration.
+    /// The digest is only computed once when the configuration deserialized.
+    pub fn digest(&self) -> &[u8; 32] {
+        &self.digest
+    }
+}
+
+/// Notification bus - distributes notifications to all registered endpoints
+// The reason for the split between `Config` and this struct is to make testing with mocked
+// endpoints a bit easier.
+#[derive(Default)]
+pub struct Bus {
+    endpoints: HashMap<String, Box<dyn Endpoint>>,
+}
+
+#[allow(unused_macros)]
+macro_rules! parse_endpoints_with_private_config {
+    ($config:ident, $public_config:ty, $private_config:ty, $endpoint_type:ident, $type_name:expr) => {
+        (|| -> Result<Vec<Box<dyn Endpoint>>, Error> {
+            let mut endpoints = Vec::<Box<dyn Endpoint>>::new();
+
+            let configs: Vec<$public_config> = $config
+                .config
+                .convert_to_typed_array($type_name)
+                .map_err(|err| Error::ConfigDeserialization(err.into()))?;
+
+            for config in configs {
+                match $config.private_config.sections.get(&config.name) {
+                    Some((section_type_name, private_config)) => {
+                        if $type_name != section_type_name {
+                            log::error!(
+                                "Could not instantiate endpoint '{name}': \
+                                private config has wrong type",
+                                name = config.name
+                            );
+                        }
+                        let private_config = <$private_config>::deserialize(private_config)
+                            .map_err(|err| Error::ConfigDeserialization(err.into()))?;
+
+                        endpoints.push(Box::new($endpoint_type {
+                            config,
+                            private_config: private_config.clone(),
+                        }));
+                    }
+                    None => log::error!(
+                        "Could not instantiate endpoint '{name}': \
+                            private config does not exist",
+                        name = config.name
+                    ),
+                }
+            }
+
+            Ok(endpoints)
+        })()
+    };
+}
+
+#[allow(unused_macros)]
+macro_rules! parse_endpoints_without_private_config {
+    ($config:ident, $public_config:ty, $endpoint_type:ident, $type_name:expr) => {
+        (|| -> Result<Vec<Box<dyn Endpoint>>, Error> {
+            let mut endpoints = Vec::<Box<dyn Endpoint>>::new();
+
+            let configs: Vec<$public_config> = $config
+                .config
+                .convert_to_typed_array($type_name)
+                .map_err(|err| Error::ConfigDeserialization(err.into()))?;
+
+            for config in configs {
+                endpoints.push(Box::new($endpoint_type { config }));
+            }
+
+            Ok(endpoints)
+        })()
+    };
+}
+
+impl Bus {
+    /// Instantiate notification bus from a given configuration.
+    pub fn from_config(config: &Config) -> Result<Self, Error> {
+        let mut endpoints = HashMap::new();
+
+        Ok(Bus { endpoints })
+    }
+
+    #[cfg(test)]
+    pub fn add_endpoint(&mut self, endpoint: Box<dyn Endpoint>) {
+        self.endpoints.insert(endpoint.name().to_string(), endpoint);
+    }
+
+    pub fn send(&self, target: &str, notification: &Notification) -> Result<(), Error> {
+        log::info!(
+            "sending notification with title '{title}'",
+            title = notification.title
+        );
+
+        let endpoint = self
+            .endpoints
+            .get(target)
+            .ok_or(Error::TargetDoesNotExist(target.into()))?;
+
+        endpoint.send(notification).unwrap_or_else(|e| {
+            log::error!(
+                "could not notfiy via endpoint `{name}`: {e}",
+                name = endpoint.name()
+            )
+        });
+
+        Ok(())
+    }
+
+    pub fn test_target(&self, target: &str) -> Result<(), Error> {
+        let endpoint = self
+            .endpoints
+            .get(target)
+            .ok_or(Error::TargetDoesNotExist(target.into()))?;
+
+        endpoint.send(&Notification {
+            severity: Severity::Info,
+            title: "Test notification".into(),
+            body: "This is a test of the notification target '{{ target }}'".into(),
+            properties: Some(json!({ "target": target })),
+        })?;
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::{cell::RefCell, rc::Rc};
+
+    use super::*;
+
+    #[derive(Default, Clone)]
+    struct MockEndpoint {
+        // Needs to be an Rc so that we can clone MockEndpoint before
+        // passing it to Bus, while still retaining a handle to the Vec
+        messages: Rc<RefCell<Vec<Notification>>>,
+    }
+
+    impl Endpoint for MockEndpoint {
+        fn send(&self, message: &Notification) -> Result<(), Error> {
+            self.messages.borrow_mut().push(message.clone());
+
+            Ok(())
+        }
+
+        fn name(&self) -> &str {
+            "mock-endpoint"
+        }
+    }
+
+    impl MockEndpoint {
+        fn messages(&self) -> Vec<Notification> {
+            self.messages.borrow().clone()
+        }
+    }
+
+    #[test]
+    fn test_add_mock_endpoint() -> Result<(), Error> {
+        let mock = MockEndpoint::default();
+
+        let mut bus = Bus::default();
+
+        bus.add_endpoint(Box::new(mock.clone()));
+
+        bus.send(
+            "mock-endpoint",
+            &Notification {
+                title: "Title".into(),
+                body: "Body".into(),
+                severity: Severity::Info,
+                properties: Default::default(),
+            },
+        )?;
+        let messages = mock.messages();
+        assert_eq!(messages.len(), 1);
+
+        Ok(())
+    }
+}
diff --git a/proxmox-notify/src/schema.rs b/proxmox-notify/src/schema.rs
new file mode 100644
index 00000000..aebffa0d
--- /dev/null
+++ b/proxmox-notify/src/schema.rs
@@ -0,0 +1,21 @@
+use proxmox_schema::api_types::{SAFE_ID_FORMAT, SINGLE_LINE_COMMENT_FORMAT};
+use proxmox_schema::{Schema, StringSchema};
+
+pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
+    .format(&SINGLE_LINE_COMMENT_FORMAT)
+    .min_length(2)
+    .max_length(64)
+    .schema();
+
+pub const BACKEND_NAME_SCHEMA: Schema = StringSchema::new("Notification backend name.")
+    .format(&SAFE_ID_FORMAT)
+    .min_length(3)
+    .max_length(32)
+    .schema();
+
+pub const ENTITY_NAME_SCHEMA: Schema =
+    StringSchema::new("Name schema for endpoints, filters and groups")
+        .format(&SAFE_ID_FORMAT)
+        .min_length(2)
+        .max_length(32)
+        .schema();
-- 
2.39.2





  parent reply	other threads:[~2023-07-20 14:33 UTC|newest]

Thread overview: 84+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-07-20 14:31 [pve-devel] [PATCH v4 many 00/69] fix #4156: introduce new notification system Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 01/69] section-config: derive Clone for SectionConfigData Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 02/69] schema: add schema/format for comments Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 03/69] add proxmox-notify crate Lukas Wagner
2023-07-20 14:31 ` Lukas Wagner [this message]
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 05/69] notify: preparation for the API Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 06/69] notify: api: add API for sending notifications/testing endpoints Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 07/69] notify: add sendmail plugin Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 08/69] notify: api: add API for sendmail endpoints Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 09/69] notify: add gotify endpoint Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 10/69] notify: api: add API for gotify endpoints Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 11/69] notify: add notification groups Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 12/69] notify: api: add API for groups Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 13/69] notify: add notification filter mechanism Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 14/69] notify: api: add API for filters Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 15/69] notify: add template rendering Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 16/69] notify: add example for " Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 17/69] notify: add context Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 18/69] notify: sendmail: allow users as recipients Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 19/69] notify: sendmail: query default author/mailfrom from context Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 20/69] notify: gotify: add proxy support Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 21/69] notify: api: allow to query entities referenced by filter/target Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 22/69] notify: on deletion, check if a filter/endp. is still used by anything Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 23/69] notify: ensure that filter/group/endpoint names are unique Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 24/69] notify: additional logging when sending a notification Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox 25/69] notify: add debian packaging Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox-perl-rs 26/69] add PVE::RS::Notify module Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox-perl-rs 27/69] notify: add api for sending notifications/testing endpoints Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox-perl-rs 28/69] notify: add api for notification groups Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox-perl-rs 29/69] notify: add api for sendmail endpoints Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox-perl-rs 30/69] notify: add api for gotify endpoints Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox-perl-rs 31/69] notify: add api for notification filters Lukas Wagner
2023-07-20 14:31 ` [pve-devel] [PATCH v4 proxmox-perl-rs 32/69] notify: sendmail: support the `mailto-user` parameter Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-perl-rs 33/69] notify: implement context for getting default author/mailfrom Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-perl-rs 34/69] notify: add context for getting http_proxy from datacenter.cfg Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-perl-rs 35/69] notify: add wrapper for `get_referenced_entities` Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-cluster 36/69] cluster files: add notifications.cfg Lukas Wagner
2023-07-24 13:02   ` [pve-devel] partially-applied: " Wolfgang Bumiller
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-cluster 37/69] datacenter: add APT/fencing/replication notification configuration Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-cluster 38/69] add libpve-notify-perl package Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-guest-common 39/69] vzdump: add config options for new notification backend Lukas Wagner
2023-07-24 13:30   ` [pve-devel] partially-applied: " Wolfgang Bumiller
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-common 40/69] JSONSchema: increase maxLength of config-digest to 64 Lukas Wagner
2023-07-24  9:56   ` [pve-devel] applied: " Wolfgang Bumiller
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-ha-manager 41/69] manager: send notifications via new notification module Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 42/69] test: fix names of .PHONY targets Lukas Wagner
2023-07-24 13:59   ` Wolfgang Bumiller
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 43/69] d/control: add dependency to `libpve-notify-perl` Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 44/69] vzdump: send notifications via new notification module Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 45/69] test: rename mail_test.pl to vzdump_notification_test.pl Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 46/69] api: apt: send notification via new notification module Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 47/69] api: replication: send notifications " Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 48/69] api: prepare api handler module for notification config Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 49/69] api: notification: add api routes for groups Lukas Wagner
2023-07-24 13:54   ` Wolfgang Bumiller
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 50/69] api: notification: add api routes for sendmail endpoints Lukas Wagner
2023-07-24 13:55   ` Wolfgang Bumiller
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 51/69] api: notification: add api routes for gotify endpoints Lukas Wagner
2023-07-24 13:55   ` Wolfgang Bumiller
2023-07-25  9:02   ` Thomas Lamprecht
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 52/69] api: notification: add api routes for filters Lukas Wagner
2023-07-24 13:56   ` Wolfgang Bumiller
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 53/69] api: notification: allow fetching notification targets Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 54/69] api: notification: allow to test targets Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 55/69] api: notification: disallow removing targets if they are used Lukas Wagner
2023-07-24 13:50   ` Wolfgang Bumiller
2023-07-24 14:09     ` Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 56/69] ui: backup: allow to select notification target for jobs Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 57/69] ui: backup: adapt backup job details to new notification params Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 58/69] ui: backup: allow to set notification-target for one-off backups Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 59/69] ui: allow to configure notification event -> target mapping Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 60/69] ui: add notification target configuration panel Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 61/69] ui: perm path: add ACL paths for notifications, usb and pci mappings Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 62/69] ui: perm path: increase width of the perm path selector combobox Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-manager 63/69] ui: dc: remove notify key from datacenter option view Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-widget-toolkit 64/69] notification: add gui for sendmail notification endpoints Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-widget-toolkit 65/69] notification: add gui for gotify " Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-widget-toolkit 66/69] notification: add gui for notification groups Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-widget-toolkit 67/69] notification: allow to select filter for notification targets Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 proxmox-widget-toolkit 68/69] notification: add ui for managing notification filters Lukas Wagner
2023-07-20 14:32 ` [pve-devel] [PATCH v4 pve-docs 69/69] add documentation for the new notification system Lukas Wagner
2023-07-24  9:07 ` [pve-devel] partially-applied: [PATCH v4 many 00/69] fix #4156: introduce " Wolfgang Bumiller
2023-07-24  9:20 ` [pve-devel] " Thomas Lamprecht
2023-07-24  9:23 ` [pve-devel] partially-applied: " Wolfgang Bumiller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230720143236.652292-5-l.wagner@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal