Compare commits

..

30 Commits

Author SHA1 Message Date
Patrick Schult
df8775d4c9 Merge pull request #5040 from mailcow/staging
2023-02
2023-02-02 15:31:34 +01:00
Niklas Meyer
2bc663dcd5 Removed Twitter Action due to Twitter Paid API (soon). Thx Elon! 2023-02-02 14:55:44 +01:00
Patrick Schult
1071bb8230 Merge pull request #4967 from FELDSAM-INC/feldsam/sso
[Web] Implemented SSO for domain admins
2023-02-02 12:12:53 +01:00
Niklas Meyer
e437810eca Merge pull request #5038 from mailcow/fix/sogo-macos-fix
[Fix] SOGo Update Fix for 5.8.0 (macOS fix)
2023-02-02 11:32:35 +01:00
FreddleSpl0it
e8fd34d31f [Web] webauthn add lang strings 2023-02-02 11:28:51 +01:00
Niklas Meyer
6aebb8352e [Fix] SOGo Update Fix for 5.8.0 (macOS fix) 2023-02-02 11:03:51 +01:00
Patrick Schult
d684e0efc0 Merge pull request #5034 from mailcow/fix/skip-sogo
[Web] Skip update_sogo_static_view if sogo is disabled
2023-01-31 11:03:50 +01:00
FreddleSpl0it
64ac6a8891 [Web] Skip update_sogo_static_view if sogo is disabled 2023-01-31 10:54:16 +01:00
FreddleSpl0it
72e8180c6b [Web] datatable adjustment 2023-01-31 10:37:51 +01:00
FreddleSpl0it
d62c275004 [Web] match PAGINATION_SIZE to an existing datatable option 2023-01-31 09:49:18 +01:00
Patrick Schult
aa7f562761 Merge pull request #5011 from realizelol/staging
[BS5] Support for pagination_size + some minor improvements (to quarantine)
2023-01-31 09:43:51 +01:00
renovate[bot]
a1f033e4c1 Update docker/build-push-action action to v4 (#5032)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-30 19:58:17 +01:00
milkmaker
58ddc31db6 Translations update from Weblate (#5026)
* [Web] Updated lang.en-gb.json

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

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

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

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

Co-authored-by: Lukáš Matula <lukas@gbely.net>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: Lukáš Matula <lukas@gbely.net>
2023-01-26 20:09:52 +01:00
Kristian Feldsam
5bf62481d5 [Web] Implemented SSO for domain admins
Signed-off-by: Kristian Feldsam <feldsam@gmail.com>

Revert "[Web] Implemented SSO for domain admins"

This reverts commit 6860dc8ebe2c8f53d77df5bca7787f7cb3bb4ee0.

Signed-off-by: Kristian Feldsam <feldsam@gmail.com>
2023-01-26 15:54:44 +01:00
realizelol
6ff3f3f044 [Web] Set pageLength to pagination_size + repect savedState...
Fix width in quarantine table.
2023-01-25 23:50:39 +01:00
Niklas Meyer
640f535e99 Merge pull request #5019 from mailcow/staging
2023-01a
2023-01-25 16:29:22 +01:00
Niklas Meyer
05d1a974eb Merge pull request #5003 from mailcow/feat/acme-skip-ip-check
[Acme] Implemented IP Check Bypass properly
2023-01-25 16:10:11 +01:00
Niklas Meyer
99e38d81b1 Removed Integration Tests 2023-01-25 16:09:15 +01:00
FreddleSpl0it
ed7b384e24 [Web] fix queue btn showing undefined 2023-01-25 09:34:12 +01:00
FreddleSpl0it
5439ea1010 Merge branch 'staging' of https://github.com/mailcow/mailcow-dockerized into staging 2023-01-25 09:32:27 +01:00
FreddleSpl0it
b719982504 partial rollback of dockerapi 2023-01-25 09:31:22 +01:00
milkmaker
8281d3fa55 [Web] Updated lang.da-dk.json (#5020)
Co-authored-by: osos <osos@openeyes.dk>

Co-authored-by: osos <osos@openeyes.dk>
2023-01-24 20:18:17 +01:00
FreddleSpl0it
9ba65a572e [Web] add missing template var for dadmins 2023-01-24 10:13:30 +01:00
FreddleSpl0it
afddcf7f3b replace nullnull.org with fuzzy.mailcow.email 2023-01-24 09:49:49 +01:00
Niklas Meyer
294569f5c9 Merge pull request #5015 from mailcow/feat/nc-install-fix
Fix nextcloud install
2023-01-22 16:17:18 +01:00
Peter
ef6452cf55 Fix installation of nextcloud 2023-01-22 15:06:36 +01:00
renovate[bot]
9af40eba10 Update dependency nextcloud/server to v25.0.3 (#4996)
Signed-off-by: milkmaker <milkmaker@mailcow.de>

Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-20 15:37:12 +01:00
renovate[bot]
1b3a13ca19 Update alpine Docker tag to v3.17 (#4997)
Signed-off-by: milkmaker <milkmaker@mailcow.de>

Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-20 15:36:52 +01:00
Patrick Schult
71cc607de6 Merge pull request #5006 from mailcow/staging
Revert Docker Compose detection commits
2023-01-19 16:04:50 +01:00
DerLinkman
5c57df4669 [Acme] Implemented IP Check Bypass properly 2023-01-16 10:10:20 +01:00
46 changed files with 5597 additions and 5570 deletions

View File

@@ -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'

View File

@@ -26,7 +26,7 @@ jobs:
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }} password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
context: . context: .
file: data/Dockerfiles/backup/Dockerfile file: data/Dockerfiles/backup/Dockerfile

View File

@@ -1,20 +0,0 @@
name: "Tweet trigger release"
on:
release:
types: [published]
jobs:
tweet:
runs-on: ubuntu-latest
steps:
- name: "Get Release Tag"
run: |
RELEASE_TAG=$(curl https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest | jq -r '.tag_name')
- name: Tweet-trigger-publish-release
uses: mugi111/tweet-trigger-release@v1.2
with:
consumer_key: ${{ secrets.CONSUMER_KEY }}
consumer_secret: ${{ secrets.CONSUMER_SECRET }}
access_token_key: ${{ secrets.ACCESS_TOKEN_KEY }}
access_token_secret: ${{ secrets.ACCESS_TOKEN_SECRET }}
tweet_body: 'A new mailcow update has just been released! Checkout the GitHub Page for changelog and more informations: https://github.com/mailcow/mailcow-dockerized/releases/latest'

View File

@@ -1,6 +1,5 @@
# mailcow: dockerized - 🐮 + 🐋 = 💕 # 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/) [![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) [![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)

View File

@@ -213,11 +213,13 @@ while true; do
done done
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig') ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
# Start IP detection # Start IP detection
log_f "Detecting IP addresses..." log_f "Detecting IP addresses..."
IPV4=$(get_ipv4) IPV4=$(get_ipv4)
IPV6=$(get_ipv6) IPV6=$(get_ipv6)
log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}" log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}"
fi
######################################### #########################################
# IP and webroot challenge verification # # IP and webroot challenge verification #

View File

@@ -13,6 +13,7 @@ RUN apk add --update --no-cache python3 \
fastapi \ fastapi \
uvicorn \ uvicorn \
aiodocker \ aiodocker \
docker \
redis redis
COPY docker-entrypoint.sh /app/ COPY docker-entrypoint.sh /app/

View File

@@ -1,5 +1,6 @@
from fastapi import FastAPI, Response, Request from fastapi import FastAPI, Response, Request
import aiodocker import aiodocker
import docker
import psutil import psutil
import sys import sys
import re import re
@@ -9,11 +10,38 @@ import json
import asyncio import asyncio
import redis import redis
from datetime import datetime 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 = [] containerIds_to_update = []
host_stats_isUpdating = False host_stats_isUpdating = False
app = FastAPI() app = FastAPI()
logger = logging.getLogger('api-logger')
@app.get("/host/stats") @app.get("/host/stats")
@@ -21,18 +49,15 @@ async def get_host_update_stats():
global host_stats_isUpdating global host_stats_isUpdating
if host_stats_isUpdating == False: if host_stats_isUpdating == False:
print("start host stats task")
asyncio.create_task(get_host_stats()) asyncio.create_task(get_host_stats())
host_stats_isUpdating = True host_stats_isUpdating = True
while True: while True:
if redis_client.exists('host_stats'): if redis_client.exists('host_stats'):
break break
print("wait for host_stats results")
await asyncio.sleep(1.5) await asyncio.sleep(1.5)
print("host stats pulled")
stats = json.loads(redis_client.get('host_stats')) stats = json.loads(redis_client.get('host_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json") 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: else:
api_call_method_name = '__'.join(['container_post', str(post_action) ]) 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")) 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)) logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return await api_call_method(container_id, request_json) return api_call_method(container_id, request_json)
except Exception as e: except Exception as e:
print("error - container_post: %s" % str(e)) logger.error("error - container_post: %s" % str(e))
res = { res = {
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
@@ -152,398 +177,289 @@ class DockerUtils:
self.docker_client = docker_client self.docker_client = docker_client
# api call: container_post - post_action: stop # api call: container_post - post_action: stop
async def container_post__stop(self, container_id, request_json): def container_post__stop(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.stop()
await container.stop()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
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 # api call: container_post - post_action: start
async def container_post__start(self, container_id, request_json): def container_post__start(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.start()
await container.start()
res = { res = { 'type': 'success', 'msg': 'command completed successfully'}
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: restart # api call: container_post - post_action: restart
async def container_post__restart(self, container_id, request_json): def container_post__restart(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.restart()
await container.restart()
res = { res = { 'type': 'success', 'msg': 'command completed successfully'}
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: top # api call: container_post - post_action: top
async def container_post__top(self, container_id, request_json): def container_post__top(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: res = { 'type': 'success', 'msg': container.top()}
ps_exec = await container.exec("ps") return Response(content=json.dumps(res, indent=4), media_type="application/json")
async with ps_exec.start(detach=False) as stream: # api call: container_post - post_action: stats
ps_return = await stream.read_out() def container_post__stats(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
exec_details = await ps_exec.inspect() for stat in container.stats(decode=True, stream=True):
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: res = { 'type': 'success', 'msg': stat}
res = { return Response(content=json.dumps(res, indent=4), media_type="application/json")
'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")
# api call: container_post - post_action: exec - cmd: mailq - task: delete # 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: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-d %s' % i for i in 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 # 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: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-h %s' % i for i in filtered_qids] flagged_qids = ['-h %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}):
for container in (await self.docker_client.containers.list()): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
if container._id == container_id: return exec_run_handler('generic', postsuper_r)
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: cat # 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: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
sanitized_string = str(' '.join(filtered_qids)) sanitized_string = str(' '.join(filtered_qids));
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') if not postcat_return:
return await exec_run_handler('utf8_text_only', postcat_exec) postcat_return = 'err: invalid'
return exec_run_handler('utf8_text_only', postcat_return)
# api call: container_post - post_action: exec - cmd: mailq - task: unhold # 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: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-H %s' % i for i in filtered_qids] flagged_qids = ['-H %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}):
for container in (await self.docker_client.containers.list()): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
if container._id == container_id: return exec_run_handler('generic', postsuper_r)
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: deliver # 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: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-i %s' % i for i in filtered_qids] flagged_qids = ['-i %s' % i for i in filtered_qids]
for container in self.docker_client.containers.list(filters={"id": container_id}):
for container in (await self.docker_client.containers.list()): for i in flagged_qids:
if container._id == container_id: postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
for i in flagged_qids: # todo: check each exit code
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
async with postsuper_r_exec.start(detach=False) as stream: return Response(content=json.dumps(res, indent=4), media_type="application/json")
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 # api call: container_post - post_action: exec - cmd: mailq - task: list
async def container_post__exec__mailq__list(self, container_id, request_json): def container_post__exec__mailq__list(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix') return exec_run_handler('utf8_text_only', mailq_return)
return await exec_run_handler('utf8_text_only', mailq_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: flush # api call: container_post - post_action: exec - cmd: mailq - task: flush
async def container_post__exec__mailq__flush(self, container_id, request_json): def container_post__exec__mailq__flush(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix') return exec_run_handler('generic', postqueue_r)
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete # 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): def container_post__exec__mailq__super_delete(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"]) return exec_run_handler('generic', postsuper_r)
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan # 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): def container_post__exec__system__fts_rescan(self, container_id, request_json):
if 'username' in request_json: if 'username' in request_json:
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail') if rescan_return.exit_code == 0:
async with rescan_exec.start(detach=False) as stream: res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
rescan_return = await stream.read_out() return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
exec_details = await rescan_exec.inspect() res = { 'type': 'warning', 'msg': 'fts_rescan error'}
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: return Response(content=json.dumps(res, indent=4), media_type="application/json")
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: if 'all' in request_json:
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail') if rescan_return.exit_code == 0:
async with rescan_exec.start(detach=False) as stream: res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
rescan_return = await stream.read_out() return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
exec_details = await rescan_exec.inspect() res = { 'type': 'warning', 'msg': 'fts_rescan error'}
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: return Response(content=json.dumps(res, indent=4), media_type="application/json")
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 # api call: container_post - post_action: exec - cmd: system - task: df
async def container_post__exec__system__df(self, container_id, request_json): def container_post__exec__system__df(self, container_id, request_json):
if 'dir' in request_json: if 'dir' in request_json:
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._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')
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') if df_return.exit_code == 0:
async with df_exec.start(detach=False) as stream: return df_return.output.decode('utf-8').rstrip()
df_return = await stream.read_out() else:
return "0,0,0,0,0,0"
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 # 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): def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql') if sql_return.exit_code == 0:
async with sql_exec.start(detach=False) as stream: matched = False
sql_return = await stream.read_out() for line in sql_return.output.decode('utf-8').split("\n"):
if 'is already upgraded to' in line:
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')
}
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'):
matched = True 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: matched = False
res = { for line in cmd_response.split("\n"):
'type': 'success', if '$2$' in line:
'msg': 'command completed successfully' hash = line.strip()
} hash_out = re.search('\$2\$.+$', hash).group(0)
return Response(content=json.dumps(res, indent=4), media_type="application/json") rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
else: rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
res = { cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
'type': 'danger', cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
'msg': 'command did not complete' if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
} container.restart()
return Response(content=json.dumps(res, indent=4), media_type="application/json") 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): def recv_socket_data(c_socket, timeout):
async with exec_obj.start(detach=False) as stream: c_socket.setblocking(0)
exec_return = await stream.read_out() 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')
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 type == 'generic':
exec_details = await exec_obj.inspect() if output.exit_code == 0:
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: res = { 'type': 'success', 'msg': 'command completed successfully' }
res = {
"type": "success",
"msg": "command completed successfully"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
else: else:
res = { res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') }
"type": "success",
"msg": "'command failed: " + exec_return
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
if type == 'utf8_text_only': 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): async def get_host_stats(wait=5):
global host_stats_isUpdating global host_stats_isUpdating
@@ -570,12 +486,10 @@ async def get_host_stats(wait=5):
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
} }
print(json.dumps(res, indent=4))
await asyncio.sleep(wait) await asyncio.sleep(wait)
host_stats_isUpdating = False host_stats_isUpdating = False
async def get_container_stats(container_id, wait=5, stop=False): async def get_container_stats(container_id, wait=5, stop=False):
global containerIds_to_update global containerIds_to_update
@@ -598,13 +512,11 @@ async def get_container_stats(container_id, wait=5, stop=False):
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
} }
print(json.dumps(res, indent=4))
else: else:
res = { res = {
"type": "danger", "type": "danger",
"msg": "no or invalid id defined" "msg": "no or invalid id defined"
} }
print(json.dumps(res, indent=4))
await asyncio.sleep(wait) await asyncio.sleep(wait)
if stop == True: 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) await get_container_stats(container_id, wait=0, stop=True)
if os.environ['REDIS_SLAVEOF_IP'] != "": if os.environ['REDIS_SLAVEOF_IP'] != "":
redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0) redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
else: else:
redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0) 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') async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
logger.info('DockerApi started')

View File

@@ -175,7 +175,7 @@ BAD_SUBJECT_00 {
type = "header"; type = "header";
header = "subject"; header = "subject";
regexp = true; regexp = true;
map = "http://nullnull.org/bad-subject-regex.txt"; map = "http://fuzzy.mailcow.email/bad-subject-regex.txt";
score = 6.0; score = 6.0;
symbols_set = ["BAD_SUBJECT_00"]; symbols_set = ["BAD_SUBJECT_00"];
} }

View File

@@ -699,6 +699,38 @@ paths:
type: string type: string
type: object type: object
summary: Create Domain Admin user summary: Create Domain Admin user
/api/v1/add/sso/domain-admin:
post:
responses:
"401":
$ref: "#/components/responses/Unauthorized"
"200":
content:
application/json:
examples:
response:
value:
token: "591F6D-5C3DD2-7455CD-DAF1C1-AA4FCC"
description: OK
headers: { }
tags:
- Single Sign-On
description: >-
Using this endpoint you can issue a token for Domain Admin user. This token can be used for
autologin Domain Admin user by using query_string var sso_token={token}. Token expiration time is 30s
operationId: Issue Domain Admin SSO token
requestBody:
content:
application/json:
schema:
example:
username: testadmin
properties:
username:
description: the username for the admin user
type: object
type: object
summary: Issue Domain Admin SSO token
/api/v1/edit/da-acl: /api/v1/edit/da-acl:
post: post:
responses: responses:
@@ -5586,6 +5618,8 @@ tags:
description: Manage DKIM keys description: Manage DKIM keys
- name: Domain admin - name: Domain admin
description: Create or udpdate domain admin users description: Create or udpdate domain admin users
- name: Single Sign-On
description: Issue tokens for users
- name: Address Rewriting - name: Address Rewriting
description: Create BCC maps or recipient maps description: Create BCC maps or recipient maps
- name: Outgoing TLS Policy Map Overrides - name: Outgoing TLS Policy Map Overrides

View File

@@ -78,3 +78,21 @@ table.dataTable>tbody>tr.child span.dtr-title {
width: 30%; width: 30%;
max-width: 250px; max-width: 250px;
} }
div.dataTables_wrapper div.dataTables_filter {
text-align: left;
}
div.dataTables_wrapper div.dataTables_length {
text-align: right;
}
.dataTables_paginate, .dataTables_length, .dataTables_filter {
margin: 10px 0!important;
}
td.dt-text-right {
text-align: end !important;
}
th.dt-text-right {
text-align: end !important;
}

View File

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

View File

@@ -203,6 +203,9 @@
text-align: left; text-align: left;
} }
.senders-mw220 {
max-width: 100% !important;
}
} }
@media (max-width: 350px) { @media (max-width: 350px) {

View File

@@ -99,4 +99,6 @@ table tbody tr td input[type="checkbox"] {
font-size:110%; font-size:110%;
margin:20px; margin:20px;
} }
.senders-mw220 {
max-width: 220px;
}

View File

@@ -405,3 +405,64 @@ function domain_admin($_action, $_data = null) {
break; break;
} }
} }
function domain_admin_sso($_action, $_data) {
global $pdo;
switch ($_action) {
case 'check':
$token = $_data;
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;");
$stmt->execute(array(
':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token)
));
$return = $stmt->fetch(PDO::FETCH_ASSOC);
return empty($return['username']) ? false : $return['username'];
case 'issue':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => 'access_denied'
);
return false;
}
$username = $_data['username'];
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results < 1) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('object_doesnt_exist', htmlspecialchars($username))
);
return false;
}
$token = implode('-', array(
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3)))
));
$stmt = $pdo->prepare("INSERT INTO `da_sso` (`username`, `token`)
VALUES (:username, :token)");
$stmt->execute(array(
':username' => $username,
':token' => $token
));
// perform cleanup
$pdo->query("DELETE FROM `da_sso` WHERE created < DATE_SUB(NOW(), INTERVAL '30' SECOND);");
return ['token' => $token];
break;
}
}

