Compare commits

...

100 Commits

Author SHA1 Message Date
Niklas Meyer
74bcec45f1 Merge pull request #5250 from mailcow/staging
2023-05
2023-05-25 16:30:16 +02:00
Niklas Meyer
9700b3251f Merge pull request #5214 from mailcow/feat/gh_actions_postscreen
Add GitHub action update_postscreen_access_list.yml
2023-05-25 15:40:20 +02:00
Niklas Meyer
88b8d50cd5 Merge pull request #4028 from Daniel15/patch-2
Enable maildir_very_dirty_syncs by default
2023-05-24 11:00:38 +02:00
DerLinkman
55b0191050 [PHP] Update to 1.84 2023-05-23 10:46:21 +02:00
Peter
33c97fb318 change domain for docs 2023-05-10 20:32:38 +02:00
Niklas Meyer
23d33ad5a8 Merge pull request #5231 from mailcow/renovate/alpine-3.x
Update alpine Docker tag to v3.18
2023-05-10 08:58:47 +02:00
renovate[bot]
bd6c98047a Update alpine Docker tag to v3.18
Signed-off-by: milkmaker <milkmaker@mailcow.de>
2023-05-10 01:50:21 +00:00
Patrick Schult
73d6a29ae1 Merge pull request #5205 from mailcow/clean_sasl_log
Clean up old entries from sasl_log
2023-05-09 09:49:40 +02:00
Patrick Schult
173e39c859 Merge pull request #5200 from mailcow/fix/delete-sender-acl
[Web] Fix deleting sender_acl when mbox is deleted
2023-05-08 16:35:42 +02:00
Patrick Schult
c0745c5cde Merge pull request #5197 from mailcow/fix/bcc-validation
[Web] Fix BCC validation
2023-05-08 16:32:12 +02:00
Patrick Schult
1a6f93327e Merge pull request #5203 from mailcow/feat/bad_asn
Add IP Connect Inc to bad_asn.map
2023-05-08 16:01:44 +02:00
Patrick Schult
3c68a53170 Merge pull request #5201 from mailcow/fix/sieve-print
[Dockerapi] Fix typo in dockerapi sieve print
2023-05-08 16:00:22 +02:00
Patrick Schult
e38c27ed67 Merge pull request #5211 from goodygh/5175-fix-mobileconfig-redirect
[web] Fix typo in mobileconfig redirect
2023-05-08 15:55:50 +02:00
Patrick Schult
8eaf8bbbde Merge pull request #5220 from mailcow/fix/bcc-selectpicker
[Web] fix bcc localdest selectpicker
2023-05-08 15:53:53 +02:00
Patrick Schult
e015c7dbca Merge pull request #5202 from mailcow/feat/user-acl-tabs
[Web] hide user tabs if acl is missing
2023-05-08 15:48:52 +02:00
Patrick Schult
58452abcdf Merge pull request #5204 from mailcow/fix/rspamd-table
[Web] fix rspamd table on sm devices
2023-05-08 15:43:58 +02:00
Patrick Schult
2cbf0da137 Merge pull request #5198 from mailcow/fix/sorting-tla
[Web] Fix temporary email aliases sorting
2023-05-08 15:29:32 +02:00
FreddleSpl0it
aabcd10539 [Web] fix bcc localdest selectpicker 2023-05-03 09:59:49 +02:00
milkmaker
ee607dc3cc Translations update from Weblate (#5218)
* [Web] Updated lang.en-gb.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.cs-cz.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.de-de.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.fr-fr.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.ro-ro.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.sk-sk.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.zh-cn.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.it-it.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.zh-tw.json

Co-authored-by: Peter <magic@kthx.at>

---------

Co-authored-by: Peter <magic@kthx.at>
2023-05-02 18:29:38 +02:00
DerLinkman
1265302a8e [DockerAPI] Update to 2.04 2023-05-02 18:11:59 +02:00
DerLinkman
b5acf56e20 Added Platform Information on Status Page 2023-05-02 18:11:10 +02:00
FreddleSpl0it
fe4a418af4 [Web] fix rspamd table scan_time on sm devices 2023-04-27 10:45:11 +02:00
Peter
e5f03e8526 Update update_postscreen_whitelist.sh 2023-04-26 18:44:35 +02:00
Peter
fb60c4a150 Add update_postscreen_access_list.yml 2023-04-26 18:43:54 +02:00
goodygh
fd203abd47 Fix typo in mobileconfig redirect 2023-04-25 22:11:04 +02:00
milkmaker
6b65f0fc74 [Web] Updated lang.ru-ru.json (#5210)
Co-authored-by: Vakhtang <vakhtang.g.st@gmail.com>
2023-04-25 20:59:05 +02:00
Michael Kuron
856b3b62f2 Clean up old sasl_log entries 2023-04-22 14:16:42 +02:00
FreddleSpl0it
0372a2150d [Web] fix rspamd table on sm devices 2023-04-21 20:14:43 +02:00
Peter
f3322c0577 Add IP Connect Inc 2023-04-21 19:43:20 +02:00
FreddleSpl0it
c2bcc4e086 [Web] hide user tabs if acl is missing 2023-04-21 17:03:40 +02:00
FreddleSpl0it
6e79c48640 [Dockerapi] Fix typo in dockerapi sieve print 2023-04-21 16:15:16 +02:00
FreddleSpl0it
d7dfa95e1b [Web] Fix deleting sender_acl when mbox is deleted 2023-04-21 13:47:13 +02:00
FreddleSpl0it
cf1cc24e33 [Web] Fix temporary email aliases sorting 2023-04-21 12:26:50 +02:00
FreddleSpl0it
6824a5650f [Web] Fix BCC validation 2023-04-21 11:21:43 +02:00
Niklas Meyer
73570cc8b5 Merge pull request #5196 from ewong012/staging 2023-04-21 08:14:24 +02:00
Ethan Wong
959dcb9980 [Update.sh] Fix install docs link
Old link returns 404.
2023-04-20 13:52:46 -07:00
Patrick Schult
8f28666916 Merge pull request #5195 from mailcow/staging
2023-04b
2023-04-20 16:49:17 +02:00
Patrick Schult
3eaa5a626c Merge pull request #5187 from mailcow/fix-5185
Nextcloud helperscript - redo PHP check
2023-04-20 14:20:03 +02:00
Patrick Schult
8c79056a94 Merge pull request #5194 from mailcow/renovate/nextcloud-server-26.x
Update dependency nextcloud/server to v26.0.1
2023-04-20 14:19:19 +02:00
Patrick Schult
ed076dc23e Merge pull request #5186 from goodygh/datatables_sorting
[Web] Datatables sorting
2023-04-20 13:50:57 +02:00
FreddleSpl0it
be2286c11c [Dockerapi] fix maildir cleanup for domains 2023-04-20 13:41:11 +02:00
renovate[bot]
0e24c3d300 Update dependency nextcloud/server to v26.0.1
Signed-off-by: milkmaker <milkmaker@mailcow.de>
2023-04-20 11:36:01 +00:00
FreddleSpl0it
e1d8df6580 [Web] check mailbox before replacing sogo_static_view 2023-04-20 13:20:51 +02:00
Patrick Schult
04a08a7d69 Merge pull request #5193 from mailcow/feat/update-sogo
[SOGo] update sogo 5.8.2.20230419
2023-04-20 12:32:42 +02:00
FreddleSpl0it
3c0c8aa01f [SOGo] update sogo 5.8.2.20230419 2023-04-20 12:07:21 +02:00
Patrick Schult
026b278357 Merge pull request #5183 from mailcow/fix/add-mbox-performance
[Web] optimizing mailbox add/edit/delete performance
2023-04-20 11:34:41 +02:00
FreddleSpl0it
4121509ceb [Web] optimizing update_sogo_static_view function 2023-04-20 11:28:59 +02:00
Patrick Schult
00ac61f0a4 Merge pull request #5184 from bdwebnet/fix/ui-allowed-protocols
Added dropdown divider to "allowed protocols" selection on mailbox page
2023-04-19 17:31:05 +02:00
Patrick Schult
4bb0dbb2f7 Merge pull request #5191 from shiz0/patch-1
Fix Typo
2023-04-19 17:26:54 +02:00
Patrick Schult
13b6df74af Merge pull request #5174 from bdwebnet/staging
Fix error  "Deprecated: Using ${var} in strings is deprecated, use {$…
2023-04-19 17:23:26 +02:00
FreddleSpl0it
5c025bf865 [Rspamd] rollback to 3.4 2023-04-19 17:03:04 +02:00
Hannes Happle
20fc9eaf84 Fix Typo 2023-04-16 14:32:44 +02:00
Peter
22a0479fab Redo the PHP check grep 2023-04-13 21:11:40 +02:00
goodygh
3510d5617d Fix sorting for active relayhost 2023-04-13 19:18:04 +02:00
goodygh
236d627fbd Fix sorting for active transport map 2023-04-13 19:14:20 +02:00
goodygh
99739eada0 Fix sorting for active fowrardinghoststable 2023-04-13 19:01:03 +02:00
goodygh
7bfef57894 Fix sorting for active and tla on admins 2023-04-13 18:54:59 +02:00
goodygh
d9dfe15253 Fix sorting for active and tla on domain-admins 2023-04-13 18:54:08 +02:00
goodygh
3fe8aaa719 Fix sorting for active tls-policy-map 2023-04-13 18:14:18 +02:00
goodygh
78a8fac6af Fix sorting for active bcc-map and recipient-map 2023-04-13 18:10:21 +02:00
bd
6986e7758f Added dropdown divider to "allowed protocols" selection on mailbox page 2023-04-13 17:33:28 +02:00
BD
b4a9df76b8 Merge branch 'mailcow:staging' into staging 2023-04-13 17:22:13 +02:00
FreddleSpl0it
d9d958356a [Web] optimizing update_sogo_static_view function 2023-04-13 14:35:55 +02:00
goodygh
96f954a4e2 Fix sorting for active syncjobs 2023-04-12 00:36:46 +02:00
goodygh
44585e1c15 Fix sorting datatable in domain aliases 2023-04-12 00:23:53 +02:00
goodygh
c737ff4180 Fix sorting datatable in aliases 2023-04-12 00:21:27 +02:00
goodygh
025279009d Fix sorting for active resources 2023-04-12 00:17:41 +02:00
goodygh
a9dc13d567 Fix sorting datatable in mailbox templates 2023-04-12 00:15:16 +02:00
goodygh
c3ed01c9b5 Fix sorting for active mailboxes 2023-04-11 23:49:50 +02:00
goodygh
bd0b4a521e Fix sorting datatable in domain templates 2023-04-11 23:42:43 +02:00
goodygh
800a0ace71 Fix sorting for active domain in domains table 2023-04-11 23:19:56 +02:00
goodygh
db97869472 Datatable hide sorting value 2023-04-11 23:18:13 +02:00
milkmaker
f681fcf154 [Web] Updated lang.cs-cz.json (#5177)
Co-authored-by: utaxiu <kontakt@malyjakub.cz>
2023-04-11 17:38:39 +02:00
Patrick Schult
db1b5956fc Merge pull request #5133 from FELDSAM-INC/feldsam/bs5-related-fixes
BS5 related fixes
2023-04-11 06:35:41 +02:00
BD
bdb07061ed Fix error "Deprecated: Using ${var} in strings is deprecated, use {$var} instead in /web/sogo-auth.php on line 63" 2023-04-08 17:29:34 +02:00
Niklas Meyer
428b917579 Merge pull request #5166 from mailcow/staging
Hotfix php8.2 nextcloud < 26
2023-04-03 20:15:46 +02:00
Niklas Meyer
469f959e96 Merge pull request #5164 from mailcow/fix-5163
Add a check for PHP>=8.2 errormsg
2023-04-03 20:10:05 +02:00
Peter
b68e189d97 Add a check for PHP>=8.2 errormsg 2023-04-03 19:03:13 +02:00
Niklas Meyer
028ef22878 Merge pull request #5162 from mailcow/staging
Update 2023-04
2023-04-03 14:55:55 +02:00
Kristian Feldsam
80dacc015a [web] fixed mailbox/user settings buttons styling
Signed-off-by: Kristian Feldsam <feldsam@gmail.com>

[web] fixed mailbox/user settings buttons styling

Signed-off-by: Kristian Feldsam <feldsam@gmail.com>
2023-03-31 13:19:20 +02:00
Patrick Schult
0194c39bd5 Merge pull request #5158 from mailcow/feat/sogo-5.8.2
[SOGo] Update to 5.8.2
2023-03-31 08:16:57 +02:00
FreddleSpl0it
f53ca24bb0 [SOGo] Update to 5.8.2 2023-03-30 16:00:21 +02:00
Patrick Schult
ae46a877d3 Merge pull request #5157 from mailcow/feat/netfilter-1.52
[Netfilter] Update to 1.52
2023-03-30 09:05:52 +02:00
FreddleSpl0it
400939faf6 [Netfilter] Update to 1.52 2023-03-30 08:44:38 +02:00
Patrick Schult
fd0205aafd Merge pull request #5127 from th-joerger/feature/bantime-increment
[Netfilter] Implemented exponentially incrementing bantime
2023-03-30 07:53:33 +02:00
Patrick Schult
e367a8ce24 Merge pull request #5153 from mailcow/fix/del-vmail-index
[Dockerapi] delete vmail_index on maildir cleanup
2023-03-30 07:52:00 +02:00
Thorbjörn Jörger
096e2a41e9 Push verified options to redis after each check 2023-03-29 17:09:25 +02:00
Thorbjörn Jörger
e010f08143 verify options after loading them, set defaults if options are missing or invalid 2023-03-29 15:24:14 +02:00
Patrick Schult
3d2483ca37 Merge pull request #5093 from brunoleon/fix_snat
Fix SNAT never being added because of exception
2023-03-29 08:13:11 +02:00
FreddleSpl0it
26c34b484a increase dockerapi image 2023-03-28 11:01:14 +02:00
FreddleSpl0it
4021613059 delete vmail_index when mbox is deleted 2023-03-28 10:59:08 +02:00
Thorbjörn Jörger
c28a6b89f0 Added ban_time_increment and max_ban_time to UI 2023-03-21 11:06:13 +01:00
Thorbjörn Jörger
1233613bea implemented handling of max_bantime and ban_time_increment flag 2023-03-21 11:06:13 +01:00
Thorbjörn Jörger
0206e0886c implemented exponentially incrementing bantime, removed active_window code that did nothing, cleanly initialized dictionary 2023-03-21 11:06:13 +01:00
Kristian Feldsam
e5e6418be8 [web] fixed tooltips in ajax loaded alias table
Signed-off-by: Kristian Feldsam <feldsam@gmail.com>
2023-03-20 01:38:34 +01:00
Kristian Feldsam
6507b53bbb [web] fix mailbox badge height
Signed-off-by: Kristian Feldsam <feldsam@gmail.com>
2023-03-20 01:38:31 +01:00
Bruno Léon
f77c65411d Fix SNAT never being added because of exception
Some firewall rule object (iptc) do not have a parameter
attribute, which results in an exception being triggered,
and the mailcow SNAT rule to never be created.

Firewall rules that trigger such exception are:
- -A POSTROUTING -s 192.168.122.0/24 -d 224.0.0.0/24 -j RETURN

This commit just verify attribute presence, and skip the rule
properly instead of triggering an exception.
2023-02-27 12:04:32 +01:00
Daniel Lo Nigro
1606658cb1 Add missing spaces 2021-08-28 20:02:39 -07:00
Daniel Lo Nigro
54ba66733e Enable maildir_very_dirty_syncs rather than just adding comment 2021-05-02 16:39:26 -07:00
Daniel Lo Nigro
f6847e6f8c Add comment about maildir_very_dirty_syncs to dovecot.conf 2021-03-13 10:46:32 -08:00
45 changed files with 488 additions and 199 deletions

View File

@@ -0,0 +1,39 @@
name: Update postscreen_access.cidr
on:
schedule:
# Monthly
- cron: "0 0 1 * *"
workflow_dispatch: # Allow to run workflow manually
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
Update-postscreen_access_cidr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Generate postscreen_access.cidr
run: |
bash helper-scripts/update_postscreen_whitelist.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
commit-message: update postscreen_access.cidr
committer: milkmaker <milkmaker@mailcow.de>
author: milkmaker <milkmaker@mailcow.de>
signoff: false
branch: update/postscreen_access.cidr
base: staging
delete-branch: true
add-paths: |
data/conf/postfix/postscreen_access.cidr
title: '[Postfix] update postscreen_access.cidr'
body: |
This PR updates the postscreen_access.cidr using GitHub Actions and [helper-scripts/update_postscreen_whitelist.sh](https://github.com/mailcow/mailcow-dockerized/blob/master/helper-scripts/update_postscreen_whitelist.sh)

View File

@@ -9,6 +9,7 @@ import os
import json import json
import asyncio import asyncio
import redis import redis
import platform
from datetime import datetime from datetime import datetime
import logging import logging
from logging.config import dictConfig from logging.config import dictConfig
@@ -370,7 +371,7 @@ class DockerUtils:
return exec_run_handler('utf8_text_only', sieve_return) return exec_run_handler('utf8_text_only', sieve_return)
# api call: container_post - post_action: exec - cmd: sieve - task: print # api call: container_post - post_action: exec - cmd: sieve - task: print
def container_post__exec__sieve__print(self, container_id, request_json): def container_post__exec__sieve__print(self, container_id, request_json):
if 'username' in request.json and 'script_name' in request_json: if 'username' in request_json and 'script_name' in request_json:
for container in self.docker_client.containers.list(filters={"id": container_id}): for container in self.docker_client.containers.list(filters={"id": container_id}):
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"] cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
sieve_return = container.exec_run(cmd) sieve_return = container.exec_run(cmd)
@@ -380,7 +381,15 @@ class DockerUtils:
if 'maildir' in request_json: if 'maildir' in request_json:
for container in self.docker_client.containers.list(filters={"id": container_id}): for container in self.docker_client.containers.list(filters={"id": container_id}):
sane_name = re.sub(r'\W+', '', request_json['maildir']) sane_name = re.sub(r'\W+', '', request_json['maildir'])
cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"] vmail_name = request_json['maildir'].replace("'", "'\\''")
cmd_vmail = "if [[ -d '/var/vmail/" + vmail_name + "' ]]; then /bin/mv '/var/vmail/" + vmail_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"
index_name = request_json['maildir'].split("/")
if len(index_name) > 1:
index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''")
cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi"
cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index]
else:
cmd = ["/bin/bash", "-c", cmd_vmail]
maildir_cleanup = container.exec_run(cmd, user='vmail') maildir_cleanup = container.exec_run(cmd, user='vmail')
return exec_run_handler('generic', maildir_cleanup) return exec_run_handler('generic', maildir_cleanup)
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
@@ -477,7 +486,8 @@ async def get_host_stats(wait=5):
"swap": psutil.swap_memory() "swap": psutil.swap_memory()
}, },
"uptime": time.time() - psutil.boot_time(), "uptime": time.time() - psutil.boot_time(),
"system_time": system_time.strftime("%d.%m.%Y %H:%M:%S") "system_time": system_time.strftime("%d.%m.%Y %H:%M:%S"),
"architecture": platform.machine()
} }
redis_client.set('host_stats', json.dumps(host_stats), ex=10) redis_client.set('host_stats', json.dumps(host_stats), ex=10)

