From cf6f30adfcf239a55711b1ee64dc7595e8e0c9d9 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 5 Sep 2018 23:03:29 +0200 Subject: [PATCH] Added combined notifications (#639) If multiple servers go up or down together, you don't get spammed with notifications if you have this option enabled. It will send one combined notification. --- CHANGELOG.rst | 1 + src/includes/functions.inc.php | 30 +- src/lang/en_US.lang.php | 21 +- .../Config/Controller/ConfigController.php | 3 + src/psm/Util/Install/Installer.php | 6 +- src/psm/Util/Server/UpdateManager.php | 3 + .../Util/Server/Updater/StatusNotifier.php | 276 +++++++-- .../default/module/config/config.tpl.html | 573 +++++++++--------- 8 files changed, 570 insertions(+), 343 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 338b2aa9..22f5c1c2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Changelog Not yet released ---------------- +* #639: Added combined notifications. * #626: Added redirect check. * #627: Latest server output, error and output during a failure will be saved and are shown on the server page. * #631: Added option to specify the request method. diff --git a/src/includes/functions.inc.php b/src/includes/functions.inc.php index a0e3b32d..8bf71c47 100644 --- a/src/includes/functions.inc.php +++ b/src/includes/functions.inc.php @@ -33,8 +33,9 @@ /** * Retrieve language settings from the selected language file + * Return false if arg is not found * - * @return string + * @return string|bool * @see psm_load_lang() */ function psm_get_lang() { @@ -62,7 +63,7 @@ function psm_get_lang() { $lang = $GLOBALS['sm_lang_default']; foreach ($args as $translation) { - $lang = $lang[$translation]; + $lang = $lang[$translation]; } return $lang; } @@ -311,15 +312,20 @@ function psm_log_uptime($server_id, $status, $latency) { /** * Parses a string from the language file with the correct variables replaced in the message * - * @param boolean $status + * @param boolean|null $status * @param string $type is either 'sms', 'email', 'pushover_title', 'pushover_message' or 'telegram_message' - * @param array $server information about the server which may be placed in a message: %KEY% will be replaced by your value + * @param array $vars server information about the server which may be placed in a message: %KEY% will be replaced by your value + * @param boolean $combi parse other message if notifications need to be send combined * @return string parsed message */ -function psm_parse_msg($status, $type, $vars) { - $status = ($status === true) ? 'on' : 'off'; +function psm_parse_msg($status, $type, $vars, $combi = false) { + if(is_bool($status)) { + $status = ($status === true) ? 'on_' : 'off_'; + } - $message = psm_get_lang('notifications', $status.'_'.$type); + $combi = ($combi === true) ? 'combi_' : ''; + + $message = psm_get_lang('notifications', $combi.$status.$type); if (!$message) { return $message; @@ -817,6 +823,12 @@ class telegram $this->_user = (string) $user; } public function setMessage($message) { + $message = str_replace("","\n",$message); + $message = str_replace("
  • ","- ",$message); + $message = str_replace("
  • ","\n",$message); + $message = str_replace("
    ","\n",$message); + $message = str_replace("
    ","\n",$message); $this->_message = (string) $message; } public function sendurl() { @@ -830,7 +842,9 @@ class telegram } public function send() { if (!Empty($this->_token) && !Empty($this->_user) && !Empty($this->_message)) { - $this->_url = 'https://api.telegram.org/bot'.urlencode($this->_token).'/sendMessage?chat_id='.urlencode($this->_user).'&text='.urlencode($this->_message); + $this->_url = 'https://api.telegram.org/bot'.urlencode($this->_token). + '/sendMessage?chat_id='.urlencode($this->_user).'&text='. + urlencode($this->_message).'&parse_mode=HTML'; } return $this->sendurl(); } diff --git a/src/lang/en_US.lang.php b/src/lang/en_US.lang.php index d474ece3..4893082a 100644 --- a/src/lang/en_US.lang.php +++ b/src/lang/en_US.lang.php @@ -272,16 +272,18 @@ $sm_lang = array( 'telegram_api_token_description' => 'Before you can use Telegram, you need to get a API token. Visit the documentation for help.', 'alert_type' => 'Select when you\'d like to be notified.', 'alert_type_description' => 'Status change: '. - 'You will receive a notifcation when a server has a change in status. So from online -> offline or offline -> online.
    '. + 'You will receive a notification when a server has a change in status. So from online -> offline or offline -> online.
    '. '
    Offline: '. 'You will receive a notification when a server goes offline for the *FIRST TIME ONLY*. For example, '. - 'your cronjob is every 15 mins and your server goes down at 1 am and stays down till 6 am. '. - 'You will get 1 notification at 1 am and thats it.
    '. + 'your cronjob is every 15 minutes and your server goes down at 1 am and stays down till 6 am. '. + 'You will get 1 notification at 1 am and that\'s it.
    '. '
    Always: '. 'You will receive a notification every time the script runs and a site is down, even if the site has been offline for hours.', 'alert_type_status' => 'Status change', 'alert_type_offline' => 'Offline', 'alert_type_always' => 'Always', + 'combine_notifications' => 'Combine notifications', + 'combine_notifications_description' => 'Reduces the amount of notification by combining the notifications into 1 single notification. (This does not affect SMS notifications.)', 'alert_proxy' => 'Even if enabled, proxy is never used for services', 'alert_proxy_url' => 'Format: Host:Port', 'log_status' => 'Log status', @@ -347,7 +349,18 @@ $sm_lang = array( 'on_email_body' => "Server '%LABEL%' is running again, it was down for %LAST_OFFLINE_DURATION%:

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Date: %DATE%", 'on_pushover_title' => 'Server \'%LABEL%\' is RUNNING', 'on_pushover_message' => 'Server \'%LABEL%\' is running again, it was down for %LAST_OFFLINE_DURATION%:

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Date: %DATE%', - 'on_telegram_message' => 'Server \'%LABEL%\' is running again, it was down for %LAST_OFFLINE_DURATION%:

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Date: %DATE%', + 'on_telegram_message' => 'Server \'%LABEL%\' is running again, it was down for :

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Downtime: %LAST_OFFLINE_DURATION%
    Date: %DATE%', + 'combi_off_email_message' => "", + 'combi_off_pushover_message' => "", + 'combi_off_telegram_message' => "- Server: %LABEL%
    - IP: %IP%
    - Port: %PORT%
    - Error: %ERROR%
    - Date: %DATE%

    ", + 'combi_on_email_message' => "", + 'combi_on_pushover_message' => '', + 'combi_on_telegram_message' => '- Server: %LABEL%
    - IP: %IP%
    - Port: %PORT%
    - Downtime: %LAST_OFFLINE_DURATION%
    - Date: %DATE%

    ', + 'combi_email_subject' => 'IMPORTANT: \'%UP%\' servers UP again, \'%DOWN%\' servers DOWN', + 'combi_pushover_subject' => '\'%UP%\' servers UP again, \'%DOWN%\' servers DOWN', + 'combi_email_message' => 'The following servers went down:
    %DOWN_SERVERS%
    The following servers are up again:
    %UP_SERVERS%', + 'combi_pushover_message' => 'The following servers went down:
    %DOWN_SERVERS%
    The following servers are up again:
    %UP_SERVERS%', + 'combi_telegram_message' => 'The following servers went down:
    %DOWN_SERVERS%
    The following servers are up again:
    %UP_SERVERS%', ), 'login' => array( 'welcome_usermenu' => 'Welcome, %user_name%', diff --git a/src/psm/Module/Config/Controller/ConfigController.php b/src/psm/Module/Config/Controller/ConfigController.php index b6faa983..040d6e95 100644 --- a/src/psm/Module/Config/Controller/ConfigController.php +++ b/src/psm/Module/Config/Controller/ConfigController.php @@ -48,6 +48,7 @@ class ConfigController extends AbstractController { 'log_pushover', 'log_telegram', 'show_update', + 'combine_notifications', ); /** @@ -395,6 +396,8 @@ class ConfigController extends AbstractController { 'label_alert_type_status' => psm_get_lang('config', 'alert_type_status'), 'label_alert_type_offline' => psm_get_lang('config', 'alert_type_offline'), 'label_alert_type_always' => psm_get_lang('config', 'alert_type_always'), + 'label_combine_notifications' => psm_get_lang('config', 'combine_notifications'), + 'label_combine_notifications_description' => psm_get_lang('config', 'combine_notifications_description'), 'label_log_status' => psm_get_lang('config', 'log_status'), 'label_log_status_description' => psm_get_lang('config', 'log_status_description'), 'label_log_email' => psm_get_lang('config', 'log_email'), diff --git a/src/psm/Util/Install/Installer.php b/src/psm/Util/Install/Installer.php index ccb4a299..7df7e0aa 100644 --- a/src/psm/Util/Install/Installer.php +++ b/src/psm/Util/Install/Installer.php @@ -560,13 +560,15 @@ class Installer { * If you have a lot of server that are redirecting, * this will make sure you're servers stay online. */ - $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` ADD COLUMN `allow_http_status` VARCHAR(255) NOT NULL DEFAULT '' AFTER `pattern_online`;"; + $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` ADD COLUMN `allow_http_status` VARCHAR(255) NOT NULL DEFAULT '' AFTER `pattern_online`;"; $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` ADD `redirect_check` ENUM( 'ok','bad' ) NOT NULL DEFAULT 'ok' AFTER `allow_http_status`;"; $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` CHANGE `redirect_check` `redirect_check` ENUM('ok','bad') NOT NULL DEFAULT 'bad';"; $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` ADD COLUMN `last_error` VARCHAR(255) NULL AFTER `website_password`;"; $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` ADD COLUMN `last_error_output` TEXT NULL AFTER `last_error`;"; $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` ADD COLUMN `last_output` TEXT NULL AFTER `last_error_output`;"; $queries[] = "ALTER TABLE `".PSM_DB_PREFIX."servers` ADD COLUMN `request_method` varchar(50) NULL AFTER `port`;"; - $this->execSQL($queries); + $queries[] = "INSERT INTO `".PSM_DB_PREFIX."config` (`key`, `value`) VALUES ('combine_notifications', '1');"; + $this->execSQL($queries); + $this->log('Combined notifications enabled. Check out the config page for more info.'); } } diff --git a/src/psm/Util/Server/UpdateManager.php b/src/psm/Util/Server/UpdateManager.php index 0e8ad262..fe335f09 100644 --- a/src/psm/Util/Server/UpdateManager.php +++ b/src/psm/Util/Server/UpdateManager.php @@ -79,5 +79,8 @@ class UpdateManager implements ContainerAwareInterface { $archive->archive($server['server_id']); $archive->cleanup($server['server_id']); } + if($notifier->combine){ + $notifier->notifyCombined(); + } } } diff --git a/src/psm/Util/Server/Updater/StatusNotifier.php b/src/psm/Util/Server/Updater/StatusNotifier.php index c8d65e3f..21a433ce 100644 --- a/src/psm/Util/Server/Updater/StatusNotifier.php +++ b/src/psm/Util/Server/Updater/StatusNotifier.php @@ -72,6 +72,23 @@ class StatusNotifier { */ protected $save_logs = false; + /** + * Send multiple notifications as one? + * @var boolean $combine + */ + public $combine = false; + + /** + * Notification list + * @var array $combiNotification + */ + protected $combiNotification = array( + 'count' => array(), + 'users' => array(), + 'notifications' => array(), + 'userNotifications' => array() + ); + /** * Server id * @var int $server_id @@ -104,18 +121,26 @@ class StatusNotifier { $this->send_pushover = psm_get_conf('pushover_status'); $this->send_telegram = psm_get_conf('telegram_status'); $this->save_logs = psm_get_conf('log_status'); + $this->combine = psm_get_conf('combine_notifications'); } - /** - * This function initializes the sending (text msg & email) and logging - * - * @param int $server_id - * @param boolean $status_old - * @param boolean $status_new - * @return boolean - */ + /** + * This function initializes the sending (text msg, email, Pushover and Telegram) and logging + * + * @param int $server_id + * @param boolean $status_old + * @param boolean $status_new + * @return boolean + * @throws \PHPMailer\PHPMailer\Exception + */ public function notify($server_id, $status_old, $status_new) { - if (!$this->send_emails && !$this->send_sms && !$this->save_logs) { + if ( + !$this->send_emails && + !$this->send_sms && + !$this->send_pushover && + !$this->send_telegram && + !$this->save_logs + ) { // seems like we have nothing to do. skip the rest return false; } @@ -125,10 +150,12 @@ class StatusNotifier { $this->status_new = $status_new; // get server info from db + // only get info that will be put into the notification + // or is needed to check if a notification need to be send $this->server = $this->db->selectRow(PSM_DB_PREFIX.'servers', array( 'server_id' => $server_id, ), array( - 'server_id', 'ip', 'port', 'label', 'type', 'pattern', 'status', 'header_name', 'header_value', 'error', 'active', 'email', 'sms', 'pushover', 'telegram', 'last_online', 'last_offline', 'last_offline_duration', + 'server_id', 'ip', 'port', 'label', 'error', 'email', 'sms', 'pushover', 'telegram', 'last_online', 'last_offline', 'last_offline_duration', )); if (empty($this->server)) { return false; @@ -177,14 +204,19 @@ class StatusNotifier { return $notify; } + if($this->combine){ + $this->setCombi('init', $users); + } + // check if email is enabled for this server if ($this->send_emails && $this->server['email'] == 'yes') { // send email - $this->notifyByEmail($users); + $this->combine ? $this->setCombi('email') : $this->notifyByEmail($users); } // check if sms is enabled for this server if ($this->send_sms && $this->server['sms'] == 'yes') { + // sms will not be send combined as some gateways don't support long sms / charge extra // yay lets wake those nerds up! $this->notifyByTxtMsg($users); } @@ -192,31 +224,143 @@ class StatusNotifier { // check if pushover is enabled for this server if ($this->send_pushover && $this->server['pushover'] == 'yes') { // yay lets wake those nerds up! - $this->notifyByPushover($users); + $this->combine ? $this->setCombi('pushover') : $this->notifyByPushover($users); } // check if telegram is enabled for this server if ($this->send_telegram && $this->server['telegram'] == 'yes') { - // yay lets wake those nerds up! - $this->notifyByTelegram($users); + $this->combine ? $this->setCombi('telegram') : $this->notifyByTelegram($users); } return $notify; } - /** - * This functions performs the email notifications - * - * @param array $users - * @return boolean - */ - protected function notifyByEmail($users) { + /** + * This functions collects all of the notifications + * + * @param string $method notification method + * @param array $users Users + * @return void + */ + public function setCombi($method, $users = array()) { + $status = $this->status_new ? 'on' : 'off'; + + if ($method == 'init' && !empty($users)){ + foreach($users as $user) { + if(!isset($this->combiNotification['count'][$user['user_id']])){ + $this->combiNotification['count'][$user['user_id']] = array('on' => 0, 'off' => 0); + } + $this->combiNotification['userNotifications'][$user['user_id']][] = $this->server_id; + $this->combiNotification['users'][$user['user_id']] = $user; + $this->combiNotification['count'][$user['user_id']][$status] += 1; + } + return; + } + + $this->combiNotification['notifications'][$method][$status][$this->server_id] = + psm_parse_msg($this->status_new, $method.'_message', $this->server, true); + return; + } + + /** + * This functions returns the subject for a combined notification + * + * @return void + */ + public function notifyCombined() { + if(empty($this->combiNotification['userNotifications'])){ + return; + } + // Get the servers the user will get notified of + $this->status_new = true; + foreach ($this->combiNotification['userNotifications'] as $user => $servers) { + $notifications = array(); + // Combine all of the messages belonging to the server the user will get notification of + foreach ($servers as $server) { + foreach ($this->combiNotification['notifications'] as $method => $status){ + foreach ($status as $the_status => $value) { + if(!key_exists($method, $notifications)){ + $notifications[$method] = array('on' => '', 'off' => ''); + } + if(key_exists($server, $status[$the_status])){ + $notifications[$method][$the_status] .= $status[$the_status][$server]; + } + // Set $this->status_new to false if a server is down. + // This is used by Pushover to determine the priority. + if(!empty($notifications[$method]['off'])){ + $this->status_new = false; + } + } + } + } + // Send combined notification per user + foreach ($notifications as $method => $notification){ + $finalNotification['message'] = $this->createCombiMessage($method, $notification); + $subject = $this->createCombiSubject($method, $user); + if(!is_null($subject)){ + $finalNotification['subject'] = $subject; + } + $this->{'notifyBy' . ucwords($method)} + (array($this->combiNotification['users'][$user]), $finalNotification); + } + } + unset($notifications); + return; + } + + /** + * This functions returns the message for a combined notification + * + * @param string $method Notification method + * @param array $notification Notification + * @return string + */ + protected function createCombiMessage($method, $notification){ + if(empty($notification['off'])){ + $notification['off'] = ""; + } + if(empty($notification['on'])){ + $notification['on'] = ""; + } + $vars = array('DOWN_SERVERS' => $notification['off'], 'UP_SERVERS' => $notification['on']); + return psm_parse_msg(null, $method.'_message', $vars, true); + } + + /** + * This functions returns the subject for a combined notification + * + * @param string $method Notification method + * @param integer $user_id User id + * @return string|null + */ + protected function createCombiSubject($method, $user_id){ + //die(var_dump($GLOBALS['sm_lang_default']['notifications']['combi_'.$method.'_subject'])); + $vars = array('DOWN' => $this->combiNotification['count'][$user_id]['off'], 'UP' => $this->combiNotification['count'][$user_id]['on']); + $translation = isset($GLOBALS['sm_lang_default']['notifications']['combi_'.$method.'_subject']) ? + psm_parse_msg(null, $method.'_subject', $vars, true) : + null; + return $translation; + } + + /** + * This functions performs the email notifications + * + * @param \PDOStatement $users + * @param array $combi contains message and subject (optional) + * @return void + * @throws \PHPMailer\PHPMailer\Exception + */ + protected function notifyByEmail($users, $combi = array()) { // build mail object with some default values $mail = psm_build_mail(); - $mail->Subject = psm_parse_msg($this->status_new, 'email_subject', $this->server); + $mail->Subject = key_exists('subject', $combi) ? + $combi['subject'] : + psm_parse_msg($this->status_new, 'email_subject', $this->server); $mail->Priority = 1; - $body = psm_parse_msg($this->status_new, 'email_body', $this->server); + $body = key_exists('message', $combi) ? + $combi['message'] : + psm_parse_msg($this->status_new, 'email_body', $this->server); $mail->Body = $body; $mail->AltBody = str_replace('
    ', "\n", $body); @@ -230,20 +374,22 @@ class StatusNotifier { psm_add_log_user($log_id, $user['user_id']); } - // we sent a seperate email to every single user. + // we sent a separate email to every single user. $mail->AddAddress($user['email'], $user['name']); $mail->Send(); $mail->ClearAddresses(); } + return; } /** * This functions performs the pushover notifications * - * @param array $users - * @return boolean + * @param \PDOStatement $users + * @param array $combi contains message and subject (optional) + * @return void */ - protected function notifyByPushover($users) { + protected function notifyByPushover($users, $combi = array()) { // Remove users that have no pushover_key foreach ($users as $k => $user) { if (trim($user['pushover_key']) == '') { @@ -257,7 +403,10 @@ class StatusNotifier { } // Pushover - $message = psm_parse_msg($this->status_new, 'pushover_message', $this->server); + $message = key_exists('message', $combi) ? + $combi['message'] : + psm_parse_msg($this->status_new, 'pushover_message', $this->server); + $pushover = psm_build_pushover(); if ($this->status_new === true) { $pushover->setPriority(0); @@ -266,7 +415,11 @@ class StatusNotifier { $pushover->setRetry(300); //Used with Priority = 2; Pushover will resend the notification every 60 seconds until the user accepts. $pushover->setExpire(3600); //Used with Priority = 2; Pushover will resend the notification every 60 seconds for 3600 seconds. After that point, it stops sending notifications. } - $pushover->setTitle(psm_parse_msg($this->status_new, 'pushover_title', $this->server)); + $title = key_exists('subject', $combi) ? + $combi['subject'] : + psm_parse_msg($this->status_new, 'pushover_title', $this->server); + $pushover->setHtml(1); + $pushover->setTitle($title); $pushover->setMessage(str_replace('
    ', "\n", $message)); $pushover->setUrl(psm_build_url()); $pushover->setUrlTitle(psm_get_lang('system', 'title')); @@ -294,7 +447,7 @@ class StatusNotifier { /** * This functions performs the text message notifications * - * @param array $users + * @param \PDOStatement $users * @return boolean */ protected function notifyByTxtMsg($users) { @@ -329,39 +482,44 @@ class StatusNotifier { /** * This functions performs the telegram notifications * - * @param array $users - * @return boolean + * @param \PDOStatement $users + * @param array $combi contains message and subject (optional) + * @return void */ - protected function notifyByTelegram($users) { - // Remove users that have no telegram_id - foreach ($users as $k => $user) { - if (trim($user['telegram_id']) == '') { - unset($users[$k]); - } - } + protected function notifyByTelegram($users, $combi = array()) { + // Remove users that have no telegram_id + foreach ($users as $k => $user) { + if (trim($user['telegram_id']) == '') { + unset($users[$k]); + } + } - // Validation - if (empty($users)) { - return; - } + // Validation + if (empty($users)) { + return; + } - // Telegram - $message = psm_parse_msg($this->status_new, 'telegram_message', $this->server); - $telegram = psm_build_telegram(); - $telegram->setMessage(str_replace('
    ', "\n", $message)); - // Log - if (psm_get_conf('log_telegram')) { - $log_id = psm_add_log($this->server_id, 'telegram', $message); - } - foreach ($users as $user) { - // Log - if (!empty($log_id)) { - psm_add_log_user($log_id, $user['user_id']); + // Telegram + $message = key_exists('message', $combi) ? + $combi['message'] : + psm_parse_msg($this->status_new, 'telegram_message', $this->server); + $telegram = psm_build_telegram(); + $telegram->setMessage($message); + + // Log + if (psm_get_conf('log_telegram')) { + $log_id = psm_add_log($this->server_id, 'telegram', $message); } - $telegram->setUser($user['telegram_id']); - $telegram->send(); - } - } + + foreach ($users as $user) { + // Log + if (!empty($log_id)) { + psm_add_log_user($log_id, $user['user_id']); + } + $telegram->setUser($user['telegram_id']); + $telegram->send(); + } + } /** * Get all users for the provided server id diff --git a/src/templates/default/module/config/config.tpl.html b/src/templates/default/module/config/config.tpl.html index a262c508..fca9463d 100644 --- a/src/templates/default/module/config/config.tpl.html +++ b/src/templates/default/module/config/config.tpl.html @@ -1,293 +1,326 @@ {% import 'main/macros.tpl.html' as macro %}
    -
    -
    -
    - {{ label_general }} -
    - -
    - +
    +
    +
    + {{ label_general }} +
    + +
    + +
    -
    -
    -
    - +
    +
    + +
    -
    -
    - -
    -  {{ label_seconds }} +
    + +
    +  {{ + label_seconds }} +
    -
    -
    - -
    - -
    -
    -
    -
    - {{ label_settings_notification }} -
    - -
    - -

    {{ label_alert_type_description|raw }}

    +
    + +
    + +
    -
    -
    -
    - {{ label_settings_log }} -
    -
    - -
    -
    -
    - -
    -  {{ label_log_retention_days }} +
    +
    + {{ label_settings_notification }} +
    +
    + +

    {{ label_combine_notifications_description }}

    +
    -
    - {{ label_settings_proxy }} -
    -
    - -

    {{ label_alert_proxy|raw }}

    +
    + +
    + +

    {{ label_alert_type_description|raw }}

    +
    -
    -
    - -
    - -

    {{ label_alert_proxy_url|raw }}

    + +
    + {{ label_settings_log }} +
    +
    + +
    -
    -
    - -
    - +
    + +
    +  {{ + label_log_retention_days }} +
    -
    -
    - -
    - + {{ label_settings_proxy }} +
    +
    + +

    {{ label_alert_proxy|raw }}

    +
    -
    -
    - -
    - -
    - -
    -
    - {{ label_settings_sms }} -
    -
    - -
    -
    -
    -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    -
    - {{ label_settings_pushover }} -
    -
    - -

    {{ label_pushover_description|raw }}

    -
    -
    -
    -
    - -
    -
    -
    - -
    -

    - -

    {{ label_pushover_api_token_description|raw }}

    -
    -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    -
    - {{ label_settings_telegram }} -
    -
    - -

    {{ label_telegram_description|raw }}

    -
    +
    -
    -
    - -
    + -
    - -
    - -

    {{ label_telegram_api_token_description|raw }}

    -
    +
    +
    + {{ label_settings_sms }} +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    -
    -
    - - -
    +
    +
    + {{ label_settings_pushover }} +
    +
    + +

    {{ label_pushover_description|raw }}

    +
    +
    +
    +
    + +
    +
    +
    + +
    +

    + +

    {{ label_pushover_api_token_description|raw }}

    +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    -
    - +
    +
    + {{ label_settings_telegram }} +
    +
    + +

    {{ label_telegram_description|raw }}

    +
    +
    +
    +
    + +
    +
    +
    + +
    + +

    {{ label_telegram_api_token_description|raw }}

    +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    -
    -
    - {{ macro.csrf_input() }} - + {{ macro.csrf_input() }} + \ No newline at end of file