View File

@@ -1739,7 +1739,7 @@ function verify_tfa_login($username, $_data) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'), 'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'authenticator not found') 'msg' => array('webauthn_authenticator_failed')
); );
return false; return false;
} }
@@ -1748,11 +1748,20 @@ function verify_tfa_login($username, $_data) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'), 'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'publicKey not found') 'msg' => array('webauthn_publickey_failed')
); );
return false; return false;
} }
if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_username_failed')
);
return false;
}
try { try {
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']); $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']);
} }
@@ -1784,21 +1793,12 @@ function verify_tfa_login($username, $_data) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'), 'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'could not determine user role') 'msg' => array('webauthn_role_failed')
); );
return false; return false;
} }
} }
if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'user who requests does not match with sql entry')
);
return false;
}
$_SESSION["mailcow_cc_username"] = $process_webauthn['username']; $_SESSION["mailcow_cc_username"] = $process_webauthn['username'];
$_SESSION['tfa_id'] = $process_webauthn['id']; $_SESSION['tfa_id'] = $process_webauthn['id'];
$_SESSION['authReq'] = null; $_SESSION['authReq'] = null;

View File

@@ -5264,7 +5264,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
break; break;
} }
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource'))) { if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource')) && getenv('SKIP_SOGO') != "y") {
update_sogo_static_view(); update_sogo_static_view();
} }
} }