View File

@@ -64,28 +64,40 @@ def refreshF2boptions():
global f2boptions global f2boptions
global quit_now global quit_now
global exit_code global exit_code
if not r.get('F2B_OPTIONS'):
f2boptions = {} f2boptions = {}
f2boptions['ban_time'] = int
f2boptions['max_attempts'] = int if not r.get('F2B_OPTIONS'):
f2boptions['retry_window'] = int f2boptions['ban_time'] = r.get('F2B_BAN_TIME')
f2boptions['netban_ipv4'] = int f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME')
f2boptions['netban_ipv6'] = int f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT')
f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800 f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS')
f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW')
f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4')
f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 32 f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6')
f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 128
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
else: else:
try: try:
f2boptions = {}
f2boptions = json.loads(r.get('F2B_OPTIONS')) f2boptions = json.loads(r.get('F2B_OPTIONS'))
except ValueError: except ValueError:
print('Error loading F2B options: F2B_OPTIONS is not json') print('Error loading F2B options: F2B_OPTIONS is not json')
quit_now = True quit_now = True
exit_code = 2 exit_code = 2
verifyF2boptions(f2boptions)
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
def verifyF2boptions(f2boptions):
verifyF2boption(f2boptions,'ban_time', 1800)
verifyF2boption(f2boptions,'max_ban_time', 10000)
verifyF2boption(f2boptions,'ban_time_increment', True)
verifyF2boption(f2boptions,'max_attempts', 10)
verifyF2boption(f2boptions,'retry_window', 600)
verifyF2boption(f2boptions,'netban_ipv4', 32)
verifyF2boption(f2boptions,'netban_ipv6', 128)
def verifyF2boption(f2boptions, f2boption, f2bdefault):
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
def refreshF2bregex(): def refreshF2bregex():
global f2bregex global f2bregex
global quit_now global quit_now
@@ -147,6 +159,7 @@ def ban(address):
global lock global lock
refreshF2boptions() refreshF2boptions()
BAN_TIME = int(f2boptions['ban_time']) BAN_TIME = int(f2boptions['ban_time'])
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
MAX_ATTEMPTS = int(f2boptions['max_attempts']) MAX_ATTEMPTS = int(f2boptions['max_attempts'])
RETRY_WINDOW = int(f2boptions['retry_window']) RETRY_WINDOW = int(f2boptions['retry_window'])
NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4'])
@@ -174,20 +187,16 @@ def ban(address):
net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False) net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
net = str(net) net = str(net)
if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW: if not net in bans:
bans[net] = { 'attempts': 0 } bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
active_window = RETRY_WINDOW
else:
active_window = time.time() - bans[net]['last_attempt']
bans[net]['attempts'] += 1 bans[net]['attempts'] += 1
bans[net]['last_attempt'] = time.time() bans[net]['last_attempt'] = time.time()
active_window = time.time() - bans[net]['last_attempt']
if bans[net]['attempts'] >= MAX_ATTEMPTS: if bans[net]['attempts'] >= MAX_ATTEMPTS:
cur_time = int(round(time.time())) cur_time = int(round(time.time()))
logCrit('Banning %s for %d minutes' % (net, BAN_TIME / 60)) NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
if type(ip) is ipaddress.IPv4Address: if type(ip) is ipaddress.IPv4Address:
with lock: with lock:
chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
@@ -206,7 +215,7 @@ def ban(address):
rule.target = target rule.target = target
if rule not in chain.rules: if rule not in chain.rules:
chain.insert_rule(rule) chain.insert_rule(rule)
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME) r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
else: else:
logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
@@ -238,7 +247,8 @@ def unban(net):
r.hdel('F2B_ACTIVE_BANS', '%s' % net) r.hdel('F2B_ACTIVE_BANS', '%s' % net)
r.hdel('F2B_QUEUE_UNBAN', '%s' % net) r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
if net in bans: if net in bans:
del bans[net] bans[net]['attempts'] = 0
bans[net]['ban_counter'] += 1
def permBan(net, unban=False): def permBan(net, unban=False):
global lock global lock
@@ -366,6 +376,8 @@ def snat4(snat_target):
chain.insert_rule(new_rule) chain.insert_rule(new_rule)
else: else:
for position, rule in enumerate(chain.rules): for position, rule in enumerate(chain.rules):
if not hasattr(rule.target, 'parameter'):
continue
match = all(( match = all((
new_rule.get_src() == rule.get_src(), new_rule.get_src() == rule.get_src(),
new_rule.get_dst() == rule.get_dst(), new_rule.get_dst() == rule.get_dst(),
@@ -425,6 +437,8 @@ def autopurge():
time.sleep(10) time.sleep(10)
refreshF2boptions() refreshF2boptions()
BAN_TIME = int(f2boptions['ban_time']) BAN_TIME = int(f2boptions['ban_time'])
MAX_BAN_TIME = int(f2boptions['max_ban_time'])
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
MAX_ATTEMPTS = int(f2boptions['max_attempts']) MAX_ATTEMPTS = int(f2boptions['max_attempts'])
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
if QUEUE_UNBAN: if QUEUE_UNBAN:
@@ -432,7 +446,9 @@ def autopurge():
unban(str(net)) unban(str(net))
for net in bans.copy(): for net in bans.copy():
if bans[net]['attempts'] >= MAX_ATTEMPTS: if bans[net]['attempts'] >= MAX_ATTEMPTS:
if time.time() - bans[net]['last_attempt'] > BAN_TIME: NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME:
unban(net) unban(net)
def isIpNetwork(address): def isIpNetwork(address):

View File

@@ -172,6 +172,24 @@ BEGIN
END; END;
// //
DELIMITER ; DELIMITER ;
DROP EVENT IF EXISTS clean_sasl_log;
DELIMITER //
CREATE EVENT clean_sasl_log
ON SCHEDULE EVERY 1 DAY DO
BEGIN
DELETE sasl_log.* FROM sasl_log
LEFT JOIN (
SELECT username, service, MAX(datetime) AS lastdate
FROM sasl_log
GROUP BY username, service
) AS last ON sasl_log.username = last.username AND sasl_log.service = last.service
WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY) AND datetime < lastdate;
DELETE FROM sasl_log
WHERE username NOT IN (SELECT username FROM mailbox) AND
datetime < DATE_SUB(NOW(), INTERVAL 31 DAY);
END;
//
DELIMITER ;
EOF EOF
fi fi

View File

@@ -24,6 +24,11 @@ mail_plugins = </etc/dovecot/mail_plugins
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix: mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
mail_attachment_dir = /var/attachments mail_attachment_dir = /var/attachments
mail_attachment_min_size = 128k mail_attachment_min_size = 128k
# Significantly speeds up very large mailboxes, but is only safe to enable if
# you do not manually modify the files in the `cur` directories in
# mailcowdockerized_vmail-vol-1.
# https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-performance/
maildir_very_dirty_syncs = yes
# Dovecot 2.2 # Dovecot 2.2
#ssl_protocols = !SSLv3 #ssl_protocols = !SSLv3

View File

@@ -28,3 +28,4 @@
#197695 2 #Domain names registrar REG.RU Ltd, Russia #197695 2 #Domain names registrar REG.RU Ltd, Russia
#198068 2 #P.A.G.M. OU, Estonia #198068 2 #P.A.G.M. OU, Estonia
#201942 5 #Soltia Consulting SL, Spain #201942 5 #Soltia Consulting SL, Spain
#213373 4 #IP Connect Inc

View File

@@ -3176,8 +3176,10 @@ paths:
example: example:
attr: attr:
ban_time: "86400" ban_time: "86400"
ban_time_increment: "1"
blacklist: "10.100.6.5/32,10.100.8.4/32" blacklist: "10.100.6.5/32,10.100.8.4/32"
max_attempts: "5" max_attempts: "5"
max_ban_time: "86400"
netban_ipv4: "24" netban_ipv4: "24"
netban_ipv6: "64" netban_ipv6: "64"
retry_window: "600" retry_window: "600"
@@ -3191,11 +3193,17 @@ paths:
description: the backlisted ips or hostnames separated by comma description: the backlisted ips or hostnames separated by comma
type: string type: string
ban_time: ban_time:
description: the time a ip should be banned description: the time an ip should be banned
type: number type: number
ban_time_increment:
description: if the time of the ban should increase each time
type: boolean
max_attempts: max_attempts:
description: the maximum numbe of wrong logins before a ip is banned description: the maximum numbe of wrong logins before a ip is banned
type: number type: number
max_ban_time:
description: the maximum time an ip should be banned
type: number
netban_ipv4: netban_ipv4:
description: the networks mask to ban for ipv4 description: the networks mask to ban for ipv4
type: number type: number
@@ -4113,10 +4121,12 @@ paths:
response: response:
value: value:
ban_time: 604800 ban_time: 604800
ban_time_increment: 1
blacklist: |- blacklist: |-
45.82.153.37/32 45.82.153.37/32
92.118.38.52/32 92.118.38.52/32
max_attempts: 1 max_attempts: 1
max_ban_time: 604800
netban_ipv4: 32 netban_ipv4: 32
netban_ipv6: 128 netban_ipv6: 128
perm_bans: perm_bans:

View File

@@ -342,6 +342,10 @@ div.dataTables_wrapper div.dt-row {
position: relative; position: relative;
} }
div.dataTables_wrapper span.sorting-value {
display: none;
}
div.dataTables_scrollHead table.dataTable { div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }

View File

@@ -66,4 +66,6 @@ table tbody tr td input[type="checkbox"] {
padding: .2em .4em .3em !important; padding: .2em .4em .3em !important;
background-color: #ececec!important; background-color: #ececec!important;
} }
.badge.bg-info .bi {
font-size: inherit;
}

View File

@@ -20,6 +20,11 @@ legend {
background-color: #7a7a7a !important; background-color: #7a7a7a !important;
border-color: #5c5c5c !important; border-color: #5c5c5c !important;
} }
.btn-dark {
color: #000 !important;;
background-color: #f6f6f6 !important;;
border-color: #ddd !important;;
}
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle { .btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
border-color: #7a7a7a !important; border-color: #7a7a7a !important;
} }

