1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2024-12-12 10:45:14 +02:00

Merge pull request #4966 from mailcow/fix/bs5

BS5 UI fixes
This commit is contained in:
Niklas Meyer 2023-01-09 12:40:14 +01:00 committed by GitHub
commit 27c87de4ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 523 additions and 98 deletions

View File

@ -103,6 +103,7 @@ $template_data = [
'rsettings' => $rsettings,
'rspamd_regex_maps' => $rspamd_regex_maps,
'logo_specs' => customize('get', 'main_logo_specs'),
'ip_check' => customize('get', 'ip_check'),
'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'lang_admin' => json_encode($lang['admin']),

View File

@ -370,3 +370,14 @@ button[aria-expanded='true'] > .caret {
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: #f0f0f0 !important;
}
div.dataTables_wrapper div.dataTables_filter {
text-align: left;
}
div.dataTables_wrapper div.dataTables_length {
text-align: right;
}
.dataTables_paginate, .dataTables_length, .dataTables_filter {
margin: 10px 0!important;
}

View File

@ -199,6 +199,10 @@
display: none !important;
}
div.dataTables_wrapper div.dataTables_length {
text-align: left;
}
}
@media (max-width: 350px) {

View File

@ -11,7 +11,86 @@
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,700;1,400&display=swap");
/* source-sans-pro-300 - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: url('/fonts/source-sans-pro-v21-latin-300.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-300.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-300.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-300italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: url('/fonts/source-sans-pro-v21-latin-300italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-300italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-300italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-300italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-regular - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: url('/fonts/source-sans-pro-v21-latin-regular.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-regular.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-regular.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: url('/fonts/source-sans-pro-v21-latin-italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-700 - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: url('/fonts/source-sans-pro-v21-latin-700.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-700.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-700italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 700;
src: url('/fonts/source-sans-pro-v21-latin-700italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-700italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-700italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
:root {
--bs-blue: #158cba;
--bs-indigo: #6610f2;

View File

@ -358,3 +358,11 @@ table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
background: #333;
}
span.mail-address-item {
background-color: #333;
border-radius: 4px;
border: 1px solid #555;
padding: 2px 7px;
display: inline-block;
margin: 2px 6px 2px 0;
}

View File

@ -65,6 +65,7 @@ $template_data = [
'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60),
'clamd_status' => $clamd_status,
'containers' => $containers,
'ip_check' => customize('get', 'ip_check'),
'lang_admin' => json_encode($lang['admin']),
'lang_debug' => json_encode($lang['debug']),
'lang_datatables' => json_encode($lang['datatables']),

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -160,6 +160,25 @@ function customize($_action, $_item, $_data = null) {
'msg' => 'ui_texts'
);
break;
case 'ip_check':
$ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0;
try {
$redis->set('IP_CHECK', $ip_check);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'ip_check_opt_in_modified'
);
break;
}
break;
case 'delete':
@ -276,6 +295,20 @@ function customize($_action, $_item, $_data = null) {
return false;
}
break;
case 'ip_check':
try {
$ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0;
return $ip_check;
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
break;
}
break;
}

View File

@ -2879,67 +2879,68 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
'msg' => 'extended_sender_acl_denied'
);
return false;
}
$extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl']));
foreach ($extra_acls as $i => &$extra_acl) {
if (empty($extra_acl)) {
continue;
}
if (substr($extra_acl, 0, 1) === "@") {
$extra_acl = ltrim($extra_acl, '@');
}
if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl))
);
unset($extra_acls[$i]);
continue;
}
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) {
$extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
if (in_array($extra_acl_domain, $domains)) {
else {
$extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl']));
foreach ($extra_acls as $i => &$extra_acl) {
if (empty($extra_acl)) {
continue;
}
if (substr($extra_acl, 0, 1) === "@") {
$extra_acl = ltrim($extra_acl, '@');
}
if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl))
);
unset($extra_acls[$i]);
continue;
}
}
else {
if (in_array($extra_acl, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
);
unset($extra_acls[$i]);
continue;
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) {
$extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
if (in_array($extra_acl_domain, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
);
unset($extra_acls[$i]);
continue;
}
}
else {
if (in_array($extra_acl, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
);
unset($extra_acls[$i]);
continue;
}
$extra_acl = '@' . $extra_acl;
}
$extra_acl = '@' . $extra_acl;
}
}
$extra_acls = array_filter($extra_acls);
$extra_acls = array_values($extra_acls);
$extra_acls = array_unique($extra_acls);
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
foreach ($extra_acls as $sender_acl_external) {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`)
VALUES (:sender_acl, :username, 1)");
$extra_acls = array_filter($extra_acls);
$extra_acls = array_values($extra_acls);
$extra_acls = array_unique($extra_acls);
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username");
$stmt->execute(array(
':sender_acl' => $sender_acl_external,
':username' => $username
));
foreach ($extra_acls as $sender_acl_external) {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`)
VALUES (:sender_acl, :username, 1)");
$stmt->execute(array(
':sender_acl' => $sender_acl_external,
':username' => $username
));
}
}
}
if (isset($_data['sender_acl'])) {

View File

@ -12,14 +12,22 @@ $(document).ready(function() {
$.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
}
$(".generate_password").click(function( event ) {
$(".generate_password").click(async function( event ) {
try {
var password_policy = await window.fetch("/api/v1/get/passwordpolicy", { method:'GET', cache:'no-cache' });
var password_policy = await password_policy.json();
random_passwd_length = password_policy.length;
} catch(err) {
var random_passwd_length = 8;
}
event.preventDefault();
$('[data-hibp]').trigger('input');
if (typeof($(this).closest("form").data('pwgen-length')) == "number") {
var random_passwd = GPW.pronounceable($(this).closest("form").data('pwgen-length'))
}
else {
var random_passwd = GPW.pronounceable(8)
var random_passwd = GPW.pronounceable(random_passwd_length)
}
$(this).closest("form").find('[data-pwgen-field]').attr('type', 'text');
$(this).closest("form").find('[data-pwgen-field]').val(random_passwd);

View File

@ -70,8 +70,13 @@ jQuery(function($){
}
$('#domainadminstable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -143,8 +148,13 @@ jQuery(function($){
}
$('#oauth2clientstable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -206,8 +216,13 @@ jQuery(function($){
}
$('#adminstable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -272,8 +287,13 @@ jQuery(function($){
}
$('#forwardinghoststable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -330,8 +350,13 @@ jQuery(function($){
}
$('#relayhoststable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -402,8 +427,13 @@ jQuery(function($){
}
$('#transportstable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",

View File

@ -51,7 +51,40 @@ $(document).ready(function() {
showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
})
// get public ips
get_public_ips();
$("#host_show_ip").click(function(){
$("#host_show_ip").find(".text").addClass("d-none");
$("#host_show_ip").find(".spinner-border").removeClass("d-none");
window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
// display host ips
if (data.ipv4)
$("#host_ipv4").text(data.ipv4);
if (data.ipv6)
$("#host_ipv6").text(data.ipv6);
$("#host_show_ip").addClass("d-none");
$("#host_show_ip").find(".text").removeClass("d-none");
$("#host_show_ip").find(".spinner-border").addClass("d-none");
$("#host_ipv4").removeClass("d-none");
$("#host_ipv6").removeClass("d-none");
$("#host_ipv6").removeClass("text-danger");
$("#host_ipv4").addClass("d-block");
$("#host_ipv6").addClass("d-block");
}).catch(function(error){
console.log(error);
$("#host_ipv6").removeClass("d-none");
$("#host_ipv6").addClass("d-block");
$("#host_ipv6").addClass("text-danger");
$("#host_ipv6").text(lang_debug.error_show_ip);
$("#host_show_ip").find(".text").removeClass("d-none");
$("#host_show_ip").find(".spinner-border").addClass("d-none");
});
});
update_container_stats();
});
jQuery(function($){
@ -86,8 +119,13 @@ jQuery(function($){
}
$('#autodiscover_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -143,8 +181,13 @@ jQuery(function($){
}
$('#postfix_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -185,8 +228,13 @@ jQuery(function($){
}
$('#watchdog_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -231,8 +279,13 @@ jQuery(function($){
}
$('#api_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -284,8 +337,13 @@ jQuery(function($){
}
$('#rl_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -375,8 +433,13 @@ jQuery(function($){
}
$('#ui_logs').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -446,8 +509,13 @@ jQuery(function($){
}
$('#sasl_logs').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -494,8 +562,13 @@ jQuery(function($){
}
$('#acme_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -531,8 +604,13 @@ jQuery(function($){
}
$('#netfilter_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -573,8 +651,13 @@ jQuery(function($){
}
$('#sogo_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -615,8 +698,13 @@ jQuery(function($){
}
$('#dovecot_log').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -718,8 +806,13 @@ jQuery(function($){
}
$('#rspamd_history').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order: [[0, 'desc']],
ajax: {
@ -1224,20 +1317,6 @@ function update_container_stats(timeout=5){
// run again in n seconds
setTimeout(update_container_stats, timeout * 1000);
}
// get public ips
function get_public_ips(){
window.fetch("/api/v1/get/status/host/ip", {method:'GET',cache:'no-cache'}).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
// display host ips
if (data.ipv4)
$("#host_ipv4").text(data.ipv4);
if (data.ipv6)
$("#host_ipv6").text(data.ipv6);
});
}
// format hosts uptime seconds to readable string
function formatUptime(seconds){
seconds = Number(seconds);

View File

@ -78,8 +78,13 @@ jQuery(function($){
}
function draw_wl_policy_domain_table() {
$('#wl_policy_domain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -133,8 +138,13 @@ jQuery(function($){
}
function draw_bl_policy_domain_table() {
$('#bl_policy_domain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",

View File

@ -433,8 +433,13 @@ jQuery(function($){
}
var table = $('#domain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -619,9 +624,13 @@ jQuery(function($){
}
$('#templates_domain_table').DataTable({
responsive : true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {
@ -817,16 +826,26 @@ jQuery(function($){
}
$('#mailbox_table').DataTable({
responsive : true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
url: "/api/v1/get/mailbox/reduced",
dataSrc: function(json){
$.each(json, function (i, item) {
item.quota = item.quota_used + "/" + item.quota;
item.quota = {
sortBy: item.quota_used,
value: item.quota
}
item.quota.value = (item.quota.value == 0 ? "∞" : humanFileSize(item.quota.value));
item.quota.value = humanFileSize(item.quota_used) + "/" + item.quota.value;
item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox);
item.last_mail_login = item.last_imap_login + '/' + item.last_pop3_login + '/' + item.last_smtp_login;
/*
@ -931,14 +950,10 @@ jQuery(function($){
},
{
title: lang.domain_quota,
data: 'quota',
data: 'quota.value',
responsivePriority: 8,
defaultContent: '',
render: function (data, type) {
data = data.split("/");
var of_q = (data[1] == 0 ? "∞" : humanFileSize(data[1]));
return humanFileSize(data[0]) + " / " + of_q;
}
defaultContent: '',
orderData: 23
},
{
title: lang.last_mail_login,
@ -1064,6 +1079,13 @@ jQuery(function($){
responsivePriority: 6,
defaultContent: ''
},
{
title: "",
data: 'quota.sortBy',
responsivePriority: 8,
defaultContent: '',
className: "d-none"
},
]
});
}
@ -1075,9 +1097,13 @@ jQuery(function($){
}
$('#templates_mbox_table').DataTable({
responsive : true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {
@ -1287,8 +1313,13 @@ jQuery(function($){
}
$('#resource_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -1413,8 +1444,13 @@ jQuery(function($){
}
$('#bcc_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {
@ -1510,8 +1546,13 @@ jQuery(function($){
}
$('#recipient_map_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {
@ -1594,8 +1635,13 @@ jQuery(function($){
}
$('#tls_policy_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {
@ -1688,8 +1734,13 @@ jQuery(function($){
}
$('#alias_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {
@ -1829,8 +1880,13 @@ jQuery(function($){
}
$('#aliasdomain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -1911,8 +1967,13 @@ jQuery(function($){
}
$('#sync_job_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {
@ -2051,9 +2112,14 @@ jQuery(function($){
}
var table = $('#filter_table').DataTable({
responsive: true,
autoWidth: false,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
order:[[2, 'desc']],
ajax: {

View File

@ -14,8 +14,13 @@ jQuery(function($){
});
function draw_quarantine_table() {
$('#quarantinetable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -129,9 +134,15 @@ jQuery(function($){
title: lang.received,
data: 'created',
defaultContent: '',
render: function (data,type) {
var date = new Date(data ? data * 1000 : 0);
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
createdCell: function(td, cellData) {
$(td).attr({
"data-order": cellData,
"data-sort": cellData
});
var date = new Date(cellData ? cellData * 1000 : 0);
var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
$(td).html(dateString);
}
},
{

View File

@ -35,8 +35,13 @@ jQuery(function($){
}
$('#queuetable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",

View File

@ -135,8 +135,13 @@ jQuery(function($){
}
$('#tla_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -216,8 +221,13 @@ jQuery(function($){
}
$('#sync_job_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -366,8 +376,13 @@ jQuery(function($){
}
$('#app_passwd_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -456,8 +471,13 @@ jQuery(function($){
}
$('#wl_policy_mailbox_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
@ -521,8 +541,13 @@ jQuery(function($){
}
$('#bl_policy_mailbox_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",

View File

@ -561,6 +561,15 @@ if (isset($_GET['query'])) {
echo '{}';
}
break;
default:
$password_complexity_rules = password_complexity('get');
if ($password_complexity_rules !== false) {
process_get_return($password_complexity_rules);
}
else {
echo '{}';
}
break;
}
break;
@ -1544,14 +1553,15 @@ if (isset($_GET['query'])) {
}
else if ($extra == "ip") {
// get public ips
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 15);
curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
$ipv4 = curl_exec($curl);
curl_setopt($curl, CURLOPT_URL, 'http://ipv6.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
$ipv6 = curl_exec($curl);
$ips = array(
"ipv4" => $ipv4,
@ -1913,6 +1923,9 @@ if (isset($_GET['query'])) {
case "ui_texts":
process_edit_return(customize('edit', 'ui_texts', $attr));
break;
case "ip_check":
process_edit_return(customize('edit', 'ip_check', $attr));
break;
case "self":
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
process_edit_return(domain_admin('edit', $attr));

View File

@ -204,6 +204,9 @@
"include_exclude": "Ein- und Ausschlüsse",
"include_exclude_info": "Ohne Auswahl werden <b>alle Mailboxen</b> adressiert.",
"includes": "Diese Empfänger einschließen",
"ip_check": "IP Check",
"ip_check_disabled": "IP check ist deaktiviert. Unter dem angegebenen Pfad kann es aktiviert werden<br> <strong>System > Konfiguration > Einstellungen > UI-Anpassung</strong>",
"ip_check_opt_in": "Opt-In für die Nutzung der Drittanbieter-Dienste <strong>ipv4.mailcow.email</strong> und <strong>ipv6.mailcow.email</strong> zur Auflösung externer IP-Adressen.",
"is_mx_based": "MX-basiert",
"last_applied": "Zuletzt angewendet",
"license_info": "Eine Lizenz ist nicht erforderlich, hilft jedoch der Entwicklung mailcows.<br><a href=\"https://www.servercow.de/mailcow#sal\" target=\"_blank\" alt=\"SAL Bestellung\">Hier kann die mailcow-GUID registriert werden.</a> Alternativ ist <a href=\"https://www.servercow.de/mailcow#support\" target=\"_blank\" alt=\"SAL Bestellung\">die Bestellung von Support-Paketen möglich</a>.",
@ -363,6 +366,7 @@
"domain_not_empty": "Domain %s ist nicht leer",
"domain_not_found": "Domain %s nicht gefunden",
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
"extended_sender_acl_denied": "Keine Rechte zum setzen von externen Absenderadressen",
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
@ -494,6 +498,7 @@
"current_time": "Systemzeit",
"disk_usage": "Festplattennutzung",
"docs": "Dokumente",
"error_show_ip": "konnte die öffentlichen IP Adressen nicht auflösen",
"external_logs": "Externe Logs",
"history_all_servers": "History (alle Server)",
"in_memory_logs": "In-memory Logs",
@ -506,6 +511,7 @@
"online_users": "Benutzer online",
"restart_container": "Neustart",
"service": "Dienst",
"show_ip": "Zeige öffentliche IP",
"size": "Größe",
"solr_dead": "Solr startet, ist deaktiviert oder temporär nicht erreichbar.",
"solr_status": "Solr Status",
@ -1001,6 +1007,7 @@
"forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",
"global_filter_written": "Filterdatei wurde erfolgreich geschrieben",
"hash_deleted": "Hash wurde gelöscht",
"ip_check_opt_in_modified": "IP Check wurde erfolgreich gespeichert",
"item_deleted": "Objekt %s wurde entfernt",
"item_released": "Objekt %s freigegeben",
"items_deleted": "Objekt(e) %s wurde(n) erfolgreich entfernt",

View File

@ -206,6 +206,9 @@
"include_exclude": "Include/Exclude",
"include_exclude_info": "By default - with no selection - <b>all mailboxes</b> are addressed",
"includes": "Include these recipients",
"ip_check": "IP Check",
"ip_check_disabled": "IP check is disabled. You can enable it under<br> <strong>System > Configuration > Options > Customize</strong>",
"ip_check_opt_in": "Opt-In for using third party service <strong>ipv4.mailcow.email</strong> and <strong>ipv6.mailcow.email</strong> to resolve external IP addresses.",
"is_mx_based": "MX based",
"last_applied": "Last applied",
"license_info": "A license is not required but helps further development.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Register your GUID here</a> or <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">buy support for your mailcow installation.</a>",
@ -363,6 +366,7 @@
"domain_not_empty": "Cannot remove non-empty domain %s",
"domain_not_found": "Domain %s not found",
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB",
"extended_sender_acl_denied": "missing ACL to set external sender addresses",
"extra_acl_invalid": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
"fido2_verification_failed": "FIDO2 verification failed: %s",
@ -497,6 +501,7 @@
"current_time": "System Time",
"disk_usage": "Disk usage",
"docs": "Docs",
"error_show_ip": "Could not resolve the public IP addresses",
"external_logs": "External logs",
"history_all_servers": "History (all servers)",
"in_memory_logs": "In-memory logs",
@ -509,6 +514,7 @@
"online_users": "Users online",
"restart_container": "Restart",
"service": "Service",
"show_ip": "Show public IP",
"size": "Size",
"solr_dead": "Solr is starting, disabled or died.",
"solr_status": "Solr status",
@ -1013,6 +1019,7 @@
"forwarding_host_removed": "Forwarding host %s has been removed",
"global_filter_written": "Filter was successfully written to file",
"hash_deleted": "Hash deleted",
"ip_check_opt_in_modified": "IP check was saved successfully",
"item_deleted": "Item %s successfully deleted",
"item_released": "Item %s released",
"items_deleted": "Item %s successfully deleted",

View File

@ -33,6 +33,20 @@
</div>
</div>
{% endif %}
<legend style="padding-top:20px" unselectable="on">{{ lang.admin.ip_check }}</legend><hr />
<div id="ip_check">
<form class="form" data-id="ip_check" role="form" method="post">
<div class="mb-4">
<input class="form-check-input" type="checkbox" value="1" name="ip_check_opt_in" id="ip_check_opt_in" {% if ip_check == 1 %}checked{% endif %}>
<label class="form-check-label" for="ip_check_opt_in">
{{ lang.admin.ip_check_opt_in|raw }}
</label>
</div>
<p><div class="btn-group">
<button class="btn btn-sm btn-xs-half d-block d-sm-inline btn-success" data-action="edit_selected" data-item="admin" data-id="ip_check" data-reload="no" data-api-url='edit/ip_check' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div></p>
</form>
</div>
<legend>{{ lang.admin.app_links }}</legend><hr />
<p class="text-muted">{{ lang.admin.merged_vars_hint|raw }}</p>
<form class="form-inline" data-id="app_links" role="form" method="post">

View File

@ -36,7 +36,7 @@
</div>
<div class="mb-4">
<label for="rlyhost_password">{{ lang.admin.password }}</label>
<input class="form-control" id="rlyhost_password" name="password">
<input class="form-control" id="rlyhost_password" name="password" type="password">
</div>
<button class="btn btn-sm d-block d-sm-inline btn-success" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><i class="bi bi-plus-lg"></i> {{ lang.admin.add }}</button>
</form>
@ -86,7 +86,7 @@
</div>
<div class="mb-4">
<label for="transport_password">{{ lang.admin.password }}</label>
<input class="form-control" id="transport_password" name="password">
<input class="form-control" id="transport_password" name="password" type="password">
</div>
<div class="mb-2">
<label>

View File

@ -52,8 +52,18 @@
<tr>
<td>IPs</td>
<td class="text-break">
<span class="d-block" id="host_ipv4">-</span>
<span class="d-block" id="host_ipv6">-</span>
{% if ip_check == 1 %}
<span class="d-none" id="host_ipv4">-</span>
<span class="d-none mb-2" id="host_ipv6">-</span>
<button class="d-block btn btn-primary btn-sm" id="host_show_ip">
<span class="text">{{ lang.debug.show_ip }}</span>
<div class="spinner-border spinner-border-sm d-none" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</button>
{% else %}
<span class="d-block">{{ lang.admin.ip_check_disabled|raw }}</span>
{% endif %}
</td>
</tr>
<tr>

View File

@ -200,8 +200,10 @@
{% if sender_acl_handles.external_sender_aliases %}
{% set ext_sender_acl = sender_acl_handles.external_sender_aliases|join(', ') %}
{% endif %}
<input type="text" class="form-control" name="extended_sender_acl" value="{{ ext_sender_acl }}" placeholder="user1@example.com, user2@example.org, @example.com, ...">
<small class="text-muted">{{ lang.edit.extended_sender_acl_info|raw }}</small>
{% if acl.extend_sender_acl and acl.extend_sender_acl == 1 %}
<input type="text" class="form-control" name="extended_sender_acl" value="{{ ext_sender_acl }}" placeholder="user1@example.com, user2@example.org, @example.com, ...">
<small class="text-muted">{{ lang.edit.extended_sender_acl_info|raw }}</small>
{% endif %}
</div>
</div>
<div class="row">

View File

@ -4,18 +4,18 @@
<div id="mail-content" class="responsive-tabs">
<ul class="nav nav-tabs" role="tablist">
<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-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>
<a class="nav-link dropdown-toggle active" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.domains }}</a>
<ul class="dropdown-menu">
<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 dropdown" role="presentation">
<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><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>
<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><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>
<li class="nav-item dropdown">