View File

@@ -3,7 +3,7 @@ function init_db_schema() {
try { try {
global $pdo; global $pdo;
$db_version = "23122022_1445"; $db_version = "06012023_1924";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -664,6 +664,19 @@ function init_db_schema() {
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
), ),
"da_sso" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"token" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
),
"keys" => array(
"primary" => array(
"" => array("token", "created")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync" => array( "imapsync" => array(
"cols" => array( "cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT", "id" => "INT NOT NULL AUTO_INCREMENT",

View File

@@ -1,4 +1,15 @@
<?php <?php
// SSO Domain Admin
if (!empty($_GET['sso_token'])) {
$username = domain_admin_sso('check', $_GET['sso_token']);
if ($username !== false) {
$_SESSION['mailcow_cc_username'] = $username;
$_SESSION['mailcow_cc_role'] = 'domainadmin';
header('Location: /mailbox');
}
}
if (isset($_POST["verify_tfa_login"])) { if (isset($_POST["verify_tfa_login"])) {
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) { if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username']; $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];

View File

@@ -124,7 +124,7 @@ $MAILCOW_APPS = array(
); );
// Rows until pagination begins // Rows until pagination begins
$PAGINATION_SIZE = 20; $PAGINATION_SIZE = 25;
// Default number of rows/lines to display (log table) // Default number of rows/lines to display (log table)
$LOG_LINES = 1000; $LOG_LINES = 1000;

View File

@@ -47,9 +47,9 @@ jQuery(function($){
$('button[data-id="' + regex_map_id + '"]').attr({"disabled": false}); $('button[data-id="' + regex_map_id + '"]').attr({"disabled": false});
} }
}); });
$('.textarea-code').on('keyup', function() { $('.textarea-code').on('keyup', function() {
$('.submit_rspamd_regex').attr({"disabled": true}); $('.submit_rspamd_regex').attr({"disabled": true});
}); });
$("#show_rspamd_global_filters").click(function() { $("#show_rspamd_global_filters").click(function() {
$.get("inc/ajax/show_rspamd_global_filters.php"); $.get("inc/ajax/show_rspamd_global_filters.php");
$("#confirm_show_rspamd_global_filters").hide(); $("#confirm_show_rspamd_global_filters").hide();
@@ -70,10 +70,11 @@ jQuery(function($){
} }
$('#domainadminstable').DataTable({ $('#domainadminstable').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -86,55 +87,55 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.username, title: lang.username,
data: 'username', data: 'username',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.admin_domains, title: lang.admin_domains,
data: 'selected_domains', data: 'selected_domains',
defaultContent: '', defaultContent: '',
}, },
{ {
title: "TFA", title: "TFA",
data: 'tfa_active', data: 'tfa_active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>' else return '<i class="bi bi-x-lg"></i>';
} }
}, },
{ {
title: lang.active, title: lang.active,
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>' else return '<i class="bi bi-x-lg"></i>';
} }
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
}, },
], ],
initComplete: function(settings, json){ initComplete: function(settings, json){
} }
@@ -148,10 +149,11 @@ jQuery(function($){
} }
$('#oauth2clientstable').DataTable({ $('#oauth2clientstable').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -164,47 +166,47 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'ID', title: 'ID',
data: 'id', data: 'id',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.oauth2_client_id, title: lang.oauth2_client_id,
data: 'client_id', data: 'client_id',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.oauth2_client_secret, title: lang.oauth2_client_secret,
data: 'client_secret', data: 'client_secret',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.oauth2_redirect_uri, title: lang.oauth2_redirect_uri,
data: 'redirect_uri', data: 'redirect_uri',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
}, },
] ]
}); });
} }
@@ -216,10 +218,11 @@ jQuery(function($){
} }
$('#adminstable').DataTable({ $('#adminstable').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -232,50 +235,50 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.username, title: lang.username,
data: 'username', data: 'username',
defaultContent: '' defaultContent: ''
}, },
{ {
title: "TFA", title: "TFA",
data: 'tfa_active', data: 'tfa_active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>' else return '<i class="bi bi-x-lg"></i>';
} }
}, },
{ {
title: lang.active, title: lang.active,
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>' else return '<i class="bi bi-x-lg"></i>';
} }
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
defaultContent: '', defaultContent: '',
className: 'text-md-end dt-sm-head-hidden dt-body-right' className: 'dt-sm-head-hidden dt-text-right'
}, },
] ]
}); });
} }
@@ -287,10 +290,11 @@ jQuery(function($){
} }
$('#forwardinghoststable').DataTable({ $('#forwardinghoststable').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -303,45 +307,45 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.host, title: lang.host,
data: 'host', data: 'host',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.source, title: lang.source,
data: 'source', data: 'source',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.spamfilter, title: lang.spamfilter,
data: 'keep_spam', data: 'keep_spam',
defaultContent: '', defaultContent: '',
render: function(data, type){ render: function(data, type){
return 'yes'==data?'<i class="bi bi-x-lg"></i>':'no'==data&&'<i class="bi bi-check-lg"></i>'; return 'yes'==data?'<i class="bi bi-x-lg"></i>':'no'==data&&'<i class="bi bi-check-lg"></i>';
} }
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
}, },
] ]
}); });
} }
@@ -353,10 +357,11 @@ jQuery(function($){
} }
$('#relayhoststable').DataTable({ $('#relayhoststable').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -369,56 +374,56 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'ID', title: 'ID',
data: 'id', data: 'id',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.host, title: lang.host,
data: 'hostname', data: 'hostname',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.username, title: lang.username,
data: 'username', data: 'username',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.in_use_by, title: lang.in_use_by,
data: 'in_use_by', data: 'in_use_by',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.active, title: lang.active,
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>' else return '<i class="bi bi-x-lg"></i>';
} }
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
}, },
] ]
}); });
} }
@@ -430,10 +435,11 @@ jQuery(function($){
} }
$('#transportstable').DataTable({ $('#transportstable').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -446,56 +452,56 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'ID', title: 'ID',
data: 'id', data: 'id',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.destination, title: lang.destination,
data: 'destination', data: 'destination',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.nexthop, title: lang.nexthop,
data: 'nexthop', data: 'nexthop',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.username, title: lang.username,
data: 'username', data: 'username',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.active, title: lang.active,
data: 'active', data: 'active',
defaultContent: '', defaultContent: '',
render: function (data, type) { render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>'; if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>' else return '<i class="bi bi-x-lg"></i>';
} }
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
}, },
] ]
}); });
} }
@@ -645,15 +651,15 @@ jQuery(function($){
$(this).prop("disabled",true); $(this).prop("disabled",true);
$(this).html('<i class="bi bi-arrow-repeat icon-spin"></i> '); $(this).html('<i class="bi bi-arrow-repeat icon-spin"></i> ');
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: 'inc/ajax/relay_check.php', url: 'inc/ajax/relay_check.php',
dataType: 'text', dataType: 'text',
data: $('#test_relayhost_form').serialize(), data: $('#test_relayhost_form').serialize(),
complete: function (data) { complete: function (data) {
$('#test_relayhost_result').html(data.responseText); $('#test_relayhost_result').html(data.responseText);
$('#test_relayhost').prop("disabled",false); $('#test_relayhost').prop("disabled",false);
$('#test_relayhost').text(prev); $('#test_relayhost').text(prev);
} }
}); });
}) })
// Transport // Transport
@@ -671,15 +677,15 @@ jQuery(function($){
$(this).prop("disabled",true); $(this).prop("disabled",true);
$(this).html('<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div> '); $(this).html('<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div> ');
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: 'inc/ajax/transport_check.php', url: 'inc/ajax/transport_check.php',
dataType: 'text', dataType: 'text',
data: $('#test_transport_form').serialize(), data: $('#test_transport_form').serialize(),
complete: function (data) { complete: function (data) {
$('#test_transport_result').html(data.responseText); $('#test_transport_result').html(data.responseText);
$('#test_transport').prop("disabled",false); $('#test_transport').prop("disabled",false);
$('#test_transport').text(prev); $('#test_transport').text(prev);
} }
}); });
}) })
// DKIM private key modal // DKIM private key modal
@@ -723,9 +729,9 @@ jQuery(function($){
$(this).parents('tr').remove(); $(this).parents('tr').remove();
}); });
$('#add_app_link_row').click(function() { $('#add_app_link_row').click(function() {
add_table_row($('#app_link_table'), "app_link"); add_table_row($('#app_link_table'), "app_link");
}); });
$('#add_f2b_regex_row').click(function() { $('#add_f2b_regex_row').click(function() {
add_table_row($('#f2b_regex_table'), "f2b_regex"); add_table_row($('#f2b_regex_table'), "f2b_regex");
}); });
}); });

