public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[Web] Feature: Allow copies of quota notifications to be sent via SMTP to internal or external addresses [Web] Various style fixes [Web] Remove unused or unnecessary dependencies
e6d5516c
data/web/admin.php | 12 ++++----
data/web/css/build/005-bootstrap-switch.min.css | 10 -------
data/web/css/build/008-mailcow.css | 12 ++++++--
data/web/css/site/admin.css | 3 ++
data/web/css/site/mailbox.css | 4 +++
data/web/css/site/quarantine.css | 4 +--
data/web/edit.php | 34 +++++++++++++++++++++--
data/web/inc/functions.mailbox.inc.php | 2 ++
data/web/inc/functions.quota_notification.inc.php | 30 +++++++++++++-------
data/web/inc/header.inc.php | 4 +--
data/web/js/build/002-bootstrap-switch.min.js | 10 -------
data/web/js/build/005-bootstrap-filestyle.min.js | 10 -------
data/web/js/site/admin.js | 16 +++++------
data/web/js/site/mailbox.js | 26 ++++++++---------
data/web/js/site/user.js | 4 +--
data/web/json_api.php | 17 +++++++++---
data/web/lang/lang.de.json | 3 +-
data/web/lang/lang.en.json | 1 +
18 files changed, 117 insertions(+), 85 deletions(-)
delete mode 100644 data/web/css/build/005-bootstrap-switch.min.css
delete mode 100644 data/web/js/build/002-bootstrap-switch.min.js
delete mode 100644 data/web/js/build/005-bootstrap-filestyle.min.js
Diff
diff --git a/data/web/admin.php b/data/web/admin.php
index 3ec9e52d..385e1bef 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -115,9 +115,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
<? // FIDO2 ?>
<legend style="margin-top:20px">
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="margin-bottom: -5px;">
- <path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"/>
- </svg>
+ <i class="bi bi-shield-fill-check"></i>
<?=$lang['fido2']['fido2_auth'];?></legend>
<div class="row">
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['fido2']['known_ids'];?>:</div>
@@ -134,7 +132,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
?>
<tr>
<td>
- <?=($_SESSION['fido2_cid'] == $key_info['cid']) ? '→ ' : NULL; ?><?=(!empty($key_info['fn']))?$key_info['fn']:$key_info['subject'];?>
+ <?=($_SESSION['fido2_cid'] == $key_info['cid']) ? '<i class="bi bi-unlock-fill"></i> ' : NULL; ?><?=(!empty($key_info['fn']))?$key_info['fn']:$key_info['subject'];?>
</td>
<td style="min-width:240px;text-align: right">
<form style="display:inline;" method="post">
@@ -1121,12 +1119,12 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['customize'];?></div>
<div class="panel-body">
- <legend><?=$lang['admin']['change_logo'];?></legend>
+ <legend><i class="bi bi-file-image"></i> <?=$lang['admin']['change_logo'];?></legend>
<p class="help-block"><?=$lang['admin']['logo_info'];?></p>
<form class="form-inline" role="form" method="post" enctype="multipart/form-data">
<p>
- <input type="file" name="main_logo" class="filestyle" data-buttonName="btn-default" data-buttonText="Select" accept="image/gif, image/jpeg, image/pjpeg, image/x-png, image/png, image/svg+xml">
- <button name="submit_main_logo" type="submit" class="btn btn-default"><i class="bi bi-cloud-arrow-up-fill"></i> <?=$lang['admin']['upload'];?></button>
+ <input type="file" name="main_logo" accept="image/gif, image/jpeg, image/pjpeg, image/x-png, image/png, image/svg+xml"><br>
+ <button name="submit_main_logo" type="submit" class="btn btn-xs btn-default"><i class="bi bi-upload"></i> <?=$lang['admin']['upload'];?></button>
</p>
</form>
<?php
diff --git a/data/web/css/build/005-bootstrap-switch.min.css b/data/web/css/build/005-bootstrap-switch.min.css
deleted file mode 100644
index cbfa013b..00000000
--- a/data/web/css/build/005-bootstrap-switch.min.css
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * bootstrap-switch - Turn checkboxes and radio buttons into toggle switches.
- *
- * @version v3.3.3
- * @homepage http://www.bootstrap-switch.org
- * @author Mattia Larentis <[email protected]> (http://larentis.eu)
- * @license Apache-2.0
- */
-
-.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid #ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block!important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary{color:#fff;background:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#5cb85c}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute!important;top:0;left:0;margin:0;z-index:-1;opacity:0;filter:alpha(opacity=0)}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.3333333}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-indeterminate,.bootstrap-switch.bootstrap-switch-readonly{cursor:default!important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default!important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;-o-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:0 3px 3px 0}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:3px 0 0 3px}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px}
\ No newline at end of file
diff --git a/data/web/css/build/008-mailcow.css b/data/web/css/build/008-mailcow.css
index 8bd703fd..8af1f96a 100644
--- a/data/web/css/build/008-mailcow.css
+++ b/data/web/css/build/008-mailcow.css
@@ -160,9 +160,6 @@ legend {
.lang-link-disabled {
cursor: not-allowed;
}
-.dkim-label {
- margin: 0 0 2px !important;
-}
.overlay {
background: #fff;
position: absolute;
@@ -235,4 +232,13 @@ table.footable>tbody>tr.footable-empty>td {
}
.dropdown-menu > li > a {
font-size: 1rem !important;
+}
+.label {
+ font-size:inherit;
+}
+[class^="bi-"]::before, [class*=" bi-"]::before {
+ vertical-align: -0.2em !important;
+}
+legend > [class^="bi-"]::before, legend > [class*=" bi-"]::before {
+ vertical-align: 0em !important;
}
\ No newline at end of file
diff --git a/data/web/css/site/admin.css b/data/web/css/site/admin.css
index bd7e5d69..bff4f204 100644
--- a/data/web/css/site/admin.css
+++ b/data/web/css/site/admin.css
@@ -81,4 +81,7 @@ table tbody tr td input[type="checkbox"] {
.key-action {
font-weight:bold;
color:white !important;
+}
+.dkim-label {
+ margin: 0 0 8px !important;
}
\ No newline at end of file
diff --git a/data/web/css/site/mailbox.css b/data/web/css/site/mailbox.css
index 7b15e8c2..81e1ff3f 100644
--- a/data/web/css/site/mailbox.css
+++ b/data/web/css/site/mailbox.css
@@ -53,8 +53,12 @@ table tbody tr {
table tbody tr td input[type="checkbox"] {
cursor: pointer;
}
+.label-last-login .bi {
+ font-size: 8pt !important;
+}
.label-last-login {
line-height: 2.5;
color: #4a4a4a!important;
background-color: #ececec!important;
}
+
diff --git a/data/web/css/site/quarantine.css b/data/web/css/site/quarantine.css
index 9ad19f0f..6f601f8c 100644
--- a/data/web/css/site/quarantine.css
+++ b/data/web/css/site/quarantine.css
@@ -95,6 +95,4 @@ table tbody tr td input[type="checkbox"] {
font-size:110%;
margin:20px;
}
-.label {
- font-size:inherit;
-}
+
diff --git a/data/web/edit.php b/data/web/edit.php
index 190628dd..fa061523 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -254,15 +254,16 @@ if (isset($_SESSION['mailcow_cc_role'])) {
!empty($_GET["domain"])) {
$domain = $_GET["domain"];
$result = mailbox('get', 'domain_details', $domain);
+ $quota_notification_bcc = quota_notification_bcc('get', $domain);
$rl = ratelimit('get', 'domain', $domain);
$rlyhosts = relayhost('get');
if (!empty($result)) {
?>
- <ul class="nav nav-tabs">
+ <ul class="nav nav-tabs" role="tablist">
<li class="active"><a data-toggle="tab" href="#dedit"><?=$lang['edit']['domain'];?></a></li>
<li><a data-toggle="tab" href="#dratelimit"><?=$lang['edit']['ratelimit'];?></a></li>
<li><a data-toggle="tab" href="#dspamfilter"><?=$lang['edit']['spam_filter'];?></a></li>
- <!-- <li><a data-toggle="tab" href="#dqwbcc"><?=$lang['edit']['quota_warning_bcc'];?></a></li> -->
+ <li><a data-toggle="tab" href="#dqwbcc"><?=$lang['edit']['quota_warning_bcc'];?></a></li>
</ul>
<hr>
<div class="tab-content">
@@ -469,6 +470,35 @@ if (isset($_SESSION['mailcow_cc_role'])) {
</div>
</div>
</div>
+ <div id="dqwbcc" class="tab-pane">
+ <div class="row">
+ <div class="col-sm-12">
+ <h4><?=$lang['edit']['quota_warning_bcc'];?></h4>
+ <p><?=$lang['edit']['quota_warning_bcc_info'];?></p>
+ <form class="form-horizontal" data-id="quota_bcc">
+ <input type="hidden" value="0" name="active">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="script_data"><?=$lang['edit']['target_address'];?>:</label>
+ <div class="col-sm-10">
+ <textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control" rows="10" id="bcc_rcpt" name="bcc_rcpt"><?=implode(PHP_EOL, $quota_notification_bcc['bcc_rcpts']);?></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="active" <?=($quota_notification_bcc['active']=="1") ? "checked" : "";?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button class="btn btn-success" data-action="edit_selected" data-id="quota_bcc" data-item="quota_bcc" data-api-url='edit/quota_notification_bcc' data-api-attr='{"domain":"<?=$domain;?>"}' href="#"><?=$lang['edit']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
</div>
<?php
}
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index f602e111..33ddac39 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -684,6 +684,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
continue;
}
}
+ $gotos = array_unique($gotos);
$gotos = array_filter($gotos);
if (empty($gotos)) { return false; }
$goto = implode(",", $gotos);
@@ -2082,6 +2083,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':address' => $address
));
}
+ $gotos = array_unique($gotos);
$gotos = array_filter($gotos);
$goto = implode(",", $gotos);
}
diff --git a/data/web/inc/functions.quota_notification.inc.php b/data/web/inc/functions.quota_notification.inc.php
index 574cc4e8..9f58bfb4 100644
--- a/data/web/inc/functions.quota_notification.inc.php
+++ b/data/web/inc/functions.quota_notification.inc.php
@@ -88,17 +88,27 @@ function quota_notification_bcc($_action, $_data = null) {
return false;
}
$active = intval($_data['active']);
- $bcc_rcpt = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $_data['bcc_rcpt']);
- if (filter_var($bcc_rcpt, FILTER_VALIDATE_EMAIL) === false) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $_action, $_data_log),
- 'msg' => 'access_denied'
- );
- return false;
+ $bcc_rcpts = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['bcc_rcpt']));
+ foreach ($bcc_rcpts as $i => &$rcpt) {
+ $rcpt = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $rcpt);
+ if (!empty($rcpt) && filter_var($rcpt, FILTER_VALIDATE_EMAIL) === false) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data_log),
+ 'msg' => array('goto_invalid', htmlspecialchars($rcpt))
+ );
+ unset($bcc_rcpts[$i]);
+ continue;
+ }
+ }
+ $bcc_rcpts = array_unique($bcc_rcpts);
+ $bcc_rcpts = array_filter($bcc_rcpts);
+ if (empty($bcc_rcpts)) {
+ $active = 0;
+
}
try {
- $redis->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpt' => $bcc_rcpt, 'active' => $active)));
+ $redis->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active)));
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
@@ -115,7 +125,7 @@ function quota_notification_bcc($_action, $_data = null) {
);
break;
case 'get':
- $domain = $_data['domain'];
+ $domain = $_data;
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php
index 794c365c..a86ed42d 100644
--- a/data/web/inc/header.inc.php
+++ b/data/web/inc/header.inc.php
@@ -127,9 +127,9 @@
</ul>
</li>
<?php } if (!isset($_SESSION['dual-login']) && isset($_SESSION['mailcow_cc_username'])) { ?>
- <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?></b> <i class="bi bi-x-circle"></i></a></li>
+ <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?></b> <i class="bi bi-power"></i></a></li>
<?php } elseif (isset($_SESSION['dual-login'])) { ?>
- <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?> <span class="text-info">(<?= htmlspecialchars($_SESSION['dual-login']['username']); ?>)</span> </b><i class="bi bi-x-circle"></i></a></li>
+ <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?> <span class="text-info">(<?= htmlspecialchars($_SESSION['dual-login']['username']); ?>)</span> </b><i class="bi bi-power"></i></a></li>
<?php } if (!preg_match('/y|yes/i', getenv('MASTER'))) { ?>
<li class="text-warning slave-info">[ slave ]</li>
<?php } ?>
diff --git a/data/web/js/build/002-bootstrap-switch.min.js b/data/web/js/build/002-bootstrap-switch.min.js
deleted file mode 100644
index effcb6cb..00000000
--- a/data/web/js/build/002-bootstrap-switch.min.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * bootstrap-switch - Turn checkboxes and radio buttons into toggle switches.
- *
- * @version v3.3.3
- * @homepage http://www.bootstrap-switch.org
- * @author Mattia Larentis <[email protected]> (http://larentis.eu)
- * @license Apache-2.0
- */
-
-'use strict';var _createClass=function(){function a(b,c){for(var e,d=0;d<c.length;d++)e=c[d],e.enumerable=e.enumerable||!1,e.configurable=!0,'value'in e&&(e.writable=!0),Object.defineProperty(b,e.key,e)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}();function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')}(function(a,b){var c=function(){function d(e){var g=this,f=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,d),this.$element=a(e),this.options=a.extend({},a.fn.bootstrapSwitch.defaults,this._getElementOptions(),f),this.prevOptions={},this.$wrapper=a('<div>',{class:function _class(){var h=[];return h.push(g.options.state?'on':'off'),g.options.size&&h.push(g.options.size),g.options.disabled&&h.push('disabled'),g.options.readonly&&h.push('readonly'),g.options.indeterminate&&h.push('indeterminate'),g.options.inverse&&h.push('inverse'),g.$element.attr('id')&&h.push('id-'+g.$element.attr('id')),h.map(g._getClass.bind(g)).concat([g.options.baseClass],g._getClasses(g.options.wrapperClass)).join(' ')}}),this.$container=a('<div>',{class:this._getClass('container')}),this.$on=a('<span>',{html:this.options.onText,class:this._getClass('handle-on')+' '+this._getClass(this.options.onColor)}),this.$off=a('<span>',{html:this.options.offText,class:this._getClass('handle-off')+' '+this._getClass(this.options.offColor)}),this.$label=a('<span>',{html:this.options.labelText,class:this._getClass('label')}),this.$element.on('init.bootstrapSwitch',this.options.onInit.bind(this,e)),this.$element.on('switchChange.bootstrapSwitch',function(){for(var j=arguments.length,h=Array(j),k=0;k<j;k++)h[k]=arguments[k];!1===g.options.onSwitchChange.apply(e,h)&&(g.$element.is(':radio')?a('[name="'+g.$element.attr('name')+'"]').trigger('previousState.bootstrapSwitch',!0):g.$element.trigger('previousState.bootstrapSwitch',!0))}),this.$container=this.$element.wrap(this.$container).parent(),this.$wrapper=this.$container.wrap(this.$wrapper).parent(),this.$element.before(this.options.inverse?this.$off:this.$on).before(this.$label).before(this.options.inverse?this.$on:this.$off),this.options.indeterminate&&this.$element.prop('indeterminate',!0),this._init(),this._elementHandlers(),this._handleHandlers(),this._labelHandlers(),this._formHandler(),this._externalLabelHandler(),this.$element.trigger('init.bootstrapSwitch',this.options.state)}return _createClass(d,[{key:'setPrevOptions',value:function setPrevOptions(){this.prevOptions=Object.assign({},this.options)}},{key:'state',value:function state(e,f){return'undefined'==typeof e?this.options.state:this.options.disabled||this.options.readonly||this.options.state&&!this.options.radioAllOff&&this.$element.is(':radio')?this.$element:(this.$element.is(':radio')?a('[name="'+this.$element.attr('name')+'"]').trigger('setPreviousOptions.bootstrapSwitch'):this.$element.trigger('setPreviousOptions.bootstrapSwitch'),this.options.indeterminate&&this.indeterminate(!1),this.$element.prop('checked',!!e).trigger('change.bootstrapSwitch',f),this.$element)}},{key:'toggleState',value:function toggleState(e){return this.options.disabled||this.options.readonly?this.$element:this.options.indeterminate?(this.indeterminate(!1),this.state(!0)):this.$element.prop('checked',!this.options.state).trigger('change.bootstrapSwitch',e)}},{key:'size',value:function size(e){return'undefined'==typeof e?this.options.size:(null!=this.options.size&&this.$wrapper.removeClass(this._getClass(this.options.size)),e&&this.$wrapper.addClass(this._getClass(e)),this._width(),this._containerPosition(),this.options.size=e,this.$element)}},{key:'animate',value:function animate(e){return'undefined'==typeof e?this.options.animate:this.options.animate===!!e?this.$element:this.toggleAnimate()}},{key:'toggleAnimate',value:function toggleAnimate(){return this.options.animate=!this.options.animate,this.$wrapper.toggleClass(this._getClass('animate')),this.$element}},{key:'disabled',value:function disabled(e){return'undefined'==typeof e?this.options.disabled:this.options.disabled===!!e?this.$element:this.toggleDisabled()}},{key:'toggleDisabled',value:function toggleDisabled(){return this.options.disabled=!this.options.disabled,this.$element.prop('disabled',this.options.disabled),this.$wrapper.toggleClass(this._getClass('disabled')),this.$element}},{key:'readonly',value:function readonly(e){return'undefined'==typeof e?this.options.readonly:this.options.readonly===!!e?this.$element:this.toggleReadonly()}},{key:'toggleReadonly',value:function toggleReadonly(){return this.options.readonly=!this.options.readonly,this.$element.prop('readonly',this.options.readonly),this.$wrapper.toggleClass(this._getClass('readonly')),this.$element}},{key:'indeterminate',value:function indeterminate(e){return'undefined'==typeof e?this.options.indeterminate:this.options.indeterminate===!!e?this.$element:this.toggleIndeterminate()}},{key:'toggleIndeterminate',value:function toggleIndeterminate(){return this.options.indeterminate=!this.options.indeterminate,this.$element.prop('indeterminate',this.options.indeterminate),this.$wrapper.toggleClass(this._getClass('indeterminate')),this._containerPosition(),this.$element}},{key:'inverse',value:function inverse(e){return'undefined'==typeof e?this.options.inverse:this.options.inverse===!!e?this.$element:this.toggleInverse()}},{key:'toggleInverse',value:function toggleInverse(){this.$wrapper.toggleClass(this._getClass('inverse'));var e=this.$on.clone(!0),f=this.$off.clone(!0);return this.$on.replaceWith(f),this.$off.replaceWith(e),this.$on=f,this.$off=e,this.options.inverse=!this.options.inverse,this.$element}},{key:'onColor',value:function onColor(e){return'undefined'==typeof e?this.options.onColor:(this.options.onColor&&this.$on.removeClass(this._getClass(this.options.onColor)),this.$on.addClass(this._getClass(e)),this.options.onColor=e,this.$element)}},{key:'offColor',value:function offColor(e){return'undefined'==typeof e?this.options.offColor:(this.options.offColor&&this.$off.removeClass(this._getClass(this.options.offColor)),this.$off.addClass(this._getClass(e)),this.options.offColor=e,this.$element)}},{key:'onText',value:function onText(e){return'undefined'==typeof e?this.options.onText:(this.$on.html(e),this._width(),this._containerPosition(),this.options.onText=e,this.$element)}},{key:'offText',value:function offText(e){return'undefined'==typeof e?this.options.offText:(this.$off.html(e),this._width(),this._containerPosition(),this.options.offText=e,this.$element)}},{key:'labelText',value:function labelText(e){return'undefined'==typeof e?this.options.labelText:(this.$label.html(e),this._width(),this.options.labelText=e,this.$element)}},{key:'handleWidth',value:function handleWidth(e){return'undefined'==typeof e?this.options.handleWidth:(this.options.handleWidth=e,this._width(),this._containerPosition(),this.$element)}},{key:'labelWidth',value:function labelWidth(e){return'undefined'==typeof e?this.options.labelWidth:(this.options.labelWidth=e,this._width(),this._containerPosition(),this.$element)}},{key:'baseClass',value:function baseClass(){return this.options.baseClass}},{key:'wrapperClass',value:function wrapperClass(e){return'undefined'==typeof e?this.options.wrapperClass:(e||(e=a.fn.bootstrapSwitch.defaults.wrapperClass),this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(' ')),this.$wrapper.addClass(this._getClasses(e).join(' ')),this.options.wrapperClass=e,this.$element)}},{key:'radioAllOff',value:function radioAllOff(e){if('undefined'==typeof e)return this.options.radioAllOff;var f=!!e;return this.options.radioAllOff===f?this.$element:(this.options.radioAllOff=f,this.$element)}},{key:'onInit',value:function onInit(e){return'undefined'==typeof e?this.options.onInit:(e||(e=a.fn.bootstrapSwitch.defaults.onInit),this.options.onInit=e,this.$element)}},{key:'onSwitchChange',value:function onSwitchChange(e){return'undefined'==typeof e?this.options.onSwitchChange:(e||(e=a.fn.bootstrapSwitch.defaults.onSwitchChange),this.options.onSwitchChange=e,this.$element)}},{key:'destroy',value:function destroy(){var e=this.$element.closest('form');return e.length&&e.off('reset.bootstrapSwitch').removeData('bootstrap-switch'),this.$container.children().not(this.$element).remove(),this.$element.unwrap().unwrap().off('.bootstrapSwitch').removeData('bootstrap-switch'),this.$element}},{key:'_getElementOptions',value:function _getElementOptions(){return{state:this.$element.is(':checked'),size:this.$element.data('size'),animate:this.$element.data('animate'),disabled:this.$element.is(':disabled'),readonly:this.$element.is('[readonly]'),indeterminate:this.$element.data('indeterminate'),inverse:this.$element.data('inverse'),radioAllOff:this.$element.data('radio-all-off'),onColor:this.$element.data('on-color'),offColor:this.$element.data('off-color'),onText:this.$element.data('on-text'),offText:this.$element.data('off-text'),labelText:this.$element.data('label-text'),handleWidth:this.$element.data('handle-width'),labelWidth:this.$element.data('label-width'),baseClass:this.$element.data('base-class'),wrapperClass:this.$element.data('wrapper-class')}}},{key:'_width',value:function _width(){var g=this,e=this.$on.add(this.$off).add(this.$label).css('width',''),f=void 0;return f='auto'===this.options.handleWidth?Math.round(Math.max(this.$on.width(),this.$off.width())):this.options.handleWidth,e.width(f),this.$label.width(function(h,j){return'auto'===g.options.labelWidth?j<f?f:j:g.options.labelWidth}),this._handleWidth=this.$on.outerWidth(),this._labelWidth=this.$label.outerWidth(),this.$container.width(2*this._handleWidth+this._labelWidth),this.$wrapper.width(this._handleWidth+this._labelWidth)}},{key:'_containerPosition',value:function _containerPosition(){var g=this,e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:this.options.state,f=arguments[1];this.$container.css('margin-left',function(){var h=[0,'-'+g._handleWidth+'px'];return g.options.indeterminate?'-'+g._handleWidth/2+'px':e?g.options.inverse?h[1]:h[0]:g.options.inverse?h[0]:h[1]})}},{key:'_init',value:function _init(){var f=this,e=function(){f.setPrevOptions(),f._width(),f._containerPosition(),setTimeout(function(){if(f.options.animate)return f.$wrapper.addClass(f._getClass('animate'))},50)};if(this.$wrapper.is(':visible'))return void e();var g=b.setInterval(function(){if(f.$wrapper.is(':visible'))return e(),b.clearInterval(g)},50)}},{key:'_elementHandlers',value:function _elementHandlers(){var e=this;return this.$element.on({'setPreviousOptions.bootstrapSwitch':this.setPrevOptions.bind(this),'previousState.bootstrapSwitch':function previousStateBootstrapSwitch(){e.options=e.prevOptions,e.options.indeterminate&&e.$wrapper.addClass(e._getClass('indeterminate')),e.$element.prop('checked',e.options.state).trigger('change.bootstrapSwitch',!0)},'change.bootstrapSwitch':function changeBootstrapSwitch(f,g){f.preventDefault(),f.stopImmediatePropagation();var h=e.$element.is(':checked');e._containerPosition(h),h===e.options.state||(e.options.state=h,e.$wrapper.toggleClass(e._getClass('off')).toggleClass(e._getClass('on')),!g&&(e.$element.is(':radio')&&a('[name="'+e.$element.attr('name')+'"]').not(e.$element).prop('checked',!1).trigger('change.bootstrapSwitch',!0),e.$element.trigger('switchChange.bootstrapSwitch',[h])))},'focus.bootstrapSwitch':function focusBootstrapSwitch(f){f.preventDefault(),e.$wrapper.addClass(e._getClass('focused'))},'blur.bootstrapSwitch':function blurBootstrapSwitch(f){f.preventDefault(),e.$wrapper.removeClass(e._getClass('focused'))},'keydown.bootstrapSwitch':function keydownBootstrapSwitch(f){!f.which||e.options.disabled||e.options.readonly||(37===f.which||39===f.which)&&(f.preventDefault(),f.stopImmediatePropagation(),e.state(39===f.which))}})}},{key:'_handleHandlers',value:function _handleHandlers(){var e=this;return this.$on.on('click.bootstrapSwitch',function(f){return f.preventDefault(),f.stopPropagation(),e.state(!1),e.$element.trigger('focus.bootstrapSwitch')}),this.$off.on('click.bootstrapSwitch',function(f){return f.preventDefault(),f.stopPropagation(),e.state(!0),e.$element.trigger('focus.bootstrapSwitch')})}},{key:'_labelHandlers',value:function _labelHandlers(){var f=this;this.$label.on({click:function click(g){g.stopPropagation()},'mousedown.bootstrapSwitch touchstart.bootstrapSwitch':function mousedownBootstrapSwitchTouchstartBootstrapSwitch(g){f._dragStart||f.options.disabled||f.options.readonly||(g.preventDefault(),g.stopPropagation(),f._dragStart=(g.pageX||g.originalEvent.touches[0].pageX)-parseInt(f.$container.css('margin-left'),10),f.options.animate&&f.$wrapper.removeClass(f._getClass('animate')),f.$element.trigger('focus.bootstrapSwitch'))},'mousemove.bootstrapSwitch touchmove.bootstrapSwitch':function mousemoveBootstrapSwitchTouchmoveBootstrapSwitch(g){if(null!=f._dragStart){var h=(g.pageX||g.originalEvent.touches[0].pageX)-f._dragStart;g.preventDefault(),h<-f._handleWidth||0<h||(f._dragEnd=h,f.$container.css('margin-left',f._dragEnd+'px'))}},'mouseup.bootstrapSwitch touchend.bootstrapSwitch':function mouseupBootstrapSwitchTouchendBootstrapSwitch(g){if(f._dragStart){if(g.preventDefault(),f.options.animate&&f.$wrapper.addClass(f._getClass('animate')),f._dragEnd){var h=f._dragEnd>-(f._handleWidth/2);f._dragEnd=!1,f.state(f.options.inverse?!h:h)}else f.state(!f.options.state);f._dragStart=!1}},'mouseleave.bootstrapSwitch':function mouseleaveBootstrapSwitch(){f.$label.trigger('mouseup.bootstrapSwitch')}})}},{key:'_externalLabelHandler',value:function _externalLabelHandler(){var f=this,e=this.$element.closest('label');e.on('click',function(g){g.preventDefault(),g.stopImmediatePropagation(),g.target===e[0]&&f.toggleState()})}},{key:'_formHandler',value:function _formHandler(){var e=this.$element.closest('form');e.data('bootstrap-switch')||e.on('reset.bootstrapSwitch',function(){b.setTimeout(function(){e.find('input').filter(function(){return a(this).data('bootstrap-switch')}).each(function(){return a(this).bootstrapSwitch('state',this.checked)})},1)}).data('bootstrap-switch',!0)}},{key:'_getClass',value:function _getClass(e){return this.options.baseClass+'-'+e}},{key:'_getClasses',value:function _getClasses(e){return a.isArray(e)?e.map(this._getClass.bind(this)):[this._getClass(e)]}}]),d}();a.fn.bootstrapSwitch=function(d){for(var f=arguments.length,e=Array(1<f?f-1:0),g=1;g<f;g++)e[g-1]=arguments[g];var h=this;return this.each(function(){var j=a(this),k=j.data('bootstrap-switch');k||(k=new c(this,d),j.data('bootstrap-switch',k)),'string'==typeof d&&(h=k[d].apply(k,e))}),h},a.fn.bootstrapSwitch.Constructor=c,a.fn.bootstrapSwitch.defaults={state:!0,size:null,animate:!0,disabled:!1,readonly:!1,indeterminate:!1,inverse:!1,radioAllOff:!1,onColor:'primary',offColor:'default',onText:'ON',offText:'OFF',labelText:' ',handleWidth:'auto',labelWidth:'auto',baseClass:'bootstrap-switch',wrapperClass:'wrapper',onInit:function onInit(){},onSwitchChange:function onSwitchChange(){}}})(window.jQuery,window);
diff --git a/data/web/js/build/005-bootstrap-filestyle.min.js b/data/web/js/build/005-bootstrap-filestyle.min.js
deleted file mode 100644
index 365b676d..00000000
--- a/data/web/js/build/005-bootstrap-filestyle.min.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * bootstrap-filestyle
- * doc: http://markusslima.github.io/bootstrap-filestyle/
- * github: https://github.com/markusslima/bootstrap-filestyle
- *
- * Copyright (c) 2017 Markus Vinicius da Silva Lima
- * Version 1.2.3
- * Licensed under the MIT license.
- */
-!function(t){"use strict";var e=0,i=function(e,i){this.options=i,this.$elementFilestyle=[],this.$element=t(e)};i.prototype={clear:function(){this.$element.val(""),this.$elementFilestyle.find(":text").val(""),this.$elementFilestyle.find(".badge").remove()},destroy:function(){this.$element.removeAttr("style").removeData("filestyle"),this.$elementFilestyle.remove()},disabled:function(t){if(!0===t)this.options.disabled||(this.$element.attr("disabled","true"),this.$elementFilestyle.find("label").attr("disabled","true"),this.options.disabled=!0);else{if(!1!==t)return this.options.disabled;this.options.disabled&&(this.$element.removeAttr("disabled"),this.$elementFilestyle.find("label").removeAttr("disabled"),this.options.disabled=!1)}},buttonBefore:function(t){if(!0===t)this.options.buttonBefore||(this.options.buttonBefore=!0,this.options.input&&(this.$elementFilestyle.remove(),this.constructor(),this.pushNameFiles()));else{if(!1!==t)return this.options.buttonBefore;this.options.buttonBefore&&(this.options.buttonBefore=!1,this.options.input&&(this.$elementFilestyle.remove(),this.constructor(),this.pushNameFiles()))}},icon:function(t){if(!0===t)this.options.icon||(this.options.icon=!0,this.$elementFilestyle.find("label").prepend(this.htmlIcon()));else{if(!1!==t)return this.options.icon;this.options.icon&&(this.options.icon=!1,this.$elementFilestyle.find(".icon-span-filestyle").remove())}},input:function(t){if(!0===t)this.options.input||(this.options.input=!0,this.options.buttonBefore?this.$elementFilestyle.append(this.htmlInput()):this.$elementFilestyle.prepend(this.htmlInput()),this.$elementFilestyle.find(".badge").remove(),this.pushNameFiles(),this.$elementFilestyle.find(".group-span-filestyle").addClass("input-group-btn"));else{if(!1!==t)return this.options.input;if(this.options.input){this.options.input=!1,this.$elementFilestyle.find(":text").remove();var e=this.pushNameFiles();e.length>0&&this.options.badge&&this.$elementFilestyle.find("label").append(' <span class="badge">'+e.length+"</span>"),this.$elementFilestyle.find(".group-span-filestyle").removeClass("input-group-btn")}}},size:function(t){if(void 0===t)return this.options.size;var e=this.$elementFilestyle.find("label"),i=this.$elementFilestyle.find("input");e.removeClass("btn-lg btn-sm"),i.removeClass("input-lg input-sm"),"nr"!=t&&(e.addClass("btn-"+t),i.addClass("input-"+t))},placeholder:function(t){if(void 0===t)return this.options.placeholder;this.options.placeholder=t,this.$elementFilestyle.find("input").attr("placeholder",t)},buttonText:function(t){if(void 0===t)return this.options.buttonText;this.options.buttonText=t,this.$elementFilestyle.find("label .buttonText").html(this.options.buttonText)},buttonName:function(t){if(void 0===t)return this.options.buttonName;this.options.buttonName=t,this.$elementFilestyle.find("label").attr({class:"btn "+this.options.buttonName})},iconName:function(t){if(void 0===t)return this.options.iconName;this.$elementFilestyle.find(".icon-span-filestyle").attr({class:"icon-span-filestyle "+this.options.iconName})},htmlIcon:function(){return this.options.icon?'<span style="margin-right: 3px;" class="icon-span-filestyle '+this.options.iconName+'"></span> ':""},htmlInput:function(){return this.options.input?'<input type="text" class="form-control '+("nr"==this.options.size?"":"input-"+this.options.size)+'" placeholder="'+this.options.placeholder+'" disabled> ':""},pushNameFiles:function(){var t="",e=[];void 0===this.$element[0].files?e[0]={name:this.$element[0]&&this.$element[0].value}:e=this.$element[0].files;for(var i=0;i<e.length;i++)t+=e[i].name.split("\\").pop()+", ";return""!==t?this.$elementFilestyle.find(":text").val(t.replace(/\, $/g,"")):this.$elementFilestyle.find(":text").val(""),e},constructor:function(){var i=this,n="",s=i.$element.attr("id"),l="";""!==s&&s||(s="filestyle-"+e,i.$element.attr({id:s}),e++),l='<span class="group-span-filestyle '+(i.options.input?"input-group-btn":"")+'"><label for="'+s+'" class="btn '+i.options.buttonName+" "+("nr"==i.options.size?"":"btn-"+i.options.size)+'" '+(i.options.disabled||i.$element.attr("disabled")?'disabled="true"':"")+">"+i.htmlIcon()+'<span class="buttonText">'+i.options.buttonText+"</span></label></span>",n=i.options.buttonBefore?l+i.htmlInput():i.htmlInput()+l,i.$elementFilestyle=t('<div class="bootstrap-filestyle input-group">'+n+"</div>"),i.$elementFilestyle.find(".group-span-filestyle").attr("tabindex","0").keypress(function(t){if(13===t.keyCode||32===t.charCode)return i.$elementFilestyle.find("label").click(),!1}),i.$element.css({position:"absolute",clip:"rect(0px 0px 0px 0px)"}).attr("tabindex","-1").after(i.$elementFilestyle),(i.options.disabled||i.$element.attr("disabled"))&&i.$element.attr("disabled","true"),i.$element.change(function(){var t=i.pushNameFiles();0==i.options.input&&i.options.badge?0==i.$elementFilestyle.find(".badge").length?i.$elementFilestyle.find("label").append(' <span class="badge">'+t.length+"</span>"):0==t.length?i.$elementFilestyle.find(".badge").remove():i.$elementFilestyle.find(".badge").html(t.length):i.$elementFilestyle.find(".badge").remove()}),window.navigator.userAgent.search(/firefox/i)>-1&&i.$elementFilestyle.find("label").click(function(){return i.$element.click(),!1})}};var n=t.fn.filestyle;t.fn.filestyle=function(e,n){var s="",l=this.each(function(){if("file"===t(this).attr("type")){var l=t(this),o=l.data("filestyle"),a=t.extend({},t.fn.filestyle.defaults,e,"object"==typeof e&&e);o||(l.data("filestyle",o=new i(this,a)),o.constructor()),"string"==typeof e&&(s=o[e](n))}});return void 0!==typeof s?s:l},t.fn.filestyle.defaults={buttonText:"Choose file",iconName:"bi bi-folder2-open",buttonName:"btn-default",size:"nr",input:!0,badge:!0,icon:!0,buttonBefore:!1,disabled:!1,placeholder:""},t.fn.filestyle.noConflict=function(){return t.fn.filestyle=n,this},t(function(){t(".filestyle").each(function(){var e=t(this),i={input:"false"!==e.attr("data-input"),icon:"false"!==e.attr("data-icon"),buttonBefore:"true"===e.attr("data-buttonBefore"),disabled:"true"===e.attr("data-disabled"),size:e.attr("data-size"),buttonText:e.attr("data-buttonText"),buttonName:e.attr("data-buttonName"),iconName:e.attr("data-iconName"),badge:"false"!==e.attr("data-badge"),placeholder:e.attr("data-placeholder")};e.filestyle(i)})})}(window.jQuery);
\ No newline at end of file
diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js
index 4661e86e..dd9ba83e 100644
--- a/data/web/js/site/admin.js
+++ b/data/web/js/site/admin.js
@@ -76,8 +76,8 @@ jQuery(function($){
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
{"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"},
- {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"rows": $.ajax({
@@ -131,8 +131,8 @@ jQuery(function($){
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"usr","title":lang.username,"style":{"width":"250px"}},
- {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"rows": $.ajax({
@@ -160,7 +160,7 @@ jQuery(function($){
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"host","type":"text","title":lang.host,"style":{"width":"250px"}},
{"name":"source","title":lang.source,"breakpoints":"xs sm"},
- {"name":"keep_spam","title":lang.spamfilter, "type": "text","style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 'yes'==value?'✕':'no'==value&&'✓';}},
+ {"name":"keep_spam","title":lang.spamfilter, "type": "text","style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 'yes'==value?'<i class="bi bi-x-lg"></i>':'no'==value&&'<i class="bi bi-check-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"rows": $.ajax({
@@ -188,7 +188,7 @@ jQuery(function($){
{"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}},
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"used_by_domains","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
@@ -216,7 +216,7 @@ jQuery(function($){
{"name":"destination","type":"text","title":lang.destination,"style":{"width":"250px"}},
{"name":"nexthop","type":"text","title":lang.nexthop,"style":{"width":"250px"}},
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
@@ -337,7 +337,7 @@ jQuery(function($){
} else if (table == 'adminstable') {
$.each(data, function (i, item) {
if (admin_username.toLowerCase() == item.username.toLowerCase()) {
- item.usr = '→ ' + item.username;
+ item.usr = '<i class="bi bi-person-check"></i> ' + item.username;
} else {
item.usr = item.username;
}
diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js
index 6397bb98..ee488670 100644
--- a/data/web/js/site/mailbox.js
+++ b/data/web/js/site/mailbox.js
@@ -204,7 +204,7 @@ jQuery(function($){
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
// http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
- function unix_time_format(i){return""==i?'✕':new Date(i?1e3*i:0).toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
+ function unix_time_format(i){return""==i?'<i class="bi bi-x-lg"></i>':new Date(i?1e3*i:0).toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"})}
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
@@ -254,10 +254,10 @@ jQuery(function($){
{"name":"def_quota_for_mbox","title":lang.mailbox_defquota,"breakpoints":"xs sm md","style":{"width":"125px"}},
{"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}},
{"name":"rl","title":"RL","breakpoints":"xs sm md lg","style":{"maxWidth":"100px","width":"100px"}},
- {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm md lg","formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm md lg","formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"domain_admins","title":lang.domain_admins,"style":{"word-break":"break-all","min-width":"200px"},"breakpoints":"xs sm md lg","filterable":(role == "admin"),"visible":(role == "admin")},
- {"name":"xmpp","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":"XMPP","formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"xmpp","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":"XMPP","formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
@@ -379,7 +379,7 @@ jQuery(function($){
},
{"name":"messages","filterable": false,"title":lang.msg_num,"breakpoints":"xs sm md"},
/* {"name":"rl","title":"RL","breakpoints":"all","style":{"width":"125px"}}, */
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':(0==value?'✕':2==value&&'—');}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':(0==value?'<i class="bi bi-x-lg"></i>':2==value&&'—');}},
{"name":"action","filterable": false,"sortable": false,"style":{"min-width":"290px","text-align":"right"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"empty": lang.empty,
@@ -497,7 +497,7 @@ jQuery(function($){
{"name":"kind","title":lang.kind},
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
{"name":"multiple_bookings","filterable": false,"style":{"maxWidth":"150px","width":"140px"},"title":lang.multiple_bookings,"breakpoints":"xs sm"},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"empty": lang.empty,
@@ -570,7 +570,7 @@ jQuery(function($){
{"name":"local_dest","title":lang.bcc_local_dest},
{"name":"bcc_dest","title":lang.bcc_destinations},
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"empty": lang.empty,
@@ -637,7 +637,7 @@ jQuery(function($){
{"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
{"name":"recipient_map_old","title":lang.recipient_map_old},
{"name":"recipient_map_new","title":lang.recipient_map_new},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"}
],
"empty": lang.empty,
@@ -702,7 +702,7 @@ jQuery(function($){
{"name":"dest","title":lang.tls_map_dest},
{"name":"policy","title":lang.tls_map_policy},
{"name":"parameters","title":lang.tls_map_parameters},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"}
],
"empty": lang.empty,
@@ -774,8 +774,8 @@ jQuery(function($){
{"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
{"name":"public_comment","title":lang.public_comment,"breakpoints":"all"},
{"name":"private_comment","title":lang.private_comment,"breakpoints":"all"},
- {"name":"sogo_visible","title":lang.sogo_visible,"formatter": function(value){return 1==value?'✓':0==value&&'✕';},"breakpoints":"all"},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"sogo_visible","title":lang.sogo_visible,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';},"breakpoints":"all"},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"empty": lang.empty,
@@ -869,7 +869,7 @@ jQuery(function($){
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"alias_domain","title":lang.alias,"style":{"width":"250px"}},
{"name":"target_domain","title":lang.target_domain,"type":"html"},
- {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"empty": lang.empty,
@@ -940,7 +940,7 @@ jQuery(function($){
{"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"},
{"name":"last_run","title":lang.last_run,"breakpoints":"xs sm md"},
{"name":"log","title":"Log"},
- {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js
index 45f02dbe..856e8a5d 100644
--- a/data/web/js/site/user.js
+++ b/data/web/js/site/user.js
@@ -130,7 +130,7 @@ jQuery(function($){
{"name":"mins_interval","title":lang.interval + " (min)","breakpoints":"all"},
{"name":"last_run","title":lang.last_run,"breakpoints":"all"},
{"name":"log","title":"Log"},
- {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
@@ -192,7 +192,7 @@ jQuery(function($){
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
{"name":"name","title":lang.app_name},
- {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'✓':0==value&&'✕';}},
+ {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"empty": lang.empty,
diff --git a/data/web/json_api.php b/data/web/json_api.php
index 7e842754..a1198f2f 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -319,8 +319,14 @@ if (isset($_GET['query'])) {
}
break;
case "get":
- function process_get_return($data) {
- echo (!isset($data) || empty($data)) ? '{}' : json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+ function process_get_return($data, $object = true) {
+ if ($object === true) {
+ $ret_str = '{}';
+ }
+ else {
+ $ret_str = '[]';
+ }
+ echo (!isset($data) || empty($data)) ? $ret_str : json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
// only allow GET requests to GET API endpoints
if ($_SERVER['REQUEST_METHOD'] != 'GET') {
@@ -1169,10 +1175,10 @@ if (isset($_GET['query'])) {
// "all" will not print details
switch ($object) {
case "all":
- process_get_return(quarantine('get'));
+ process_get_return(quarantine('get'), false);
break;
default:
- process_get_return(quarantine('details', $object));
+ process_get_return(quarantine('details', $object), false);
break;
}
break;
@@ -1608,6 +1614,9 @@ if (isset($_GET['query'])) {
case "quota_notification":
process_edit_return(quota_notification('edit', $attr));
break;
+ case "quota_notification_bcc":
+ process_edit_return(quota_notification_bcc('edit', $attr));
+ break;
case "mailq":
process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr)));
break;
diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json
index 00e983ee..f3785cbe 100644
--- a/data/web/lang/lang.de.json
+++ b/data/web/lang/lang.de.json
@@ -580,6 +580,7 @@
"pushover_verify": "Verbindung verifizieren",
"quota_mb": "Speicherplatz (MiB)",
"quota_warning_bcc": "Quota-Warnung BCC",
+ "quota_warning_bcc_info": "Die Warnungen werden als separate Kopie an die nachstehenden Empfänger versendet. Dem Betreff wird der jeweilige Benutzername in Klammern (etwa <code>Quota-Warnung ([email protected])</code>) angehangen.",
"ratelimit": "Rate Limit",
"redirect_uri": "Redirect/Callback-URL",
"relay_all": "Alle Empfänger-Adressen relayen",
@@ -608,7 +609,7 @@
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
"syncjob": "Sync-Job bearbeiten",
- "target_address": "Ziel-Adresse(n) <small>(getrennt durch Komma)</small>",
+ "target_address": "Ziel-Adresse(n)",
"target_domain": "Ziel-Domain",
"timeout1": "Timeout für Verbindung zum Remote-Host",
"timeout2": "Timeout für Verbindung zum lokalen Host",
diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json
index 40aac1f6..08ce6fe8 100644
--- a/data/web/lang/lang.en.json
+++ b/data/web/lang/lang.en.json
@@ -578,6 +578,7 @@
"pushover_verify": "Verify credentials",
"quota_mb": "Quota (MiB)",
"quota_warning_bcc": "Quota warning BCC",
+ "quota_warning_bcc_info": "Warnings will be sent as separate copies to the following recipients. The subject will be suffixed by the corresponding username in brackets, for example: <code>Quota warning ([email protected])</code>.",
"ratelimit": "Rate limit",
"redirect_uri": "Redirect/Callback URL",
"relay_all": "Relay all recipients",