View File

@@ -49,7 +49,9 @@ function bcc($_action, $_data = null, $_attr = null) {
} }
elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) { elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) {
$mailbox = mailbox('get', 'mailbox_details', $local_dest); $mailbox = mailbox('get', 'mailbox_details', $local_dest);
if ($mailbox === false && array_key_exists($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) { $shared_aliases = mailbox('get', 'shared_aliases');
$direct_aliases = mailbox('get', 'direct_aliases');
if ($mailbox === false && in_array($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $_attr), 'log' => array(__FUNCTION__, $_action, $_data, $_attr),

View File

@@ -239,7 +239,9 @@ function fail2ban($_action, $_data = null) {
$is_now = fail2ban('get'); $is_now = fail2ban('get');
if (!empty($is_now)) { if (!empty($is_now)) {
$ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
$ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0;
$max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']); $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']);
$max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']);
$retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']); $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']);
$netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']); $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']);
$netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']); $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']);
@@ -256,6 +258,8 @@ function fail2ban($_action, $_data = null) {
} }
$f2b_options = array(); $f2b_options = array();
$f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time; $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time;
$f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false;
$f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time;
$f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4; $f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4;
$f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6; $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
$f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4; $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;

View File

@@ -1015,20 +1015,58 @@ function formatBytes($size, $precision = 2) {
} }
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
} }
function update_sogo_static_view() { function update_sogo_static_view($mailbox = null) {
if (getenv('SKIP_SOGO') == "y") { if (getenv('SKIP_SOGO') == "y") {
return true; return true;
} }
global $pdo; global $pdo;
global $lang; global $lang;
$stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'sogo_view'"); $mailbox_exists = false;
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($mailbox !== null) {
if ($num_results != 0) { // Check if the mailbox exists
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`) $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'");
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view"); $stmt->execute(array(':mailbox' => $mailbox));
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row){
$mailbox_exists = true;
} }
}
$query = "REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
SELECT
mailbox.username,
mailbox.domain,
mailbox.username,
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.force_pw_update')) = '0',
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.sogo_access')) = 1, password, '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
'{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
mailbox.name,
mailbox.username,
IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''),
IFNULL(gda.ad_alias, ''),
IFNULL(external_acl.send_as_acl, ''),
mailbox.kind,
mailbox.multiple_bookings
FROM
mailbox
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
WHERE
mailbox.active = '1'";
if ($mailbox_exists) {
$query .= " AND mailbox.username = :mailbox";
$stmt = $pdo->prepare($query);
$stmt->execute(array(':mailbox' => $mailbox));
} else {
$query .= " GROUP BY mailbox.username";
$stmt = $pdo->query($query);
}
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
flush_memcached(); flush_memcached();
} }
function edit_user_account($_data) { function edit_user_account($_data) {

View File

@@ -1264,11 +1264,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
)); ));
} }
update_sogo_static_view($username);
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_added', htmlspecialchars($username)) 'msg' => array('mailbox_added', htmlspecialchars($username))
); );
return true;
break; break;
case 'resource': case 'resource':
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
@@ -3130,7 +3132,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username) 'msg' => array('mailbox_modified', $username)
); );
update_sogo_static_view($username);
} }
return true;
break; break;
case 'mailbox_templates': case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
@@ -3960,6 +3965,39 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
return $aliasdomaindata; return $aliasdomaindata;
break; break;
case 'shared_aliases':
$shared_aliases = array();
$stmt = $pdo->query("SELECT `address` FROM `alias`
WHERE `goto` REGEXP ','
AND `address` NOT LIKE '@%'
AND `goto` != `address`");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domain = explode("@", $row['address'])[1];
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$shared_aliases[] = $row['address'];
}
}
return $shared_aliases;
break;
case 'direct_aliases':
$direct_aliases = array();
$stmt = $pdo->query("SELECT `address` FROM `alias`
WHERE `goto` NOT LIKE '%,%'
AND `address` NOT LIKE '@%'
AND `goto` != `address`");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domain = explode("@", $row['address'])[1];
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$direct_aliases[] = $row['address'];
}
}
return $direct_aliases;
break;
case 'domains': case 'domains':
$domains = array(); $domains = array();
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
@@ -4951,9 +4989,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':username' => $username
)); ));
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as OR `send_as` = :send_as");
$stmt->execute(array( $stmt->execute(array(
':username' => $username ':logged_in_as' => $username,
':send_as' => $username
)); ));
// fk, better safe than sorry // fk, better safe than sorry
$stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username"); $stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username");
@@ -5053,12 +5092,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
update_sogo_static_view($username);
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_removed', htmlspecialchars($username)) 'msg' => array('mailbox_removed', htmlspecialchars($username))
); );
} }
return true;
break; break;
case 'mailbox_templates': case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
@@ -5264,7 +5306,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
break; break;
} }
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource')) && getenv('SKIP_SOGO') != "y") { if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
update_sogo_static_view(); update_sogo_static_view();
} }
} }

View File

@@ -63,7 +63,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
unset($_SESSION['index_query_string']); unset($_SESSION['index_query_string']);
if (in_array('mobileconfig', $http_parameters)) { if (in_array('mobileconfig', $http_parameters)) {
if (in_array('only_email', $http_parameters)) { if (in_array('only_email', $http_parameters)) {
header("Location: /mobileconfig.php?email_only"); header("Location: /mobileconfig.php?only_email");
die(); die();
} }
header("Location: /mobileconfig.php"); header("Location: /mobileconfig.php");

View File

@@ -1,3 +1,13 @@
const LOCALE = undefined;
const DATETIME_FORMAT = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
};
$(document).ready(function() { $(document).ready(function() {
// mailcow alert box generator // mailcow alert box generator
window.mailcow_alert_box = function(message, type) { window.mailcow_alert_box = function(message, type) {

View File

@@ -117,8 +117,8 @@ jQuery(function($){
data: 'tfa_active', data: 'tfa_active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
else return '<i class="bi bi-x-lg"></i>'; else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -126,8 +126,8 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
else return '<i class="bi bi-x-lg"></i>'; else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -260,8 +260,8 @@ jQuery(function($){
data: 'tfa_active', data: 'tfa_active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
else return '<i class="bi bi-x-lg"></i>'; else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -269,8 +269,8 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
else return '<i class="bi bi-x-lg"></i>'; else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -337,7 +337,7 @@ jQuery(function($){
data: 'keep_spam', data: 'keep_spam',
defaultContent: '', defaultContent: '',
render: function(data, type){ render: function(data, type){
return 'yes'==data?'<i class="bi bi-x-lg"></i>':'no'==data&&'<i class="bi bi-check-lg"></i>'; return 'yes'==data?'<i class="bi bi-x-lg"><span class="sorting-value">yes</span></i>':'no'==data&&'<i class="bi bi-check-lg"><span class="sorting-value">no</span></i>';
} }
}, },
{ {
@@ -414,8 +414,8 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
else return '<i class="bi bi-x-lg"></i>'; else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -492,8 +492,8 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
else return '<i class="bi bi-x-lg"></i>'; else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {

View File

@@ -1,13 +1,3 @@
const LOCALE = undefined;
const DATETIME_FORMAT = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
};
$(document).ready(function() { $(document).ready(function() {
// Parse seconds ago to date // Parse seconds ago to date
// Get "now" timestamp // Get "now" timestamp
@@ -43,7 +33,7 @@ $(document).ready(function() {
if (mailcow_info.branch === "master"){ if (mailcow_info.branch === "master"){
check_update(mailcow_info.version_tag, mailcow_info.project_url); check_update(mailcow_info.version_tag, mailcow_info.project_url);
} }
$("#maiclow_version").click(function(){ $("#mailcow_version").click(function(){
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master") if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master")
return; return;
@@ -829,13 +819,10 @@ jQuery(function($){
url: '/api/v1/get/rspamd/actions', url: '/api/v1/get/rspamd/actions',
async: true, async: true,
success: function(data){ success: function(data){
console.log(data);
var total = 0; var total = 0;
$(data).map(function(){total += this[1];}); $(data).map(function(){total += this[1];});
var labels = $.makeArray($(data).map(function(){return this[0] + ' ' + Math.round(this[1]/total * 100) + '%';})); var labels = $.makeArray($(data).map(function(){return this[0] + ' ' + Math.round(this[1]/total * 100) + '%';}));
var values = $.makeArray($(data).map(function(){return this[1];})); var values = $.makeArray($(data).map(function(){return this[1];}));
console.log(values);
var graphdata = { var graphdata = {
labels: labels, labels: labels,
@@ -951,12 +938,15 @@ jQuery(function($){
title: 'Score', title: 'Score',
data: 'score', data: 'score',
defaultContent: '', defaultContent: '',
class: 'text-nowrap',
createdCell: function(td, cellData) { createdCell: function(td, cellData) {
$(td).attr({ $(td).attr({
"data-order": cellData.sortBy, "data-order": cellData.sortBy,
"data-sort": cellData.sortBy "data-sort": cellData.sortBy
}); });
$(td).html(cellData.value); },
render: function (data) {
return data.value;
} }
}, },
{ {
@@ -979,7 +969,9 @@ jQuery(function($){
"data-order": cellData.sortBy, "data-order": cellData.sortBy,
"data-sort": cellData.sortBy "data-sort": cellData.sortBy
}); });
$(td).html(cellData.value); },
render: function (data) {
return data.value;
} }
}, },
{ {
@@ -1302,6 +1294,12 @@ function update_stats(timeout=5){
$("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%"); $("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%");
$("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB"); $("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB");
$("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%"); $("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%");
if (data.architecture == "aarch64"){
$("#host_architecture").html('<span data-bs-toggle="tooltip" data-bs-placement="top" title="' + lang_debug.wip +'">' + data.architecture + ' ⚠️</span>');
}
else {
$("#host_architecture").html(data.architecture);
}
// update cpu and mem chart // update cpu and mem chart
var cpu_chart = Chart.getChart("host_cpu_chart"); var cpu_chart = Chart.getChart("host_cpu_chart");

View File

@@ -607,7 +607,7 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
responsivePriority: 6, responsivePriority: 6,
render: function (data, type) { 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&&'&#8212;'); return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
} }
}, },
{ {
@@ -754,7 +754,7 @@ jQuery(function($){
data: 'attributes.gal', data: 'attributes.gal',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -762,7 +762,7 @@ jQuery(function($){
data: 'attributes.backupmx', data: 'attributes.backupmx',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -770,7 +770,7 @@ jQuery(function($){
data: 'attributes.relay_all_recipients', data: 'attributes.relay_all_recipients',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -778,7 +778,7 @@ jQuery(function($){
data: 'attributes.relay_unknown_only', data: 'attributes.relay_unknown_only',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -787,7 +787,7 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
responsivePriority: 4, responsivePriority: 4,
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -1093,7 +1093,7 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
responsivePriority: 4, responsivePriority: 4,
render: function (data, type) { 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&&'&#8212;'); return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
} }
}, },
{ {
@@ -1164,13 +1164,13 @@ jQuery(function($){
item.attributes.quota = humanFileSize(item.attributes.quota); 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_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></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.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></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.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></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.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.imap_access == 1 ? '1' : '0') + '</span></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.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.smtp_access == 1 ? '1' : '0') + '</span></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.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sieve_access == 1 ? '1' : '0') + '</span></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>'; 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') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
if (item.attributes.quarantine_notification === 'never') { if (item.attributes.quarantine_notification === 'never') {
item.attributes.quarantine_notification = lang.never; item.attributes.quarantine_notification = lang.never;
} else if (item.attributes.quarantine_notification === 'hourly') { } else if (item.attributes.quarantine_notification === 'hourly') {
@@ -1188,7 +1188,6 @@ jQuery(function($){
item.attributes.quarantine_category = lang.q_all; item.attributes.quarantine_category = lang.q_all;
} }
if (item.template.toLowerCase() == "default"){ if (item.template.toLowerCase() == "default"){
item.action = '<div class="btn-group">' + 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="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
@@ -1329,7 +1328,7 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
responsivePriority: 4, responsivePriority: 4,
render: function (data, type) { 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&&'&#8212;'); return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
} }
}, },
{ {
@@ -1440,7 +1439,7 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { 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&&'&#8212;'); return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
} }
}, },
{ {
@@ -1459,30 +1458,37 @@ jQuery(function($){
} }
function draw_bcc_table() { function draw_bcc_table() {
$.get("/api/v1/get/bcc-destination-options", function(data){ $.get("/api/v1/get/bcc-destination-options", function(data){
var optgroup = "";
// Domains // Domains
var optgroup = "<optgroup label='" + lang.domains + "'>"; if (data.domains && data.domains.length > 0) {
optgroup = "<optgroup label='" + lang.domains + "'>";
$.each(data.domains, function(index, domain){ $.each(data.domains, function(index, domain){
optgroup += "<option value='" + domain + "'>" + domain + "</option>"; optgroup += "<option value='" + domain + "'>" + domain + "</option>";
}); });
optgroup += "</optgroup>"; optgroup += "</optgroup>";
$('#bcc-local-dest').append(optgroup); $('#bcc-local-dest').append(optgroup);
}
// Alias domains // Alias domains
var optgroup = "<optgroup label='" + lang.domain_aliases + "'>"; if (data.alias_domains && data.alias_domains.length > 0) {
optgroup = "<optgroup label='" + lang.domain_aliases + "'>";
$.each(data.alias_domains, function(index, alias_domain){ $.each(data.alias_domains, function(index, alias_domain){
optgroup += "<option value='" + alias_domain + "'>" + alias_domain + "</option>"; optgroup += "<option value='" + alias_domain + "'>" + alias_domain + "</option>";
}); });
optgroup += "</optgroup>" optgroup += "</optgroup>"
$('#bcc-local-dest').append(optgroup); $('#bcc-local-dest').append(optgroup);
}
// Mailboxes and aliases // Mailboxes and aliases
if (data.mailboxes && Object.keys(data.mailboxes).length > 0) {
$.each(data.mailboxes, function(mailbox, aliases){ $.each(data.mailboxes, function(mailbox, aliases){
var optgroup = "<optgroup label='" + mailbox + "'>"; optgroup = "<optgroup label='" + mailbox + "'>";
$.each(aliases, function(index, alias){ $.each(aliases, function(index, alias){
optgroup += "<option value='" + alias + "'>" + alias + "</option>"; optgroup += "<option value='" + alias + "'>" + alias + "</option>";
}); });
optgroup += "</optgroup>"; optgroup += "</optgroup>";
$('#bcc-local-dest').append(optgroup); $('#bcc-local-dest').append(optgroup);
}); });
// Finish }
// Recreate picker
$('#bcc-local-dest').selectpicker('refresh'); $('#bcc-local-dest').selectpicker('refresh');
}); });
@@ -1578,7 +1584,7 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { 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&&'&#8212;'); return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
} }
}, },
{ {
@@ -1675,7 +1681,7 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -1782,7 +1788,7 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -1917,7 +1923,7 @@ jQuery(function($){
data: 'sogo_visible', data: 'sogo_visible',
defaultContent: '', defaultContent: '',
render: function(data, type){ render: function(data, type){
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -1936,7 +1942,7 @@ jQuery(function($){
defaultContent: '', defaultContent: '',
responsivePriority: 6, responsivePriority: 6,
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -1952,6 +1958,10 @@ jQuery(function($){
table.on('responsive-resize', function (e, datatable, columns){ table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table'); hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table');
}); });
table.on( 'draw', function (){
$('#alias_table [data-bs-toggle="tooltip"]').tooltip();
});
} }
function draw_aliasdomain_table() { function draw_aliasdomain_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
@@ -2031,7 +2041,7 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -2167,7 +2177,7 @@ jQuery(function($){
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'; return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
} }
}, },
{ {
@@ -2323,16 +2333,19 @@ jQuery(function($){
// detect element visibility changes // detect element visibility changes
function onVisible(element, callback) { function onVisible(element, callback) {
$(document).ready(function() { $(document).ready(function() {
element_object = document.querySelector(element); let element_object = document.querySelector(element);
if (element_object === null) return; if (element_object === null) return;
new IntersectionObserver((entries, observer) => { let observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => { entries.forEach(entry => {
if(entry.intersectionRatio > 0) { if(entry.intersectionRatio > 0) {
callback(element_object); callback(element_object);
observer.unobserve(element_object);
} }
}); });
}).observe(element_object); })
observer.observe(element_object);
}); });
} }

View File

@@ -127,6 +127,20 @@ jQuery(function($){
} }
} }
function createSortableDate(td, cellData, date_string = false) {
if (date_string)
var date = new Date(cellData);
else
var date = new Date(cellData ? cellData * 1000 : 0);
var timestamp = date.getTime();
$(td).attr({
"data-order": timestamp,
"data-sort": timestamp
});
$(td).html(date.toLocaleDateString(LOCALE, DATETIME_FORMAT));
}
function draw_tla_table() { function draw_tla_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#tla_table') ) { if ($.fn.DataTable.isDataTable('#tla_table') ) {
@@ -144,6 +158,7 @@ jQuery(function($){
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables, language: lang_datatables,
order: [[4, 'desc']],
ajax: { ajax: {
type: "GET", type: "GET",
url: "/api/v1/get/time_limited_aliases", url: "/api/v1/get/time_limited_aliases",
@@ -191,18 +206,16 @@ jQuery(function($){
title: lang.alias_valid_until, title: lang.alias_valid_until,
data: 'validity', data: 'validity',
defaultContent: '', defaultContent: '',
render: function (data, type) { createdCell: function(td, cellData) {
var date = new Date(data ? data * 1000 : 0); createSortableDate(td, cellData)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
title: lang.created_on, title: lang.created_on,
data: 'created', data: 'created',
defaultContent: '', defaultContent: '',
render: function (data, type) { createdCell: function(td, cellData) {
var date = new Date(data.replace(/-/g, "/")); createSortableDate(td, cellData, true)
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {

View File

@@ -105,7 +105,8 @@
"timeout2": "Časový limit pro připojení k lokálnímu serveru", "timeout2": "Časový limit pro připojení k lokálnímu serveru",
"username": "Uživatelské jméno", "username": "Uživatelské jméno",
"validate": "Ověřit", "validate": "Ověřit",
"validation_success": "Úspěšně ověřeno" "validation_success": "Úspěšně ověřeno",
"tags": "Štítky"
}, },
"admin": { "admin": {
"access": "Přístupy", "access": "Přístupy",
@@ -333,7 +334,11 @@
"username": "Uživatelské jméno", "username": "Uživatelské jméno",
"validate_license_now": "Ověřit GUID na licenčním serveru", "validate_license_now": "Ověřit GUID na licenčním serveru",
"verify": "Ověřit", "verify": "Ověřit",
"yes": "&#10003;" "yes": "&#10003;",
"f2b_ban_time_increment": "Délka banu je prodlužována s každým dalším banem",
"f2b_max_ban_time": "Maximální délka banu (s)",
"ip_check": "Kontrola IP",
"ip_check_disabled": "Kontrola IP je vypnuta. Můžete ji zapnout v <br> <strong>System > Nastavení > Options > Přizpůsobení</strong>"
}, },
"danger": { "danger": {
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři", "access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
@@ -536,7 +541,7 @@
"inactive": "Neaktivní", "inactive": "Neaktivní",
"kind": "Druh", "kind": "Druh",
"last_modified": "Naposledy změněn", "last_modified": "Naposledy změněn",
"lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (<code>.*google\\.com</code> směřuje veškerou poštu na MX které jsou cílem pro google.com přes tento skok)", "lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (<code>.*\\.google\\.com</code> směřuje veškerou poštu na MX které jsou cílem pro google.com přes tento skok)",
"mailbox": "Úprava mailové schránky", "mailbox": "Úprava mailové schránky",
"mailbox_quota_def": "Výchozí kvóta schránky", "mailbox_quota_def": "Výchozí kvóta schránky",
"mailbox_relayhost_info": "Aplikované jen na uživatelskou schránku a přímé aliasy, přepisuje předávající server domény.", "mailbox_relayhost_info": "Aplikované jen na uživatelskou schránku a přímé aliasy, přepisuje předávající server domény.",

View File

@@ -175,10 +175,12 @@
"empty": "Keine Einträge vorhanden", "empty": "Keine Einträge vorhanden",
"excludes": "Diese Empfänger ausschließen", "excludes": "Diese Empfänger ausschließen",
"f2b_ban_time": "Bannzeit in Sekunden", "f2b_ban_time": "Bannzeit in Sekunden",
"f2b_ban_time_increment": "Bannzeit erhöht sich mit jedem Bann",
"f2b_blacklist": "Blacklist für Netzwerke und Hosts", "f2b_blacklist": "Blacklist für Netzwerke und Hosts",
"f2b_filter": "Regex-Filter", "f2b_filter": "Regex-Filter",
"f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>", "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
"f2b_max_attempts": "Max. Versuche", "f2b_max_attempts": "Max. Versuche",
"f2b_max_ban_time": "Maximale Bannzeit in Sekunden",
"f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)", "f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)",
"f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)", "f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)",
"f2b_parameters": "Fail2ban-Parameter", "f2b_parameters": "Fail2ban-Parameter",
@@ -214,7 +216,7 @@
"loading": "Bitte warten...", "loading": "Bitte warten...",
"login_time": "Zeit", "login_time": "Zeit",
"logo_info": "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.", "logo_info": "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.",
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)", "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
"main_name": "\"mailcow UI\" Name", "main_name": "\"mailcow UI\" Name",
"merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.", "merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.",
"message": "Nachricht", "message": "Nachricht",
@@ -496,6 +498,7 @@
} }
}, },
"debug": { "debug": {
"architecture": "Architektur",
"chart_this_server": "Chart (dieser Server)", "chart_this_server": "Chart (dieser Server)",
"containers_info": "Container-Information", "containers_info": "Container-Information",
"container_running": "Läuft", "container_running": "Läuft",
@@ -532,7 +535,8 @@
"update_available": "Es ist ein Update verfügbar", "update_available": "Es ist ein Update verfügbar",
"no_update_available": "Das System ist auf aktuellem Stand", "no_update_available": "Das System ist auf aktuellem Stand",
"update_failed": "Es konnte nicht nach einem Update gesucht werden", "update_failed": "Es konnte nicht nach einem Update gesucht werden",
"username": "Benutzername" "username": "Benutzername",
"wip": "Aktuell noch in Arbeit"
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "Wert abgeleitet von A/AAAA-Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt.", "cname_from_a": "Wert abgeleitet von A/AAAA-Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt.",
@@ -591,7 +595,7 @@
"inactive": "Inaktiv", "inactive": "Inaktiv",
"kind": "Art", "kind": "Art",
"last_modified": "Zuletzt geändert", "last_modified": "Zuletzt geändert",
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)", "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
"mailbox": "Mailbox bearbeiten", "mailbox": "Mailbox bearbeiten",
"mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_quota_def": "Standard-Quota einer Mailbox",
"mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.", "mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.",

View File

@@ -177,10 +177,12 @@
"empty": "No results", "empty": "No results",
"excludes": "Excludes these recipients", "excludes": "Excludes these recipients",
"f2b_ban_time": "Ban time (s)", "f2b_ban_time": "Ban time (s)",
"f2b_ban_time_increment": "Ban time is incremented with each ban",
"f2b_blacklist": "Blacklisted networks/hosts", "f2b_blacklist": "Blacklisted networks/hosts",
"f2b_filter": "Regex filters", "f2b_filter": "Regex filters",
"f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>", "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
"f2b_max_attempts": "Max. attempts", "f2b_max_attempts": "Max. attempts",
"f2b_max_ban_time": "Max. ban time (s)",
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
"f2b_parameters": "Fail2ban parameters", "f2b_parameters": "Fail2ban parameters",
@@ -216,7 +218,7 @@
"loading": "Please wait...", "loading": "Please wait...",
"login_time": "Login time", "login_time": "Login time",
"logo_info": "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.", "logo_info": "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.",
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)", "lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
"main_name": "\"mailcow UI\" name", "main_name": "\"mailcow UI\" name",
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.", "merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
"message": "Message", "message": "Message",
@@ -496,6 +498,7 @@
} }
}, },
"debug": { "debug": {
"architecture": "Architecture",
"chart_this_server": "Chart (this server)", "chart_this_server": "Chart (this server)",
"containers_info": "Container information", "containers_info": "Container information",
"container_running": "Running", "container_running": "Running",
@@ -532,7 +535,8 @@
"update_available": "There is an update available", "update_available": "There is an update available",
"no_update_available": "The System is on the latest version", "no_update_available": "The System is on the latest version",
"update_failed": "Could not check for an Update", "update_failed": "Could not check for an Update",
"username": "Username" "username": "Username",
"wip": "Currently Work in Progress"
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.", "cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.",
@@ -591,7 +595,7 @@
"inactive": "Inactive", "inactive": "Inactive",
"kind": "Kind", "kind": "Kind",
"last_modified": "Last modified", "last_modified": "Last modified",
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)", "lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
"mailbox": "Edit mailbox", "mailbox": "Edit mailbox",
"mailbox_quota_def": "Default mailbox quota", "mailbox_quota_def": "Default mailbox quota",
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.", "mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",

View File

@@ -141,9 +141,11 @@
"empty": "Sin resultados", "empty": "Sin resultados",
"excludes": "Excluye a estos destinatarios", "excludes": "Excluye a estos destinatarios",
"f2b_ban_time": "Tiempo de restricción (s)", "f2b_ban_time": "Tiempo de restricción (s)",
"f2b_ban_time_increment": "Tiempo de restricción se incrementa con cada restricción",
"f2b_blacklist": "Redes y hosts en lista negra", "f2b_blacklist": "Redes y hosts en lista negra",
"f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>", "f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
"f2b_max_attempts": "Max num. de intentos", "f2b_max_attempts": "Max num. de intentos",
"f2b_max_ban_time": "Max tiempo de restricción (s)",
"f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)", "f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)",
"f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)", "f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)",
"f2b_parameters": "Parametros Fail2ban", "f2b_parameters": "Parametros Fail2ban",

View File

@@ -172,11 +172,13 @@
"edit": "Editer", "edit": "Editer",
"empty": "Aucun résultat", "empty": "Aucun résultat",
"excludes": "Exclure ces destinataires", "excludes": "Exclure ces destinataires",
"f2b_ban_time": "Durée du bannissement(s)", "f2b_ban_time": "Durée du bannissement (s)",
"f2b_ban_time_increment": "Durée du bannissement est augmentée à chaque bannissement",
"f2b_blacklist": "Réseaux/Domaines sur Liste Noire", "f2b_blacklist": "Réseaux/Domaines sur Liste Noire",
"f2b_filter": "Filtre(s) Regex", "f2b_filter": "Filtre(s) Regex",
"f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. <b>L'application des mises à jour de liste prendra quelques secondes.</b>", "f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. <b>L'application des mises à jour de liste prendra quelques secondes.</b>",
"f2b_max_attempts": "Nb max. de tentatives", "f2b_max_attempts": "Nb max. de tentatives",
"f2b_max_ban_time": "Max. durée du bannissement (s)",
"f2b_netban_ipv4": "Taille du sous-réseau IPv4 pour l'application du bannissement (8-32)", "f2b_netban_ipv4": "Taille du sous-réseau IPv4 pour l'application du bannissement (8-32)",
"f2b_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)", "f2b_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)",
"f2b_parameters": "Paramètres Fail2ban", "f2b_parameters": "Paramètres Fail2ban",
@@ -586,7 +588,7 @@
"unchanged_if_empty": "Si non modifié, laisser en blanc", "unchanged_if_empty": "Si non modifié, laisser en blanc",
"username": "Nom d'utilisateur", "username": "Nom d'utilisateur",
"validate_save": "Valider et sauver", "validate_save": "Valider et sauver",
"lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (<code>.*google\\.com</code> pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut)", "lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (<code>.*\\.google\\.com</code> pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut)",
"mailbox_relayhost_info": "S'applique uniquement à la boîte aux lettres et aux alias directs, remplace le relayhost du domaine." "mailbox_relayhost_info": "S'applique uniquement à la boîte aux lettres et aux alias directs, remplace le relayhost du domaine."
}, },
"footer": { "footer": {

View File

@@ -175,10 +175,12 @@
"empty": "Nessun risultato", "empty": "Nessun risultato",
"excludes": "Esclude questi destinatari", "excludes": "Esclude questi destinatari",
"f2b_ban_time": "Tempo di blocco (s)", "f2b_ban_time": "Tempo di blocco (s)",
"f2b_ban_time_increment": "Tempo di blocco aumenta ad ogni blocco",
"f2b_blacklist": "Host/reti in blacklist", "f2b_blacklist": "Host/reti in blacklist",
"f2b_filter": "Filtri Regex", "f2b_filter": "Filtri Regex",
"f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. <b>L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.</b>", "f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. <b>L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.</b>",
"f2b_max_attempts": "Tentativi massimi", "f2b_max_attempts": "Tentativi massimi",
"f2b_max_ban_time": "Tempo massimo di blocco (s)",
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
"f2b_parameters": "Parametri Fail2ban", "f2b_parameters": "Parametri Fail2ban",
@@ -211,7 +213,7 @@
"loading": "Caricamento in corso...", "loading": "Caricamento in corso...",
"login_time": "Ora di accesso", "login_time": "Ora di accesso",
"logo_info": "La tua immagine verrà ridimensionata a 40px di altezza, quando verrà usata nella barra di navigazione in alto, ed ad una larghezza massima di 250px nella schermata iniziale. È altamente consigliato l'utilizzo di un'immagine modulabile.", "logo_info": "La tua immagine verrà ridimensionata a 40px di altezza, quando verrà usata nella barra di navigazione in alto, ed ad una larghezza massima di 250px nella schermata iniziale. È altamente consigliato l'utilizzo di un'immagine modulabile.",
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)", "lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
"main_name": "Nome \"mailcow UI\"", "main_name": "Nome \"mailcow UI\"",
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.", "merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
"message": "Messaggio", "message": "Messaggio",
@@ -552,7 +554,7 @@
"hostname": "Hostname", "hostname": "Hostname",
"inactive": "Inattivo", "inactive": "Inattivo",
"kind": "Genere", "kind": "Genere",
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)", "lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
"mailbox": "Modifica casella di posta", "mailbox": "Modifica casella di posta",
"mailbox_quota_def": "Default mailbox quota", "mailbox_quota_def": "Default mailbox quota",
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.", "mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",

View File

@@ -168,10 +168,12 @@
"empty": "Geen resultaten", "empty": "Geen resultaten",
"excludes": "Exclusief", "excludes": "Exclusief",
"f2b_ban_time": "Verbanningstijd (s)", "f2b_ban_time": "Verbanningstijd (s)",
"f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning",
"f2b_blacklist": "Netwerken/hosts op de blacklist", "f2b_blacklist": "Netwerken/hosts op de blacklist",
"f2b_filter": "Regex-filters", "f2b_filter": "Regex-filters",
"f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. <b>Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.</b>", "f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. <b>Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.</b>",
"f2b_max_attempts": "Maximaal aantal pogingen", "f2b_max_attempts": "Maximaal aantal pogingen",
"f2b_max_ban_time": "Maximaal verbanningstijd (s)",
"f2b_netban_ipv4": "Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)", "f2b_netban_ipv4": "Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)",
"f2b_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)", "f2b_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)",
"f2b_parameters": "Fail2ban", "f2b_parameters": "Fail2ban",

View File

@@ -539,7 +539,7 @@
"inactive": "Inactiv", "inactive": "Inactiv",
"kind": "Fel", "kind": "Fel",
"last_modified": "Ultima modificare", "last_modified": "Ultima modificare",
"lookup_mx": "Destinația este o expresie regulată care potrivită cu numele MX (<code>.*google\\.com</code> pentru a direcționa toate e-mailurile vizate către un MX care se termină în google.com peste acest hop)", "lookup_mx": "Destinația este o expresie regulată care potrivită cu numele MX (<code>.*\\.google\\.com</code> pentru a direcționa toate e-mailurile vizate către un MX care se termină în google.com peste acest hop)",
"mailbox": "Editează căsuța poștală", "mailbox": "Editează căsuța poștală",
"mailbox_quota_def": "Cota implicită a căsuței poștale", "mailbox_quota_def": "Cota implicită a căsuței poștale",
"mailbox_relayhost_info": "Aplicat numai căsuței poștale și aliasurilor directe, suprascrie un transport dependent de domeniu.", "mailbox_relayhost_info": "Aplicat numai căsuței poștale și aliasurilor directe, suprascrie un transport dependent de domeniu.",

View File

@@ -336,7 +336,9 @@
"validate_license_now": "Получить лицензию на основе GUID с сервера лицензий", "validate_license_now": "Получить лицензию на основе GUID с сервера лицензий",
"verify": "Проверить", "verify": "Проверить",
"yes": "&#10003;", "yes": "&#10003;",
"queue_unban": "разблокировать" "queue_unban": "разблокировать",
"f2b_ban_time_increment": "Время бана увеличивается с каждым баном",
"f2b_max_ban_time": "Максимальное время блокировки"
}, },
"danger": { "danger": {
"access_denied": "Доступ запрещён, или указаны неверные данные", "access_denied": "Доступ запрещён, или указаны неверные данные",

View File

@@ -213,7 +213,7 @@
"loading": "Čakajte prosím ...", "loading": "Čakajte prosím ...",
"login_time": "Čas prihlásenia", "login_time": "Čas prihlásenia",
"logo_info": "Váš obrázok bude upravený na výšku 40px pre vrchný navigačný riadok a na maximálnu šírku 250px pre úvodnú stránku. Odporúča sa škálovateľná grafika.", "logo_info": "Váš obrázok bude upravený na výšku 40px pre vrchný navigačný riadok a na maximálnu šírku 250px pre úvodnú stránku. Odporúča sa škálovateľná grafika.",
"lookup_mx": "Cieľ je regulárny výraz ktorý sa porovnáva s MX záznamom (<code>.*google\\.com</code> smeruje všetku poštu určenú pre MX ktoré sú cieľom pre google.com cez tento skok)", "lookup_mx": "Cieľ je regulárny výraz ktorý sa porovnáva s MX záznamom (<code>.*\\.google\\.com</code> smeruje všetku poštu určenú pre MX ktoré sú cieľom pre google.com cez tento skok)",
"main_name": "\"mailcow UI\" názov", "main_name": "\"mailcow UI\" názov",
"merged_vars_hint": "Sivé riadky boli načítané z <code>vars.(local.)inc.php</code> a nemôžu byť modifikované cez UI.", "merged_vars_hint": "Sivé riadky boli načítané z <code>vars.(local.)inc.php</code> a nemôžu byť modifikované cez UI.",
"message": "Správa", "message": "Správa",
@@ -539,7 +539,7 @@
"inactive": "Neaktívny", "inactive": "Neaktívny",
"kind": "Druh", "kind": "Druh",
"last_modified": "Naposledy upravené", "last_modified": "Naposledy upravené",
"lookup_mx": "Cieľ je regulárny výraz ktorý sa zhoduje s MX záznamom (<code>.*google\\.com</code> smeruje všetku poštu na MX ktoré sú cieľom pre google.com cez tento skok)", "lookup_mx": "Cieľ je regulárny výraz ktorý sa zhoduje s MX záznamom (<code>.*\\.google\\.com</code> smeruje všetku poštu na MX ktoré sú cieľom pre google.com cez tento skok)",
"mailbox": "Upraviť mailovú schránku", "mailbox": "Upraviť mailovú schránku",
"mailbox_quota_def": "Predvolená veľkosť mailovej schránky", "mailbox_quota_def": "Predvolená veľkosť mailovej schránky",
"mailbox_relayhost_info": "Aplikované len na používateľské schránky a priame aliasy, prepisuje doménového preposielateľa.", "mailbox_relayhost_info": "Aplikované len na používateľské schránky a priame aliasy, prepisuje doménového preposielateľa.",

View File

@@ -213,7 +213,7 @@
"loading": "请等待...", "loading": "请等待...",
"login_time": "登录时间", "login_time": "登录时间",
"logo_info": "你的图片将会在顶部导航栏被缩放为 40px 高,在起始页被缩放为最大 250px 高。强烈推荐使用能较好适应缩放的图片。", "logo_info": "你的图片将会在顶部导航栏被缩放为 40px 高,在起始页被缩放为最大 250px 高。强烈推荐使用能较好适应缩放的图片。",
"lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)", "lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*\\.google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)",
"main_name": "Mailcow UI 的名称", "main_name": "Mailcow UI 的名称",
"merged_vars_hint": "灰色行来自 <code>vars.(local.)inc.php</code> 文件并且无法修改。", "merged_vars_hint": "灰色行来自 <code>vars.(local.)inc.php</code> 文件并且无法修改。",
"message": "消息", "message": "消息",
@@ -544,7 +544,7 @@
"hostname": "主机名", "hostname": "主机名",
"inactive": "禁用", "inactive": "禁用",
"kind": "类型", "kind": "类型",
"lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)", "lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*\\.google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)",
"mailbox": "编辑邮箱", "mailbox": "编辑邮箱",
"mailbox_quota_def": "邮箱默认配额", "mailbox_quota_def": "邮箱默认配额",
"mailbox_relayhost_info": "只适用于邮箱和邮箱别名,不会覆盖域名的中继主机。", "mailbox_relayhost_info": "只适用于邮箱和邮箱别名,不会覆盖域名的中继主机。",

View File

@@ -213,7 +213,7 @@
"loading": "請稍等...", "loading": "請稍等...",
"login_time": "登入時間", "login_time": "登入時間",
"logo_info": "你的起始頁面圖片會在頂部導覽列的限制下被縮放為 40px 高,以及最大 250px 高度。強烈推薦使用能較好縮放的圖片。", "logo_info": "你的起始頁面圖片會在頂部導覽列的限制下被縮放為 40px 高,以及最大 250px 高度。強烈推薦使用能較好縮放的圖片。",
"lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)", "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*\\.google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
"main_name": "\"mailcow UI\" 名稱", "main_name": "\"mailcow UI\" 名稱",
"merged_vars_hint": "灰色列來自 <code>vars.(local.)inc.php</code> 並且不能修改。", "merged_vars_hint": "灰色列來自 <code>vars.(local.)inc.php</code> 並且不能修改。",
"message": "訊息", "message": "訊息",
@@ -540,7 +540,7 @@
"inactive": "停用", "inactive": "停用",
"kind": "種類", "kind": "種類",
"last_modified": "上次修改時間", "last_modified": "上次修改時間",
"lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)", "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*\\.google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
"mailbox": "編輯信箱", "mailbox": "編輯信箱",
"mailbox_quota_def": "預設信箱容量配額", "mailbox_quota_def": "預設信箱容量配額",
"mailbox_relayhost_info": "只會套用於信箱和直接別名,不會覆寫域名中繼主機。", "mailbox_relayhost_info": "只會套用於信箱和直接別名,不會覆寫域名中繼主機。",

View File

@@ -60,7 +60,7 @@ elseif (isset($_GET['login'])) {
':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
)); ));
// redirect to sogo (sogo will get the correct credentials via nginx auth_request // redirect to sogo (sogo will get the correct credentials via nginx auth_request
header("Location: /SOGo/so/${login}"); header("Location: /SOGo/so/{$login}");
exit; exit;
} }
} }

View File

@@ -12,6 +12,14 @@
<label for="f2b_ban_time">{{ lang.admin.f2b_ban_time }}:</label> <label for="f2b_ban_time">{{ lang.admin.f2b_ban_time }}:</label>
<input type="number" class="form-control" id="f2b_ban_time" name="ban_time" value="{{ f2b_data.ban_time }}" required> <input type="number" class="form-control" id="f2b_ban_time" name="ban_time" value="{{ f2b_data.ban_time }}" required>
</div> </div>
<div class="mb-4">
<label for="f2b_max_ban_time">{{ lang.admin.f2b_max_ban_time }}:</label>
<input type="number" class="form-control" id="f2b_max_ban_time" name="max_ban_time" value="{{ f2b_data.max_ban_time }}" required>
</div>
<div class="mb-4">
<input class="form-check-input" type="checkbox" value="1" name="ban_time_increment" id="f2b_ban_time_increment" {% if f2b_data.ban_time_increment == 1 %}checked{% endif %}>
<label class="form-check-label" for="f2b_ban_time_increment">{{ lang.admin.f2b_ban_time_increment }}</label>
</div>
<div class="mb-4"> <div class="mb-4">
<label for="f2b_max_attempts">{{ lang.admin.f2b_max_attempts }}:</label> <label for="f2b_max_attempts">{{ lang.admin.f2b_max_attempts }}:</label>
<input type="number" class="form-control" id="f2b_max_attempts" name="max_attempts" value="{{ f2b_data.max_attempts }}" required> <input type="number" class="form-control" id="f2b_max_attempts" name="max_attempts" value="{{ f2b_data.max_attempts }}" required>

View File

@@ -49,6 +49,12 @@
<p><b>{{ hostname }}</b></p> <p><b>{{ hostname }}</b></p>
</div></td> </div></td>
</tr> </tr>
<tr>
<td>{{ lang.debug.architecture }}</td>
<td class="text-break"><div>
<p id="host_architecture">-</p>
</div></td>
</tr>
<tr> <tr>
<td>IPs</td> <td>IPs</td>
<td class="text-break"> <td class="text-break">
@@ -70,7 +76,7 @@
<td>Version</td> <td>Version</td>
<td class="text-break"> <td class="text-break">
<div class="fw-bolder"> <div class="fw-bolder">
<p ><a href="#" id="maiclow_version">{{ mailcow_info.version_tag }}</a></p> <p ><a href="#" id="mailcow_version">{{ mailcow_info.version_tag }}</a></p>
<p id="mailcow_update"></p> <p id="mailcow_update"></p>
</div> </div>
</td> </td>

View File

@@ -109,25 +109,25 @@
<label class="control-label col-sm-2">{{ lang.user.quarantine_notification }}</label> <label class="control-label col-sm-2">{{ lang.user.quarantine_notification }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="btn-group" data-acl="{{ acl.quarantine_notification }}"> <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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'never' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="quarantine_notification" data-id="quarantine_notification"
data-api-url='edit/quarantine_notification' data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"never"}'>{{ lang.user.never }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'hourly' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="quarantine_notification" data-id="quarantine_notification"
data-api-url='edit/quarantine_notification' data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"hourly"}'>{{ lang.user.hourly }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'daily' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="quarantine_notification" data-id="quarantine_notification"
data-api-url='edit/quarantine_notification' data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"daily"}'>{{ lang.user.daily }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'weekly' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="quarantine_notification" data-id="quarantine_notification"
@@ -141,19 +141,19 @@
<label class="control-label col-sm-2">{{ lang.user.quarantine_category }}</label> <label class="control-label col-sm-2">{{ lang.user.quarantine_category }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="btn-group" data-acl="{{ acl.quarantine_category }}"> <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 %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'reject' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="quarantine_category" data-id="quarantine_category"
data-api-url='edit/quarantine_category' data-api-url='edit/quarantine_category'
data-api-attr='{"quarantine_category":"reject"}'>{{ lang.user.q_reject }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'add_header' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="quarantine_category" data-id="quarantine_category"
data-api-url='edit/quarantine_category' data-api-url='edit/quarantine_category'
data-api-attr='{"quarantine_category":"add_header"}'>{{ lang.user.q_add_header }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'all' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="quarantine_category" data-id="quarantine_category"
@@ -167,13 +167,13 @@
<label class="control-label col-sm-2" for="sender_acl">{{ lang.user.tls_policy }}</label> <label class="control-label col-sm-2" for="sender_acl">{{ lang.user.tls_policy }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="btn-group" data-acl="{{ acl.tls_policy }}"> <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 %}" <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_in == '1' %} btn-dark"{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="tls_policy" data-id="tls_policy"
data-api-url='edit/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> 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 %}" <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_out == '1' %} btn-dark"{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailbox }}" data-item="{{ mailbox }}"
data-id="tls_policy" data-id="tls_policy"

View File

@@ -54,6 +54,7 @@
<li class="dropdown-header">SMTP</li> <li class="dropdown-header">SMTP</li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":1}' href="#">{{ lang.mailbox.activate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":1}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li>
<li class="dropdown-header">Sieve</li> <li class="dropdown-header">Sieve</li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":1}' href="#">{{ lang.mailbox.activate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":1}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li> <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li>

View File

@@ -12,11 +12,21 @@
<li><button class="dropdown-item" role="tab" aria-selected="false" aria-controls="tab-config-f2b" data-bs-toggle="tab" data-bs-target="#tab-user-settings">{{ lang.user.mailbox_settings }}</button></li> <li><button class="dropdown-item" role="tab" aria-selected="false" aria-controls="tab-config-f2b" data-bs-toggle="tab" data-bs-target="#tab-user-settings">{{ lang.user.mailbox_settings }}</button></li>
</ul> </ul>
</li> </li>
{% if acl.spam_alias == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="SpamAliases" role="tab" data-bs-toggle="tab" data-bs-target="#SpamAliases">{{ lang.user.spam_aliases }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="SpamAliases" role="tab" data-bs-toggle="tab" data-bs-target="#SpamAliases">{{ lang.user.spam_aliases }}</button></li>
{% endif %}
{% if acl.spam_score == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Spamfilter" role="tab" data-bs-toggle="tab" data-bs-target="#Spamfilter">{{ lang.user.spamfilter }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Spamfilter" role="tab" data-bs-toggle="tab" data-bs-target="#Spamfilter">{{ lang.user.spamfilter }}</button></li>
{% endif %}
{% if acl.syncjobs == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#Syncjobs">{{ lang.user.sync_jobs }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#Syncjobs">{{ lang.user.sync_jobs }}</button></li>
{% endif %}
{% if acl.app_passwds == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="AppPasswds" role="tab" data-bs-toggle="tab" data-bs-target="#AppPasswds">{{ lang.user.app_passwds }}</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="AppPasswds" role="tab" data-bs-toggle="tab" data-bs-target="#AppPasswds">{{ lang.user.app_passwds }}</button></li>
{% endif %}
{% if acl.pushover == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Pushover" role="tab" data-bs-toggle="tab" data-bs-target="#Pushover">Pushover API</button></li> <li class="nav-item" role="presentation"><button class="nav-link" role="tab" aria-selected="false" aria-controls="Pushover" role="tab" data-bs-toggle="tab" data-bs-target="#Pushover">Pushover API</button></li>
{% endif %}
</ul> </ul>
<div class="row"> <div class="row">
@@ -25,11 +35,11 @@
{% include 'user/tab-user-auth.twig' %} {% include 'user/tab-user-auth.twig' %}
{% include 'user/tab-user-details.twig' %} {% include 'user/tab-user-details.twig' %}
{% include 'user/tab-user-settings.twig' %} {% include 'user/tab-user-settings.twig' %}
{% include 'user/SpamAliases.twig' %} {% if acl.spam_alias == 1 %}{% include 'user/SpamAliases.twig' %}{% endif %}
{% include 'user/Spamfilter.twig' %} {% if acl.spam_score == 1 %}{% include 'user/Spamfilter.twig' %}{% endif %}
{% include 'user/Syncjobs.twig' %} {% if acl.syncjobs == 1 %}{% include 'user/Syncjobs.twig' %}{% endif %}
{% include 'user/AppPasswds.twig' %} {% if acl.app_passwds == 1 %}{% include 'user/AppPasswds.twig' %}{% endif %}
{% include 'user/Pushover.twig' %} {% if acl.pushover == 1 %}{% include 'user/Pushover.twig' %}{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,19 +12,19 @@
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tag_handling }}:</div> <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tag_handling }}:</div>
<div class="col-sm-9 col-12"> <div class="col-sm-9 col-12">
<div class="btn-group" data-acl="{{ acl.delimiter_action }}"> <div class="btn-group" data-acl="{{ acl.delimiter_action }}">
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'subfolder' %} active{% endif %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'subfolder' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="delimiter_action" data-id="delimiter_action"
data-api-url='edit/delimiter_action' data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subfolder"}'>{{ lang.user.tag_in_subfolder }}</button> data-api-attr='{"tagged_mail_handler":"subfolder"}'>{{ lang.user.tag_in_subfolder }}</button>
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'subject' %} active{% endif %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'subject' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="delimiter_action" data-id="delimiter_action"
data-api-url='edit/delimiter_action' data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subject"}'>{{ lang.user.tag_in_subject }}</button> data-api-attr='{"tagged_mail_handler":"subject"}'>{{ lang.user.tag_in_subject }}</button>
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'none' %} active{% endif %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'none' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="delimiter_action" data-id="delimiter_action"
@@ -40,13 +40,13 @@
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tls_policy }}:</div> <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tls_policy }}:</div>
<div class="col-sm-9 col-12"> <div class="col-sm-9 col-12">
<div class="btn-group" data-acl="{{ acl.tls_policy }}"> <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 %}" <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_in == '1' %} btn-dark"{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="tls_policy" data-id="tls_policy"
data-api-url='edit/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> 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 %}" <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_out == '1' %} btn-dark"{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="tls_policy" data-id="tls_policy"
@@ -61,25 +61,25 @@
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_notification }}:</div> <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_notification }}:</div>
<div class="col-sm-9 col-12"> <div class="col-sm-9 col-12">
<div class="btn-group" data-acl="{{ acl.quarantine_notification }}"> <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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'never' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="quarantine_notification" data-id="quarantine_notification"
data-api-url='edit/quarantine_notification' data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"never"}'>{{ lang.user.never }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'hourly' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="quarantine_notification" data-id="quarantine_notification"
data-api-url='edit/quarantine_notification' data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"hourly"}'>{{ lang.user.hourly }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'daily' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="quarantine_notification" data-id="quarantine_notification"
data-api-url='edit/quarantine_notification' data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"daily"}'>{{ lang.user.daily }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'weekly' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="quarantine_notification" data-id="quarantine_notification"
@@ -93,19 +93,19 @@
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_category }}:</div> <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_category }}:</div>
<div class="col-sm-9 col-12"> <div class="col-sm-9 col-12">
<div class="btn-group" data-acl="{{ acl.quarantine_category }}"> <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 %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'reject' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="quarantine_category" data-id="quarantine_category"
data-api-url='edit/quarantine_category' data-api-url='edit/quarantine_category'
data-api-attr='{"quarantine_category":"reject"}'>{{ lang.user.q_reject }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'add_header' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="quarantine_category" data-id="quarantine_category"
data-api-url='edit/quarantine_category' data-api-url='edit/quarantine_category'
data-api-attr='{"quarantine_category":"add_header"}'>{{ lang.user.q_add_header }}</button> 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 %}" <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'all' %} btn-dark{% endif %}"
data-action="edit_selected" data-action="edit_selected"
data-item="{{ mailcow_cc_username }}" data-item="{{ mailcow_cc_username }}"
data-id="quarantine_category" data-id="quarantine_category"

View File

@@ -76,7 +76,7 @@ services:
- clamd - clamd
rspamd-mailcow: rspamd-mailcow:
image: mailcow/rspamd:1.93 image: mailcow/rspamd:1.92
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow
@@ -106,7 +106,7 @@ services:
- rspamd - rspamd
php-fpm-mailcow: php-fpm-mailcow:
image: mailcow/phpfpm:1.83 image: mailcow/phpfpm:1.84
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: depends_on:
- redis-mailcow - redis-mailcow
@@ -169,7 +169,7 @@ services:
- phpfpm - phpfpm
sogo-mailcow: sogo-mailcow:
image: mailcow/sogo:1.115 image: mailcow/sogo:1.117
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
@@ -425,7 +425,7 @@ services:
- acme - acme
netfilter-mailcow: netfilter-mailcow:
image: mailcow/netfilter:1.51 image: mailcow/netfilter:1.52
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow
@@ -510,7 +510,7 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: mailcow/dockerapi:2.01 image: mailcow/dockerapi:2.04
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always

View File

@@ -261,7 +261,7 @@ COMPOSE_PROJECT_NAME=mailcowdockerized
# Switch here between native (compose plugin) and standalone # Switch here between native (compose plugin) and standalone
# For more informations take a look at the mailcow docs regarding the configuration options. # For more informations take a look at the mailcow docs regarding the configuration options.
# Normally this should be untouched but if you decided to use either of those you can switch it manually here. # Normally this should be untouched but if you decided to use either of those you can switch it manually here.
# Please be aware that at least one of those variants should be installed on your maschine or mailcow will fail. # Please be aware that at least one of those variants should be installed on your machine or mailcow will fail.
DOCKER_COMPOSE_VERSION=${COMPOSE_VERSION} DOCKER_COMPOSE_VERSION=${COMPOSE_VERSION}

View File

@@ -26,6 +26,6 @@ services:
- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
mysql-mailcow: mysql-mailcow:
image: alpine:3.17 image: alpine:3.18
command: /bin/true command: /bin/true
restart: "no" restart: "no"

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$ # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
NEXTCLOUD_VERSION=26.0.0 NEXTCLOUD_VERSION=26.0.1
echo -ne "Checking prerequisites..." echo -ne "Checking prerequisites..."
sleep 1 sleep 1
@@ -97,8 +97,12 @@ elif [[ ${NC_UPDATE} == "y" ]]; then
echo -e "\033[31mError: Nextcloud occ not found. Is Nextcloud installed?\033[0m" echo -e "\033[31mError: Nextcloud occ not found. Is Nextcloud installed?\033[0m"
exit 1 exit 1
fi fi
if ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then if grep -Pq 'This version of Nextcloud is not compatible with (?:PHP)?(?>=?)(?:PHP)?(?>.+)' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
echo "Nextcloud seems not to be installed." echo -e "\033[31mError: This version of Nextcloud is not compatible with the current PHP version of php-fpm-mailcow, we'll fix it\033[0m"
wget -q https://raw.githubusercontent.com/nextcloud/server/v26.0.0/lib/versioncheck.php -O ./data/web/nextcloud/lib/versioncheck.php
echo -e "\e[33mPlease restart the update again.\e[0m"
elif ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
echo -e "\033[31mError: Nextcloud seems not to be installed.\033[0m"
exit 1 exit 1
else else
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "php /web/nextcloud/updater/updater.phar" docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "php /web/nextcloud/updater/updater.phar"

View File

@@ -6,7 +6,7 @@ SPFTOOLS_DIR=${WORKING_DIR}/spf-tools
POSTWHITE_DIR=${WORKING_DIR}/postwhite POSTWHITE_DIR=${WORKING_DIR}/postwhite
POSTWHITE_CONF=${POSTWHITE_DIR}/postwhite.conf POSTWHITE_CONF=${POSTWHITE_DIR}/postwhite.conf
COSTOM_HOSTS="web.de gmx.net mail.de freenet.de arcor.de unity-mail.de" CUSTOM_HOSTS='"web.de gmx.net mail.de freenet.de arcor.de unity-mail.de"'
STATIC_HOSTS=( STATIC_HOSTS=(
"194.25.134.0/24 permit # t-online.de" "194.25.134.0/24 permit # t-online.de"
) )
@@ -19,13 +19,20 @@ function set_config() {
sudo sed -i "s@^\($1\s*=\s*\).*\$@\1$2@" ${POSTWHITE_CONF} sudo sed -i "s@^\($1\s*=\s*\).*\$@\1$2@" ${POSTWHITE_CONF}
} }
set_config custom_hosts ${COSTOM_HOSTS} set_config custom_hosts "${CUSTOM_HOSTS}"
set_config reload_postfix no set_config reload_postfix no
set_config postfixpath /. set_config postfixpath /.
set_config spftoolspath ${WORKING_DIR}/spf-tools set_config spftoolspath ${WORKING_DIR}/spf-tools
set_config whitelist .${SCRIPT_DIR}/../data/conf/postfix/postscreen_access.cidr set_config whitelist .${SCRIPT_DIR}/../data/conf/postfix/postscreen_access.cidr
set_config yahoo_static_hosts ${POSTWHITE_DIR}/yahoo_static_hosts.txt set_config yahoo_static_hosts ${POSTWHITE_DIR}/yahoo_static_hosts.txt
#Fix URL for Yahoo!: https://github.com/stevejenkins/postwhite/issues/59
sudo sed -i \
-e 's#yahoo_url="https://help.yahoo.com/kb/SLN23997.html"#yahoo_url="https://senders.yahooinc.com/outbound-mail-servers/"#' \
-e 's#echo "ipv6:$line";#echo "ipv6:$line" | grep -v "ipv6:::";#' \
-e 's#`command -v wget`#`command -v skip-wget`#' \
${POSTWHITE_DIR}/scrape_yahoo
cd ${POSTWHITE_DIR} cd ${POSTWHITE_DIR}
./postwhite ${POSTWHITE_CONF} ./postwhite ${POSTWHITE_CONF}

View File

@@ -188,7 +188,7 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m" echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
else else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
exit 1 exit 1
fi fi
elif docker-compose > /dev/null 2>&1; then elif docker-compose > /dev/null 2>&1; then
@@ -203,14 +203,14 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
else else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
exit 1 exit 1
fi fi
fi fi
else else
echo -e "\e[31mCannot find Docker Compose.\e[0m" echo -e "\e[31mCannot find Docker Compose.\e[0m"
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
exit 1 exit 1
fi fi
@@ -223,7 +223,7 @@ elif [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then
if ! $COMPOSE_COMMAND > /dev/null 2>&1 || ! $COMPOSE_COMMAND --version | grep "^2." > /dev/null 2>&1; then if ! $COMPOSE_COMMAND > /dev/null 2>&1 || ! $COMPOSE_COMMAND --version | grep "^2." > /dev/null 2>&1; then
# IF it cannot find Standalone in > 2.X, then script stops # IF it cannot find Standalone in > 2.X, then script stops
echo -e "\e[31mCannot find Docker Compose or the Version is lower then 2.X.X.\e[0m" echo -e "\e[31mCannot find Docker Compose or the Version is lower then 2.X.X.\e[0m"
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
exit 1 exit 1
fi fi
# If it finds the standalone Plugin it will use this instead and change the mailcow.conf Variable accordingly # If it finds the standalone Plugin it will use this instead and change the mailcow.conf Variable accordingly
@@ -243,7 +243,7 @@ elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
if ! $COMPOSE_COMMAND > /dev/null 2>&1; then if ! $COMPOSE_COMMAND > /dev/null 2>&1; then
# IF it cannot find Native in > 2.X, then script stops # IF it cannot find Native in > 2.X, then script stops
echo -e "\e[31mCannot find Docker Compose.\e[0m" echo -e "\e[31mCannot find Docker Compose.\e[0m"
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
exit 1 exit 1
fi fi
# If it finds the native Plugin it will use this instead and change the mailcow.conf Variable accordingly # If it finds the native Plugin it will use this instead and change the mailcow.conf Variable accordingly