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
::TemplateRenderer
;
13 use crate::schema
::ENTITY_NAME_SCHEMA
;
14 use crate::{renderer, Endpoint, Error, Notification, Severity}
;
16 fn severity_to_priority(level
: Severity
) -> u32 {
19 Severity
::Notice
=> 3,
20 Severity
::Warning
=> 5,
25 pub(crate) const GOTIFY_TYPENAME
: &str = "gotify";
30 schema
: ENTITY_NAME_SCHEMA
,
34 schema
: COMMENT_SCHEMA
,
38 schema
: ENTITY_NAME_SCHEMA
,
42 #[derive(Serialize, Deserialize, Updater, Default)]
43 #[serde(rename_all = "kebab-case")]
44 /// Config for Gotify notification endpoints
45 pub struct GotifyConfig
{
46 /// Name of the endpoint
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub comment
: Option
<String
>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub filter
: Option
<String
>,
60 #[derive(Serialize, Deserialize, Clone, Updater)]
61 #[serde(rename_all = "kebab-case")]
62 /// Private configuration for Gotify notification endpoints.
63 /// This config will be saved to a separate configuration file with stricter
64 /// permissions (root:root 0600)
65 pub struct GotifyPrivateConfig
{
66 /// Name of the endpoint
69 /// Authentication token
73 /// A Gotify notification endpoint.
74 pub struct GotifyEndpoint
{
75 pub config
: GotifyConfig
,
76 pub private_config
: GotifyPrivateConfig
,
79 #[derive(Serialize, Deserialize)]
80 #[serde(rename_all = "kebab-case")]
81 pub enum DeleteableGotifyProperty
{
86 impl Endpoint
for GotifyEndpoint
{
87 fn send(&self, notification
: &Notification
) -> Result
<(), Error
> {
88 let properties
= notification
.properties
.as_ref();
90 let title
= renderer
::render_template(
91 TemplateRenderer
::Plaintext
,
96 renderer
::render_template(TemplateRenderer
::Plaintext
, ¬ification
.body
, properties
)?
;
98 // We don't have a TemplateRenderer::Markdown yet, so simply put everything
99 // in code tags. Otherwise tables etc. are not formatted properly
100 let message
= format
!("```\n{message}\n```");
105 "priority": severity_to_priority(notification
.severity
),
108 "contentType": "text/markdown"
113 let body
= serde_json
::to_vec(&body
)
114 .map_err(|err
| Error
::NotifyFailed(self.name().to_string(), err
.into()))?
;
115 let extra_headers
= HashMap
::from([(
116 "Authorization".into(),
117 format
!("Bearer {}", self.private_config
.token
),
120 let proxy_config
= context()
122 .map(|url
| ProxyConfig
::parse_proxy_url(&url
))
124 .map_err(|err
| Error
::NotifyFailed(self.name().to_string(), err
.into()))?
;
126 let options
= HttpOptions
{
131 let client
= Client
::new(options
);
132 let uri
= format
!("{}/message", self.config
.server
);
137 Some(body
.as_slice()),
138 Some("application/json"),
139 Some(&extra_headers
),
141 .map_err(|err
| Error
::NotifyFailed(self.name().to_string(), err
.into()))?
;
146 fn name(&self) -> &str {
150 fn filter(&self) -> Option
<&str> {
151 self.config
.filter
.as_deref()