]> git.proxmox.com Git - proxmox.git/blame - proxmox-notify/src/endpoints/gotify.rs
notify: endpoints: matcher: improve descriptions for API types
[proxmox.git] / proxmox-notify / src / endpoints / gotify.rs
CommitLineData
990fc8ef
LW
1use std::collections::HashMap;
2
990fc8ef 3use serde::{Deserialize, Serialize};
48657113 4use serde_json::json;
990fc8ef
LW
5
6use proxmox_http::client::sync::Client;
d44ce2c7 7use proxmox_http::{HttpClient, HttpOptions, ProxyConfig};
7cb339df 8use proxmox_schema::api_types::COMMENT_SCHEMA;
990fc8ef
LW
9use proxmox_schema::{api, Updater};
10
7cb339df 11use crate::context::context;
1516cc26 12use crate::renderer::TemplateType;
7cb339df 13use crate::schema::ENTITY_NAME_SCHEMA;
9bea76c6 14use crate::{renderer, Content, Endpoint, Error, Notification, Origin, Severity};
7cb339df 15
990fc8ef
LW
16fn severity_to_priority(level: Severity) -> u32 {
17 match level {
18 Severity::Info => 1,
19 Severity::Notice => 3,
20 Severity::Warning => 5,
21 Severity::Error => 9,
5f7ac875 22 Severity::Unknown => 3,
990fc8ef
LW
23 }
24}
25
26pub(crate) const GOTIFY_TYPENAME: &str = "gotify";
27
28#[api(
29 properties: {
30 name: {
31 schema: ENTITY_NAME_SCHEMA,
32 },
33 comment: {
34 optional: true,
35 schema: COMMENT_SCHEMA,
36 },
37 }
38)]
39#[derive(Serialize, Deserialize, Updater, Default)]
40#[serde(rename_all = "kebab-case")]
41/// Config for Gotify notification endpoints
42pub struct GotifyConfig {
08b7c501 43 /// Name of the endpoint.
990fc8ef
LW
44 #[updater(skip)]
45 pub name: String,
08b7c501 46 /// Gotify Server URL.
990fc8ef 47 pub server: String,
08b7c501 48 /// Comment.
990fc8ef
LW
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub comment: Option<String>,
b421a7ca
LW
51 /// Deprecated.
52 #[serde(skip_serializing)]
53 #[updater(skip)]
ee0ab52b 54 pub filter: Option<String>,
306f4005
LW
55 /// Disable this target.
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub disable: Option<bool>,
9bea76c6
LW
58 /// Origin of this config entry.
59 #[serde(skip_serializing_if = "Option::is_none")]
60 #[updater(skip)]
61 pub origin: Option<Origin>,
990fc8ef
LW
62}
63
64#[api()]
65#[derive(Serialize, Deserialize, Clone, Updater)]
66#[serde(rename_all = "kebab-case")]
67/// Private configuration for Gotify notification endpoints.
68/// This config will be saved to a separate configuration file with stricter
69/// permissions (root:root 0600)
70pub struct GotifyPrivateConfig {
71 /// Name of the endpoint
72 #[updater(skip)]
73 pub name: String,
74 /// Authentication token
75 pub token: String,
76}
77
78/// A Gotify notification endpoint.
79pub struct GotifyEndpoint {
80 pub config: GotifyConfig,
81 pub private_config: GotifyPrivateConfig,
82}
83
1a40d340 84#[api]
990fc8ef
LW
85#[derive(Serialize, Deserialize)]
86#[serde(rename_all = "kebab-case")]
87pub enum DeleteableGotifyProperty {
1a40d340 88 /// Delete `comment`
990fc8ef 89 Comment,
1a40d340 90 /// Delete `disable`
306f4005 91 Disable,
990fc8ef
LW
92}
93
94impl Endpoint for GotifyEndpoint {
95 fn send(&self, notification: &Notification) -> Result<(), Error> {
df4858e9
LW
96 let (title, message) = match &notification.content {
97 Content::Template {
1516cc26 98 template_name,
b421a7ca 99 data,
df4858e9
LW
100 } => {
101 let rendered_title =
1516cc26 102 renderer::render_template(TemplateType::Subject, template_name, data)?;
df4858e9 103 let rendered_message =
1516cc26 104 renderer::render_template(TemplateType::PlaintextBody, template_name, data)?;
df4858e9
LW
105
106 (rendered_title, rendered_message)
107 }
5f7ac875
LW
108 #[cfg(feature = "mail-forwarder")]
109 Content::ForwardedMail { title, body, .. } => (title.clone(), body.clone()),
df4858e9 110 };
48657113
LW
111
112 // We don't have a TemplateRenderer::Markdown yet, so simply put everything
113 // in code tags. Otherwise tables etc. are not formatted properly
114 let message = format!("```\n{message}\n```");
115
116 let body = json!({
117 "title": &title,
118 "message": &message,
b421a7ca 119 "priority": severity_to_priority(notification.metadata.severity),
48657113
LW
120 "extras": {
121 "client::display": {
122 "contentType": "text/markdown"
123 }
124 }
125 });
990fc8ef
LW
126
127 let body = serde_json::to_vec(&body)
128 .map_err(|err| Error::NotifyFailed(self.name().to_string(), err.into()))?;
6b393ac0
LW
129 let extra_headers = HashMap::from([
130 (
131 "Authorization".into(),
132 format!("Bearer {}", self.private_config.token),
133 ),
134 ("X-Gotify-Key".into(), self.private_config.token.clone()),
135 ]);
990fc8ef 136
d44ce2c7
LW
137 let proxy_config = context()
138 .http_proxy_config()
139 .map(|url| ProxyConfig::parse_proxy_url(&url))
140 .transpose()
141 .map_err(|err| Error::NotifyFailed(self.name().to_string(), err.into()))?;
142
143 let options = HttpOptions {
144 proxy_config,
145 ..Default::default()
146 };
147
148 let client = Client::new(options);
149 let uri = format!("{}/message", self.config.server);
150
990fc8ef
LW
151 client
152 .post(
153 &uri,
154 Some(body.as_slice()),
155 Some("application/json"),
156 Some(&extra_headers),
157 )
158 .map_err(|err| Error::NotifyFailed(self.name().to_string(), err.into()))?;
159
160 Ok(())
161 }
162
163 fn name(&self) -> &str {
164 &self.config.name
165 }
306f4005
LW
166
167 /// Check if the endpoint is disabled
168 fn disabled(&self) -> bool {
169 self.config.disable.unwrap_or_default()
170 }
990fc8ef 171}