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
add template feature for domains and mailboxes
79982e0e
data/web/edit.php | 44 +-
data/web/inc/functions.mailbox.inc.php | 576 +++++++++++++++++-
data/web/inc/init_db.inc.php | 101 ++-
data/web/js/build/014-mailcow.js | 73 +--
data/web/js/site/edit.js | 11 +
data/web/js/site/mailbox.js | 676 +++++++++++++++++++++
data/web/json_api.php | 64 +-
data/web/lang/lang.ca-es.json | 6 +
data/web/lang/lang.cs-cz.json | 9 +
data/web/lang/lang.da-dk.json | 10 +-
data/web/lang/lang.de-de.json | 9 +
data/web/lang/lang.en-gb.json | 20 +
data/web/lang/lang.es-es.json | 9 +
data/web/lang/lang.fi-fi.json | 11 +-
data/web/lang/lang.fr-fr.json | 11 +-
data/web/lang/lang.hu-hu.json | 1 +
data/web/lang/lang.it-it.json | 7 +
data/web/lang/lang.ko-kr.json | 9 +
data/web/lang/lang.lv-lv.json | 7 +
data/web/lang/lang.nl-nl.json | 9 +
data/web/lang/lang.pl-pl.json | 6 +
data/web/lang/lang.pt-pt.json | 8 +-
data/web/lang/lang.ro-ro.json | 9 +
data/web/lang/lang.ru-ru.json | 9 +
data/web/lang/lang.sk-sk.json | 9 +
data/web/lang/lang.sv-se.json | 9 +
data/web/lang/lang.uk-ua.json | 9 +
data/web/lang/lang.zh-cn.json | 9 +
data/web/lang/lang.zh-tw.json | 9 +
data/web/mailbox.php | 1 +
data/web/templates/base.twig | 6 +-
data/web/templates/edit/domain-defaults.twig | 156 -----
data/web/templates/edit/domain-templates.twig | 136 +++++
data/web/templates/edit/mailbox-defaults.twig | 195 ------
data/web/templates/edit/mailbox-templates.twig | 169 ++++++
data/web/templates/mailbox.twig | 9 +-
.../templates/mailbox/tab-templates-domains.twig | 51 ++
data/web/templates/mailbox/tab-templates-mbox.twig | 51 ++
data/web/templates/modals/mailbox.twig | 476 ++++++++++++++-
39 files changed, 2538 insertions(+), 452 deletions(-)
delete mode 100644 data/web/templates/edit/domain-defaults.twig
create mode 100644 data/web/templates/edit/domain-templates.twig
delete mode 100644 data/web/templates/edit/mailbox-defaults.twig
create mode 100644 data/web/templates/edit/mailbox-templates.twig
create mode 100644 data/web/templates/mailbox/tab-templates-domains.twig
create mode 100644 data/web/templates/mailbox/tab-templates-mbox.twig
Diff
diff --git a/data/web/edit.php b/data/web/edit.php
index 185e7934..09db796d 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -39,17 +39,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
$template_data = ['admin' => $admin];
}
elseif (isset($_GET['domain'])) {
- if ($_GET['domain'] == "defaults") {
- // edit domain default settings
- //$result = mailbox('get', 'domain_defaults');
- //$rl = ratelimit('get', 'domain_defaults');
- $result = true;
- $template = 'edit/domain-defaults.twig';
- $template_data = [
- // 'rl' => $rl,
- ];
- }
- elseif (is_valid_domain_name($_GET["domain"]) &&
+ if (is_valid_domain_name($_GET["domain"]) &&
!empty($_GET["domain"])) {
// edit domain
$domain = $_GET["domain"];
@@ -69,6 +59,26 @@ if (isset($_SESSION['mailcow_cc_role'])) {
];
}
}
+ elseif (isset($_GET["template"])){
+ $domain_template = mailbox('get', 'domain_templates', $_GET["template"]);
+ if ($domain_template){
+ $template_data = [
+ 'template' => $domain_template
+ ];
+ $template = 'edit/domain-templates.twig';
+ $result = true;
+ }
+ else {
+ $mailbox_template = mailbox('get', 'mailbox_templates', $_GET["template"]);
+ if ($mailbox_template){
+ $template_data = [
+ 'template' => $mailbox_template
+ ];
+ $template = 'edit/mailbox-templates.twig';
+ $result = true;
+ }
+ }
+ }
elseif (isset($_GET['oauth2client']) &&
is_numeric($_GET["oauth2client"]) &&
!empty($_GET["oauth2client"])) {
@@ -92,17 +102,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
];
}
elseif (isset($_GET['mailbox'])){
- if ($_GET['mailbox'] == "defaults"){
- // edit mailbox default settings
- // $result = mailbox('get', 'mailbox_defaults');
- // $rl = ratelimit('get', 'mailbox_defaults');
- $result = true;
- $template = 'edit/mailbox-defaults.twig';
- $template_data = [
- // 'rl' => $rl,
- ];
- }
- elseif(filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
+ if(filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
// edit mailbox
$mailbox = html_entity_decode(rawurldecode($_GET["mailbox"]));
$result = mailbox('get', 'mailbox_details', $mailbox);
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 2cf9f6c6..b30a4a97 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -1020,6 +1020,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (empty($name)) {
$name = $local_part;
}
+ if (isset($_data['protocol_access'])) {
+ $_data['protocol_access'] = (array)$_data['protocol_access'];
+ $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
+ $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
+ $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
+ $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
+ }
$active = intval($_data['active']);
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
@@ -1200,10 +1207,63 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain,
':active' => $active
));
- $stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
- $stmt->execute(array(
- ':username' => $username
- ));
+
+
+ if (isset($_data['acl'])) {
+ $_data['acl'] = (array)$_data['acl'];
+ $_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
+ $_data['tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
+ $_data['spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
+ $_data['spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
+ $_data['delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
+ $_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
+ $_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
+ $_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
+ $_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
+ $_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
+ $_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
+ $_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
+ $_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
+ $_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
+
+ $stmt = $pdo->prepare("INSERT INTO `user_acl`
+ (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
+ `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
+ VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
+ :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':spam_alias' => $_data['spam_alias'],
+ ':tls_policy' => $_data['tls_policy'],
+ ':spam_score' => $_data['spam_score'],
+ ':spam_policy' => $_data['spam_policy'],
+ ':delimiter_action' => $_data['delimiter_action'],
+ ':syncjobs' => $_data['syncjobs'],
+ ':eas_reset' => $_data['eas_reset'],
+ ':sogo_profile_reset' => $_data['sogo_profile_reset'],
+ ':pushover' => $_data['pushover'],
+ ':quarantine' => $_data['quarantine'],
+ ':quarantine_attachments' => $_data['quarantine_attachments'],
+ ':quarantine_notification' => $_data['quarantine_notification'],
+ ':quarantine_category' => $_data['quarantine_category'],
+ ':app_passwds' => $_data['app_passwds']
+ ));
+ }
+ else {
+ $stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
+ $stmt->execute(array(
+ ':username' => $username
+ ));
+ }
+
+ if (isset($_data['rl_frame']) && isset($_data['rl_value'])){
+ ratelimit('edit', 'mailbox', array(
+ 'object' => $username,
+ 'rl_frame' => $_data['rl_frame'],
+ 'rl_value' => $_data['rl_value']
+ ));
+ }
+
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1322,6 +1382,191 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('resource_added', htmlspecialchars($name))
);
break;
+ case 'domain_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (empty($_data["template"])){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'template_name_invalid'
+ );
+ return false;
+ }
+
+ // check if template name exists, return false
+ $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
+ $stmt->execute(array(
+ ":type" => "domain",
+ ":template" => $_data["template"]
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (!empty($row)){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => array('template_exists', $_data["template"])
+ );
+ return false;
+ }
+
+ // check attributes
+ $attr = array();
+ $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
+ $attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0;
+ $attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0;
+ $attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0;
+ $attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0;
+ $attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0;
+ $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
+ $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
+ $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1;
+ $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
+ $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
+ $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
+ $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
+ $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
+ $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
+
+
+ // save template
+ $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
+ VALUES (:type, :template, :attributes)");
+ $stmt->execute(array(
+ ":type" => "domain",
+ ":template" => $_data["template"],
+ ":attributes" => json_encode($attr)
+ ));
+
+ // success
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => array('template_added', $_data["template"])
+ );
+ return true;
+ break;
+ case 'mailbox_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (empty($_data["template"])){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'template_name_invalid'
+ );
+ return false;
+ }
+
+ // check if template name exists, return false
+ $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
+ $stmt->execute(array(
+ ":type" => "mailbox",
+ ":template" => $_data["template"]
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!empty($row)){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => array('template_exists', $_data["template"])
+ );
+ return false;
+ }
+
+
+ // check attributes
+ $attr = array();
+ $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
+ $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
+ $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
+ $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
+ $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
+ $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
+ $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
+ $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
+ $attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1;
+ $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
+ $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
+ if (isset($_data['protocol_access'])) {
+ $_data['protocol_access'] = (array)$_data['protocol_access'];
+ $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
+ $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
+ $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
+ $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
+ }
+ else {
+ $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
+ $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
+ $attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
+ $attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
+ }
+ if (isset($_data['acl'])) {
+ $_data['acl'] = (array)$_data['acl'];
+ $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
+ $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
+ $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
+ $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
+ $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
+ $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
+ $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
+ $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
+ $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
+ $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
+ } else {
+ $_data['acl'] = (array)$_data['acl'];
+ $attr['acl_spam_alias'] = 1;
+ $attr['acl_tls_policy'] = 1;
+ $attr['acl_spam_score'] = 1;
+ $attr['acl_spam_policy'] = 1;
+ $attr['acl_delimiter_action'] = 1;
+ $attr['acl_syncjobs'] = 0;
+ $attr['acl_eas_reset'] = 1;
+ $attr['acl_sogo_profile_reset'] = 0;
+ $attr['acl_pushover'] = 1;
+ $attr['acl_quarantine'] = 1;
+ $attr['acl_quarantine_attachments'] = 1;
+ $attr['acl_quarantine_notification'] = 1;
+ $attr['acl_quarantine_category'] = 1;
+ $attr['acl_app_passwds'] = 1;
+ }
+
+
+
+ // save template
+ $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
+ VALUES (:type, :template, :attributes)");
+ $stmt->execute(array(
+ ":type" => "mailbox",
+ ":template" => $_data["template"],
+ ":attributes" => json_encode($attr)
+ ));
+
+ // success
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => array('template_added', $_data["template"])
+ );
+ return true;
+ break;
}
break;
case 'edit':
@@ -2472,6 +2717,79 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
}
break;
+ case 'domain_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (!is_array($_data['ids'])) {
+ $ids = array();
+ $ids[] = $_data['ids'];
+ }
+ else {
+ $ids = $_data['ids'];
+ }
+ foreach ($ids as $id) {
+ $is_now = mailbox("get", "domain_templates", $id);
+ if (empty($is_now) ||
+ $is_now["type"] != "domain"){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'template_id_invalid'
+ );
+ continue;
+ }
+
+ // check name
+ if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
+ // keep template name of Default template
+ $_data["template"] = $is_now["template"];
+ }
+ else {
+ $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
+ }
+ // check attributes
+ $attr = array();
+ $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
+ $attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0;
+ $attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0;
+ $attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0;
+ $attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0;
+ $attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0;
+ $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
+ $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
+ $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1;
+ $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
+ $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
+ $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
+ $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
+ $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
+ $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
+
+ // update template
+ $stmt = $pdo->prepare("UPDATE `templates`
+ SET `template` = :template, `attributes` = :attributes
+ WHERE id = :id");
+ $stmt->execute(array(
+ ":id" => $id ,
+ ":template" => $_data["template"] ,
+ ":attributes" => json_encode($attr)
+ ));
+ }
+
+
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => array('template_modified', $_data["template"])
+ );
+ return true;
+ break;
case 'mailbox':
if (!is_array($_data['username'])) {
$usernames = array();
@@ -2814,6 +3132,110 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
}
break;
+ case 'mailbox_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (!is_array($_data['ids'])) {
+ $ids = array();
+ $ids[] = $_data['ids'];
+ }
+ else {
+ $ids = $_data['ids'];
+ }
+ foreach ($ids as $id) {
+ $is_now = mailbox("get", "mailbox_templates", $id);
+ if (empty($is_now) ||
+ $is_now["type"] != "mailbox"){
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
+ 'msg' => 'template_id_invalid'
+ );
+ continue;
+ }
+
+
+ // check name
+ if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
+ // keep template name of Default template
+ $_data["template"] = $is_now["template"];
+ }
+ else {
+ $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
+ }
+ // check attributes
+ $attr = array();
+ $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
+ $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags'];
+ $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
+ $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
+ $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
+ $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
+ $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
+ $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access'];
+ $attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
+ $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
+ $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
+ if (isset($_data['protocol_access'])) {
+ $_data['protocol_access'] = (array)$_data['protocol_access'];
+ $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
+ $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
+ $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
+ $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
+ }
+ else {
+ foreach ($is_now as $key => $value){
+ $attr[$key] = $is_now[$key];
+ }
+ }
+ if (isset($_data['acl'])) {
+ $_data['acl'] = (array)$_data['acl'];
+ $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
+ $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
+ $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
+ $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
+ $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
+ $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
+ $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
+ $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
+ $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
+ $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
+ $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
+ } else {
+ foreach ($is_now as $key => $value){
+ $attr[$key] = $is_now[$key];
+ }
+ }
+
+
+ // update template
+ $stmt = $pdo->prepare("UPDATE `templates`
+ SET `template` = :template, `attributes` = :attributes
+ WHERE id = :id");
+ $stmt->execute(array(
+ ":id" => $id ,
+ ":template" => $_data["template"] ,
+ ":attributes" => json_encode($attr)
+ ));
+ }
+
+
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => array('template_modified', $_data["template"])
+ );
+ return true;
+ break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();
@@ -3711,6 +4133,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $domaindata;
break;
+ case 'domain_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
+ return false;
+ }
+ $_data = (isset($_data)) ? intval($_data) : null;
+
+ if (isset($_data)){
+ $stmt = $pdo->prepare("SELECT * FROM `templates`
+ WHERE `id` = :id AND type = :type");
+ $stmt->execute(array(
+ ":id" => $_data,
+ ":type" => "domain"
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (empty($row)){
+ return false;
+ }
+
+ $row["attributes"] = json_decode($row["attributes"], true);
+ return $row;
+ }
+ else {
+ $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'");
+ $stmt->execute();
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ if (empty($rows)){
+ return false;
+ }
+
+ foreach($rows as $key => $row){
+ $rows[$key]["attributes"] = json_decode($row["attributes"], true);
+ }
+ return $rows;
+ }
+ break;
case 'mailbox_details':
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
@@ -3856,6 +4315,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $mailboxdata;
break;
+ case 'mailbox_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
+ return false;
+ }
+ $_data = (isset($_data)) ? intval($_data) : null;
+
+ if (isset($_data)){
+ $stmt = $pdo->prepare("SELECT * FROM `templates`
+ WHERE `id` = :id AND type = :type");
+ $stmt->execute(array(
+ ":id" => $_data,
+ ":type" => "mailbox"
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (empty($row)){
+ return false;
+ }
+
+ $row["attributes"] = json_decode($row["attributes"], true);
+ return $row;
+ }
+ else {
+ $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'mailbox'");
+ $stmt->execute();
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ if (empty($rows)){
+ return false;
+ }
+
+ foreach($rows as $key => $row){
+ $rows[$key]["attributes"] = json_decode($row["attributes"], true);
+ }
+ return $rows;
+ }
+ break;
case 'resource_details':
$resourcedata = array();
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
@@ -4224,6 +4720,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
}
break;
+ case 'domain_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (!is_array($_data['ids'])) {
+ $ids = array();
+ $ids[] = $_data['ids'];
+ }
+ else {
+ $ids = $_data['ids'];
+ }
+
+
+ foreach ($ids as $id) {
+ // delete template
+ $stmt = $pdo->prepare("DELETE FROM `templates`
+ WHERE id = :id AND type = :type AND NOT template = :template");
+ $stmt->execute(array(
+ ":id" => $id,
+ ":type" => "domain",
+ ":template" => "Default"
+ ));
+ }
+
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => 'template_removed'
+ );
+ return true;
+ break;
case 'alias':
if (!is_array($_data['id'])) {
$ids = array();
@@ -4518,6 +5050,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
}
break;
+ case 'mailbox_templates':
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (!is_array($_data['ids'])) {
+ $ids = array();
+ $ids[] = $_data['ids'];
+ }
+ else {
+ $ids = $_data['ids'];
+ }
+
+
+ foreach ($ids as $id) {
+ // delete template
+ $stmt = $pdo->prepare("DELETE FROM `templates`
+ WHERE id = :id AND type = :type AND NOT template = :template");
+ $stmt->execute(array(
+ ":id" => $id,
+ ":type" => "mailbox",
+ ":template" => "Default"
+ ));
+ }
+
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
+ 'msg' => 'template_removed'
+ );
+ return true;
+ break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index b47bd5c2..1dc39b54 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
- $db_version = "25072022_2300";
+ $db_version = "10112022_1146";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -225,6 +225,22 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
+ "templates" => array(
+ "cols" => array(
+ "id" => "INT NOT NULL AUTO_INCREMENT",
+ "template" => "VARCHAR(255) NOT NULL",
+ "type" => "VARCHAR(255) NOT NULL",
+ "attributes" => "JSON",
+ "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+ "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
+ ),
+ "keys" => array(
+ "primary" => array(
+ "" => array("id")
+ )
+ ),
+ "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+ ),
"domain" => array(
// Todo: Move some attributes to json
"cols" => array(
@@ -1292,6 +1308,89 @@ function init_db_schema() {
// Fix domain_admins
$pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';");
+ // add default templates
+ $default_domain_template = array(
+ "template" => "Default",
+ "type" => "domain",
+ "attributes" => array(
+ "tags" => array(),
+ "max_num_aliases_for_domain" => 400,
+ "max_num_mboxes_for_domain" => 10,
+ "def_quota_for_mbox" => 3072 * 1048576,
+ "max_quota_for_mbox" => 10240 * 1048576,
+ "max_quota_for_domain" => 10240 * 1048576,
+ "rl_frame" => "s",
+ "rl_value" => "",
+ "active" => 1,
+ "gal" => 1,
+ "backupmx" => 0,
+ "relay_all_recipients" => 0,
+ "relay_unknown_only" => 0,
+ "dkim_selector" => "dkim",
+ "key_size" => 2048,
+ "max_quota_for_domain" => 10240 * 1048576,
+ )
+ );
+ $default_mailbox_template = array(
+ "template" => "Default",
+ "type" => "mailbox",
+ "attributes" => array(
+ "tags" => array(),
+ "quota" => 0,
+ "quarantine_notification" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_notification']),
+ "quarantine_category" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_category']),
+ "rl_frame" => "s",
+ "rl_value" => "",
+ "force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']),
+ "sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']),
+ "active" => 1,
+ "tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']),
+ "tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']),
+ "imap_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['imap_access']),
+ "pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']),
+ "smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']),
+ "sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']),
+ "acl_spam_alias" => 1,
+ "acl_tls_policy" => 1,
+ "acl_spam_score" => 1,
+ "acl_spam_policy" => 1,
+ "acl_delimiter_action" => 1,
+ "acl_syncjobs" => 0,
+ "acl_eas_reset" => 1,
+ "acl_sogo_profile_reset" => 0,
+ "acl_pushover" => 1,
+ "acl_quarantine" => 1,
+ "acl_quarantine_attachments" => 1,
+ "acl_quarantine_notification" => 1,
+ "acl_quarantine_category" => 1,
+ "acl_app_passwds" => 1,
+ )
+ );
+ $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
+ SELECT :type, :template, :attributes FROM `templates`
+ WHERE NOT EXISTS (
+ SELECT `template` FROM `templates` WHERE `template` = :template2 AND `type` = :type2
+ ) LIMIT 1;");
+ $stmt->execute(array(
+ ":type" => "domain",
+ ":type2" => "domain",
+ ":template" => $default_domain_template["template"],
+ ":template2" => $default_mailbox_template["template"],
+ ":attributes" => json_encode($default_domain_template["attributes"])
+ ));
+ $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
+ SELECT :type, :template, :attributes FROM `templates`
+ WHERE NOT EXISTS (
+ SELECT `template` FROM `templates` WHERE `template` = :template2 AND `type` = :type2
+ ) LIMIT 1;");
+ $stmt->execute(array(
+ ":type" => "mailbox",
+ ":type2" => "mailbox",
+ ":template" => $default_mailbox_template["template"],
+ ":template2" => $default_mailbox_template["template"],
+ ":attributes" => json_encode($default_mailbox_template["attributes"])
+ ));
+
if (php_sapi_name() == "cli") {
echo "DB initialization completed" . PHP_EOL;
} else {
diff --git a/data/web/js/build/014-mailcow.js b/data/web/js/build/014-mailcow.js
index a2e25547..a211643a 100644
--- a/data/web/js/build/014-mailcow.js
+++ b/data/web/js/build/014-mailcow.js
@@ -289,37 +289,6 @@ $(document).ready(function() {
addTag(this);
}
});
- function addTag(tagAddElem){
- var tagboxElem = $(tagAddElem).parent();
- var tagInputElem = $(tagboxElem).find(".tag-input")[0];
- var tagValuesElem = $(tagboxElem).find(".tag-values")[0];
-
- var tag = escapeHtml($(tagInputElem).val());
- if (!tag) return;
- var value_tags = [];
- try {
- value_tags = JSON.parse($(tagValuesElem).val());
- } catch {}
- if (!Array.isArray(value_tags)) value_tags = [];
- if (value_tags.includes(tag)) return;
-
- $('<span class="badge bg-primary tag-badge btn-badge"><i class="bi bi-tag-fill"></i> ' + tag + '</span>').insertBefore('.tag-input').click(function(){
- var del_tag = unescapeHtml($(this).text());
- var del_tags = [];
- try {
- del_tags = JSON.parse($(tagValuesElem).val());
- } catch {}
- if (Array.isArray(del_tags)){
- del_tags.splice(del_tags.indexOf(del_tag), 1);
- $(tagValuesElem).val(JSON.stringify(del_tags));
- }
- $(this).remove();
- });
-
- value_tags.push($(tagInputElem).val());
- $(tagValuesElem).val(JSON.stringify(value_tags));
- $(tagInputElem).val('');
- }
// Dark Mode Loader
$('#dark-mode-toggle').click(toggleDarkMode);
@@ -367,13 +336,14 @@ $(document).ready(function() {
`);
localStorage.setItem("seenChangelog", Math.floor(Date.now() / 1000).toString());
+
+ new bootstrap.Modal(document.getElementById("showWhatsNewModal"), {
+ backdrop: 'static',
+ keyboard: false
+ }).show();
}
});
- new bootstrap.Modal(document.getElementById("showWhatsNewModal"), {
- backdrop: 'static',
- keyboard: false
- }).show();
}
}
@@ -408,3 +378,36 @@ $(document).ready(function() {
// https://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
function escapeHtml(n){var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function unescapeHtml(t){var n={"&":"&","<":"<",">":">",""":'"',"'":"'","/":"/","`":"`","=":"="};return String(t).replace(/&|<|>|"|'|/|`|=/g,function(t){return n[t]})}
+
+function addTag(tagAddElem, tag = null){
+ var tagboxElem = $(tagAddElem).parent();
+ var tagInputElem = $(tagboxElem).find(".tag-input")[0];
+ var tagValuesElem = $(tagboxElem).find(".tag-values")[0];
+
+ if (!tag)
+ tag = $(tagInputElem).val();
+ if (!tag) return;
+ var value_tags = [];
+ try {
+ value_tags = JSON.parse($(tagValuesElem).val());
+ } catch {}
+ if (!Array.isArray(value_tags)) value_tags = [];
+ if (value_tags.includes(tag)) return;
+
+ $('<span class="badge bg-primary tag-badge btn-badge"><i class="bi bi-tag-fill"></i> ' + escapeHtml(tag) + '</span>').insertBefore('.tag-input').click(function(){
+ var del_tag = unescapeHtml($(this).text());
+ var del_tags = [];
+ try {
+ del_tags = JSON.parse($(tagValuesElem).val());
+ } catch {}
+ if (Array.isArray(del_tags)){
+ del_tags.splice(del_tags.indexOf(del_tag), 1);
+ $(tagValuesElem).val(JSON.stringify(del_tags));
+ }
+ $(this).remove();
+ });
+
+ value_tags.push(tag);
+ $(tagValuesElem).val(JSON.stringify(value_tags));
+ $(tagInputElem).val('');
+}
\ No newline at end of file
diff --git a/data/web/js/site/edit.js b/data/web/js/site/edit.js
index 31b9d08b..55a8e6b4 100644
--- a/data/web/js/site/edit.js
+++ b/data/web/js/site/edit.js
@@ -57,6 +57,17 @@ $(document).ready(function() {
$("#multiple_bookings_custom").bind("change keypress keyup blur", function() {
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
});
+
+ // load tags
+ if ($('#tags').length){
+ var tagsEl = $('#tags').parent().find('.tag-values')[0];
+ console.log($(tagsEl).val())
+ var tags = JSON.parse($(tagsEl).val());
+ $(tagsEl).val("");
+
+ for (var i = 0; i < tags.length; i++)
+ addTag($('#tags'), tags[i]);
+ }
});
jQuery(function($){
diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js
index 77da5029..275b977f 100644
--- a/data/web/js/site/mailbox.js
+++ b/data/web/js/site/mailbox.js
@@ -77,6 +77,90 @@ $(document).ready(function() {
$('.dns-modal-body').html(xhr.responseText);
}
});
+ });
+ // @Open Domain add modal
+ $('#addDomainModal').on('show.bs.modal', function(e) {
+ $.ajax({
+ url: '/api/v1/get/domain/template/all',
+ data: {},
+ dataType: 'json',
+ success: async function(data){
+ $('#domain_templates').find('option').remove();
+ $('#domain_templates').selectpicker('destroy');
+ $('#domain_templates').selectpicker();
+ for (var i = 0; i < data.length; i++){
+ if (data[i].template === "Default"){
+ $('#domain_templates').prepend($('<option>', {
+ 'value': data[i].id,
+ 'text': data[i].template,
+ 'data-attributes': JSON.stringify(data[i].attributes),
+ 'selected': true
+ }));
+ setDomainTemplateData(data[i].attributes);
+ } else {
+ $('#domain_templates').append($('<option>', {
+ 'value': data[i].id,
+ 'text': data[i].template,
+ 'data-attributes': JSON.stringify(data[i].attributes),
+ 'selected': false
+ }));
+ }
+ };
+ $('#domain_templates').selectpicker("refresh");
+
+ // @selecting template
+ $('#domain_templates').on('change', function(){
+ var selected = $('#domain_templates option:selected');
+ var attr = selected.data('attributes');
+ setDomainTemplateData(attr);
+ });
+ },
+ error: function(xhr, status, error) {
+ console.log(error);
+ }
+ });
+ });
+ // @Open Mailbox add modal
+ $('#addMailboxModal').on('show.bs.modal', function(e) {
+ $.ajax({
+ url: '/api/v1/get/mailbox/template/all',
+ data: {},
+ dataType: 'json',
+ success: async function(data){
+ $('#mailbox_templates').find('option').remove();
+ $('#mailbox_templates').selectpicker('destroy');
+ $('#mailbox_templates').selectpicker();
+ for (var i = 0; i < data.length; i++){
+ if (data[i].template === "Default"){
+ $('#mailbox_templates').prepend($('<option>', {
+ 'value': data[i].id,
+ 'text': data[i].template,
+ 'data-attributes': JSON.stringify(data[i].attributes),
+ 'selected': true
+ }));
+ setMailboxTemplateData(data[i].attributes);
+ } else {
+ $('#mailbox_templates').append($('<option>', {
+ value: data[i].id,
+ text : data[i].template,
+ 'data-attributes': JSON.stringify(data[i].attributes),
+ 'selected': false
+ }));
+ }
+ };
+ $('#mailbox_templates').selectpicker("refresh");
+
+ // @selecting template
+ $('#mailbox_templates').on('change', function(){
+ var selected = $('#mailbox_templates option:selected');
+ var attr = selected.data('attributes');
+ setMailboxTemplateData(attr);
+ });
+ },
+ error: function(xhr, status, error) {
+ console.log(error);
+ }
+ });
});
// Sieve data modal
$('#sieveDataModal').on('show.bs.modal', function(e) {
@@ -133,6 +217,201 @@ $(document).ready(function() {
$("#multiple_bookings").val($("#multiple_bookings_custom").val());
});
+ function setDomainTemplateData(template){
+ $("#addDomain_max_aliases").val(template.max_num_aliases_for_domain);
+ $("#addDomain_max_mailboxes").val(template.max_num_mboxes_for_domain);
+ $("#addDomain_mailbox_quota_def").val(template.def_quota_for_mbox / 1048576);
+ $("#addDomain_mailbox_quota_m").val(template.max_quota_for_mbox / 1048576);
+ $("#addDomain_domain_quota_m").val(template.max_quota_for_domain / 1048576);
+
+ if (template.gal == 1){
+ $('#addDomain_gal').prop('checked', true);
+ } else {
+ $('#addDomain_gal').prop('checked', false);
+ }
+
+ if (template.active == 1){
+ $('#addDomain_active').prop('checked', true);
+ } else {
+ $('#addDomain_active').prop('checked', false);
+ }
+
+ $("#addDomain_rl_value").val(template.rl_value);
+ $('#addDomain_rl_frame').selectpicker('val', template.rl_frame);
+ $("#dkim_selector").val(template.dkim_selector);
+ if (!template.key_size)
+ template.key_size = 2048;
+ $('#key_size').selectpicker('val', template.key_size.toString());
+
+ if (template.backupmx == 1){
+ $('#addDomain_relay_domain').prop('checked', true);
+ } else {
+ $('#addDomain_relay_domain').prop('checked', false);
+ }
+ if (template.relay_all_recipients == 1){
+ $('#addDomain_relay_all').prop('checked', true);
+ } else {
+ $('#addDomain_relay_all').prop('checked', false);
+ }
+ if (template.relay_unknown_only == 1){
+ $('#addDomain_relay_unknown_only').prop('checked', true);
+ } else {
+ $('#addDomain_relay_unknown_only').prop('checked', false);
+ }
+
+
+ // load tags
+ $('#addDomain_tags').val("");
+ $($('#addDomain_tags').parent().find(".tag-values")[0]).val("");
+ $('#addDomain_tags').parent().find(".tag-badge").remove();
+ for (var i = 0; i < template.tags.length; i++)
+ addTag($('#addDomain_tags'), template.tags[i]);
+ }
+ function setMailboxTemplateData(template){
+ $("#addInputQuota").val(template.quota / 1048576);
+
+ if (template.quarantine_notification === "never"){
+ $('#quarantine_notification_never').prop('checked', true);
+ $('#quarantine_notification_hourly').prop('checked', false);
+ $('#quarantine_notification_daily').prop('checked', false);
+ $('#quarantine_notification_weekly').prop('checked', false);
+ } else if(template.quarantine_notification === "hourly"){
+ $('#quarantine_notification_never').prop('checked', false);
+ $('#quarantine_notification_hourly').prop('checked', true);
+ $('#quarantine_notification_daily').prop('checked', false);
+ $('#quarantine_notification_weekly').prop('checked', false);
+ } else if(template.quarantine_notification === "daily"){
+ $('#quarantine_notification_never').prop('checked', false);
+ $('#quarantine_notification_hourly').prop('checked', false);
+ $('#quarantine_notification_daily').prop('checked', true);
+ $('#quarantine_notification_weekly').prop('checked', false);
+ } else if(template.quarantine_notification === "weekly"){
+ $('#quarantine_notification_never').prop('checked', false);
+ $('#quarantine_notification_hourly').prop('checked', false);
+ $('#quarantine_notification_daily').prop('checked', false);
+ $('#quarantine_notification_weekly').prop('checked', true);
+ } else {
+ $('#quarantine_notification_never').prop('checked', false);
+ $('#quarantine_notification_hourly').prop('checked', false);
+ $('#quarantine_notification_daily').prop('checked', false);
+ $('#quarantine_notification_weekly').prop('checked', false);
+ }
+
+ if (template.quarantine_category === "reject"){
+ $('#quarantine_category_reject').prop('checked', true);
+ $('#quarantine_category_add_header').prop('checked', false);
+ $('#quarantine_category_all').prop('checked', false);
+ } else if(template.quarantine_category === "add_header"){
+ $('#quarantine_category_reject').prop('checked', false);
+ $('#quarantine_category_add_header').prop('checked', true);
+ $('#quarantine_category_all').prop('checked', false);
+ } else if(template.quarantine_category === "all"){
+ $('#quarantine_category_reject').prop('checked', false);
+ $('#quarantine_category_add_header').prop('checked', false);
+ $('#quarantine_category_all').prop('checked', true);
+ }
+
+ if (template.tls_enforce_in == 1){
+ $('#tls_enforce_in').prop('checked', true);
+ } else {
+ $('#tls_enforce_in').prop('checked', false);
+ }
+ if (template.tls_enforce_out == 1){
+ $('#tls_enforce_out').prop('checked', true);
+ } else {
+ $('#tls_enforce_out').prop('checked', false);
+ }
+
+ var protocol_access = [];
+ if (template.imap_access == 1){
+ protocol_access.push("imap");
+ }
+ if (template.pop3_access == 1){
+ protocol_access.push("pop3");
+ }
+ if (template.smtp_access == 1){
+ protocol_access.push("smtp");
+ }
+ if (template.sieve_access == 1){
+ protocol_access.push("sieve");
+ }
+ $('#protocol_access').selectpicker('val', protocol_access);
+
+ var acl = [];
+ if (template.acl_spam_alias == 1){
+ acl.push("spam_alias");
+ }
+ if (template.acl_tls_policy == 1){
+ acl.push("tls_policy");
+ }
+ if (template.acl_spam_score == 1){
+ acl.push("spam_score");
+ }
+ if (template.acl_spam_policy == 1){
+ acl.push("spam_policy");
+ }
+ if (template.acl_delimiter_action == 1){
+ acl.push("delimiter_action");
+ }
+ if (template.acl_syncjobs == 1){
+ acl.push("syncjobs");
+ }
+ if (template.acl_eas_reset == 1){
+ acl.push("eas_reset");
+ }
+ if (template.acl_sogo_profile_reset == 1){
+ acl.push("sogo_profile_reset");
+ }
+ if (template.acl_pushover == 1){
+ acl.push("pushover");
+ }
+ if (template.acl_quarantine == 1){
+ acl.push("quarantine");
+ }
+ if (template.acl_quarantine_attachments == 1){
+ acl.push("quarantine_attachments");
+ }
+ if (template.acl_quarantine_notification == 1){
+ acl.push("quarantine_notification");
+ }
+ if (template.acl_quarantine_category == 1){
+ acl.push("quarantine_category");
+ }
+ if (template.acl_app_passwds == 1){
+ acl.push("app_passwds");
+ }
+ $('#user_acl').selectpicker('val', acl);
+
+ $('#rl_value').val(template.rl_value);
+ if (template.rl_frame){
+ $('#rl_frame').selectpicker('val', template.rl_frame);
+ }
+
+ console.log(template.active)
+ if (template.active){
+ $('#mbox_active').selectpicker('val', template.active.toString());
+ } else {
+ $('#mbox_active').selectpicker('val', '');
+ }
+
+ if (template.force_pw_update == 1){
+ $('#force_pw_update').prop('checked', true);
+ } else {
+ $('#force_pw_update').prop('checked', false);
+ }
+ if (template.sogo_access == 1){
+ $('#sogo_access').prop('checked', true);
+ } else {
+ $('#sogo_access').prop('checked', false);
+ }
+
+ // load tags
+ $('#addMailbox_tags').val("");
+ $($('#addMailbox_tags').parent().find(".tag-values")[0]).val("");
+ $('#addMailbox_tags').parent().find(".tag-badge").remove();
+ for (var i = 0; i < template.tags.length; i++)
+ addTag($('#addMailbox_tags'), template.tags[i]);
+ }
});
jQuery(function($){
// http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
@@ -318,6 +597,196 @@ jQuery(function($){
]
});
}
+ function draw_templates_domain_table() {
+ // just recalc width if instance already exists
+ if ($.fn.DataTable.isDataTable('#templates_domain_table') ) {
+ $('#templates_domain_table').DataTable().columns.adjust().responsive.recalc();
+ return;
+ }
+
+ $('#templates_domain_table').DataTable({
+ responsive : true,
+ processing: true,
+ serverSide: false,
+ language: lang_datatables,
+ ajax: {
+ type: "GET",
+ url: "/api/v1/get/domain/template/all",
+ dataSrc: function(json){
+ console.log(json);
+ $.each(json, function (i, item) {
+ item.chkbox = '<input type="checkbox" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
+
+ item.attributes.def_quota_for_mbox = humanFileSize(item.attributes.def_quota_for_mbox);
+ item.attributes.max_quota_for_mbox = humanFileSize(item.attributes.max_quota_for_mbox);
+ item.attributes.max_quota_for_domain = humanFileSize(item.attributes.max_quota_for_domain);
+
+ item.template = escapeHtml(item.template);
+ if (item.attributes.rl_frame === "s"){
+ item.attributes.rl_frame = lang_rl.second;
+ } else if (item.attributes.rl_frame === "m"){
+ item.attributes.rl_frame = lang_rl.minute;
+ } else if (item.attributes.rl_frame === "h"){
+ item.attributes.rl_frame = lang_rl.hour;
+ } else if (item.attributes.rl_frame === "d"){
+ item.attributes.rl_frame = lang_rl.day;
+ }
+ item.attributes.rl_value = escapeHtml(item.attributes.rl_value);
+
+ item.action = '<div class="btn-group">' +
+ '<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
+ '<a href="#" data-action="delete_selected" data-id="single-template" data-api-url="delete/domain/template" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
+ '</div>';
+
+ if (Array.isArray(item.attributes.tags)){
+ var tags = '';
+ for (var i = 0; i < item.attributes.tags.length; i++)
+ tags += '<span class="badge bg-primary tag-badge"><i class="bi bi-tag-fill"></i> ' + escapeHtml(item.attributes.tags[i]) + '</span>';
+ item.attributes.tags = tags;
+ } else {
+ item.attributes.tags = '';
+ }
+ });
+
+ return json;
+ }
+ },
+ columns: [
+ {
+ // placeholder, so checkbox will not block child row toggle
+ title: '',
+ data: null,
+ searchable: false,
+ orderable: false,
+ defaultContent: '',
+ responsivePriority: 1
+ },
+ {
+ title: '',
+ data: 'chkbox',
+ searchable: false,
+ orderable: false,
+ defaultContent: '',
+ responsivePriority: 1
+ },
+ {
+ title: "ID",
+ data: 'id',
+ responsivePriority: 2,
+ defaultContent: ''
+ },
+ {
+ title: "Template",
+ data: 'template',
+ responsivePriority: 3,
+ defaultContent: ''
+ },
+ {
+ title: lang.max_aliases,
+ data: 'attributes.max_num_aliases_for_domain',
+ defaultContent: '',
+ },
+ {
+ title: lang.max_mailboxes,
+ data: 'attributes.max_num_mboxes_for_domain',
+ defaultContent: '',
+ },
+ {
+ title: lang.mailbox_defquota,
+ data: 'attributes.def_quota_for_mbox',
+ defaultContent: '',
+ },
+ {
+ title: lang.max_quota,
+ data: 'attributes.max_quota_for_mbox',
+ defaultContent: '',
+ },
+ {
+ title: lang.domain_quota_total,
+ data: 'attributes.max_quota_for_domain',
+ defaultContent: '',
+ },
+ {
+ title: lang.gal,
+ data: 'attributes.gal',
+ defaultContent: '',
+ render: function (data, type) {
+ return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+ }
+ },
+ {
+ title: lang.backup_mx,
+ data: 'attributes.backupmx',
+ defaultContent: '',
+ render: function (data, type) {
+ return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+ }
+ },
+ {
+ title: lang.relay_all,
+ data: 'attributes.relay_all_recipients',
+ defaultContent: '',
+ render: function (data, type) {
+ return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+ }
+ },
+ {
+ title: lang.relay_unknown,
+ data: 'attributes.relay_unknown_only',
+ defaultContent: '',
+ render: function (data, type) {
+ return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+ }
+ },
+ {
+ title: lang.active,
+ data: 'attributes.active',
+ defaultContent: '',
+ responsivePriority: 4,
+ render: function (data, type) {
+ return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+ }
+ },
+ {
+ title: 'rl_frame',
+ data: 'attributes.rl_frame',
+ defaultContent: '',
+ class: 'none',
+ },
+ {
+ title: 'rl_value',
+ data: 'attributes.rl_value',
+ defaultContent: '',
+ class: 'none',
+ },
+ {
+ title: lang.dkim_domains_selector,
+ data: 'attributes.dkim_selector',
+ defaultContent: '',
+ class: 'none',
+ },
+ {
+ title: lang.dkim_key_length,
+ data: 'attributes.key_size',
+ defaultContent: '',
+ class: 'none',
+ },
+ {
+ title: 'Tags',
+ data: 'attributes.tags',
+ defaultContent: '',
+ className: 'none'
+ },
+ {
+ title: lang.action,
+ data: 'action',
+ className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md',
+ responsivePriority: 6,
+ defaultContent: ''
+ },
+ ]
+ });
+ }
function draw_mailbox_table() {
// just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#mailbox_table') ) {
@@ -564,6 +1033,211 @@ jQuery(function($){
]
});
}
+ function draw_templates_mbox_table() {
+ // just recalc width if instance already exists
+ if ($.fn.DataTable.isDataTable('#templates_mbox_table') ) {
+ $('#templates_mbox_table').DataTable().columns.adjust().responsive.recalc();
+ return;
+ }
+
+ $('#templates_mbox_table').DataTable({
+ responsive : true,
+ processing: true,
+ serverSide: false,
+ language: lang_datatables,
+ ajax: {
+ type: "GET",
+ url: "/api/v1/get/mailbox/template/all",
+ dataSrc: function(json){
+ $.each(json, function (i, item) {
+ item.chkbox = '<input type="checkbox" data-id="mailbox_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
+
+ item.template = escapeHtml(item.template);
+ if (item.attributes.rl_frame === "s"){
+ item.attributes.rl_frame = lang_rl.second;
+ } else if (item.attributes.rl_frame === "m"){
+ item.attributes.rl_frame = lang_rl.minute;
+ } else if (item.attributes.rl_frame === "h"){
+ item.attributes.rl_frame = lang_rl.hour;
+ } else if (item.attributes.rl_frame === "d"){
+ item.attributes.rl_frame = lang_rl.day;
+ }
+ item.attributes.rl_value = escapeHtml(item.attributes.rl_value);
+
+ item.attributes.quota = humanFileSize(item.attributes.quota);
+
+ item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
+ item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
+ item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
+ item.attributes.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
+ item.attributes.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
+ item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
+ item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
+ if (item.attributes.quarantine_notification === 'never') {
+ item.attributes.quarantine_notification = lang.never;
+ } else if (item.attributes.quarantine_notification === 'hourly') {
+ item.attributes.quarantine_notification = lang.hourly;
+ } else if (item.attributes.quarantine_notification === 'daily') {
+ item.attributes.quarantine_notification = lang.daily;
+ } else if (item.attributes.quarantine_notification === 'weekly') {
+ item.attributes.quarantine_notification = lang.weekly;
+ }
+ if (item.attributes.quarantine_category === 'reject') {
+ item.attributes.quarantine_category = '<span class="text-danger">' + lang.q_reject + '</span>';
+ } else if (item.attributes.quarantine_category === 'add_header') {
+ item.attributes.quarantine_category = '<span class="text-warning">' + lang.q_add_header + '</span>';
+ } else if (item.attributes.quarantine_category === 'all') {
+ item.attributes.quarantine_category = lang.q_all;
+ }
+
+
+
+ item.action = '<div class="btn-group">' +
+ '<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
+ '<a href="#" data-action="delete_selected" data-id="single-template" data-api-url="delete/mailbox/template" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
+ '</div>';
+
+ if (Array.isArray(item.attributes.tags)){
+ var tags = '';
+ for (var i = 0; i < item.attributes.tags.length; i++)
+ tags += '<span class="badge bg-primary tag-badge"><i class="bi bi-tag-fill"></i> ' + escapeHtml(item.attributes.tags[i]) + '</span>';
+ item.attributes.tags = tags;
+ } else {
+ item.attributes.tags = '';
+ }
+ });
+
+ return json;
+ }
+ },
+ columns: [
+ {
+ // placeholder, so checkbox will not block child row toggle
+ title: '',
+ data: null,
+ searchable: false,
+ orderable: false,
+ defaultContent: '',
+ responsivePriority: 1
+ },
+ {
+ title: '',
+ data: 'chkbox',
+ searchable: false,
+ orderable: false,
+ defaultContent: '',
+ responsivePriority: 1
+ },
+ {
+ title: "ID",
+ data: 'id',
+ responsivePriority: 2,
+ defaultContent: ''
+ },
+ {
+ title: "Template",
+ data: 'template',
+ responsivePriority: 3,
+ defaultContent: ''
+ },
+ {
+ title: lang.domain_quota,
+ data: 'attributes.quota',
+ defaultContent: '',
+ },
+ {
+ title: lang.tls_enforce_in,
+ data: 'attributes.tls_enforce_in',
+ defaultContent: ''
+ },
+ {
+ title: lang.tls_enforce_out,
+ data: 'attributes.tls_enforce_out',
+ defaultContent: ''
+ },
+ {
+ title: 'SMTP',
+ data: 'attributes.smtp_access',
+ defaultContent: '',
+ },
+ {
+ title: 'IMAP',
+ data: 'attributes.imap_access',
+ defaultContent: '',
+ },
+ {
+ title: 'POP3',
+ data: 'attributes.pop3_access',
+ defaultContent: '',
+ },
+ {
+ title: 'SIEVE',
+ data: 'attributes.sieve_access',
+ defaultContent: '',
+ },
+ {
+ title: 'SOGO',
+ data: 'attributes.sogo_access',
+ defaultContent: '',
+ },
+ {
+ title: lang.quarantine_notification,
+ data: 'attributes.quarantine_notification',
+ defaultContent: '',
+ className: 'none'
+ },
+ {
+ title: lang.quarantine_category,
+ data: 'attributes.quarantine_category',
+ defaultContent: '',
+ className: 'none'
+ },
+ {
+ title: lang.force_pw_update,
+ data: 'attributes.force_pw_update',
+ defaultContent: '',
+ class: 'none',
+ render: function (data, type) {
+ return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+ }
+ },
+ {
+ title: "rl_frame",
+ data: 'attributes.rl_frame',
+ defaultContent: '',
+ class: 'none',
+ },
+ {
+ title: 'rl_value',
+ data: 'attributes.rl_value',
+ defaultContent: '',
+ class: 'none',
+ },
+ {
+ title: 'Tags',
+ data: 'attributes.tags',
+ defaultContent: '',
+ className: 'none'
+ },
+ {
+ title: lang.active,
+ data: 'attributes.active',
+ defaultContent: '',
+ responsivePriority: 4,
+ render: function (data, type) {
+ return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'—');
+ }
+ },
+ {
+ title: lang.action,
+ data: 'action',
+ className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md',
+ responsivePriority: 6,
+ defaultContent: ''
+ },
+ ]
+ });
+ }
function draw_resource_table() {
// just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#resource_table') ) {
@@ -1437,7 +2111,9 @@ jQuery(function($){
// Load only if the tab is visible
onVisible("[id^=domain_table]", () => draw_domain_table());
+ onVisible("[id^=templates_domain_table]", () => draw_templates_domain_table());
onVisible("[id^=mailbox_table]", () => draw_mailbox_table());
+ onVisible("[id^=templates_mbox_table]", () => draw_templates_mbox_table());
onVisible("[id^=resource_table]", () => draw_resource_table());
onVisible("[id^=alias_table]", () => draw_alias_table());
onVisible("[id^=aliasdomain_table]", () => draw_aliasdomain_table());
diff --git a/data/web/json_api.php b/data/web/json_api.php
index 1953d17c..79b6bfd5 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -230,13 +230,27 @@ if (isset($_GET['query'])) {
process_add_return(rsettings('add', $attr));
break;
case "mailbox":
- process_add_return(mailbox('add', 'mailbox', $attr));
+ switch ($object) {
+ case "template":
+ process_add_return(mailbox('add', 'mailbox_templates', $attr));
+ break;
+ default:
+ process_add_return(mailbox('add', 'mailbox', $attr));
+ break;
+ }
break;
case "oauth2-client":
process_add_return(oauth2('add', 'client', $attr));
break;
case "domain":
- process_add_return(mailbox('add', 'domain', $attr));
+ switch ($object) {
+ case "template":
+ process_add_return(mailbox('add', 'domain_templates', $attr));
+ break;
+ default:
+ process_add_return(mailbox('add', 'domain', $attr));
+ break;
+ }
break;
case "resource":
process_add_return(mailbox('add', 'resource', $attr));
@@ -519,7 +533,16 @@ if (isset($_GET['query'])) {
echo '{}';
}
break;
-
+ case "template":
+ switch ($extra){
+ case "all":
+ process_get_return(mailbox('get', 'domain_templates'));
+ break;
+ default:
+ process_get_return(mailbox('get', 'domain_templates', $extra));
+ break;
+ }
+ break;
default:
$data = mailbox('get', 'domain_details', $object);
process_get_return($data);
@@ -992,7 +1015,16 @@ if (isset($_GET['query'])) {
echo '{}';
}
break;
-
+ case "template":
+ switch ($extra){
+ case "all":
+ process_get_return(mailbox('get', 'mailbox_templates'));
+ break;
+ default:
+ process_get_return(mailbox('get', 'mailbox_templates', $extra));
+ break;
+ }
+ break;
default:
$tags = null;
if (isset($_GET['tags']) && $_GET['tags'] != '')
@@ -1641,6 +1673,9 @@ if (isset($_GET['query'])) {
case "tag":
process_delete_return(mailbox('delete', 'tags_domain', array('tags' => $items, 'domain' => $extra)));
break;
+ case "template":
+ process_delete_return(mailbox('delete', 'domain_templates', array('ids' => $items)));
+ break;
default:
process_delete_return(mailbox('delete', 'domain', array('domain' => $items)));
}
@@ -1653,6 +1688,9 @@ if (isset($_GET['query'])) {
case "tag":
process_delete_return(mailbox('delete', 'tags_mailbox', array('tags' => $items, 'username' => $extra)));
break;
+ case "template":
+ process_delete_return(mailbox('delete', 'mailbox_templates', array('ids' => $items)));
+ break;
default:
process_delete_return(mailbox('delete', 'mailbox', array('username' => $items)));
}
@@ -1814,7 +1852,14 @@ if (isset($_GET['query'])) {
process_edit_return(mailbox('edit', 'time_limited_alias', array_merge(array('address' => $items), $attr)));
break;
case "mailbox":
- process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr)));
+ switch ($object) {
+ case "template":
+ process_edit_return(mailbox('edit', 'mailbox_templates', array_merge(array('ids' => $items), $attr)));
+ break;
+ default:
+ process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr)));
+ break;
+ }
break;
case "syncjob":
process_edit_return(mailbox('edit', 'syncjob', array_merge(array('id' => $items), $attr)));
@@ -1826,7 +1871,14 @@ if (isset($_GET['query'])) {
process_edit_return(mailbox('edit', 'resource', array_merge(array('name' => $items), $attr)));
break;
case "domain":
- process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr)));
+ switch ($object) {
+ case "template":
+ process_edit_return(mailbox('edit', 'domain_templates', array_merge(array('ids' => $items), $attr)));
+ break;
+ default:
+ process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr)));
+ break;
+ }
break;
case "rl-domain":
process_edit_return(ratelimit('edit', 'domain', array_merge(array('object' => $items), $attr)));
diff --git a/data/web/lang/lang.ca-es.json b/data/web/lang/lang.ca-es.json
index 3bb0f3e3..c2032da6 100644
--- a/data/web/lang/lang.ca-es.json
+++ b/data/web/lang/lang.ca-es.json
@@ -316,6 +316,7 @@
"bcc_type": "BCC type",
"deactivate": "Desactivar",
"description": "Descripció",
+ "dkim_key_length": "Mida de la clau DKIM (bits)",
"domain": "Domini",
"domain_admins": "Administradores de dominio",
"domain_aliases": "Àlies de domini",
@@ -327,6 +328,7 @@
"filter_table": "Filtrar taula",
"filters": "Filtres",
"fname": "Nom complert",
+ "force_pw_update": "Forçar l'actualització de la contrassenya al proper login",
"in_use": "En ús (%)",
"inactive": "Inactiu",
"kind": "Tipus",
@@ -334,6 +336,9 @@
"last_run_reset": "Executar a continuació",
"mailbox_quota": "Mida màx. de quota",
"mailboxes": "Bústies",
+ "max_aliases": "Màx. àlies possibles",
+ "max_mailboxes": "Màx. bústies possibles",
+ "max_quota": "Màx. quota per bústia",
"mins_interval": "Intèrval (min)",
"msg_num": "Missatge #",
"multiple_bookings": "Múltiples reserves",
@@ -346,6 +351,7 @@
"recipient_map_new": "Nou destinatari",
"recipient_map_old": "Destinatari original",
"recipient_maps": "Recipient maps",
+ "relay_all": "Retransmetre tods els recipients",
"remove": "Esborrar",
"resources": "Recursos",
"running": "Executant-se",
diff --git a/data/web/lang/lang.cs-cz.json b/data/web/lang/lang.cs-cz.json
index 159ca886..9983f941 100644
--- a/data/web/lang/lang.cs-cz.json
+++ b/data/web/lang/lang.cs-cz.json
@@ -706,10 +706,13 @@
"description": "Popis",
"disable_login": "Zakázat přihlášení (ale stále přijímat poštu)",
"disable_x": "Vypnout",
+ "dkim_domains_selector": "Selektor",
+ "dkim_key_length": "Délka DKIM klíče (v bitech)",
"domain": "Doména",
"domain_admins": "Správci domén",
"domain_aliases": "Doménové aliasy",
"domain_quota": "Kvóta",
+ "domain_quota_total": "Celková kvóta domény",
"domains": "Domény",
"edit": "Upravit",
"empty": "Žádné výsledky",
@@ -718,6 +721,8 @@
"filter_table": "Filtrovat tabulku",
"filters": "Filtry",
"fname": "Celé jméno",
+ "force_pw_update": "Vynutit změnu hesla při příštím přihlášení",
+ "gal": "Globální seznam adres",
"goto_ham": "Učit se jako <b>ham</b>",
"goto_spam": "Učit se jako <b>spam</b>",
"hourly": "Každou hodinu",
@@ -735,6 +740,9 @@
"mailbox_defquota": "Výchozí velikost schránky",
"mailbox_quota": "Max. velikost schránky",
"mailboxes": "Mailové schránky",
+ "max_aliases": "Max. počet aliasů",
+ "max_mailboxes": "Max. počet mailových schránek",
+ "max_quota": "Max. kvóta mailové schránky",
"mins_interval": "Interval (min)",
"msg_num": "Počet zpráv",
"multiple_bookings": "Vícenásobné rezervace",
@@ -760,6 +768,7 @@
"recipient_map_old": "Původní příjemce",
"recipient_map_old_info": "Původní příjemce musí být platná emailová adresa nebo název domény.",
"recipient_maps": "Mapy příjemců",
+ "relay_all": "Předávání všech příjemců",
"remove": "Smazat",
"resources": "Zdroje",
"running": "Běží",
diff --git a/data/web/lang/lang.da-dk.json b/data/web/lang/lang.da-dk.json
index 6fd170d2..8b5ef0d5 100644
--- a/data/web/lang/lang.da-dk.json
+++ b/data/web/lang/lang.da-dk.json
@@ -640,11 +640,14 @@
"deactivate": "Deaktiver",
"description": "Beskrivelse",
"disable_login": "Tillad ikke login (indgående mail accepteres stadig)",
- "disable_x": "Deaktiver",
+ "disable_x": "Deaktiver",
+ "dkim_domains_selector": "Vælger",
+ "dkim_key_length": "DKIM nøgle længde (bits)",
"domain": "Domæne",
"domain_admins": "Domæneadministratorer",
"domain_aliases": "Domænealiaser",
"domain_quota": "Kvote",
+ "domain_quota_total": "Samlet domænekvote",
"domains": "Domains",
"edit": "Edit",
"empty": "Ingen resultater",
@@ -653,6 +656,8 @@
"filter_table": "Filtertabel",
"filters": "Filtre",
"fname": "Fulde navn",
+ "force_pw_update": "Tving adgangskodeopdatering til næste login",
+ "gal": "Global adresseliste",
"hourly": "Hver time",
"in_use": "I brug (%)",
"inactive": "Inaktiv",
@@ -667,6 +672,9 @@
"mailboxes": "Postkasser",
"mailbox_defaults": "Standardindstillinger",
"mailbox_defaults_info": "Definer standardindstillinger for nye postkasser.",
+ "max_aliases": "Maks. mulige aliasser",
+ "max_mailboxes": "Maks. mulige postkasser",
+ "max_quota": "Maks. kvote pr. postkasse",
"mins_interval": "Interval (min)",
"msg_num": "Besked #",
"multiple_bookings": "Flere bookinger",
diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json
index 0ae8c59e..8dd54454 100644
--- a/data/web/lang/lang.de-de.json
+++ b/data/web/lang/lang.de-de.json
@@ -746,10 +746,13 @@
"description": "Beschreibung",
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
"disable_x": "Deaktivieren",
+ "dkim_domains_selector": "Selector",
+ "dkim_key_length": "DKIM-Schlüssellänge (bits)",
"domain": "Domain",
"domain_admins": "Domain-Administratoren",
"domain_aliases": "Domain-Aliasse",
"domain_quota": "Gesamtspeicher",
+ "domain_quota_total": "Domain-Speicherplatz gesamt",
"domains": "Domains",
"edit": "Bearbeiten",
"empty": "Keine Einträge vorhanden",
@@ -758,6 +761,8 @@
"filter_table": "Filtern",
"filters": "Filter",
"fname": "Name",
+ "force_pw_update": "Erzwinge Passwortänderung bei nächstem Login",
+ "gal": "Globales Adressbuch",
"hourly": "Stündlich",
"in_use": "Prozentualer Gebrauch",
"inactive": "Inaktiv",
@@ -773,6 +778,9 @@
"mailbox_defquota": "Standard-Quota",
"mailbox_quota": "Max. Größe einer Mailbox",
"mailboxes": "Mailboxen",
+ "max_aliases": "Max. mögliche Aliasse",
+ "max_mailboxes": "Max. mögliche Mailboxen",
+ "max_quota": "Max. Größe per Mailbox",
"mins_interval": "Intervall (min)",
"msg_num": "Anzahl Nachrichten",
"multiple_bookings": "Mehrfachbuchen",
@@ -796,6 +804,7 @@
"recipient_map_old": "Original-Empfänger",
"recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
"recipient_maps": "Empfängerumschreibungen",
+ "relay_all": "Alle Empfänger-Adressen relayen",
"remove": "Entfernen",
"resources": "Ressourcen",
"running": "In Ausführung",
diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json
index 02a699a9..0dcfdaee 100644
--- a/data/web/lang/lang.en-gb.json
+++ b/data/web/lang/lang.en-gb.json
@@ -439,6 +439,9 @@
"target_domain_invalid": "Target domain %s is invalid",
"targetd_not_found": "Target domain %s not found",
"targetd_relay_domain": "Target domain %s is a relay domain",
+ "template_exists": "Template %s already exists",
+ "template_id_invalid": "Template ID %s invalid",
+ "template_name_invalid": "Template name invalid",
"temp_error": "Temporary error",
"text_empty": "Text must not be empty",
"tfa_token_invalid": "TFA token invalid",
@@ -713,6 +716,7 @@
"add_mailbox": "Add mailbox",
"add_recipient_map_entry": "Add recipient map",
"add_resource": "Add resource",
+ "add_template": "Add Template",
"add_tls_policy_map": "Add TLS policy map",
"address_rewriting": "Address rewriting",
"alias": "Alias",
@@ -749,10 +753,14 @@
"description": "Description",
"disable_login": "Disallow login (incoming mail is still accepted)",
"disable_x": "Disable",
+ "dkim_domains_selector": "Selector",
+ "dkim_key_length": "DKIM key length (bits)",
"domain": "Domain",
"domain_admins": "Domain administrators",
"domain_aliases": "Domain aliases",
+ "domain_templates": "Domain Templates",
"domain_quota": "Quota",
+ "domain_quota_total": "Total domain quota",
"domains": "Domains",
"edit": "Edit",
"empty": "No results",
@@ -761,6 +769,8 @@
"filter_table": "Filter table",
"filters": "Filters",
"fname": "Full name",
+ "force_pw_update": "Force password update at next login",
+ "gal": "Global Address List",
"goto_ham": "Learn as <b>ham</b>",
"goto_spam": "Learn as <b>spam</b>",
"hourly": "Hourly",
@@ -776,8 +786,12 @@
"mailbox_defaults": "Default settings",
"mailbox_defaults_info": "Define default settings for new mailboxes.",
"mailbox_defquota": "Default mailbox size",
+ "mailbox_templates": "Mailbox Templates",
"mailbox_quota": "Max. size of a mailbox",
"mailboxes": "Mailboxes",
+ "max_aliases": "Max. aliases",
+ "max_mailboxes": "Max. possible mailboxes",
+ "max_quota": "Max. quota per mailbox",
"mins_interval": "Interval (min)",
"msg_num": "Message #",
"multiple_bookings": "Multiple bookings",
@@ -803,6 +817,8 @@
"recipient_map_old": "Original recipient",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_maps": "Recipient maps",
+ "relay_all": "Relay all recipients",
+ "relay_unknown": "Relay unknown mailboxes",
"remove": "Remove",
"resources": "Resources",
"running": "Running",
@@ -839,6 +855,8 @@
"table_size_show_n": "Show %s items",
"target_address": "Goto address",
"target_domain": "Target domain",
+ "templates": "Templates",
+ "template": "Template",
"tls_enforce_in": "Enforce TLS incoming",
"tls_enforce_out": "Enforce TLS outgoing",
"tls_map_dest": "Destination",
@@ -1012,6 +1030,8 @@
"settings_map_added": "Added settings map entry",
"settings_map_removed": "Removed settings map ID %s",
"sogo_profile_reset": "SOGo profile for user %s was reset",
+ "template_added": "Added template %s",
+ "template_modified": "Changes to template %s have been saved",
"tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted",
"tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved",
"ui_texts": "Saved changes to UI texts",
diff --git a/data/web/lang/lang.es-es.json b/data/web/lang/lang.es-es.json
index 67c30ae1..d14384ee 100644
--- a/data/web/lang/lang.es-es.json
+++ b/data/web/lang/lang.es-es.json
@@ -488,10 +488,13 @@
"deactivate": "Desactivar",
"description": "Descripción",
"disable_x": "Desactivar",
+ "dkim_domains_selector": "Selector",
+ "dkim_key_length": "Longitud de la llave DKIM (bits)",
"domain": "Dominio",
"domain_admins": "Administradores por dominio",
"domain_aliases": "Alias de dominio",
"domain_quota": "Cuota",
+ "domain_quota_total": "Cuota total del dominio",
"domains": "Dominios",
"edit": "Editar",
"empty": "Sin resultados",
@@ -500,6 +503,8 @@
"filter_table": "Filtrar tabla",
"filters": "Filtros",
"fname": "Nombre completo",
+ "force_pw_update": "Forzar cambio de contraseña en el próximo inicio de sesión",
+ "gal": "Lista global de direcciones (GAL)",
"hourly": "Cada hora",
"in_use": "En uso (%)",
"inactive": "Inactivo",
@@ -508,6 +513,9 @@
"mailbox_defquota": "Tamaño de buzón predeterminado",
"mailbox_quota": "Tamaño máx. de cuota",
"mailboxes": "Buzones",
+ "max_aliases": "Máx. alias posibles",
+ "max_mailboxes": "Máx. buzones posibles",
+ "max_quota": "Máx. cuota por buzón",
"mins_interval": "Intervalo (min)",
"msg_num": "Mensaje #",
"multiple_bookings": "Reservas multiples",
@@ -523,6 +531,7 @@
"recipient_map_old": "Destinatario original",
"recipient_map_old_info": "El destino original de una regla de destinatario debe ser una dirección de correo electrónico válida o un nombre de dominio.",
"recipient_maps": "Reglas de destinatario",
+ "relay_all": "Retransmitir todos los destinatarios",
"remove": "Eliminar",
"resources": "Recursos",
"running": "En marcha",
diff --git a/data/web/lang/lang.fi-fi.json b/data/web/lang/lang.fi-fi.json
index 3ae5f0b2..1468761c 100644
--- a/data/web/lang/lang.fi-fi.json
+++ b/data/web/lang/lang.fi-fi.json
@@ -558,11 +558,14 @@
"daily": "Päivittäin",
"deactivate": "Deaktivoi",
"description": "Kuvaus",
- "disable_x": "Poista käytöstä",
+ "disable_x": "Poista käytöstä",
+ "dkim_domains_selector": "Valitsin",
+ "dkim_key_length": "DKIM avaimen pituus (bits)",
"domain": "Verkkotunnukset",
"domain_admins": "Verkkotunnuksien järjestelmänvalvojat",
"domain_aliases": "Domain alueiden aliakset",
"domain_quota": "Kiintiö",
+ "domain_quota_total": "Verkkotunnuksen kokonaiskiintiö",
"domains": "Verkkotunnukset",
"edit": "Muokkaa",
"empty": "Ei tuloksia",
@@ -571,6 +574,8 @@
"filter_table": "Suodata taulu",
"filters": "Suodattimet",
"fname": "Koko nimi",
+ "force_pw_update": "Pakota salasanan vaihto seuraavan sisään kirjautumisen jälkeen",
+ "gal": "Yleinen osoite luettelo",
"hourly": "Tunnin välein",
"in_use": "Käytössä (%)",
"inactive": "Epäaktiivinen",
@@ -581,6 +586,9 @@
"mailbox_defquota": "Tilin koko",
"mailbox_quota": "Kiintiön koko",
"mailboxes": "Sähköposti tilit",
+ "max_aliases": "Max. mahdolliset aliakset",
+ "max_mailboxes": "Max. mahdolliset sähkö postilaatikot",
+ "max_quota": "Maks. Kiintiö sähköposti laatikkoa kohden",
"mins_interval": "Aikaväli (min)",
"msg_num": "Viestejä #",
"multiple_bookings": "Useita varauksia",
@@ -600,6 +608,7 @@
"recipient_map_old": "Alkuperäinen vastaanottaja",
"recipient_map_old_info": "Vastaanottajan yhdistämis määritysten alkuperäisen kohteen on oltava kelvollinen sähköposti osoite tai verkkotunnus alueen nimi.",
"recipient_maps": "Vastaanottajien yhdistämis määritykset",
+ "relay_all": "Välitä kaikki vastaanottajat",
"remove": "Poista",
"resources": "Resursseja",
"running": "Running",
diff --git a/data/web/lang/lang.fr-fr.json b/data/web/lang/lang.fr-fr.json
index 3311e867..0a3e8075 100644
--- a/data/web/lang/lang.fr-fr.json
+++ b/data/web/lang/lang.fr-fr.json
@@ -661,18 +661,23 @@
"description": "Description",
"disable_login": "Refuser l’ouverture de session (le courrier entrant est toujours accepté)",
"disable_x": "Désactiver",
+ "dkim_domains_selector": "Sélecteur",
+ "dkim_key_length": "Longueur de la clé DKIM (bits)",
"domain": "Domaine",
"domain_admins": "Administrateurs de domaine",
"domain_aliases": "Alias de domaine",
"domain_quota": "Quota",
+ "domain_quota_total": "Quota total du domaine",
"domains": "Domaines",
"edit": "Editer",
"empty": "Pas de résulats",
"enable_x": "Activer",
"excludes": "Exclut",
"filter_table": "Table de filtre",
- "filters": "Filtres",
+ "filters": "Filtres",
"fname": "Nom complet",
+ "force_pw_update": "Forcer la mise à jour du mot de passe à la prochaine ouverture de session",
+ "gal": "Carnet d'Adresses Global (GAL)",
"hourly": "Horaire",
"in_use": "Utilisé (%)",
"inactive": "Inactif",
@@ -687,6 +692,9 @@
"mailboxes": "Boîtes mail",
"mailbox_defaults": "Paramètres par défaut",
"mailbox_defaults_info": "Définir les paramètres par défaut pour les nouvelles boîtes aux lettres.",
+ "max_aliases": "Nombre maximal d'alias",
+ "max_mailboxes": "Nombre maximal de boîtes",
+ "max_quota": "Quota max. par boîte mail",
"mins_interval": "Intervalle (min)",
"msg_num": "Message #",
"multiple_bookings": "Réservations multiples",
@@ -710,6 +718,7 @@
"recipient_map_old": "Destinataire original",
"recipient_map_old_info": "Une carte de destination originale doit être une adresse e-mail valide ou un nom de domaine.",
"recipient_maps": "Cartes des bénéficiaires",
+ "relay_all": "Relayer tous les destinataires",
"remove": "Supprimer",
"resources": "Ressources",
"running": "En fonctionnement",
diff --git a/data/web/lang/lang.hu-hu.json b/data/web/lang/lang.hu-hu.json
index 5888a186..4fa55a3b 100644
--- a/data/web/lang/lang.hu-hu.json
+++ b/data/web/lang/lang.hu-hu.json
@@ -118,6 +118,7 @@
"filter_table": "Szűrő-táblázat",
"filters": "Szűrők",
"fname": "Teljes név",
+ "force_pw_update": "Új jelszót kell beállítania a csoportos szolgáltatások eléréséhez.",
"hourly": "Óránként",
"in_use": "Foglalt (%)",
"inactive": "Inaktív",
diff --git a/data/web/lang/lang.it-it.json b/data/web/lang/lang.it-it.json
index 09b3ee30..d94ba21c 100644
--- a/data/web/lang/lang.it-it.json
+++ b/data/web/lang/lang.it-it.json
@@ -711,10 +711,13 @@
"description": "Descrizione",
"disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)",
"disable_x": "Disabilita",
+ "dkim_domains_selector": "Selettore",
+ "dkim_key_length": "Lunghezza chiave DKIM (bits)",
"domain": "Dominio",
"domain_admins": "Amministratori di dominio",
"domain_aliases": "Alias di domini",
"domain_quota": "Spazio",
+ "domain_quota_total": "Spazio totale dominio",
"domains": "Domini",
"edit": "Modifica",
"empty": "Nessun risultato",
@@ -738,6 +741,9 @@
"mailbox_defquota": "Dimensione predefinita della casella di posta",
"mailbox_quota": "Massima dimensione della casella",
"mailboxes": "Caselle",
+ "max_aliases": "Numero massimo alias",
+ "max_mailboxes": "Numero massimo caselle di posta",
+ "max_quota": "Massimo spazio per casella",
"mins_interval": "Intervallo (min)",
"msg_num": "Messaggio #",
"multiple_bookings": "Prenotazioni multiple",
@@ -761,6 +767,7 @@
"recipient_map_old": "Original recipient",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_maps": "Recipient maps",
+ "relay_all": "Trasmettere a tutti i destinatari",
"remove": "Rimuovi",
"resources": "Risorse",
"running": "In esecuzione",
diff --git a/data/web/lang/lang.ko-kr.json b/data/web/lang/lang.ko-kr.json
index 14b4554e..5a4a46cf 100644
--- a/data/web/lang/lang.ko-kr.json
+++ b/data/web/lang/lang.ko-kr.json
@@ -627,10 +627,13 @@
"description": "설명",
"disable_login": "로그인 비활성화 (오는 메일은 계속 받습니다.)",
"disable_x": "비활성화",
+ "dkim_domains_selector": "선택기",
+ "dkim_key_length": "DKIM key 길이 (bits)",
"domain": "도메인",
"domain_admins": "도메인 관리자",
"domain_aliases": "도메인 별칭",
"domain_quota": "한도",
+ "domain_quota_total": "도메인에 할당할 디스크 크기",
"domains": "도메인",
"edit": "수정",
"empty": "결과 없음",
@@ -639,6 +642,8 @@
"filter_table": "Filter table",
"filters": "Filters",
"fname": "Full name",
+ "force_pw_update": "그룹웨어 관련 서비스에 접근하기 위해서는 새 비밀번호를 <b>꼭</b> 설정해야 합니다.",
+ "gal": "글로벌 주소 리스트",
"hourly": "Hourly",
"in_use": "In use (%)",
"inactive": "Inactive",
@@ -651,6 +656,9 @@
"mailbox_defquota": "Default mailbox size",
"mailbox_quota": "Max. size of a mailbox",
"mailboxes": "Mailboxes",
+ "max_aliases": "최대 별칭 주소",
+ "max_mailboxes": "최대 메일함 수",
+ "max_quota": "Max. quota per mailbox",
"mins_interval": "Interval (min)",
"msg_num": "Message #",
"multiple_bookings": "Multiple bookings",
@@ -670,6 +678,7 @@
"recipient_map_old": "Original recipient",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_maps": "Recipient maps",
+ "relay_all": "모든 수신자에게 릴레이",
"remove": "Remove",
"resources": "Resources",
"running": "Running",
diff --git a/data/web/lang/lang.lv-lv.json b/data/web/lang/lang.lv-lv.json
index 8a658f14..d3b53ddf 100644
--- a/data/web/lang/lang.lv-lv.json
+++ b/data/web/lang/lang.lv-lv.json
@@ -323,10 +323,12 @@
"bcc_type": "BCC tips",
"deactivate": "Deaktivizēt",
"description": "Apraksts",
+ "dkim_key_length": "DKIM atslēgas garums (bits)",
"domain": "Domēns",
"domain_admins": "Domēna administratori",
"domain_aliases": "Domēna aliases",
"domain_quota": "Kvota",
+ "domain_quota_total": "Kopējā domēna kvota",
"domains": "Domēns",
"edit": "Labot",
"empty": "Nav rezultātu",
@@ -334,6 +336,7 @@
"filter_table": "Filtra tabula",
"filters": "Filtri",
"fname": "Pilns vārds",
+ "force_pw_update": "Piespiedu paroles atjaunošana pie nākošās pieslēgšanās",
"in_use": "Lietošanā (%)",
"inactive": "Neaktīvs",
"kind": "Veids",
@@ -341,6 +344,9 @@
"last_run_reset": "Nākamais grafiks",
"mailbox_quota": "Maks. pastkastes izmērs",
"mailboxes": "Pastkaste",
+ "max_aliases": "Maks. iespejamās aliases",
+ "max_mailboxes": "Maks. iespējamās pastkastes",
+ "max_quota": "Maks. kvota uz pastkasti",
"mins_interval": "Intervāls (min)",
"msg_num": "Vēstule #",
"multiple_bookings": "Vairāki rezervējumi",
@@ -352,6 +358,7 @@
"recipient_map_new": "Jauns saņēmējs",
"recipient_map_old": "Oriģinālais saņēmējs",
"recipient_maps": "Saņēmēja kartes",
+ "relay_all": "Pārsūtīt visus saņēmējus",
"remove": "Noņemt",
"resources": "Resursi",
"running": "Darbojas",
diff --git a/data/web/lang/lang.nl-nl.json b/data/web/lang/lang.nl-nl.json
index bbe96fca..025f9960 100644
--- a/data/web/lang/lang.nl-nl.json
+++ b/data/web/lang/lang.nl-nl.json
@@ -651,10 +651,13 @@
"description": "Beschrijving",
"disable_login": "Weiger aanmelden (inkomende mail blijft binnenkomen)",
"disable_x": "Schakel uit",
+ "dkim_domains_selector": "Selector",
+ "dkim_key_length": "Grootte van key (bits)",
"domain": "Domein",
"domain_admins": "Domeinadministrators",
"domain_aliases": "Domeinaliassen",
"domain_quota": "Quota",
+ "domain_quota_m": "Totale domeinquota",
"domains": "Domeinen",
"edit": "Wijzig",
"empty": "Geen resultaten",
@@ -663,6 +666,8 @@
"filter_table": "Filtertabel",
"filters": "Filters",
"fname": "Volledige naam",
+ "force_pw_update": "Vereis nieuw wachtwoord bij eerstvolgende login",
+ "gal": "Globale adreslijst",
"hourly": "Ieder uur",
"in_use": "In gebruik (%)",
"inactive": "Inactief",
@@ -677,6 +682,9 @@
"mailboxes": "Mailboxen",
"mailbox_defaults": "Standaardinstellingen",
"mailbox_defaults_info": "Stel standaardinstellingen in voor nieuwe mailboxen.",
+ "max_aliases": "Maximaal aantal aliassen",
+ "max_mailboxes": "Maximaal aantal mailboxen",
+ "max_quota": "Mailboxquota",
"mins_interval": "Interval (min)",
"msg_num": "Bericht #",
"multiple_bookings": "Meerdere boekingen",
@@ -699,6 +707,7 @@
"recipient_map_old": "Oorspronkelijke ontvanger",
"recipient_map_old_info": "De oorspronkelijke bestemming van een ontvanger-map dient een geldig mailadres of domeinnaam te zijn.",
"recipient_maps": "Ontvanger-maps",
+ "relay_all": "Forward alle ontvangers",
"remove": "Verwijder",
"resources": "Resources",
"running": "Wordt uitgevoerd",
diff --git a/data/web/lang/lang.pl-pl.json b/data/web/lang/lang.pl-pl.json
index a3721617..c2f2bd36 100644
--- a/data/web/lang/lang.pl-pl.json
+++ b/data/web/lang/lang.pl-pl.json
@@ -233,10 +233,12 @@
"daily": "Co dzień",
"deactivate": "Wyłącz",
"description": "Opis",
+ "dkim_key_length": "Długość klucza DKIM (bity)",
"domain": "Domena",
"domain_admins": "Administratorzy domeny",
"domain_aliases": "Aliasy domeny",
"domain_quota": "Limit wielkości",
+ "domain_quota_total": "Łączny limit domeny",
"domains": "Domeny",
"edit": "Edytuj",
"empty": "Brak wyników",
@@ -250,6 +252,9 @@
"last_run": "Ostatnie uruchomienie",
"mailbox_quota": "Maks. wielkość skrzynki",
"mailboxes": "Skrzynki",
+ "max_aliases": "Maks. liczba aliasów",
+ "max_mailboxes": "Maks. liczba skrzynek",
+ "max_quota": "Maks. wielkość skrzynki",
"mins_interval": "Zakres (min)",
"msg_num": "Wiadomość #",
"multiple_bookings": "Wielokrotne rejestracje",
@@ -258,6 +263,7 @@
"no_record_single": "Brak rekordu",
"quarantine_notification": "Powiadomienia o kwarantannie",
"quick_actions": "Szybkie działania",
+ "relay_all": "Przekaż wszystkim odbiorcom",
"remove": "Usuń",
"resources": "Zasoby",
"spam_aliases": "Alias tymczasowy",
diff --git a/data/web/lang/lang.pt-pt.json b/data/web/lang/lang.pt-pt.json
index 3e60fec0..6eba0529 100644
--- a/data/web/lang/lang.pt-pt.json
+++ b/data/web/lang/lang.pt-pt.json
@@ -9,7 +9,7 @@
"backup_mx_options": "Opções Backup MX:",
"description": "Descrição:",
"domain": "Domínio",
- "domain_quota_m": "Total de espaço por domínio(MiB):",
+ "domain_quota_m": "Total de espaço por domínio (MiB):",
"full_name": "Nome:",
"mailbox_quota_m": "Máximo espaço por conta (MiB):",
"mailbox_username": "Usuário (primeira parte do endereço de email):",
@@ -161,10 +161,12 @@
"aliases": "Apelidos",
"backup_mx": "Backup MX",
"description": "Descrição:",
+ "dkim_key_length": "Tamanho do registro DKIM (bits)",
"domain": "Domínio",
"domain_admins": "Administradores de domínio",
"domain_aliases": "Encaminhamento de Domínio",
"domain_quota": "Espaço",
+ "domain_quota_total": "Total de espaço por domínio",
"domains": "Domínios",
"edit": "Alterar",
"filter_table": "Procurar",
@@ -172,9 +174,13 @@
"in_use": "Em uso (%)",
"mailbox_quota": "Espaço máximo da Conta",
"mailboxes": "Contas",
+ "max_aliases": "Máximo de apelidos",
+ "max_mailboxes": "Máximo de contas",
+ "max_quota": "Máximo espaço por conta",
"msg_num": "Mensagens",
"no_record": "Nenhum registro",
"no_record_single": "Nenhum registro",
+ "relay_all": "Relay para todas as contas",
"remove": "Remover",
"target_address": "Encaminhar para",
"target_domain": "Domínio Destino",
diff --git a/data/web/lang/lang.ro-ro.json b/data/web/lang/lang.ro-ro.json
index 033d7421..6100c090 100644
--- a/data/web/lang/lang.ro-ro.json
+++ b/data/web/lang/lang.ro-ro.json
@@ -712,10 +712,13 @@
"description": "Descriere",
"disable_login": "Nu permiteți autentificarea",
"disable_x": "Dezactivează",
+ "dkim_domains_selector": "Selector",
+ "dkim_key_length": "Lungimea cheii DKIM (biți)",
"domain": "Domeniu",
"domain_admins": "Administratori domeniu",
"domain_aliases": "Aliasuri de domenii",
"domain_quota": "Cotă",
+ "domain_quota_total": "Cota totală domeniu",
"domains": "Domenii",
"edit": "Editează",
"empty": "Nici un rezultat",
@@ -724,6 +727,8 @@
"filter_table": "Tabel filtre",
"filters": "Filtre",
"fname": "Nume complet",
+ "force_pw_update": "Forțează o actualizare a parolei la următoarea conectare",
+ "gal": "Lista adreselor globale",
"goto_ham": "Învață ca <b>ham</b>",
"goto_spam": "Învață ca <b>spam</b>",
"hourly": "Din oră în oră",
@@ -741,6 +746,9 @@
"mailboxes": "Cutii poștale",
"mailbox_defaults": "Setări implicite",
"mailbox_defaults_info": "Definiți setările implicite pentru cutiile poștale noi.",
+ "max_aliases": "Număr maxim posibil de aliasuri",
+ "max_mailboxes": "Număr maxim posibil de cutii poștale",
+ "max_quota": "Cotă maximă pentru cutia poștală",
"mins_interval": "Interval (min)",
"msg_num": "Mesaj #",
"multiple_bookings": "Rezervări multiple",
@@ -766,6 +774,7 @@
"recipient_map_old": "Destinatar original",
"recipient_map_old_info": "Destinația originală a hărților destinatarilor trebuie să fie adrese de email valide sau nume de domeniu.",
"recipient_maps": "Hărți destinatar",
+ "relay_all": "Retransmite toți destinatarii",
"remove": "Elimină",
"resources": "Resurse",
"running": "Rulare",
diff --git a/data/web/lang/lang.ru-ru.json b/data/web/lang/lang.ru-ru.json
index 336272a0..69fd75b2 100644
--- a/data/web/lang/lang.ru-ru.json
+++ b/data/web/lang/lang.ru-ru.json
@@ -710,10 +710,13 @@
"description": "Описание",
"disable_login": "Вход в систему запрещен",
"disable_x": "Отключить",
+ "dkim_domains_selector": "Selector",
+ "dkim_key_length": "Длина DKIM ключа (bits)",
"domain": "Домен",
"domain_admins": "Администраторы домена",
"domain_aliases": "Псевдонимы доменов",
"domain_quota": "Квота",
+ "domain_quota_total": "Квота домена",
"domains": "Домены",
"edit": "Изменить",
"empty": "Пусто",
@@ -722,6 +725,8 @@
"filter_table": "Поиск",
"filters": "Фильтры",
"fname": "Полное имя",
+ "force_pw_update": "Требовать смены пароля при следующем входе в систему",
+ "gal": "GAL - Глобальная адресная книга",
"goto_ham": "Запомнить как <b>полезную почту</b>",
"goto_spam": "Запомнить как <b>спам</b>",
"hourly": "Раз в час",
@@ -739,6 +744,9 @@
"mailbox_defquota": "Квота по умолчанию",
"mailbox_quota": "Макс. квота почт. ящика",
"mailboxes": "Почтовые ящики",
+ "max_aliases": "Максимум псевдонимов",
+ "max_mailboxes": "Максимум почтовых ящиков",
+ "max_quota": "Максимальная квота почтового аккаунта",
"mins_interval": "Интервал (в минутах)",
"msg_num": "Писем",
"multiple_bookings": "Несколько бронирований",
@@ -764,6 +772,7 @@
"recipient_map_old": "Получатель",
"recipient_map_old_info": "Должен быть валидный почтовым ящиком или доменом.",
"recipient_maps": "Перезапись получателя",
+ "relay_all": "Ретрансляция всех получателей",
"remove": "Удалить",
"resources": "Ресурсы",
"running": "В процессе",
diff --git a/data/web/lang/lang.sk-sk.json b/data/web/lang/lang.sk-sk.json
index 365e2ffa..83dded15 100644
--- a/data/web/lang/lang.sk-sk.json
+++ b/data/web/lang/lang.sk-sk.json
@@ -712,10 +712,13 @@
"description": "Popis",
"disable_login": "Zablokovať prihlásenie (nevzťahuje sa na prichádzajúcu poštu)",
"disable_x": "Pozastaviť",
+ "dkim_domains_selector": "Selektor",
+ "dkim_key_length": "Dĺžka DKIM kľúča (bity)",
"domain": "Doména",
"domain_admins": "Administrátori domény",
"domain_aliases": "Alias domény",
"domain_quota": "Kvóta",
+ "domain_quota_total": "Celkové kvóta domény",
"domains": "Domény",
"edit": "Upraviť",
"empty": "Žiadne výsledky",
@@ -724,6 +727,8 @@
"filter_table": "Filtrovať tabuľku",
"filters": "Filtre",
"fname": "Celé meno",
+ "force_pw_update": "Vynútiť zmenu hesla pri ďalšom prihlásení",
+ "gal": "Globálny zoznam adries",
"goto_ham": "Považovať za <b>ham</b>",
"goto_spam": "Považovať za <b>spam</b>",
"hourly": "Hodinový",
@@ -741,6 +746,9 @@
"mailbox_defquota": "Predvolená veľkosť schránky",
"mailbox_quota": "Max. veľkosť schránky",
"mailboxes": "Mailové schránky",
+ "max_aliases": "Max. počet aliasov",
+ "max_mailboxes": "Max. počet mailových schránok",
+ "max_quota": "Max. kvóta pre mailovú schránku",
"mins_interval": "Interval (min)",
"msg_num": "Počet správ",
"multiple_bookings": "Viaceré rezervácie",
@@ -766,6 +774,7 @@
"recipient_map_old": "Originálny príjemca",
"recipient_map_old_info": "Originálny cieľ mapy príjemcu musí byť platná emailová adresa alebo meno domény.",
"recipient_maps": "Mapy príjemcov",
+ "relay_all": "Preposielať všetkým príjemcom",
"remove": "Odstrániť",
"resources": "Zdroje",
"running": "Bežiaci",
diff --git a/data/web/lang/lang.sv-se.json b/data/web/lang/lang.sv-se.json
index c545cd1c..518d2ddc 100644
--- a/data/web/lang/lang.sv-se.json
+++ b/data/web/lang/lang.sv-se.json
@@ -668,10 +668,13 @@
"description": "Beskrivning",
"disable_login": "Inaktivera inloggning (inkommande post kommer fortfarande tas emot)",
"disable_x": "Inaktivera",
+ "dkim_domains_selector": "Välj",
+ "dkim_key_length": "DKIM-nyckellängd (bitar)",
"domain": "Domän",
"domain_admins": "Domänadministratörer",
"domain_aliases": "Domänalias",
"domain_quota": "Kvot",
+ "domain_quota_total": "Total kvot per postlåda",
"domains": "Domäner",
"edit": "Ändra",
"empty": "Ingen information",
@@ -680,6 +683,8 @@
"filter_table": "Filtrera tabellen",
"filters": "Postfilter",
"fname": "Fullständigt namn",
+ "force_pw_update": "Kräv uppdatering av lösenordet vid nästa inloggning",
+ "gal": "Global adressbok",
"hourly": "Varje timme",
"in_use": "Användning (%)",
"inactive": "Inaktiv",
@@ -694,6 +699,9 @@
"mailboxes": "Postlådor",
"mailbox_defaults": "Standardinställningar",
"mailbox_defaults_info": "Standardinställningar för nya postlådor.",
+ "max_aliases": "Max antal alias",
+ "max_mailboxes": "Max antal postlådor",
+ "max_quota": "Max. kvot per postlåda",
"mins_interval": "Interval (min)",
"msg_num": "Antal meddelanden",
"multiple_bookings": "Flera bokningar",
@@ -717,6 +725,7 @@
"recipient_map_old": "Ursprunglig mottagaren",
"recipient_map_old_info": "Den ursprungliga mottagaren måste vara en giltiga e-postadresser eller ett domännamn.",
"recipient_maps": "Skriv om mottagaradressen",
+ "relay_all": "Vidarebefordra alla mottagare",
"remove": "Ta bort",
"resources": "Resurser",
"running": "Körs",
diff --git a/data/web/lang/lang.uk-ua.json b/data/web/lang/lang.uk-ua.json
index 06360dc2..883fd2c9 100644
--- a/data/web/lang/lang.uk-ua.json
+++ b/data/web/lang/lang.uk-ua.json
@@ -707,10 +707,13 @@
"daily": "Раз на день",
"description": "Опис",
"disable_x": "Вимкнути",
+ "dkim_domains_selector": "Селектор",
+ "dkim_key_length": "Довжина ключа DKIM (біт)",
"domain": "Домен",
"domain_admins": "Адміністратори домену",
"domain_aliases": "Псевдоніми доменів",
"domain_quota": "Квота",
+ "domain_quota_total": "Загальна квота домену",
"domains": "Домени",
"edit": "Змінити",
"empty": "Пусто",
@@ -719,6 +722,8 @@
"filter_table": "Пошук",
"filters": "Фильтри",
"fname": "Повне ім'я",
+ "gal": "GAL - Глобальна адресна книга",
+ "force_pw_update": "Вимагати зміну пароля при наступному вході до системи",
"goto_spam": "Запам'ятати як <b>спам</b>",
"hourly": "Щогодини",
"in_use": "Використано (%)",
@@ -731,6 +736,9 @@
"mailbox": "Поштовий акаунт",
"mailbox_defaults_info": "Визначте параметри за замовчуванням для нових поштових акаунтів.",
"mailbox_quota": "Макс. квота пошт. ящика",
+ "max_aliases": "Максимум псевдонімів",
+ "max_mailboxes": "Максимум поштових скриньок",
+ "max_quota": "Максимальна квота поштового акаунту",
"mins_interval": "Інтервал (у хвилинах)",
"msg_num": "Листів",
"multiple_bookings": "Декілька бронювань",
@@ -751,6 +759,7 @@
"recipient_map_new_info": "Повинен бути чинною поштовою скринькою.",
"recipient_map_old": "Одержувач",
"recipient_maps": "Перезапис одержувача",
+ "relay_all": "Ретрансляція всіх отримувачів",
"remove": "Видалити",
"resources": "Ресурси",
"running": "В процесі",
diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json
index e1c50ffe..ddb5a9ec 100644
--- a/data/web/lang/lang.zh-cn.json
+++ b/data/web/lang/lang.zh-cn.json
@@ -640,10 +640,13 @@
"description": "描述",
"disable_login": "不允许登录 (仍然会接收邮件)",
"disable_x": "关闭",
+ "dkim_domains_selector": "选择器",
+ "dkim_key_length": "DKIM密钥长度 (bits)",
"domain": "域名",
"domain_admins": "域名管理员",
"domain_aliases": "域名别名",
"domain_quota": "配额",
+ "domain_quota_total": "域名总配额",
"domains": "域名",
"edit": "编辑",
"empty": "结果为空",
@@ -652,6 +655,8 @@
"filter_table": "筛选表格",
"filters": "过滤器",
"fname": "全名",
+ "force_pw_update": "你<b>必须</b>设置一个新密码以继续使用群件相关服务。",
+ "gal": "全球地址簿",
"hourly": "每小时",
"in_use": "使用数 (%)",
"inactive": "禁用",
@@ -666,6 +671,9 @@
"mailboxes": "邮箱",
"mailbox_defaults": "默认设置",
"mailbox_defaults_info": "配置新邮箱的默认设置",
+ "max_aliases": "最大允许地址别名数",
+ "max_mailboxes": "最大允许邮箱数",
+ "max_quota": "每个邮箱的最大配额",
"mins_interval": "间隔 (分钟)",
"msg_num": "消息 #",
"multiple_bookings": "登记限制",
@@ -685,6 +693,7 @@
"recipient_map_old": "原收件人",
"recipient_map_old_info": "原收件人必须为合法的邮件地址",
"recipient_maps": "收件人映射",
+ "relay_all": "中继所有收件人",
"remove": "删除",
"resources": "日历资源",
"running": "运行中",
diff --git a/data/web/lang/lang.zh-tw.json b/data/web/lang/lang.zh-tw.json
index d0d6bb14..89b984f6 100644
--- a/data/web/lang/lang.zh-tw.json
+++ b/data/web/lang/lang.zh-tw.json
@@ -722,10 +722,13 @@
"description": "描述",
"disable_login": "不允許登入 (仍然會接收郵件)",
"disable_x": "關閉",
+ "dkim_domains_selector": "選擇器",
+ "dkim_key_length": "DKIM 金鑰長度 (bits)",
"domain": "域名",
"domain_admins": "域名管理員",
"domain_aliases": "域名別名",
"domain_quota": "容量配額",
+ "domain_quota_total": "域名總容量配額",
"domains": "域名",
"edit": "編輯",
"empty": "沒有結果",
@@ -734,6 +737,8 @@
"filter_table": "篩選表格",
"filters": "過濾器",
"fname": "全名",
+ "force_pw_update": "你<b>必須</b>設定一個新密碼以繼續使用相關服務。",
+ "gal": "全域地址清單",
"goto_ham": "學習為<b>非垃圾郵件</b>",
"goto_spam": "學習為<b>垃圾郵件</b>",
"hourly": "每小時",
@@ -751,6 +756,9 @@
"mailbox_defquota": "預設信箱大小",
"mailbox_quota": "最大信箱大小",
"mailboxes": "信箱",
+ "max_aliases": "地址別名上限",
+ "max_mailboxes": "信箱數量上限",
+ "max_quota": "每個信箱的最大容量配額",
"mins_interval": "間隔 (分鐘)",
"msg_num": "訊息 #",
"multiple_bookings": "重複登記",
@@ -776,6 +784,7 @@
"recipient_map_old": "原收件人",
"recipient_map_old_info": "原收件人必須為有效的郵件地址",
"recipient_maps": "收件人規則表",
+ "relay_all": "中繼所有收件人",
"remove": "刪除",
"resources": "資源",
"running": "執行中",
diff --git a/data/web/mailbox.php b/data/web/mailbox.php
index a8abd7ca..65c76f53 100644
--- a/data/web/mailbox.php
+++ b/data/web/mailbox.php
@@ -40,6 +40,7 @@ $template_data = [
'domains' => $domains,
'mailboxes' => $mailboxes,
'lang_mailbox' => json_encode($lang['mailbox']),
+ 'lang_rl' => json_encode($lang['ratelimit']),
'lang_datatables' => json_encode($lang['datatables']),
];
diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig
index 7df1f49b..08da5ca9 100644
--- a/data/web/templates/base.twig
+++ b/data/web/templates/base.twig
@@ -143,10 +143,10 @@
var mailcow_info = {
version_tag: '{{ mailcow_info.version_tag }}',
last_version_tag: '{{ mailcow_info.last_version_tag }}',
- updatedAt: '{{ mailcow_info.updatedAt }}',
+ updatedAt: '{{ mailcow_info.updated_at }}',
project_url: '{{ mailcow_info.project_url }}',
- project_owner: '{{ mailcow_info.project_owner }}',
- project_repo: '{{ mailcow_info.project_repo }}',
+ project_owner: '{{ mailcow_info.git_owner }}',
+ project_repo: '{{ mailcow_info.git_repo }}',
branch: '{{ mailcow_info.mailcow_branch }}'
};
diff --git a/data/web/templates/edit/domain-defaults.twig b/data/web/templates/edit/domain-defaults.twig
deleted file mode 100644
index 84b150da..00000000
--- a/data/web/templates/edit/domain-defaults.twig
+++ /dev/null
@@ -1,156 +0,0 @@
-{% extends 'edit.twig' %}
-
-{% block inner_content %}
-{% if result %}
-<ul class="nav nav-tabs" role="tablist">
- <li role="presentation" class="nav-item"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#dedit">{{ lang.edit.domain }}</button></li>
- <li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#dratelimit">{{ lang.edit.ratelimit }}</button></li>
- <li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#dspamfilter">{{ lang.edit.spam_filter }}</button></li>
- <li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#dqwbcc">{{ lang.edit.quota_warning_bcc }}</button></li>
-</ul>
-<hr>
-<div class="tab-content">
- <div id="dedit" class="tab-pane fade show active" role="tabpanel" aria-labelledby="domain-edit">
- <form data-id="editdomain" class="form-horizontal" role="form" method="post">
- <input type="hidden" value="0" name="active">
- <input type="hidden" value="0" name="backupmx">
- <input type="hidden" value="0" name="gal">
- <input type="hidden" value="0" name="relay_all_recipients">
- <input type="hidden" value="0" name="relay_unknown_only">
- {% if mailcow_cc_role == 'admin' %}
- <div class="row mb-2">
- <label class="control-label col-sm-2" for="aliases">{{ lang.edit.max_aliases }}</label>
- <div class="col-sm-10">
- <input type="number" class="form-control" name="aliases" value="{{ result.max_num_aliases_for_domain }}">
- </div>
- </div>
- <div class="row mb-2">
- <label class="control-label col-sm-2" for="mailboxes">{{ lang.edit.max_mailboxes }}</label>
- <div class="col-sm-10">
- <input type="number" class="form-control" name="mailboxes" value="{{ result.max_num_mboxes_for_domain }}">
- </div>
- </div>
- <div class="row mb-2">
- <label class="control-label col-sm-2" for="defquota">{{ lang.edit.mailbox_quota_def }}</label>
- <div class="col-sm-10">
- <input type="number" class="form-control" name="defquota" value="{{ (result.def_quota_for_mbox / 1048576) }}">
- </div>
- </div>
- <div class="row mb-2">
- <label class="control-label col-sm-2" for="maxquota">{{ lang.edit.max_quota }}</label>
- <div class="col-sm-10">
- <input type="number" class="form-control" name="maxquota" value="{{ (result.max_quota_for_mbox / 1048576) }}">
- </div>
- </div>
- <div class="row mb-4">
- <label class="control-label col-sm-2" for="quota">{{ lang.edit.domain_quota }}</label>
- <div class="col-sm-10">
- <input type="number" class="form-control" name="quota" value="{{ (result.max_quota_for_domain / 1048576) }}">
- </div>
- </div>
- {% endif %}
- <hr>
- <div class="row mb-2">
- <div class="offset-sm-2 col-sm-10">
- <div class="checkbox">
- <label><input type="checkbox" value="1" name="active"{% if result.active == '1' %} checked{% endif %}{% if mailcow_cc_role != 'admin' %} disabled{% endif %}> {{ lang.edit.active }}</label>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="offset-sm-2 col-sm-10">
- <button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="editdomain" data-item="{{ domain }}" data-api-url='edit/domain' data-api-attr='{}' href="#">{{ lang.admin.save }}</button>
- </div>
- </div>
- </form>
- </div>
- <div id="dratelimit" class="tab-pane fade" role="tabpanel" aria-labelledby="domain-ratelimit">
- <form data-id="domratelimit" class="form-inline well" method="post">
- <div class="row">
- <div class="col-12">
- <label class="control-label mb-2">{{ lang.edit.ratelimit }}</label>
- <input name="rl_value" type="number" value="{{ rl.value }}" autocomplete="off" class="form-control mb-4" placeholder="{{ lang.ratelimit.disabled }}">
- <select name="rl_frame" class="form-control">
- {% include 'mailbox/rl-frame.twig' %}
- </select>
- <button data-acl="{{ acl.ratelimit }}" class="btn btn-xs-lg d-block d-sm-inline btn-secondary" data-action="edit_selected" data-id="domratelimit" data-item="{{ domain }}" data-api-url='edit/rl-domain' data-api-attr='{}' href="#">{{ lang.admin.save }}</button>
- </div>
- </div>
- </form>
- </div>
- <div id="dspamfilter" class="tab-pane fade" role="tabpanel" aria-labelledby="domain-spamfilter">
- <div class="row">
- <div class="col-sm-6">
- <h4>{{ lang.user.spamfilter_wl }}</h4>
- <p>{{ lang.user.spamfilter_wl_desc|raw }}</p>
- <form class="form-inline mb-4" data-id="add_wl_policy_domain">
- <div class="input-group" data-acl="{{ acl.spam_policy }}">
- <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
- <span class="input-group-btn">
- <button class="btn btn-secondary" data-action="add_item" data-id="add_wl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"{{ domain }}","object_list":"wl"}' href="#">{{ lang.user.spamfilter_table_add }}</button>
- </span>
- </div>
- </form>
- <table id="wl_policy_domain_table" class="table table-striped dt-responsive w-100"></table>
- <div class="mass-actions-user">
- <div class="btn-group" data-acl="{{ acl.spam_policy }}">
- <a class="btn btn-xs-half d-block d-sm-inline btn-sm btn-secondary" id="toggle_multi_select_all" data-id="policy_wl_domain" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
- <a class="btn btn-xs-half d-block d-sm-inline btn-sm btn-danger" data-action="delete_selected" data-id="policy_wl_domain" data-api-url='delete/domain-policy' href="#">{{ lang.mailbox.remove }}</a>
- </div>
- </div>
- </div>
- <div class="col-sm-6">
- <h4>{{ lang.user.spamfilter_bl }}</h4>
- <p>{{ lang.user.spamfilter_bl_desc|raw }}</p>
- <form class="form-inline mb-4" data-id="add_bl_policy_domain">
- <div class="input-group" data-acl="{{ acl.spam_policy }}">
- <input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
- <span class="input-group-btn">
- <button class="btn btn-secondary" data-action="add_item" data-id="add_bl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"{{ domain }}","object_list":"bl"}' href="#">{{ lang.user.spamfilter_table_add }}</button>
- </span>
- </div>
- </form>
- <table id="bl_policy_domain_table" class="table table-striped dt-responsive w-100"></table>
- <div class="mass-actions-user">
- <div class="btn-group" data-acl="{{ acl.spam_policy }}">
- <a class="btn btn-xs-half d-block d-sm-inline btn-sm btn-secondary" id="toggle_multi_select_all" data-id="policy_bl_domain" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
- <a class="btn btn-xs-half d-block d-sm-inline btn-sm btn-danger" data-action="delete_selected" data-id="policy_bl_domain" data-api-url='delete/domain-policy' href="#">{{ lang.mailbox.remove }}</a></li>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div id="dqwbcc" class="tab-pane fade" role="tabpanel" aria-labelledby="domain-qwbcc">
- <div class="row">
- <div class="col-sm-12">
- <h4>{{ lang.edit.quota_warning_bcc }}</h4>
- <p>{{ lang.edit.quota_warning_bcc_info|raw }}</p>
- <form class="form-horizontal" data-id="quota_bcc">
- <input type="hidden" value="0" name="active">
- <div class="row mb-2">
- <label class="control-label col-sm-2" for="script_data">{{ lang.edit.target_address|raw }}:</label>
- <div class="col-sm-10">
- <textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control" rows="10" id="bcc_rcpt" name="bcc_rcpt">{{ quota_notification_bcc.bcc_rcpts|join("\n") }}</textarea>
- </div>
- </div>
- <div class="row mb-4">
- <div class="offset-sm-2 col-sm-10">
- <div class="checkbox">
- <label><input type="checkbox" value="1" name="active"{% if quota_notification_bcc.active == '1' %} checked{% endif %}> {{ lang.edit.active }}</label>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="offset-sm-2 col-sm-10">
- <button class="btn btn-xs-lg d-block d-sm-inline 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>
-{% else %}
- {{ parent() }}
-{% endif %}
-{% endblock %}
diff --git a/data/web/templates/edit/domain-templates.twig b/data/web/templates/edit/domain-templates.twig
new file mode 100644
index 00000000..5a0cbb69
--- /dev/null
+++ b/data/web/templates/edit/domain-templates.twig
@@ -0,0 +1,136 @@
+{% extends 'edit.twig' %}
+
+{% block inner_content %}
+{% if result %}
+<div id="dedit" class="tab-pane fade show active" role="tabpanel" aria-labelledby="domain-edit">
+ <form data-id="editdomain_template" class="form-horizontal" role="form" method="post">
+
+ <input type="hidden" value="0" name="active">
+ <input type="hidden" value="0" name="backupmx">
+ <input type="hidden" value="0" name="gal">
+ <input type="hidden" value="0" name="relay_all_recipients">
+ <input type="hidden" value="0" name="relay_unknown_only">
+
+ {% if mailcow_cc_role == 'admin' %}
+ <div class="row mb-4">
+ <label class="control-label col-sm-2" for="max_num_aliases_for_domain">{{ lang.mailbox.template }}</label>
+ <div class="col-sm-10">
+
+ <div class="input-group mb-3">
+ <input type="text" name="template" class="form-control" aria-label="Text input with dropdown button" value="{{ template.template }}" />
+ </div>
+ </div>
+
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2">{{ lang.add.tags }}</label>
+ <div class="col-sm-10">
+ <div class="form-control tag-box">
+ <input id="tags" type="text" class="tag-input">
+ <span class="btn tag-add"><i class="bi bi-plus-lg"></i></span>
+ <input type="hidden" value='{{ template.attributes.tags|json_encode }}' name="tags" class="tag-values" />
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2" for="max_num_aliases_for_domain">{{ lang.edit.max_aliases }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_num_aliases_for_domain" value="{{ template.attributes.max_num_aliases_for_domain }}">
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2" for="max_num_mboxes_for_domain">{{ lang.edit.max_mailboxes }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_num_mboxes_for_domain" value="{{ template.attributes.max_num_mboxes_for_domain }}">
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2" for="def_quota_for_mbox">{{ lang.edit.mailbox_quota_def }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="def_quota_for_mbox" value="{{ (template.attributes.def_quota_for_mbox / 1048576) }}">
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2" for="max_quota_for_mbox">{{ lang.edit.max_quota }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_quota_for_mbox" value="{{ (template.attributes.max_quota_for_mbox / 1048576) }}">
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2" for="max_quota_for_domain">{{ lang.edit.domain_quota }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_quota_for_domain" value="{{ (template.attributes.max_quota_for_domain / 1048576) }}">
+ </div>
+ </div>
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="gal"{% if template.attributes.gal == '1' %} checked{% endif %}> {{ lang.edit.gal }}</label>
+ <small class="text-muted">{{ lang.edit.gal_info|raw }}</small>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="active"{% if template.attributes.active == '1' %} checked{% endif %}{% if mailcow_cc_role != 'admin' %} disabled{% endif %}> {{ lang.edit.active }}</label>
+ </div>
+ </div>
+ </div>
+ <hr>
+ <div class="row">
+ <label class="control-label col-sm-2">{{ lang.edit.ratelimit }}</label>
+ <div class="col-sm-10">
+ <input name="rl_value" type="number" value="{{ template.attributes.rl_value }}" autocomplete="off" class="form-control mb-4" placeholder="{{ lang.ratelimit.disabled }}">
+ <select name="rl_frame" class="form-control">
+ <option value="s"{% if template.attributes.rl_frame == 's' %} selected{% endif %}>{{ lang.ratelimit.second }}</option>
+ <option value="m"{% if template.attributes.rl_frame == 'm' %} selected{% endif %}>{{ lang.ratelimit.minute }}</option>
+ <option value="h"{% if template.attributes.rl_frame == 'h' %} selected{% endif %}>{{ lang.ratelimit.hour }}</option>
+ <option value="d"{% if template.attributes.rl_frame == 'd' %} selected{% endif %}>{{ lang.ratelimit.day }}</option>
+ </select>
+ </div>
+ </div>
+ {% endif %}
+ <hr>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2" for="dkim_selector">{{ lang.admin.dkim_domains_selector }}</label>
+ <div class="col-sm-10">
+ <input class="form-control" id="dkim_selector" name="dkim_selector" value="{{ template.attributes.dkim_selector }}">
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2" for="key_size">{{ lang.admin.dkim_key_length }}</label>
+ <div class="col-sm-10">
+ <select data-style="btn btn-secondary btn-sm" class="form-control" id="key_size" name="key_size">
+ <option value="1024" data-subtext="bits" {% if template.attributes.key_size == 1024 %} selected{% endif %}>1024</option>
+ <option value="2048" data-subtext="bits" {% if template.attributes.key_size == 2048 %} selected{% endif %}>2048</option>
+ </select>
+ </div>
+ </div>
+ <hr>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2">{{ lang.edit.backup_mx_options }}</label>
+ <div class="col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="backupmx"{% if template.attributes.backupmx == '1' %} checked{% endif %}> {{ lang.edit.relay_domain }}</label>
+ <br>
+ <label><input type="checkbox" value="1" name="relay_all_recipients"{% if template.attributes.relay_all_recipients == '1' %} checked{% endif %}> {{ lang.edit.relay_all }}</label>
+ <p>{{ lang.edit.relay_all_info|raw }}</p>
+ <label><input type="checkbox" value="1" name="relay_unknown_only"{% if template.attributes.relay_unknown_only == '1' %} checked{% endif %}> {{ lang.edit.relay_unknown_only }}</label>
+ <br>
+ <p>{{ lang.edit.relay_transport_info|raw }}</p>
+ </div>
+ </div>
+ </div>
+ <hr>
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="editdomain_template" data-item="{{ template.id }}" data-api-url='edit/domain/template' data-api-attr='{}' href="#">{{ lang.admin.save }}</button>
+ </div>
+ </div>
+ </form>
+</div>
+{% else %}
+ {{ parent() }}
+{% endif %}
+{% endblock %}
diff --git a/data/web/templates/edit/mailbox-defaults.twig b/data/web/templates/edit/mailbox-defaults.twig
deleted file mode 100644
index 9487c856..00000000
--- a/data/web/templates/edit/mailbox-defaults.twig
+++ /dev/null
@@ -1,195 +0,0 @@
-{% extends 'edit.twig' %}
-
-{% block inner_content %}
-{% if result %}
-<ul class="nav nav-tabs" role="tablist">
- <li role="presentation" class="nav-item"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#medit">{{ lang.edit.mailbox }}</button></li>
- <li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#macl">{{ lang.edit.acl }}</button></li>
- <li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#mrl">{{ lang.edit.ratelimit }}</button></li>
-</ul>
-<hr>
-<div class="tab-content">
- <div id="medit" class="tab-pane fade show active" role="tabpanel" aria-labelledby="mailbox-edit">
- <form class="form-horizontal" data-id="editmailbox" role="form" method="post">
- <input type="hidden" value="default" name="sender_acl">
- <input type="hidden" value="0" name="force_pw_update">
- <input type="hidden" value="0" name="sogo_access">
- <input type="hidden" value="0" name="protocol_access">
- <div class="row mb-2">
- <label class="control-label col-sm-2" for="quota">
- {{ lang.edit.quota_mb }}
- </label>
- <div class="col-sm-10">
- <input type="number" name="quota" style="width:100%" min="0" max="{{ (result.max_new_quota / 1048576) }}" value="{{ (result.quota / 1048576) }}" class="form-control">
- <small class="text-muted">0 = ∞</small>
- </div>
- </div>
- <div class="row mb-2">
- <label class="control-label col-sm-2">{{ lang.user.quarantine_notification }}</label>
- <div class="col-sm-10">
- <div class="btn-group" data-acl="{{ acl.quarantine_notification }}">
- <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'never' %} active{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="quarantine_notification"
- data-api-url='edit/quarantine_notification'
- data-api-attr='{"quarantine_notification":"never"}'>{{ lang.user.never }}</button>
- <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'hourly' %} active{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="quarantine_notification"
- data-api-url='edit/quarantine_notification'
- data-api-attr='{"quarantine_notification":"hourly"}'>{{ lang.user.hourly }}</button>
- <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'daily' %} active{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="quarantine_notification"
- data-api-url='edit/quarantine_notification'
- data-api-attr='{"quarantine_notification":"daily"}'>{{ lang.user.daily }}</button>
- <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'weekly' %} active{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="quarantine_notification"
- data-api-url='edit/quarantine_notification'
- data-api-attr='{"quarantine_notification":"weekly"}'>{{ lang.user.weekly }}</button>
- </div>
- <p class="text-muted"><small>{{ lang.user.quarantine_notification_info }}</small></p>
- </div>
- </div>
- <div class="row mb-2">
- <label class="control-label col-sm-2">{{ lang.user.quarantine_category }}</label>
- <div class="col-sm-10">
- <div class="btn-group" data-acl="{{ acl.quarantine_category }}">
- <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'reject' %} active{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="quarantine_category"
- data-api-url='edit/quarantine_category'
- data-api-attr='{"quarantine_category":"reject"}'>{{ lang.user.q_reject }}</button>
- <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'add_header' %} active{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="quarantine_category"
- data-api-url='edit/quarantine_category'
- data-api-attr='{"quarantine_category":"add_header"}'>{{ lang.user.q_add_header }}</button>
- <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'all' %} active{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="quarantine_category"
- data-api-url='edit/quarantine_category'
- data-api-attr='{"quarantine_category":"all"}'>{{ lang.user.q_all }}</button>
- </div>
- <p class="text-muted"><small>{{ lang.user.quarantine_category_info }}</small></p>
- </div>
- </div>
- <div class="row mb-4">
- <label class="control-label col-sm-2" for="sender_acl">{{ lang.user.tls_policy }}</label>
- <div class="col-sm-10">
- <div class="btn-group" data-acl="{{ acl.tls_policy }}">
- <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_in == '1' %} active"{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="tls_policy"
- data-api-url='edit/tls_policy'
- data-api-attr='{"tls_enforce_in": {% if get_tls_policy.tls_enforce_in == '1' %}0{% else %}1{% endif %} }'>{{ lang.user.tls_enforce_in }}</button>
- <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_out == '1' %} active"{% endif %}"
- data-action="edit_selected"
- data-item="{{ mailbox }}"
- data-id="tls_policy"
- data-api-url='edit/tls_policy'
- data-api-attr='{"tls_enforce_out": {% if get_tls_policy.tls_enforce_out == '1' %}0{% else %}1{% endif %} }'>{{ lang.user.tls_enforce_out }}</button>
- </div>
- </div>
- </div>
- <div class="row">
- <label class="control-label col-sm-2" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
- <div class="col-sm-10">
- <select data-acl="{{ acl.protocol_access }}" name="protocol_access" multiple class="form-control">
- <option value="imap"{% if result.attributes.imap_access == '1' %} selected{% endif %}>IMAP</option>
- <option value="pop3"{% if result.attributes.pop3_access == '1' %} selected{% endif %}>POP3</option>
- <option value="smtp"{% if result.attributes.smtp_access == '1' %} selected{% endif %}>SMTP</option>
- <option value="sieve"{% if result.attributes.sieve_access == '1' %} selected{% endif %}>Sieve</option>
- </select>
- </div>
- </div>
- <div hidden data-acl="{{ acl.smtp_ip_access }}" class="row">
- <label class="control-label col-sm-2" for="allow_from_smtp">{{ lang.edit.allow_from_smtp }}</label>
- <div class="col-sm-10">
- <input type="text" class="form-control" name="allow_from_smtp" value="{{ allow_from_smtp }}" placeholder="1.1.1.1, 10.2.0.0/24, ...">
- <small class="text-muted">{{ lang.edit.allow_from_smtp_info }}</small>
- </div>
- </div>
- <hr>
- <div class="row my-2">
- <div class="offset-sm-2 col-sm-10">
- <select name="active" class="form-control">
- <option value="1"{% if result.active == '1' %} selected{% endif %}>{{ lang.edit.active }}</option>
- <option value="2"{% if result.active == '2' %} selected{% endif %}>{{ lang.edit.disable_login }}</option>
- <option value="0"{% if result.active == '0' %} selected{% endif %}>{{ lang.edit.inactive }}</option>
- </select>
- </div>
- </div>
- <div class="row">
- <div class="offset-sm-2 col-sm-10">
- <div class="checkbox">
- <label><input type="checkbox" value="1" name="force_pw_update"{% if result.attributes.force_pw_update == '1' %} checked{% endif %}> {{ lang.edit.force_pw_update }}</label>
- <small class="text-muted">{{ lang.edit.force_pw_update_info|format(ui_texts.main_name) }}</small>
- </div>
- </div>
- </div>
- {% if not skip_sogo %}
- <div data-acl="{{ acl.sogo_access }}" class="row">
- <div class="offset-sm-2 col-sm-10">
- <div class="checkbox">
- <label><input type="checkbox" value="1" name="sogo_access"{% if result.attributes.sogo_access == '1' %} checked{% endif %}> {{ lang.edit.sogo_access }}</label>
- <small class="text-muted">{{ lang.edit.sogo_access_info }}</small>
- </div>
- </div>
- </div>
- {% endif %}
- <div class="row my-2">
- <div class="offset-sm-2 col-sm-10">
- <button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="editmailbox" data-item="{{ result.username }}" data-api-url='edit/mailbox' data-api-attr='{}' href="#">{{ lang.edit.save }}</button>
- </div>
- </div>
- </form>
- </div>
- <div id="macl" class="tab-pane fade" role="tabpanel" aria-labelledby="mailbox-acl">
- <form data-id="useracl" class="form-inline well" method="post">
- <div class="row">
- <div class="col-sm-1">
- <p class="text-muted">ACL</p>
- </div>
- <div class="col-sm-10">
- <select id="user_acl" name="user_acl" size="10" multiple>
- {% for acl, val in user_acls %}
- <option value="{{ acl }}"{% if val == 1 %} selected{% endif %}>{{ lang.acl[acl] }}</option>
- {% endfor %}
- </select>
- <button class="btn btn-xs-lg d-block d-sm-inline btn-secondary" data-action="edit_selected" data-id="useracl" data-item="{{ mailbox }}" data-api-url='edit/user-acl' data-api-attr='{}' href="#">{{ lang.edit.save }}</button>
- </div>
- </div>
- </form>
- </div>
- <div id="mrl" class="tab-pane fade" role="tabpanel" aria-labelledby="mailbox-rl">
- <form data-id="mboxratelimit" class="form-inline well" method="post">
- <div class="row">
- <div class="col-sm-1">
- <p class="text-muted">{{ lang.acl.ratelimit }}</p>
- </div>
- <div class="col-sm-10">
- <input name="rl_value" type="number" autocomplete="off" value="{{ rl.value }}" class="form-control mb-4" placeholder="{{ lang.ratelimit.disabled }}">
- <select name="rl_frame" class="form-control">
- {% include 'mailbox/rl-frame.twig' %}
- </select>
- <button class="btn btn-xs-lg d-block d-sm-inline btn-secondary" data-action="edit_selected" data-id="mboxratelimit" data-item="{{ mailbox }}" data-api-url='edit/rl-mbox' data-api-attr='{}' href="#">{{ lang.edit.save }}</button>
- <p class="text-muted mt-3">{{ lang.edit.mbox_rl_info }}</p>
- </div>
- </div>
- </form>
- </div>
-</div>
-{% else %}
- {{ parent() }}
-{% endif %}
-{% endblock %}
diff --git a/data/web/templates/edit/mailbox-templates.twig b/data/web/templates/edit/mailbox-templates.twig
new file mode 100644
index 00000000..fea99bdd
--- /dev/null
+++ b/data/web/templates/edit/mailbox-templates.twig
@@ -0,0 +1,169 @@
+{% extends 'edit.twig' %}
+
+{% block inner_content %}
+{% if result %}
+<hr>
+<div id="medit" class="tab-pane fade show active" role="tabpanel" aria-labelledby="mailbox-edit">
+ <form class="form-horizontal" data-id="editmailbox_template" role="form" method="post">
+
+ <input type="hidden" value="default" name="sender_acl">
+ <input type="hidden" value="0" name="force_pw_update">
+ <input type="hidden" value="0" name="sogo_access">
+ <input type="hidden" value="0" name="protocol_access">
+
+ <div class="row mb-4">
+ <label class="control-label col-sm-2" for="template">{{ lang.mailbox.template }}</label>
+ <div class="col-sm-10">
+ <div class="input-group mb-3">
+ <input type="text" name="template" class="form-control" aria-label="Text input with dropdown button" value="{{ template.template }}" />
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2">{{ lang.add.tags }}</label>
+ <div class="col-sm-10">
+ <div class="form-control tag-box">
+ <input id="tags" type="text" class="tag-input">
+ <span class="btn tag-add"><i class="bi bi-plus-lg"></i></span>
+ <input type="hidden" value='{{ template.attributes.tags|json_encode }}' name="tags" class="tag-values" />
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2" for="quota">{{ lang.edit.quota_mb }}</label>
+ <div class="col-sm-10">
+ <input type="number" name="quota" class="w-100 form-control" min="0" value="{{ template.attributes.quota / 1048576 }}">
+ <small class="text-muted">0 = ∞</small>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2">{{ lang.user.quarantine_notification }}</label>
+ <div class="col-sm-10">
+ <div class="btn-group">
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_never" autocomplete="off" value="never" {% if template.attributes.quarantine_notification == 'never' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_never">{{ lang.user.never }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_hourly" autocomplete="off" value="hourly" {% if template.attributes.quarantine_notification == 'hourly' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_hourly">{{ lang.user.hourly }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_daily" autocomplete="off" value="daily" {% if template.attributes.quarantine_notification == 'daily' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_daily">{{ lang.user.daily }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_weekly" autocomplete="off" value="weekly" {% if template.attributes.quarantine_notification == 'weekly' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_weekly">{{ lang.user.weekly }}</label>
+ </div>
+ <p class="text-muted"><small>{{ lang.user.quarantine_notification_info }}</small></p>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2">{{ lang.user.quarantine_category }}</label>
+ <div class="col-sm-10">
+ <div class="btn-group">
+ <input type="radio" class="btn-check" name="quarantine_category" id="quarantine_category_reject" autocomplete="off" value="reject" {% if template.attributes.quarantine_category == 'reject' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_category_reject">{{ lang.user.q_reject }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_category" id="quarantine_category_add_header" autocomplete="off" value="add_header" {% if template.attributes.quarantine_category == 'add_header' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_category_add_header">{{ lang.user.q_add_header }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_category" id="quarantine_category_all" autocomplete="off" value="all" {% if template.attributes.quarantine_category == 'all' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_category_all">{{ lang.user.q_all }}</label>
+ </div>
+ <p class="text-muted"><small>{{ lang.user.quarantine_category_info }}</small></p>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2" for="sender_acl">{{ lang.user.tls_policy }}</label>
+ <div class="col-sm-10">
+ <div class="btn-group">
+ <input type="checkbox" class="btn-check" name="tls_enforce_in" id="tls_enforce_in" autocomplete="off" value="1" {% if template.attributes.tls_enforce_in == '1' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="tls_enforce_in">{{ lang.user.tls_enforce_in }}</label>
+
+ <input type="checkbox" class="btn-check" name="tls_enforce_out" id="tls_enforce_out" autocomplete="off" value="1" {% if template.attributes.tls_enforce_out == '1' %}checked{% endif %}>
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="tls_enforce_out">{{ lang.user.tls_enforce_out }}</label>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
+ <div class="col-sm-10">
+ <select name="protocol_access" multiple class="form-control">
+ <option value="imap"{% if template.attributes.imap_access == '1' %} selected{% endif %}>IMAP</option>
+ <option value="pop3"{% if template.attributes.pop3_access == '1' %} selected{% endif %}>POP3</option>
+ <option value="smtp"{% if template.attributes.smtp_access == '1' %} selected{% endif %}>SMTP</option>
+ <option value="sieve"{% if template.attributes.sieve_access == '1' %} selected{% endif %}>Sieve</option>
+ </select>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2">ACL</label>
+ <div class="col-sm-10">
+ <select id="template_user_acl" name="acl" size="10" multiple class="form-control">
+ <option value="spam_alias" {% if template.attributes.acl_spam_alias == '1' %} selected{% endif %}>{{ lang.acl["spam_alias"] }}</option>
+ <option value="tls_policy" {% if template.attributes.acl_tls_policy == '1' %} selected{% endif %}>{{ lang.acl["tls_policy"] }}</option>
+ <option value="spam_score" {% if template.attributes.acl_spam_score == '1' %} selected{% endif %}>{{ lang.acl["spam_score"] }}</option>
+ <option value="spam_policy" {% if template.attributes.acl_spam_policy == '1' %} selected{% endif %}>{{ lang.acl["spam_policy"] }}</option>
+ <option value="delimiter_action" {% if template.attributes.acl_delimiter_action == '1' %} selected{% endif %}>{{ lang.acl["delimiter_action"] }}</option>
+ <option value="syncjobs" {% if template.attributes.acl_syncjobs == '1' %} selected{% endif %}>{{ lang.acl["syncjobs"] }}</option>
+ <option value="eas_reset" {% if template.attributes.acl_eas_reset == '1' %} selected{% endif %}>{{ lang.acl["eas_reset"] }}</option>
+ <option value="sogo_profile_reset" {% if template.attributes.acl_sogo_profile_reset == '1' %} selected{% endif %}>{{ lang.acl["sogo_profile_reset"] }}</option>
+ <option value="pushover" {% if template.attributes.acl_pushover == '1' %} selected{% endif %}>{{ lang.acl["pushover"] }}</option>
+ <option value="quarantine" {% if template.attributes.acl_quarantine == '1' %} selected{% endif %}>{{ lang.acl["quarantine"] }}</option>
+ <option value="quarantine_attachments" {% if template.attributes.acl_quarantine_attachments == '1' %} selected{% endif %}>{{ lang.acl["quarantine_attachments"] }}</option>
+ <option value="quarantine_notification" {% if template.attributes.acl_quarantine_notification == '1' %} selected{% endif %}>{{ lang.acl["quarantine_notification"] }}</option>
+ <option value="quarantine_category" {% if template.attributes.acl_quarantine_category == '1' %} selected{% endif %}>{{ lang.acl["quarantine_category"] }}</option>
+ <option value="app_passwds" {% if template.attributes.acl_app_passwds == '1' %} selected{% endif %}>{{ lang.acl["app_passwds"] }}</option>
+ </select>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2">{{ lang.acl.ratelimit }}</label>
+ <div class="col-sm-10">
+ <input name="rl_value" type="number" autocomplete="off" value="{{ template.attributes.rl_value }}" class="form-control mb-2" placeholder="{{ lang.ratelimit.disabled }}">
+ <select name="rl_frame" class="form-control">
+ <option value="s"{% if template.attributes.rl_frame == 's' %} selected{% endif %}>{{ lang.ratelimit.second }}</option>
+ <option value="m"{% if template.attributes.rl_frame == 'm' %} selected{% endif %}>{{ lang.ratelimit.minute }}</option>
+ <option value="h"{% if template.attributes.rl_frame == 'h' %} selected{% endif %}>{{ lang.ratelimit.hour }}</option>
+ <option value="d"{% if template.attributes.rl_frame == 'd' %} selected{% endif %}>{{ lang.ratelimit.day }}</option>
+ </select>
+ <p class="text-muted mt-3">{{ lang.edit.mbox_rl_info }}</p>
+ </div>
+ </div>
+ <hr>
+ <div class="row my-2">
+ <div class="offset-sm-2 col-sm-10">
+ <select name="active" class="form-control">
+ <option value="1"{% if template.attributes.active == '1' %} selected{% endif %}>{{ lang.edit.active }}</option>
+ <option value="2"{% if template.attributes.active == '2' %} selected{% endif %}>{{ lang.edit.disable_login }}</option>
+ <option value="0"{% if template.attributes.active == '0' %} selected{% endif %}>{{ lang.edit.inactive }}</option>
+ </select>
+ </div>
+ </div>
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="force_pw_update"{% if template.attributes.force_pw_update == '1' %} checked{% endif %}> {{ lang.edit.force_pw_update }}</label>
+ <small class="text-muted">{{ lang.edit.force_pw_update_info|format(ui_texts.main_name) }}</small>
+ </div>
+ </div>
+ </div>
+ {% if not skip_sogo %}
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="sogo_access"{% if template.attributes.sogo_access == '1' %} checked{% endif %}> {{ lang.edit.sogo_access }}</label>
+ <small class="text-muted">{{ lang.edit.sogo_access_info }}</small>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ <div class="row my-2">
+ <div class="offset-sm-2 col-sm-10">
+ <button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="editmailbox_template" data-item="{{ template.id }}" data-api-url='edit/mailbox/template' data-api-attr='{}' href="#">{{ lang.edit.save }}</button>
+ </div>
+ </div>
+ </form>
+</div>
+{% else %}
+ {{ parent() }}
+{% endif %}
+{% endblock %}
diff --git a/data/web/templates/mailbox.twig b/data/web/templates/mailbox.twig
index 26215ef2..fa89b001 100644
--- a/data/web/templates/mailbox.twig
+++ b/data/web/templates/mailbox.twig
@@ -6,8 +6,8 @@
<li class="nav-item dropdown" role="presentation">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.domains }}</a>
<ul class="dropdown-menu">
- <li><button class="dropdown-item" aria-selected="false" aria-controls="tab-mailboxes" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
- <li><a href="/edit/domain/defaults" class="dropdown-item" aria-selected="false" aria-controls="tab-domain-defaults" role="tab">{{ lang.mailbox.mailbox_defaults }}</a></li>
+ <li><button class="dropdown-item" aria-selected="false" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li>
+ <li><button class="dropdown-item {% if mailcow_cc_role != 'admin' %} d-none{% endif %}" aria-selected="false" aria-controls="tab-templates-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-domains">{{ lang.mailbox.templates }}</button></li>
</ul>
</li>
{# <li class="nav-item" role="presentation"><button class="nav-link active" aria-selected="false" aria-controls="tab-domains" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domains">{{ lang.mailbox.domains }}</button></li> #}
@@ -16,7 +16,7 @@
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.mailboxes }}</a>
<ul class="dropdown-menu">
<li><button class="dropdown-item" aria-selected="false" aria-controls="tab-mailboxes" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mailboxes">{{ lang.mailbox.mailboxes }}</button></li>
- <li><a href="/edit/mailbox/defaults" class="dropdown-item" aria-selected="false" aria-controls="tab-mailbox-defaults" role="tab">{{ lang.mailbox.mailbox_defaults }}</a></li>
+ <li><button class="dropdown-item {% if mailcow_cc_role != 'admin' %} d-none{% endif %}" aria-selected="false" aria-controls="tab-templates-mbox" role="tab" data-bs-toggle="tab" data-bs-target="#tab-templates-mbox">{{ lang.mailbox.templates }}</button></li>
</ul>
</li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-controls="tab-resources" role="tab" data-bs-toggle="tab" data-bs-target="#tab-resources">{{ lang.mailbox.resources }}</button></li>
@@ -38,7 +38,9 @@
<div class="tab-content" style="padding-top:20px">
{% include 'mailbox/tab-domains.twig' %}
{# {% include 'mailbox/tab-mailbox-defaults.twig' %} #}
+ {% include 'mailbox/tab-templates-domains.twig' %}
{% include 'mailbox/tab-mailboxes.twig' %}
+ {% include 'mailbox/tab-templates-mbox.twig' %}
{% include 'mailbox/tab-resources.twig' %}
{% include 'mailbox/tab-domain-aliases.twig' %}
{% include 'mailbox/tab-mbox-aliases.twig' %}
@@ -56,6 +58,7 @@
<script type='text/javascript'>
var acl = '{{ acl_json|raw }}';
var lang = {{ lang_mailbox|raw }};
+ var lang_rl = {{ lang_rl|raw }};
var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}';
diff --git a/data/web/templates/mailbox/tab-templates-domains.twig b/data/web/templates/mailbox/tab-templates-domains.twig
new file mode 100644
index 00000000..95bd2d7a
--- /dev/null
+++ b/data/web/templates/mailbox/tab-templates-domains.twig
@@ -0,0 +1,51 @@
+<div role="tabpanel" class="tab-pane fade show" id="tab-templates-domains" role="tabpanel" aria-labelledby="tab-templates-domains">
+ <div class="card mb-4">
+ <div class="card-header d-flex fs-5">
+ <button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-templates-domains" data-bs-toggle="collapse" aria-controls="collapse-tab-templates-domains">
+ {{ lang.mailbox.domain_templates }} <span class="badge bg-info table-lines"></span>
+ </button>
+ <span class="d-none d-md-block">{{ lang.mailbox.domain_templates }} <span class="badge bg-info table-lines"></span></span>
+
+ <div class="btn-group ms-auto d-flex">
+ <button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_templates_domain_table" data-table="templates_domain_table">{{ lang.admin.refresh }}</button>
+ </div>
+ </div>
+ <div id="collapse-tab-templates-domains" class="card-body collapse show" data-bs-parent="#mail-content">
+ <div class="mass-actions-mailbox mb-4">
+ <div class="btn-group">
+ <button class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="domain_template" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</button>
+ <button class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</button>
+ <ul class="dropdown-menu">
+ {% if mailcow_cc_role == 'admin' %}
+ <li><a class="dropdown-item" data-action="delete_selected" data-id="domain_template" data-api-url='delete/domain/template' href="#">{{ lang.mailbox.remove }}</a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><a class="dropdown-item" data-datatables-expand="templates_domain_table">{{ lang.datatables.expand_all }}</a></li>
+ <li><a class="dropdown-item" data-datatables-collapse="templates_domain_table">{{ lang.datatables.collapse_all }}</a></li>
+ {% endif %}
+ </ul>
+ {% if mailcow_cc_role == 'admin' %}
+ <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addDomainTemplateModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_template }}</a>
+ {% endif %}
+ </div>
+ </div>
+ <table id="templates_domain_table" class="table table-striped dt-responsive w-100"></table>
+ <div class="mass-actions-mailbox mt-4">
+ <div class="btn-group">
+ <button class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="domain_template" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</button>
+ <button class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</button>
+ <ul class="dropdown-menu">
+ {% if mailcow_cc_role == 'admin' %}
+ <li><a class="dropdown-item" data-action="delete_selected" data-id="domain_template" data-api-url='delete/domain/template' href="#">{{ lang.mailbox.remove }}</a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><a class="dropdown-item" data-datatables-expand="templates_domain_table">{{ lang.datatables.expand_all }}</a></li>
+ <li><a class="dropdown-item" data-datatables-collapse="templates_domain_table">{{ lang.datatables.collapse_all }}</a></li>
+ {% endif %}
+ </ul>
+ {% if mailcow_cc_role == 'admin' %}
+ <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addDomainTemplateModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_template }}</a>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/data/web/templates/mailbox/tab-templates-mbox.twig b/data/web/templates/mailbox/tab-templates-mbox.twig
new file mode 100644
index 00000000..a43ec914
--- /dev/null
+++ b/data/web/templates/mailbox/tab-templates-mbox.twig
@@ -0,0 +1,51 @@
+<div role="tabpanel" class="tab-pane fade show" id="tab-templates-mbox" role="tabpanel" aria-labelledby="tab-templates-mbox">
+ <div class="card mb-4">
+ <div class="card-header d-flex fs-5">
+ <button class="btn d-sm-block d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-templates-mbox" data-bs-toggle="collapse" aria-controls="collapse-tab-templates-mbox">
+ {{ lang.mailbox.mailbox_templates }} <span class="badge bg-info table-lines"></span>
+ </button>
+ <span class="d-none d-md-block">{{ lang.mailbox.mailbox_templates }} <span class="badge bg-info table-lines"></span></span>
+
+ <div class="btn-group ms-auto d-flex">
+ <button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_templates_mbox_table" data-table="templates_mbox_table">{{ lang.admin.refresh }}</button>
+ </div>
+ </div>
+ <div id="collapse-tab-templates-mbox" class="card-body collapse show" data-bs-parent="#mail-content">
+ <div class="mass-actions-mailbox mb-4">
+ <div class="btn-group">
+ <button class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="mailbox_template" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</button>
+ <button class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</button>
+ <ul class="dropdown-menu">
+ {% if mailcow_cc_role == 'admin' %}
+ <li><a class="dropdown-item" data-action="delete_selected" data-id="mailbox_template" data-api-url='delete/mailbox/template' href="#">{{ lang.mailbox.remove }}</a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><a class="dropdown-item" data-datatables-expand="templates_mbox_table">{{ lang.datatables.expand_all }}</a></li>
+ <li><a class="dropdown-item" data-datatables-collapse="templates_mbox_table">{{ lang.datatables.collapse_all }}</a></li>
+ {% endif %}
+ </ul>
+ {% if mailcow_cc_role == 'admin' %}
+ <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addMailboxTemplateModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_template }}</a>
+ {% endif %}
+ </div>
+ </div>
+ <table id="templates_mbox_table" class="table table-striped dt-responsive w-100"></table>
+ <div class="mass-actions-mailbox mt-4">
+ <div class="btn-group">
+ <button class="btn btn-sm btn-xs-half btn-secondary" id="toggle_multi_select_all" data-id="mailbox_template" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</button>
+ <button class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</button>
+ <ul class="dropdown-menu">
+ {% if mailcow_cc_role == 'admin' %}
+ <li><a class="dropdown-item" data-action="delete_selected" data-id="mailbox_template" data-api-url='delete/mailbox/template' href="#">{{ lang.mailbox.remove }}</a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><a class="dropdown-item" data-datatables-expand="templates_mbox_table">{{ lang.datatables.expand_all }}</a></li>
+ <li><a class="dropdown-item" data-datatables-collapse="templates_mbox_table">{{ lang.datatables.collapse_all }}</a></li>
+ {% endif %}
+ </ul>
+ {% if mailcow_cc_role == 'admin' %}
+ <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addMailboxTemplateModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_template }}</a>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/data/web/templates/modals/mailbox.twig b/data/web/templates/modals/mailbox.twig
index f5306e24..25185de0 100644
--- a/data/web/templates/modals/mailbox.twig
+++ b/data/web/templates/modals/mailbox.twig
@@ -8,6 +8,10 @@
</div>
<div class="modal-body">
<form class="form-horizontal" data-cached-form="true" data-id="add_mailbox" role="form" autocomplete="off">
+ <input type="hidden" value="0" name="force_pw_update">
+ <input type="hidden" value="0" name="sogo_access">
+ <input type="hidden" value="0" name="protocol_access">
+
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="local_part">{{ lang.add.mailbox_username }}</label>
<div class="col-sm-10">
@@ -30,19 +34,38 @@
<input type="text" class="form-control" name="name">
</div>
</div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="password">{{ lang.add.password }} (<a href="#" class="generate_password">{{ lang.add.generate }}</a>)</label>
+ <div class="col-sm-10">
+ <input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="" autocomplete="new-password" required>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="password2">{{ lang.add.password_repeat }}</label>
+ <div class="col-sm-10">
+ <input type="password" data-pwgen-field="true" class="form-control" name="password2" placeholder="" autocomplete="new-password" required>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="description">{{ lang.mailbox.template }}</label>
+ <div class="col-sm-10">
+ <select data-live-search="true" id="mailbox_templates" class="form-control" title="{{ lang.mailbox.template }}">
+ </select>
+ </div>
+ </div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.add.tags }}</label>
<div class="col-sm-10">
<div class="form-control tag-box">
- <input type="text" class="tag-input">
+ <input type="text" class="tag-input" id="addMailbox_tags">
<span class="btn tag-add"><i class="bi bi-plus-lg"></i></span>
<input type="hidden" value="" name="tags" class="tag-values" />
</div>
</div>
</div>
- <div class="row mb-2">
+ <div class="row mb-4">
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="addInputQuota">{{ lang.add.quota_mb }}
- <br /><span id="quotaBadge" class="badge">max. - MiB</span>
+ <br /><span id="quotaBadge" class="badge bg-primary">max. - MiB</span>
</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="quota" min="0" max="" id="addInputQuota" disabled value="{{ lang.add.select_domain }}" required>
@@ -51,26 +74,124 @@
</div>
</div>
<div class="row mb-2">
- <label class="control-label col-sm-2 text-sm-end text-sm-end" for="password">{{ lang.add.password }} (<a href="#" class="generate_password">{{ lang.add.generate }}</a>)</label>
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.quarantine_notification }}</label>
<div class="col-sm-10">
- <input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="" autocomplete="new-password" required>
+ <div class="btn-group">
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_never" autocomplete="off" value="never">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_never">{{ lang.user.never }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_hourly" autocomplete="off" value="hourly">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_hourly">{{ lang.user.hourly }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_daily" autocomplete="off" value="daily">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_daily">{{ lang.user.daily }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="quarantine_notification_weekly" autocomplete="off" value="weekly">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_notification_weekly">{{ lang.user.weekly }}</label>
+ </div>
+ <p class="text-muted"><small>{{ lang.user.quarantine_notification_info }}</small></p>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.quarantine_category }}</label>
+ <div class="col-sm-10">
+ <div class="btn-group">
+ <input type="radio" class="btn-check" name="quarantine_category" id="quarantine_category_reject" autocomplete="off" value="reject">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_category_reject">{{ lang.user.q_reject }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_category" id="quarantine_category_add_header" autocomplete="off" value="add_header">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_category_add_header">{{ lang.user.q_add_header }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_category" id="quarantine_category_all" autocomplete="off" value="all">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="quarantine_category_all">{{ lang.user.q_all }}</label>
+ </div>
+ <p class="text-muted"><small>{{ lang.user.quarantine_category_info }}</small></p>
</div>
</div>
<div class="row mb-4">
- <label class="control-label col-sm-2 text-sm-end text-sm-end" for="password2">{{ lang.add.password_repeat }}</label>
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="tls_enforce_in">{{ lang.user.tls_policy }}</label>
<div class="col-sm-10">
- <input type="password" data-pwgen-field="true" class="form-control" name="password2" placeholder="" autocomplete="new-password" required>
+ <div class="btn-group">
+ <input type="checkbox" class="btn-check" name="tls_enforce_in" id="tls_enforce_in" autocomplete="off" value="1">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="tls_enforce_in">{{ lang.user.tls_enforce_in }}</label>
+
+ <input type="checkbox" class="btn-check" name="tls_enforce_out" id="tls_enforce_out" autocomplete="off" value="1">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="tls_enforce_out">{{ lang.user.tls_enforce_out }}</label>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
+ <div class="col-sm-10">
+ <select name="protocol_access" id="protocol_access" multiple class="form-control">
+ <option value="imap">IMAP</option>
+ <option value="pop3">POP3</option>
+ <option value="smtp">SMTP</option>
+ <option value="sieve">Sieve</option>
+ </select>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">ACL</label>
+ <div class="col-sm-10">
+ <select id="user_acl" name="acl" multiple class="form-control">
+ <option value="spam_alias" selected>{{ lang.acl["spam_alias"] }}</option>
+ <option value="tls_policy" selected>{{ lang.acl["tls_policy"] }}</option>
+ <option value="spam_score" selected>{{ lang.acl["spam_score"] }}</option>
+ <option value="spam_policy" selected>{{ lang.acl["spam_policy"] }}</option>
+ <option value="delimiter_action" selected>{{ lang.acl["delimiter_action"] }}</option>
+ <option value="syncjobs">{{ lang.acl["syncjobs"] }}</option>
+ <option value="eas_reset" selected>{{ lang.acl["eas_reset"] }}</option>
+ <option value="sogo_profile_reset">{{ lang.acl["sogo_profile_reset"] }}</option>
+ <option value="pushover" selected>{{ lang.acl["pushover"] }}</option>
+ <option value="quarantine" selected>{{ lang.acl["quarantine"] }}</option>
+ <option value="quarantine_attachments" selected>{{ lang.acl["quarantine_attachments"] }}</option>
+ <option value="quarantine_notification" selected>{{ lang.acl["quarantine_notification"] }}</option>
+ <option value="quarantine_category" selected>{{ lang.acl["quarantine_category"] }}</option>
+ <option value="app_passwds" selected>{{ lang.acl["app_passwds"] }}</option>
+ </select>
</div>
</div>
<div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.acl.ratelimit }}</label>
+ <div class="col-sm-10">
+ <input name="rl_value" id="rl_value" type="number" autocomplete="off" value="" class="form-control mb-2" placeholder="{{ lang.ratelimit.disabled }}">
+ <select name="rl_frame" id="rl_frame" class="form-control">
+ <option value="s">{{ lang.ratelimit.second }}</option>
+ <option value="m">{{ lang.ratelimit.minute }}</option>
+ <option value="h">{{ lang.ratelimit.hour }}</option>
+ <option value="d">{{ lang.ratelimit.day }}</option>
+ </select>
+ <p class="text-muted mt-3">{{ lang.edit.mbox_rl_info }}</p>
+ </div>
+ </div>
+ <div class="row mb-2">
<div class="offset-sm-2 col-sm-10">
- <select name="active" class="form-control">
+ <select name="active" id="mbox_active" class="form-control">
<option value="1" selected>{{ lang.add.active }}</option>
<option value="2">{{ lang.add.disable_login }}</option>
<option value="0">{{ lang.add.inactive }}</option>
</select>
</div>
</div>
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="force_pw_update" id="force_pw_update"> {{ lang.edit.force_pw_update }}</label>
+ <small class="text-muted">{{ lang.edit.force_pw_update_info|format(ui_texts.main_name) }}</small>
+ </div>
+ </div>
+ </div>
+ {% if not skip_sogo %}
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="sogo_access" id="sogo_access"> {{ lang.edit.sogo_access }}</label>
+ <small class="text-muted">{{ lang.edit.sogo_access_info }}</small>
+ </div>
+ </div>
+ </div>
+ {% endif %}
<hr>
<div class="row">
<div class="offset-sm-2 col-sm-10">
@@ -82,6 +203,176 @@
</div>
</div>
</div><!-- add mailbox modal -->
+<!-- add mailbox template modal -->
+<div class="modal fade" id="addMailboxTemplateModal" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog modal-xl">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h3 class="modal-title">{{ lang.mailbox.add_template }}</h3>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+ <div class="modal-body">
+ <form class="form-horizontal" data-id="addmailbox_template" role="form" method="post">
+ <input type="hidden" value="default" name="sender_acl">
+ <input type="hidden" value="0" name="force_pw_update">
+ <input type="hidden" value="0" name="sogo_access">
+ <input type="hidden" value="0" name="protocol_access">
+
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="template">{{ lang.mailbox.template }}</label>
+ <div class="col-sm-10">
+ <div class="input-group mb-3">
+ <input type="text" name="template" class="form-control" aria-label="Text input with dropdown button" value="" />
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.add.tags }}</label>
+ <div class="col-sm-10">
+ <div class="form-control tag-box">
+ <input type="text" class="tag-input" id="addMailbox_tags">
+ <span class="btn tag-add"><i class="bi bi-plus-lg"></i></span>
+ <input type="hidden" value="" name="tags" class="tag-values" />
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="quota">{{ lang.edit.quota_mb }}</label>
+ <div class="col-sm-10">
+ <input type="number" name="quota" style="width:100%" min="0" value="" class="form-control">
+ <small class="text-muted">0 = ∞</small>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.quarantine_notification }}</label>
+ <div class="col-sm-10">
+ <div class="btn-group">
+ <input type="radio" class="btn-check" name="quarantine_notification" id="template_quarantine_notification_never" autocomplete="off" value="never">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_quarantine_notification_never">{{ lang.user.never }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="template_quarantine_notification_hourly" autocomplete="off" value="hourly">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_quarantine_notification_hourly">{{ lang.user.hourly }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="template_quarantine_notification_daily" autocomplete="off" value="daily">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_quarantine_notification_daily">{{ lang.user.daily }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_notification" id="template_quarantine_notification_weekly" autocomplete="off" value="weekly">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_quarantine_notification_weekly">{{ lang.user.weekly }}</label>
+ </div>
+ <p class="text-muted"><small>{{ lang.user.quarantine_notification_info }}</small></p>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.quarantine_category }}</label>
+ <div class="col-sm-10">
+ <div class="btn-group">
+ <input type="radio" class="btn-check" name="quarantine_category" id="template_quarantine_category_reject" autocomplete="off" value="reject" >
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_quarantine_category_reject">{{ lang.user.q_reject }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_category" id="template_quarantine_category_add_header" autocomplete="off" value="add_header">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_quarantine_category_add_header">{{ lang.user.q_add_header }}</label>
+
+ <input type="radio" class="btn-check" name="quarantine_category" id="template_quarantine_category_all" autocomplete="off" value="all">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_quarantine_category_all">{{ lang.user.q_all }}</label>
+ </div>
+ <p class="text-muted"><small>{{ lang.user.quarantine_category_info }}</small></p>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="sender_acl">{{ lang.user.tls_policy }}</label>
+ <div class="col-sm-10">
+ <div class="btn-group">
+ <input type="checkbox" class="btn-check" name="tls_enforce_in" id="template_tls_enforce_in" autocomplete="off" value="1">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_tls_enforce_in">{{ lang.user.tls_enforce_in }}</label>
+
+ <input type="checkbox" class="btn-check" name="tls_enforce_out" id="template_tls_enforce_out" autocomplete="off" value="1">
+ <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="template_tls_enforce_out">{{ lang.user.tls_enforce_out }}</label>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="protocol_access">{{ lang.edit.allowed_protocols }}</label>
+ <div class="col-sm-10">
+ <select name="protocol_access" multiple class="form-control">
+ <option value="imap" selected>IMAP</option>
+ <option value="pop3" selected>POP3</option>
+ <option value="smtp" selected>SMTP</option>
+ <option value="sieve" selected>Sieve</option>
+ </select>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">ACL</label>
+ <div class="col-sm-10">
+ <select id="template_user_acl" name="acl" size="10" multiple class="form-control">
+ <option value="spam_alias" selected>{{ lang.acl["spam_alias"] }}</option>
+ <option value="tls_policy" selected>{{ lang.acl["tls_policy"] }}</option>
+ <option value="spam_score" selected>{{ lang.acl["spam_score"] }}</option>
+ <option value="spam_policy" selected>{{ lang.acl["spam_policy"] }}</option>
+ <option value="delimiter_action" selected>{{ lang.acl["delimiter_action"] }}</option>
+ <option value="syncjobs">{{ lang.acl["syncjobs"] }}</option>
+ <option value="eas_reset" selected>{{ lang.acl["eas_reset"] }}</option>
+ <option value="sogo_profile_reset">{{ lang.acl["sogo_profile_reset"] }}</option>
+ <option value="pushover" selected>{{ lang.acl["pushover"] }}</option>
+ <option value="quarantine" selected>{{ lang.acl["quarantine"] }}</option>
+ <option value="quarantine_attachments" selected>{{ lang.acl["quarantine_attachments"] }}</option>
+ <option value="quarantine_notification" selected>{{ lang.acl["quarantine_notification"] }}</option>
+ <option value="quarantine_category" selected>{{ lang.acl["quarantine_category"] }}</option>
+ <option value="app_passwds" selected>{{ lang.acl["app_passwds"] }}</option>
+ </select>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.acl.ratelimit }}</label>
+ <div class="col-sm-10">
+ <input name="rl_value" type="number" autocomplete="off" value="" class="form-control mb-2" placeholder="{{ lang.ratelimit.disabled }}">
+ <select name="rl_frame" class="form-control">
+ <option value="s">{{ lang.ratelimit.second }}</option>
+ <option value="m">{{ lang.ratelimit.minute }}</option>
+ <option value="h">{{ lang.ratelimit.hour }}</option>
+ <option value="d">{{ lang.ratelimit.day }}</option>
+ </select>
+ <p class="text-muted mt-3">{{ lang.edit.mbox_rl_info }}</p>
+ </div>
+ </div>
+ <hr>
+ <div class="row my-2">
+ <div class="offset-sm-2 col-sm-10">
+ <select id="mbox_acitve" name="active" class="form-control">
+ <option value="1" selected>{{ lang.edit.active }}</option>
+ <option value="2">{{ lang.edit.disable_login }}</option>
+ <option value="0">{{ lang.edit.inactive }}</option>
+ </select>
+ </div>
+ </div>
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="force_pw_update"> {{ lang.edit.force_pw_update }}</label>
+ <small class="text-muted">{{ lang.edit.force_pw_update_info|format(ui_texts.main_name) }}</small>
+ </div>
+ </div>
+ </div>
+ {% if not skip_sogo %}
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="sogo_access"> {{ lang.edit.sogo_access }}</label>
+ <small class="text-muted">{{ lang.edit.sogo_access_info }}</small>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ <div class="row my-2">
+ <div class="offset-sm-2 col-sm-10">
+ <button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="add_item" data-id="addmailbox_template" data-api-url='add/mailbox/template' data-api-attr='{}' href="#">{{ lang.admin.add }}</button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div><!-- add mailbox template modal -->
<!-- add domain modal -->
<div class="modal fade" id="addDomainModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl">
@@ -104,11 +395,18 @@
<input type="text" class="form-control" name="description">
</div>
</div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="description">{{ lang.mailbox.template }}</label>
+ <div class="col-sm-10">
+ <select data-live-search="true" id="domain_templates" class="form-control">
+ </select>
+ </div>
+ </div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.add.tags }}</label>
<div class="col-sm-10">
<div class="form-control tag-box">
- <input type="text" class="tag-input">
+ <input type="text" class="tag-input" id="addDomain_tags">
<span class="btn tag-add"><i class="bi bi-plus-lg"></i></span>
<input type="hidden" value="" name="tags" class="tag-values" />
</div>
@@ -117,38 +415,38 @@
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="aliases">{{ lang.add.max_aliases }}</label>
<div class="col-sm-10">
- <input type="number" class="form-control" name="aliases" value="400" required>
+ <input type="number" id="addDomain_max_aliases" class="form-control" name="aliases" value="400" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="mailboxes">{{ lang.add.max_mailboxes }}</label>
<div class="col-sm-10">
- <input type="number" class="form-control" name="mailboxes" value="10" required>
+ <input type="number" id="addDomain_max_mailboxes" class="form-control" name="mailboxes" value="10" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="defquota">{{ lang.add.mailbox_quota_def }}</label>
<div class="col-sm-10">
- <input type="number" class="form-control" name="defquota" value="3072" required>
+ <input type="number" id="addDomain_mailbox_quota_def" class="form-control" name="defquota" value="3072" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="maxquota">{{ lang.add.mailbox_quota_m }}</label>
<div class="col-sm-10">
- <input type="number" class="form-control" name="maxquota" value="10240" required>
+ <input type="number" id="addDomain_mailbox_quota_m" class="form-control" name="maxquota" value="10240" required>
</div>
</div>
<div class="row mb-4">
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="quota">{{ lang.add.domain_quota_m }}</label>
<div class="col-sm-10">
- <input type="number" class="form-control" name="quota" value="10240" required>
+ <input type="number" id="addDomain_domain_quota_m" class="form-control" name="quota" value="10240" required>
</div>
</div>
{% if not skip_sogo %}
<div class="row mb-2">
<div class="offset-sm-2 col-sm-10">
<div class="checkbox">
- <label><input type="checkbox" value="1" name="gal" checked> {{ lang.edit.gal }}</label>
+ <label><input type="checkbox" id="addDomain_gal" value="1" name="gal" checked> {{ lang.edit.gal }}</label>
<small class="text-muted">{{ lang.edit.gal_info|raw }}</small>
</div>
</div>
@@ -157,7 +455,7 @@
<div class="row mb-4">
<div class="offset-sm-2 col-sm-10">
<div class="checkbox">
- <label><input type="checkbox" value="1" name="active" checked> {{ lang.add.active }}</label>
+ <label><input type="checkbox" id="addDomain_active" value="1" name="active" checked> {{ lang.add.active }}</label>
</div>
</div>
</div>
@@ -165,10 +463,10 @@
<div class="row mb-4">
<label class="control-label col-sm-2 text-sm-end" for="rl_frame">{{ lang.acl.ratelimit }}</label>
<div class="col-sm-7">
- <input name="rl_value" type="number" class="form-control" placeholder="{{ lang.ratelimit.disabled }}">
+ <input name="rl_value" id="addDomain_rl_value" type="number" class="form-control" placeholder="{{ lang.ratelimit.disabled }}">
</div>
<div class="col-sm-3">
- <select name="rl_frame" class="form-control">
+ <select name="rl_frame" id="addDomain_rl_frame" class="form-control">
{% include 'mailbox/rl-frame.twig' %}
</select>
</div>
@@ -184,8 +482,8 @@
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="key_size">{{ lang.admin.dkim_key_length }}</label>
<div class="col-sm-10">
<select data-style="btn btn-secondary btn-sm" class="form-control" id="key_size" name="key_size">
- <option data-subtext="bits">1024</option>
- <option data-subtext="bits" selected>2048</option>
+ <option data-subtext="bits" value="1024">1024</option>
+ <option data-subtext="bits" value="2048" selected>2048</option>
</select>
</div>
</div>
@@ -194,11 +492,11 @@
<label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.add.backup_mx_options }}</label>
<div class="col-sm-10">
<div class="checkbox">
- <label><input type="checkbox" value="1" name="backupmx"> {{ lang.add.relay_domain }}</label>
+ <label><input type="checkbox" id="addDomain_relay_domain" value="1" name="backupmx"> {{ lang.add.relay_domain }}</label>
<br>
- <label><input type="checkbox" value="1" name="relay_all_recipients"> {{ lang.add.relay_all }}</label>
+ <label><input type="checkbox" id="addDomain_relay_all" value="1" name="relay_all_recipients"> {{ lang.add.relay_all }}</label>
<p>{{ lang.add.relay_all_info|raw }}</p>
- <label><input type="checkbox" value="1" name="relay_unknown_only"> {{ lang.add.relay_unknown_only }}</label>
+ <label><input type="checkbox" id="addDomain_relay_unknown_only" value="1" name="relay_unknown_only"> {{ lang.add.relay_unknown_only }}</label>
<br>
<p>{{ lang.add.relay_transport_info|raw }}</p>
</div>
@@ -225,6 +523,138 @@
</div>
</div>
</div><!-- add domain modal -->
+<!-- add domain template modal -->
+<div class="modal fade" id="addDomainTemplateModal" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog modal-xl">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h3 class="modal-title">{{ lang.mailbox.add_template }}</h3>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+ <div class="modal-body">
+ <form data-id="adddomain_template" class="form-horizontal" role="form" method="post">
+ {% if mailcow_cc_role == 'admin' %}
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="template">{{ lang.mailbox.template }}</label>
+ <div class="col-sm-10">
+ <div class="input-group mb-3">
+ <input type="text" name="template" class="form-control" aria-label="Text input with dropdown button" value="" />
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.add.tags }}</label>
+ <div class="col-sm-10">
+ <div class="form-control tag-box">
+ <input type="text" class="tag-input">
+ <span class="btn tag-add"><i class="bi bi-plus-lg"></i></span>
+ <input type="hidden" value="" name="tags" class="tag-values" />
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="max_num_aliases_for_domain">{{ lang.add.max_aliases }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_num_aliases_for_domain" value="">
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="max_num_mboxes_for_domain">{{ lang.add.max_mailboxes }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_num_mboxes_for_domain" value="">
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="def_quota_for_mbox">{{ lang.add.mailbox_quota_def }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="def_quota_for_mbox" value="">
+ </div>
+ </div>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="max_quota_for_mbox">{{ lang.add.mailbox_quota_m }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_quota_for_mbox" value="">
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="max_quota_for_domain">{{ lang.add.domain_quota_m }}</label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="max_quota_for_domain" value="">
+ </div>
+ </div>
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="gal" checked> {{ lang.add.gal }}</label>
+ <small class="text-muted">{{ lang.add.gal_info|raw }}</small>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="active" checked> {{ lang.add.active }}</label>
+ </div>
+ </div>
+ </div>
+ <hr>
+ <div class="row">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.edit.ratelimit }}</label>
+ <div class="col-sm-7">
+ <input name="rl_value" type="number" value="" autocomplete="off" class="form-control mb-4" placeholder="{{ lang.ratelimit.disabled }}">
+ </div>
+ <div class="col-sm-3">
+ <select name="rl_frame" class="form-control">
+ <option value="s">{{ lang.ratelimit.second }}</option>
+ <option value="m">{{ lang.ratelimit.minute }}</option>
+ <option value="h">{{ lang.ratelimit.hour }}</option>
+ <option value="d">{{ lang.ratelimit.day }}</option>
+ </select>
+ </div>
+ </div>
+ {% endif %}
+ <hr>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="dkim_selector">{{ lang.admin.dkim_domains_selector }}</label>
+ <div class="col-sm-10">
+ <input class="form-control" id="dkim_selector" name="dkim_selector" value="dkim">
+ </div>
+ </div>
+ <div class="row mb-4">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end" for="key_size">{{ lang.admin.dkim_key_length }}</label>
+ <div class="col-sm-10">
+ <select data-style="btn btn-secondary btn-sm" class="form-control" id="key_size" name="key_size">
+ <option data-subtext="bits">1024</option>
+ <option data-subtext="bits" selected>2048</option>
+ </select>
+ </div>
+ </div>
+ <hr>
+ <div class="row mb-2">
+ <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.edit.backup_mx_options }}</label>
+ <div class="col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="backupmx"> {{ lang.edit.relay_domain }}</label>
+ <br>
+ <label><input type="checkbox" value="1" name="relay_all_recipients"> {{ lang.edit.relay_all }}</label>
+ <p>{{ lang.edit.relay_all_info|raw }}</p>
+ <label><input type="checkbox" value="1" name="relay_unknown_only"> {{ lang.edit.relay_unknown_only }}</label>
+ <br>
+ <p>{{ lang.edit.relay_transport_info|raw }}</p>
+ </div>
+ </div>
+ </div>
+ <hr>
+ <div class="row">
+ <div class="offset-sm-2 col-sm-10">
+ <button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="add_item" data-id="adddomain_template" data-item="{{ domain }}" data-api-url='add/domain/template' data-api-attr='{}' href="#">{{ lang.admin.add }}</button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div><!-- add domain template modal -->
<!-- add resource modal -->
<div class="modal fade" id="addResourceModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl">