From 5c57df466943580d77c44ff8af22fe4115eb058a Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 16 Jan 2023 10:10:20 +0100 Subject: [PATCH 01/42] [Acme] Implemented IP Check Bypass properly --- data/Dockerfiles/acme/acme.sh | 2 ++ docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/acme/acme.sh b/data/Dockerfiles/acme/acme.sh index 4f5cb803..1cd456a4 100755 --- a/data/Dockerfiles/acme/acme.sh +++ b/data/Dockerfiles/acme/acme.sh @@ -213,11 +213,13 @@ while true; do done ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig') + if [[ ${SKIP_IP_CHECK} != "y" ]]; then # Start IP detection log_f "Detecting IP addresses..." IPV4=$(get_ipv4) IPV6=$(get_ipv6) log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}" + fi ######################################### # IP and webroot challenge verification # diff --git a/docker-compose.yml b/docker-compose.yml index b940b336..32302d87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -389,7 +389,7 @@ services: acme-mailcow: depends_on: - nginx-mailcow - image: mailcow/acme:1.83 + image: mailcow/acme:1.84 dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: From 9c55d46bc6335505cb0c03b3eb303dd3664a50cd Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 16 Jan 2023 14:35:15 +0100 Subject: [PATCH 02/42] [Nextcloud] Updated and improved script (implemented -u and more) --- helper-scripts/nextcloud.sh | 104 +++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 26 deletions(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 37c0d5f3..849a3016 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -9,9 +9,14 @@ done [[ -z ${1} ]] && NC_HELP=y while [ "$1" != '' ]; do + if [[ $# -ne 1 ]]; then + echo -e "\033[31mPlease use only one parameter at the same time!\033[0m" >&2 + exit 2 + fi case "${1}" in -p|--purge) NC_PURGE=y && shift;; -i|--install) NC_INSTALL=y && shift;; + -u|--update) NC_UPDATE=y && shift;; -r|--resetpw) NC_RESETPW=y && shift;; -h|--help) NC_HELP=y && shift;; *) echo "Unknown parameter: ${1}" && shift;; @@ -22,13 +27,11 @@ if [[ ${NC_HELP} == "y" ]]; then printf 'Usage:\n\n' printf ' -p|--purge\n Purge Nextcloud\n' printf ' -i|--install\n Install Nextcloud\n' + printf ' -u|--update\n Update Nextcloud\n' printf ' -r|--resetpw\n Reset password\n\n' exit 0 fi -[[ ${NC_PURGE} == "y" ]] && [[ ${NC_INSTALL} == "y" ]] && { echo "Cannot use -p and -i at the same time!"; exit 1; } -[[ ${NC_PURGE} == "y" ]] && [[ ${NC_RESETPW} == "y" ]] && { echo "Cannot use -p and -r at the same time!"; exit 1; } - SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd ${SCRIPT_DIR}/../ source mailcow.conf @@ -41,8 +44,27 @@ if [[ ${NC_PURGE} == "y" ]]; then exit 1 fi - docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \ - "$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'oc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" + echo -e "\033[33mDetecting Database information...\033[0m" + if [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "Show databases" | grep "nextcloud") ]]; then + echo -e "\033[32mFound seperate nextcloud Database (newer scheme)!\033[0m" + echo -e "\033[31mPurging...\033[0m" + docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP DATABASE nextcloud;" > /dev/null + docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP USER 'nextcloud'@'%';" > /dev/null + elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'oc_%'") && $? -eq 0 ]]; then + echo -e "\033[32mFound nextcloud (oc) tables inside of mailcow Database (old scheme)!\033[0m" + echo -e "\033[31mPurging...\033[0m" + docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \ + "$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'oc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null + elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'nc_%'") && $? -eq 0 ]]; then + echo -e "\033[32mFound nextcloud (nc) tables inside of mailcow Database (old scheme)!\033[0m" + echo -e "\033[31mPurging...\033[0m" + docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \ + "$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null + else + echo -e "\033[31mError: No Nextcloud Databases/Tables found!" + echo -e "\033[33mNot purging anything...\033[0m" + exit 1 + fi docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c ' cat <&1 /dev/null + echo -ne "\r[3/4] Setting custom parameters inside the nextcloud config file" + echo "" docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings config:system:set redis host --value=redis --type=string; \ /web/nextcloud/occ --no-warnings config:system:set redis port --value=6379 --type=integer; \ /web/nextcloud/occ --no-warnings config:system:set redis timeout --value=0.0 --type=integer; \ @@ -141,13 +178,28 @@ elif [[ ${NC_INSTALL} == "y" ]]; then #/web/nextcloud/occ --no-warnings config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert}; \ #/web/nextcloud/occ --no-warnings config:system:set user_backends 0 class --value=OC_User_IMAP; \ + echo -e "\r[4/4] Enabling NGINX Configuration" cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/ sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf + sleep 2 - echo "Restarting Nginx..." + echo "" + echo -e "\033[33mFinalizing installation...\033[0m" docker restart $(docker ps -aqf name=nginx-mailcow) - echo "Login as admin with password: ${ADMIN_NC_PASS}" + echo "" + echo "******************************************" + echo "* SAVE THESE CREDENTIALS *" + echo "* INSTALL DATE: $(date +%Y-%m-%d_%H-%M-%S) *" + echo "******************************************" + echo "" + echo -e "\033[36mDatabase Name: ${NC_DBNAME}\033[0m" + echo -e "\033[36mDatabase User: ${NC_DBUSER}\033[0m" + echo -e "\033[36mDatabase Password: ${NC_DBPASS}\033[0m" + echo "" + echo -e "\033[31mUI Admin Password: ${ADMIN_NC_PASS}\033[0m" + echo "" + elif [[ ${NC_RESETPW} == "y" ]]; then printf 'You are about to set a new password for a Nextcloud user.\n\nDo not use this option if your Nextcloud is configured to use mailcow for authentication.\nSet a new password for the corresponding mailbox in mailcow, instead.\n\n' From 9279ee2e76a1ad67720e2cd69ef5a1298b434afe Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 17 Jan 2023 16:23:31 +0100 Subject: [PATCH 03/42] [Dovecot] Update to 2.3.20 --- data/Dockerfiles/dovecot/Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 38af3c20..b3a83974 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -2,7 +2,7 @@ FROM debian:bullseye-slim LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive -ARG DOVECOT=2.3.19.1 +ARG DOVECOT=2.3.20 # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced ARG GOSU_VERSION=1.16 ENV LC_ALL C diff --git a/docker-compose.yml b/docker-compose.yml index b940b336..629f3f35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -216,7 +216,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.21 + image: mailcow/dovecot:1.22 depends_on: - mysql-mailcow dns: From 5d5e95972914e548209b54c9a7f996a247d5e6f6 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 17 Jan 2023 19:45:32 +0100 Subject: [PATCH 04/42] Add regex for matchstring line in Dockerfiles Update composer to 2.5.1 --- data/Dockerfiles/phpfpm/Dockerfile | 32 ++++++++++++++++++------------ docker-compose.yml | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 93acb33f..05081c6d 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,12 +1,18 @@ FROM php:8.1-fpm-alpine3.17 LABEL maintainer "Andre Peters " -ENV APCU_PECL 5.1.22 -ENV IMAGICK_PECL 3.7.0 -ENV MAILPARSE_PECL 3.1.4 -ENV MEMCACHED_PECL 3.2.0 -ENV REDIS_PECL 5.3.7 -ENV COMPOSER 2.4.4 +# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced +ARG APCU_PECL_VERSION=5.1.22 +# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced +ARG IMAGICK_PECL_VERSION=3.7.0 +# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced +ARG MAILPARSE_PECL_VERSION=3.1.4 +# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced +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 RUN apk add -U --no-cache autoconf \ aspell-dev \ @@ -55,11 +61,11 @@ RUN apk add -U --no-cache autoconf \ samba-client \ zlib-dev \ tzdata \ - && pecl install mailparse-${MAILPARSE_PECL} \ - && pecl install redis-${REDIS_PECL} \ - && pecl install memcached-${MEMCACHED_PECL} \ - && pecl install APCu-${APCU_PECL} \ - && pecl install imagick-${IMAGICK_PECL} \ + && pecl install APCu-${APCU_PECL_VERSION} \ + && pecl install imagick-${IMAGICK_PECL_VERSION} \ + && pecl install mailparse-${MAILPARSE_PECL_VERSION} \ + && pecl install memcached-${MEMCACHED_PECL_VERSION} \ + && pecl install redis-${REDIS_PECL_VERSION} \ && docker-php-ext-enable apcu imagick memcached mailparse redis \ && pecl clear-cache \ && docker-php-ext-configure intl \ @@ -72,7 +78,7 @@ RUN apk add -U --no-cache autoconf \ && 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-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} \ + && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \ && mv composer.phar /usr/local/bin/composer \ && chmod +x /usr/local/bin/composer \ && apk del --purge autoconf \ @@ -102,4 +108,4 @@ COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["php-fpm"] +CMD ["php-fpm"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index b940b336..bbf67c79 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -106,7 +106,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.81 + image: mailcow/phpfpm:1.82 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow From 7626becb38f8b5908b1e4c0d238f3a918bba2f1c Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 17 Jan 2023 19:48:42 +0100 Subject: [PATCH 05/42] Add regex for matchstring line in Dockerfiles --- data/Dockerfiles/dovecot/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index b3a83974..1d8e1e5b 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -2,6 +2,7 @@ FROM debian:bullseye-slim LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive +# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced ARG DOVECOT=2.3.20 # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced ARG GOSU_VERSION=1.16 From 1af785a94f73cbe588435e15b7e749762e876019 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Jan 2023 19:37:09 +0100 Subject: [PATCH 06/42] Enable dependencyDashboard Add label for PRs Add docker-compose manager --- .github/renovate.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 5fab8128..36b4aec5 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,15 +1,19 @@ { "enabled": true, "timezone": "Europe/Berlin", - "dependencyDashboard": false, + "dependencyDashboard": true, "dependencyDashboardTitle": "Renovate Dashboard", "commitBody": "Signed-off-by: milkmaker ", "rebaseWhen": "auto", + "labels": ["renovate"], "assignees": [ "@magiccc" ], "baseBranches": ["staging"], - "enabledManagers": ["github-actions", "regex"], + "enabledManagers": ["github-actions", "regex", "docker-compose"], + "ignorePaths": [ + "data\/web\/inc\/lib\/vendor\/matthiasmullie\/minify\/**" + ], "regexManagers": [ { "fileMatch": ["^helper-scripts\/nextcloud.sh$"], From 8e3d2f70106fdc7f33b4f7d6984bd9bb6556bc66 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 19 Jan 2023 11:28:03 +0100 Subject: [PATCH 07/42] [SOGo] Update to newer 5.8.0 (fix for macOS Caldav Bug) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index bbf67c79..8e462881 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -169,7 +169,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.113 + image: mailcow/sogo:1.114 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} From 520d070081a0e863f1b6a68e135ac3347be3215c Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 19 Jan 2023 14:04:55 +0100 Subject: [PATCH 08/42] [Compose] Removed OOMKillDisabled from dockerapi --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 93257cf5..3b3ccee3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -514,7 +514,6 @@ services: security_opt: - label=disable restart: always - oom_kill_disable: true dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: From f5baeb31c1766e797a9bab5c0b8112dedf8e18c9 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 19 Jan 2023 15:57:49 +0100 Subject: [PATCH 09/42] Revert "[Update.sh] Implemented optimized Regex Compose Detection" This reverts commit a76e6b32f7973c79936cc9aa3dfbfc900135df3d. --- update.sh | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/update.sh b/update.sh index db6be252..34d17354 100755 --- a/update.sh +++ b/update.sh @@ -177,35 +177,38 @@ remove_obsolete_nginx_ports() { detect_docker_compose_command(){ if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then - if command -v docker compose > /dev/null 2>&1; then - version=$(docker compose version --short) - if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then - COMPOSE_VERSION=native + if docker compose > /dev/null 2>&1; then + if docker compose version --short | grep "2." > /dev/null 2>&1; then + DOCKER_COMPOSE_VERSION=native + COMPOSE_COMMAND="docker compose" echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m" sleep 2 - 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 echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/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://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi - elif command -v docker-compose > /dev/null 2>&1; then - version=$(docker-compose version --short) - if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then - COMPOSE_VERSION=standalone + elif docker-compose > /dev/null 2>&1; then + if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then + if docker-compose version --short | grep "^2." > /dev/null 2>&1; then + DOCKER_COMPOSE_VERSION=standalone + COMPOSE_COMMAND="docker-compose" echo -e "\e[31mFound Docker Compose Standalone.\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m" sleep 2 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 echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi + fi + else echo -e "\e[31mCannot find Docker Compose.\e[0m" - echo -e "\e[31mPlease install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi From 2ebd8345dfe4fe6ebb246be6de06f2f5fb0e4f8b Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 19 Jan 2023 15:58:22 +0100 Subject: [PATCH 10/42] Revert "[Generate] Refactor compose version detection using regex" This reverts commit 4c6f8c4f6034c7e3d526c2c940555243c0991955. --- generate_config.sh | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index 6b3ad711..89af0f64 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -25,9 +25,8 @@ for bin in openssl curl docker git awk sha1sum; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi done -if command -v docker compose > /dev/null 2>&1; then - version=$(docker compose version --short) - if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then +if docker compose > /dev/null 2>&1; then + if docker compose version --short | grep "^2." > /dev/null 2>&1; then COMPOSE_VERSION=native echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m" @@ -35,12 +34,12 @@ if command -v docker compose > /dev/null 2>&1; then echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/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://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi -elif command -v docker-compose > /dev/null 2>&1; then - version=$(docker-compose version --short) - if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then +elif docker-compose > /dev/null 2>&1; then + if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then + if docker-compose version --short | grep "^2." > /dev/null 2>&1; then COMPOSE_VERSION=standalone echo -e "\e[31mFound Docker Compose Standalone.\e[0m" echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m" @@ -51,9 +50,11 @@ elif command -v docker-compose > /dev/null 2>&1; then echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi + fi + else echo -e "\e[31mCannot find Docker Compose.\e[0m" - echo -e "\e[31mPlease install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m" exit 1 fi @@ -457,6 +458,15 @@ case ${git_branch} in mailcow_last_git_version="" ;; esac +# if [ ${git_branch} == "master" ]; then +# mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`) +# elif [ ${git_branch} == "nightly" ]; then +# mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream})) +# mailcow_last_git_version="" +# else +# mailcow_git_version=$(git rev-parse --short HEAD) +# mailcow_last_git_version="" +# fi if [[ $SKIP_BRANCH != "y" ]]; then mailcow_git_commit=$(git rev-parse origin/${git_branch}) From 1b3a13ca19086130dd9cca98974b4ad8c2fff4c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 15:36:52 +0100 Subject: [PATCH 11/42] Update alpine Docker tag to v3.17 (#4997) Signed-off-by: milkmaker Signed-off-by: milkmaker Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml b/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml index 7d4424e3..8fcc6fff 100644 --- a/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml +++ b/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml @@ -26,6 +26,6 @@ services: - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock mysql-mailcow: - image: alpine:3.10 + image: alpine:3.17 command: /bin/true restart: "no" From 9af40eba10da1741f1005223e7d953215adf9eae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 15:37:12 +0100 Subject: [PATCH 12/42] Update dependency nextcloud/server to v25.0.3 (#4996) Signed-off-by: milkmaker Signed-off-by: milkmaker Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- helper-scripts/nextcloud.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 849a3016..9565c836 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?.*)$ -NEXTCLOUD_VERSION=25.0.2 +NEXTCLOUD_VERSION=25.0.3 for bin in curl dirmngr; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi From ef6452cf55f5f8ba4f92d563a6ebe038615b68e3 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 22 Jan 2023 15:06:36 +0100 Subject: [PATCH 13/42] Fix installation of nextcloud --- helper-scripts/nextcloud.sh | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 9565c836..31cdb6a4 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -46,22 +46,22 @@ if [[ ${NC_PURGE} == "y" ]]; then echo -e "\033[33mDetecting Database information...\033[0m" if [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "Show databases" | grep "nextcloud") ]]; then - echo -e "\033[32mFound seperate nextcloud Database (newer scheme)!\033[0m" + echo -e "\033[32mFound seperate Nextcloud database (newer scheme)!\033[0m" echo -e "\033[31mPurging...\033[0m" docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP DATABASE nextcloud;" > /dev/null docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP USER 'nextcloud'@'%';" > /dev/null elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'oc_%'") && $? -eq 0 ]]; then - echo -e "\033[32mFound nextcloud (oc) tables inside of mailcow Database (old scheme)!\033[0m" + echo -e "\033[32mFound Nextcloud (oc) tables inside of mailcow database (old scheme)!\033[0m" echo -e "\033[31mPurging...\033[0m" docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \ "$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'oc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'nc_%'") && $? -eq 0 ]]; then - echo -e "\033[32mFound nextcloud (nc) tables inside of mailcow Database (old scheme)!\033[0m" + echo -e "\033[32mFound Nextcloud (nc) tables inside of mailcow database (old scheme)!\033[0m" echo -e "\033[31mPurging...\033[0m" docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \ "$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null else - echo -e "\033[31mError: No Nextcloud Databases/Tables found!" + echo -e "\033[31mError: No Nextcloud databases/tables found!" echo -e "\033[33mNot purging anything...\033[0m" exit 1 fi @@ -80,10 +80,10 @@ EOF docker restart $(docker ps -aqf name=nginx-mailcow) - echo -e "\033[32mNextcloud has been sucessfully uninstalled!\033[0m" + echo -e "\033[32mNextcloud has been uninstalled sucessfully!\033[0m" elif [[ ${NC_UPDATE} == "y" ]]; then - read -r -p "Are you sure you want to update Nextcloud (with nextclouds own updater)? [y/N] " response + read -r -p "Are you sure you want to update Nextcloud (with Nextclouds own updater)? [y/N] " response response=${response,,} if [[ ! "$response" =~ ^(yes|y)$ ]]; then echo "OK, aborting." @@ -118,18 +118,18 @@ elif [[ ${NC_INSTALL} == "y" ]]; then && mkdir -p ./data/web/nextcloud/data \ && chmod +x ./data/web/nextcloud/occ - echo -e "\033[33mCreating Nextcloud Database...\033[0m" + echo -e "\033[33mCreating 'nextcloud' database...\033[0m" NC_DBPASS=$(&1 /dev/null + --data-dir /web/nextcloud/data > /dev/null 2>&1 - echo -ne "\r[3/4] Setting custom parameters inside the nextcloud config file" + echo -ne "\r[3/4] Setting custom parameters inside the Nextcloud config file" echo "" docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings config:system:set redis host --value=redis --type=string; \ /web/nextcloud/occ --no-warnings config:system:set redis port --value=6379 --type=integer; \ @@ -178,7 +178,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then #/web/nextcloud/occ --no-warnings config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert}; \ #/web/nextcloud/occ --no-warnings config:system:set user_backends 0 class --value=OC_User_IMAP; \ - echo -e "\r[4/4] Enabling NGINX Configuration" + echo -e "\r[4/4] Enabling Nginx Configuration" cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/ sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf sleep 2 @@ -193,11 +193,11 @@ elif [[ ${NC_INSTALL} == "y" ]]; then echo "* INSTALL DATE: $(date +%Y-%m-%d_%H-%M-%S) *" echo "******************************************" echo "" - echo -e "\033[36mDatabase Name: ${NC_DBNAME}\033[0m" - echo -e "\033[36mDatabase User: ${NC_DBUSER}\033[0m" - echo -e "\033[36mDatabase Password: ${NC_DBPASS}\033[0m" + echo -e "\033[36mDatabase name: ${NC_DBNAME}\033[0m" + echo -e "\033[36mDatabase user: ${NC_DBUSER}\033[0m" + echo -e "\033[36mDatabase password: ${NC_DBPASS}\033[0m" echo "" - echo -e "\033[31mUI Admin Password: ${ADMIN_NC_PASS}\033[0m" + echo -e "\033[31mUI admin password: ${ADMIN_NC_PASS}\033[0m" echo "" From afddcf7f3b79c506a5b385a7dbbf6fadf95639fa Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 24 Jan 2023 09:49:49 +0100 Subject: [PATCH 14/42] replace nullnull.org with fuzzy.mailcow.email --- data/conf/rspamd/local.d/multimap.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/rspamd/local.d/multimap.conf b/data/conf/rspamd/local.d/multimap.conf index 17ada99e..3f554c5b 100644 --- a/data/conf/rspamd/local.d/multimap.conf +++ b/data/conf/rspamd/local.d/multimap.conf @@ -175,7 +175,7 @@ BAD_SUBJECT_00 { type = "header"; header = "subject"; regexp = true; - map = "http://nullnull.org/bad-subject-regex.txt"; + map = "http://fuzzy.mailcow.email/bad-subject-regex.txt"; score = 6.0; symbols_set = ["BAD_SUBJECT_00"]; } From 9ba65a572e1e22d89d75130f0ee609ec1f9f6d70 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 24 Jan 2023 10:13:30 +0100 Subject: [PATCH 15/42] [Web] add missing template var for dadmins --- data/web/user.php | 1 + 1 file changed, 1 insertion(+) diff --git a/data/web/user.php b/data/web/user.php index b92e87a7..5fddff6e 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -20,6 +20,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma 'tfa_data' => $tfa_data, 'fido2_data' => $fido2_data, 'lang_user' => json_encode($lang['user']), + 'lang_datatables' => json_encode($lang['datatables']), ]; } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { From 8281d3fa557c01f4a250550443001d4c1412d0be Mon Sep 17 00:00:00 2001 From: milkmaker Date: Tue, 24 Jan 2023 20:18:17 +0100 Subject: [PATCH 16/42] [Web] Updated lang.da-dk.json (#5020) Co-authored-by: osos Co-authored-by: osos --- data/web/lang/lang.da-dk.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/web/lang/lang.da-dk.json b/data/web/lang/lang.da-dk.json index c0d42afe..fa50ff59 100644 --- a/data/web/lang/lang.da-dk.json +++ b/data/web/lang/lang.da-dk.json @@ -1,6 +1,6 @@ { "acl": { - "alias_domains": "Tilføj kældenavn domæner", + "alias_domains": "Tilføj domænealias", "app_passwds": "Administrer app-adgangskoder", "bcc_maps": "BCC kort", "delimiter_action": "Afgrænsning handling", @@ -22,9 +22,9 @@ "spam_alias": "Midlertidige aliasser", "spam_policy": "Sortliste / hvidliste", "spam_score": "Spam-score", - "syncjobs": "Synkroniser job", + "syncjobs": "Synkroniserings job", "tls_policy": "TLS politik", - "unlimited_quota": "Ubegrænset quote for mailbokse", + "unlimited_quota": "Ubegrænset plads for mailbokse", "domain_desc": "Skift domæne beskrivelse" }, "add": { @@ -33,7 +33,7 @@ "add": "Tilføj", "add_domain_only": "Tilføj kun domæne", "add_domain_restart": "Tilføj domæne og genstart SOGo", - "alias_address": "Alias adresse (r)", + "alias_address": "Alias adresse(r)", "alias_address_info": "Fuld e-mail-adresse eller @ eksempel.com for at fange alle beskeder til et domæne (kommasepareret). kun mailcow-domæner.", "alias_domain": "Alias-domæne", "alias_domain_info": "Kun gyldige domænenavne (kommasepareret).", From b71998250406656963fc9176826522e1af73cc66 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 25 Jan 2023 09:31:22 +0100 Subject: [PATCH 17/42] partial rollback of dockerapi --- data/Dockerfiles/dockerapi/Dockerfile | 1 + data/Dockerfiles/dockerapi/dockerapi.py | 614 ++++++++++-------------- docker-compose.yml | 2 +- 3 files changed, 267 insertions(+), 350 deletions(-) diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile index 97c3808c..aa4a3858 100644 --- a/data/Dockerfiles/dockerapi/Dockerfile +++ b/data/Dockerfiles/dockerapi/Dockerfile @@ -13,6 +13,7 @@ RUN apk add --update --no-cache python3 \ fastapi \ uvicorn \ aiodocker \ + docker \ redis COPY docker-entrypoint.sh /app/ diff --git a/data/Dockerfiles/dockerapi/dockerapi.py b/data/Dockerfiles/dockerapi/dockerapi.py index 304c1781..9e699c22 100644 --- a/data/Dockerfiles/dockerapi/dockerapi.py +++ b/data/Dockerfiles/dockerapi/dockerapi.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, Response, Request import aiodocker +import docker import psutil import sys import re @@ -9,11 +10,38 @@ import json import asyncio import redis from datetime import datetime +import logging +from logging.config import dictConfig +log_config = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": "%(levelprefix)s %(asctime)s %(message)s", + "datefmt": "%Y-%m-%d %H:%M:%S", + + }, + }, + "handlers": { + "default": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr", + }, + }, + "loggers": { + "api-logger": {"handlers": ["default"], "level": "INFO"}, + }, +} +dictConfig(log_config) + containerIds_to_update = [] host_stats_isUpdating = False app = FastAPI() +logger = logging.getLogger('api-logger') @app.get("/host/stats") @@ -21,18 +49,15 @@ async def get_host_update_stats(): global host_stats_isUpdating if host_stats_isUpdating == False: - print("start host stats task") asyncio.create_task(get_host_stats()) host_stats_isUpdating = True while True: if redis_client.exists('host_stats'): break - print("wait for host_stats results") await asyncio.sleep(1.5) - print("host stats pulled") stats = json.loads(redis_client.get('host_stats')) return Response(content=json.dumps(stats, indent=4), media_type="application/json") @@ -106,14 +131,14 @@ async def post_containers(container_id : str, post_action : str, request: Reques else: api_call_method_name = '__'.join(['container_post', str(post_action) ]) - docker_utils = DockerUtils(async_docker_client) + docker_utils = DockerUtils(sync_docker_client) api_call_method = getattr(docker_utils, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json")) - print("api call: %s, container_id: %s" % (api_call_method_name, container_id)) - return await api_call_method(container_id, request_json) + logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id)) + return api_call_method(container_id, request_json) except Exception as e: - print("error - container_post: %s" % str(e)) + logger.error("error - container_post: %s" % str(e)) res = { "type": "danger", "msg": str(e) @@ -152,398 +177,289 @@ class DockerUtils: self.docker_client = docker_client # api call: container_post - post_action: stop - async def container_post__stop(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - await container.stop() - res = { - 'type': 'success', - 'msg': 'command completed successfully' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") + def container_post__stop(self, container_id, request_json): + for container in self.docker_client.containers.list(all=True, filters={"id": container_id}): + container.stop() + res = { 'type': 'success', 'msg': 'command completed successfully'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: start - async def container_post__start(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - await container.start() - res = { - 'type': 'success', - 'msg': 'command completed successfully' - } + def container_post__start(self, container_id, request_json): + for container in self.docker_client.containers.list(all=True, filters={"id": container_id}): + container.start() + + res = { 'type': 'success', 'msg': 'command completed successfully'} return Response(content=json.dumps(res, indent=4), media_type="application/json") - - # api call: container_post - post_action: restart - async def container_post__restart(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - await container.restart() - res = { - 'type': 'success', - 'msg': 'command completed successfully' - } + def container_post__restart(self, container_id, request_json): + for container in self.docker_client.containers.list(all=True, filters={"id": container_id}): + container.restart() + + res = { 'type': 'success', 'msg': 'command completed successfully'} return Response(content=json.dumps(res, indent=4), media_type="application/json") - - # api call: container_post - post_action: top - async def container_post__top(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - ps_exec = await container.exec("ps") - async with ps_exec.start(detach=False) as stream: - ps_return = await stream.read_out() - - exec_details = await ps_exec.inspect() - if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: - res = { - 'type': 'success', - 'msg': ps_return.data.decode('utf-8') - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - else: - res = { - 'type': 'danger', - 'msg': '' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - + def container_post__top(self, container_id, request_json): + for container in self.docker_client.containers.list(all=True, filters={"id": container_id}): + res = { 'type': 'success', 'msg': container.top()} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: stats + def container_post__stats(self, container_id, request_json): + for container in self.docker_client.containers.list(all=True, filters={"id": container_id}): + for stat in container.stats(decode=True, stream=True): + res = { 'type': 'success', 'msg': stat} + return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: mailq - task: delete - async def container_post__exec__mailq__delete(self, container_id, request_json): + def container_post__exec__mailq__delete(self, container_id, request_json): if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-d %s' % i for i in filtered_qids] - sanitized_string = str(' '.join(flagged_qids)) + sanitized_string = str(' '.join(flagged_qids)); + for container in self.docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return exec_run_handler('generic', postsuper_r) - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) - return await exec_run_handler('generic', postsuper_r_exec) # api call: container_post - post_action: exec - cmd: mailq - task: hold - async def container_post__exec__mailq__hold(self, container_id, request_json): + def container_post__exec__mailq__hold(self, container_id, request_json): if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-h %s' % i for i in filtered_qids] - sanitized_string = str(' '.join(flagged_qids)) - - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) - return await exec_run_handler('generic', postsuper_r_exec) + sanitized_string = str(' '.join(flagged_qids)); + for container in self.docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return exec_run_handler('generic', postsuper_r) # api call: container_post - post_action: exec - cmd: mailq - task: cat - async def container_post__exec__mailq__cat(self, container_id, request_json): + def container_post__exec__mailq__cat(self, container_id, request_json): if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: - sanitized_string = str(' '.join(filtered_qids)) + sanitized_string = str(' '.join(filtered_qids)); - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') - return await exec_run_handler('utf8_text_only', postcat_exec) + for container in self.docker_client.containers.list(filters={"id": container_id}): + postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') + if not postcat_return: + postcat_return = 'err: invalid' + return exec_run_handler('utf8_text_only', postcat_return) # api call: container_post - post_action: exec - cmd: mailq - task: unhold - async def container_post__exec__mailq__unhold(self, container_id, request_json): + def container_post__exec__mailq__unhold(self, container_id, request_json): if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-H %s' % i for i in filtered_qids] - sanitized_string = str(' '.join(flagged_qids)) - - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) - return await exec_run_handler('generic', postsuper_r_exec) - + sanitized_string = str(' '.join(flagged_qids)); + for container in self.docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return exec_run_handler('generic', postsuper_r) # api call: container_post - post_action: exec - cmd: mailq - task: deliver - async def container_post__exec__mailq__deliver(self, container_id, request_json): + def container_post__exec__mailq__deliver(self, container_id, request_json): if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-i %s' % i for i in filtered_qids] - - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - for i in flagged_qids: - postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') - async with postsuper_r_exec.start(detach=False) as stream: - postsuper_r_return = await stream.read_out() - # todo: check each exit code - res = { - 'type': 'success', - 'msg': 'Scheduled immediate delivery' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - - - # api call: container_post - post_action: exec - cmd: mailq - task: list - async def container_post__exec__mailq__list(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix') - return await exec_run_handler('utf8_text_only', mailq_exec) - - - # api call: container_post - post_action: exec - cmd: mailq - task: flush - async def container_post__exec__mailq__flush(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix') - return await exec_run_handler('generic', postsuper_r_exec) - - - # api call: container_post - post_action: exec - cmd: mailq - task: super_delete - async def container_post__exec__mailq__super_delete(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"]) - return await exec_run_handler('generic', postsuper_r_exec) - - - # api call: container_post - post_action: exec - cmd: system - task: fts_rescan - async def container_post__exec__system__fts_rescan(self, container_id, request_json): - if 'username' in request_json: - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail') - async with rescan_exec.start(detach=False) as stream: - rescan_return = await stream.read_out() - - exec_details = await rescan_exec.inspect() - if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: - res = { - 'type': 'success', - 'msg': 'fts_rescan: rescan triggered' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - else: - res = { - 'type': 'warning', - 'msg': 'fts_rescan error' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - - if 'all' in request_json: - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail') - async with rescan_exec.start(detach=False) as stream: - rescan_return = await stream.read_out() - - exec_details = await rescan_exec.inspect() - if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: - res = { - 'type': 'success', - 'msg': 'fts_rescan: rescan triggered' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - else: - res = { - 'type': 'warning', - 'msg': 'fts_rescan error' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - - - # api call: container_post - post_action: exec - cmd: system - task: df - async def container_post__exec__system__df(self, container_id, request_json): - if 'dir' in request_json: - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - df_exec = await container.exec(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') - async with df_exec.start(detach=False) as stream: - df_return = await stream.read_out() - - print(df_return) - print(await df_exec.inspect()) - exec_details = await df_exec.inspect() - if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: - return df_return.data.decode('utf-8').rstrip() - else: - return "0,0,0,0,0,0" - - - # api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade - async def container_post__exec__system__mysql_upgrade(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql') - async with sql_exec.start(detach=False) as stream: - sql_return = await stream.read_out() - - exec_details = await sql_exec.inspect() - if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: - matched = False - for line in sql_return.data.decode('utf-8').split("\n"): - if 'is already upgraded to' in line: - matched = True - if matched: - res = { - 'type': 'success', - 'msg': 'mysql_upgrade: already upgraded', - 'text': sql_return.data.decode('utf-8') - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - else: - await container.restart() - res = { - 'type': 'warning', - 'msg': 'mysql_upgrade: upgrade was applied', - 'text': sql_return.data.decode('utf-8') - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - else: - res = { - 'type': 'error', - 'msg': 'mysql_upgrade: error running command', - 'text': sql_return.data.decode('utf-8') - } + for container in self.docker_client.containers.list(filters={"id": container_id}): + for i in flagged_qids: + postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') + # todo: check each exit code + res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'} return Response(content=json.dumps(res, indent=4), media_type="application/json") - - # api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql - async def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql') - async with sql_exec.start(detach=False) as stream: - sql_return = await stream.read_out() - - exec_details = await sql_exec.inspect() - if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: - res = { - 'type': 'info', - 'msg': 'mysql_tzinfo_to_sql: command completed successfully', - 'text': sql_return.data.decode('utf-8') - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - else: - res = { - 'type': 'error', - 'msg': 'mysql_tzinfo_to_sql: error running command', - 'text': sql_return.data.decode('utf-8') - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - - # api call: container_post - post_action: exec - cmd: reload - task: dovecot - async def container_post__exec__reload__dovecot(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/dovecot reload"]) - return await exec_run_handler('generic', reload_exec) - - - # api call: container_post - post_action: exec - cmd: reload - task: postfix - async def container_post__exec__reload__postfix(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postfix reload"]) - return await exec_run_handler('generic', reload_exec) - - - # api call: container_post - post_action: exec - cmd: reload - task: nginx - async def container_post__exec__reload__nginx(self, container_id, request_json): - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - reload_exec = await container.exec(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"]) - return await exec_run_handler('generic', reload_exec) - - - # api call: container_post - post_action: exec - cmd: sieve - task: list - async def container_post__exec__sieve__list(self, container_id, request_json): - if 'username' in request_json: - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - sieve_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"]) - return await exec_run_handler('utf8_text_only', sieve_exec) - - - # api call: container_post - post_action: exec - cmd: sieve - task: print - async def container_post__exec__sieve__print(self, container_id, request_json): - if 'username' in request_json and 'script_name' in request_json: - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"] - sieve_exec = await container.exec(cmd) - return await exec_run_handler('utf8_text_only', sieve_exec) - - - # api call: container_post - post_action: exec - cmd: maildir - task: cleanup - async def container_post__exec__maildir__cleanup(self, container_id, request_json): - if 'maildir' in request_json: - for container in (await self.docker_client.containers.list()): - if container._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"] - maildir_cleanup_exec = await container.exec(cmd, user='vmail') - return await exec_run_handler('generic', maildir_cleanup_exec) - - # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password - async def container_post__exec__rspamd__worker_password(self, container_id, request_json): - if 'raw' in request_json: - for container in (await self.docker_client.containers.list()): - if container._id == container_id: - cmd = "./set_worker_password.sh '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null" - rspamd_password_exec = await container.exec(cmd, user='_rspamd') - async with rspamd_password_exec.start(detach=False) as stream: - rspamd_password_return = await stream.read_out() - - matched = False - if "OK" in rspamd_password_return.data.decode('utf-8'): + # api call: container_post - post_action: exec - cmd: mailq - task: list + def container_post__exec__mailq__list(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix') + return exec_run_handler('utf8_text_only', mailq_return) + # api call: container_post - post_action: exec - cmd: mailq - task: flush + def container_post__exec__mailq__flush(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix') + return exec_run_handler('generic', postqueue_r) + # api call: container_post - post_action: exec - cmd: mailq - task: super_delete + def container_post__exec__mailq__super_delete(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"]) + return exec_run_handler('generic', postsuper_r) + # api call: container_post - post_action: exec - cmd: system - task: fts_rescan + def container_post__exec__system__fts_rescan(self, container_id, request_json): + if 'username' in request_json: + for container in self.docker_client.containers.list(filters={"id": container_id}): + rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail') + if rescan_return.exit_code == 0: + res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'warning', 'msg': 'fts_rescan error'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + if 'all' in request_json: + for container in self.docker_client.containers.list(filters={"id": container_id}): + rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail') + if rescan_return.exit_code == 0: + res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'warning', 'msg': 'fts_rescan error'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: system - task: df + def container_post__exec__system__df(self, container_id, request_json): + if 'dir' in request_json: + for container in self.docker_client.containers.list(filters={"id": container_id}): + df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') + if df_return.exit_code == 0: + return df_return.output.decode('utf-8').rstrip() + else: + return "0,0,0,0,0,0" + # api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade + def container_post__exec__system__mysql_upgrade(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql') + if sql_return.exit_code == 0: + matched = False + for line in sql_return.output.decode('utf-8').split("\n"): + if 'is already upgraded to' in line: matched = True - await container.restart() + if matched: + res = { 'type': 'success', 'msg':'mysql_upgrade: already upgraded', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + container.restart() + res = { 'type': 'warning', 'msg':'mysql_upgrade: upgrade was applied', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'error', 'msg': 'mysql_upgrade: error running command', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql + def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql') + if sql_return.exit_code == 0: + res = { 'type': 'info', 'msg': 'mysql_tzinfo_to_sql: command completed successfully', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'error', 'msg': 'mysql_tzinfo_to_sql: error running command', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: reload - task: dovecot + def container_post__exec__reload__dovecot(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"]) + return exec_run_handler('generic', reload_return) + # api call: container_post - post_action: exec - cmd: reload - task: postfix + def container_post__exec__reload__postfix(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"]) + return exec_run_handler('generic', reload_return) + # api call: container_post - post_action: exec - cmd: reload - task: nginx + def container_post__exec__reload__nginx(self, container_id, request_json): + for container in self.docker_client.containers.list(filters={"id": container_id}): + reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"]) + return exec_run_handler('generic', reload_return) + # api call: container_post - post_action: exec - cmd: sieve - task: list + def container_post__exec__sieve__list(self, container_id, request_json): + if 'username' in request_json: + for container in self.docker_client.containers.list(filters={"id": container_id}): + sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"]) + 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: + 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) + return exec_run_handler('utf8_text_only', sieve_return) + # api call: container_post - post_action: exec - cmd: maildir - task: cleanup + def container_post__exec__maildir__cleanup(self, container_id, request_json): + 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"] + 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 + def container_post__exec__rspamd__worker_password(self, container_id, request_json): + if 'raw' in request_json: + for container in self.docker_client.containers.list(filters={"id": container_id}): + cmd = "/usr/bin/rspamadm pw -e -p '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null" + cmd_response = exec_cmd_container(container, cmd, user="_rspamd") - if matched: - res = { - 'type': 'success', - 'msg': 'command completed successfully' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") - else: - res = { - 'type': 'danger', - 'msg': 'command did not complete' - } - return Response(content=json.dumps(res, indent=4), media_type="application/json") + matched = False + for line in cmd_response.split("\n"): + if '$2$' in line: + hash = line.strip() + hash_out = re.search('\$2\$.+$', hash).group(0) + rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) + rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc" + cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename) + cmd_response = exec_cmd_container(container, cmd, user="_rspamd") + if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response: + container.restart() + matched = True + if matched: + res = { 'type': 'success', 'msg': 'command completed successfully' } + logger.info('success changing Rspamd password') + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + logger.error('failed changing Rspamd password') + res = { 'type': 'danger', 'msg': 'command did not complete' } + return Response(content=json.dumps(res, indent=4), media_type="application/json") +def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"): -async def exec_run_handler(type, exec_obj): - async with exec_obj.start(detach=False) as stream: - exec_return = await stream.read_out() + def recv_socket_data(c_socket, timeout): + c_socket.setblocking(0) + total_data=[] + data='' + begin=time.time() + while True: + if total_data and time.time()-begin > timeout: + break + elif time.time()-begin > timeout*2: + break + try: + data = c_socket.recv(8192) + if data: + total_data.append(data.decode('utf-8')) + #change the beginning time for measurement + begin=time.time() + else: + #sleep for sometime to indicate a gap + time.sleep(0.1) + break + except: + pass + return ''.join(total_data) + - if exec_return == None: - exec_return = "" - else: - exec_return = exec_return.data.decode('utf-8') - - if type == 'generic': - exec_details = await exec_obj.inspect() - if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: - res = { - "type": "success", - "msg": "command completed successfully" - } + try : + socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock + if not cmd.endswith("\n"): + cmd = cmd + "\n" + socket.send(cmd.encode('utf-8')) + data = recv_socket_data(socket, timeout) + socket.close() + return data + except Exception as e: + logger.error("error - exec_cmd_container: %s" % str(e)) + traceback.print_exc(file=sys.stdout) +def exec_run_handler(type, output): + if type == 'generic': + if output.exit_code == 0: + res = { 'type': 'success', 'msg': 'command completed successfully' } return Response(content=json.dumps(res, indent=4), media_type="application/json") else: - res = { - "type": "success", - "msg": "'command failed: " + exec_return - } + res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') } return Response(content=json.dumps(res, indent=4), media_type="application/json") if type == 'utf8_text_only': - return Response(content=exec_return, media_type="text/plain") + return Response(content=output.output.decode('utf-8'), media_type="text/plain") async def get_host_stats(wait=5): global host_stats_isUpdating @@ -570,12 +486,10 @@ async def get_host_stats(wait=5): "type": "danger", "msg": str(e) } - print(json.dumps(res, indent=4)) await asyncio.sleep(wait) host_stats_isUpdating = False - async def get_container_stats(container_id, wait=5, stop=False): global containerIds_to_update @@ -598,13 +512,11 @@ async def get_container_stats(container_id, wait=5, stop=False): "type": "danger", "msg": str(e) } - print(json.dumps(res, indent=4)) else: res = { "type": "danger", "msg": "no or invalid id defined" } - print(json.dumps(res, indent=4)) await asyncio.sleep(wait) if stop == True: @@ -615,9 +527,13 @@ async def get_container_stats(container_id, wait=5, stop=False): await get_container_stats(container_id, wait=0, stop=True) + if os.environ['REDIS_SLAVEOF_IP'] != "": redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0) else: redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0) +sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock') + +logger.info('DockerApi started') diff --git a/docker-compose.yml b/docker-compose.yml index 3b3ccee3..19e20345 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -510,7 +510,7 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:2.0 + image: mailcow/dockerapi:2.01 security_opt: - label=disable restart: always From ed7b384e2431e4c98a45c3262c2229b285f778b6 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 25 Jan 2023 09:34:12 +0100 Subject: [PATCH 18/42] [Web] fix queue btn showing undefined --- data/web/js/site/queue.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/web/js/site/queue.js b/data/web/js/site/queue.js index 6b2d2b3b..ebebaff8 100644 --- a/data/web/js/site/queue.js +++ b/data/web/js/site/queue.js @@ -21,7 +21,6 @@ jQuery(function($){ url: '/api/v1/get/postcat/' + button.data('queue-id'), dataType: 'text', complete: function (data) { - console.log(data); $('#queue_msg_content').text(data.responseText); } }); @@ -54,7 +53,7 @@ jQuery(function($){ }); item.recipients = rcpts.join('
'); item.action = ''; }); return data; From 99e38d81b1a34893f5738919e719fa50a032fcb9 Mon Sep 17 00:00:00 2001 From: Niklas Meyer Date: Wed, 25 Jan 2023 16:09:15 +0100 Subject: [PATCH 19/42] Removed Integration Tests --- .github/workflows/integration_tests.yml | 63 ------------------------- README.md | 1 - 2 files changed, 64 deletions(-) delete mode 100644 .github/workflows/integration_tests.yml diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml deleted file mode 100644 index ee083bf4..00000000 --- a/.github/workflows/integration_tests.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: mailcow Integration Tests - -on: - push: - branches: [ "master", "staging" ] - workflow_dispatch: - -permissions: - contents: read - -jobs: - integration_tests: - runs-on: ubuntu-latest - steps: - - name: Setup Ansible - run: | - export DEBIAN_FRONTEND=noninteractive - sudo apt-get update - sudo apt-get install python3 python3-pip git - sudo pip3 install ansible - - name: Prepair Test Environment - run: | - git clone https://github.com/mailcow/mailcow-integration-tests.git --branch $(curl -sL https://api.github.com/repos/mailcow/mailcow-integration-tests/releases/latest | jq -r '.tag_name') --single-branch . - ./fork_check.sh - ./ci.sh - ./ci-pip-requirements.sh - env: - VAULT_PW: ${{ secrets.MAILCOW_TESTS_VAULT_PW }} - VAULT_FILE: ${{ secrets.MAILCOW_TESTS_VAULT_FILE }} - - name: Start Integration Test Server - run: | - ./fork_check.sh - ansible-playbook mailcow-start-server.yml --diff - env: - PY_COLORS: '1' - ANSIBLE_FORCE_COLOR: '1' - ANSIBLE_HOST_KEY_CHECKING: 'false' - - name: Setup Integration Test Server - run: | - ./fork_check.sh - sleep 30 - ansible-playbook mailcow-setup-server.yml --private-key id_ssh_rsa --diff - env: - PY_COLORS: '1' - ANSIBLE_FORCE_COLOR: '1' - ANSIBLE_HOST_KEY_CHECKING: 'false' - - name: Run Integration Tests - run: | - ./fork_check.sh - ansible-playbook mailcow-integration-tests.yml --private-key id_ssh_rsa --diff - env: - PY_COLORS: '1' - ANSIBLE_FORCE_COLOR: '1' - ANSIBLE_HOST_KEY_CHECKING: 'false' - - name: Delete Integration Test Server - if: always() - run: | - ./fork_check.sh - ansible-playbook mailcow-delete-server.yml --diff - env: - PY_COLORS: '1' - ANSIBLE_FORCE_COLOR: '1' - ANSIBLE_HOST_KEY_CHECKING: 'false' diff --git a/README.md b/README.md index b40a767c..c15b8ef0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # mailcow: dockerized - 🐮 + 🐋 = 💕 -[![Mailcow Integration Tests](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml/badge.svg?branch=master)](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml) [![Translation status](https://translate.mailcow.email/widgets/mailcow-dockerized/-/translation/svg-badge.svg)](https://translate.mailcow.email/engage/mailcow-dockerized/) [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/mailcow_email.svg?style=social&label=Follow%20%40mailcow_email)](https://twitter.com/mailcow_email) From 6ff3f3f044b8673d315d0e7dc3f7c33e70bef977 Mon Sep 17 00:00:00 2001 From: realizelol Date: Wed, 25 Jan 2023 23:50:39 +0100 Subject: [PATCH 20/42] [Web] Set pageLength to pagination_size + repect savedState... Fix width in quarantine table. --- data/web/css/site/quarantine.css | 206 +- data/web/js/site/admin.js | 1468 +++++++------- data/web/js/site/debug.js | 144 +- data/web/js/site/edit.js | 442 ++--- data/web/js/site/mailbox.js | 1740 +++++++++-------- data/web/js/site/qhandler.js | 142 +- data/web/js/site/quarantine.js | 583 +++--- data/web/js/site/queue.js | 217 +- data/web/js/site/user.js | 1329 ++++++------- data/web/templates/admin.twig | 6 +- data/web/templates/debug.twig | 40 +- data/web/templates/domainadmin.twig | 2 +- data/web/templates/edit.twig | 2 +- data/web/templates/mailbox.twig | 2 +- data/web/templates/quarantine.twig | 4 +- data/web/templates/queue.twig | 2 +- .../templates/user_domainadmin_common.twig | 2 +- 17 files changed, 3190 insertions(+), 3141 deletions(-) diff --git a/data/web/css/site/quarantine.css b/data/web/css/site/quarantine.css index 98a74d66..0455b7c1 100644 --- a/data/web/css/site/quarantine.css +++ b/data/web/css/site/quarantine.css @@ -1,102 +1,104 @@ -.pagination a { - text-decoration: none !important; -} - -.panel.panel-default { - overflow: visible !important; -} - -.table-responsive { - overflow: visible !important; -} - -.table-responsive { - overflow-x: scroll !important; -} - -.footer-add-item { - display: block; - text-align: center; - font-style: italic; - padding: 10px; - background: #F5F5F5; -} - -@media (min-width: 992px) { - .container { - width: 100%; - } -} -@media (min-width: 1920px) { - .container { - width: 80%; - } -} - -.mass-actions-quarantine { - user-select: none; -} - -.inputMissingAttr { - border-color: #FF4136; -} - -.modal#qidDetailModal p { - word-break: break-all; -} - -span#qid_detail_score { - font-weight: 700; - margin-left: 5px; -} - -span.rspamd-symbol { - display: inline-block; - margin: 2px 6px 2px 0; - border-radius: 4px; - padding: 0 7px; -} - -span.rspamd-symbol.positive { - background: #4CAF50; - border: 1px solid #4CAF50; - color: white; -} - -span.rspamd-symbol.negative { - background: #ff4136; - border: 1px solid #ff4136; - color: white; -} - -span.rspamd-symbol.neutral { - background: #f5f5f5; - color: #333; - border: 1px solid #ccc; -} - -span.rspamd-symbol span.score { - font-weight: 700; -} - -span.mail-address-item { - background-color: #f5f5f5; - border-radius: 4px; - border: 1px solid #ccc; - padding: 2px 7px; - display: inline-block; - margin: 2px 6px 2px 0; -} - -table tbody tr { - cursor: pointer; -} - -table tbody tr td input[type="checkbox"] { - cursor: pointer; -} -.label-rspamd-action { - font-size:110%; - margin:20px; -} - +.pagination a { + text-decoration: none !important; +} + +.panel.panel-default { + overflow: visible !important; +} + +.table-responsive { + overflow: visible !important; +} + +.table-responsive { + overflow-x: scroll !important; +} + +.footer-add-item { + display: block; + text-align: center; + font-style: italic; + padding: 10px; + background: #F5F5F5; +} + +@media (min-width: 992px) { + .container { + width: 100%; + } +} +@media (min-width: 1920px) { + .container { + width: 80%; + } +} + +.mass-actions-quarantine { + user-select: none; +} + +.inputMissingAttr { + border-color: #FF4136; +} + +.modal#qidDetailModal p { + word-break: break-all; +} + +span#qid_detail_score { + font-weight: 700; + margin-left: 5px; +} + +span.rspamd-symbol { + display: inline-block; + margin: 2px 6px 2px 0; + border-radius: 4px; + padding: 0 7px; +} + +span.rspamd-symbol.positive { + background: #4CAF50; + border: 1px solid #4CAF50; + color: white; +} + +span.rspamd-symbol.negative { + background: #ff4136; + border: 1px solid #ff4136; + color: white; +} + +span.rspamd-symbol.neutral { + background: #f5f5f5; + color: #333; + border: 1px solid #ccc; +} + +span.rspamd-symbol span.score { + font-weight: 700; +} + +span.mail-address-item { + background-color: #f5f5f5; + border-radius: 4px; + border: 1px solid #ccc; + padding: 2px 7px; + display: inline-block; + margin: 2px 6px 2px 0; +} + +table tbody tr { + cursor: pointer; +} + +table tbody tr td input[type="checkbox"] { + cursor: pointer; +} +.label-rspamd-action { + font-size:110%; + margin:20px; +} +.senders-mw220 { + max-width: 220px; +} diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index 0dba1aa8..0e5a9ae6 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -1,731 +1,737 @@ -// Base64 functions -var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; -jQuery(function($){ - // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery - var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; - function jq(myid) {return "#" + myid.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1" );} - function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} - function validateRegex(e){var t=e.split("/"),n=e,r="";t.length>1&&(n=t[1],r=t[2]);try{return new RegExp(n,r),!0}catch(e){return!1}} - function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: "/api/v1/get/domain-admin/all", - dataSrc: function(data){ - return process_table_data(data, 'domainadminstable'); - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: lang.admin_domains, - data: 'selected_domains', - defaultContent: '', - }, - { - title: "TFA", - data: 'tfa_active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ], - initComplete: function(settings, json){ - } - }); - } - function draw_oauth2_clients() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#oauth2clientstable') ) { - $('#oauth2clientstable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#oauth2clientstable').DataTable({ - responsive: true, - processing: true, - serverSide: false, - stateSave: true, - dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: "/api/v1/get/oauth2-client/all", - dataSrc: function(data){ - return process_table_data(data, 'oauth2clientstable'); - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: 'ID', - data: 'id', - defaultContent: '' - }, - { - title: lang.oauth2_client_id, - data: 'client_id', - defaultContent: '' - }, - { - title: lang.oauth2_client_secret, - data: 'client_secret', - defaultContent: '' - }, - { - title: lang.oauth2_redirect_uri, - data: 'redirect_uri', - defaultContent: '' - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - function draw_admins() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#adminstable') ) { - $('#adminstable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#adminstable').DataTable({ - responsive: true, - processing: true, - serverSide: false, - stateSave: true, - dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: "/api/v1/get/admin/all", - dataSrc: function(data){ - return process_table_data(data, 'adminstable'); - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: "TFA", - data: 'tfa_active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - defaultContent: '', - className: 'text-md-end dt-sm-head-hidden dt-body-right' - }, - ] - }); - } - function draw_fwd_hosts() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#forwardinghoststable') ) { - $('#forwardinghoststable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#forwardinghoststable').DataTable({ - responsive: true, - processing: true, - serverSide: false, - stateSave: true, - dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: "/api/v1/get/fwdhost/all", - dataSrc: function(data){ - return process_table_data(data, 'forwardinghoststable'); - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: lang.host, - data: 'host', - defaultContent: '' - }, - { - title: lang.source, - data: 'source', - defaultContent: '' - }, - { - title: lang.spamfilter, - data: 'keep_spam', - defaultContent: '', - render: function(data, type){ - return 'yes'==data?'':'no'==data&&''; - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - function draw_relayhosts() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#relayhoststable') ) { - $('#relayhoststable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#relayhoststable').DataTable({ - responsive: true, - processing: true, - serverSide: false, - stateSave: true, - dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: "/api/v1/get/relayhost/all", - dataSrc: function(data){ - return process_table_data(data, 'relayhoststable'); - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: 'ID', - data: 'id', - defaultContent: '' - }, - { - title: lang.host, - data: 'hostname', - defaultContent: '' - }, - { - title: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: lang.in_use_by, - data: 'in_use_by', - defaultContent: '' - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - function draw_transport_maps() { - // just recalc width if instance already exists - if ($.fn.DataTable.isDataTable('#transportstable') ) { - $('#transportstable').DataTable().columns.adjust().responsive.recalc(); - return; - } - - $('#transportstable').DataTable({ - responsive: true, - processing: true, - serverSide: false, - stateSave: true, - dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: "/api/v1/get/transport/all", - dataSrc: function(data){ - return process_table_data(data, 'transportstable'); - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: 'ID', - data: 'id', - defaultContent: '' - }, - { - title: lang.destination, - data: 'destination', - defaultContent: '' - }, - { - title: lang.nexthop, - data: 'nexthop', - defaultContent: '' - }, - { - title: lang.username, - data: 'username', - defaultContent: '' - }, - { - title: lang.active, - data: 'active', - defaultContent: '', - render: function (data, type) { - if(data == 1) return ''; - else return '' - } - }, - { - title: lang.action, - data: 'action', - className: 'text-md-end dt-sm-head-hidden dt-body-right', - defaultContent: '' - }, - ] - }); - } - - function process_table_data(data, table) { - if (table == 'relayhoststable') { - $.each(data, function (i, item) { - item.action = ''; - if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; } - else if (item.used_by_domains == '') { item.in_use_by = item.used_by_mailboxes; } - else { item.in_use_by = item.used_by_mailboxes + '
' + item.used_by_domains; } - item.chkbox = ''; - }); - } else if (table == 'transportstable') { - $.each(data, function (i, item) { - if (item.is_mx_based) { - item.destination = ' ' + item.destination + ''; - } - if (item.username) { - item.username = ' ' + item.username; - } - item.action = ''; - item.chkbox = ''; - }); - } else if (table == 'queuetable') { - $.each(data, function (i, item) { - item.chkbox = ''; - rcpts = $.map(item.recipients, function(i) { - return escapeHtml(i); - }); - item.recipients = rcpts.join('
'); - item.action = ''; - }); - } else if (table == 'forwardinghoststable') { - $.each(data, function (i, item) { - item.action = '
' + - ' ' + lang.remove + '' + - '
'; - item.chkbox = ''; - }); - } else if (table == 'oauth2clientstable') { - $.each(data, function (i, item) { - item.action = ''; - item.scope = "profile"; - item.grant_types = 'refresh_token password authorization_code'; - item.chkbox = ''; - }); - } else if (table == 'domainadminstable') { - $.each(data, function (i, item) { - item.selected_domains = escapeHtml(item.selected_domains); - item.selected_domains = item.selected_domains.toString().replace(/,/g, "
"); - item.chkbox = ''; - item.action = ''; - }); - } else if (table == 'adminstable') { - $.each(data, function (i, item) { - if (admin_username.toLowerCase() == item.username.toLowerCase()) { - item.usr = ' ' + item.username; - } else { - item.usr = item.username; - } - item.chkbox = ''; - item.action = ''; - }); - } - return data - }; - - // detect element visibility changes - function onVisible(element, callback) { - $(document).ready(function() { - element_object = document.querySelector(element); - if (element_object === null) return; - - new IntersectionObserver((entries, observer) => { - entries.forEach(entry => { - if(entry.intersectionRatio > 0) { - callback(element_object); - } - }); - }).observe(element_object); - }); - } - // Draw Table if tab is active - onVisible("[id^=adminstable]", () => draw_admins()); - onVisible("[id^=domainadminstable]", () => draw_domain_admins()); - onVisible("[id^=oauth2clientstable]", () => draw_oauth2_clients()); - onVisible("[id^=forwardinghoststable]", () => draw_fwd_hosts()); - onVisible("[id^=relayhoststable]", () => draw_relayhosts()); - onVisible("[id^=transportstable]", () => draw_transport_maps()); - - - $('body').on('click', 'span.footable-toggle', function () { - event.stopPropagation(); - }) - - // API IP check toggle - $("#skip_ip_check_ro").click(function( event ) { - $("#skip_ip_check_ro").not(this).prop('checked', false); - if ($("#skip_ip_check_ro:checked").length > 0) { - $('#allow_from_ro').prop('disabled', true); - } - else { - $("#allow_from_ro").removeAttr('disabled'); - } - }); - $("#skip_ip_check_rw").click(function( event ) { - $("#skip_ip_check_rw").not(this).prop('checked', false); - if ($("#skip_ip_check_rw:checked").length > 0) { - $('#allow_from_rw').prop('disabled', true); - } - else { - $("#allow_from_rw").removeAttr('disabled'); - } - }); - // Relayhost - $('#testRelayhostModal').on('show.bs.modal', function (e) { - $('#test_relayhost_result').text("-"); - button = $(e.relatedTarget) - if (button != null) { - $('#relayhost_id').val(button.data('relayhost-id')); - } - }) - $('#test_relayhost').on('click', function (e) { - e.preventDefault(); - prev = $('#test_relayhost').text(); - $(this).prop("disabled",true); - $(this).html(' '); - $.ajax({ - type: 'GET', - url: 'inc/ajax/relay_check.php', - dataType: 'text', - data: $('#test_relayhost_form').serialize(), - complete: function (data) { - $('#test_relayhost_result').html(data.responseText); - $('#test_relayhost').prop("disabled",false); - $('#test_relayhost').text(prev); - } - }); - }) - // Transport - $('#testTransportModal').on('show.bs.modal', function (e) { - $('#test_transport_result').text("-"); - button = $(e.relatedTarget) - if (button != null) { - $('#transport_id').val(button.data('transport-id')); - $('#transport_type').val(button.data('transport-type')); - } - }) - $('#test_transport').on('click', function (e) { - e.preventDefault(); - prev = $('#test_transport').text(); - $(this).prop("disabled",true); - $(this).html('
Loading...
'); - $.ajax({ - type: 'GET', - url: 'inc/ajax/transport_check.php', - dataType: 'text', - data: $('#test_transport_form').serialize(), - complete: function (data) { - $('#test_transport_result').html(data.responseText); - $('#test_transport').prop("disabled",false); - $('#test_transport').text(prev); - } - }); - }) - // DKIM private key modal - $('#showDKIMprivKey').on('show.bs.modal', function (e) { - $('#priv_key_pre').text("-"); - p_related = $(e.relatedTarget) - if (p_related != null) { - var decoded_key = Base64.decode((p_related.data('priv-key'))); - $('#priv_key_pre').text(decoded_key); - } - }) - // FIDO2 friendly name modal - $('#fido2ChangeFn').on('show.bs.modal', function (e) { - rename_link = $(e.relatedTarget) - if (rename_link != null) { - $('#fido2_cid').val(rename_link.data('cid')); - $('#fido2_subject_desc').text(Base64.decode(rename_link.data('subject'))); - } - }) - // App links - function add_table_row(table_id, type) { - var row = $(''); - if (type == "app_link") { - cols = ''; - cols += ''; - cols += '' + lang.remove_row + ''; - } else if (type == "f2b_regex") { - cols = ''; - cols += ''; - cols += '' + lang.remove_row + ''; - } - row.append(cols); - table_id.append(row); - } - $('#app_link_table').on('click', 'tr a', function (e) { - e.preventDefault(); - $(this).parents('tr').remove(); - }); - $('#f2b_regex_table').on('click', 'tr a', function (e) { - e.preventDefault(); - $(this).parents('tr').remove(); - }); - $('#add_app_link_row').click(function() { - add_table_row($('#app_link_table'), "app_link"); - }); - $('#add_f2b_regex_row').click(function() { - add_table_row($('#f2b_regex_table'), "f2b_regex"); - }); -}); +// Base64 functions +var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; +jQuery(function($){ + // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery + var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; + function jq(myid) {return "#" + myid.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1" );} + function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} + function validateRegex(e){var t=e.split("/"),n=e,r="";t.length>1&&(n=t[1],r=t[2]);try{return new RegExp(n,r),!0}catch(e){return!1}} + function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: "/api/v1/get/domain-admin/all", + dataSrc: function(data){ + return process_table_data(data, 'domainadminstable'); + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: lang.admin_domains, + data: 'selected_domains', + defaultContent: '', + }, + { + title: "TFA", + data: 'tfa_active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + className: 'text-md-end dt-sm-head-hidden dt-body-right', + defaultContent: '' + }, + ], + initComplete: function(settings, json){ + } + }); + } + function draw_oauth2_clients() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#oauth2clientstable') ) { + $('#oauth2clientstable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#oauth2clientstable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + stateSave: true, + pageLength: pagination_size, + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: "/api/v1/get/oauth2-client/all", + dataSrc: function(data){ + return process_table_data(data, 'oauth2clientstable'); + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: 'ID', + data: 'id', + defaultContent: '' + }, + { + title: lang.oauth2_client_id, + data: 'client_id', + defaultContent: '' + }, + { + title: lang.oauth2_client_secret, + data: 'client_secret', + defaultContent: '' + }, + { + title: lang.oauth2_redirect_uri, + data: 'redirect_uri', + defaultContent: '' + }, + { + title: lang.action, + data: 'action', + className: 'text-md-end dt-sm-head-hidden dt-body-right', + defaultContent: '' + }, + ] + }); + } + function draw_admins() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#adminstable') ) { + $('#adminstable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#adminstable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + stateSave: true, + pageLength: pagination_size, + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: "/api/v1/get/admin/all", + dataSrc: function(data){ + return process_table_data(data, 'adminstable'); + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: "TFA", + data: 'tfa_active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + defaultContent: '', + className: 'text-md-end dt-sm-head-hidden dt-body-right' + }, + ] + }); + } + function draw_fwd_hosts() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#forwardinghoststable') ) { + $('#forwardinghoststable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#forwardinghoststable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + stateSave: true, + pageLength: pagination_size, + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: "/api/v1/get/fwdhost/all", + dataSrc: function(data){ + return process_table_data(data, 'forwardinghoststable'); + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: lang.host, + data: 'host', + defaultContent: '' + }, + { + title: lang.source, + data: 'source', + defaultContent: '' + }, + { + title: lang.spamfilter, + data: 'keep_spam', + defaultContent: '', + render: function(data, type){ + return 'yes'==data?'':'no'==data&&''; + } + }, + { + title: lang.action, + data: 'action', + className: 'text-md-end dt-sm-head-hidden dt-body-right', + defaultContent: '' + }, + ] + }); + } + function draw_relayhosts() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#relayhoststable') ) { + $('#relayhoststable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#relayhoststable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + stateSave: true, + pageLength: pagination_size, + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: "/api/v1/get/relayhost/all", + dataSrc: function(data){ + return process_table_data(data, 'relayhoststable'); + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: 'ID', + data: 'id', + defaultContent: '' + }, + { + title: lang.host, + data: 'hostname', + defaultContent: '' + }, + { + title: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: lang.in_use_by, + data: 'in_use_by', + defaultContent: '' + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + className: 'text-md-end dt-sm-head-hidden dt-body-right', + defaultContent: '' + }, + ] + }); + } + function draw_transport_maps() { + // just recalc width if instance already exists + if ($.fn.DataTable.isDataTable('#transportstable') ) { + $('#transportstable').DataTable().columns.adjust().responsive.recalc(); + return; + } + + $('#transportstable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + stateSave: true, + pageLength: pagination_size, + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: "/api/v1/get/transport/all", + dataSrc: function(data){ + return process_table_data(data, 'transportstable'); + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: 'ID', + data: 'id', + defaultContent: '' + }, + { + title: lang.destination, + data: 'destination', + defaultContent: '' + }, + { + title: lang.nexthop, + data: 'nexthop', + defaultContent: '' + }, + { + title: lang.username, + data: 'username', + defaultContent: '' + }, + { + title: lang.active, + data: 'active', + defaultContent: '', + render: function (data, type) { + if(data == 1) return ''; + else return ''; + } + }, + { + title: lang.action, + data: 'action', + className: 'text-md-end dt-sm-head-hidden dt-body-right', + defaultContent: '' + }, + ] + }); + } + + function process_table_data(data, table) { + if (table == 'relayhoststable') { + $.each(data, function (i, item) { + item.action = ''; + if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; } + else if (item.used_by_domains == '') { item.in_use_by = item.used_by_mailboxes; } + else { item.in_use_by = item.used_by_mailboxes + '
' + item.used_by_domains; } + item.chkbox = ''; + }); + } else if (table == 'transportstable') { + $.each(data, function (i, item) { + if (item.is_mx_based) { + item.destination = ' ' + item.destination + ''; + } + if (item.username) { + item.username = ' ' + item.username; + } + item.action = ''; + item.chkbox = ''; + }); + } else if (table == 'queuetable') { + $.each(data, function (i, item) { + item.chkbox = ''; + rcpts = $.map(item.recipients, function(i) { + return escapeHtml(i); + }); + item.recipients = rcpts.join('
'); + item.action = ''; + }); + } else if (table == 'forwardinghoststable') { + $.each(data, function (i, item) { + item.action = '
' + + ' ' + lang.remove + '' + + '
'; + item.chkbox = ''; + }); + } else if (table == 'oauth2clientstable') { + $.each(data, function (i, item) { + item.action = ''; + item.scope = "profile"; + item.grant_types = 'refresh_token password authorization_code'; + item.chkbox = ''; + }); + } else if (table == 'domainadminstable') { + $.each(data, function (i, item) { + item.selected_domains = escapeHtml(item.selected_domains); + item.selected_domains = item.selected_domains.toString().replace(/,/g, "
"); + item.chkbox = ''; + item.action = ''; + }); + } else if (table == 'adminstable') { + $.each(data, function (i, item) { + if (admin_username.toLowerCase() == item.username.toLowerCase()) { + item.usr = ' ' + item.username; + } else { + item.usr = item.username; + } + item.chkbox = ''; + item.action = ''; + }); + } + return data + }; + + // detect element visibility changes + function onVisible(element, callback) { + $(document).ready(function() { + element_object = document.querySelector(element); + if (element_object === null) return; + + new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if(entry.intersectionRatio > 0) { + callback(element_object); + } + }); + }).observe(element_object); + }); + } + // Draw Table if tab is active + onVisible("[id^=adminstable]", () => draw_admins()); + onVisible("[id^=domainadminstable]", () => draw_domain_admins()); + onVisible("[id^=oauth2clientstable]", () => draw_oauth2_clients()); + onVisible("[id^=forwardinghoststable]", () => draw_fwd_hosts()); + onVisible("[id^=relayhoststable]", () => draw_relayhosts()); + onVisible("[id^=transportstable]", () => draw_transport_maps()); + + + $('body').on('click', 'span.footable-toggle', function () { + event.stopPropagation(); + }) + + // API IP check toggle + $("#skip_ip_check_ro").click(function( event ) { + $("#skip_ip_check_ro").not(this).prop('checked', false); + if ($("#skip_ip_check_ro:checked").length > 0) { + $('#allow_from_ro').prop('disabled', true); + } + else { + $("#allow_from_ro").removeAttr('disabled'); + } + }); + $("#skip_ip_check_rw").click(function( event ) { + $("#skip_ip_check_rw").not(this).prop('checked', false); + if ($("#skip_ip_check_rw:checked").length > 0) { + $('#allow_from_rw').prop('disabled', true); + } + else { + $("#allow_from_rw").removeAttr('disabled'); + } + }); + // Relayhost + $('#testRelayhostModal').on('show.bs.modal', function (e) { + $('#test_relayhost_result').text("-"); + button = $(e.relatedTarget) + if (button != null) { + $('#relayhost_id').val(button.data('relayhost-id')); + } + }) + $('#test_relayhost').on('click', function (e) { + e.preventDefault(); + prev = $('#test_relayhost').text(); + $(this).prop("disabled",true); + $(this).html(' '); + $.ajax({ + type: 'GET', + url: 'inc/ajax/relay_check.php', + dataType: 'text', + data: $('#test_relayhost_form').serialize(), + complete: function (data) { + $('#test_relayhost_result').html(data.responseText); + $('#test_relayhost').prop("disabled",false); + $('#test_relayhost').text(prev); + } + }); + }) + // Transport + $('#testTransportModal').on('show.bs.modal', function (e) { + $('#test_transport_result').text("-"); + button = $(e.relatedTarget) + if (button != null) { + $('#transport_id').val(button.data('transport-id')); + $('#transport_type').val(button.data('transport-type')); + } + }) + $('#test_transport').on('click', function (e) { + e.preventDefault(); + prev = $('#test_transport').text(); + $(this).prop("disabled",true); + $(this).html('
Loading...
'); + $.ajax({ + type: 'GET', + url: 'inc/ajax/transport_check.php', + dataType: 'text', + data: $('#test_transport_form').serialize(), + complete: function (data) { + $('#test_transport_result').html(data.responseText); + $('#test_transport').prop("disabled",false); + $('#test_transport').text(prev); + } + }); + }) + // DKIM private key modal + $('#showDKIMprivKey').on('show.bs.modal', function (e) { + $('#priv_key_pre').text("-"); + p_related = $(e.relatedTarget) + if (p_related != null) { + var decoded_key = Base64.decode((p_related.data('priv-key'))); + $('#priv_key_pre').text(decoded_key); + } + }) + // FIDO2 friendly name modal + $('#fido2ChangeFn').on('show.bs.modal', function (e) { + rename_link = $(e.relatedTarget) + if (rename_link != null) { + $('#fido2_cid').val(rename_link.data('cid')); + $('#fido2_subject_desc').text(Base64.decode(rename_link.data('subject'))); + } + }) + // App links + function add_table_row(table_id, type) { + var row = $(''); + if (type == "app_link") { + cols = ''; + cols += ''; + cols += '' + lang.remove_row + ''; + } else if (type == "f2b_regex") { + cols = ''; + cols += ''; + cols += '' + lang.remove_row + ''; + } + row.append(cols); + table_id.append(row); + } + $('#app_link_table').on('click', 'tr a', function (e) { + e.preventDefault(); + $(this).parents('tr').remove(); + }); + $('#f2b_regex_table').on('click', 'tr a', function (e) { + e.preventDefault(); + $(this).parents('tr').remove(); + }); + $('#add_app_link_row').click(function() { + add_table_row($('#app_link_table'), "app_link"); + }); + $('#add_f2b_regex_row').click(function() { + add_table_row($('#f2b_regex_table'), "f2b_regex"); + }); +}); diff --git a/data/web/js/site/debug.js b/data/web/js/site/debug.js index a7b06e5a..e0b9a5ab 100644 --- a/data/web/js/site/debug.js +++ b/data/web/js/site/debug.js @@ -34,7 +34,7 @@ $(document).ready(function() { }); // set update loop container list - containersToUpdate = {} + containersToUpdate = {}; // set default ChartJs Font Color Chart.defaults.color = '#999'; // create host cpu and mem charts @@ -44,14 +44,13 @@ $(document).ready(function() { check_update(mailcow_info.version_tag, mailcow_info.project_url); } $("#maiclow_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; showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag); }) // get public ips - $("#host_show_ip").click(function(){ + $("#host_show_ip").click(function(){ $("#host_show_ip").find(".text").addClass("d-none"); $("#host_show_ip").find(".spinner-border").removeClass("d-none"); @@ -76,7 +75,7 @@ $(document).ready(function() { $("#host_ipv6").addClass("d-block"); }).catch(function(error){ console.log(error); - + $("#host_ipv6").removeClass("d-none"); $("#host_ipv6").addClass("d-block"); $("#host_ipv6").addClass("text-danger"); @@ -119,10 +118,11 @@ jQuery(function($){ } var table = $('#autodiscover_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -188,10 +188,11 @@ jQuery(function($){ } var table = $('#postfix_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -242,10 +243,11 @@ jQuery(function($){ } var table = $('#watchdog_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -300,10 +302,11 @@ jQuery(function($){ } var table = $('#api_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -352,7 +355,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-api-logs', '#api_log'); }); @@ -365,10 +368,11 @@ jQuery(function($){ } var table = $('#rl_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -455,7 +459,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-rl-logs', '#rl_log'); }); @@ -468,10 +472,11 @@ jQuery(function($){ } var table = $('#ui_logs').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -538,7 +543,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-ui-logs', '#ui_log'); }); @@ -551,10 +556,11 @@ jQuery(function($){ } var table = $('#sasl_logs').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -598,7 +604,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-sasl-logs', '#sasl_logs'); }); @@ -611,10 +617,11 @@ jQuery(function($){ } var table = $('#acme_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -647,7 +654,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-acme-logs', '#acme_log'); }); @@ -660,10 +667,11 @@ jQuery(function($){ } var table = $('#netfilter_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -701,7 +709,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-netfilter-logs', '#netfilter_log'); }); @@ -714,10 +722,11 @@ jQuery(function($){ } var table = $('#sogo_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -755,7 +764,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-sogo-logs', '#sogo_log'); }); @@ -768,10 +777,11 @@ jQuery(function($){ } var table = $('#dovecot_log').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -883,10 +893,11 @@ jQuery(function($){ } var table = $('#rspamd_history').DataTable({ - responsive: true, + responsive: true, processing: true, serverSide: false, stateSave: true, + pageLength: log_pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + "tr" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", @@ -983,7 +994,7 @@ jQuery(function($){ } ] }); - + table.on('responsive-resize', function (e, datatable, columns){ hideTableExpandCollapseBtn('#tab-rspamd-history', '#rspamd_history'); }); @@ -998,31 +1009,31 @@ jQuery(function($){ item.rcpt = escapeHtml(item.rcpt_smtp.join(", ")); } item.symbols = Object.keys(item.symbols).sort(function (a, b) { - if (item.symbols[a].score === 0) return 1 - if (item.symbols[b].score === 0) return -1 + if (item.symbols[a].score === 0) return 1; + if (item.symbols[b].score === 0) return -1; if (item.symbols[b].score < 0 && item.symbols[a].score < 0) { - return item.symbols[a].score - item.symbols[b].score + return item.symbols[a].score - item.symbols[b].score; } if (item.symbols[b].score > 0 && item.symbols[a].score > 0) { - return item.symbols[b].score - item.symbols[a].score + return item.symbols[b].score - item.symbols[a].score; } - return item.symbols[b].score - item.symbols[a].score + return item.symbols[b].score - item.symbols[a].score; }).map(function(key) { var sym = item.symbols[key]; if (sym.score < 0) { - sym.score_formatted = '(' + sym.score + ')' + sym.score_formatted = '(' + sym.score + ')'; } else if (sym.score === 0) { - sym.score_formatted = '(' + sym.score + ')' + sym.score_formatted = '(' + sym.score + ')'; } else { - sym.score_formatted = '(' + sym.score + ')' + sym.score_formatted = '(' + sym.score + ')'; } var str = '' + key + ' ' + sym.score_formatted; if (sym.options) { str += ' [' + escapeHtml(sym.options.join(", ")) + "]"; } - return str + return str; }).join('
\n'); item.subject = escapeHtml(item.subject); var scan_time = item.time_real.toFixed(3); @@ -1155,14 +1166,14 @@ jQuery(function($){ } }); } - return data + return data; }; $('.add_log_lines').on('click', function (e) { e.preventDefault(); - var log_table= $(this).data("table") - var new_nrows = $(this).data("nrows") - var post_process = $(this).data("post-process") - var log_url = $(this).data("log-url") + var log_table= $(this).data("table"); + var new_nrows = $(this).data("nrows"); + var post_process = $(this).data("post-process"); + var log_url = $(this).data("log-url"); if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) { console.log("no data-table or data-nrows or log_url or data-post-process attr found"); return; @@ -1184,9 +1195,9 @@ jQuery(function($){ }) function hideTableExpandCollapseBtn(tab, table){ if ($(table).hasClass('collapsed')) - $(tab).find(".table_collapse_option").show(); + $(tab).find(".table_collapse_option").show(); else - $(tab).find(".table_collapse_option").hide(); + $(tab).find(".table_collapse_option").hide(); } // detect element visibility changes @@ -1220,7 +1231,6 @@ jQuery(function($){ onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph()); - // start polling host stats if tab is active onVisible("[id^=tab-containers]", () => update_stats()); // start polling container stats if collapse is active @@ -1303,9 +1313,9 @@ function update_stats(timeout=5){ if (mem_chart.data.labels.length > 30) mem_chart.data.labels.shift(); cpu_chart.data.datasets[0].data.push(data.cpu.usage); - if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift(); + if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift(); mem_chart.data.datasets[0].data.push(data.memory.usage); - if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift(); + if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift(); cpu_chart.update(); mem_chart.update(); @@ -1464,23 +1474,23 @@ function createReadWriteChart(chart_id, read_lable, write_lable){ }; var optionsNet = { interaction: { - mode: 'index' + mode: 'index' }, scales: { yAxis: { min: 0, grid: { - display: false + display: false }, ticks: { callback: function(i, index, ticks) { - return formatBytes(i); + return formatBytes(i); } } }, xAxis: { grid: { - display: false + display: false } } } @@ -1528,13 +1538,13 @@ function createHostCpuAndMemChart(){ }; var optionsCpu = { interaction: { - mode: 'index' + mode: 'index' }, scales: { yAxis: { min: 0, grid: { - display: false + display: false }, ticks: { callback: function(i, index, ticks) { @@ -1544,7 +1554,7 @@ function createHostCpuAndMemChart(){ }, xAxis: { grid: { - display: false + display: false } } } @@ -1566,13 +1576,13 @@ function createHostCpuAndMemChart(){ }; var optionsMem = { interaction: { - mode: 'index' + mode: 'index' }, scales: { yAxis: { min: 0, grid: { - display: false + display: false }, ticks: { callback: function(i, index, ticks) { @@ -1582,7 +1592,7 @@ function createHostCpuAndMemChart(){ }, xAxis: { grid: { - display: false + display: false } } } @@ -1678,22 +1688,22 @@ function parseGithubMarkdownLinks(inputText) { replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => { - if (matched.includes('github.com')){ - // return short link if it's github link - last_uri_path = matched.split('/'); - last_uri_path = last_uri_path[last_uri_path.length - 1]; + if (matched.includes('github.com')){ + // return short link if it's github link + last_uri_path = matched.split('/'); + last_uri_path = last_uri_path[last_uri_path.length - 1]; - // adjust Full Changelog link to match last git version and new git version, if link is a compare link - if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){ - matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.version_tag); - last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag; - } + // adjust Full Changelog link to match last git version and new git version, if link is a compare link + if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){ + matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.version_tag); + last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag; + } - return '' + last_uri_path + '
'; - }; + return '' + last_uri_path + '
'; + }; - // if it's not a github link, return complete link - return '' + matched + ''; + // if it's not a github link, return complete link + return '' + matched + ''; }); return replacedText; diff --git a/data/web/js/site/edit.js b/data/web/js/site/edit.js index 4c57b35e..4680bdfa 100644 --- a/data/web/js/site/edit.js +++ b/data/web/js/site/edit.js @@ -1,220 +1,222 @@ -$(document).ready(function() { - $(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); }); - $("#pushover_delete").click(function() { return confirm(lang.delete_ays); }); - $(".goto_checkbox").click(function( event ) { - $("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false); - if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { - $('#textarea_alias_goto').prop('disabled', true); - } - else { - $("#textarea_alias_goto").removeAttr('disabled'); - } - }); - $("#disable_sender_check").click(function( event ) { - if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) { - $('#editSelectSenderACL').prop('disabled', true); - $('#editSelectSenderACL').selectpicker('refresh'); - } - else { - $('#editSelectSenderACL').prop('disabled', false); - $('#editSelectSenderACL').selectpicker('refresh'); - } - }); - if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { - $('#textarea_alias_goto').prop('disabled', true); - } - - $("#mailbox-password-warning-close").click(function( event ) { - $('#mailbox-passwd-hidden-info').addClass('hidden'); - $('#mailbox-passwd-form-groups').removeClass('hidden'); - }); - // Sender ACL - if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ - $("#sender_acl_disabled").show(); - } - $('#editSelectSenderACL').change(function() { - if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ - $("#sender_acl_disabled").show(); - } - else { - $("#sender_acl_disabled").hide(); - } - }); - // Resources - if ($("#editSelectMultipleBookings").val() == "custom") { - $("#multiple_bookings_custom_div").show(); - $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); - } - $("#editSelectMultipleBookings").change(function() { - $('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val()); - if ($('input[name=multiple_bookings]').val() == "custom") { - $("#multiple_bookings_custom_div").show(); - } - else { - $("#multiple_bookings_custom_div").hide(); - } - }); - $("#multiple_bookings_custom").bind("change keypress keyup blur", function() { - $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); - }); - - // load tags - if ($('#tags').length){ - var tagsEl = $('#tags').parent().find('.tag-values')[0]; - console.log($(tagsEl).val()) - var tags = JSON.parse($(tagsEl).val()); - $(tagsEl).val(""); - - for (var i = 0; i < tags.length; i++) - addTag($('#tags'), tags[i]); - } -}); - -jQuery(function($){ - // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript - function validateEmail(email) { - var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(email); - } - function draw_wl_policy_domain_table() { - $('#wl_policy_domain_table').DataTable({ - responsive: true, - processing: true, - serverSide: false, - stateSave: true, - dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: '/api/v1/get/policy_wl_domain/' + table_for_domain, - dataSrc: function(data){ - $.each(data, function (i, item) { - if (!validateEmail(item.object)) { - item.chkbox = ''; - } - else { - item.chkbox = ''; - } - }); - - return data; - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: 'ID', - data: 'prefid', - defaultContent: '' - }, - { - title: lang_user.spamfilter_table_rule, - data: 'value', - defaultContent: '' - }, - { - title: 'Scope', - data: 'object', - defaultContent: '' - } - ] - }); - } - function draw_bl_policy_domain_table() { - $('#bl_policy_domain_table').DataTable({ - responsive: true, - processing: true, - serverSide: false, - stateSave: true, - dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + - "tr" + - "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", - language: lang_datatables, - ajax: { - type: "GET", - url: '/api/v1/get/policy_bl_domain/' + table_for_domain, - dataSrc: function(data){ - $.each(data, function (i, item) { - if (!validateEmail(item.object)) { - item.chkbox = ''; - } - else { - item.chkbox = ''; - } - }); - - return data; - } - }, - columns: [ - { - // placeholder, so checkbox will not block child row toggle - title: '', - data: null, - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: '', - data: 'chkbox', - searchable: false, - orderable: false, - defaultContent: '' - }, - { - title: 'ID', - data: 'prefid', - defaultContent: '' - }, - { - title: lang_user.spamfilter_table_rule, - data: 'value', - defaultContent: '' - }, - { - title: 'Scope', - data: 'object', - defaultContent: '' - } - ] - }); - } - - - // detect element visibility changes - function onVisible(element, callback) { - $(document).ready(function() { - element_object = document.querySelector(element); - if (element_object === null) return; - - new IntersectionObserver((entries, observer) => { - entries.forEach(entry => { - if(entry.intersectionRatio > 0) { - callback(element_object); - observer.disconnect(); - } - }); - }).observe(element_object); - }); - } - // Draw Table if tab is active - onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table()); - onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table()); -}); +$(document).ready(function() { + $(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); }); + $("#pushover_delete").click(function() { return confirm(lang.delete_ays); }); + $(".goto_checkbox").click(function( event ) { + $("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false); + if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { + $('#textarea_alias_goto').prop('disabled', true); + } + else { + $("#textarea_alias_goto").removeAttr('disabled'); + } + }); + $("#disable_sender_check").click(function( event ) { + if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) { + $('#editSelectSenderACL').prop('disabled', true); + $('#editSelectSenderACL').selectpicker('refresh'); + } + else { + $('#editSelectSenderACL').prop('disabled', false); + $('#editSelectSenderACL').selectpicker('refresh'); + } + }); + if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { + $('#textarea_alias_goto').prop('disabled', true); + } + + $("#mailbox-password-warning-close").click(function( event ) { + $('#mailbox-passwd-hidden-info').addClass('hidden'); + $('#mailbox-passwd-form-groups').removeClass('hidden'); + }); + // Sender ACL + if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ + $("#sender_acl_disabled").show(); + } + $('#editSelectSenderACL').change(function() { + if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){ + $("#sender_acl_disabled").show(); + } + else { + $("#sender_acl_disabled").hide(); + } + }); + // Resources + if ($("#editSelectMultipleBookings").val() == "custom") { + $("#multiple_bookings_custom_div").show(); + $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); + } + $("#editSelectMultipleBookings").change(function() { + $('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val()); + if ($('input[name=multiple_bookings]').val() == "custom") { + $("#multiple_bookings_custom_div").show(); + } + else { + $("#multiple_bookings_custom_div").hide(); + } + }); + $("#multiple_bookings_custom").bind("change keypress keyup blur", function() { + $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); + }); + + // load tags + if ($('#tags').length){ + var tagsEl = $('#tags').parent().find('.tag-values')[0]; + console.log($(tagsEl).val()) + var tags = JSON.parse($(tagsEl).val()); + $(tagsEl).val(""); + + for (var i = 0; i < tags.length; i++) + addTag($('#tags'), tags[i]); + } +}); + +jQuery(function($){ + // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript + function validateEmail(email) { + var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + function draw_wl_policy_domain_table() { + $('#wl_policy_domain_table').DataTable({ + responsive: true, + processing: true, + serverSide: false, + stateSave: true, + pageLength: pagination_size, + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: '/api/v1/get/policy_wl_domain/' + table_for_domain, + dataSrc: function(data){ + $.each(data, function (i, item) { + if (!validateEmail(item.object)) { + item.chkbox = ''; + } + else { + item.chkbox = ''; + } + }); + + return data; + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: 'ID', + data: 'prefid', + defaultContent: '' + }, + { + title: lang_user.spamfilter_table_rule, + data: 'value', + defaultContent: '' + }, + { + title: 'Scope', + data: 'object', + defaultContent: '' + } + ] + }); + } + function draw_bl_policy_domain_table() { + $('#bl_policy_domain_table').DataTable({ + responsive: true, + processing: true, + serverSide: false, + stateSave: true, + pageLength: pagination_size, + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + + "tr" + + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", + language: lang_datatables, + ajax: { + type: "GET", + url: '/api/v1/get/policy_bl_domain/' + table_for_domain, + dataSrc: function(data){ + $.each(data, function (i, item) { + if (!validateEmail(item.object)) { + item.chkbox = ''; + } + else { + item.chkbox = ''; + } + }); + + return data; + } + }, + columns: [ + { + // placeholder, so checkbox will not block child row toggle + title: '', + data: null, + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: '', + data: 'chkbox', + searchable: false, + orderable: false, + defaultContent: '' + }, + { + title: 'ID', + data: 'prefid', + defaultContent: '' + }, + { + title: lang_user.spamfilter_table_rule, + data: 'value', + defaultContent: '' + }, + { + title: 'Scope', + data: 'object', + defaultContent: '' + } + ] + }); + } + + + // detect element visibility changes + function onVisible(element, callback) { + $(document).ready(function() { + element_object = document.querySelector(element); + if (element_object === null) return; + + new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if(entry.intersectionRatio > 0) { + callback(element_object); + observer.disconnect(); + } + }); + }).observe(element_object); + }); + } + // Draw Table if tab is active + onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table()); + onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table()); +}); diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index b93b1819..49cce1b2 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -77,7 +77,7 @@ $(document).ready(function() { $('.dns-modal-body').html(xhr.responseText); } }); - }); + }); // @Open Domain add modal $('#addDomainModal').on('show.bs.modal', function(e) { $.ajax({ @@ -85,24 +85,24 @@ $(document).ready(function() { data: {}, dataType: 'json', success: async function(data){ - $('#domain_templates').find('option').remove(); + $('#domain_templates').find('option').remove(); $('#domain_templates').selectpicker('destroy'); $('#domain_templates').selectpicker(); for (var i = 0; i < data.length; i++){ if (data[i].template === "Default"){ - $('#domain_templates').prepend($('