View File

@@ -34,7 +34,7 @@ $(document).ready(function() {
}); });
// set update loop container list // set update loop container list
containersToUpdate = {} containersToUpdate = {};
// set default ChartJs Font Color // set default ChartJs Font Color
Chart.defaults.color = '#999'; Chart.defaults.color = '#999';
// create host cpu and mem charts // create host cpu and mem charts
@@ -44,8 +44,7 @@ $(document).ready(function() {
check_update(mailcow_info.version_tag, mailcow_info.project_url); check_update(mailcow_info.version_tag, mailcow_info.project_url);
} }
$("#maiclow_version").click(function(){ $("#maiclow_version").click(function(){
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master")
mailcow_info.branch !== "master")
return; return;
showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag); showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
@@ -119,10 +118,11 @@ jQuery(function($){
} }
var table = $('#autodiscover_log').DataTable({ var table = $('#autodiscover_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -188,10 +188,11 @@ jQuery(function($){
} }
var table = $('#postfix_log').DataTable({ var table = $('#postfix_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -242,10 +243,11 @@ jQuery(function($){
} }
var table = $('#watchdog_log').DataTable({ var table = $('#watchdog_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -300,10 +302,11 @@ jQuery(function($){
} }
var table = $('#api_log').DataTable({ var table = $('#api_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -365,10 +368,11 @@ jQuery(function($){
} }
var table = $('#rl_log').DataTable({ var table = $('#rl_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -468,10 +472,11 @@ jQuery(function($){
} }
var table = $('#ui_logs').DataTable({ var table = $('#ui_logs').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -551,10 +556,11 @@ jQuery(function($){
} }
var table = $('#sasl_logs').DataTable({ var table = $('#sasl_logs').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -611,10 +617,11 @@ jQuery(function($){
} }
var table = $('#acme_log').DataTable({ var table = $('#acme_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -660,10 +667,11 @@ jQuery(function($){
} }
var table = $('#netfilter_log').DataTable({ var table = $('#netfilter_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -714,10 +722,11 @@ jQuery(function($){
} }
var table = $('#sogo_log').DataTable({ var table = $('#sogo_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -768,10 +777,11 @@ jQuery(function($){
} }
var table = $('#dovecot_log').DataTable({ var table = $('#dovecot_log').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -883,10 +893,11 @@ jQuery(function($){
} }
var table = $('#rspamd_history').DataTable({ var table = $('#rspamd_history').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -998,31 +1009,31 @@ jQuery(function($){
item.rcpt = escapeHtml(item.rcpt_smtp.join(", ")); item.rcpt = escapeHtml(item.rcpt_smtp.join(", "));
} }
item.symbols = Object.keys(item.symbols).sort(function (a, b) { item.symbols = Object.keys(item.symbols).sort(function (a, b) {
if (item.symbols[a].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) return -1;
if (item.symbols[b].score < 0 && item.symbols[a].score < 0) { 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) { 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) { }).map(function(key) {
var sym = item.symbols[key]; var sym = item.symbols[key];
if (sym.score < 0) { if (sym.score < 0) {
sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)' sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)';
} }
else if (sym.score === 0) { else if (sym.score === 0) {
sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)' sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)';
} }
else { else {
sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)' sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)';
} }
var str = '<strong>' + key + '</strong> ' + sym.score_formatted; var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
if (sym.options) { if (sym.options) {
str += ' [' + escapeHtml(sym.options.join(", ")) + "]"; str += ' [' + escapeHtml(sym.options.join(", ")) + "]";
} }
return str return str;
}).join('<br>\n'); }).join('<br>\n');
item.subject = escapeHtml(item.subject); item.subject = escapeHtml(item.subject);
var scan_time = item.time_real.toFixed(3); 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) { $('.add_log_lines').on('click', function (e) {
e.preventDefault(); e.preventDefault();
var log_table= $(this).data("table") var log_table= $(this).data("table");
var new_nrows = $(this).data("nrows") var new_nrows = $(this).data("nrows");
var post_process = $(this).data("post-process") var post_process = $(this).data("post-process");
var log_url = $(this).data("log-url") var log_url = $(this).data("log-url");
if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) { 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"); console.log("no data-table or data-nrows or log_url or data-post-process attr found");
return; return;
@@ -1220,7 +1231,6 @@ jQuery(function($){
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph()); onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
// start polling host stats if tab is active // start polling host stats if tab is active
onVisible("[id^=tab-containers]", () => update_stats()); onVisible("[id^=tab-containers]", () => update_stats());
// start polling container stats if collapse is active // 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(); if (mem_chart.data.labels.length > 30) mem_chart.data.labels.shift();
cpu_chart.data.datasets[0].data.push(data.cpu.usage); 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); 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(); cpu_chart.update();
mem_chart.update(); mem_chart.update();
@@ -1464,23 +1474,23 @@ function createReadWriteChart(chart_id, read_lable, write_lable){
}; };
var optionsNet = { var optionsNet = {
interaction: { interaction: {
mode: 'index' mode: 'index'
}, },
scales: { scales: {
yAxis: { yAxis: {
min: 0, min: 0,
grid: { grid: {
display: false display: false
}, },
ticks: { ticks: {
callback: function(i, index, ticks) { callback: function(i, index, ticks) {
return formatBytes(i); return formatBytes(i);
} }
} }
}, },
xAxis: { xAxis: {
grid: { grid: {
display: false display: false
} }
} }
} }
@@ -1528,13 +1538,13 @@ function createHostCpuAndMemChart(){
}; };
var optionsCpu = { var optionsCpu = {
interaction: { interaction: {
mode: 'index' mode: 'index'
}, },
scales: { scales: {
yAxis: { yAxis: {
min: 0, min: 0,
grid: { grid: {
display: false display: false
}, },
ticks: { ticks: {
callback: function(i, index, ticks) { callback: function(i, index, ticks) {
@@ -1544,7 +1554,7 @@ function createHostCpuAndMemChart(){
}, },
xAxis: { xAxis: {
grid: { grid: {
display: false display: false
} }
} }
} }
@@ -1566,13 +1576,13 @@ function createHostCpuAndMemChart(){
}; };
var optionsMem = { var optionsMem = {
interaction: { interaction: {
mode: 'index' mode: 'index'
}, },
scales: { scales: {
yAxis: { yAxis: {
min: 0, min: 0,
grid: { grid: {
display: false display: false
}, },
ticks: { ticks: {
callback: function(i, index, ticks) { callback: function(i, index, ticks) {
@@ -1582,7 +1592,7 @@ function createHostCpuAndMemChart(){
}, },
xAxis: { xAxis: {
grid: { grid: {
display: false display: false
} }
} }
} }
@@ -1678,22 +1688,22 @@ function parseGithubMarkdownLinks(inputText) {
replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => { replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => {
if (matched.includes('github.com')){ if (matched.includes('github.com')){
// return short link if it's github link // return short link if it's github link
last_uri_path = matched.split('/'); last_uri_path = matched.split('/');
last_uri_path = last_uri_path[last_uri_path.length - 1]; 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 // 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 !== ''){ if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){
matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.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; last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag;
} }
return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>'; return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>';
}; };
// if it's not a github link, return complete link // if it's not a github link, return complete link
return '<a href="' + matched + '" target="_blank">' + matched + '</a>'; return '<a href="' + matched + '" target="_blank">' + matched + '</a>';
}); });
return replacedText; return replacedText;

View File

@@ -2,7 +2,7 @@ $(document).ready(function() {
$(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); }); $(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); });
$("#pushover_delete").click(function() { return confirm(lang.delete_ays); }); $("#pushover_delete").click(function() { return confirm(lang.delete_ays); });
$(".goto_checkbox").click(function( event ) { $(".goto_checkbox").click(function( event ) {
$("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false); $("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false);
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) { if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
$('#textarea_alias_goto').prop('disabled', true); $('#textarea_alias_goto').prop('disabled', true);
} }
@@ -78,10 +78,11 @@ jQuery(function($){
} }
function draw_wl_policy_domain_table() { function draw_wl_policy_domain_table() {
$('#wl_policy_domain_table').DataTable({ $('#wl_policy_domain_table').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -103,45 +104,46 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'ID', title: 'ID',
data: 'prefid', data: 'prefid',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang_user.spamfilter_table_rule, title: lang_user.spamfilter_table_rule,
data: 'value', data: 'value',
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'Scope', title: 'Scope',
data: 'object', data: 'object',
defaultContent: '' defaultContent: ''
} }
] ]
}); });
} }
function draw_bl_policy_domain_table() { function draw_bl_policy_domain_table() {
$('#bl_policy_domain_table').DataTable({ $('#bl_policy_domain_table').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -163,36 +165,36 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'ID', title: 'ID',
data: 'prefid', data: 'prefid',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang_user.spamfilter_table_rule, title: lang_user.spamfilter_table_rule,
data: 'value', data: 'value',
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'Scope', title: 'Scope',
data: 'object', data: 'object',
defaultContent: '' defaultContent: ''
} }
] ]
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@@ -25,24 +25,24 @@ jQuery(function($){
} }
if (typeof data.symbols !== 'undefined') { if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) { data.symbols.sort(function (a, b) {
if (a.score === 0) return 1 if (a.score === 0) return 1;
if (b.score === 0) return -1 if (b.score === 0) return -1;
if (b.score < 0 && a.score < 0) { if (b.score < 0 && a.score < 0) {
return a.score - b.score return a.score - b.score;
} }
if (b.score > 0 && a.score > 0) { if (b.score > 0 && a.score > 0) {
return b.score - a.score return b.score - a.score;
} }
return b.score - a.score return b.score - a.score;
}) })
$.each(data.symbols, function (index, value) { $.each(data.symbols, function (index, value) {
var highlightClass = '' var highlightClass = '';
if (value.score > 0) highlightClass = 'negative' if (value.score > 0) highlightClass = 'negative';
else if (value.score < 0) highlightClass = 'positive' else if (value.score < 0) highlightClass = 'positive';
else highlightClass = 'neutral' else highlightClass = 'neutral';
$('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
}); });
$('[data-bs-toggle="tooltip"]').tooltip() $('[data-bs-toggle="tooltip"]').tooltip();
} }
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action === "add header") { if (data.action === "add header") {

View File

@@ -14,10 +14,21 @@ jQuery(function($){
}); });
function draw_quarantine_table() { function draw_quarantine_table() {
var table = $('#quarantinetable').DataTable({ var table = $('#quarantinetable').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
order: [[2, 'desc']],
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, 'all']
],
pagingType: 'first_last_numbers',
aColumns: [
{ sWidth: '8.25%' },
{ sClass: 'classDataTable' }
],
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -73,87 +84,88 @@ jQuery(function($){
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'ID', title: 'ID',
data: 'id', data: 'id',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.qid, title: lang.qid,
data: 'qid', data: 'qid',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.sender, title: lang.sender,
data: 'sender', data: 'sender',
defaultContent: '' className: 'senders-mw220',
}, defaultContent: ''
{ },
title: lang.subj, {
data: 'subject', title: lang.subj,
defaultContent: '' data: 'subject',
}, defaultContent: ''
{ },
title: lang.rspamd_result, {
data: 'rspamdaction', title: lang.rspamd_result,
defaultContent: '' data: 'rspamdaction',
}, defaultContent: ''
{ },
title: lang.rcpt, {
data: 'rcpt', title: lang.rcpt,
defaultContent: '' data: 'rcpt',
}, defaultContent: ''
{ },
title: lang.danger, {
data: 'virus', title: lang.danger,
defaultContent: '' data: 'virus',
}, defaultContent: ''
{ },
title: lang.spam_score, {
data: 'score', title: lang.spam_score,
defaultContent: '' data: 'score',
}, defaultContent: ''
{ },
title: lang.notified, {
data: 'notified', title: lang.notified,
defaultContent: '' data: 'notified',
}, defaultContent: ''
{ },
title: lang.received, {
data: 'created', title: lang.received,
defaultContent: '', data: 'created',
createdCell: function(td, cellData) { defaultContent: '',
$(td).attr({ createdCell: function(td, cellData) {
"data-order": cellData, $(td).attr({
"data-sort": cellData "data-order": cellData,
}); "data-sort": cellData
});
var date = new Date(cellData ? cellData * 1000 : 0); var date = new Date(cellData ? cellData * 1000 : 0);
var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
$(td).html(dateString); $(td).html(dateString);
} }
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-text-right dt-sm-head-hidden',
defaultContent: '' defaultContent: ''
}, },
] ]
}); });
@@ -193,24 +205,24 @@ jQuery(function($){
$('#qid_detail_fuzzy').html(''); $('#qid_detail_fuzzy').html('');
if (typeof data.symbols !== 'undefined') { if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) { data.symbols.sort(function (a, b) {
if (a.score === 0) return 1 if (a.score === 0) return 1;
if (b.score === 0) return -1 if (b.score === 0) return -1;
if (b.score < 0 && a.score < 0) { if (b.score < 0 && a.score < 0) {
return a.score - b.score return a.score - b.score;
} }
if (b.score > 0 && a.score > 0) { if (b.score > 0 && a.score > 0) {
return b.score - a.score return b.score - a.score;
} }
return b.score - a.score return b.score - a.score;
}) })
$.each(data.symbols, function (index, value) { $.each(data.symbols, function (index, value) {
var highlightClass = '' var highlightClass = '';
if (value.score > 0) highlightClass = 'negative' if (value.score > 0) highlightClass = 'negative';
else if (value.score < 0) highlightClass = 'positive' else if (value.score < 0) highlightClass = 'positive';
else highlightClass = 'neutral' else highlightClass = 'neutral';
$('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
}); });
$('[data-bs-toggle="tooltip"]').tooltip() $('[data-bs-toggle="tooltip"]').tooltip();
} }
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) { $.each(data.fuzzy_hashes, function (index, value) {
@@ -276,7 +288,6 @@ jQuery(function($){
// Initial table drawings // Initial table drawings
draw_quarantine_table(); draw_quarantine_table();
function hideTableExpandCollapseBtn(table){ function hideTableExpandCollapseBtn(table){
if ($(table).hasClass('collapsed')) if ($(table).hasClass('collapsed'))
$(".table_collapse_option").show(); $(".table_collapse_option").show();

View File

@@ -1,124 +1,124 @@
jQuery(function($){ jQuery(function($){
$(".refresh_table").on('click', function(e) { $(".refresh_table").on('click', function(e) {
e.preventDefault(); e.preventDefault();
var table_name = $(this).data('table'); var table_name = $(this).data('table');
$('#' + table_name).DataTable().ajax.reload(); $('#' + table_name).DataTable().ajax.reload();
}); });
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<B.length-1);return i.toFixed(1)+" "+B[e]} 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<B.length-1);return i.toFixed(1)+" "+B[e]}
// Queue item // Queue item
$('#showQueuedMsg').on('show.bs.modal', function (e) { $('#showQueuedMsg').on('show.bs.modal', function (e) {
$('#queue_msg_content').text(lang.loading); $('#queue_msg_content').text(lang.loading);
button = $(e.relatedTarget) button = $(e.relatedTarget)
if (button != null) { if (button != null) {
$('#queue_id').text(button.data('queue-id')); $('#queue_id').text(button.data('queue-id'));
}
$.ajax({
type: 'GET',
url: '/api/v1/get/postcat/' + button.data('queue-id'),
dataType: 'text',
complete: function (data) {
console.log(data);
$('#queue_msg_content').text(data.responseText);
}
});
})
function draw_queue() {
// just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#queuetable') ) {
$('#queuetable').DataTable().columns.adjust().responsive.recalc();
return;
} }
$.ajax({
type: 'GET',
url: '/api/v1/get/postcat/' + button.data('queue-id'),
dataType: 'text',
complete: function (data) {
$('#queue_msg_content').text(data.responseText);
}
});
})
$('#queuetable').DataTable({ function draw_queue() {
responsive: true, // just recalc width if instance already exists
processing: true, if ($.fn.DataTable.isDataTable('#queuetable') ) {
serverSide: false, $('#queuetable').DataTable().columns.adjust().responsive.recalc();
stateSave: true, return;
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>>", $('#queuetable').DataTable({
language: lang_datatables, responsive: true,
ajax: { processing: true,
type: "GET", serverSide: false,
url: "/api/v1/get/mailq/all", stateSave: true,
dataSrc: function(data){ pageLength: pagination_size,
$.each(data, function (i, item) { dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />'; "tr" +
rcpts = $.map(item.recipients, function(i) { "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
return escapeHtml(i); language: lang_datatables,
}); ajax: {
item.recipients = rcpts.join('<hr style="margin:1px!important">'); type: "GET",
item.action = '<div class="btn-group">' + url: "/api/v1/get/mailq/all",
'<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.queue_show_message + '</a>' + dataSrc: function(data){
$.each(data, function (i, item) {
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
rcpts = $.map(item.recipients, function(i) {
return escapeHtml(i);
});
item.recipients = rcpts.join('<hr style="margin:1px!important">');
item.action = '<div class="btn-group">' +
'<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.show_message + '</a>' +
'</div>'; '</div>';
}); });
return data; return data;
} }
}, },
columns: [ columns: [
{ {
// placeholder, so checkbox will not block child row toggle // placeholder, so checkbox will not block child row toggle
title: '', title: '',
data: null, data: null,
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: '', title: '',
data: 'chkbox', data: 'chkbox',
searchable: false, searchable: false,
orderable: false, orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'QID', title: 'QID',
data: 'queue_id', data: 'queue_id',
defaultContent: '' defaultContent: ''
}, },
{ {
title: 'Queue', title: 'Queue',
data: 'queue_name', data: 'queue_name',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang_admin.arrival_time, title: lang_admin.arrival_time,
data: 'arrival_time', data: 'arrival_time',
defaultContent: '', defaultContent: '',
render: function (data, type){ render: function (data, type){
var date = new Date(data ? data * 1000 : 0); var date = new Date(data ? data * 1000 : 0);
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} }
}, },
{ {
title: lang_admin.message_size, title: lang_admin.message_size,
data: 'message_size', data: 'message_size',
defaultContent: '', defaultContent: '',
render: function (data, type){ render: function (data, type){
return humanFileSize(data); return humanFileSize(data);
} }
}, },
{ {
title: lang_admin.sender, title: lang_admin.sender,
data: 'sender', data: 'sender',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang_admin.recipients, title: lang_admin.recipients,
data: 'recipients', data: 'recipients',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang_admin.action, title: lang_admin.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
}, },
] ]
}); });
} }

View File

@@ -127,13 +127,6 @@ jQuery(function($){
} }
} }
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
if ($.fn.DataTable.isDataTable('#' + table_name))
$('#' + table_name).DataTable().ajax.reload();
});
function draw_tla_table() { function draw_tla_table() {
// just recalc width if instance already exists // just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#tla_table') ) { if ($.fn.DataTable.isDataTable('#tla_table') ) {
@@ -142,10 +135,11 @@ jQuery(function($){
} }
$('#tla_table').DataTable({ $('#tla_table').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -214,7 +208,7 @@ jQuery(function($){
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
} }
] ]
@@ -228,10 +222,11 @@ jQuery(function($){
} }
$('#sync_job_table').DataTable({ $('#sync_job_table').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -240,6 +235,7 @@ jQuery(function($){
type: "GET", type: "GET",
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log', url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
dataSrc: function(data){ dataSrc: function(data){
console.log(data);
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.user1 = escapeHtml(item.user1); item.user1 = escapeHtml(item.user1);
item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>' item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
@@ -260,15 +256,13 @@ jQuery(function($){
item.action = '<span>-</span>'; item.action = '<span>-</span>';
item.chkbox = '<input type="checkbox" disabled />'; item.chkbox = '<input type="checkbox" disabled />';
} }
if (item.is_running == 1 && item.active == 1) { if (item.is_running == 1) {
item.is_running = '<span id="active-script" class="badge fs-6 bg-success">' + lang.running + '</span>'; item.is_running = '<span id="active-script" class="badge fs-6 bg-success">' + lang.running + '</span>';
} else if (item.is_running == 0 && item.active == 1) {
item.is_running = '<span id="inactive-script" class="badge fs-6 bg-warning">' + lang.waiting + '</span>';
} else { } else {
item.is_running = '<span id="disabled-script" class="badge fs-6 bg-danger">' + lang.inactive + '</span>'; item.is_running = '<span id="inactive-script" class="badge fs-6 bg-warning">' + lang.waiting + '</span>';
} }
if (!item.last_run) { if (!item.last_run > 0) {
item.last_run = lang.never; item.last_run = lang.waiting;
} }
if (item.success == null) { if (item.success == null) {
item.success = '-'; item.success = '-';
@@ -277,9 +271,9 @@ jQuery(function($){
item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>'; item.success = '<i class="text-' + (item.success == 1 ? 'success' : 'danger') + ' bi bi-' + (item.success == 1 ? 'check-lg' : 'x-lg') + '"></i>';
} }
if (lang['syncjob_'+item.exit_status]) { if (lang['syncjob_'+item.exit_status]) {
item.exit_status = lang['syncjob_'+item.exit_status]; item.exit_status = lang['syncjob_'+item.exit_status];
} else if (item.success != '-') { } else if (item.success != '-') {
item.exit_status = lang.syncjob_check_log; item.exit_status = lang.syncjob_check_log;
} }
item.exit_status = item.success + ' ' + item.exit_status; item.exit_status = item.success + ' ' + item.exit_status;
}); });
@@ -337,6 +331,14 @@ jQuery(function($){
data: 'log', data: 'log',
defaultContent: '' defaultContent: ''
}, },
{
title: lang.active,
data: 'active',
defaultContent: '',
render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'
}
},
{ {
title: lang.status, title: lang.status,
data: 'is_running', data: 'is_running',
@@ -346,25 +348,22 @@ jQuery(function($){
{ {
title: lang.encryption, title: lang.encryption,
data: 'enc1', data: 'enc1',
defaultContent: '', defaultContent: ''
className: 'none'
}, },
{ {
title: lang.excludes, title: lang.excludes,
data: 'exclude', data: 'exclude',
defaultContent: '', defaultContent: ''
className: 'none'
}, },
{ {
title: lang.interval + " (min)", title: lang.interval + " (min)",
data: 'mins_interval', data: 'mins_interval',
defaultContent: '', defaultContent: ''
className: 'none'
}, },
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '', defaultContent: '',
responsivePriority: 5 responsivePriority: 5
} }
@@ -379,10 +378,11 @@ jQuery(function($){
} }
$('#app_passwd_table').DataTable({ $('#app_passwd_table').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -460,7 +460,7 @@ jQuery(function($){
{ {
title: lang.action, title: lang.action,
data: 'action', data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right', className: 'dt-sm-head-hidden dt-text-right',
defaultContent: '' defaultContent: ''
} }
] ]
@@ -474,10 +474,11 @@ jQuery(function($){
} }
$('#wl_policy_mailbox_table').DataTable({ $('#wl_policy_mailbox_table').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@@ -544,10 +545,11 @@ jQuery(function($){
} }
$('#bl_policy_mailbox_table').DataTable({ $('#bl_policy_mailbox_table').DataTable({
responsive: true, responsive: true,
processing: true, processing: true,
serverSide: false, serverSide: false,
stateSave: true, stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" + "tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",

View File

@@ -288,6 +288,18 @@ if (isset($_GET['query'])) {
case "domain-admin": case "domain-admin":
process_add_return(domain_admin('add', $attr)); process_add_return(domain_admin('add', $attr));
break; break;
case "sso":
switch ($object) {
case "domain-admin":
$data = domain_admin_sso('issue', $attr);
if($data) {
echo json_encode($data);
exit(0);
}
process_add_return($data);
break;
}
break;
case "admin": case "admin":
process_add_return(admin('add', $attr)); process_add_return(admin('add', $attr));
break; break;

View File

@@ -1,6 +1,6 @@
{ {
"acl": { "acl": {
"alias_domains": "Tilføj kældenavn domæner", "alias_domains": "Tilføj domænealias",
"app_passwds": "Administrer app-adgangskoder", "app_passwds": "Administrer app-adgangskoder",
"bcc_maps": "BCC kort", "bcc_maps": "BCC kort",
"delimiter_action": "Afgrænsning handling", "delimiter_action": "Afgrænsning handling",
@@ -22,9 +22,9 @@
"spam_alias": "Midlertidige aliasser", "spam_alias": "Midlertidige aliasser",
"spam_policy": "Sortliste / hvidliste", "spam_policy": "Sortliste / hvidliste",
"spam_score": "Spam-score", "spam_score": "Spam-score",
"syncjobs": "Synkroniser job", "syncjobs": "Synkroniserings job",
"tls_policy": "TLS politik", "tls_policy": "TLS politik",
"unlimited_quota": "Ubegrænset quote for mailbokse", "unlimited_quota": "Ubegrænset plads for mailbokse",
"domain_desc": "Skift domæne beskrivelse" "domain_desc": "Skift domæne beskrivelse"
}, },
"add": { "add": {
@@ -33,7 +33,7 @@
"add": "Tilføj", "add": "Tilføj",
"add_domain_only": "Tilføj kun domæne", "add_domain_only": "Tilføj kun domæne",
"add_domain_restart": "Tilføj domæne og genstart SOGo", "add_domain_restart": "Tilføj domæne og genstart SOGo",
"alias_address": "Alias adresse (r)", "alias_address": "Alias adresse(r)",
"alias_address_info": "<small>Fuld e-mail-adresse eller @ eksempel.com for at fange alle beskeder til et domæne (kommasepareret). <b> kun mailcow-domæner</b>.</small>", "alias_address_info": "<small>Fuld e-mail-adresse eller @ eksempel.com for at fange alle beskeder til et domæne (kommasepareret). <b> kun mailcow-domæner</b>.</small>",
"alias_domain": "Alias-domæne", "alias_domain": "Alias-domæne",
"alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>", "alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>",

View File

@@ -339,7 +339,8 @@
"oauth2_add_client": "Füge OAuth2 Client hinzu", "oauth2_add_client": "Füge OAuth2 Client hinzu",
"api_read_only": "Schreibgeschützter Zugriff", "api_read_only": "Schreibgeschützter Zugriff",
"api_read_write": "Lese-Schreib-Zugriff", "api_read_write": "Lese-Schreib-Zugriff",
"oauth2_apps": "OAuth2 Apps" "oauth2_apps": "OAuth2 Apps",
"queue_unban": "entsperren"
}, },
"danger": { "danger": {
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten", "access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
@@ -366,7 +367,7 @@
"domain_not_empty": "Domain %s ist nicht leer", "domain_not_empty": "Domain %s ist nicht leer",
"domain_not_found": "Domain %s nicht gefunden", "domain_not_found": "Domain %s nicht gefunden",
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein", "domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
"extended_sender_acl_denied": "Keine Rechte zum setzen von externen Absenderadressen", "extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen",
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig", "extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain", "extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s", "fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
@@ -454,17 +455,23 @@
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen", "totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits", "transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
"webauthn_verification_failed": "WebAuthn-Verifizierung fehlgeschlagen: %s", "webauthn_verification_failed": "WebAuthn-Verifizierung fehlgeschlagen: %s",
"webauthn_authenticator_failed": "Der ausgewählte Authenticator wurde nicht gefunden",
"webauthn_publickey_failed": "Zu dem ausgewählten Authenticator wurde kein Publickey hinterlegt",
"webauthn_username_failed": "Der ausgewählte Authenticator gehört zu einem anderen Konto",
"unknown": "Ein unbekannter Fehler trat auf", "unknown": "Ein unbekannter Fehler trat auf",
"unknown_tfa_method": "Unbekannte TFA-Methode", "unknown_tfa_method": "Unbekannte TFA-Methode",
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL", "unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
"username_invalid": "Benutzername %s kann nicht verwendet werden", "username_invalid": "Benutzername %s kann nicht verwendet werden",
"validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an", "validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an",
"value_missing": "Bitte alle Felder ausfüllen", "value_missing": "Bitte alle Felder ausfüllen",
"yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s" "yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s",
"template_exists": "Vorlage %s existiert bereits",
"template_id_invalid": "Vorlagen-ID %s ungültig",
"template_name_invalid": "Name der Vorlage ungültig"
}, },
"datatables": { "datatables": {
"collapse_all": "Alle Einklappen", "collapse_all": "Alle Einklappen",
"decimal": "", "decimal": ",",
"emptyTable": "Keine Daten in der Tabelle vorhanden", "emptyTable": "Keine Daten in der Tabelle vorhanden",
"expand_all": "Alle Ausklappen", "expand_all": "Alle Ausklappen",
"info": "_START_ bis _END_ von _TOTAL_ Einträgen", "info": "_START_ bis _END_ von _TOTAL_ Einträgen",
@@ -498,7 +505,7 @@
"current_time": "Systemzeit", "current_time": "Systemzeit",
"disk_usage": "Festplattennutzung", "disk_usage": "Festplattennutzung",
"docs": "Dokumente", "docs": "Dokumente",
"error_show_ip": "konnte die öffentlichen IP Adressen nicht auflösen", "error_show_ip": "Konnte die öffentlichen IP Adressen nicht auflösen",
"external_logs": "Externe Logs", "external_logs": "Externe Logs",
"history_all_servers": "History (alle Server)", "history_all_servers": "History (alle Server)",
"in_memory_logs": "In-memory Logs", "in_memory_logs": "In-memory Logs",
@@ -651,7 +658,8 @@
"title": "Objekt bearbeiten", "title": "Objekt bearbeiten",
"unchanged_if_empty": "Unverändert, wenn leer", "unchanged_if_empty": "Unverändert, wenn leer",
"username": "Benutzername", "username": "Benutzername",
"validate_save": "Validieren und speichern" "validate_save": "Validieren und speichern",
"pushover_sound": "Ton"
}, },
"fido2": { "fido2": {
"confirm": "Bestätigen", "confirm": "Bestätigen",
@@ -692,7 +700,8 @@
"quarantine": "Quarantäne", "quarantine": "Quarantäne",
"restart_netfilter": "Netfilter neustarten", "restart_netfilter": "Netfilter neustarten",
"restart_sogo": "SOGo neustarten", "restart_sogo": "SOGo neustarten",
"user_settings": "Benutzereinstellungen" "user_settings": "Benutzereinstellungen",
"mailcow_system": "System"
}, },
"info": { "info": {
"awaiting_tfa_confirmation": "Warte auf TFA-Verifizierung", "awaiting_tfa_confirmation": "Warte auf TFA-Verifizierung",
@@ -771,7 +780,6 @@
"edit": "Bearbeiten", "edit": "Bearbeiten",
"empty": "Keine Einträge vorhanden", "empty": "Keine Einträge vorhanden",
"enable_x": "Aktivieren", "enable_x": "Aktivieren",
"encryption": "Verschlüsselung",
"excludes": "Ausschlüsse", "excludes": "Ausschlüsse",
"filter_table": "Filtern", "filter_table": "Filtern",
"filters": "Filter", "filters": "Filter",
@@ -1176,7 +1184,6 @@
"recent_successful_connections": "Kürzlich erfolgreiche Verbindungen", "recent_successful_connections": "Kürzlich erfolgreiche Verbindungen",
"remove": "Entfernen", "remove": "Entfernen",
"running": "Wird ausgeführt", "running": "Wird ausgeführt",
"inactive": "Inaktiv",
"save": "Änderungen speichern", "save": "Änderungen speichern",
"save_changes": "Änderungen speichern", "save_changes": "Änderungen speichern",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Absenderprüfung deaktiviert</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Absenderprüfung deaktiviert</span>",
@@ -1222,7 +1229,7 @@
"user_settings": "Benutzereinstellungen", "user_settings": "Benutzereinstellungen",
"username": "Benutzername", "username": "Benutzername",
"verify": "Verifizieren", "verify": "Verifizieren",
"waiting": "Wartend", "waiting": "Warte auf Ausführung",
"week": "Woche", "week": "Woche",
"weekly": "Wöchentlich", "weekly": "Wöchentlich",
"weeks": "Wochen", "weeks": "Wochen",
@@ -1238,7 +1245,8 @@
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem", "syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung", "syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem", "syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort" "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
"pushover_sound": "Ton"
}, },
"warning": { "warning": {
"cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen", "cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen",

View File

@@ -458,6 +458,9 @@
"totp_verification_failed": "TOTP verification failed", "totp_verification_failed": "TOTP verification failed",
"transport_dest_exists": "Transport destination \"%s\" exists", "transport_dest_exists": "Transport destination \"%s\" exists",
"webauthn_verification_failed": "WebAuthn verification failed: %s", "webauthn_verification_failed": "WebAuthn verification failed: %s",
"webauthn_authenticator_failed": "The selected authenticator was not found",
"webauthn_publickey_failed": "No public key was stored for the selected authenticator",
"webauthn_username_failed": "The selected authenticator belongs to another account",
"unknown": "An unknown error occurred", "unknown": "An unknown error occurred",
"unknown_tfa_method": "Unknown TFA method", "unknown_tfa_method": "Unknown TFA method",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL", "unlimited_quota_acl": "Unlimited quota prohibited by ACL",
@@ -468,7 +471,7 @@
}, },
"datatables": { "datatables": {
"collapse_all": "Collapse All", "collapse_all": "Collapse All",
"decimal": "", "decimal": ".",
"emptyTable": "No data available in table", "emptyTable": "No data available in table",
"expand_all": "Expand All", "expand_all": "Expand All",
"info": "Showing _START_ to _END_ of _TOTAL_ entries", "info": "Showing _START_ to _END_ of _TOTAL_ entries",
@@ -779,7 +782,6 @@
"edit": "Edit", "edit": "Edit",
"empty": "No results", "empty": "No results",
"enable_x": "Enable", "enable_x": "Enable",
"encryption": "Encryption",
"excludes": "Excludes", "excludes": "Excludes",
"filter_table": "Filter table", "filter_table": "Filter table",
"filters": "Filters", "filters": "Filters",
@@ -1144,7 +1146,6 @@
"hour": "hour", "hour": "hour",
"hourly": "Hourly", "hourly": "Hourly",
"hours": "hours", "hours": "hours",
"inactive": "Inactive",
"in_use": "Used", "in_use": "Used",
"interval": "Interval", "interval": "Interval",
"is_catch_all": "Catch-all for domain/s", "is_catch_all": "Catch-all for domain/s",

View File

@@ -106,7 +106,8 @@
"username": "Používateľské meno", "username": "Používateľské meno",
"validate": "Overiť", "validate": "Overiť",
"validation_success": "Úspešne overené", "validation_success": "Úspešne overené",
"app_passwd_protocols": "Povolené protokoly k heslu aplikácie" "app_passwd_protocols": "Povolené protokoly k heslu aplikácie",
"tags": "Štítky"
}, },
"admin": { "admin": {
"access": "Prístup", "access": "Prístup",

View File

@@ -66,7 +66,7 @@ var lang = {{ lang_admin|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var admin_username = '{{ mailcow_cc_username }}'; var admin_username = '{{ mailcow_cc_username }}';
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}'; var pagination_size = Math.trunc('{{ pagination_size }}');
var log_pagination_size = '{{ log_pagination_size }}'; var log_pagination_size = Math.trunc('{{ log_pagination_size }}');
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -41,7 +41,7 @@
</div> </div>
<div class="col-sm-12 col-md-8"> <div class="col-sm-12 col-md-8">
<div class="table-responsive" style="margin-top: 10px;"> <div class="table-responsive" style="margin-top: 10px;">
<table class="table table-striped table-condensed"> <table class="table table-striped table-condensed w-100">
<tbody> <tbody>
<tr> <tr>
<td>Hostname</td> <td>Hostname</td>
@@ -627,6 +627,6 @@
var lang_debug = {{ lang_debug|raw }}; var lang_debug = {{ lang_debug|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var log_pagination_size = '{{ log_pagination_size }}'; var log_pagination_size = Math.trunc('{{ log_pagination_size }}');
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -46,7 +46,7 @@
<div class="col-sm-3 col-5 text-end">{{ lang.fido2.known_ids }}:</div> <div class="col-sm-3 col-5 text-end">{{ lang.fido2.known_ids }}:</div>
<div class="col-sm-9 col-7"> <div class="col-sm-9 col-7">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover table-condensed" id="fido2_keys"> <table class="table table-striped table-hover table-condensed w-100" id="fido2_keys">
<tr> <tr>
<th>ID</th> <th>ID</th>
<th style="min-width:240px;text-align: right">{{ lang.admin.action }}</th> <th style="min-width:240px;text-align: right">{{ lang.admin.action }}</th>

View File

@@ -26,7 +26,7 @@
var lang_user = {{ lang_user|raw }}; var lang_user = {{ lang_user|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}'; var pagination_size = Math.trunc('{{ pagination_size }}');
var table_for_domain = '{{ domain }}'; var table_for_domain = '{{ domain }}';
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -58,7 +58,7 @@
var lang_rl = {{ lang_rl|raw }}; var lang_rl = {{ lang_rl|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}'; var pagination_size = Math.trunc('{{ pagination_size }}');
var role = '{{ role }}'; var role = '{{ role }}';
var is_dual = {{ is_dual }}; var is_dual = {{ is_dual }};
var ALLOW_ADMIN_EMAIL_LOGIN = {{ allow_admin_email_login }}; var ALLOW_ADMIN_EMAIL_LOGIN = {{ allow_admin_email_login }};

View File

@@ -37,7 +37,7 @@
</p> </p>
{% endif %} {% endif %}
</p> </p>
<table id="quarantinetable" class="table table-striped"></table> <table id="quarantinetable" class="table table-striped w-100"></table>
<div class="mass-actions-quarantine mt-4"> <div class="mass-actions-quarantine mt-4">
<div class="btn-group" data-acl="{{ acl.quarantine }}"> <div class="btn-group" data-acl="{{ acl.quarantine }}">
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a> <a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a>
@@ -66,7 +66,7 @@ var acl = '{{ acl_json|raw }}';
var lang = {{ lang_quarantine|raw }}; var lang = {{ lang_quarantine|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}'; var pagination_size = Math.trunc('{{ pagination_size }}');
var role = '{{ role }}'; var role = '{{ role }}';
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -55,7 +55,7 @@
var lang = {{ lang_queue|raw }}; var lang = {{ lang_queue|raw }};
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}'; var pagination_size = Math.trunc('{{ pagination_size }}');
var table_for_domain = '{{ domain }}'; var table_for_domain = '{{ domain }}';
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -4,11 +4,7 @@
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs"> <button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-Syncjobs" data-bs-toggle="collapse" aria-controls="collapse-tab-Syncjobs">
{{ lang.user.sync_jobs }} {{ lang.user.sync_jobs }}
</button> </button>
<span class="d-none d-md-block">{{ lang.user.sync_jobs }} <span class="badge bg-info table-lines"></span></span> <span class="d-none d-md-block">{{ lang.user.sync_jobs }}
<div class="btn-group ms-auto d-flex">
<button class="btn btn-xs btn-secondary refresh_table" data-draw="draw_sync_job_table" data-table="sync_job_table">{{ lang.admin.refresh }}</button>
</div>
</div> </div>
<div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content"> <div id="collapse-tab-Syncjobs" class="card-body collapse" data-bs-parent="#user-content">
<div class="mass-actions-user mb-4"> <div class="mass-actions-user mb-4">

View File

@@ -4,7 +4,7 @@
var acl = '{{ acl_json|raw }}'; var acl = '{{ acl_json|raw }}';
var lang = {{ lang_user|raw }}; var lang = {{ lang_user|raw }};
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}'; var pagination_size = Math.trunc('{{ pagination_size }}');
var mailcow_cc_username = '{{ mailcow_cc_username }}'; var mailcow_cc_username = '{{ mailcow_cc_username }}';
var user_spam_score = [{{ user_spam_score }}]; var user_spam_score = [{{ user_spam_score }}];
var lang_datatables = {{ lang_datatables|raw }}; var lang_datatables = {{ lang_datatables|raw }};

View File

@@ -20,6 +20,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
'tfa_data' => $tfa_data, 'tfa_data' => $tfa_data,
'fido2_data' => $fido2_data, 'fido2_data' => $fido2_data,
'lang_user' => json_encode($lang['user']), 'lang_user' => json_encode($lang['user']),
'lang_datatables' => json_encode($lang['datatables']),
]; ];
} }
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {

View File

@@ -169,7 +169,7 @@ services:
- phpfpm - phpfpm
sogo-mailcow: sogo-mailcow:
image: mailcow/sogo:1.114 image: mailcow/sogo:1.115
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
@@ -389,7 +389,7 @@ services:
acme-mailcow: acme-mailcow:
depends_on: depends_on:
- nginx-mailcow - nginx-mailcow
image: mailcow/acme:1.83 image: mailcow/acme:1.84
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:
@@ -510,7 +510,7 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: mailcow/dockerapi:2.0 image: mailcow/dockerapi:2.01
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always

View File

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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$ # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
NEXTCLOUD_VERSION=25.0.2 NEXTCLOUD_VERSION=25.0.3
for bin in curl dirmngr; do for bin in curl dirmngr; do
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
@@ -46,22 +46,22 @@ if [[ ${NC_PURGE} == "y" ]]; then
echo -e "\033[33mDetecting Database information...\033[0m" 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 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" 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 DATABASE nextcloud;" > /dev/null
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP USER '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 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" 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 \
"$(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 "$(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 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" 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 \
"$(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 "$(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 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" echo -e "\033[33mNot purging anything...\033[0m"
exit 1 exit 1
fi fi
@@ -80,10 +80,10 @@ EOF
docker restart $(docker ps -aqf name=nginx-mailcow) 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 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,,} response=${response,,}
if [[ ! "$response" =~ ^(yes|y)$ ]]; then if [[ ! "$response" =~ ^(yes|y)$ ]]; then
echo "OK, aborting." echo "OK, aborting."
@@ -118,18 +118,18 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
&& mkdir -p ./data/web/nextcloud/data \ && mkdir -p ./data/web/nextcloud/data \
&& chmod +x ./data/web/nextcloud/occ && chmod +x ./data/web/nextcloud/occ
echo -e "\033[33mCreating Nextcloud Database...\033[0m" echo -e "\033[33mCreating 'nextcloud' database...\033[0m"
NC_DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28) NC_DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
NC_DBUSER=nextcloud NC_DBUSER=nextcloud
NC_DBNAME=nextcloud NC_DBNAME=nextcloud
echo -ne "[1/3] Creating nextcloud Database" echo -ne "[1/3] Creating 'nextcloud' database"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "CREATE DATABASE ${NC_DBNAME};" docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "CREATE DATABASE ${NC_DBNAME};"
sleep 2 sleep 2
echo -ne "\r[2/3] Creating nextcloud Database user" echo -ne "\r[2/3] Creating 'nextcloud' database user"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "CREATE USER '${NC_DBUSER}'@'%' IDENTIFIED BY '${NC_DBPASS}';" docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "CREATE USER '${NC_DBUSER}'@'%' IDENTIFIED BY '${NC_DBPASS}';"
sleep 2 sleep 2
echo -ne "\r[3/3] Granting nextcloud user all permissions on database nextcloud" echo -ne "\r[3/3] Granting 'nextcloud' user all permissions on database 'nextcloud'"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "GRANT ALL PRIVILEGES ON ${NC_DBNAME}.* TO '${NC_DBUSER}'@'%';" docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "GRANT ALL PRIVILEGES ON ${NC_DBNAME}.* TO '${NC_DBUSER}'@'%';"
sleep 2 sleep 2
@@ -140,7 +140,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
echo -ne "[1/4] Setting correct permissions for www-data" echo -ne "[1/4] Setting correct permissions for www-data"
docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud" docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud"
sleep 2 sleep 2
echo -ne "\r[2/4] Running occ maintenance:install to install nextcloud" echo -ne "\r[2/4] Running occ maintenance:install to install Nextcloud"
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings maintenance:install \ docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings maintenance:install \
--database mysql \ --database mysql \
--database-host mysql \ --database-host mysql \
@@ -149,9 +149,9 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
--database-pass ${NC_DBPASS} \ --database-pass ${NC_DBPASS} \
--admin-user admin \ --admin-user admin \
--admin-pass ${ADMIN_NC_PASS} \ --admin-pass ${ADMIN_NC_PASS} \
--data-dir /web/nextcloud/data 2>&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 "" 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; \ 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 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 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; \ #/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/ cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/
sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf
sleep 2 sleep 2
@@ -193,11 +193,11 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
echo "* INSTALL DATE: $(date +%Y-%m-%d_%H-%M-%S) *" echo "* INSTALL DATE: $(date +%Y-%m-%d_%H-%M-%S) *"
echo "******************************************" echo "******************************************"
echo "" echo ""
echo -e "\033[36mDatabase Name: ${NC_DBNAME}\033[0m" echo -e "\033[36mDatabase name: ${NC_DBNAME}\033[0m"
echo -e "\033[36mDatabase User: ${NC_DBUSER}\033[0m" echo -e "\033[36mDatabase user: ${NC_DBUSER}\033[0m"
echo -e "\033[36mDatabase Password: ${NC_DBPASS}\033[0m" echo -e "\033[36mDatabase password: ${NC_DBPASS}\033[0m"
echo "" echo ""
echo -e "\033[31mUI Admin Password: ${ADMIN_NC_PASS}\033[0m" echo -e "\033[31mUI admin password: ${ADMIN_NC_PASS}\033[0m"
echo "" echo ""