diff --git a/.github/workflows/close_old_issues_and_prs.yml b/.github/workflows/close_old_issues_and_prs.yml index 64002617..21ab3a8e 100644 --- a/.github/workflows/close_old_issues_and_prs.yml +++ b/.github/workflows/close_old_issues_and_prs.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - name: Mark/Close Stale Issues and Pull Requests 🗑️ - uses: actions/stale@v7.0.0 + uses: actions/stale@v8.0.0 with: repo-token: ${{ secrets.STALE_ACTION_PAT }} days-before-stale: 60 diff --git a/.github/workflows/pr_to_nightly.yml b/.github/workflows/pr_to_nightly.yml index 54dbda34..57aac781 100644 --- a/.github/workflows/pr_to_nightly.yml +++ b/.github/workflows/pr_to_nightly.yml @@ -12,7 +12,7 @@ jobs: with: fetch-depth: 0 - name: Run the Action - uses: devops-infra/action-pull-request@v0.5.3 + uses: devops-infra/action-pull-request@v0.5.5 with: github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }} title: Automatic PR to nightly from ${{ github.event.repository.updated_at}} diff --git a/data/Dockerfiles/clamd/Dockerfile b/data/Dockerfiles/clamd/Dockerfile index 91716b84..f381e0ef 100644 --- a/data/Dockerfiles/clamd/Dockerfile +++ b/data/Dockerfiles/clamd/Dockerfile @@ -1,4 +1,4 @@ -FROM clamav/clamav:1.0_base +FROM clamav/clamav:1.0.1-1_base LABEL maintainer "André Peters " diff --git a/data/Dockerfiles/dockerapi/dockerapi.py b/data/Dockerfiles/dockerapi/dockerapi.py index 9e699c22..7edb2e08 100644 --- a/data/Dockerfiles/dockerapi/dockerapi.py +++ b/data/Dockerfiles/dockerapi/dockerapi.py @@ -9,6 +9,7 @@ import os import json import asyncio import redis +import platform from datetime import datetime import logging from logging.config import dictConfig @@ -370,7 +371,7 @@ class DockerUtils: return exec_run_handler('utf8_text_only', sieve_return) # api call: container_post - post_action: exec - cmd: sieve - task: print 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}): 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) @@ -380,7 +381,15 @@ class DockerUtils: if 'maildir' in request_json: for container in self.docker_client.containers.list(filters={"id": container_id}): 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') return exec_run_handler('generic', maildir_cleanup) # 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() }, "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) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 9d0789b3..0abf6bae 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -21,6 +21,7 @@ RUN groupadd -g 5000 vmail \ && touch /etc/default/locale \ && apt-get update \ && apt-get -y --no-install-recommends install \ + build-essential \ apt-transport-https \ ca-certificates \ cpanminus \ @@ -61,6 +62,7 @@ RUN groupadd -g 5000 vmail \ libproc-processtable-perl \ libreadonly-perl \ libregexp-common-perl \ + libssl-dev \ libsys-meminfo-perl \ libterm-readkey-perl \ libtest-deep-perl \ @@ -112,6 +114,8 @@ RUN groupadd -g 5000 vmail \ && apt-get autoclean \ && rm -rf /var/lib/apt/lists/* \ && rm -rf /tmp/* /var/tmp/* /root/.cache/ +# imapsync dependencies +RUN cpan Crypt::OpenSSL::PKCS12 COPY trim_logs.sh /usr/local/bin/trim_logs.sh COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh diff --git a/data/Dockerfiles/dovecot/imapsync b/data/Dockerfiles/dovecot/imapsync index 0d34504e..de63d658 100755 --- a/data/Dockerfiles/dovecot/imapsync +++ b/data/Dockerfiles/dovecot/imapsync @@ -8492,6 +8492,7 @@ sub xoauth2 require HTML::Entities ; require JSON ; require JSON::WebToken::Crypt::RSA ; + require Crypt::OpenSSL::PKCS12; require Crypt::OpenSSL::RSA ; require Encode::Byte ; require IO::Socket::SSL ; @@ -8532,8 +8533,9 @@ sub xoauth2 $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n"); - # Get private key from p12 file (would be better in perl...) - $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`; + # Get private key from p12 file + my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile); + $key = $pkcs12->private_key($keypass); $sync->{ debug } and myprint( "Private key:\n$key\n"); } diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 1ccc150e..698137bf 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -64,28 +64,40 @@ def refreshF2boptions(): global f2boptions global quit_now global exit_code + + f2boptions = {} + if not r.get('F2B_OPTIONS'): - f2boptions = {} - f2boptions['ban_time'] = int - f2boptions['max_attempts'] = int - f2boptions['retry_window'] = int - f2boptions['netban_ipv4'] = int - f2boptions['netban_ipv6'] = int - f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800 - f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 - f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 - f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 32 - f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 128 - r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) + f2boptions['ban_time'] = r.get('F2B_BAN_TIME') + f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') + f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') + f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') + f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') + f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') + f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') else: try: - f2boptions = {} f2boptions = json.loads(r.get('F2B_OPTIONS')) except ValueError: print('Error loading F2B options: F2B_OPTIONS is not json') quit_now = True 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(): global f2bregex global quit_now @@ -147,6 +159,7 @@ def ban(address): global lock refreshF2boptions() BAN_TIME = int(f2boptions['ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) MAX_ATTEMPTS = int(f2boptions['max_attempts']) RETRY_WINDOW = int(f2boptions['retry_window']) 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 = str(net) - if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW: - bans[net] = { 'attempts': 0 } - active_window = RETRY_WINDOW - else: - active_window = time.time() - bans[net]['last_attempt'] + if not net in bans: + bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0} bans[net]['attempts'] += 1 bans[net]['last_attempt'] = time.time() - active_window = time.time() - bans[net]['last_attempt'] - if bans[net]['attempts'] >= MAX_ATTEMPTS: 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: with lock: chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') @@ -206,7 +215,7 @@ def ban(address): rule.target = target if rule not in chain.rules: 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: 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_QUEUE_UNBAN', '%s' % net) if net in bans: - del bans[net] + bans[net]['attempts'] = 0 + bans[net]['ban_counter'] += 1 def permBan(net, unban=False): global lock @@ -332,7 +342,7 @@ def watch(): logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) ban(addr) except Exception as ex: - logWarn('Error reading log line from pubsub') + logWarn('Error reading log line from pubsub: %s' % ex) quit_now = True exit_code = 2 @@ -359,21 +369,30 @@ def snat4(snat_target): chain = iptc.Chain(table, 'POSTROUTING') table.autocommit = False new_rule = get_snat4_rule() - for position, rule in enumerate(chain.rules): - match = all(( - new_rule.get_src() == rule.get_src(), - new_rule.get_dst() == rule.get_dst(), - new_rule.target.parameters == rule.target.parameters, - new_rule.target.name == rule.target.name - )) - if position == 0: - if not match: - logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') - chain.insert_rule(new_rule) - else: - if match: - logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}') - chain.delete_rule(rule) + + if not chain.rules: + # if there are no rules in the chain, insert the new rule directly + logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') + chain.insert_rule(new_rule) + else: + for position, rule in enumerate(chain.rules): + if not hasattr(rule.target, 'parameter'): + continue + match = all(( + new_rule.get_src() == rule.get_src(), + new_rule.get_dst() == rule.get_dst(), + new_rule.target.parameters == rule.target.parameters, + new_rule.target.name == rule.target.name + )) + if position == 0: + if not match: + logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') + chain.insert_rule(new_rule) + else: + if match: + logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}') + chain.delete_rule(rule) + table.commit() table.autocommit = True except: @@ -418,6 +437,8 @@ def autopurge(): time.sleep(10) refreshF2boptions() 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']) QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') if QUEUE_UNBAN: @@ -425,7 +446,9 @@ def autopurge(): unban(str(net)) for net in bans.copy(): 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) def isIpNetwork(address): diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 05081c6d..0ff47206 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1-fpm-alpine3.17 +FROM php:8.2-fpm-alpine3.17 LABEL maintainer "Andre Peters " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced @@ -12,7 +12,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced ARG REDIS_PECL_VERSION=5.3.7 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced -ARG COMPOSER_VERSION=2.5.1 +ARG COMPOSER_VERSION=2.5.5 RUN apk add -U --no-cache autoconf \ aspell-dev \ @@ -52,6 +52,7 @@ RUN apk add -U --no-cache autoconf \ libxpm-dev \ libzip \ libzip-dev \ + linux-headers \ make \ mysql-client \ openldap-dev \ @@ -75,7 +76,7 @@ RUN apk add -U --no-cache autoconf \ --with-webp \ --with-xpm \ --with-avif \ - && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets zip bcmath gmp \ + && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets sysvsem zip bcmath gmp \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-install -j 4 imap \ && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \ @@ -99,6 +100,7 @@ RUN apk add -U --no-cache autoconf \ libxml2-dev \ libxpm-dev \ libzip-dev \ + linux-headers \ make \ openldap-dev \ pcre-dev \ diff --git a/data/assets/nextcloud/nextcloud.conf b/data/assets/nextcloud/nextcloud.conf index 3755c4a7..eda2c779 100644 --- a/data/assets/nextcloud/nextcloud.conf +++ b/data/assets/nextcloud/nextcloud.conf @@ -24,7 +24,7 @@ server { add_header X-Download-Options "noopen" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "none" always; + add_header X-Robots-Tag "noindex, nofollow" always; add_header X-XSS-Protection "1; mode=block" always; fastcgi_hide_header X-Powered-By; diff --git a/data/conf/rspamd/custom/bad_asn.map b/data/conf/rspamd/custom/bad_asn.map index 1858c55f..a8d49cf3 100644 --- a/data/conf/rspamd/custom/bad_asn.map +++ b/data/conf/rspamd/custom/bad_asn.map @@ -27,4 +27,5 @@ #197518 2 #Rackmarkt SL, Spain #197695 2 #Domain names registrar REG.RU Ltd, Russia #198068 2 #P.A.G.M. OU, Estonia -#201942 5 #Soltia Consulting SL, Spain \ No newline at end of file +#201942 5 #Soltia Consulting SL, Spain +#213373 4 #IP Connect Inc \ No newline at end of file diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index 337a2eb1..02ff955b 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -8,7 +8,7 @@ VIRUS_FOUND { } # Bad policy from free mail providers FREEMAIL_POLICY_FAILURE { - expression = "-g+:policies & !DMARC_POLICY_ALLOW & !MAILLIST & ( FREEMAIL_ENVFROM | FREEMAIL_FROM ) & !WHITELISTED_FWD_HOST"; + expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies"; score = 16.0; } # Applies to freemail with undisclosed recipients diff --git a/data/conf/rspamd/local.d/multimap.conf b/data/conf/rspamd/local.d/multimap.conf index 3f554c5b..888bf363 100644 --- a/data/conf/rspamd/local.d/multimap.conf +++ b/data/conf/rspamd/local.d/multimap.conf @@ -159,8 +159,8 @@ BAZAAR_ABUSE_CH { } URLHAUS_ABUSE_CH { - type = "url"; - filter = "full"; + type = "selector"; + selector = "urls"; map = "https://urlhaus.abuse.ch/downloads/text_online/"; score = 10.0; } diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 97a34e9e..2c042c30 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -62,7 +62,7 @@ SOGoFirstDayOfWeek = "1"; SOGoSieveFolderEncoding = "UTF-8"; - SOGoPasswordChangeEnabled = YES; + SOGoPasswordChangeEnabled = NO; SOGoSentFolderName = "Sent"; SOGoMailShowSubscribedFoldersOnly = NO; NGImap4ConnectionStringSeparator = "/"; diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index 5e07c4b3..65bd1211 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -3176,8 +3176,10 @@ paths: example: attr: ban_time: "86400" + ban_time_increment: "1" blacklist: "10.100.6.5/32,10.100.8.4/32" max_attempts: "5" + max_ban_time: "86400" netban_ipv4: "24" netban_ipv6: "64" retry_window: "600" @@ -3191,11 +3193,17 @@ paths: description: the backlisted ips or hostnames separated by comma type: string ban_time: - description: the time a ip should be banned + description: the time an ip should be banned type: number + ban_time_increment: + description: if the time of the ban should increase each time + type: boolean max_attempts: description: the maximum numbe of wrong logins before a ip is banned type: number + max_ban_time: + description: the maximum time an ip should be banned + type: number netban_ipv4: description: the networks mask to ban for ipv4 type: number @@ -4113,10 +4121,12 @@ paths: response: value: ban_time: 604800 + ban_time_increment: 1 blacklist: |- 45.82.153.37/32 92.118.38.52/32 max_attempts: 1 + max_ban_time: 604800 netban_ipv4: 32 netban_ipv6: 128 perm_bans: diff --git a/data/web/css/build/011-datatables.css b/data/web/css/build/011-datatables.css index d03514ff..d262f07c 100644 --- a/data/web/css/build/011-datatables.css +++ b/data/web/css/build/011-datatables.css @@ -342,6 +342,10 @@ div.dataTables_wrapper div.dt-row { position: relative; } +div.dataTables_wrapper span.sorting-value { + display: none; +} + div.dataTables_scrollHead table.dataTable { margin-bottom: 0 !important; } diff --git a/data/web/css/site/mailbox.css b/data/web/css/site/mailbox.css index f62ead31..e896abca 100644 --- a/data/web/css/site/mailbox.css +++ b/data/web/css/site/mailbox.css @@ -66,4 +66,6 @@ table tbody tr td input[type="checkbox"] { padding: .2em .4em .3em !important; background-color: #ececec!important; } - +.badge.bg-info .bi { + font-size: inherit; +} diff --git a/data/web/css/themes/mailcow-darkmode.css b/data/web/css/themes/mailcow-darkmode.css index 6e0db0e9..abaa7499 100644 --- a/data/web/css/themes/mailcow-darkmode.css +++ b/data/web/css/themes/mailcow-darkmode.css @@ -20,6 +20,11 @@ legend { background-color: #7a7a7a !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 { border-color: #7a7a7a !important; } @@ -299,22 +304,22 @@ a:hover { } -table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover, +table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover, table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover { background-color: #7a7a7a !important; } -table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before, +table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before { background-color: #7a7a7a !important; border: 1.5px solid #5c5c5c !important; color: #fff !important; } -table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before, +table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before, table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before { background-color: #949494; } -table.dataTable.dtr-inline.collapsed>tbody>tr>td.child, -table.dataTable.dtr-inline.collapsed>tbody>tr>th.child, +table.dataTable.dtr-inline.collapsed>tbody>tr>td.child, +table.dataTable.dtr-inline.collapsed>tbody>tr>th.child, table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty { background-color: #444444; } @@ -327,7 +332,7 @@ table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty { } .btn.btn-outline-secondary { color: #fff !important; - border-color: #7a7a7a !important; + border-color: #7a7a7a !important; } .btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { background-color: #9b9b9b !important; diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php index 2a7f11e8..2c4aa41d 100644 --- a/data/web/inc/functions.fail2ban.inc.php +++ b/data/web/inc/functions.fail2ban.inc.php @@ -239,7 +239,9 @@ function fail2ban($_action, $_data = null) { $is_now = fail2ban('get'); if (!empty($is_now)) { $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_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']); $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']); @@ -256,6 +258,8 @@ function fail2ban($_action, $_data = null) { } $f2b_options = array(); $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_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6; $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4; diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index bb632304..6591fe9c 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -821,20 +821,58 @@ function formatBytes($size, $precision = 2) { } 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") { return true; } global $pdo; global $lang; - $stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_NAME = 'sogo_view'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $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`) - SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view"); - $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); + + $mailbox_exists = false; + if ($mailbox !== null) { + // Check if the mailbox exists + $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'"); + $stmt->execute(array(':mailbox' => $mailbox)); + $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(); } function edit_user_account($_data) { diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index a912b29b..174f81d3 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1281,11 +1281,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ), $_extra); } + update_sogo_static_view($username); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('mailbox_added', htmlspecialchars($username)) ); + return true; break; case 'mailbox_from_template': $stmt = $pdo->prepare("SELECT * FROM `templates` @@ -3197,7 +3199,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('mailbox_modified', $username) ); + + update_sogo_static_view($username); } + return true; break; case 'mailbox_from_template': $stmt = $pdo->prepare("SELECT * FROM `templates` @@ -5189,12 +5194,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + + update_sogo_static_view($username); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('mailbox_removed', htmlspecialchars($username)) ); } + return true; break; case 'mailbox_templates': if ($_SESSION['mailcow_cc_role'] != "admin") { @@ -5400,7 +5408,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } 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(); } diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 9866bfd0..21e57ab7 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "14032023_1050"; + $db_version = "14022023_1000"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -1111,7 +1111,7 @@ function init_db_schema() { $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($num_results != 0) { - $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` + $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE `', `table_schema`, '`.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;"); $stmt->execute(array(':table' => $table)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index 9b5a9516..9e3c685d 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -90,7 +90,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { unset($_SESSION['index_query_string']); if (in_array('mobileconfig', $http_parameters)) { if (in_array('only_email', $http_parameters)) { - header("Location: /mobileconfig.php?email_only"); + header("Location: /mobileconfig.php?only_email"); die(); } header("Location: /mobileconfig.php"); diff --git a/data/web/js/build/013-mailcow.js b/data/web/js/build/013-mailcow.js index d3dfc6ec..4cfc78e5 100644 --- a/data/web/js/build/013-mailcow.js +++ b/data/web/js/build/013-mailcow.js @@ -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() { // mailcow alert box generator window.mailcow_alert_box = function(message, type) { diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index 117527c6..5737b398 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -117,8 +117,8 @@ jQuery(function($){ data: 'tfa_active', defaultContent: '', render: function (data, type) { - if(data == 1) return ''; - else return ''; + if(data == 1) return '1'; + else return '0'; } }, { @@ -126,8 +126,8 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - if(data == 1) return ''; - else return ''; + if(data == 1) return '1'; + else return '0'; } }, { @@ -260,8 +260,8 @@ jQuery(function($){ data: 'tfa_active', defaultContent: '', render: function (data, type) { - if(data == 1) return ''; - else return ''; + if(data == 1) return '1'; + else return '0'; } }, { @@ -269,8 +269,8 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - if(data == 1) return ''; - else return ''; + if(data == 1) return '1'; + else return '0'; } }, { @@ -337,7 +337,7 @@ jQuery(function($){ data: 'keep_spam', defaultContent: '', render: function(data, type){ - return 'yes'==data?'':'no'==data&&''; + return 'yes'==data?'yes':'no'==data&&'no'; } }, { @@ -414,8 +414,8 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - if(data == 1) return ''; - else return ''; + if(data == 1) return '1'; + else return '0'; } }, { @@ -492,8 +492,8 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - if(data == 1) return ''; - else return ''; + if(data == 1) return '1'; + else return '0'; } }, { diff --git a/data/web/js/site/debug.js b/data/web/js/site/debug.js index f7b9c011..83d7205e 100644 --- a/data/web/js/site/debug.js +++ b/data/web/js/site/debug.js @@ -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() { // Parse seconds ago to date // Get "now" timestamp @@ -43,7 +33,7 @@ $(document).ready(function() { if (mailcow_info.branch === "master"){ 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") return; @@ -889,13 +879,10 @@ jQuery(function($){ url: '/api/v1/get/rspamd/actions', async: true, success: function(data){ - console.log(data); - var total = 0; $(data).map(function(){total += this[1];}); var labels = $.makeArray($(data).map(function(){return this[0] + ' ' + Math.round(this[1]/total * 100) + '%';})); var values = $.makeArray($(data).map(function(){return this[1];})); - console.log(values); var graphdata = { labels: labels, @@ -1011,12 +998,15 @@ jQuery(function($){ title: 'Score', data: 'score', defaultContent: '', + class: 'text-nowrap', createdCell: function(td, cellData) { $(td).attr({ "data-order": cellData.sortBy, "data-sort": cellData.sortBy }); - $(td).html(cellData.value); + }, + render: function (data) { + return data.value; } }, { @@ -1039,7 +1029,9 @@ jQuery(function($){ "data-order": cellData.sortBy, "data-sort": cellData.sortBy }); - $(td).html(cellData.value); + }, + render: function (data) { + return data.value; } }, { @@ -1363,6 +1355,12 @@ function update_stats(timeout=5){ $("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%"); $("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB"); $("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%"); + if (data.architecture == "aarch64"){ + $("#host_architecture").html('' + data.architecture + ' ⚠️'); + } + else { + $("#host_architecture").html(data.architecture); + } // update cpu and mem chart var cpu_chart = Chart.getChart("host_cpu_chart"); diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index bc2a1bb8..497b923f 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -606,7 +606,7 @@ jQuery(function($){ defaultContent: '', responsivePriority: 6, render: function (data, type) { - return 1==data?'':(0==data?'':2==data&&'—'); + return 1==data?'1':(0==data?'0':2==data&&'—'); } }, { @@ -753,7 +753,7 @@ jQuery(function($){ data: 'attributes.gal', defaultContent: '', render: function (data, type) { - return 1==data?'':''; + return 1==data?'1':'0'; } }, { @@ -761,7 +761,7 @@ jQuery(function($){ data: 'attributes.backupmx', defaultContent: '', render: function (data, type) { - return 1==data?'':''; + return 1==data?'1':'0'; } }, { @@ -769,7 +769,7 @@ jQuery(function($){ data: 'attributes.relay_all_recipients', defaultContent: '', render: function (data, type) { - return 1==data?'':''; + return 1==data?'1':'0'; } }, { @@ -777,7 +777,7 @@ jQuery(function($){ data: 'attributes.relay_unknown_only', defaultContent: '', render: function (data, type) { - return 1==data?'':''; + return 1==data?'1':'0'; } }, { @@ -786,7 +786,7 @@ jQuery(function($){ defaultContent: '', responsivePriority: 4, render: function (data, type) { - return 1==data?'':''; + return 1==data?'1':'0'; } }, { @@ -925,9 +925,12 @@ jQuery(function($){ ' ' + lang.remove + '' + ''; } - item.in_use = '
' + + item.in_use = { + sortBy: item.percent_in_use, + value: '
' + '
' + item.percent_in_use + '%' + '
'; + 'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '
' + }; item.username = escapeHtml(item.username); if (Array.isArray(item.tags)){ @@ -993,10 +996,11 @@ jQuery(function($){ }, { title: lang.in_use, - data: 'in_use', + data: 'in_use.value', defaultContent: '', responsivePriority: 9, - className: 'dt-data-w100' + className: 'dt-data-w100', + orderData: 24 }, { title: lang.fname, @@ -1097,7 +1101,7 @@ jQuery(function($){ defaultContent: '', responsivePriority: 4, render: function (data, type) { - return 1==data?'':(0==data?'':2==data&&'—'); + return 1==data?'1':(0==data?'0':2==data&&'—'); } }, { @@ -1110,7 +1114,12 @@ jQuery(function($){ { title: "", data: 'quota.sortBy', - responsivePriority: 8, + defaultContent: '', + className: "d-none" + }, + { + title: "", + data: 'in_use.sortBy', defaultContent: '', className: "d-none" }, @@ -1163,13 +1172,13 @@ jQuery(function($){ item.attributes.quota = humanFileSize(item.attributes.quota); - item.attributes.tls_enforce_in = ''; - item.attributes.tls_enforce_out = ''; - item.attributes.pop3_access = ''; - item.attributes.imap_access = ''; - item.attributes.smtp_access = ''; - item.attributes.sieve_access = ''; - item.attributes.sogo_access = ''; + item.attributes.tls_enforce_in = '' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + ''; + item.attributes.tls_enforce_out = '' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + ''; + item.attributes.pop3_access = '' + (item.attributes.pop3_access == 1 ? '1' : '0') + ''; + item.attributes.imap_access = '' + (item.attributes.imap_access == 1 ? '1' : '0') + ''; + item.attributes.smtp_access = '' + (item.attributes.smtp_access == 1 ? '1' : '0') + ''; + item.attributes.sieve_access = '' + (item.attributes.sieve_access == 1 ? '1' : '0') + ''; + item.attributes.sogo_access = '' + (item.attributes.sogo_access == 1 ? '1' : '0') + ''; if (item.attributes.quarantine_notification === 'never') { item.attributes.quarantine_notification = lang.never; } else if (item.attributes.quarantine_notification === 'hourly') { @@ -1187,7 +1196,6 @@ jQuery(function($){ item.attributes.quarantine_category = lang.q_all; } - if (item.template.toLowerCase() == "default"){ item.action = '
' + ' ' + lang.edit + '' + @@ -1328,7 +1336,7 @@ jQuery(function($){ defaultContent: '', responsivePriority: 4, render: function (data, type) { - return 1==data?'':(0==data?'':2==data&&'—'); + return 1==data?'1':(0==data?'0':2==data&&'—'); } }, { @@ -1439,7 +1447,7 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - return 1==data?'':(0==data?'':2==data&&'—'); + return 1==data?'1':(0==data?'0':2==data&&'—'); } }, { @@ -1458,30 +1466,37 @@ jQuery(function($){ } function draw_bcc_table() { $.get("/api/v1/get/bcc-destination-options", function(data){ + var optgroup = ""; // Domains - var optgroup = ""; - $.each(data.domains, function(index, domain){ - optgroup += ""; - }); - optgroup += ""; - $('#bcc-local-dest').append(optgroup); - // Alias domains - var optgroup = ""; - $.each(data.alias_domains, function(index, alias_domain){ - optgroup += ""; - }); - optgroup += "" - $('#bcc-local-dest').append(optgroup); - // Mailboxes and aliases - $.each(data.mailboxes, function(mailbox, aliases){ - var optgroup = ""; - $.each(aliases, function(index, alias){ - optgroup += ""; + if (data.domains && data.domains.length > 0) { + optgroup = ""; + $.each(data.domains, function(index, domain){ + optgroup += ""; }); optgroup += ""; $('#bcc-local-dest').append(optgroup); - }); - // Finish + } + // Alias domains + if (data.alias_domains && data.alias_domains.length > 0) { + optgroup = ""; + $.each(data.alias_domains, function(index, alias_domain){ + optgroup += ""; + }); + optgroup += "" + $('#bcc-local-dest').append(optgroup); + } + // Mailboxes and aliases + if (data.mailboxes && Object.keys(data.mailboxes).length > 0) { + $.each(data.mailboxes, function(mailbox, aliases){ + optgroup = ""; + $.each(aliases, function(index, alias){ + optgroup += ""; + }); + optgroup += ""; + $('#bcc-local-dest').append(optgroup); + }); + } + // Recreate picker $('#bcc-local-dest').selectpicker('refresh'); }); @@ -1577,7 +1592,7 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - return 1==data?'':(0==data?'':2==data&&'—'); + return 1==data?'1':(0==data?'0':2==data&&'—'); } }, { @@ -1674,7 +1689,7 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - return 1==data?'':0==data&&''; + return 1==data?'1':0==data&&'0'; } }, { @@ -1781,7 +1796,7 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - return 1==data?'':0==data&&''; + return 1==data?'1':0==data&&'0'; } }, { @@ -1916,7 +1931,7 @@ jQuery(function($){ data: 'sogo_visible', defaultContent: '', render: function(data, type){ - return 1==data?'':0==data&&''; + return 1==data?'1':0==data&&'0'; } }, { @@ -1935,7 +1950,7 @@ jQuery(function($){ defaultContent: '', responsivePriority: 6, render: function (data, type) { - return 1==data?'':0==data&&''; + return 1==data?'1':0==data&&'0'; } }, { @@ -1951,6 +1966,10 @@ jQuery(function($){ table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table'); }); + + table.on( 'draw', function (){ + $('#alias_table [data-bs-toggle="tooltip"]').tooltip(); + }); } function draw_aliasdomain_table() { // just recalc width if instance already exists @@ -2030,7 +2049,7 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - return 1==data?'':0==data&&''; + return 1==data?'1':0==data&&'0'; } }, { @@ -2166,7 +2185,7 @@ jQuery(function($){ data: 'active', defaultContent: '', render: function (data, type) { - return 1==data?'':0==data&&''; + return 1==data?'1':0==data&&'0'; } }, { @@ -2322,16 +2341,19 @@ jQuery(function($){ // detect element visibility changes function onVisible(element, callback) { $(document).ready(function() { - element_object = document.querySelector(element); + let element_object = document.querySelector(element); if (element_object === null) return; - new IntersectionObserver((entries, observer) => { + let observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if(entry.intersectionRatio > 0) { callback(element_object); + observer.unobserve(element_object); } }); - }).observe(element_object); + }) + + observer.observe(element_object); }); } diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js index cf79b277..1227a5ed 100644 --- a/data/web/js/site/user.js +++ b/data/web/js/site/user.js @@ -139,6 +139,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() { // just recalc width if instance already exists if ($.fn.DataTable.isDataTable('#tla_table') ) { @@ -156,6 +170,7 @@ jQuery(function($){ "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", language: lang_datatables, + order: [[4, 'desc']], ajax: { type: "GET", url: "/api/v1/get/time_limited_aliases", @@ -203,18 +218,16 @@ jQuery(function($){ title: lang.alias_valid_until, data: 'validity', defaultContent: '', - render: function (data, type) { - var date = new Date(data ? data * 1000 : 0); - return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); + createdCell: function(td, cellData) { + createSortableDate(td, cellData) } }, { title: lang.created_on, data: 'created', defaultContent: '', - render: function (data, type) { - var date = new Date(data.replace(/-/g, "/")); - return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); + createdCell: function(td, cellData) { + createSortableDate(td, cellData, true) } }, { diff --git a/data/web/lang/lang.cs-cz.json b/data/web/lang/lang.cs-cz.json index d1f2bb07..d4f62495 100644 --- a/data/web/lang/lang.cs-cz.json +++ b/data/web/lang/lang.cs-cz.json @@ -105,7 +105,8 @@ "timeout2": "Časový limit pro připojení k lokálnímu serveru", "username": "Uživatelské jméno", "validate": "Ověřit", - "validation_success": "Úspěšně ověřeno" + "validation_success": "Úspěšně ověřeno", + "tags": "Štítky" }, "admin": { "access": "Přístupy", @@ -333,7 +334,11 @@ "username": "Uživatelské jméno", "validate_license_now": "Ověřit GUID na licenčním serveru", "verify": "Ověřit", - "yes": "✓" + "yes": "✓", + "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
System > Nastavení > Options > Přizpůsobení" }, "danger": { "access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři", @@ -536,7 +541,7 @@ "inactive": "Neaktivní", "kind": "Druh", "last_modified": "Naposledy změněn", - "lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (.*google\\.com 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 (.*\\.google\\.com 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_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.", @@ -650,7 +655,7 @@ }, "login": { "delayed": "Přihlášení zpožděno o %s sekund.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Přihlásit", "mobileconfig_info": "Ke stažení profilového souboru se přihlaste jako uživatel schránky.", "other_logins": "Přihlášení klíčem", diff --git a/data/web/lang/lang.da-dk.json b/data/web/lang/lang.da-dk.json index fa50ff59..5846181b 100644 --- a/data/web/lang/lang.da-dk.json +++ b/data/web/lang/lang.da-dk.json @@ -4,15 +4,15 @@ "app_passwds": "Administrer app-adgangskoder", "bcc_maps": "BCC kort", "delimiter_action": "Afgrænsning handling", - "eas_reset": "Nulstil EAS endheder", + "eas_reset": "Nulstil EAS enheder", "extend_sender_acl": "Tillad at udvide afsenderens ACL med eksterne adresser", "filters": "Filtre", "login_as": "Login som mailboks bruger", - "prohibited": "Forbudt af ACL", - "protocol_access": "Ændre protokol adgang", + "prohibited": "Nægtet af ACL", + "protocol_access": "Skift protokol adgang", "pushover": "Pushover", - "quarantine": "Karantæneaktioner", - "quarantine_attachments": "Karantæne vedhæftede filer", + "quarantine": "Karantænehandlinger", + "quarantine_attachments": "Karantænevedhæftede filer", "quarantine_notification": "Skift karantænemeddelelser", "ratelimit": "Satsgrænse", "recipient_maps": "Modtagerkort", @@ -20,12 +20,15 @@ "sogo_access": "Tillad styring af SOGo-adgang", "sogo_profile_reset": "Nulstil SOGo-profil", "spam_alias": "Midlertidige aliasser", - "spam_policy": "Sortliste / hvidliste", + "spam_policy": "Sortliste/hvidliste", "spam_score": "Spam-score", "syncjobs": "Synkroniserings job", "tls_policy": "TLS politik", "unlimited_quota": "Ubegrænset plads for mailbokse", - "domain_desc": "Skift domæne beskrivelse" + "domain_desc": "Skift domæne beskrivelse", + "domain_relayhost": "Skift relæ host for et domæne", + "mailbox_relayhost": "Skift relæ-host for en postkasse", + "quarantine_category": "Skift kategorien for karantænemeddelelse" }, "add": { "activate_filter_warn": "Alle andre filtre deaktiveres, når aktiv er markeret.", @@ -59,7 +62,7 @@ "gal": "Global adresseliste", "gal_info": "GAL indeholder alle objekter i et domæne og kan ikke redigeres af nogen bruger. Information om ledig / optaget i SOGo mangler, hvis deaktiveret! Genstart SOGo for at anvende ændringer. ", "generate": "generere", - "goto_ham": "Lær som ham", + "goto_ham": "Lær som ønsket", "goto_null": "Kassér e-mail i stilhed", "goto_spam": "Lær som spam", "hostname": "Vært", @@ -80,7 +83,7 @@ "private_comment": "Privat kommentar", "public_comment": "Offentlig kommentar", "quota_mb": "Kvota (Mb)", - "relay_all": "Send alle modtagere videre", + "relay_all": "Besvar alle modtager", "relay_all_info": "↪ Hvis du vælger ikke at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.", "relay_domain": "Send dette domæne videre", "relay_transport_info": "
Info
Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.", @@ -101,7 +104,10 @@ "timeout2": "Timeout for forbindelse til lokal vært", "username": "Brugernavn", "validate": "Bekræft", - "validation_success": "Valideret med succes" + "validation_success": "Valideret med succes", + "bcc_dest_format": "BCC-destination skal være en enkelt gyldig e-mail-adresse.
Hvis du har brug for at sende en kopi til flere adresser, kan du oprette et alias og bruge det her.", + "app_passwd_protocols": "Tilladte protokoller for app adgangskode", + "tags": "Tag's" }, "admin": { "access": "Adgang", @@ -308,7 +314,10 @@ "username": "Brugernavn", "validate_license_now": "Valider GUID mod licensserver", "verify": "Verificere", - "yes": "✓" + "yes": "✓", + "ip_check_opt_in": "Opt-In for brug af tredjepartstjeneste ipv4.mailcow.email og ipv6.mailcow.email til at finde eksterne IP-adresser.", + "queue_unban": "unban", + "admins": "Administratorer" }, "danger": { "access_denied": "Adgang nægtet eller ugyldig formular data", @@ -425,7 +434,8 @@ "username_invalid": "Brugernavn %s kan ikke bruges", "validity_missing": "Tildel venligst en gyldighedsperiode", "value_missing": "Angiv alle værdier", - "yotp_verification_failed": "Yubico OTP verifikationen mislykkedes: %s" + "yotp_verification_failed": "Yubico OTP verifikationen mislykkedes: %s", + "webauthn_publickey_failed": "Der er ikke gemt nogen offentlig nøgle for den valgte autentifikator" }, "debug": { "chart_this_server": "Diagram (denne server)", @@ -442,7 +452,8 @@ "solr_status": "Solr-status", "started_on": "Startede den", "static_logs": "Statiske logfiler", - "system_containers": "System og Beholdere" + "system_containers": "System og Beholdere", + "error_show_ip": "Kunne ikke finde de offentlige IP-adresser" }, "diagnostics": { "cname_from_a": "Værdi afledt af A / AAAA-post. Dette understøttes, så længe posten peger på den korrekte ressource.", @@ -553,7 +564,11 @@ "title": "Rediger objekt", "unchanged_if_empty": "Lad være tomt, hvis uændret", "username": "Brugernavn", - "validate_save": "Valider og gem" + "validate_save": "Valider og gem", + "admin": "Rediger administrator", + "lookup_mx": "Destination er et regulært udtryk, der matcher MX-navnet (.*google\\.dk for at dirigere al e-mail, der er målrettet til en MX, der ender på google.dk, over dette hop)", + "mailbox_relayhost_info": "Anvendt på postkassen og kun direkte aliasser, og overskriver et domæne relæ-host.", + "quota_warning_bcc": "Kvoteadvarsel BCC" }, "footer": { "cancel": "Afbestille", @@ -571,7 +586,7 @@ "header": { "administration": "Konfiguration og detailer", "apps": "Apps", - "debug": "Systemoplysninger", + "debug": "Information", "email": "E-Mail", "mailcow_config": "Konfiguration", "quarantine": "Karantæne", @@ -586,7 +601,7 @@ }, "login": { "delayed": "Login blev forsinket med% s sekunder.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Login", "mobileconfig_info": "Log ind som postkassebruger for at downloade den anmodede Apple-forbindelsesprofil.", "other_logins": "Nøgle login", @@ -739,7 +754,10 @@ "username": "Brugernavn", "waiting": "Venter", "weekly": "Ugentlig", - "yes": "✓" + "yes": "✓", + "goto_ham": "Lær som ønsket", + "catch_all": "Fang-alt", + "open_logs": "Åben logfiler" }, "oauth2": { "access_denied": "Log ind som mailboks ejer for at give adgang via OAuth2.", @@ -1030,7 +1048,7 @@ "spamfilter_table_empty": "Intet data at vise", "spamfilter_table_remove": "slet", "spamfilter_table_rule": "Regl", - "spamfilter_wl": "Hvisliste", + "spamfilter_wl": "Hvidliste", "spamfilter_wl_desc": "Hvidlistede e-mail-adresser til aldrig at klassificeres som spam. Wildcards kan bruges. Et filter anvendes kun på direkte aliaser (aliaser med en enkelt målpostkasse) eksklusive catch-aliaser og selve en postkasse.", "spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe", "status": "Status", @@ -1066,5 +1084,11 @@ "quota_exceeded_scope": "Domænekvote overskredet: Kun ubegrænsede postkasser kan oprettes i dette domæneomfang.", "session_token": "Form nøgle ugyldig: Nøgle passer ikke", "session_ua": "Form nøgle ugyldig: Bruger-Agent gyldighedskontrols fejl" + }, + "datatables": { + "lengthMenu": "Vis _MENU_ poster", + "paginate": { + "first": "Først" + } } } diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index e7c5b024..fc927e05 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -176,10 +176,12 @@ "empty": "Keine Einträge vorhanden", "excludes": "Diese Empfänger ausschließen", "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_filter": "Regex-Filter", "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. Die Aktualisierung der Liste dauert einige Sekunden.", "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_ipv6": "Netzbereich für IPv6-Banns (8-128)", "f2b_parameters": "Fail2ban-Parameter", @@ -215,7 +217,7 @@ "loading": "Bitte warten...", "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.", - "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa .*google\\.com, um alle Ziele mit MX *google.com zu routen)", + "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa .*\\.google\\.com, um alle Ziele mit MX *google.com zu routen)", "main_name": "\"mailcow UI\" Name", "merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei vars.(local.)inc.php gelesen und können hier nicht verändert werden.", "message": "Nachricht", @@ -498,6 +500,7 @@ } }, "debug": { + "architecture": "Architektur", "chart_this_server": "Chart (dieser Server)", "containers_info": "Container-Information", "container_running": "Läuft", @@ -534,7 +537,8 @@ "update_available": "Es ist ein Update verfügbar", "no_update_available": "Das System ist auf aktuellem Stand", "update_failed": "Es konnte nicht nach einem Update gesucht werden", - "username": "Benutzername" + "username": "Benutzername", + "wip": "Aktuell noch in Arbeit" }, "diagnostics": { "cname_from_a": "Wert abgeleitet von A/AAAA-Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt.", @@ -593,7 +597,7 @@ "inactive": "Inaktiv", "kind": "Art", "last_modified": "Zuletzt geändert", - "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa .*google\\.com, um alle Ziele mit MX *google.com zu routen)", + "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa .*\\.google\\.com, um alle Ziele mit MX *google.com zu routen)", "mailbox": "Mailbox bearbeiten", "mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.", @@ -712,7 +716,7 @@ }, "login": { "delayed": "Login wurde zur Sicherheit um %s Sekunde/n verzögert.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Anmelden", "mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.", "other_logins": "Key Login", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index f79fc612..80726f2b 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -178,10 +178,12 @@ "empty": "No results", "excludes": "Excludes these recipients", "f2b_ban_time": "Ban time (s)", + "f2b_ban_time_increment": "Ban time is incremented with each ban", "f2b_blacklist": "Blacklisted networks/hosts", "f2b_filter": "Regex filters", "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. List updates will take a few seconds to be applied.", "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_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_parameters": "Fail2ban parameters", @@ -238,7 +240,7 @@ "loading": "Please wait...", "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.", - "lookup_mx": "Destination is a regular expression to match against MX name (.*google\\.com 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 (.*\\.google\\.com to route all mail targeted to a MX ending in google.com over this hop)", "main_name": "\"mailcow UI\" name", "merged_vars_hint": "Greyed out rows were merged from vars.(local.)inc.php and cannot be modified.", "message": "Message", @@ -522,6 +524,7 @@ } }, "debug": { + "architecture": "Architecture", "chart_this_server": "Chart (this server)", "containers_info": "Container information", "container_running": "Running", @@ -558,7 +561,8 @@ "update_available": "There is an update available", "no_update_available": "The System is on the latest version", "update_failed": "Could not check for an Update", - "username": "Username" + "username": "Username", + "wip": "Currently Work in Progress" }, "diagnostics": { "cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.", @@ -617,7 +621,7 @@ "inactive": "Inactive", "kind": "Kind", "last_modified": "Last modified", - "lookup_mx": "Destination is a regular expression to match against MX name (.*google\\.com 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 (.*\\.google\\.com to route all mail targeted to a MX ending in google.com over this hop)", "mailbox": "Edit mailbox", "mailbox_quota_def": "Default mailbox quota", "mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.", @@ -736,7 +740,7 @@ }, "login": { "delayed": "Login was delayed by %s seconds.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Login", "mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.", "other_logins": "Key login", diff --git a/data/web/lang/lang.es-es.json b/data/web/lang/lang.es-es.json index d9c3bfd3..e56e6bdd 100644 --- a/data/web/lang/lang.es-es.json +++ b/data/web/lang/lang.es-es.json @@ -141,9 +141,11 @@ "empty": "Sin resultados", "excludes": "Excluye a estos destinatarios", "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_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. Las actualizaciones de la lista tardarán unos segundos en aplicarse.", "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_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)", "f2b_parameters": "Parametros Fail2ban", diff --git a/data/web/lang/lang.fr-fr.json b/data/web/lang/lang.fr-fr.json index 8afc5738..0bc0ba02 100644 --- a/data/web/lang/lang.fr-fr.json +++ b/data/web/lang/lang.fr-fr.json @@ -24,7 +24,7 @@ "spam_policy": "Liste Noire/Liste Blanche", "spam_score": "Score SPAM", "syncjobs": "Tâches de synchronisation", - "tls_policy": "Police TLS", + "tls_policy": "Politique TLS", "unlimited_quota": "Quota illimité pour les boites de courriel", "domain_desc": "Modifier la description du domaine", "domain_relayhost": "Changer le relais pour un domaine", @@ -106,7 +106,8 @@ "validate": "Valider", "validation_success": "Validation réussie", "bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.
Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.", - "tags": "Etiquettes" + "tags": "Etiquettes", + "app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application" }, "admin": { "access": "Accès", @@ -171,11 +172,13 @@ "edit": "Editer", "empty": "Aucun résultat", "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_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. L'application des mises à jour de liste prendra quelques secondes.", "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_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)", "f2b_parameters": "Paramètres Fail2ban", @@ -321,7 +324,9 @@ "admins": "Administrateurs", "api_read_only": "Accès lecture-seule", "password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules", - "password_policy_numbers": "Doit contenir au moins un chiffre" + "password_policy_numbers": "Doit contenir au moins un chiffre", + "ip_check": "Vérification IP", + "ip_check_disabled": "La vérification IP est désactivée. Vous pouvez l'activer sous
Système > Configuration > Options > Personnaliser" }, "danger": { "access_denied": "Accès refusé ou données de formulaire non valides", @@ -440,7 +445,12 @@ "username_invalid": "Le nom d'utilisateur %s ne peut pas être utilisé", "validity_missing": "Veuillez attribuer une période de validité", "value_missing": "Veuillez fournir toutes les valeurs", - "yotp_verification_failed": "La vérification Yubico OTP a échoué : %s" + "yotp_verification_failed": "La vérification Yubico OTP a échoué : %s", + "webauthn_authenticator_failed": "L'authentificateur selectionné est introuvable", + "demo_mode_enabled": "Le mode de démonstration est activé", + "template_exists": "La template %s existe déja", + "template_id_invalid": "Le numéro de template %s est invalide", + "template_name_invalid": "Le nom de la template est invalide" }, "debug": { "chart_this_server": "Graphique (ce serveur)", @@ -578,7 +588,7 @@ "unchanged_if_empty": "Si non modifié, laisser en blanc", "username": "Nom d'utilisateur", "validate_save": "Valider et sauver", - "lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (.*google\\.com 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 (.*\\.google\\.com 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." }, "footer": { @@ -612,7 +622,7 @@ }, "login": { "delayed": "La connexion a été retardée de %s secondes.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Connexion", "mobileconfig_info": "Veuillez vous connecter en tant qu’utilisateur de la boîte pour télécharger le profil de connexion Apple demandé.", "other_logins": "Clé d'authentification", @@ -1081,9 +1091,12 @@ "username": "Nom d'utilisateur", "verify": "Vérification", "waiting": "En attente", - "week": "Semaine", + "week": "semaine", "weekly": "Hebdomadaire", - "weeks": "semaines" + "weeks": "semaines", + "months": "mois", + "year": "année", + "years": "années" }, "warning": { "cannot_delete_self": "Impossible de supprimer l’utilisateur connecté", diff --git a/data/web/lang/lang.it-it.json b/data/web/lang/lang.it-it.json index 8bfa9738..0d5b3f25 100644 --- a/data/web/lang/lang.it-it.json +++ b/data/web/lang/lang.it-it.json @@ -175,10 +175,12 @@ "empty": "Nessun risultato", "excludes": "Esclude questi destinatari", "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_filter": "Filtri Regex", "f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.", "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_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_parameters": "Parametri Fail2ban", @@ -211,7 +213,7 @@ "loading": "Caricamento in corso...", "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.", - "lookup_mx": "Destination is a regular expression to match against MX name (.*google\\.com 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 (.*\\.google\\.com to route all mail targeted to a MX ending in google.com over this hop)", "main_name": "Nome \"mailcow UI\"", "merged_vars_hint": "Greyed out rows were merged from vars.(local.)inc.php and cannot be modified.", "message": "Messaggio", @@ -552,7 +554,7 @@ "hostname": "Hostname", "inactive": "Inattivo", "kind": "Genere", - "lookup_mx": "Destination is a regular expression to match against MX name (.*google\\.com 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 (.*\\.google\\.com to route all mail targeted to a MX ending in google.com over this hop)", "mailbox": "Modifica casella di posta", "mailbox_quota_def": "Default mailbox quota", "mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.", @@ -674,7 +676,7 @@ }, "login": { "delayed": "L'accesso è stato ritardato di %s secondi.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Login", "mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.", "other_logins": "Key login", diff --git a/data/web/lang/lang.lv-lv.json b/data/web/lang/lang.lv-lv.json index b03848a4..962b9000 100644 --- a/data/web/lang/lang.lv-lv.json +++ b/data/web/lang/lang.lv-lv.json @@ -3,7 +3,8 @@ "bcc_maps": "BCC kartes", "filters": "Filtri", "recipient_maps": "Saņēmēja kartes", - "syncjobs": "Sinhronizācijas uzdevumi" + "syncjobs": "Sinhronizācijas uzdevumi", + "spam_score": "Mēstules novērtējums" }, "add": { "activate_filter_warn": "Visi pārējie filtri tiks deaktivizēti, kad aktīvs ir atzīmēts.", @@ -104,10 +105,10 @@ "host": "Hosts", "import": "Importēt", "import_private_key": "Importēt privātu atslēgu", - "in_use_by": "Tiek lietots ar", + "in_use_by": "Izmanto", "inactive": "Neaktīvs", "link": "Saite", - "loading": "Lūdzu uzgaidiet...", + "loading": "Lūgums uzgaidīt...", "logo_info": "Jūsu attēls augšējā navigācijas joslā tiks palielināts līdz 40 pikseļiem un maks. sākumlapas platums par 250 pikseļi. Ir ļoti ieteicama pielāgojama grafikaYour image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. Ir ļoti ieteicama pielāgojamā grafika", "main_name": "\"mailcow UI\" nosaukums", "merged_vars_hint": "Pelēkās rindas tika apvienotas vars.(local.)inc.php un nevar tikt modificētas.", @@ -144,7 +145,10 @@ "ui_texts": "UI etiķetes un teksti", "unchanged_if_empty": "Ja nav veiktas izmaiņas, atstājiet tukšu", "upload": "Augšupielādēt", - "username": "Lietotājvārds" + "username": "Lietotājvārds", + "generate": "izveidot", + "message": "Ziņojums", + "last_applied": "Pēdējoreiz pielietots" }, "danger": { "access_denied": "Piekļuve liegta, vai nepareizi dati", @@ -170,7 +174,7 @@ "is_alias": "%s jau ir zināms alias", "is_alias_or_mailbox": "%s jau ir zināms alias, pastkastes vai alias addrese izvērsta no alias domēna.", "is_spam_alias": "%s ir jau zināms spam alias", - "last_key": "Pēdējā atslēga nevar būt dzēsta", + "last_key": "Pēdējo atslēgu nevar izdzēst, tā vietā jāatspējo divpakāpju pārbaude.", "login_failed": "Ielogošanās neveiksmīga", "mailbox_invalid": "Pastkastes vārds ir nederīgs", "mailbox_quota_exceeded": "Kvota pārsniedz domēna limitu (max. %d MiB)", @@ -262,7 +266,8 @@ "title": "Labot priekšmetu", "unchanged_if_empty": "Ja neizmainīts atstājiet tukšu", "username": "Lietotājvārds", - "validate_save": "Apstiprināt un saglabāt" + "validate_save": "Apstiprināt un saglabāt", + "last_modified": "Pēdējoreiz mainīts" }, "footer": { "cancel": "Atcelt", @@ -314,21 +319,21 @@ "bcc_destinations": "BCC galamērķi/s", "bcc_info": "BCC kartes tiek izmantotas, lai klusu pārsūtītu visu ziņojumu kopijas uz citu adresi. Saņēmēja kartes tipa ieraksts tiek izmantots, kad vietējais galamērķis darbojas kā pasta adresāts. Sūtītāja kartes atbilst vienam un tam pašam principam.
\r\n   Vietējais galamērķis netiks informēts par piegādes neveiksmi. ", "bcc_local_dest": "Vietējais galamērķis", - "bcc_map_type": "BCC tips", + "bcc_map_type": "BCC veids", "bcc_maps": "BCC kartes", "bcc_rcpt_map": "saņēmēja karte", "bcc_sender_map": "Sūtītāja karte", "bcc_to_rcpt": "Pārslēdzieties uz adresāta kartes tipu", "bcc_to_sender": "Pārslēgties uz sūtītāja kartes tipu", "bcc_type": "BCC tips", - "deactivate": "Deaktivizēt", + "deactivate": "Deaktivēt", "description": "Apraksts", "dkim_key_length": "DKIM atslēgas garums (bits)", "domain": "Domēns", "domain_admins": "Domēna administratori", "domain_aliases": "Domēna aliases", "domain_quota": "Kvota", - "domain_quota_total": "Kopējā domēna kvota", + "domain_quota_total": "Kopējais domēna ierobežojums", "domains": "Domēns", "edit": "Labot", "empty": "Nav rezultātu", @@ -341,7 +346,7 @@ "inactive": "Neaktīvs", "kind": "Veids", "last_run": "Pēdējā norise", - "last_run_reset": "Nākamais grafiks", + "last_run_reset": "Ievietot sarakstā kā nākamo", "mailbox_quota": "Maks. pastkastes izmērs", "mailboxes": "Pastkaste", "max_aliases": "Maks. iespejamās aliases", @@ -374,7 +379,13 @@ "tls_enforce_out": "Piespiest TLS izejošajiem", "toggle_all": "Pārslēgt visu", "username": "Lietotājvārds", - "waiting": "Gaidīšana" + "waiting": "Gaidīšana", + "last_modified": "Pēdējoreiz mainīts", + "booking_0_short": "Vienmēŗ bezmaksas", + "daily": "Ik dienu", + "hourly": "Ik stundu", + "last_mail_login": "Pēdējā pieteikšanās pastkastē", + "mailbox": "Pastkaste" }, "quarantine": { "action": "Darbības", @@ -547,5 +558,14 @@ "waiting": "Waiting", "week": "Nedēļa", "weeks": "Nedēļas" + }, + "datatables": { + "paginate": { + "first": "Pirmā", + "last": "Pēdējā" + } + }, + "debug": { + "last_modified": "Pēdējoreiz mainīts" } } diff --git a/data/web/lang/lang.nl-nl.json b/data/web/lang/lang.nl-nl.json index 547c7bb7..4c2ea0b1 100644 --- a/data/web/lang/lang.nl-nl.json +++ b/data/web/lang/lang.nl-nl.json @@ -168,10 +168,12 @@ "empty": "Geen resultaten", "excludes": "Exclusief", "f2b_ban_time": "Verbanningstijd (s)", + "f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning", "f2b_blacklist": "Netwerken/hosts op de blacklist", "f2b_filter": "Regex-filters", "f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.", "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_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)", "f2b_parameters": "Fail2ban", @@ -598,7 +600,7 @@ }, "login": { "delayed": "Aanmelding vertraagd met %s seconden.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Aanmelden", "mobileconfig_info": "Log in als mailboxgebruiker om het Apple-verbindingsprofiel te downloaden.", "other_logins": "Meld aan met key", diff --git a/data/web/lang/lang.pl-pl.json b/data/web/lang/lang.pl-pl.json index b2862d8e..aa185d32 100644 --- a/data/web/lang/lang.pl-pl.json +++ b/data/web/lang/lang.pl-pl.json @@ -1,7 +1,8 @@ { "acl": { "sogo_profile_reset": "Usuń profil SOGo (webmail)", - "syncjobs": "Polecenie synchronizacji" + "syncjobs": "Polecenie synchronizacji", + "alias_domains": "Dodaj aliasy domen" }, "add": { "active": "Aktywny", diff --git a/data/web/lang/lang.ro-ro.json b/data/web/lang/lang.ro-ro.json index fe0d2064..e6315db0 100644 --- a/data/web/lang/lang.ro-ro.json +++ b/data/web/lang/lang.ro-ro.json @@ -539,7 +539,7 @@ "inactive": "Inactiv", "kind": "Fel", "last_modified": "Ultima modificare", - "lookup_mx": "Destinația este o expresie regulată care potrivită cu numele MX (.*google\\.com 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 (.*\\.google\\.com 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_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.", @@ -656,7 +656,7 @@ }, "login": { "delayed": "Conectarea a fost întârziată cu %s secunde.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Autentificare", "mobileconfig_info": "Autentificați-vă cu adresa de email pentru a descărca profilul de conexiune Apple.", "other_logins": "Autentificare cu cheie", diff --git a/data/web/lang/lang.ru-ru.json b/data/web/lang/lang.ru-ru.json index 017ffd02..bad64184 100644 --- a/data/web/lang/lang.ru-ru.json +++ b/data/web/lang/lang.ru-ru.json @@ -336,7 +336,9 @@ "validate_license_now": "Получить лицензию на основе GUID с сервера лицензий", "verify": "Проверить", "yes": "✓", - "queue_unban": "разблокировать" + "queue_unban": "разблокировать", + "f2b_ban_time_increment": "Время бана увеличивается с каждым баном", + "f2b_max_ban_time": "Максимальное время блокировки" }, "danger": { "access_denied": "Доступ запрещён, или указаны неверные данные", @@ -655,7 +657,7 @@ }, "login": { "delayed": "Вход был отложен на %s секунд.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Войти", "mobileconfig_info": "Пожалуйста, войдите в систему как пользователь почтового аккаунта для загрузки профиля подключения Apple.", "other_logins": "Вход с помощью ключа", diff --git a/data/web/lang/lang.sk-sk.json b/data/web/lang/lang.sk-sk.json index f2f23681..29b36c44 100644 --- a/data/web/lang/lang.sk-sk.json +++ b/data/web/lang/lang.sk-sk.json @@ -213,7 +213,7 @@ "loading": "Čakajte prosím ...", "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.", - "lookup_mx": "Cieľ je regulárny výraz ktorý sa porovnáva s MX záznamom (.*google\\.com 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 (.*\\.google\\.com 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", "merged_vars_hint": "Sivé riadky boli načítané z vars.(local.)inc.php a nemôžu byť modifikované cez UI.", "message": "Správa", @@ -539,7 +539,7 @@ "inactive": "Neaktívny", "kind": "Druh", "last_modified": "Naposledy upravené", - "lookup_mx": "Cieľ je regulárny výraz ktorý sa zhoduje s MX záznamom (.*google\\.com 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 (.*\\.google\\.com smeruje všetku poštu na MX ktoré sú cieľom pre google.com cez tento skok)", "mailbox": "Upraviť mailovú schránku", "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.", @@ -657,7 +657,7 @@ }, "login": { "delayed": "Prihlásenie bolo oneskorené o %s sekúnd.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Prihlásenie", "mobileconfig_info": "Prosím, prihláste sa ako mailový používateľ pre stiahnutie požadovaného Apple profilu.", "other_logins": "Prihlásenie kľúčom", diff --git a/data/web/lang/lang.sv-se.json b/data/web/lang/lang.sv-se.json index 4cc84619..31bc0ab3 100644 --- a/data/web/lang/lang.sv-se.json +++ b/data/web/lang/lang.sv-se.json @@ -618,7 +618,7 @@ }, "login": { "delayed": "Av säkerhetsskäl har inloggning inaktiverats i %s sekunder.", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Logga in", "mobileconfig_info": "Logga in som en användare av brevlåda för att ladda ner den begärda Apple-anslutningsprofilen.", "other_logins": "Loggain med nyckel", diff --git a/data/web/lang/lang.uk-ua.json b/data/web/lang/lang.uk-ua.json index e3acb8b5..0a5c71b8 100644 --- a/data/web/lang/lang.uk-ua.json +++ b/data/web/lang/lang.uk-ua.json @@ -656,7 +656,7 @@ "awaiting_tfa_confirmation": "В очікуванні підтвердження TFA" }, "login": { - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "Увійти", "other_logins": "Вхід за допомогою ключа", "password": "Пароль", diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json index 5d05ef62..90888efd 100644 --- a/data/web/lang/lang.zh-cn.json +++ b/data/web/lang/lang.zh-cn.json @@ -213,7 +213,7 @@ "loading": "请等待...", "login_time": "登录时间", "logo_info": "你的图片将会在顶部导航栏被缩放为 40px 高,在起始页被缩放为最大 250px 高。强烈推荐使用能较好适应缩放的图片。", - "lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 .*google\\.com 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)", + "lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 .*\\.google\\.com 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)", "main_name": "Mailcow UI 的名称", "merged_vars_hint": "灰色行来自 vars.(local.)inc.php 文件并且无法修改。", "message": "消息", @@ -544,7 +544,7 @@ "hostname": "主机名", "inactive": "禁用", "kind": "类型", - "lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 .*google\\.com 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)", + "lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 .*\\.google\\.com 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)", "mailbox": "编辑邮箱", "mailbox_quota_def": "邮箱默认配额", "mailbox_relayhost_info": "只适用于邮箱和邮箱别名,不会覆盖域名的中继主机。", @@ -661,7 +661,7 @@ }, "login": { "delayed": "请在 %s 秒后重新登录。", - "fido2_webauthn": "使用 FIDO2/WebAuthn 登录", + "fido2_webauthn": "使用 FIDO2/WebAuthn Login 登录", "login": "登录", "mobileconfig_info": "请使用邮箱用户登录以下载 Apple 连接描述文件。", "other_logins": "Key 登录", diff --git a/data/web/lang/lang.zh-tw.json b/data/web/lang/lang.zh-tw.json index f9c81c66..ff9ed334 100644 --- a/data/web/lang/lang.zh-tw.json +++ b/data/web/lang/lang.zh-tw.json @@ -213,7 +213,7 @@ "loading": "請稍等...", "login_time": "登入時間", "logo_info": "你的起始頁面圖片會在頂部導覽列的限制下被縮放為 40px 高,以及最大 250px 高度。強烈推薦使用能較好縮放的圖片。", - "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (.*google\\.com 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)", + "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (.*\\.google\\.com 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)", "main_name": "\"mailcow UI\" 名稱", "merged_vars_hint": "灰色列來自 vars.(local.)inc.php 並且不能修改。", "message": "訊息", @@ -540,7 +540,7 @@ "inactive": "停用", "kind": "種類", "last_modified": "上次修改時間", - "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (.*google\\.com 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)", + "lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (.*\\.google\\.com 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)", "mailbox": "編輯信箱", "mailbox_quota_def": "預設信箱容量配額", "mailbox_relayhost_info": "只會套用於信箱和直接別名,不會覆寫域名中繼主機。", @@ -655,7 +655,7 @@ }, "login": { "delayed": "請在 %s 秒後重新登入。", - "fido2_webauthn": "FIDO2/WebAuthn", + "fido2_webauthn": "FIDO2/WebAuthn Login", "login": "登入", "mobileconfig_info": "請使用信箱使用者登入以下載 Apple 連接描述檔案。", "other_logins": "金鑰登入", diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 1a6a7dd3..af6f851b 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -61,7 +61,7 @@ elseif (isset($_GET['login'])) { ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) )); // redirect to sogo (sogo will get the correct credentials via nginx auth_request - header("Location: /SOGo/so/${login}"); + header("Location: /SOGo/so/{$login}"); exit; } } diff --git a/data/web/templates/admin/tab-config-f2b.twig b/data/web/templates/admin/tab-config-f2b.twig index bbd3e367..c15fb72f 100644 --- a/data/web/templates/admin/tab-config-f2b.twig +++ b/data/web/templates/admin/tab-config-f2b.twig @@ -12,6 +12,14 @@
+
+ + +
+
+ + +
diff --git a/data/web/templates/debug.twig b/data/web/templates/debug.twig index 33933340..3a47fe11 100644 --- a/data/web/templates/debug.twig +++ b/data/web/templates/debug.twig @@ -50,6 +50,12 @@

{{ hostname }}

+ + {{ lang.debug.architecture }} +
+

-

+
+ IPs @@ -71,7 +77,7 @@ Version @@ -613,7 +619,7 @@
  • {{ lang.datatables.expand_all }}
  • {{ lang.datatables.collapse_all }}
  • -

    {{ lang.admin.hash_remove_info }}

    +

    {{ lang.admin.hash_remove_info|raw }}

    diff --git a/data/web/templates/edit/mailbox.twig b/data/web/templates/edit/mailbox.twig index 1167727c..8c904421 100644 --- a/data/web/templates/edit/mailbox.twig +++ b/data/web/templates/edit/mailbox.twig @@ -121,25 +121,25 @@
    - - - - - - -
    - {{ lang.mailbox.toggle_all }} + {{ lang.mailbox.toggle_all }} {{ lang.mailbox.quick_actions }} + {% if acl.spam_alias == 1 %} + {% endif %} + {% if acl.spam_score == 1 %} + {% endif %} + {% if acl.syncjobs == 1 %} + {% endif %} + {% if acl.app_passwds == 1 %} + {% endif %} + {% if acl.pushover == 1 %} + {% endif %}
    @@ -25,11 +35,11 @@ {% include 'user/tab-user-auth.twig' %} {% include 'user/tab-user-details.twig' %} {% include 'user/tab-user-settings.twig' %} - {% include 'user/SpamAliases.twig' %} - {% include 'user/Spamfilter.twig' %} - {% include 'user/Syncjobs.twig' %} - {% include 'user/AppPasswds.twig' %} - {% include 'user/Pushover.twig' %} + {% if acl.spam_alias == 1 %}{% include 'user/SpamAliases.twig' %}{% endif %} + {% if acl.spam_score == 1 %}{% include 'user/Spamfilter.twig' %}{% endif %} + {% if acl.syncjobs == 1 %}{% include 'user/Syncjobs.twig' %}{% endif %} + {% if acl.app_passwds == 1 %}{% include 'user/AppPasswds.twig' %}{% endif %} + {% if acl.pushover == 1 %}{% include 'user/Pushover.twig' %}{% endif %}
    diff --git a/data/web/templates/user/tab-user-settings.twig b/data/web/templates/user/tab-user-settings.twig index 005a0555..1dcfed98 100644 --- a/data/web/templates/user/tab-user-settings.twig +++ b/data/web/templates/user/tab-user-settings.twig @@ -12,19 +12,19 @@
    {{ lang.user.tag_handling }}:
    - - -
    - -
    - - - -
    - - -