1 use std
::collections
::HashMap
;
3 use serde
::{Deserialize, Serialize}
;
6 use proxmox_http
::client
::sync
::Client
;
7 use proxmox_http
::{HttpClient, HttpOptions, ProxyConfig}
;
8 use proxmox_schema
::api_types
::COMMENT_SCHEMA
;
9 use proxmox_schema
::{api, Updater}
;
11 use crate::context
::context
;
12 use crate::renderer
::TemplateType
;
13 use crate::schema
::ENTITY_NAME_SCHEMA
;
14 use crate::{renderer, Content, Endpoint, Error, Notification, Origin, Severity}
;
16 fn severity_to_priority(level
: Severity
) -> u32 {
19 Severity
::Notice
=> 3,
20 Severity
::Warning
=> 5,
22 Severity
::Unknown
=> 3,
26 pub(crate) const GOTIFY_TYPENAME
: &str = "gotify";
31 schema
: ENTITY_NAME_SCHEMA
,
35 schema
: COMMENT_SCHEMA
,
39 #[derive(Serialize, Deserialize, Updater, Default)]
40 #[serde(rename_all = "kebab-case")]
41 /// Config for Gotify notification endpoints
42 pub struct GotifyConfig
{
43 /// Name of the endpoint.
46 /// Gotify Server URL.
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub comment
: Option
<String
>,
52 #[serde(skip_serializing)]
54 pub filter
: Option
<String
>,
55 /// Disable this target.
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub disable
: Option
<bool
>,
58 /// Origin of this config entry.
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub origin
: Option
<Origin
>,
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)
70 pub struct GotifyPrivateConfig
{
71 /// Name of the endpoint
74 /// Authentication token
78 /// A Gotify notification endpoint.
79 pub struct GotifyEndpoint
{
80 pub config
: GotifyConfig
,
81 pub private_config
: GotifyPrivateConfig
,
85 #[derive(Serialize, Deserialize)]
86 #[serde(rename_all = "kebab-case")]
87 pub enum DeleteableGotifyProperty
{
94 impl Endpoint
for GotifyEndpoint
{
95 fn send(&self, notification
: &Notification
) -> Result
<(), Error
> {
96 let (title
, message
) = match ¬ification
.content
{
102 renderer
::render_template(TemplateType
::Subject
, template_name
, data
)?
;
103 let rendered_message
=
104 renderer
::render_template(TemplateType
::PlaintextBody
, template_name
, data
)?
;
106 (rendered_title
, rendered_message
)
108 #[cfg(feature = "mail-forwarder")]
109 Content
::ForwardedMail { title, body, .. }
=> (title
.clone(), body
.clone()),
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```");
119 "priority": severity_to_priority(notification
.metadata
.severity
),
122 "contentType": "text/markdown"
127 let body
= serde_json
::to_vec(&body
)
128 .map_err(|err
| Error
::NotifyFailed(self.name().to_string(), err
.into()))?
;
129 let extra_headers
= HashMap
::from([
131 "Authorization".into(),
132 format
!("Bearer {}", self.private_config
.token
),
134 ("X-Gotify-Key".into(), self.private_config
.token
.clone()),
137 let proxy_config
= context()
139 .map(|url
| ProxyConfig
::parse_proxy_url(&url
))
141 .map_err(|err
| Error
::NotifyFailed(self.name().to_string(), err
.into()))?
;
143 let options
= HttpOptions
{
148 let client
= Client
::new(options
);
149 let uri
= format
!("{}/message", self.config
.server
);
154 Some(body
.as_slice()),
155 Some("application/json"),
156 Some(&extra_headers
),
158 .map_err(|err
| Error
::NotifyFailed(self.name().to_string(), err
.into()))?
;
163 fn name(&self) -> &str {
167 /// Check if the endpoint is disabled
168 fn disabled(&self) -> bool
{
169 self.config
.disable
.unwrap_or_default()