Compare commits
159 Commits
2023-01
...
feat/custo
Author | SHA1 | Date | |
---|---|---|---|
|
4c495200c8 | ||
|
73570cc8b5 | ||
|
959dcb9980 | ||
|
3eaa5a626c | ||
|
8c79056a94 | ||
|
ed076dc23e | ||
|
be2286c11c | ||
|
0e24c3d300 | ||
|
e1d8df6580 | ||
|
04a08a7d69 | ||
|
3c0c8aa01f | ||
|
026b278357 | ||
|
4121509ceb | ||
|
00ac61f0a4 | ||
|
4bb0dbb2f7 | ||
|
13b6df74af | ||
|
5c025bf865 | ||
|
20fc9eaf84 | ||
|
22a0479fab | ||
|
3510d5617d | ||
|
236d627fbd | ||
|
99739eada0 | ||
|
7bfef57894 | ||
|
d9dfe15253 | ||
|
3fe8aaa719 | ||
|
78a8fac6af | ||
|
6986e7758f | ||
|
b4a9df76b8 | ||
|
d9d958356a | ||
|
96f954a4e2 | ||
|
44585e1c15 | ||
|
c737ff4180 | ||
|
025279009d | ||
|
a9dc13d567 | ||
|
c3ed01c9b5 | ||
|
bd0b4a521e | ||
|
800a0ace71 | ||
|
db97869472 | ||
|
f681fcf154 | ||
|
db1b5956fc | ||
|
bdb07061ed | ||
|
428b917579 | ||
|
469f959e96 | ||
|
b68e189d97 | ||
|
028ef22878 | ||
|
80dacc015a | ||
|
0194c39bd5 | ||
|
f53ca24bb0 | ||
|
ae46a877d3 | ||
|
400939faf6 | ||
|
fd0205aafd | ||
|
e367a8ce24 | ||
|
096e2a41e9 | ||
|
e010f08143 | ||
|
3d2483ca37 | ||
|
535dd23509 | ||
|
4336a99c6a | ||
|
4cd5f93cdf | ||
|
67955779b0 | ||
|
26c34b484a | ||
|
4021613059 | ||
|
e891bf8411 | ||
|
f7798d1aac | ||
|
d11f00261b | ||
|
22cd12f37b | ||
|
db2fb12837 | ||
|
e808e595eb | ||
|
ce6742c676 | ||
|
cf3dc584d0 | ||
|
62f3603588 | ||
|
9fd4aa93e9 | ||
|
5bc3d93545 | ||
|
c28a6b89f0 | ||
|
1233613bea | ||
|
0206e0886c | ||
|
f6d135fbad | ||
|
f7da314dcf | ||
|
e6ce5e88f7 | ||
|
e5e6418be8 | ||
|
6507b53bbb | ||
|
0f59d4952b | ||
|
7225bd2f55 | ||
|
deb2b80352 | ||
|
ad9dee92be | ||
|
f36bc16ca7 | ||
|
bda5f0ed4a | ||
|
cbe1c97a82 | ||
|
81fcbdd104 | ||
|
1a9294b58f | ||
|
310c01aac2 | ||
|
229303c1f8 | ||
|
fc075bc6b7 | ||
|
d04f0257c2 | ||
|
d11d356803 | ||
|
c54750ef8b | ||
|
510ef5196b | ||
|
04e46f9f5b | ||
|
6c0a5028c0 | ||
|
791bbeeb39 | ||
|
a5b8f1b7f7 | ||
|
af267ff706 | ||
|
46cc022590 | ||
|
f77c65411d | ||
|
1052e13af8 | ||
|
11e1502b12 | ||
|
02afc45a15 | ||
|
3e1cfe0d08 | ||
|
d20df7d73e | ||
|
a8c61daeaf | ||
|
1a4f11209a | ||
|
04403aaf70 | ||
|
7f0dd7d0d7 | ||
|
cd29ad883e | ||
|
e1cd719a17 | ||
|
15bb331a7d | ||
|
6f3179bb8d | ||
|
29e5b87207 | ||
|
4403bc2d18 | ||
|
63e92e0897 | ||
|
aa4d8b1f47 | ||
|
9054ca18be | ||
|
38291d123f | ||
|
ca64ff2c0b | ||
|
dc85f49961 | ||
|
5dca4dac81 | ||
|
df8775d4c9 | ||
|
2bc663dcd5 | ||
|
1071bb8230 | ||
|
e437810eca | ||
|
e8fd34d31f | ||
|
6aebb8352e | ||
|
d684e0efc0 | ||
|
64ac6a8891 | ||
|
72e8180c6b | ||
|
d62c275004 | ||
|
aa7f562761 | ||
|
a1f033e4c1 | ||
|
58ddc31db6 | ||
|
5bf62481d5 | ||
|
6ff3f3f044 | ||
|
640f535e99 | ||
|
05d1a974eb | ||
|
99e38d81b1 | ||
|
ed7b384e24 | ||
|
5439ea1010 | ||
|
b719982504 | ||
|
8281d3fa55 | ||
|
9ba65a572e | ||
|
afddcf7f3b | ||
|
294569f5c9 | ||
|
ef6452cf55 | ||
|
9af40eba10 | ||
|
1b3a13ca19 | ||
|
71cc607de6 | ||
|
2ebd8345df | ||
|
f5baeb31c1 | ||
|
5abda44bc6 | ||
|
520d070081 | ||
|
5c57df4669 |
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||
uses: actions/stale@v7.0.0
|
||||
uses: actions/stale@v8.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||
days-before-stale: 60
|
||||
|
63
.github/workflows/integration_tests.yml
vendored
63
.github/workflows/integration_tests.yml
vendored
@@ -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'
|
2
.github/workflows/pr_to_nightly.yml
vendored
2
.github/workflows/pr_to_nightly.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run the Action
|
||||
uses: devops-infra/action-pull-request@v0.5.3
|
||||
uses: devops-infra/action-pull-request@v0.5.5
|
||||
with:
|
||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||
|
2
.github/workflows/rebuild_backup_image.yml
vendored
2
.github/workflows/rebuild_backup_image.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: data/Dockerfiles/backup/Dockerfile
|
||||
|
@@ -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'
|
@@ -1,6 +1,5 @@
|
||||
# mailcow: dockerized - 🐮 + 🐋 = 💕
|
||||
|
||||
[](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml)
|
||||
[](https://translate.mailcow.email/engage/mailcow-dockerized/)
|
||||
[](https://twitter.com/mailcow_email)
|
||||
|
||||
|
@@ -213,11 +213,13 @@ while true; do
|
||||
done
|
||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
|
||||
|
||||
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
|
||||
# Start IP detection
|
||||
log_f "Detecting IP addresses..."
|
||||
IPV4=$(get_ipv4)
|
||||
IPV6=$(get_ipv6)
|
||||
log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}"
|
||||
fi
|
||||
|
||||
#########################################
|
||||
# IP and webroot challenge verification #
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM clamav/clamav:1.0_base
|
||||
FROM clamav/clamav:1.0.1-1_base
|
||||
|
||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
||||
|
||||
|
@@ -13,6 +13,7 @@ RUN apk add --update --no-cache python3 \
|
||||
fastapi \
|
||||
uvicorn \
|
||||
aiodocker \
|
||||
docker \
|
||||
redis
|
||||
|
||||
COPY docker-entrypoint.sh /app/
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from fastapi import FastAPI, Response, Request
|
||||
import aiodocker
|
||||
import docker
|
||||
import psutil
|
||||
import sys
|
||||
import re
|
||||
@@ -9,11 +10,38 @@ import json
|
||||
import asyncio
|
||||
import redis
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
|
||||
|
||||
log_config = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"default": {
|
||||
"()": "uvicorn.logging.DefaultFormatter",
|
||||
"fmt": "%(levelprefix)s %(asctime)s %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"default": {
|
||||
"formatter": "default",
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stderr",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"api-logger": {"handlers": ["default"], "level": "INFO"},
|
||||
},
|
||||
}
|
||||
dictConfig(log_config)
|
||||
|
||||
containerIds_to_update = []
|
||||
host_stats_isUpdating = False
|
||||
app = FastAPI()
|
||||
logger = logging.getLogger('api-logger')
|
||||
|
||||
|
||||
@app.get("/host/stats")
|
||||
@@ -21,18 +49,15 @@ async def get_host_update_stats():
|
||||
global host_stats_isUpdating
|
||||
|
||||
if host_stats_isUpdating == False:
|
||||
print("start host stats task")
|
||||
asyncio.create_task(get_host_stats())
|
||||
host_stats_isUpdating = True
|
||||
|
||||
while True:
|
||||
if redis_client.exists('host_stats'):
|
||||
break
|
||||
print("wait for host_stats results")
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
|
||||
print("host stats pulled")
|
||||
stats = json.loads(redis_client.get('host_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
@@ -106,14 +131,14 @@ async def post_containers(container_id : str, post_action : str, request: Reques
|
||||
else:
|
||||
api_call_method_name = '__'.join(['container_post', str(post_action) ])
|
||||
|
||||
docker_utils = DockerUtils(async_docker_client)
|
||||
docker_utils = DockerUtils(sync_docker_client)
|
||||
api_call_method = getattr(docker_utils, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json"))
|
||||
|
||||
|
||||
print("api call: %s, container_id: %s" % (api_call_method_name, container_id))
|
||||
return await api_call_method(container_id, request_json)
|
||||
logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id))
|
||||
return api_call_method(container_id, request_json)
|
||||
except Exception as e:
|
||||
print("error - container_post: %s" % str(e))
|
||||
logger.error("error - container_post: %s" % str(e))
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
@@ -152,398 +177,340 @@ class DockerUtils:
|
||||
self.docker_client = docker_client
|
||||
|
||||
# api call: container_post - post_action: stop
|
||||
async def container_post__stop(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
await container.stop()
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': 'command completed successfully'
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
def container_post__stop(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
|
||||
container.stop()
|
||||
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: start
|
||||
async def container_post__start(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
await container.start()
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': 'command completed successfully'
|
||||
}
|
||||
def container_post__start(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
|
||||
container.start()
|
||||
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
# api call: container_post - post_action: restart
|
||||
async def container_post__restart(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
await container.restart()
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': 'command completed successfully'
|
||||
}
|
||||
def container_post__restart(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
|
||||
container.restart()
|
||||
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
# api call: container_post - post_action: top
|
||||
async def container_post__top(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
ps_exec = await container.exec("ps")
|
||||
async with ps_exec.start(detach=False) as stream:
|
||||
ps_return = await stream.read_out()
|
||||
|
||||
exec_details = await ps_exec.inspect()
|
||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': ps_return.data.decode('utf-8')
|
||||
}
|
||||
def container_post__top(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
|
||||
res = { 'type': 'success', 'msg': container.top()}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = {
|
||||
'type': 'danger',
|
||||
'msg': ''
|
||||
}
|
||||
# api call: container_post - post_action: stats
|
||||
def container_post__stats(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
|
||||
for stat in container.stats(decode=True, stream=True):
|
||||
res = { 'type': 'success', 'msg': stat}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: delete
|
||||
async def container_post__exec__mailq__delete(self, container_id, request_json):
|
||||
def container_post__exec__mailq__delete(self, container_id, request_json):
|
||||
if 'items' in request_json:
|
||||
r = re.compile("^[0-9a-fA-F]+$")
|
||||
filtered_qids = filter(r.match, request_json['items'])
|
||||
if filtered_qids:
|
||||
flagged_qids = ['-d %s' % i for i in filtered_qids]
|
||||
sanitized_string = str(' '.join(flagged_qids))
|
||||
sanitized_string = str(' '.join(flagged_qids));
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return exec_run_handler('generic', postsuper_r)
|
||||
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return await exec_run_handler('generic', postsuper_r_exec)
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: hold
|
||||
async def container_post__exec__mailq__hold(self, container_id, request_json):
|
||||
def container_post__exec__mailq__hold(self, container_id, request_json):
|
||||
if 'items' in request_json:
|
||||
r = re.compile("^[0-9a-fA-F]+$")
|
||||
filtered_qids = filter(r.match, request_json['items'])
|
||||
if filtered_qids:
|
||||
flagged_qids = ['-h %s' % i for i in filtered_qids]
|
||||
sanitized_string = str(' '.join(flagged_qids))
|
||||
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return await exec_run_handler('generic', postsuper_r_exec)
|
||||
sanitized_string = str(' '.join(flagged_qids));
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return exec_run_handler('generic', postsuper_r)
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: cat
|
||||
async def container_post__exec__mailq__cat(self, container_id, request_json):
|
||||
def container_post__exec__mailq__cat(self, container_id, request_json):
|
||||
if 'items' in request_json:
|
||||
r = re.compile("^[0-9a-fA-F]+$")
|
||||
filtered_qids = filter(r.match, request_json['items'])
|
||||
if filtered_qids:
|
||||
sanitized_string = str(' '.join(filtered_qids))
|
||||
sanitized_string = str(' '.join(filtered_qids));
|
||||
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
|
||||
return await exec_run_handler('utf8_text_only', postcat_exec)
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
|
||||
if not postcat_return:
|
||||
postcat_return = 'err: invalid'
|
||||
return exec_run_handler('utf8_text_only', postcat_return)
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: unhold
|
||||
async def container_post__exec__mailq__unhold(self, container_id, request_json):
|
||||
def container_post__exec__mailq__unhold(self, container_id, request_json):
|
||||
if 'items' in request_json:
|
||||
r = re.compile("^[0-9a-fA-F]+$")
|
||||
filtered_qids = filter(r.match, request_json['items'])
|
||||
if filtered_qids:
|
||||
flagged_qids = ['-H %s' % i for i in filtered_qids]
|
||||
sanitized_string = str(' '.join(flagged_qids))
|
||||
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return await exec_run_handler('generic', postsuper_r_exec)
|
||||
|
||||
sanitized_string = str(' '.join(flagged_qids));
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return exec_run_handler('generic', postsuper_r)
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: deliver
|
||||
async def container_post__exec__mailq__deliver(self, container_id, request_json):
|
||||
def container_post__exec__mailq__deliver(self, container_id, request_json):
|
||||
if 'items' in request_json:
|
||||
r = re.compile("^[0-9a-fA-F]+$")
|
||||
filtered_qids = filter(r.match, request_json['items'])
|
||||
if filtered_qids:
|
||||
flagged_qids = ['-i %s' % i for i in filtered_qids]
|
||||
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
for i in flagged_qids:
|
||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
|
||||
async with postsuper_r_exec.start(detach=False) as stream:
|
||||
postsuper_r_return = await stream.read_out()
|
||||
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
|
||||
# todo: check each exit code
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': 'Scheduled immediate delivery'
|
||||
}
|
||||
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: sogo - task: customize_enable
|
||||
def container_post__exec__sogo__customize_enable(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "/customize.sh enable"]
|
||||
sogo_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sogo_return)
|
||||
# api call: container_post - post_action: exec - cmd: sogo - task: customize_disable
|
||||
def container_post__exec__sogo__customize_disable(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "/customize.sh disable"]
|
||||
sogo_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sogo_return)
|
||||
# api call: container_post - post_action: exec - cmd: sogo - task: set_logo
|
||||
def container_post__exec__sogo__set_logo(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "/customize.sh set_logo"]
|
||||
sogo_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sogo_return)
|
||||
# api call: container_post - post_action: exec - cmd: sogo - task: remove_logo
|
||||
def container_post__exec__sogo__remove_logo(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "rm -f /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg"]
|
||||
sogo_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sogo_return)
|
||||
# api call: container_post - post_action: exec - cmd: sogo - task: set_favicon
|
||||
def container_post__exec__sogo__set_favicon(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "/customize.sh set_favicon"]
|
||||
sogo_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sogo_return)
|
||||
# api call: container_post - post_action: exec - cmd: sogo - task: remove_favicon
|
||||
def container_post__exec__sogo__remove_favicon(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "cp /sogo.ico /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico"]
|
||||
sogo_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sogo_return)
|
||||
# api call: container_post - post_action: exec - cmd: sogo - task: set_theme
|
||||
def container_post__exec__sogo__set_theme(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "/customize.sh set_theme"]
|
||||
sogo_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sogo_return)
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: list
|
||||
async def container_post__exec__mailq__list(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix')
|
||||
return await exec_run_handler('utf8_text_only', mailq_exec)
|
||||
|
||||
|
||||
def container_post__exec__mailq__list(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
|
||||
return exec_run_handler('utf8_text_only', mailq_return)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: flush
|
||||
async def container_post__exec__mailq__flush(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix')
|
||||
return await exec_run_handler('generic', postsuper_r_exec)
|
||||
|
||||
|
||||
def container_post__exec__mailq__flush(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
|
||||
return exec_run_handler('generic', postqueue_r)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete
|
||||
async def container_post__exec__mailq__super_delete(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"])
|
||||
return await exec_run_handler('generic', postsuper_r_exec)
|
||||
|
||||
|
||||
def container_post__exec__mailq__super_delete(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
|
||||
return exec_run_handler('generic', postsuper_r)
|
||||
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan
|
||||
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:
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
|
||||
async with rescan_exec.start(detach=False) as stream:
|
||||
rescan_return = await stream.read_out()
|
||||
|
||||
exec_details = await rescan_exec.inspect()
|
||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': 'fts_rescan: rescan triggered'
|
||||
}
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
|
||||
if rescan_return.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = {
|
||||
'type': 'warning',
|
||||
'msg': 'fts_rescan error'
|
||||
}
|
||||
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
if 'all' in request_json:
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
|
||||
async with rescan_exec.start(detach=False) as stream:
|
||||
rescan_return = await stream.read_out()
|
||||
|
||||
exec_details = await rescan_exec.inspect()
|
||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': 'fts_rescan: rescan triggered'
|
||||
}
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
|
||||
if rescan_return.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = {
|
||||
'type': 'warning',
|
||||
'msg': 'fts_rescan error'
|
||||
}
|
||||
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: system - task: df
|
||||
async def container_post__exec__system__df(self, container_id, request_json):
|
||||
def container_post__exec__system__df(self, container_id, request_json):
|
||||
if 'dir' in request_json:
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
df_exec = await container.exec(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
|
||||
async with df_exec.start(detach=False) as stream:
|
||||
df_return = await stream.read_out()
|
||||
|
||||
print(df_return)
|
||||
print(await df_exec.inspect())
|
||||
exec_details = await df_exec.inspect()
|
||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
||||
return df_return.data.decode('utf-8').rstrip()
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
|
||||
if df_return.exit_code == 0:
|
||||
return df_return.output.decode('utf-8').rstrip()
|
||||
else:
|
||||
return "0,0,0,0,0,0"
|
||||
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
|
||||
async def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
|
||||
for container in (await self.docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
|
||||
async with sql_exec.start(detach=False) as stream:
|
||||
sql_return = await stream.read_out()
|
||||
|
||||
exec_details = await sql_exec.inspect()
|
||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
||||
def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
|
||||
if sql_return.exit_code == 0:
|
||||
matched = False
|
||||
for line in sql_return.data.decode('utf-8').split("\n"):
|
||||
for line in sql_return.output.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')
|
||||
}
|
||||
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:
|
||||
await container.restart()
|
||||
res = {
|
||||
'type': 'warning',
|
||||
'msg': 'mysql_upgrade: upgrade was applied',
|
||||
'text': sql_return.data.decode('utf-8')
|
||||
}
|
||||
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.data.decode('utf-8')
|
||||
}
|
||||
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
|
||||
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')
|
||||
}
|
||||
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.data.decode('utf-8')
|
||||
}
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
async def container_post__exec__sieve__list(self, container_id, request_json):
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
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:
|
||||
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_exec = await container.exec(cmd)
|
||||
return await exec_run_handler('utf8_text_only', sieve_exec)
|
||||
|
||||
|
||||
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
|
||||
async def container_post__exec__maildir__cleanup(self, container_id, request_json):
|
||||
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:
|
||||
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_exec = await container.exec(cmd, user='vmail')
|
||||
return await exec_run_handler('generic', maildir_cleanup_exec)
|
||||
|
||||
vmail_name = request_json['maildir'].replace("'", "'\\''")
|
||||
cmd_vmail = "if [[ -d '/var/vmail/" + vmail_name + "' ]]; then /bin/mv '/var/vmail/" + vmail_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"
|
||||
index_name = request_json['maildir'].split("/")
|
||||
if len(index_name) > 1:
|
||||
index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''")
|
||||
cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi"
|
||||
cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index]
|
||||
else:
|
||||
cmd = ["/bin/bash", "-c", cmd_vmail]
|
||||
maildir_cleanup = container.exec_run(cmd, user='vmail')
|
||||
return exec_run_handler('generic', maildir_cleanup)
|
||||
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
|
||||
async def container_post__exec__rspamd__worker_password(self, container_id, request_json):
|
||||
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()
|
||||
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")
|
||||
|
||||
matched = False
|
||||
if "OK" in rspamd_password_return.data.decode('utf-8'):
|
||||
for line in cmd_response.split("\n"):
|
||||
if '$2$' in line:
|
||||
hash = line.strip()
|
||||
hash_out = re.search('\$2\$.+$', hash).group(0)
|
||||
rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
|
||||
rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
|
||||
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
|
||||
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
|
||||
if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
|
||||
container.restart()
|
||||
matched = True
|
||||
await container.restart()
|
||||
|
||||
if matched:
|
||||
res = {
|
||||
'type': 'success',
|
||||
'msg': 'command completed successfully'
|
||||
}
|
||||
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:
|
||||
res = {
|
||||
'type': 'danger',
|
||||
'msg': 'command did not complete'
|
||||
}
|
||||
logger.error('failed changing Rspamd password')
|
||||
res = { 'type': 'danger', 'msg': 'command did not complete' }
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
|
||||
|
||||
async def exec_run_handler(type, exec_obj):
|
||||
async with exec_obj.start(detach=False) as stream:
|
||||
exec_return = await stream.read_out()
|
||||
|
||||
if exec_return == None:
|
||||
exec_return = ""
|
||||
def recv_socket_data(c_socket, timeout):
|
||||
c_socket.setblocking(0)
|
||||
total_data=[]
|
||||
data=''
|
||||
begin=time.time()
|
||||
while True:
|
||||
if total_data and time.time()-begin > timeout:
|
||||
break
|
||||
elif time.time()-begin > timeout*2:
|
||||
break
|
||||
try:
|
||||
data = c_socket.recv(8192)
|
||||
if data:
|
||||
total_data.append(data.decode('utf-8'))
|
||||
#change the beginning time for measurement
|
||||
begin=time.time()
|
||||
else:
|
||||
exec_return = exec_return.data.decode('utf-8')
|
||||
#sleep for sometime to indicate a gap
|
||||
time.sleep(0.1)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
return ''.join(total_data)
|
||||
|
||||
|
||||
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':
|
||||
exec_details = await exec_obj.inspect()
|
||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
||||
res = {
|
||||
"type": "success",
|
||||
"msg": "command completed successfully"
|
||||
}
|
||||
if output.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully' }
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = {
|
||||
"type": "success",
|
||||
"msg": "'command failed: " + exec_return
|
||||
}
|
||||
res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') }
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
if type == 'utf8_text_only':
|
||||
return Response(content=exec_return, media_type="text/plain")
|
||||
return Response(content=output.output.decode('utf-8'), media_type="text/plain")
|
||||
|
||||
async def get_host_stats(wait=5):
|
||||
global host_stats_isUpdating
|
||||
@@ -570,12 +537,10 @@ async def get_host_stats(wait=5):
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
print(json.dumps(res, indent=4))
|
||||
|
||||
await asyncio.sleep(wait)
|
||||
host_stats_isUpdating = False
|
||||
|
||||
|
||||
async def get_container_stats(container_id, wait=5, stop=False):
|
||||
global containerIds_to_update
|
||||
|
||||
@@ -598,13 +563,11 @@ async def get_container_stats(container_id, wait=5, stop=False):
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
print(json.dumps(res, indent=4))
|
||||
else:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "no or invalid id defined"
|
||||
}
|
||||
print(json.dumps(res, indent=4))
|
||||
|
||||
await asyncio.sleep(wait)
|
||||
if stop == True:
|
||||
@@ -615,9 +578,13 @@ async def get_container_stats(container_id, wait=5, stop=False):
|
||||
await get_container_stats(container_id, wait=0, stop=True)
|
||||
|
||||
|
||||
|
||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
||||
redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
|
||||
else:
|
||||
redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0)
|
||||
|
||||
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
|
||||
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
|
||||
|
||||
logger.info('DockerApi started')
|
||||
|
@@ -21,6 +21,7 @@ RUN groupadd -g 5000 vmail \
|
||||
&& touch /etc/default/locale \
|
||||
&& apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
build-essential \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
cpanminus \
|
||||
@@ -61,6 +62,7 @@ RUN groupadd -g 5000 vmail \
|
||||
libproc-processtable-perl \
|
||||
libreadonly-perl \
|
||||
libregexp-common-perl \
|
||||
libssl-dev \
|
||||
libsys-meminfo-perl \
|
||||
libterm-readkey-perl \
|
||||
libtest-deep-perl \
|
||||
@@ -110,6 +112,8 @@ RUN groupadd -g 5000 vmail \
|
||||
&& apt-get autoclean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -rf /tmp/* /var/tmp/* /root/.cache/
|
||||
# imapsync dependencies
|
||||
RUN cpan Crypt::OpenSSL::PKCS12
|
||||
|
||||
COPY trim_logs.sh /usr/local/bin/trim_logs.sh
|
||||
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
|
||||
|
@@ -8492,6 +8492,7 @@ sub xoauth2
|
||||
require HTML::Entities ;
|
||||
require JSON ;
|
||||
require JSON::WebToken::Crypt::RSA ;
|
||||
require Crypt::OpenSSL::PKCS12;
|
||||
require Crypt::OpenSSL::RSA ;
|
||||
require Encode::Byte ;
|
||||
require IO::Socket::SSL ;
|
||||
@@ -8532,8 +8533,9 @@ sub xoauth2
|
||||
|
||||
$sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
|
||||
|
||||
# Get private key from p12 file (would be better in perl...)
|
||||
$key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`;
|
||||
# Get private key from p12 file
|
||||
my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile);
|
||||
$key = $pkcs12->private_key($keypass);
|
||||
|
||||
$sync->{ debug } and myprint( "Private key:\n$key\n");
|
||||
}
|
||||
|
@@ -64,28 +64,40 @@ def refreshF2boptions():
|
||||
global f2boptions
|
||||
global quit_now
|
||||
global exit_code
|
||||
if not r.get('F2B_OPTIONS'):
|
||||
|
||||
f2boptions = {}
|
||||
f2boptions['ban_time'] = int
|
||||
f2boptions['max_attempts'] = int
|
||||
f2boptions['retry_window'] = int
|
||||
f2boptions['netban_ipv4'] = int
|
||||
f2boptions['netban_ipv6'] = int
|
||||
f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800
|
||||
f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10
|
||||
f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600
|
||||
f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 32
|
||||
f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 128
|
||||
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
||||
|
||||
if not r.get('F2B_OPTIONS'):
|
||||
f2boptions['ban_time'] = r.get('F2B_BAN_TIME')
|
||||
f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME')
|
||||
f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT')
|
||||
f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS')
|
||||
f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW')
|
||||
f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4')
|
||||
f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6')
|
||||
else:
|
||||
try:
|
||||
f2boptions = {}
|
||||
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
||||
except ValueError:
|
||||
print('Error loading F2B options: F2B_OPTIONS is not json')
|
||||
quit_now = True
|
||||
exit_code = 2
|
||||
|
||||
verifyF2boptions(f2boptions)
|
||||
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
||||
|
||||
def verifyF2boptions(f2boptions):
|
||||
verifyF2boption(f2boptions,'ban_time', 1800)
|
||||
verifyF2boption(f2boptions,'max_ban_time', 10000)
|
||||
verifyF2boption(f2boptions,'ban_time_increment', True)
|
||||
verifyF2boption(f2boptions,'max_attempts', 10)
|
||||
verifyF2boption(f2boptions,'retry_window', 600)
|
||||
verifyF2boption(f2boptions,'netban_ipv4', 32)
|
||||
verifyF2boption(f2boptions,'netban_ipv6', 128)
|
||||
|
||||
def verifyF2boption(f2boptions, f2boption, f2bdefault):
|
||||
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
|
||||
|
||||
def refreshF2bregex():
|
||||
global f2bregex
|
||||
global quit_now
|
||||
@@ -147,6 +159,7 @@ def ban(address):
|
||||
global lock
|
||||
refreshF2boptions()
|
||||
BAN_TIME = int(f2boptions['ban_time'])
|
||||
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
RETRY_WINDOW = int(f2boptions['retry_window'])
|
||||
NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4'])
|
||||
@@ -174,20 +187,16 @@ def ban(address):
|
||||
net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
|
||||
net = str(net)
|
||||
|
||||
if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW:
|
||||
bans[net] = { 'attempts': 0 }
|
||||
active_window = RETRY_WINDOW
|
||||
else:
|
||||
active_window = time.time() - bans[net]['last_attempt']
|
||||
if not net in bans:
|
||||
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
|
||||
|
||||
bans[net]['attempts'] += 1
|
||||
bans[net]['last_attempt'] = time.time()
|
||||
|
||||
active_window = time.time() - bans[net]['last_attempt']
|
||||
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
cur_time = int(round(time.time()))
|
||||
logCrit('Banning %s for %d minutes' % (net, BAN_TIME / 60))
|
||||
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
|
||||
logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
|
||||
if type(ip) is ipaddress.IPv4Address:
|
||||
with lock:
|
||||
chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
|
||||
@@ -206,7 +215,7 @@ def ban(address):
|
||||
rule.target = target
|
||||
if rule not in chain.rules:
|
||||
chain.insert_rule(rule)
|
||||
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME)
|
||||
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
||||
else:
|
||||
logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
||||
|
||||
@@ -238,7 +247,8 @@ def unban(net):
|
||||
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
if net in bans:
|
||||
del bans[net]
|
||||
bans[net]['attempts'] = 0
|
||||
bans[net]['ban_counter'] += 1
|
||||
|
||||
def permBan(net, unban=False):
|
||||
global lock
|
||||
@@ -332,7 +342,7 @@ def watch():
|
||||
logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data']))
|
||||
ban(addr)
|
||||
except Exception as ex:
|
||||
logWarn('Error reading log line from pubsub')
|
||||
logWarn('Error reading log line from pubsub: %s' % ex)
|
||||
quit_now = True
|
||||
exit_code = 2
|
||||
|
||||
@@ -359,7 +369,15 @@ def snat4(snat_target):
|
||||
chain = iptc.Chain(table, 'POSTROUTING')
|
||||
table.autocommit = False
|
||||
new_rule = get_snat4_rule()
|
||||
|
||||
if not chain.rules:
|
||||
# if there are no rules in the chain, insert the new rule directly
|
||||
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
|
||||
chain.insert_rule(new_rule)
|
||||
else:
|
||||
for position, rule in enumerate(chain.rules):
|
||||
if not hasattr(rule.target, 'parameter'):
|
||||
continue
|
||||
match = all((
|
||||
new_rule.get_src() == rule.get_src(),
|
||||
new_rule.get_dst() == rule.get_dst(),
|
||||
@@ -374,6 +392,7 @@ def snat4(snat_target):
|
||||
if match:
|
||||
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}')
|
||||
chain.delete_rule(rule)
|
||||
|
||||
table.commit()
|
||||
table.autocommit = True
|
||||
except:
|
||||
@@ -418,6 +437,8 @@ def autopurge():
|
||||
time.sleep(10)
|
||||
refreshF2boptions()
|
||||
BAN_TIME = int(f2boptions['ban_time'])
|
||||
MAX_BAN_TIME = int(f2boptions['max_ban_time'])
|
||||
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
||||
if QUEUE_UNBAN:
|
||||
@@ -425,7 +446,9 @@ def autopurge():
|
||||
unban(str(net))
|
||||
for net in bans.copy():
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
if time.time() - bans[net]['last_attempt'] > BAN_TIME:
|
||||
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
|
||||
TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
|
||||
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME:
|
||||
unban(net)
|
||||
|
||||
def isIpNetwork(address):
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM php:8.1-fpm-alpine3.17
|
||||
FROM php:8.2-fpm-alpine3.17
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
|
||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
|
||||
@@ -12,7 +12,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0
|
||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced
|
||||
ARG REDIS_PECL_VERSION=5.3.7
|
||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced
|
||||
ARG COMPOSER_VERSION=2.5.1
|
||||
ARG COMPOSER_VERSION=2.5.5
|
||||
|
||||
RUN apk add -U --no-cache autoconf \
|
||||
aspell-dev \
|
||||
@@ -52,6 +52,7 @@ RUN apk add -U --no-cache autoconf \
|
||||
libxpm-dev \
|
||||
libzip \
|
||||
libzip-dev \
|
||||
linux-headers \
|
||||
make \
|
||||
mysql-client \
|
||||
openldap-dev \
|
||||
@@ -75,7 +76,7 @@ RUN apk add -U --no-cache autoconf \
|
||||
--with-webp \
|
||||
--with-xpm \
|
||||
--with-avif \
|
||||
&& docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets zip bcmath gmp \
|
||||
&& docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets sysvsem zip bcmath gmp \
|
||||
&& docker-php-ext-configure imap --with-imap --with-imap-ssl \
|
||||
&& docker-php-ext-install -j 4 imap \
|
||||
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
|
||||
@@ -99,6 +100,7 @@ RUN apk add -U --no-cache autoconf \
|
||||
libxml2-dev \
|
||||
libxpm-dev \
|
||||
libzip-dev \
|
||||
linux-headers \
|
||||
make \
|
||||
openldap-dev \
|
||||
pcre-dev \
|
||||
|
@@ -25,6 +25,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
||||
psmisc \
|
||||
wget \
|
||||
patch \
|
||||
redis-tools \
|
||||
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
||||
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
|
||||
&& chmod +x /usr/local/bin/gosu \
|
||||
@@ -46,10 +47,14 @@ COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY acl.diff /acl.diff
|
||||
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||
COPY customize.sh /
|
||||
COPY docker-entrypoint.sh /
|
||||
|
||||
RUN rm -rf /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
|
||||
RUN mv /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico /sogo.ico
|
||||
RUN chmod +x /bootstrap-sogo.sh \
|
||||
/usr/local/sbin/stop-supervisor.sh
|
||||
/usr/local/sbin/stop-supervisor.sh \
|
||||
/customize.sh
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
|
@@ -240,6 +240,8 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
||||
|
||||
# Copy logo, if any
|
||||
[[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
|
||||
# Use the mailcow logo if no sogo-full.svg file does exist
|
||||
! [[ -f /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg ]] && cp /etc/sogo/cow_mailcow.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
|
||||
|
||||
# Rsync web content
|
||||
echo "Syncing web content with named volume"
|
||||
|
112
data/Dockerfiles/sogo/customize.sh
Normal file
112
data/Dockerfiles/sogo/customize.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$1" == "enable" ]]; then
|
||||
# enable debug mode
|
||||
if grep -q "SOGoUIxDebugEnabled = YES;" "/etc/sogo/sogo.conf"; then
|
||||
sed -i "s|//SOGoUIxDebugEnabled = YES;|SOGoUIxDebugEnabled = YES;|" "/etc/sogo/sogo.conf"
|
||||
else
|
||||
echo "SOGoUIxDebugEnabled = YES;" >> "/etc/sogo/sogo.conf"
|
||||
fi
|
||||
|
||||
echo "Success: SOGoUIxDebugEnabled has been enabled"
|
||||
elif [[ "$1" == "disable" ]]; then
|
||||
# disable debug mode
|
||||
if grep -q "SOGoUIxDebugEnabled = YES;" "/etc/sogo/sogo.conf"; then
|
||||
if ! grep -q "//SOGoUIxDebugEnabled = YES;" "/etc/sogo/sogo.conf"; then
|
||||
sed -i "s|SOGoUIxDebugEnabled = YES;|//SOGoUIxDebugEnabled = YES;|" "/etc/sogo/sogo.conf"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Success: SOGoUIxDebugEnabled has been disabled"
|
||||
elif [[ "$1" == "set_theme" ]]; then
|
||||
# Get the sogo palettes from Redis
|
||||
PRIMARY=$(redis-cli -h redis HGET SOGO_THEME primary)
|
||||
if [ $? -ne 0 ]; then
|
||||
PRIMARY="green"
|
||||
fi
|
||||
ACCENT=$(redis-cli -h redis HGET SOGO_THEME accent)
|
||||
if [ $? -ne 0 ]; then
|
||||
ACCENT="green"
|
||||
fi
|
||||
BACKGROUND=$(redis-cli -h redis HGET SOGO_THEME background)
|
||||
if [ $? -ne 0 ]; then
|
||||
BACKGROUND="grey"
|
||||
fi
|
||||
|
||||
# Read custom palettes
|
||||
if [ -f /etc/sogo/custom-palettes.js ]; then
|
||||
COLORS=$(cat /etc/sogo/custom-palettes.js)
|
||||
else
|
||||
COLORS=""
|
||||
fi
|
||||
|
||||
# Write theme to /usr/lib/GNUstep/SOGo/WebServerResources/js/theme.js
|
||||
cat > /usr/lib/GNUstep/SOGo/WebServerResources/js/theme.js <<EOL
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.Common')
|
||||
.config(configure)
|
||||
|
||||
configure.\$inject = ['\$mdThemingProvider'];
|
||||
function configure(\$mdThemingProvider) {
|
||||
|
||||
$COLORS
|
||||
|
||||
var primary = \$mdThemingProvider.extendPalette('$PRIMARY', {});
|
||||
var accent = \$mdThemingProvider.extendPalette('$ACCENT', {
|
||||
'A100': 'ffffff'
|
||||
});
|
||||
var background = \$mdThemingProvider.extendPalette('$BACKGROUND', {});
|
||||
|
||||
\$mdThemingProvider.definePalette('primary-cow', primary);
|
||||
\$mdThemingProvider.definePalette('accent-cow', accent);
|
||||
\$mdThemingProvider.definePalette('background-cow', background);
|
||||
|
||||
\$mdThemingProvider.theme('default')
|
||||
.primaryPalette('primary-cow', primarySettings)
|
||||
.accentPalette('accent-cow', accentSettings)
|
||||
.backgroundPalette('background-cow', backgroundSettings);
|
||||
\$mdThemingProvider.generateThemesOnDemand(false);
|
||||
}
|
||||
})();
|
||||
EOL
|
||||
|
||||
echo "Success: Theme configuration written"
|
||||
elif [[ "$1" == "set_logo" ]]; then
|
||||
# Get the image data from Redis and save it to a tmp file
|
||||
redis-cli -h redis GET MAIN_LOGO > /tmp/logo_base64.txt
|
||||
|
||||
# Check if mime type is svg+xml
|
||||
mime_type=$(awk -F'[:;]' '{print $2}' /tmp/logo_base64.txt | sed 's/.*\///')
|
||||
if [ "$mime_type" != "svg+xml" ]; then
|
||||
echo "Error: Image format must be of type svg"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Decode base64 and save to file
|
||||
payload=$(cat /tmp/logo_base64.txt | sed 's/^data:[^;]*;//' | awk '{ sub(/^base64,/, ""); print $0 }')
|
||||
echo $payload | base64 -d | tee /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg > /dev/null
|
||||
|
||||
# Remove temp file
|
||||
rm /tmp/logo_base64.txt
|
||||
echo "Success: Image has been set"
|
||||
elif [[ "$1" == "set_favicon" ]]; then
|
||||
# Get the image data from Redis and save it to a tmp file
|
||||
redis-cli -h redis GET FAVICON > /tmp/favicon_base64.txt
|
||||
|
||||
# Check if mime type is png or ico
|
||||
mime_type=$(awk -F'[:;]' '{print $2}' /tmp/favicon_base64.txt | sed 's/.*\///')
|
||||
if [[ "$mime_type" != "png" && "$mime_type" != "ico" ]]; then
|
||||
echo "Error: Image format must be of type png or ico"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Decode base64 and save to file
|
||||
payload=$(cat /tmp/favicon_base64.txt | sed 's/^data:[^;]*;//' | awk '{ sub(/^base64,/, ""); print $0 }')
|
||||
echo $payload | base64 -d | tee /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico > /dev/null
|
||||
|
||||
# Remove temp file
|
||||
rm /tmp/favicon_base64.txt
|
||||
echo "Success: Image has been set"
|
||||
fi
|
@@ -24,7 +24,7 @@ server {
|
||||
add_header X-Download-Options "noopen" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||
add_header X-Robots-Tag "none" always;
|
||||
add_header X-Robots-Tag "noindex, nofollow" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
|
@@ -8,7 +8,7 @@ VIRUS_FOUND {
|
||||
}
|
||||
# Bad policy from free mail providers
|
||||
FREEMAIL_POLICY_FAILURE {
|
||||
expression = "-g+:policies & !DMARC_POLICY_ALLOW & !MAILLIST & ( FREEMAIL_ENVFROM | FREEMAIL_FROM ) & !WHITELISTED_FWD_HOST";
|
||||
expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies";
|
||||
score = 16.0;
|
||||
}
|
||||
# Applies to freemail with undisclosed recipients
|
||||
|
@@ -159,8 +159,8 @@ BAZAAR_ABUSE_CH {
|
||||
}
|
||||
|
||||
URLHAUS_ABUSE_CH {
|
||||
type = "url";
|
||||
filter = "full";
|
||||
type = "selector";
|
||||
selector = "urls";
|
||||
map = "https://urlhaus.abuse.ch/downloads/text_online/";
|
||||
score = 10.0;
|
||||
}
|
||||
@@ -175,7 +175,7 @@ BAD_SUBJECT_00 {
|
||||
type = "header";
|
||||
header = "subject";
|
||||
regexp = true;
|
||||
map = "http://nullnull.org/bad-subject-regex.txt";
|
||||
map = "http://fuzzy.mailcow.email/bad-subject-regex.txt";
|
||||
score = 6.0;
|
||||
symbols_set = ["BAD_SUBJECT_00"];
|
||||
}
|
||||
|
182
data/conf/sogo/cow_mailcow.svg
Normal file
182
data/conf/sogo/cow_mailcow.svg
Normal file
@@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="layer1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="434.82101"
|
||||
height="376.871"
|
||||
viewBox="0 0 434.82101 376.871"
|
||||
enable-background="new 0 0 374.82 356.871"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="cow_mailcow.svg"><metadata
|
||||
id="metadata77"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title><cc:license
|
||||
rdf:resource="" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs75" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1721"
|
||||
inkscape:window-height="1177"
|
||||
id="namedview73"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4142136"
|
||||
inkscape:cx="219.01206"
|
||||
inkscape:cy="236.74714"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
fit-margin-top="10"
|
||||
fit-margin-left="50"
|
||||
fit-margin-bottom="10"
|
||||
fit-margin-right="10"
|
||||
showguides="true" /><g
|
||||
id="g3"
|
||||
transform="translate(50,10)"><g
|
||||
id="grey_5_"><path
|
||||
d="m 55.948,213.25 c 0.07331,-20.26146 -0.716379,-17.26061 -3.655806,-39.26743 2.227824,-22.4392 -7.627923,-38.85857 -7.669233,-58.34044 0,-4.715 -5.805961,-6.78013 -4.760961,-11.13713 -6.292,13.037 -9.833,27.707 -9.833,43.222 0,25.946 9.89,49.533 26.027,67.059 -0.048,-0.511 -0.082,-1.023 -0.108,-1.536 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#3d5263"
|
||||
sodipodi:nodetypes="ccccscc" /></g><g
|
||||
id="yellow"><path
|
||||
d="m 254.808,180.412 -0.567,0.455 c -10.49,39.88 -40.951,71.658 -80.048,83.996 l 10.952,9.206 53.296,44.799 31.601,26.563 c 0.783,-2.011 1.229,-4.19 1.231,-6.478 0,-0.007 10e-4,-0.013 10e-4,-0.02 l 0,-16.836 0,-10e-4 0,-28.141 0,-126.736 -16.466,13.193 z"
|
||||
id="path9"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f9e82d" /><path
|
||||
d="m 23.027,185.52 -6.574,-5.225 -16.452,-13.076 0,90.407 0,81.307 c 0,2.295 0.447,4.481 1.233,6.499 l 58.39,-48.683 26.964,-22.481 12.38,-10.321 C 62.73,251.524 34.307,222.274 23.027,185.52 Z"
|
||||
id="path11"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f9e82d" /><path
|
||||
d="m 238.441,318.868 -53.296,-44.799 -10.952,-9.206 c -11.431,3.607 -23.597,5.558 -36.22,5.558 -13.653,0 -26.772,-2.28 -39.004,-6.474 l -12.38,10.321 -26.965,22.482 -58.39,48.683 c 2.605,6.69 9.094,11.438 16.706,11.438 l 235.394,0 c 7.613,0 14.103,-4.749 16.707,-11.44 l -31.6,-26.563 z"
|
||||
id="path13"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#edd514;fill-opacity:0.89499996" /></g><g
|
||||
id="grey_4_"><path
|
||||
enable-background="new "
|
||||
d="M 238.441,318.868 C 196.984,322.876 123.368,324.434 59.625,296.75 38.082,287.394 17.666,274.7 0.002,257.627 l 0,81.307 c 0,2.295 0.447,4.481 1.233,6.499 2.605,6.69 9.094,11.438 16.706,11.438 l 235.394,0 c 7.613,0 14.103,-4.749 16.707,-11.44 0.783,-2.011 1.229,-4.19 1.231,-6.478 l 0,-24.584 c 0,0 -12.58,2.541 -32.832,4.499 z"
|
||||
id="path16"
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.1;fill:#3d5263" /><path
|
||||
enable-background="new "
|
||||
d="m 86.588,274.268 c 14.979,6.703 31.579,10.435 49.051,10.435 17.648,0 34.408,-3.803 49.505,-10.634 37.082,-16.777 64.125,-51.824 69.664,-93.657 l -0.567,0.455 c -10.49,39.88 -40.951,71.658 -80.048,83.996 -11.431,3.607 -23.597,5.558 -36.22,5.558 -13.653,0 -26.772,-2.28 -39.004,-6.474 C 62.731,251.524 34.308,222.274 23.028,185.52 l -6.574,-5.225 c 5.525,42.054 32.786,77.261 70.134,93.973 z"
|
||||
id="path18"
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.1;fill:#3d5263" /></g><g
|
||||
id="white_1_"><path
|
||||
d="m 54.293,63.875 c -1.799,1.745 -3.541,3.548 -5.229,5.402 -0.042,0.046 -0.085,0.092 -0.127,0.139 -0.234,0.258 -0.473,0.51 -0.705,0.77 0.055,-0.055 0.111,-0.108 0.166,-0.163 21.76,-21.782 51.828,-35.259 85.046,-35.259 66.396,0 120.222,53.826 120.222,120.223 0,30.718 -11.526,58.74 -30.482,79.991 21.633,-21.737 35.006,-51.7 35.01,-84.791 0,-0.004 0,-0.009 0,-0.013 0,-21.143 -5.465,-41.007 -15.049,-58.269 -1.449,-2.608 -2.991,-5.157 -4.624,-7.643 -5.377,-8.187 -11.727,-15.676 -18.885,-22.307 -5.903,-5.467 -12.351,-10.354 -19.26,-14.558 -4.278,-2.604 -8.734,-4.944 -13.341,-7.006 -10.627,-4.756 -22.07,-8.016 -34.062,-9.509 -4.915,-0.612 -9.921,-0.931 -15.001,-0.931 -5.747,0 -11.398,0.409 -16.93,1.189 -12.291,1.733 -23.981,5.329 -34.784,10.487 -4.742,2.264 -9.313,4.83 -13.688,7.672 -6.561,4.266 -12.682,9.149 -18.277,14.576 z"
|
||||
id="path21"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" /><path
|
||||
d="m 95.828,118.535 c 2.559,0 4.63,-2.071 4.63,-4.629 0,-2.553 -2.071,-4.626 -4.63,-4.626 -2.558,0 -4.634,2.074 -4.634,4.626 10e-4,2.557 2.076,4.629 4.634,4.629 z"
|
||||
id="path23"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" /><path
|
||||
d="m 186.85,118.535 c 2.556,0 4.629,-2.071 4.629,-4.629 0,-2.553 -2.074,-4.626 -4.629,-4.626 -2.559,0 -4.631,2.074 -4.631,4.626 0,2.557 2.073,4.629 4.631,4.629 z"
|
||||
id="path25"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" /></g><g
|
||||
id="grey_3_"><g
|
||||
id="g28"><path
|
||||
d="m 223.701,234.394 c 18.648,-21.18 29.965,-48.971 29.965,-79.408 0,-66.396 -53.825,-120.223 -120.222,-120.223 -33.218,0 -63.286,13.477 -85.046,35.259 -4.591,5.125 -8.746,10.647 -12.413,16.507 -1.524,2.437 -2.963,4.931 -4.314,7.48 -7.067,13.341 -11.704,28.167 -13.301,43.893 -0.411,4.043 -0.622,8.146 -0.622,12.298 0,3.849 0.188,7.653 0.542,11.409 0.776,8.241 2.38,16.24 4.735,23.912 11.281,36.754 39.703,66.004 75.941,78.427 12.231,4.193 25.351,6.474 39.004,6.474 12.623,0 24.79,-1.95 36.22,-5.558 18.139,-5.725 34.412,-15.64 47.7,-28.603 0.536,-0.522 1.811,-1.867 1.811,-1.867 z m -5.788,-58.356 c -2.132,7.217 -5.052,14.085 -8.668,20.495 -16.571,29.372 -47.64,49.146 -83.233,49.146 -27.584,0 -52.447,-11.88 -69.956,-30.895 C 39.919,197.26 30.03,173.673 30.03,147.726 c 0,-15.515 3.54,-30.185 9.833,-43.222 15.648,-32.42 48.344,-54.73 86.15,-54.73 3.967,0 7.876,0.25 11.717,0.728 47.479,5.898 84.262,47.175 84.262,97.224 -0.002,9.846 -1.431,19.348 -4.079,28.312 z"
|
||||
id="path30"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f1f2f2" /></g><path
|
||||
d="m 49.064,69.277 c -0.042,0.046 -0.085,0.092 -0.127,0.139 0.042,-0.047 0.085,-0.093 0.127,-0.139 z"
|
||||
id="path32"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f1f2f2" /></g><g
|
||||
id="darkbrown_1_"><path
|
||||
d="m 257.626,161.89 c -0.488,5.062 -1.29,10.032 -2.387,14.89 -0.31,1.371 -0.643,2.733 -0.999,4.086 l 0.567,-0.455 16.466,-13.193 0,-0.023 -13.647,-5.305 z"
|
||||
id="path35"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#5a3620" /><path
|
||||
d="m 0.001,167.219 16.451,13.076 6.574,5.225 c -2.354,-7.672 -3.959,-15.671 -4.735,-23.912 l -2.85,0.871 L 0,167.196"
|
||||
id="path37"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#5a3620" /><path
|
||||
d="m 87.491,192.337 c -6.21,0 -11.254,5.034 -11.254,11.257 0,6.216 5.043,11.257 11.254,11.257 6.221,0 11.261,-5.041 11.261,-11.257 0,-6.223 -5.041,-11.257 -11.261,-11.257 z"
|
||||
id="path39"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#5a3620" /><path
|
||||
d="m 181.307,192.337 c -6.218,0 -11.259,5.034 -11.259,11.257 0,6.216 5.041,11.257 11.259,11.257 6.22,0 11.257,-5.041 11.257,-11.257 0,-6.223 -5.037,-11.257 -11.257,-11.257 z"
|
||||
id="path41"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#5a3620" /><path
|
||||
d="m 182.997,102.25057 c -6.963,0 -15.44243,7.76632 -15.44243,14.73532 0,6.965 8.12588,17.2072 15.08888,17.2072 6.968,0 15.79898,-9.53609 15.79898,-16.50009 0.001,-6.97 -8.47743,-15.44243 -15.44543,-15.44243 z m 3.853,16.28443 c -2.558,0 -4.631,-2.072 -4.631,-4.629 0,-2.552 2.072,-4.626 4.631,-4.626 2.555,0 4.629,2.073 4.629,4.626 0,2.558 -2.073,4.629 -4.629,4.629 z"
|
||||
id="path43"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#5a3620"
|
||||
sodipodi:nodetypes="ssscssssss" /><path
|
||||
d="m 89.709786,102.60413 c -6.971,0 -14.379767,8.11987 -14.379767,15.08887 0,6.965 8.824981,16.14653 15.793981,16.14653 6.963,0 15.79298,-9.18253 15.79298,-16.14653 0.001,-6.97 -10.243194,-15.08887 -17.207194,-15.08887 z M 95.828,118.535 c -2.559,0 -4.634,-2.072 -4.634,-4.629 0,-2.552 2.076,-4.626 4.634,-4.626 2.559,0 4.63,2.073 4.63,4.626 0,2.558 -2.071,4.629 -4.63,4.629 z"
|
||||
id="path45"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#5a3620"
|
||||
sodipodi:nodetypes="ssscssssss" /></g><g
|
||||
id="cream"><path
|
||||
d="m 336.302,256.425 c 3.59,-9.155 7.701,-11 9.346,-11.346 -40.757,3.757 -36.661,27.769 -34.026,35.96 0.55,1.712 1.037,2.733 1.037,2.733 0,0 2.031,4.787 7.536,8.748 4.149,2.986 10.27,5.503 18.995,5.144 27.063,0.461 35.631,-50.166 35.631,-50.166 -6.654,11.655 -26.404,9.876 -38.519,8.927 z"
|
||||
id="path48"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fef3df" /><path
|
||||
d="m 48.937,69.415 c 0.042,-0.046 0.085,-0.092 0.127,-0.139 1.688,-1.854 3.43,-3.657 5.229,-5.402 -8.915,-6.977 -24.344,-15.826 -41.744,-11.633 0,0 2.814,20.458 23.437,34.287 3.667,-5.86 7.822,-11.381 12.413,-16.507 -0.055,0.055 -0.111,0.108 -0.166,0.163 0.231,-0.258 0.47,-0.511 0.704,-0.769 z"
|
||||
id="path50"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fef3df" /><path
|
||||
d="m 258.812,52.242 c -15.831,-3.815 -30.029,3.169 -39.176,9.714 7.158,6.63 13.508,14.12 18.885,22.307 17.763,-13.689 20.291,-32.021 20.291,-32.021 z"
|
||||
id="path52"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fef3df" /><path
|
||||
d="m 134.269,160.225 c -43.299,0 -78.388,22.964 -78.388,51.289 0,0.582 0.038,1.157 0.067,1.735 0.026,0.514 0.06,1.025 0.108,1.535 17.508,19.015 42.371,30.895 69.956,30.895 35.594,0 66.662,-19.774 83.233,-49.146 -9.796,-21.016 -39.651,-36.308 -74.976,-36.308 z M 87.491,214.85 c -6.211,0 -11.254,-5.041 -11.254,-11.257 0,-6.223 5.044,-11.257 11.254,-11.257 6.22,0 11.261,5.034 11.261,11.257 0,6.216 -5.04,11.257 -11.261,11.257 z m 93.816,0 c -6.218,0 -11.259,-5.041 -11.259,-11.257 0,-6.223 5.041,-11.257 11.259,-11.257 6.22,0 11.257,5.034 11.257,11.257 0,6.216 -5.037,11.257 -11.257,11.257 z"
|
||||
id="path54"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fef3df" /><path
|
||||
d="M 86.265,0 C 68.102,16.373 86.113,41.427 86.258,41.628 97.061,36.47 108.751,32.874 121.042,31.141 97.629,27.686 86.265,0 86.265,0 Z"
|
||||
id="path56"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fef3df" /><path
|
||||
d="m 186.204,0 c 0,0 -10.863,26.476 -33.231,30.883 11.992,1.493 23.435,4.752 34.062,9.509 C 190.383,35.136 202.036,14.271 186.204,0 Z"
|
||||
id="path58"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fef3df" /></g><g
|
||||
id="g60"><path
|
||||
d="m 217.913,176.038 c 2.647,-8.964 6.55187,-25.89162 6.55187,-35.73662 C 224.46487,90.252379 185.208,56.4 137.728,50.502 c -2.157,28.03 3.629,87.043 80.185,125.536 z m -47.53,-58.345 c 0,-6.97 5.651,-12.614 12.614,-12.614 6.968,0 12.617,5.645 12.617,12.614 0,6.964 -5.649,12.611 -12.617,12.611 -6.963,0 -12.614,-5.646 -12.614,-12.611 z"
|
||||
id="path62"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#87654a"
|
||||
sodipodi:nodetypes="csccsssss" /></g><g
|
||||
id="brown"><path
|
||||
d="m 312.658,283.772 c 0,0 -0.487,-1.021 -1.037,-2.733 -3.758,3.317 -13.036,10.236 -27.03,12.416 l 0,-10e-4 c -0.009,0.002 -0.019,0.003 -0.027,0.005 -4.044,0.628 -8.479,0.863 -13.29,0.497 l 0,28.141 c 2.059,-0.801 4.607,-1.834 7.477,-3.083 5.462,-2.377 12.093,-5.542 18.771,-9.395 0.027,-0.016 0.054,-0.031 0.081,-0.047 8.158,-4.713 16.37,-10.452 22.593,-17.052 -5.506,-3.961 -7.538,-8.748 -7.538,-8.748 z"
|
||||
id="path65"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b58765" /><path
|
||||
d="m 12.549,52.242 c 17.4,-4.193 32.83,4.656 41.744,11.633 C 59.888,58.449 66.009,53.565 72.57,49.301 48.272,18.498 2.169,37.201 2.169,37.201 -1.114,67.502 15.288,84.594 31.672,94.01 33.023,91.461 34.462,88.966 35.986,86.53 15.363,72.699 12.549,52.242 12.549,52.242 Z"
|
||||
id="path67"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b58765" /><path
|
||||
d="m 200.376,47.398 c 6.909,4.205 13.356,9.091 19.26,14.558 9.146,-6.545 23.345,-13.529 39.176,-9.714 0,0 -2.527,18.332 -20.291,32.021 1.633,2.485 3.175,5.034 4.624,7.643 15.141,-9.784 29.097,-26.539 26.046,-54.704 0,-10e-4 -44.152,-17.909 -68.815,10.196 z"
|
||||
id="path69"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b58765" /><path
|
||||
d="m 138.854,50.502 c -3.841,-0.478 -8.875,-0.728 -12.842,-0.728 -37.806,0 -70.502,22.31 -86.15,54.73 -1.045,4.357 -1.603,8.897 -1.603,13.612 0,1.454 0.085,2.787 0.121,4.175 0.127,3.935 0.448,7.585 0.855,11.135 4.291755,24.95762 7.959057,42.49186 13.464,66.758 0.056,0.407 0.164,0.804 0.224,1.211 0.617,4.028 1.642,7.992 3.025,11.854 -0.029,-0.578 -0.067,-1.153 -0.067,-1.735 0,-28.325 35.089,-51.289 78.388,-51.289 35.325,0 65.181,15.292 74.977,36.308 3.616,-6.409 6.536,-13.277 8.668,-20.495 C 179.98905,152.54886 163.9995,134.88987 153.25313,111.82124 142.50675,88.752624 137.775,64.517 138.854,50.502 Z m -47.73,79.802 c -6.97,0 -12.612,-5.646 -12.612,-12.611 0,-6.97 5.642,-12.614 12.612,-12.614 6.964,0 12.611,5.645 12.611,12.614 0.001,6.964 -5.648,12.611 -12.611,12.611 z"
|
||||
id="path71"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b58765"
|
||||
sodipodi:nodetypes="cscscccccssccscssscs" /></g></g></svg>
|
After Width: | Height: | Size: 14 KiB |
7
data/conf/sogo/custom-palettes.js
Normal file
7
data/conf/sogo/custom-palettes.js
Normal file
@@ -0,0 +1,7 @@
|
||||
$mdThemingProvider.definePalette("sogo-green",{50:"eaf5e9",100:"cbe5c8",200:"aad6a5",300:"88c781",400:"66b86a",500:"56b04c",600:"4da143",700:"388e3c",800:"367d2e",900:"225e1b",A100:"ffffff",A200:"69f0ae",A400:"00e676",A700:"00c853",contrastDefaultColor:"dark",contrastLightColors:["300","400","500","600","700","800","900"]})
|
||||
$mdThemingProvider.definePalette("sogo-blue",{50:"f0faf9",100:"e1f5f3",200:"ceebe8",300:"bfe0dd",400:"b2d6d3",500:"a1ccc8",600:"8ebfbb",700:"7db3b0",800:"639997",900:"4d8080",A100:"d4f7fa",A200:"c3f5fa",A400:"53e3f0",A700:"00b0c0",contrastDefaultColor:"light",contrastDarkColors:["50","100","200"]})
|
||||
$mdThemingProvider.definePalette("sogo-grey",$mdThemingProvider.extendPalette("grey",{1e3:"baa870"}))
|
||||
|
||||
var primarySettings = {default:"900","hue-1":"400","hue-2":"800","hue-3":"A700"}
|
||||
var accentSettings = {default:"500","hue-1":"A100","hue-2":"300","hue-3":"A700"}
|
||||
var backgroundSettings = {}
|
@@ -1,36 +1,34 @@
|
||||
/* EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.Common')
|
||||
.config(configure)
|
||||
|
||||
configure.$inject = ['$mdThemingProvider'];
|
||||
function configure($mdThemingProvider) {
|
||||
var greyMap = $mdThemingProvider.extendPalette('grey', {
|
||||
'200': 'F5F5F5',
|
||||
'300': 'E5E5E5',
|
||||
'1000': '4C566A'
|
||||
|
||||
$mdThemingProvider.definePalette("sogo-green",{50:"eaf5e9",100:"cbe5c8",200:"aad6a5",300:"88c781",400:"66b86a",500:"56b04c",600:"4da143",700:"388e3c",800:"367d2e",900:"225e1b",A100:"ffffff",A200:"69f0ae",A400:"00e676",A700:"00c853",contrastDefaultColor:"dark",contrastLightColors:["300","400","500","600","700","800","900"]})
|
||||
$mdThemingProvider.definePalette("sogo-blue",{50:"f0faf9",100:"e1f5f3",200:"ceebe8",300:"bfe0dd",400:"b2d6d3",500:"a1ccc8",600:"8ebfbb",700:"7db3b0",800:"639997",900:"4d8080",A100:"d4f7fa",A200:"c3f5fa",A400:"53e3f0",A700:"00b0c0",contrastDefaultColor:"light",contrastDarkColors:["50","100","200"]})
|
||||
$mdThemingProvider.definePalette("sogo-grey",$mdThemingProvider.extendPalette("grey",{1e3:"baa870"}))
|
||||
|
||||
var primarySettings = {default:"900","hue-1":"400","hue-2":"800","hue-3":"A700"}
|
||||
var accentSettings = {default:"500","hue-1":"A100","hue-2":"300","hue-3":"A700"}
|
||||
var backgroundSettings = {}
|
||||
|
||||
var primary = $mdThemingProvider.extendPalette('sogo-blue', {});
|
||||
var accent = $mdThemingProvider.extendPalette('sogo-green', {
|
||||
'A100': 'ffffff'
|
||||
});
|
||||
var greenCow = $mdThemingProvider.extendPalette('green', {
|
||||
'600': 'E5E5E5'
|
||||
});
|
||||
$mdThemingProvider.definePalette('frost-grey', greyMap);
|
||||
$mdThemingProvider.definePalette('green-cow', greenCow);
|
||||
var background = $mdThemingProvider.extendPalette('sogo-grey', {});
|
||||
|
||||
$mdThemingProvider.definePalette('primary-cow', primary);
|
||||
$mdThemingProvider.definePalette('accent-cow', accent);
|
||||
$mdThemingProvider.definePalette('background-cow', background);
|
||||
|
||||
$mdThemingProvider.theme('default')
|
||||
.primaryPalette('green-cow', {
|
||||
'default': '400',
|
||||
'hue-1': '400',
|
||||
'hue-2': '600',
|
||||
'hue-3': 'A700'
|
||||
})
|
||||
.accentPalette('green', {
|
||||
'default': '600',
|
||||
'hue-1': '300',
|
||||
'hue-2': '300',
|
||||
'hue-3': 'A700'
|
||||
})
|
||||
.backgroundPalette('frost-grey');
|
||||
.primaryPalette('primary-cow', primarySettings)
|
||||
.accentPalette('accent-cow', accentSettings)
|
||||
.backgroundPalette('background-cow', backgroundSettings);
|
||||
$mdThemingProvider.generateThemesOnDemand(false);
|
||||
}
|
||||
})();
|
||||
*/
|
@@ -62,7 +62,7 @@
|
||||
SOGoFirstDayOfWeek = "1";
|
||||
|
||||
SOGoSieveFolderEncoding = "UTF-8";
|
||||
SOGoPasswordChangeEnabled = YES;
|
||||
SOGoPasswordChangeEnabled = NO;
|
||||
SOGoSentFolderName = "Sent";
|
||||
SOGoMailShowSubscribedFoldersOnly = NO;
|
||||
NGImap4ConnectionStringSeparator = "/";
|
||||
|
@@ -103,9 +103,12 @@ $template_data = [
|
||||
'rsettings' => $rsettings,
|
||||
'rspamd_regex_maps' => $rspamd_regex_maps,
|
||||
'logo_specs' => customize('get', 'main_logo_specs'),
|
||||
'favicon_specs' => customize('get', 'favicon_specs'),
|
||||
'ip_check' => customize('get', 'ip_check'),
|
||||
'password_complexity' => password_complexity('get'),
|
||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||
'sogo_palettes' => $GLOBALS['SOGO_PALETTES'],
|
||||
'sogo_theme' => customize('get', 'sogo_theme'),
|
||||
'lang_admin' => json_encode($lang['admin']),
|
||||
'lang_datatables' => json_encode($lang['datatables'])
|
||||
];
|
||||
|
@@ -699,6 +699,38 @@ paths:
|
||||
type: string
|
||||
type: object
|
||||
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:
|
||||
post:
|
||||
responses:
|
||||
@@ -3144,8 +3176,10 @@ paths:
|
||||
example:
|
||||
attr:
|
||||
ban_time: "86400"
|
||||
ban_time_increment: "1"
|
||||
blacklist: "10.100.6.5/32,10.100.8.4/32"
|
||||
max_attempts: "5"
|
||||
max_ban_time: "86400"
|
||||
netban_ipv4: "24"
|
||||
netban_ipv6: "64"
|
||||
retry_window: "600"
|
||||
@@ -3159,11 +3193,17 @@ paths:
|
||||
description: the backlisted ips or hostnames separated by comma
|
||||
type: string
|
||||
ban_time:
|
||||
description: the time a ip should be banned
|
||||
description: the time an ip should be banned
|
||||
type: number
|
||||
ban_time_increment:
|
||||
description: if the time of the ban should increase each time
|
||||
type: boolean
|
||||
max_attempts:
|
||||
description: the maximum numbe of wrong logins before a ip is banned
|
||||
type: number
|
||||
max_ban_time:
|
||||
description: the maximum time an ip should be banned
|
||||
type: number
|
||||
netban_ipv4:
|
||||
description: the networks mask to ban for ipv4
|
||||
type: number
|
||||
@@ -4081,10 +4121,12 @@ paths:
|
||||
response:
|
||||
value:
|
||||
ban_time: 604800
|
||||
ban_time_increment: 1
|
||||
blacklist: |-
|
||||
45.82.153.37/32
|
||||
92.118.38.52/32
|
||||
max_attempts: 1
|
||||
max_ban_time: 604800
|
||||
netban_ipv4: 32
|
||||
netban_ipv6: 128
|
||||
perm_bans:
|
||||
@@ -5560,6 +5602,109 @@ paths:
|
||||
description: You can list all mailboxes existing in system for a specific domain.
|
||||
operationId: Get mailboxes of a domain
|
||||
summary: Get mailboxes of a domain
|
||||
/api/v1/edit/sogo_theme/:
|
||||
post:
|
||||
responses:
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
- type: success
|
||||
log:
|
||||
- customize
|
||||
- edit
|
||||
- sogo_theme
|
||||
- primary: "brown"
|
||||
accent: "brown"
|
||||
background: "amber"
|
||||
msg:
|
||||
- sogo_theme_modified
|
||||
schema:
|
||||
properties:
|
||||
log:
|
||||
description: contains request object
|
||||
items: {}
|
||||
type: array
|
||||
msg:
|
||||
items: {}
|
||||
type: array
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
type: object
|
||||
description: OK
|
||||
headers: {}
|
||||
tags:
|
||||
- Customize
|
||||
description: >-
|
||||
Using this endpoint you can edit the sogo theme. SOGo has to be restarted after each change.
|
||||
operationId: Edit SOGo theme
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
example:
|
||||
primary: "brown"
|
||||
accent: "brown"
|
||||
background: "amber"
|
||||
summary: Edit SOGo theme
|
||||
/api/v1/delete/sogo_theme/:
|
||||
post:
|
||||
responses:
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
- type: success
|
||||
log:
|
||||
- customize
|
||||
- delete
|
||||
- sogo_theme
|
||||
- items:
|
||||
- sogo-theme
|
||||
msg: "sogo_theme_removed"
|
||||
schema:
|
||||
properties:
|
||||
log:
|
||||
description: contains request object
|
||||
items: {}
|
||||
type: array
|
||||
msg:
|
||||
items: {}
|
||||
type: array
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
type: object
|
||||
description: OK
|
||||
headers: {}
|
||||
tags:
|
||||
- Customize
|
||||
description: >-
|
||||
Using this endpoint you can reset the sogo theme. SOGo has to be restarted after each change.
|
||||
operationId: Reset SOGo theme
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
example:
|
||||
- items:
|
||||
- "sogo-theme"
|
||||
summary: Reset SOGo theme
|
||||
|
||||
tags:
|
||||
- name: Domains
|
||||
@@ -5586,6 +5731,8 @@ tags:
|
||||
description: Manage DKIM keys
|
||||
- name: Domain admin
|
||||
description: Create or udpdate domain admin users
|
||||
- name: Single Sign-On
|
||||
description: Issue tokens for users
|
||||
- name: Address Rewriting
|
||||
description: Create BCC maps or recipient maps
|
||||
- name: Outgoing TLS Policy Map Overrides
|
||||
@@ -5602,3 +5749,5 @@ tags:
|
||||
description: Get the status of your cow
|
||||
- name: Ratelimits
|
||||
description: Edit domain ratelimits
|
||||
- name: Customize
|
||||
description: You can customize mailcow's appearance
|
@@ -342,6 +342,10 @@ div.dataTables_wrapper div.dt-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper span.sorting-value {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.dataTables_scrollHead table.dataTable {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
@@ -78,3 +78,21 @@ table.dataTable>tbody>tr.child span.dtr-title {
|
||||
width: 30%;
|
||||
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;
|
||||
}
|
||||
|
@@ -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 {
|
||||
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;
|
||||
}
|
@@ -203,6 +203,9 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.senders-mw220 {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
|
@@ -66,4 +66,6 @@ table tbody tr td input[type="checkbox"] {
|
||||
padding: .2em .4em .3em !important;
|
||||
background-color: #ececec!important;
|
||||
}
|
||||
|
||||
.badge.bg-info .bi {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
@@ -99,4 +99,6 @@ table tbody tr td input[type="checkbox"] {
|
||||
font-size:110%;
|
||||
margin:20px;
|
||||
}
|
||||
|
||||
.senders-mw220 {
|
||||
max-width: 220px;
|
||||
}
|
||||
|
@@ -20,6 +20,11 @@ legend {
|
||||
background-color: #7a7a7a !important;
|
||||
border-color: #5c5c5c !important;
|
||||
}
|
||||
.btn-dark {
|
||||
color: #000 !important;;
|
||||
background-color: #f6f6f6 !important;;
|
||||
border-color: #ddd !important;;
|
||||
}
|
||||
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
|
||||
border-color: #7a7a7a !important;
|
||||
}
|
||||
|
@@ -73,6 +73,81 @@ function customize($_action, $_item, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'set_logo'));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'upload_success'
|
||||
);
|
||||
break;
|
||||
case 'favicon':
|
||||
if (in_array($_data['favicon']['type'], array('image/png', 'image/x-icon'))) {
|
||||
try {
|
||||
if (file_exists($_data['favicon']['tmp_name']) !== true) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'img_tmp_missing'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$image = new Imagick($_data['favicon']['tmp_name']);
|
||||
if ($image->valid() !== true) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'img_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$available_sizes = array(32, 128, 180, 192, 256);
|
||||
if ($image->getImageWidth() != $image->getImageHeight()) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'img_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!in_array($image->getImageWidth(), $available_sizes)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'img_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$image->destroy();
|
||||
}
|
||||
catch (ImagickException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'img_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'invalid_mime_type'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$redis->Set('FAVICON', 'data:' . $_data['favicon']['type'] . ';base64,' . base64_encode(file_get_contents($_data['favicon']['tmp_name'])));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'set_favicon'));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
@@ -179,6 +254,34 @@ function customize($_action, $_item, $_data = null) {
|
||||
'msg' => 'ip_check_opt_in_modified'
|
||||
);
|
||||
break;
|
||||
case 'sogo_theme':
|
||||
$_data['primary'] = (isset($_data['primary']) && in_array($_data['primary'], $GLOBALS['SOGO_PALETTES'])) ? $_data['primary'] : 'green';
|
||||
$_data['accent'] = (isset($_data['accent']) && in_array($_data['accent'], $GLOBALS['SOGO_PALETTES'])) ? $_data['accent'] : 'green';
|
||||
$_data['background'] = (isset($_data['background']) && in_array($_data['background'], $GLOBALS['SOGO_PALETTES'])) ? $_data['background'] : 'grey';
|
||||
|
||||
try {
|
||||
$redis->hSet('SOGO_THEME', 'primary', $_data['primary']);
|
||||
$redis->hSet('SOGO_THEME', 'accent', $_data['accent']);
|
||||
$redis->hSet('SOGO_THEME', 'background', $_data['background']);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'customize_enable'));
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'set_theme'));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'sogo_theme_modified'
|
||||
);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
@@ -203,6 +306,7 @@ function customize($_action, $_item, $_data = null) {
|
||||
case 'main_logo':
|
||||
try {
|
||||
if ($redis->del('MAIN_LOGO')) {
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'remove_logo'));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
@@ -220,6 +324,51 @@ function customize($_action, $_item, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'favicon':
|
||||
try {
|
||||
if ($redis->del('FAVICON')) {
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'remove_favicon'));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'reset_favicon'
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'sogo_theme':
|
||||
try {
|
||||
$redis->hSet('SOGO_THEME', 'primary', 'sogo-blue');
|
||||
$redis->hSet('SOGO_THEME', 'accent', 'sogo-green');
|
||||
$redis->hSet('SOGO_THEME', 'background', 'sogo-grey');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'set_theme'));
|
||||
$docker_return = docker('post', 'sogo-mailcow', 'exec', array('cmd' => 'sogo', 'task' => 'customize_disable'));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'sogo_theme_removed'
|
||||
);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'get':
|
||||
@@ -251,6 +400,19 @@ function customize($_action, $_item, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'favicon':
|
||||
try {
|
||||
return $redis->get('FAVICON');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'ui_texts':
|
||||
try {
|
||||
$data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI';
|
||||
@@ -295,6 +457,25 @@ function customize($_action, $_item, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'favicon_specs':
|
||||
try {
|
||||
$image = new Imagick();
|
||||
$img_data = explode('base64,', customize('get', 'favicon'));
|
||||
if ($img_data[1]) {
|
||||
$image->readImageBlob(base64_decode($img_data[1]));
|
||||
return $image->identifyImage();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (ImagickException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'imagick_exception'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'ip_check':
|
||||
try {
|
||||
$ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0;
|
||||
@@ -309,6 +490,28 @@ function customize($_action, $_item, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'sogo_theme':
|
||||
$data = array();
|
||||
try {
|
||||
$data['primary'] = $redis->hGet('SOGO_THEME', 'primary');
|
||||
$data['accent'] = $redis->hGet('SOGO_THEME', 'accent');
|
||||
$data['background'] = $redis->hGet('SOGO_THEME', 'background');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$data['primary'] = empty($data['primary']) ? 'sogo-blue' : $data['primary'];
|
||||
$data['accent'] = empty($data['accent']) ? 'sogo-green' : $data['accent'];
|
||||
$data['background'] = empty($data['background']) ? 'sogo-grey' : $data['background'];
|
||||
|
||||
return $data;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@@ -405,3 +405,64 @@ function domain_admin($_action, $_data = null) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -239,7 +239,9 @@ function fail2ban($_action, $_data = null) {
|
||||
$is_now = fail2ban('get');
|
||||
if (!empty($is_now)) {
|
||||
$ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
|
||||
$ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0;
|
||||
$max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']);
|
||||
$max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']);
|
||||
$retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']);
|
||||
$netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']);
|
||||
$netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']);
|
||||
@@ -256,6 +258,8 @@ function fail2ban($_action, $_data = null) {
|
||||
}
|
||||
$f2b_options = array();
|
||||
$f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time;
|
||||
$f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false;
|
||||
$f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time;
|
||||
$f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4;
|
||||
$f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
|
||||
$f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;
|
||||
|
@@ -1015,20 +1015,58 @@ function formatBytes($size, $precision = 2) {
|
||||
}
|
||||
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
|
||||
}
|
||||
function update_sogo_static_view() {
|
||||
function update_sogo_static_view($mailbox = null) {
|
||||
if (getenv('SKIP_SOGO') == "y") {
|
||||
return true;
|
||||
}
|
||||
global $pdo;
|
||||
global $lang;
|
||||
$stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_NAME = 'sogo_view'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
if ($num_results != 0) {
|
||||
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
|
||||
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view");
|
||||
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
|
||||
|
||||
$mailbox_exists = false;
|
||||
if ($mailbox !== null) {
|
||||
// Check if the mailbox exists
|
||||
$stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'");
|
||||
$stmt->execute(array(':mailbox' => $mailbox));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row){
|
||||
$mailbox_exists = true;
|
||||
}
|
||||
}
|
||||
|
||||
$query = "REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
|
||||
SELECT
|
||||
mailbox.username,
|
||||
mailbox.domain,
|
||||
mailbox.username,
|
||||
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.force_pw_update')) = '0',
|
||||
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.sogo_access')) = 1, password, '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
|
||||
'{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
|
||||
mailbox.name,
|
||||
mailbox.username,
|
||||
IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''),
|
||||
IFNULL(gda.ad_alias, ''),
|
||||
IFNULL(external_acl.send_as_acl, ''),
|
||||
mailbox.kind,
|
||||
mailbox.multiple_bookings
|
||||
FROM
|
||||
mailbox
|
||||
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
|
||||
LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
|
||||
LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
|
||||
WHERE
|
||||
mailbox.active = '1'";
|
||||
|
||||
if ($mailbox_exists) {
|
||||
$query .= " AND mailbox.username = :mailbox";
|
||||
$stmt = $pdo->prepare($query);
|
||||
$stmt->execute(array(':mailbox' => $mailbox));
|
||||
} else {
|
||||
$query .= " GROUP BY mailbox.username";
|
||||
$stmt = $pdo->query($query);
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
|
||||
|
||||
flush_memcached();
|
||||
}
|
||||
function edit_user_account($_data) {
|
||||
@@ -1739,7 +1777,7 @@ function verify_tfa_login($username, $_data) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $username, '*'),
|
||||
'msg' => array('webauthn_verification_failed', 'authenticator not found')
|
||||
'msg' => array('webauthn_authenticator_failed')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -1748,7 +1786,16 @@ function verify_tfa_login($username, $_data) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $username, '*'),
|
||||
'msg' => array('webauthn_verification_failed', 'publicKey not found')
|
||||
'msg' => array('webauthn_publickey_failed')
|
||||
);
|
||||
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;
|
||||
}
|
||||
@@ -1784,21 +1831,12 @@ function verify_tfa_login($username, $_data) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $username, '*'),
|
||||
'msg' => array('webauthn_verification_failed', 'could not determine user role')
|
||||
'msg' => array('webauthn_role_failed')
|
||||
);
|
||||
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['tfa_id'] = $process_webauthn['id'];
|
||||
$_SESSION['authReq'] = null;
|
||||
|
@@ -1264,11 +1264,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
));
|
||||
}
|
||||
|
||||
update_sogo_static_view($username);
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('mailbox_added', htmlspecialchars($username))
|
||||
);
|
||||
return true;
|
||||
break;
|
||||
case 'resource':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
@@ -3130,7 +3132,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('mailbox_modified', $username)
|
||||
);
|
||||
|
||||
update_sogo_static_view($username);
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
case 'mailbox_templates':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
@@ -5053,12 +5058,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
update_sogo_static_view($username);
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('mailbox_removed', htmlspecialchars($username))
|
||||
);
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
case 'mailbox_templates':
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
@@ -5264,7 +5272,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
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', 'resource')) && getenv('SKIP_SOGO') != "y") {
|
||||
update_sogo_static_view();
|
||||
}
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ $globalVariables = [
|
||||
'ui_texts' => $UI_TEXTS,
|
||||
'css_path' => '/cache/'.basename($CSSPath),
|
||||
'logo' => customize('get', 'main_logo'),
|
||||
'favicon' => customize('get', 'favicon'),
|
||||
'available_languages' => $AVAILABLE_LANGUAGES,
|
||||
'lang' => $lang,
|
||||
'skip_sogo' => (getenv('SKIP_SOGO') == 'y'),
|
||||
|
@@ -3,7 +3,7 @@ function init_db_schema() {
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "23122022_1445";
|
||||
$db_version = "14022023_1000";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$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"
|
||||
),
|
||||
"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(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
@@ -1083,7 +1096,7 @@ function init_db_schema() {
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
if ($num_results != 0) {
|
||||
$stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints`
|
||||
$stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE `', `table_schema`, '`.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints`
|
||||
WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;");
|
||||
$stmt->execute(array(':table' => $table));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
@@ -1,4 +1,15 @@
|
||||
<?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 (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
|
||||
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
||||
@@ -114,6 +125,14 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
|
||||
if (isset($_POST["reset_main_logo"])) {
|
||||
customize('delete', 'main_logo');
|
||||
}
|
||||
if (isset($_POST["submit_favicon"])) {
|
||||
if ($_FILES['favicon']['error'] == 0) {
|
||||
customize('add', 'favicon', $_FILES);
|
||||
}
|
||||
}
|
||||
if (isset($_POST["reset_favicon"])) {
|
||||
customize('delete', 'favicon');
|
||||
}
|
||||
// Some actions will not be available via API
|
||||
if (isset($_POST["license_validate_now"])) {
|
||||
license('verify');
|
||||
|
@@ -124,7 +124,7 @@ $MAILCOW_APPS = array(
|
||||
);
|
||||
|
||||
// Rows until pagination begins
|
||||
$PAGINATION_SIZE = 20;
|
||||
$PAGINATION_SIZE = 25;
|
||||
|
||||
// Default number of rows/lines to display (log table)
|
||||
$LOG_LINES = 1000;
|
||||
@@ -210,6 +210,30 @@ $FIDO2_USER_PRESENT_FLAG = true;
|
||||
|
||||
$FIDO2_FORMATS = array('apple', 'android-key', 'android-safetynet', 'fido-u2f', 'none', 'packed', 'tpm');
|
||||
|
||||
$SOGO_PALETTES = array(
|
||||
'sogo-green',
|
||||
'sogo-blue',
|
||||
'sogo-grey',
|
||||
'red',
|
||||
'pink',
|
||||
'purple',
|
||||
'deep-purple',
|
||||
'indigo',
|
||||
'blue',
|
||||
'light-blue',
|
||||
'cyan',
|
||||
'teal',
|
||||
'green',
|
||||
'light-green',
|
||||
'lime',
|
||||
'yellow',
|
||||
'amber',
|
||||
'orange',
|
||||
'deep-orange',
|
||||
'brown',
|
||||
'grey',
|
||||
'blue-grey'
|
||||
);
|
||||
|
||||
// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
|
||||
$RSPAMD_MAPS = array(
|
||||
|
@@ -74,6 +74,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -116,8 +117,8 @@ jQuery(function($){
|
||||
data: 'tfa_active',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
if(data == 1) return '<i class="bi bi-check-lg"></i>';
|
||||
else return '<i class="bi bi-x-lg"></i>'
|
||||
if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
|
||||
else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -125,14 +126,14 @@ jQuery(function($){
|
||||
data: 'active',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
if(data == 1) return '<i class="bi bi-check-lg"></i>';
|
||||
else return '<i class="bi bi-x-lg"></i>'
|
||||
if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
|
||||
else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
},
|
||||
],
|
||||
@@ -152,6 +153,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -202,7 +204,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
},
|
||||
]
|
||||
@@ -220,6 +222,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -257,8 +260,8 @@ jQuery(function($){
|
||||
data: 'tfa_active',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
if(data == 1) return '<i class="bi bi-check-lg"></i>';
|
||||
else return '<i class="bi bi-x-lg"></i>'
|
||||
if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
|
||||
else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -266,15 +269,15 @@ jQuery(function($){
|
||||
data: 'active',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
if(data == 1) return '<i class="bi bi-check-lg"></i>';
|
||||
else return '<i class="bi bi-x-lg"></i>'
|
||||
if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
|
||||
else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
defaultContent: '',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right'
|
||||
className: 'dt-sm-head-hidden dt-text-right'
|
||||
},
|
||||
]
|
||||
});
|
||||
@@ -291,6 +294,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -333,13 +337,13 @@ jQuery(function($){
|
||||
data: 'keep_spam',
|
||||
defaultContent: '',
|
||||
render: function(data, type){
|
||||
return 'yes'==data?'<i class="bi bi-x-lg"></i>':'no'==data&&'<i class="bi bi-check-lg"></i>';
|
||||
return 'yes'==data?'<i class="bi bi-x-lg"><span class="sorting-value">yes</span></i>':'no'==data&&'<i class="bi bi-check-lg"><span class="sorting-value">no</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
},
|
||||
]
|
||||
@@ -357,6 +361,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -409,14 +414,14 @@ jQuery(function($){
|
||||
data: 'active',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
if(data == 1) return '<i class="bi bi-check-lg"></i>';
|
||||
else return '<i class="bi bi-x-lg"></i>'
|
||||
if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
|
||||
else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
},
|
||||
]
|
||||
@@ -434,6 +439,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -486,14 +492,14 @@ jQuery(function($){
|
||||
data: 'active',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
if(data == 1) return '<i class="bi bi-check-lg"></i>';
|
||||
else return '<i class="bi bi-x-lg"></i>'
|
||||
if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
|
||||
else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
},
|
||||
]
|
||||
|
@@ -34,7 +34,7 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
// set update loop container list
|
||||
containersToUpdate = {}
|
||||
containersToUpdate = {};
|
||||
// set default ChartJs Font Color
|
||||
Chart.defaults.color = '#999';
|
||||
// create host cpu and mem charts
|
||||
@@ -44,8 +44,7 @@ $(document).ready(function() {
|
||||
check_update(mailcow_info.version_tag, mailcow_info.project_url);
|
||||
}
|
||||
$("#maiclow_version").click(function(){
|
||||
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" ||
|
||||
mailcow_info.branch !== "master")
|
||||
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master")
|
||||
return;
|
||||
|
||||
showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
|
||||
@@ -123,6 +122,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -192,6 +192,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -246,6 +247,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -304,6 +306,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -369,6 +372,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -472,6 +476,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -555,6 +560,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -615,6 +621,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -664,6 +671,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -718,6 +726,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -772,6 +781,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -887,6 +897,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -998,31 +1009,31 @@ jQuery(function($){
|
||||
item.rcpt = escapeHtml(item.rcpt_smtp.join(", "));
|
||||
}
|
||||
item.symbols = Object.keys(item.symbols).sort(function (a, b) {
|
||||
if (item.symbols[a].score === 0) return 1
|
||||
if (item.symbols[b].score === 0) return -1
|
||||
if (item.symbols[a].score === 0) return 1;
|
||||
if (item.symbols[b].score === 0) return -1;
|
||||
if (item.symbols[b].score < 0 && item.symbols[a].score < 0) {
|
||||
return item.symbols[a].score - item.symbols[b].score
|
||||
return item.symbols[a].score - item.symbols[b].score;
|
||||
}
|
||||
if (item.symbols[b].score > 0 && item.symbols[a].score > 0) {
|
||||
return item.symbols[b].score - item.symbols[a].score
|
||||
return item.symbols[b].score - item.symbols[a].score;
|
||||
}
|
||||
return item.symbols[b].score - item.symbols[a].score
|
||||
return item.symbols[b].score - item.symbols[a].score;
|
||||
}).map(function(key) {
|
||||
var sym = item.symbols[key];
|
||||
if (sym.score < 0) {
|
||||
sym.score_formatted = '(<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) {
|
||||
sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)'
|
||||
sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)';
|
||||
}
|
||||
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;
|
||||
if (sym.options) {
|
||||
str += ' [' + escapeHtml(sym.options.join(", ")) + "]";
|
||||
}
|
||||
return str
|
||||
return str;
|
||||
}).join('<br>\n');
|
||||
item.subject = escapeHtml(item.subject);
|
||||
var scan_time = item.time_real.toFixed(3);
|
||||
@@ -1155,14 +1166,14 @@ jQuery(function($){
|
||||
}
|
||||
});
|
||||
}
|
||||
return data
|
||||
return data;
|
||||
};
|
||||
$('.add_log_lines').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var log_table= $(this).data("table")
|
||||
var new_nrows = $(this).data("nrows")
|
||||
var post_process = $(this).data("post-process")
|
||||
var log_url = $(this).data("log-url")
|
||||
var log_table= $(this).data("table");
|
||||
var new_nrows = $(this).data("nrows");
|
||||
var post_process = $(this).data("post-process");
|
||||
var log_url = $(this).data("log-url");
|
||||
if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
|
||||
console.log("no data-table or data-nrows or log_url or data-post-process attr found");
|
||||
return;
|
||||
@@ -1170,7 +1181,7 @@ jQuery(function($){
|
||||
|
||||
if (table = $('#' + log_table).DataTable()) {
|
||||
var heading = $('#' + log_table).closest('.card').find('.card-header');
|
||||
var load_rows = (table.page.len() + 1) + '-' + (table.page.len() + new_nrows)
|
||||
var load_rows = (table.data().length + 1) + '-' + (table.data().length + new_nrows)
|
||||
|
||||
$.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){
|
||||
if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }
|
||||
@@ -1220,7 +1231,6 @@ jQuery(function($){
|
||||
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
|
||||
|
||||
|
||||
|
||||
// start polling host stats if tab is active
|
||||
onVisible("[id^=tab-containers]", () => update_stats());
|
||||
// start polling container stats if collapse is active
|
||||
|
@@ -82,6 +82,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -142,6 +143,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
|
@@ -437,6 +437,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -606,13 +607,13 @@ jQuery(function($){
|
||||
defaultContent: '',
|
||||
responsivePriority: 6,
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'—');
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'—');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -635,11 +636,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#tab-templates-domains', '#templates_domain_table');
|
||||
},
|
||||
@@ -672,7 +674,7 @@ jQuery(function($){
|
||||
'<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
else{
|
||||
else {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-template" data-api-url="delete/domain/template" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
@@ -752,7 +754,7 @@ jQuery(function($){
|
||||
data: 'attributes.gal',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -760,7 +762,7 @@ jQuery(function($){
|
||||
data: 'attributes.backupmx',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -768,7 +770,7 @@ jQuery(function($){
|
||||
data: 'attributes.relay_all_recipients',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -776,7 +778,7 @@ jQuery(function($){
|
||||
data: 'attributes.relay_unknown_only',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -785,7 +787,7 @@ jQuery(function($){
|
||||
defaultContent: '',
|
||||
responsivePriority: 4,
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -821,7 +823,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 6,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -844,6 +846,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -923,9 +926,12 @@ jQuery(function($){
|
||||
'<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
item.in_use = '<div class="progress">' +
|
||||
item.in_use = {
|
||||
sortBy: item.percent_in_use,
|
||||
value: '<div class="progress">' +
|
||||
'<div class="progress-bar-mailbox progress-bar progress-bar-' + item.percent_class + '" role="progressbar" aria-valuenow="' + item.percent_in_use + '" aria-valuemin="0" aria-valuemax="100" ' +
|
||||
'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>';
|
||||
'style="min-width:2em;width:' + item.percent_in_use + '%">' + item.percent_in_use + '%' + '</div></div>'
|
||||
};
|
||||
item.username = escapeHtml(item.username);
|
||||
|
||||
if (Array.isArray(item.tags)){
|
||||
@@ -991,10 +997,11 @@ jQuery(function($){
|
||||
},
|
||||
{
|
||||
title: lang.in_use,
|
||||
data: 'in_use',
|
||||
data: 'in_use.value',
|
||||
defaultContent: '',
|
||||
responsivePriority: 9,
|
||||
className: 'dt-data-w100'
|
||||
className: 'dt-data-w100',
|
||||
orderData: 24
|
||||
},
|
||||
{
|
||||
title: lang.fname,
|
||||
@@ -1086,20 +1093,25 @@ jQuery(function($){
|
||||
defaultContent: '',
|
||||
responsivePriority: 4,
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'—');
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'—');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 6,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
data: 'quota.sortBy',
|
||||
responsivePriority: 8,
|
||||
defaultContent: '',
|
||||
className: "d-none"
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
data: 'in_use.sortBy',
|
||||
defaultContent: '',
|
||||
className: "d-none"
|
||||
},
|
||||
@@ -1122,11 +1134,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#tab-templates-mbox', '#templates_mbox_table');
|
||||
},
|
||||
@@ -1151,13 +1164,13 @@ jQuery(function($){
|
||||
|
||||
item.attributes.quota = humanFileSize(item.attributes.quota);
|
||||
|
||||
item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
|
||||
item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
|
||||
item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.attributes.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.attributes.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.imap_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.smtp_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sieve_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
|
||||
if (item.attributes.quarantine_notification === 'never') {
|
||||
item.attributes.quarantine_notification = lang.never;
|
||||
} else if (item.attributes.quarantine_notification === 'hourly') {
|
||||
@@ -1175,7 +1188,6 @@ jQuery(function($){
|
||||
item.attributes.quarantine_category = lang.q_all;
|
||||
}
|
||||
|
||||
|
||||
if (item.template.toLowerCase() == "default"){
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
@@ -1316,13 +1328,13 @@ jQuery(function($){
|
||||
defaultContent: '',
|
||||
responsivePriority: 4,
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'—');
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'—');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 6,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -1345,6 +1357,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -1426,7 +1439,7 @@ jQuery(function($){
|
||||
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>':2==data&&'—');
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'—');
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1434,7 +1447,7 @@ jQuery(function($){
|
||||
data: 'action',
|
||||
responsivePriority: 5,
|
||||
defaultContent: '',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right'
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right'
|
||||
},
|
||||
]
|
||||
});
|
||||
@@ -1448,14 +1461,14 @@ jQuery(function($){
|
||||
// Domains
|
||||
var optgroup = "<optgroup label='" + lang.domains + "'>";
|
||||
$.each(data.domains, function(index, domain){
|
||||
optgroup += "<option value='" + domain + "'>" + domain + "</option>"
|
||||
optgroup += "<option value='" + domain + "'>" + domain + "</option>";
|
||||
});
|
||||
optgroup += "</optgroup>"
|
||||
optgroup += "</optgroup>";
|
||||
$('#bcc-local-dest').append(optgroup);
|
||||
// Alias domains
|
||||
var optgroup = "<optgroup label='" + lang.domain_aliases + "'>";
|
||||
$.each(data.alias_domains, function(index, alias_domain){
|
||||
optgroup += "<option value='" + alias_domain + "'>" + alias_domain + "</option>"
|
||||
optgroup += "<option value='" + alias_domain + "'>" + alias_domain + "</option>";
|
||||
});
|
||||
optgroup += "</optgroup>"
|
||||
$('#bcc-local-dest').append(optgroup);
|
||||
@@ -1463,9 +1476,9 @@ jQuery(function($){
|
||||
$.each(data.mailboxes, function(mailbox, aliases){
|
||||
var optgroup = "<optgroup label='" + mailbox + "'>";
|
||||
$.each(aliases, function(index, alias){
|
||||
optgroup += "<option value='" + alias + "'>" + alias + "</option>"
|
||||
optgroup += "<option value='" + alias + "'>" + alias + "</option>";
|
||||
});
|
||||
optgroup += "</optgroup>"
|
||||
optgroup += "</optgroup>";
|
||||
$('#bcc-local-dest').append(optgroup);
|
||||
});
|
||||
// Finish
|
||||
@@ -1483,11 +1496,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#collapse-tab-bcc', '#bcc_table');
|
||||
},
|
||||
@@ -1563,13 +1577,13 @@ jQuery(function($){
|
||||
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>':2==data&&'—');
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'—');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -1592,11 +1606,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#collapse-tab-bcc-filters', '#recipient_map_table');
|
||||
},
|
||||
@@ -1659,13 +1674,13 @@ jQuery(function($){
|
||||
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>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -1688,11 +1703,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#tab-tls-policy', '#tls_policy_table');
|
||||
},
|
||||
@@ -1765,13 +1781,13 @@ jQuery(function($){
|
||||
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>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -1794,11 +1810,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table');
|
||||
},
|
||||
@@ -1899,7 +1916,7 @@ jQuery(function($){
|
||||
data: 'sogo_visible',
|
||||
defaultContent: '',
|
||||
render: function(data, type){
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1918,13 +1935,13 @@ jQuery(function($){
|
||||
defaultContent: '',
|
||||
responsivePriority: 6,
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -1934,6 +1951,10 @@ jQuery(function($){
|
||||
table.on('responsive-resize', function (e, datatable, columns){
|
||||
hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table');
|
||||
});
|
||||
|
||||
table.on( 'draw', function (){
|
||||
$('#alias_table [data-bs-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
}
|
||||
function draw_aliasdomain_table() {
|
||||
// just recalc width if instance already exists
|
||||
@@ -1947,6 +1968,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -2012,13 +2034,13 @@ jQuery(function($){
|
||||
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>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -2041,11 +2063,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#tab-syncjobs', '#sync_job_table');
|
||||
},
|
||||
@@ -2147,7 +2170,7 @@ jQuery(function($){
|
||||
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>';
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2170,7 +2193,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
@@ -2194,11 +2217,12 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order:[[2, 'desc']],
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
hideTableExpandCollapseBtn('#tab-filters', '#filter_table');
|
||||
},
|
||||
@@ -2280,7 +2304,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
},
|
||||
|
@@ -25,24 +25,24 @@ jQuery(function($){
|
||||
}
|
||||
if (typeof data.symbols !== 'undefined') {
|
||||
data.symbols.sort(function (a, b) {
|
||||
if (a.score === 0) return 1
|
||||
if (b.score === 0) return -1
|
||||
if (a.score === 0) return 1;
|
||||
if (b.score === 0) return -1;
|
||||
if (b.score < 0 && a.score < 0) {
|
||||
return a.score - b.score
|
||||
return a.score - b.score;
|
||||
}
|
||||
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) {
|
||||
var highlightClass = ''
|
||||
if (value.score > 0) highlightClass = 'negative'
|
||||
else if (value.score < 0) highlightClass = 'positive'
|
||||
else highlightClass = 'neutral'
|
||||
var highlightClass = '';
|
||||
if (value.score > 0) highlightClass = 'negative';
|
||||
else if (value.score < 0) highlightClass = 'positive';
|
||||
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>');
|
||||
});
|
||||
$('[data-bs-toggle="tooltip"]').tooltip()
|
||||
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||
}
|
||||
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
|
||||
if (data.action === "add header") {
|
||||
|
@@ -18,6 +18,17 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
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>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -101,6 +112,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.sender,
|
||||
data: 'sender',
|
||||
className: 'senders-mw220',
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
@@ -151,7 +163,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-text-right dt-sm-head-hidden',
|
||||
defaultContent: ''
|
||||
},
|
||||
]
|
||||
@@ -193,24 +205,24 @@ jQuery(function($){
|
||||
$('#qid_detail_fuzzy').html('');
|
||||
if (typeof data.symbols !== 'undefined') {
|
||||
data.symbols.sort(function (a, b) {
|
||||
if (a.score === 0) return 1
|
||||
if (b.score === 0) return -1
|
||||
if (a.score === 0) return 1;
|
||||
if (b.score === 0) return -1;
|
||||
if (b.score < 0 && a.score < 0) {
|
||||
return a.score - b.score
|
||||
return a.score - b.score;
|
||||
}
|
||||
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) {
|
||||
var highlightClass = ''
|
||||
if (value.score > 0) highlightClass = 'negative'
|
||||
else if (value.score < 0) highlightClass = 'positive'
|
||||
else highlightClass = 'neutral'
|
||||
var highlightClass = '';
|
||||
if (value.score > 0) highlightClass = 'negative';
|
||||
else if (value.score < 0) highlightClass = 'positive';
|
||||
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>');
|
||||
});
|
||||
$('[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) {
|
||||
$.each(data.fuzzy_hashes, function (index, value) {
|
||||
@@ -276,7 +288,6 @@ jQuery(function($){
|
||||
// Initial table drawings
|
||||
draw_quarantine_table();
|
||||
|
||||
|
||||
function hideTableExpandCollapseBtn(table){
|
||||
if ($(table).hasClass('collapsed'))
|
||||
$(".table_collapse_option").show();
|
||||
|
@@ -21,7 +21,6 @@ jQuery(function($){
|
||||
url: '/api/v1/get/postcat/' + button.data('queue-id'),
|
||||
dataType: 'text',
|
||||
complete: function (data) {
|
||||
console.log(data);
|
||||
$('#queue_msg_content').text(data.responseText);
|
||||
}
|
||||
});
|
||||
@@ -39,6 +38,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -54,7 +54,7 @@ jQuery(function($){
|
||||
});
|
||||
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.queue_show_message + '</a>' +
|
||||
'<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>';
|
||||
});
|
||||
return data;
|
||||
@@ -116,7 +116,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang_admin.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
},
|
||||
]
|
||||
|
@@ -139,6 +139,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -207,7 +208,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
}
|
||||
]
|
||||
@@ -225,6 +226,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -361,7 +363,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: '',
|
||||
responsivePriority: 5
|
||||
}
|
||||
@@ -380,6 +382,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -457,7 +460,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
||||
className: 'dt-sm-head-hidden dt-text-right',
|
||||
defaultContent: ''
|
||||
}
|
||||
]
|
||||
@@ -475,6 +478,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
@@ -545,6 +549,7 @@ jQuery(function($){
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
|
@@ -288,6 +288,18 @@ if (isset($_GET['query'])) {
|
||||
case "domain-admin":
|
||||
process_add_return(domain_admin('add', $attr));
|
||||
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":
|
||||
process_add_return(admin('add', $attr));
|
||||
break;
|
||||
@@ -1732,6 +1744,9 @@ if (isset($_GET['query'])) {
|
||||
case "rlhash":
|
||||
echo ratelimit('delete', null, implode($items));
|
||||
break;
|
||||
case "sogo_theme":
|
||||
process_delete_return(customize('delete', 'sogo_theme', $items));
|
||||
break;
|
||||
// return no route found if no case is matched
|
||||
default:
|
||||
http_response_code(404);
|
||||
@@ -1926,6 +1941,9 @@ if (isset($_GET['query'])) {
|
||||
case "ip_check":
|
||||
process_edit_return(customize('edit', 'ip_check', $attr));
|
||||
break;
|
||||
case "sogo_theme":
|
||||
process_edit_return(customize('edit', 'sogo_theme', $attr));
|
||||
break;
|
||||
case "self":
|
||||
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||
process_edit_return(domain_admin('edit', $attr));
|
||||
|
@@ -105,7 +105,8 @@
|
||||
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
|
||||
"username": "Uživatelské jméno",
|
||||
"validate": "Ověřit",
|
||||
"validation_success": "Úspěšně ověřeno"
|
||||
"validation_success": "Úspěšně ověřeno",
|
||||
"tags": "Štítky"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Přístupy",
|
||||
@@ -333,7 +334,11 @@
|
||||
"username": "Uživatelské jméno",
|
||||
"validate_license_now": "Ověřit GUID na licenčním serveru",
|
||||
"verify": "Ověřit",
|
||||
"yes": "✓"
|
||||
"yes": "✓",
|
||||
"f2b_ban_time_increment": "Délka banu je prodlužována s každým dalším banem",
|
||||
"f2b_max_ban_time": "Maximální délka banu (s)",
|
||||
"ip_check": "Kontrola IP",
|
||||
"ip_check_disabled": "Kontrola IP je vypnuta. Můžete ji zapnout v <br> <strong>System > Nastavení > Options > Přizpůsobení</strong>"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
|
||||
@@ -650,7 +655,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Přihlášení zpožděno o %s sekund.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Přihlásit",
|
||||
"mobileconfig_info": "Ke stažení profilového souboru se přihlaste jako uživatel schránky.",
|
||||
"other_logins": "Přihlášení klíčem",
|
||||
|
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"acl": {
|
||||
"alias_domains": "Tilføj kældenavn domæner",
|
||||
"alias_domains": "Tilføj domænealias",
|
||||
"app_passwds": "Administrer app-adgangskoder",
|
||||
"bcc_maps": "BCC kort",
|
||||
"delimiter_action": "Afgrænsning handling",
|
||||
"eas_reset": "Nulstil EAS endheder",
|
||||
"eas_reset": "Nulstil EAS enheder",
|
||||
"extend_sender_acl": "Tillad at udvide afsenderens ACL med eksterne adresser",
|
||||
"filters": "Filtre",
|
||||
"login_as": "Login som mailboks bruger",
|
||||
"prohibited": "Forbudt af ACL",
|
||||
"protocol_access": "Ændre protokol adgang",
|
||||
"prohibited": "Nægtet af ACL",
|
||||
"protocol_access": "Skift protokol adgang",
|
||||
"pushover": "Pushover",
|
||||
"quarantine": "Karantæneaktioner",
|
||||
"quarantine_attachments": "Karantæne vedhæftede filer",
|
||||
"quarantine": "Karantænehandlinger",
|
||||
"quarantine_attachments": "Karantænevedhæftede filer",
|
||||
"quarantine_notification": "Skift karantænemeddelelser",
|
||||
"ratelimit": "Satsgrænse",
|
||||
"recipient_maps": "Modtagerkort",
|
||||
@@ -20,12 +20,15 @@
|
||||
"sogo_access": "Tillad styring af SOGo-adgang",
|
||||
"sogo_profile_reset": "Nulstil SOGo-profil",
|
||||
"spam_alias": "Midlertidige aliasser",
|
||||
"spam_policy": "Sortliste / hvidliste",
|
||||
"spam_policy": "Sortliste/hvidliste",
|
||||
"spam_score": "Spam-score",
|
||||
"syncjobs": "Synkroniser job",
|
||||
"syncjobs": "Synkroniserings job",
|
||||
"tls_policy": "TLS politik",
|
||||
"unlimited_quota": "Ubegrænset quote for mailbokse",
|
||||
"domain_desc": "Skift domæne beskrivelse"
|
||||
"unlimited_quota": "Ubegrænset plads for mailbokse",
|
||||
"domain_desc": "Skift domæne beskrivelse",
|
||||
"domain_relayhost": "Skift relæ host for et domæne",
|
||||
"mailbox_relayhost": "Skift relæ-host for en postkasse",
|
||||
"quarantine_category": "Skift kategorien for karantænemeddelelse"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "Alle andre filtre deaktiveres, når aktiv er markeret.",
|
||||
@@ -33,7 +36,7 @@
|
||||
"add": "Tilføj",
|
||||
"add_domain_only": "Tilføj kun domæne",
|
||||
"add_domain_restart": "Tilføj domæne og genstart SOGo",
|
||||
"alias_address": "Alias adresse (r)",
|
||||
"alias_address": "Alias adresse(r)",
|
||||
"alias_address_info": "<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_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>",
|
||||
@@ -59,7 +62,7 @@
|
||||
"gal": "Global adresseliste",
|
||||
"gal_info": "GAL indeholder alle objekter i et domæne og kan ikke redigeres af nogen bruger. Information om ledig / optaget i SOGo mangler, hvis deaktiveret! <b> Genstart SOGo for at anvende ændringer. </b>",
|
||||
"generate": "generere",
|
||||
"goto_ham": "Lær som <span class=\"text-success\"><b>ham</b></span>",
|
||||
"goto_ham": "Lær som <span class=\"text-success\"><b>ønsket</b></span>",
|
||||
"goto_null": "Kassér e-mail i stilhed",
|
||||
"goto_spam": "Lær som <span class=\"text-danger\"><b>spam</b></span>",
|
||||
"hostname": "Vært",
|
||||
@@ -80,7 +83,7 @@
|
||||
"private_comment": "Privat kommentar",
|
||||
"public_comment": "Offentlig kommentar",
|
||||
"quota_mb": "Kvota (Mb)",
|
||||
"relay_all": "Send alle modtagere videre",
|
||||
"relay_all": "Besvar alle modtager",
|
||||
"relay_all_info": "↪ Hvis du vælger <b> ikke </b> at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.",
|
||||
"relay_domain": "Send dette domæne videre",
|
||||
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.",
|
||||
@@ -101,7 +104,10 @@
|
||||
"timeout2": "Timeout for forbindelse til lokal vært",
|
||||
"username": "Brugernavn",
|
||||
"validate": "Bekræft",
|
||||
"validation_success": "Valideret med succes"
|
||||
"validation_success": "Valideret med succes",
|
||||
"bcc_dest_format": "BCC-destination skal være en enkelt gyldig e-mail-adresse.<br>Hvis du har brug for at sende en kopi til flere adresser, kan du oprette et alias og bruge det her.",
|
||||
"app_passwd_protocols": "Tilladte protokoller for app adgangskode",
|
||||
"tags": "Tag's"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Adgang",
|
||||
@@ -308,7 +314,10 @@
|
||||
"username": "Brugernavn",
|
||||
"validate_license_now": "Valider GUID mod licensserver",
|
||||
"verify": "Verificere",
|
||||
"yes": "✓"
|
||||
"yes": "✓",
|
||||
"ip_check_opt_in": "Opt-In for brug af tredjepartstjeneste <strong>ipv4.mailcow.email</strong> og <strong>ipv6.mailcow.email</strong> til at finde eksterne IP-adresser.",
|
||||
"queue_unban": "unban",
|
||||
"admins": "Administratorer"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Adgang nægtet eller ugyldig formular data",
|
||||
@@ -425,7 +434,8 @@
|
||||
"username_invalid": "Brugernavn %s kan ikke bruges",
|
||||
"validity_missing": "Tildel venligst en gyldighedsperiode",
|
||||
"value_missing": "Angiv alle værdier",
|
||||
"yotp_verification_failed": "Yubico OTP verifikationen mislykkedes: %s"
|
||||
"yotp_verification_failed": "Yubico OTP verifikationen mislykkedes: %s",
|
||||
"webauthn_publickey_failed": "Der er ikke gemt nogen offentlig nøgle for den valgte autentifikator"
|
||||
},
|
||||
"debug": {
|
||||
"chart_this_server": "Diagram (denne server)",
|
||||
@@ -442,7 +452,8 @@
|
||||
"solr_status": "Solr-status",
|
||||
"started_on": "Startede den",
|
||||
"static_logs": "Statiske logfiler",
|
||||
"system_containers": "System og Beholdere"
|
||||
"system_containers": "System og Beholdere",
|
||||
"error_show_ip": "Kunne ikke finde de offentlige IP-adresser"
|
||||
},
|
||||
"diagnostics": {
|
||||
"cname_from_a": "Værdi afledt af A / AAAA-post. Dette understøttes, så længe posten peger på den korrekte ressource.",
|
||||
@@ -553,7 +564,11 @@
|
||||
"title": "Rediger objekt",
|
||||
"unchanged_if_empty": "Lad være tomt, hvis uændret",
|
||||
"username": "Brugernavn",
|
||||
"validate_save": "Valider og gem"
|
||||
"validate_save": "Valider og gem",
|
||||
"admin": "Rediger administrator",
|
||||
"lookup_mx": "Destination er et regulært udtryk, der matcher MX-navnet (<code>.*google\\.dk</code> for at dirigere al e-mail, der er målrettet til en MX, der ender på google.dk, over dette hop)",
|
||||
"mailbox_relayhost_info": "Anvendt på postkassen og kun direkte aliasser, og overskriver et domæne relæ-host.",
|
||||
"quota_warning_bcc": "Kvoteadvarsel BCC"
|
||||
},
|
||||
"footer": {
|
||||
"cancel": "Afbestille",
|
||||
@@ -571,7 +586,7 @@
|
||||
"header": {
|
||||
"administration": "Konfiguration og detailer",
|
||||
"apps": "Apps",
|
||||
"debug": "Systemoplysninger",
|
||||
"debug": "Information",
|
||||
"email": "E-Mail",
|
||||
"mailcow_config": "Konfiguration",
|
||||
"quarantine": "Karantæne",
|
||||
@@ -586,7 +601,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Login blev forsinket med% s sekunder.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Login",
|
||||
"mobileconfig_info": "Log ind som postkassebruger for at downloade den anmodede Apple-forbindelsesprofil.",
|
||||
"other_logins": "Nøgle login",
|
||||
@@ -739,7 +754,10 @@
|
||||
"username": "Brugernavn",
|
||||
"waiting": "Venter",
|
||||
"weekly": "Ugentlig",
|
||||
"yes": "✓"
|
||||
"yes": "✓",
|
||||
"goto_ham": "Lær som <b>ønsket</b>",
|
||||
"catch_all": "Fang-alt",
|
||||
"open_logs": "Åben logfiler"
|
||||
},
|
||||
"oauth2": {
|
||||
"access_denied": "Log ind som mailboks ejer for at give adgang via OAuth2.",
|
||||
@@ -1030,7 +1048,7 @@
|
||||
"spamfilter_table_empty": "Intet data at vise",
|
||||
"spamfilter_table_remove": "slet",
|
||||
"spamfilter_table_rule": "Regl",
|
||||
"spamfilter_wl": "Hvisliste",
|
||||
"spamfilter_wl": "Hvidliste",
|
||||
"spamfilter_wl_desc": "Hvidlistede e-mail-adresser til <b>aldrig</b> at klassificeres som spam. Wildcards kan bruges. Et filter anvendes kun på direkte aliaser (aliaser med en enkelt målpostkasse) eksklusive catch-aliaser og selve en postkasse.",
|
||||
"spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe",
|
||||
"status": "Status",
|
||||
@@ -1066,5 +1084,11 @@
|
||||
"quota_exceeded_scope": "Domænekvote overskredet: Kun ubegrænsede postkasser kan oprettes i dette domæneomfang.",
|
||||
"session_token": "Form nøgle ugyldig: Nøgle passer ikke",
|
||||
"session_ua": "Form nøgle ugyldig: Bruger-Agent gyldighedskontrols fejl"
|
||||
},
|
||||
"datatables": {
|
||||
"lengthMenu": "Vis _MENU_ poster",
|
||||
"paginate": {
|
||||
"first": "Først"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -145,6 +145,7 @@
|
||||
"ays": "Soll der Vorgang wirklich ausgeführt werden?",
|
||||
"ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Blacklist-Einträge.",
|
||||
"change_logo": "Logo ändern",
|
||||
"change_favicon": "Favicon ändern",
|
||||
"configuration": "Konfiguration",
|
||||
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
|
||||
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
|
||||
@@ -175,10 +176,12 @@
|
||||
"empty": "Keine Einträge vorhanden",
|
||||
"excludes": "Diese Empfänger ausschließen",
|
||||
"f2b_ban_time": "Bannzeit in Sekunden",
|
||||
"f2b_ban_time_increment": "Bannzeit erhöht sich mit jedem Bann",
|
||||
"f2b_blacklist": "Blacklist für Netzwerke und Hosts",
|
||||
"f2b_filter": "Regex-Filter",
|
||||
"f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
|
||||
"f2b_max_attempts": "Max. Versuche",
|
||||
"f2b_max_ban_time": "Maximale Bannzeit in Sekunden",
|
||||
"f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)",
|
||||
"f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)",
|
||||
"f2b_parameters": "Fail2ban-Parameter",
|
||||
@@ -214,6 +217,7 @@
|
||||
"loading": "Bitte warten...",
|
||||
"login_time": "Zeit",
|
||||
"logo_info": "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.",
|
||||
"favicon_info": "Das Bild muss eine PNG- oder ICO-Datei mit den Abmessungen <code>32 x 32</code>, <code>128 x 128</code>, <code>180 x 180</code>, <code>192 x 192</code> oder <code>256 x 256</code> sein. SOGo muss nachdem ändern neugestartet werden.",
|
||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||
"main_name": "\"mailcow UI\" Name",
|
||||
"merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.",
|
||||
@@ -304,6 +308,8 @@
|
||||
"sender": "Sender",
|
||||
"service": "Dienst",
|
||||
"service_id": "Service",
|
||||
"sogo_theme": "SOGo Theme",
|
||||
"sogo_theme_info": "SOGo muss nachdem ändern neugestartet werden.",
|
||||
"source": "Quelle",
|
||||
"spamfilter": "Spamfilter",
|
||||
"subject": "Betreff",
|
||||
@@ -339,7 +345,8 @@
|
||||
"oauth2_add_client": "Füge OAuth2 Client hinzu",
|
||||
"api_read_only": "Schreibgeschützter Zugriff",
|
||||
"api_read_write": "Lese-Schreib-Zugriff",
|
||||
"oauth2_apps": "OAuth2 Apps"
|
||||
"oauth2_apps": "OAuth2 Apps",
|
||||
"queue_unban": "entsperren"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
|
||||
@@ -366,7 +373,7 @@
|
||||
"domain_not_empty": "Domain %s ist nicht leer",
|
||||
"domain_not_found": "Domain %s nicht gefunden",
|
||||
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
|
||||
"extended_sender_acl_denied": "Keine Rechte zum setzen von externen Absenderadressen",
|
||||
"extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen",
|
||||
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
|
||||
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
|
||||
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
|
||||
@@ -454,17 +461,23 @@
|
||||
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
|
||||
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
|
||||
"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_tfa_method": "Unbekannte TFA-Methode",
|
||||
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
|
||||
"username_invalid": "Benutzername %s kann nicht verwendet werden",
|
||||
"validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an",
|
||||
"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": {
|
||||
"collapse_all": "Alle Einklappen",
|
||||
"decimal": "",
|
||||
"decimal": ",",
|
||||
"emptyTable": "Keine Daten in der Tabelle vorhanden",
|
||||
"expand_all": "Alle Ausklappen",
|
||||
"info": "_START_ bis _END_ von _TOTAL_ Einträgen",
|
||||
@@ -498,7 +511,7 @@
|
||||
"current_time": "Systemzeit",
|
||||
"disk_usage": "Festplattennutzung",
|
||||
"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",
|
||||
"history_all_servers": "History (alle Server)",
|
||||
"in_memory_logs": "In-memory Logs",
|
||||
@@ -651,7 +664,8 @@
|
||||
"title": "Objekt bearbeiten",
|
||||
"unchanged_if_empty": "Unverändert, wenn leer",
|
||||
"username": "Benutzername",
|
||||
"validate_save": "Validieren und speichern"
|
||||
"validate_save": "Validieren und speichern",
|
||||
"pushover_sound": "Ton"
|
||||
},
|
||||
"fido2": {
|
||||
"confirm": "Bestätigen",
|
||||
@@ -692,7 +706,8 @@
|
||||
"quarantine": "Quarantäne",
|
||||
"restart_netfilter": "Netfilter neustarten",
|
||||
"restart_sogo": "SOGo neustarten",
|
||||
"user_settings": "Benutzereinstellungen"
|
||||
"user_settings": "Benutzereinstellungen",
|
||||
"mailcow_system": "System"
|
||||
},
|
||||
"info": {
|
||||
"awaiting_tfa_confirmation": "Warte auf TFA-Verifizierung",
|
||||
@@ -701,7 +716,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Login wurde zur Sicherheit um %s Sekunde/n verzögert.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Anmelden",
|
||||
"mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.",
|
||||
"other_logins": "Key Login",
|
||||
@@ -1029,6 +1044,7 @@
|
||||
"relayhost_added": "Map-Eintrag %s wurde hinzugefügt",
|
||||
"relayhost_removed": "Map-Eintrag %s wurde entfernt",
|
||||
"reset_main_logo": "Standardgrafik wurde wiederhergestellt",
|
||||
"reset_favicon": "Standard favicon wurde wiederhergestellt",
|
||||
"resource_added": "Ressource %s wurde angelegt",
|
||||
"resource_modified": "Änderungen an Ressource %s wurden gespeichert",
|
||||
"resource_removed": "Ressource %s wurde entfernt",
|
||||
@@ -1041,6 +1057,8 @@
|
||||
"template_modified": "Änderungen am Template %s wurden gespeichert",
|
||||
"template_removed": "Template ID %s wurde gelöscht",
|
||||
"sogo_profile_reset": "ActiveSync-Gerät des Benutzers %s wurde zurückgesetzt",
|
||||
"sogo_theme_modified": "SOGo Theme wurde gespeichert",
|
||||
"sogo_theme_removed": "SOGo Theme wurde entfernt",
|
||||
"tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht",
|
||||
"tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert",
|
||||
"ui_texts": "Änderungen an UI-Texten",
|
||||
@@ -1236,7 +1254,8 @@
|
||||
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
|
||||
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
|
||||
"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": {
|
||||
"cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen",
|
||||
|
@@ -147,6 +147,7 @@
|
||||
"ays": "Are you sure you want to proceed?",
|
||||
"ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
|
||||
"change_logo": "Change logo",
|
||||
"change_favicon": "Change favicon",
|
||||
"configuration": "Configuration",
|
||||
"convert_html_to_text": "Convert HTML to plain text",
|
||||
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
|
||||
@@ -177,10 +178,12 @@
|
||||
"empty": "No results",
|
||||
"excludes": "Excludes these recipients",
|
||||
"f2b_ban_time": "Ban time (s)",
|
||||
"f2b_ban_time_increment": "Ban time is incremented with each ban",
|
||||
"f2b_blacklist": "Blacklisted networks/hosts",
|
||||
"f2b_filter": "Regex filters",
|
||||
"f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
|
||||
"f2b_max_attempts": "Max. attempts",
|
||||
"f2b_max_ban_time": "Max. ban time (s)",
|
||||
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
|
||||
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
|
||||
"f2b_parameters": "Fail2ban parameters",
|
||||
@@ -216,6 +219,7 @@
|
||||
"loading": "Please wait...",
|
||||
"login_time": "Login time",
|
||||
"logo_info": "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.",
|
||||
"favicon_info": "The image has to be a PNG or ICO file in the dimensions <code>32 x 32</code>, <code>128 x 128</code>, <code>180 x 180</code>, <code>192 x 192</code>, or <code>256 x 256</code>. Restart SOGo after changing the favicon",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"main_name": "\"mailcow UI\" name",
|
||||
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
|
||||
@@ -309,6 +313,11 @@
|
||||
"sender": "Sender",
|
||||
"service": "Service",
|
||||
"service_id": "Service ID",
|
||||
"sogo_theme": "SOGo Theme",
|
||||
"sogo_theme_info": "Restart SOGo after changing the theme.",
|
||||
"sogo_theme_primary": "Primary Color:",
|
||||
"sogo_theme_accent": "Accent Color:",
|
||||
"sogo_theme_background": "Background Color:",
|
||||
"source": "Source",
|
||||
"spamfilter": "Spam filter",
|
||||
"subject": "Subject",
|
||||
@@ -458,6 +467,9 @@
|
||||
"totp_verification_failed": "TOTP verification failed",
|
||||
"transport_dest_exists": "Transport destination \"%s\" exists",
|
||||
"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_tfa_method": "Unknown TFA method",
|
||||
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
|
||||
@@ -468,7 +480,7 @@
|
||||
},
|
||||
"datatables": {
|
||||
"collapse_all": "Collapse All",
|
||||
"decimal": "",
|
||||
"decimal": ".",
|
||||
"emptyTable": "No data available in table",
|
||||
"expand_all": "Expand All",
|
||||
"info": "Showing _START_ to _END_ of _TOTAL_ entries",
|
||||
@@ -707,7 +719,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Login was delayed by %s seconds.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Login",
|
||||
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
|
||||
"other_logins": "Key login",
|
||||
@@ -1042,6 +1054,7 @@
|
||||
"relayhost_added": "Map entry %s has been added",
|
||||
"relayhost_removed": "Map entry %s has been removed",
|
||||
"reset_main_logo": "Reset to default logo",
|
||||
"reset_favicon": "Reset to default favicon",
|
||||
"resource_added": "Resource %s has been added",
|
||||
"resource_modified": "Changes to mailbox %s have been saved",
|
||||
"resource_removed": "Resource %s has been removed",
|
||||
@@ -1051,6 +1064,8 @@
|
||||
"settings_map_added": "Added settings map entry",
|
||||
"settings_map_removed": "Removed settings map ID %s",
|
||||
"sogo_profile_reset": "SOGo profile for user %s was reset",
|
||||
"sogo_theme_modified": "SOGo Theme has been modified",
|
||||
"sogo_theme_removed": "SOGo Theme has been removed",
|
||||
"template_added": "Added template %s",
|
||||
"template_modified": "Changes to template %s have been saved",
|
||||
"template_removed": "Template ID %s has been deleted",
|
||||
|
@@ -141,9 +141,11 @@
|
||||
"empty": "Sin resultados",
|
||||
"excludes": "Excluye a estos destinatarios",
|
||||
"f2b_ban_time": "Tiempo de restricción (s)",
|
||||
"f2b_ban_time_increment": "Tiempo de restricción se incrementa con cada restricción",
|
||||
"f2b_blacklist": "Redes y hosts en lista negra",
|
||||
"f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
|
||||
"f2b_max_attempts": "Max num. de intentos",
|
||||
"f2b_max_ban_time": "Max tiempo de restricción (s)",
|
||||
"f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)",
|
||||
"f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)",
|
||||
"f2b_parameters": "Parametros Fail2ban",
|
||||
|
@@ -24,7 +24,7 @@
|
||||
"spam_policy": "Liste Noire/Liste Blanche",
|
||||
"spam_score": "Score SPAM",
|
||||
"syncjobs": "Tâches de synchronisation",
|
||||
"tls_policy": "Police TLS",
|
||||
"tls_policy": "Politique TLS",
|
||||
"unlimited_quota": "Quota illimité pour les boites de courriel",
|
||||
"domain_desc": "Modifier la description du domaine",
|
||||
"domain_relayhost": "Changer le relais pour un domaine",
|
||||
@@ -106,7 +106,8 @@
|
||||
"validate": "Valider",
|
||||
"validation_success": "Validation réussie",
|
||||
"bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
|
||||
"tags": "Etiquettes"
|
||||
"tags": "Etiquettes",
|
||||
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Accès",
|
||||
@@ -171,11 +172,13 @@
|
||||
"edit": "Editer",
|
||||
"empty": "Aucun résultat",
|
||||
"excludes": "Exclure ces destinataires",
|
||||
"f2b_ban_time": "Durée du bannissement(s)",
|
||||
"f2b_ban_time": "Durée du bannissement (s)",
|
||||
"f2b_ban_time_increment": "Durée du bannissement est augmentée à chaque bannissement",
|
||||
"f2b_blacklist": "Réseaux/Domaines sur Liste Noire",
|
||||
"f2b_filter": "Filtre(s) Regex",
|
||||
"f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. <b>L'application des mises à jour de liste prendra quelques secondes.</b>",
|
||||
"f2b_max_attempts": "Nb max. de tentatives",
|
||||
"f2b_max_ban_time": "Max. durée du bannissement (s)",
|
||||
"f2b_netban_ipv4": "Taille du sous-réseau IPv4 pour l'application du bannissement (8-32)",
|
||||
"f2b_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)",
|
||||
"f2b_parameters": "Paramètres Fail2ban",
|
||||
@@ -321,7 +324,9 @@
|
||||
"admins": "Administrateurs",
|
||||
"api_read_only": "Accès lecture-seule",
|
||||
"password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules",
|
||||
"password_policy_numbers": "Doit contenir au moins un chiffre"
|
||||
"password_policy_numbers": "Doit contenir au moins un chiffre",
|
||||
"ip_check": "Vérification IP",
|
||||
"ip_check_disabled": "La vérification IP est désactivée. Vous pouvez l'activer sous<br> <strong>Système > Configuration > Options > Personnaliser</strong>"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Accès refusé ou données de formulaire non valides",
|
||||
@@ -440,7 +445,12 @@
|
||||
"username_invalid": "Le nom d'utilisateur %s ne peut pas être utilisé",
|
||||
"validity_missing": "Veuillez attribuer une période de validité",
|
||||
"value_missing": "Veuillez fournir toutes les valeurs",
|
||||
"yotp_verification_failed": "La vérification Yubico OTP a échoué : %s"
|
||||
"yotp_verification_failed": "La vérification Yubico OTP a échoué : %s",
|
||||
"webauthn_authenticator_failed": "L'authentificateur selectionné est introuvable",
|
||||
"demo_mode_enabled": "Le mode de démonstration est activé",
|
||||
"template_exists": "La template %s existe déja",
|
||||
"template_id_invalid": "Le numéro de template %s est invalide",
|
||||
"template_name_invalid": "Le nom de la template est invalide"
|
||||
},
|
||||
"debug": {
|
||||
"chart_this_server": "Graphique (ce serveur)",
|
||||
@@ -578,7 +588,7 @@
|
||||
"unchanged_if_empty": "Si non modifié, laisser en blanc",
|
||||
"username": "Nom d'utilisateur",
|
||||
"validate_save": "Valider et sauver",
|
||||
"lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (<code>.*google\\.com</code> pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut).",
|
||||
"lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (<code>.*google\\.com</code> pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut)",
|
||||
"mailbox_relayhost_info": "S'applique uniquement à la boîte aux lettres et aux alias directs, remplace le relayhost du domaine."
|
||||
},
|
||||
"footer": {
|
||||
@@ -612,7 +622,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "La connexion a été retardée de %s secondes.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Connexion",
|
||||
"mobileconfig_info": "Veuillez vous connecter en tant qu’utilisateur de la boîte pour télécharger le profil de connexion Apple demandé.",
|
||||
"other_logins": "Clé d'authentification",
|
||||
@@ -1081,9 +1091,12 @@
|
||||
"username": "Nom d'utilisateur",
|
||||
"verify": "Vérification",
|
||||
"waiting": "En attente",
|
||||
"week": "Semaine",
|
||||
"week": "semaine",
|
||||
"weekly": "Hebdomadaire",
|
||||
"weeks": "semaines"
|
||||
"weeks": "semaines",
|
||||
"months": "mois",
|
||||
"year": "année",
|
||||
"years": "années"
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
||||
|
@@ -175,10 +175,12 @@
|
||||
"empty": "Nessun risultato",
|
||||
"excludes": "Esclude questi destinatari",
|
||||
"f2b_ban_time": "Tempo di blocco (s)",
|
||||
"f2b_ban_time_increment": "Tempo di blocco aumenta ad ogni blocco",
|
||||
"f2b_blacklist": "Host/reti in blacklist",
|
||||
"f2b_filter": "Filtri Regex",
|
||||
"f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. <b>L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.</b>",
|
||||
"f2b_max_attempts": "Tentativi massimi",
|
||||
"f2b_max_ban_time": "Tempo massimo di blocco (s)",
|
||||
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
|
||||
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
|
||||
"f2b_parameters": "Parametri Fail2ban",
|
||||
@@ -674,7 +676,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "L'accesso è stato ritardato di %s secondi.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Login",
|
||||
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
|
||||
"other_logins": "Key login",
|
||||
|
@@ -3,7 +3,8 @@
|
||||
"bcc_maps": "BCC kartes",
|
||||
"filters": "Filtri",
|
||||
"recipient_maps": "Saņēmēja kartes",
|
||||
"syncjobs": "Sinhronizācijas uzdevumi"
|
||||
"syncjobs": "Sinhronizācijas uzdevumi",
|
||||
"spam_score": "Mēstules novērtējums"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "Visi pārējie filtri tiks deaktivizēti, kad aktīvs ir atzīmēts.",
|
||||
@@ -104,10 +105,10 @@
|
||||
"host": "Hosts",
|
||||
"import": "Importēt",
|
||||
"import_private_key": "Importēt privātu atslēgu",
|
||||
"in_use_by": "Tiek lietots ar",
|
||||
"in_use_by": "Izmanto",
|
||||
"inactive": "Neaktīvs",
|
||||
"link": "Saite",
|
||||
"loading": "Lūdzu uzgaidiet...",
|
||||
"loading": "Lūgums uzgaidīt...",
|
||||
"logo_info": "Jūsu attēls augšējā navigācijas joslā tiks palielināts līdz 40 pikseļiem un maks. sākumlapas platums par 250 pikseļi. Ir ļoti ieteicama pielāgojama grafikaYour image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. Ir ļoti ieteicama pielāgojamā grafika",
|
||||
"main_name": "\"mailcow UI\" nosaukums",
|
||||
"merged_vars_hint": "Pelēkās rindas tika apvienotas <code>vars.(local.)inc.php</code> un nevar tikt modificētas.",
|
||||
@@ -144,7 +145,10 @@
|
||||
"ui_texts": "UI etiķetes un teksti",
|
||||
"unchanged_if_empty": "Ja nav veiktas izmaiņas, atstājiet tukšu",
|
||||
"upload": "Augšupielādēt",
|
||||
"username": "Lietotājvārds"
|
||||
"username": "Lietotājvārds",
|
||||
"generate": "izveidot",
|
||||
"message": "Ziņojums",
|
||||
"last_applied": "Pēdējoreiz pielietots"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Piekļuve liegta, vai nepareizi dati",
|
||||
@@ -170,7 +174,7 @@
|
||||
"is_alias": "%s jau ir zināms alias",
|
||||
"is_alias_or_mailbox": "%s jau ir zināms alias, pastkastes vai alias addrese izvērsta no alias domēna.",
|
||||
"is_spam_alias": "%s ir jau zināms spam alias",
|
||||
"last_key": "Pēdējā atslēga nevar būt dzēsta",
|
||||
"last_key": "Pēdējo atslēgu nevar izdzēst, tā vietā jāatspējo divpakāpju pārbaude.",
|
||||
"login_failed": "Ielogošanās neveiksmīga",
|
||||
"mailbox_invalid": "Pastkastes vārds ir nederīgs",
|
||||
"mailbox_quota_exceeded": "Kvota pārsniedz domēna limitu (max. %d MiB)",
|
||||
@@ -262,7 +266,8 @@
|
||||
"title": "Labot priekšmetu",
|
||||
"unchanged_if_empty": "Ja neizmainīts atstājiet tukšu",
|
||||
"username": "Lietotājvārds",
|
||||
"validate_save": "Apstiprināt un saglabāt"
|
||||
"validate_save": "Apstiprināt un saglabāt",
|
||||
"last_modified": "Pēdējoreiz mainīts"
|
||||
},
|
||||
"footer": {
|
||||
"cancel": "Atcelt",
|
||||
@@ -314,21 +319,21 @@
|
||||
"bcc_destinations": "BCC galamērķi/s",
|
||||
"bcc_info": "BCC kartes tiek izmantotas, lai klusu pārsūtītu visu ziņojumu kopijas uz citu adresi. Saņēmēja kartes tipa ieraksts tiek izmantots, kad vietējais galamērķis darbojas kā pasta adresāts. Sūtītāja kartes atbilst vienam un tam pašam principam. <br/>\r\n Vietējais galamērķis netiks informēts par piegādes neveiksmi. ",
|
||||
"bcc_local_dest": "Vietējais galamērķis",
|
||||
"bcc_map_type": "BCC tips",
|
||||
"bcc_map_type": "BCC veids",
|
||||
"bcc_maps": "BCC kartes",
|
||||
"bcc_rcpt_map": "saņēmēja karte",
|
||||
"bcc_sender_map": "Sūtītāja karte",
|
||||
"bcc_to_rcpt": "Pārslēdzieties uz adresāta kartes tipu",
|
||||
"bcc_to_sender": "Pārslēgties uz sūtītāja kartes tipu",
|
||||
"bcc_type": "BCC tips",
|
||||
"deactivate": "Deaktivizēt",
|
||||
"deactivate": "Deaktivēt",
|
||||
"description": "Apraksts",
|
||||
"dkim_key_length": "DKIM atslēgas garums (bits)",
|
||||
"domain": "Domēns",
|
||||
"domain_admins": "Domēna administratori",
|
||||
"domain_aliases": "Domēna aliases",
|
||||
"domain_quota": "Kvota",
|
||||
"domain_quota_total": "Kopējā domēna kvota",
|
||||
"domain_quota_total": "Kopējais domēna ierobežojums",
|
||||
"domains": "Domēns",
|
||||
"edit": "Labot",
|
||||
"empty": "Nav rezultātu",
|
||||
@@ -341,7 +346,7 @@
|
||||
"inactive": "Neaktīvs",
|
||||
"kind": "Veids",
|
||||
"last_run": "Pēdējā norise",
|
||||
"last_run_reset": "Nākamais grafiks",
|
||||
"last_run_reset": "Ievietot sarakstā kā nākamo",
|
||||
"mailbox_quota": "Maks. pastkastes izmērs",
|
||||
"mailboxes": "Pastkaste",
|
||||
"max_aliases": "Maks. iespejamās aliases",
|
||||
@@ -374,7 +379,13 @@
|
||||
"tls_enforce_out": "Piespiest TLS izejošajiem",
|
||||
"toggle_all": "Pārslēgt visu",
|
||||
"username": "Lietotājvārds",
|
||||
"waiting": "Gaidīšana"
|
||||
"waiting": "Gaidīšana",
|
||||
"last_modified": "Pēdējoreiz mainīts",
|
||||
"booking_0_short": "Vienmēŗ bezmaksas",
|
||||
"daily": "Ik dienu",
|
||||
"hourly": "Ik stundu",
|
||||
"last_mail_login": "Pēdējā pieteikšanās pastkastē",
|
||||
"mailbox": "Pastkaste"
|
||||
},
|
||||
"quarantine": {
|
||||
"action": "Darbības",
|
||||
@@ -547,5 +558,14 @@
|
||||
"waiting": "Waiting",
|
||||
"week": "Nedēļa",
|
||||
"weeks": "Nedēļas"
|
||||
},
|
||||
"datatables": {
|
||||
"paginate": {
|
||||
"first": "Pirmā",
|
||||
"last": "Pēdējā"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"last_modified": "Pēdējoreiz mainīts"
|
||||
}
|
||||
}
|
||||
|
@@ -168,10 +168,12 @@
|
||||
"empty": "Geen resultaten",
|
||||
"excludes": "Exclusief",
|
||||
"f2b_ban_time": "Verbanningstijd (s)",
|
||||
"f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning",
|
||||
"f2b_blacklist": "Netwerken/hosts op de blacklist",
|
||||
"f2b_filter": "Regex-filters",
|
||||
"f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. <b>Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.</b>",
|
||||
"f2b_max_attempts": "Maximaal aantal pogingen",
|
||||
"f2b_max_ban_time": "Maximaal verbanningstijd (s)",
|
||||
"f2b_netban_ipv4": "Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)",
|
||||
"f2b_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)",
|
||||
"f2b_parameters": "Fail2ban",
|
||||
@@ -598,7 +600,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Aanmelding vertraagd met %s seconden.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Aanmelden",
|
||||
"mobileconfig_info": "Log in als mailboxgebruiker om het Apple-verbindingsprofiel te downloaden.",
|
||||
"other_logins": "Meld aan met key",
|
||||
|
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"acl": {
|
||||
"sogo_profile_reset": "Usuń profil SOGo (webmail)",
|
||||
"syncjobs": "Polecenie synchronizacji"
|
||||
"syncjobs": "Polecenie synchronizacji",
|
||||
"alias_domains": "Dodaj aliasy domen"
|
||||
},
|
||||
"add": {
|
||||
"active": "Aktywny",
|
||||
|
@@ -656,7 +656,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Conectarea a fost întârziată cu %s secunde.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Autentificare",
|
||||
"mobileconfig_info": "Autentificați-vă cu adresa de email pentru a descărca profilul de conexiune Apple.",
|
||||
"other_logins": "Autentificare cu cheie",
|
||||
|
@@ -37,7 +37,7 @@
|
||||
"add_domain_only": "Только добавить домен",
|
||||
"add_domain_restart": "Добавить домен и перезапустить SOGo",
|
||||
"alias_address": "Псевдоним/ы",
|
||||
"alias_address_info": "<small>Укажите почтовые адреса разделенные запятыми или, если хотите пересылать все сообщения для домена владельцам псевдонима то: <code>@example.com</code>. <b>Только домены mailcow разрешены</b>.</small>",
|
||||
"alias_address_info": "<small>Адрес(а) электронной почты (через запятую) или @example.com (для перехвата всех писем для домена). <b>только домены mailcow</b>.</small>",
|
||||
"alias_domain": "Псевдоним домена",
|
||||
"alias_domain_info": "<small>Действительные имена доменов, раздёленные запятыми.</small>",
|
||||
"app_name": "Название приложения",
|
||||
@@ -335,7 +335,8 @@
|
||||
"username": "Имя пользователя",
|
||||
"validate_license_now": "Получить лицензию на основе GUID с сервера лицензий",
|
||||
"verify": "Проверить",
|
||||
"yes": "✓"
|
||||
"yes": "✓",
|
||||
"queue_unban": "разблокировать"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Доступ запрещён, или указаны неверные данные",
|
||||
@@ -654,7 +655,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Вход был отложен на %s секунд.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Войти",
|
||||
"mobileconfig_info": "Пожалуйста, войдите в систему как пользователь почтового аккаунта для загрузки профиля подключения Apple.",
|
||||
"other_logins": "Вход с помощью ключа",
|
||||
|
@@ -106,7 +106,8 @@
|
||||
"username": "Používateľské meno",
|
||||
"validate": "Overiť",
|
||||
"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": {
|
||||
"access": "Prístup",
|
||||
@@ -656,7 +657,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Prihlásenie bolo oneskorené o %s sekúnd.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Prihlásenie",
|
||||
"mobileconfig_info": "Prosím, prihláste sa ako mailový používateľ pre stiahnutie požadovaného Apple profilu.",
|
||||
"other_logins": "Prihlásenie kľúčom",
|
||||
|
@@ -618,7 +618,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "Av säkerhetsskäl har inloggning inaktiverats i %s sekunder.",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Logga in",
|
||||
"mobileconfig_info": "Logga in som en användare av brevlåda för att ladda ner den begärda Apple-anslutningsprofilen.",
|
||||
"other_logins": "Loggain med nyckel",
|
||||
|
@@ -656,7 +656,7 @@
|
||||
"awaiting_tfa_confirmation": "В очікуванні підтвердження TFA"
|
||||
},
|
||||
"login": {
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "Увійти",
|
||||
"other_logins": "Вхід за допомогою ключа",
|
||||
"password": "Пароль",
|
||||
|
@@ -661,7 +661,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "请在 %s 秒后重新登录。",
|
||||
"fido2_webauthn": "使用 FIDO2/WebAuthn 登录",
|
||||
"fido2_webauthn": "使用 FIDO2/WebAuthn Login 登录",
|
||||
"login": "登录",
|
||||
"mobileconfig_info": "请使用邮箱用户登录以下载 Apple 连接描述文件。",
|
||||
"other_logins": "Key 登录",
|
||||
|
@@ -655,7 +655,7 @@
|
||||
},
|
||||
"login": {
|
||||
"delayed": "請在 %s 秒後重新登入。",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||
"login": "登入",
|
||||
"mobileconfig_info": "請使用信箱使用者登入以下載 Apple 連接描述檔案。",
|
||||
"other_logins": "金鑰登入",
|
||||
|
@@ -60,7 +60,7 @@ elseif (isset($_GET['login'])) {
|
||||
':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
|
||||
));
|
||||
// redirect to sogo (sogo will get the correct credentials via nginx auth_request
|
||||
header("Location: /SOGo/so/${login}");
|
||||
header("Location: /SOGo/so/{$login}");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
@@ -66,7 +66,7 @@ var lang = {{ lang_admin|raw }};
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
var admin_username = '{{ mailcow_cc_username }}';
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
var pagination_size = '{{ pagination_size }}';
|
||||
var log_pagination_size = '{{ log_pagination_size }}';
|
||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||
var log_pagination_size = Math.trunc('{{ log_pagination_size }}');
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -7,6 +7,8 @@
|
||||
<span class="d-none d-md-block">{{ lang.admin.customize }}</span>
|
||||
</div>
|
||||
<div id="collapse-tab-config-customize" class="card-body collapse" data-bs-parent="#admin-content">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 d-flex flex-column">
|
||||
<legend><i class="bi bi-file-image"></i> {{ lang.admin.change_logo }}</legend><hr />
|
||||
<p class="text-muted">{{ lang.admin.logo_info }}</p>
|
||||
<form class="form-inline" role="form" method="post" enctype="multipart/form-data">
|
||||
@@ -16,22 +18,78 @@
|
||||
</p>
|
||||
</form>
|
||||
{% if logo %}
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="thumbnail">
|
||||
<div class="thumbnail mt-auto">
|
||||
<img class="img-thumbnail" src="{{ logo }}" alt="mailcow logo">
|
||||
<div class="caption">
|
||||
<div class="caption d-flex flex-wrap mt-2 mb-4">
|
||||
<span class="badge fs-5 bg-info">{{ logo_specs.geometry.width }}x{{ logo_specs.geometry.height }} px</span>
|
||||
<span class="badge fs-5 bg-info">{{ logo_specs.mimetype }}</span>
|
||||
<span class="badge fs-5 bg-info mx-2">{{ logo_specs.mimetype }}</span>
|
||||
<span class="badge fs-5 bg-info">{{ logo_specs.fileSize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<form class="form-inline" role="form" method="post">
|
||||
<p><button name="reset_main_logo" type="submit" class="btn btn-sm d-block d-sm-inline btn-secondary">{{ lang.admin.reset_default }}</button></p>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 d-flex flex-column">
|
||||
<legend><i class="bi bi-file-image"></i> {{ lang.admin.change_favicon }}</legend><hr />
|
||||
<p class="text-muted">{{ lang.admin.favicon_info|raw }}</p>
|
||||
<form class="form-inline" role="form" method="post" enctype="multipart/form-data">
|
||||
<p>
|
||||
<input class="mb-4" type="file" name="favicon" accept="image/x-icon, image/png"><br>
|
||||
<button name="submit_favicon" type="submit" class="btn btn-sm d-block d-sm-inline btn-secondary"><i class="bi bi-upload"></i> {{ lang.admin.upload }}</button>
|
||||
</p>
|
||||
</form>
|
||||
{% if favicon %}
|
||||
<div class="thumbnail mt-auto">
|
||||
<img class="img-thumbnail" src="{{ favicon }}" alt="mailcow favicon">
|
||||
<div class="caption d-flex flex-wrap mt-2 mb-4">
|
||||
<span class="badge fs-5 bg-info">{{ favicon_specs.geometry.width }}x{{ favicon_specs.geometry.height }} px</span>
|
||||
<span class="badge fs-5 bg-info mx-2">{{ favicon_specs.mimetype }}</span>
|
||||
<span class="badge fs-5 bg-info">{{ favicon_specs.fileSize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form-inline" role="form" method="post">
|
||||
<p><button name="reset_favicon" type="submit" class="btn btn-sm d-block d-sm-inline btn-secondary">{{ lang.admin.reset_default }}</button></p>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if not skip_sogo %}
|
||||
<legend style="padding-top:20px" unselectable="on">{{ lang.admin.sogo_theme }}</legend><hr />
|
||||
<form class="form" data-id="sogo_theme" role="form" method="post">
|
||||
<div class="mb-4 row">
|
||||
<div class="col-12 col-md-4 mb-2">
|
||||
<label class="d-block" for="sogo_primary">{{ lang.admin.sogo_theme_primary }}</label>
|
||||
<select multiple data-width="100%" id="sogo_primary" name="primary" class="selectpicker show-tick" data-max-options="1" data-id="sogo_theme">
|
||||
{% for sogo_palette in sogo_palettes %}
|
||||
<option {% if sogo_palette == sogo_theme.primary %}selected{% endif %} value="{{ sogo_palette }}">{{ sogo_palette }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 mb-2">
|
||||
<label class="d-block" for="sogo_accent">{{ lang.admin.sogo_theme_accent }}</label>
|
||||
<select multiple data-width="100%" id="sogo_accent" name="accent" class="selectpicker show-tick" data-max-options="1" data-id="sogo_theme">
|
||||
{% for sogo_palette in sogo_palettes %}
|
||||
<option {% if sogo_palette == sogo_theme.accent %}selected{% endif %} value="{{ sogo_palette }}">{{ sogo_palette }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 mb-2">
|
||||
<label class="d-block" for="sogo_background">{{ lang.admin.sogo_theme_background }}</label>
|
||||
<select multiple data-width="100%" id="sogo_background" name="background" class="selectpicker show-tick" data-max-options="1" data-id="sogo_theme">
|
||||
{% for sogo_palette in sogo_palettes %}
|
||||
<option {% if sogo_palette == sogo_theme.background %}selected{% endif %} value="{{ sogo_palette }}">{{ sogo_palette }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted">{{ lang.admin.sogo_theme_info }}</p>
|
||||
<p><div class="btn-group">
|
||||
<button class="btn btn-sm btn-xs-half d-block d-sm-inline btn-success" type="button" data-action="edit_selected" data-item="sogo-theme" data-id="sogo_theme" data-reload="no" data-api-url='edit/sogo_theme' data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
||||
<button class="btn btn-sm btn-xs-half d-block d-sm-inline btn-danger" type="button" data-action="delete_selected" data-item="sogo-theme" data-id="sogo_theme" data-api-url="delete/sogo_theme"><i class="bi bi-trash"></i> {{ lang.admin.remove }}</button>
|
||||
</div></p>
|
||||
</form>
|
||||
{% endif %}
|
||||
<legend style="padding-top:20px" unselectable="on">{{ lang.admin.ip_check }}</legend><hr />
|
||||
<div id="ip_check">
|
||||
|
@@ -12,6 +12,14 @@
|
||||
<label for="f2b_ban_time">{{ lang.admin.f2b_ban_time }}:</label>
|
||||
<input type="number" class="form-control" id="f2b_ban_time" name="ban_time" value="{{ f2b_data.ban_time }}" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="f2b_max_ban_time">{{ lang.admin.f2b_max_ban_time }}:</label>
|
||||
<input type="number" class="form-control" id="f2b_max_ban_time" name="max_ban_time" value="{{ f2b_data.max_ban_time }}" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<input class="form-check-input" type="checkbox" value="1" name="ban_time_increment" id="f2b_ban_time_increment" {% if f2b_data.ban_time_increment == 1 %}checked{% endif %}>
|
||||
<label class="form-check-label" for="f2b_ban_time_increment">{{ lang.admin.f2b_ban_time_increment }}</label>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="f2b_max_attempts">{{ lang.admin.f2b_max_attempts }}:</label>
|
||||
<input type="number" class="form-control" id="f2b_max_attempts" name="max_attempts" value="{{ f2b_data.max_attempts }}" required>
|
||||
|
@@ -23,8 +23,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<link rel="shortcut icon" href="/favicon.png" type="image/png">
|
||||
<link rel="icon" href="/favicon.png" type="image/png">
|
||||
<link rel="shortcut icon" href="{{ favicon|default('/favicon.png') }}" type="image/png">
|
||||
<link rel="icon" href="{{ favicon|default('/favicon.png') }}" type="image/png">
|
||||
</head>
|
||||
<body>
|
||||
<div class="overlay"></div>
|
||||
|
@@ -41,7 +41,7 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-8">
|
||||
<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>
|
||||
<tr>
|
||||
<td>Hostname</td>
|
||||
@@ -612,7 +612,7 @@
|
||||
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="rl_log" data-table="rl_log" href="#">{{ lang.datatables.expand_all }}</a></li>
|
||||
<li class="table_collapse_option"><a class="dropdown-item" data-datatables-collapse="rl_log" data-table="rl_log" href="#">{{ lang.datatables.collapse_all }}</a></li>
|
||||
</ul>
|
||||
<p class="text-muted">{{ lang.admin.hash_remove_info }}</p>
|
||||
<p class="text-muted">{{ lang.admin.hash_remove_info|raw }}</p>
|
||||
<table id="rl_log" class="table table-striped dt-responsive w-100"></table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -627,6 +627,6 @@
|
||||
var lang_debug = {{ lang_debug|raw }};
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
var log_pagination_size = '{{ log_pagination_size }}';
|
||||
var log_pagination_size = Math.trunc('{{ log_pagination_size }}');
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -46,7 +46,7 @@
|
||||
<div class="col-sm-3 col-5 text-end">{{ lang.fido2.known_ids }}:</div>
|
||||
<div class="col-sm-9 col-7">
|
||||
<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>
|
||||
<th>ID</th>
|
||||
<th style="min-width:240px;text-align: right">{{ lang.admin.action }}</th>
|
||||
|
@@ -26,7 +26,7 @@
|
||||
var lang_user = {{ lang_user|raw }};
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
var pagination_size = '{{ pagination_size }}';
|
||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||
var table_for_domain = '{{ domain }}';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -109,25 +109,25 @@
|
||||
<label class="control-label col-sm-2">{{ lang.user.quarantine_notification }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="btn-group" data-acl="{{ acl.quarantine_notification }}">
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'never' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'never' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="quarantine_notification"
|
||||
data-api-url='edit/quarantine_notification'
|
||||
data-api-attr='{"quarantine_notification":"never"}'>{{ lang.user.never }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'hourly' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'hourly' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="quarantine_notification"
|
||||
data-api-url='edit/quarantine_notification'
|
||||
data-api-attr='{"quarantine_notification":"hourly"}'>{{ lang.user.hourly }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'daily' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'daily' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="quarantine_notification"
|
||||
data-api-url='edit/quarantine_notification'
|
||||
data-api-attr='{"quarantine_notification":"daily"}'>{{ lang.user.daily }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'weekly' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'weekly' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="quarantine_notification"
|
||||
@@ -141,19 +141,19 @@
|
||||
<label class="control-label col-sm-2">{{ lang.user.quarantine_category }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="btn-group" data-acl="{{ acl.quarantine_category }}">
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'reject' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'reject' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="quarantine_category"
|
||||
data-api-url='edit/quarantine_category'
|
||||
data-api-attr='{"quarantine_category":"reject"}'>{{ lang.user.q_reject }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'add_header' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'add_header' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="quarantine_category"
|
||||
data-api-url='edit/quarantine_category'
|
||||
data-api-attr='{"quarantine_category":"add_header"}'>{{ lang.user.q_add_header }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'all' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'all' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="quarantine_category"
|
||||
@@ -167,13 +167,13 @@
|
||||
<label class="control-label col-sm-2" for="sender_acl">{{ lang.user.tls_policy }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="btn-group" data-acl="{{ acl.tls_policy }}">
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_in == '1' %} active"{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_in == '1' %} btn-dark"{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="tls_policy"
|
||||
data-api-url='edit/tls_policy'
|
||||
data-api-attr='{"tls_enforce_in": {% if get_tls_policy.tls_enforce_in == '1' %}0{% else %}1{% endif %} }'>{{ lang.user.tls_enforce_in }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_out == '1' %} active"{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_out == '1' %} btn-dark"{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailbox }}"
|
||||
data-id="tls_policy"
|
||||
|
@@ -38,15 +38,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex mt-4" style="position: relative">
|
||||
<div class="btn-group">
|
||||
<div class="btn-group">
|
||||
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
|
||||
<button type="button" class="btn btn-xs-lg btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-xs-lg btn-success ms-2" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</button>
|
||||
{% if not oauth2_request %}
|
||||
<button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-xs-lg btn-secondary ms-auto dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span>
|
||||
|
@@ -19,7 +19,7 @@
|
||||
</li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" aria-controls="tab-resources" role="tab" data-bs-toggle="tab" data-bs-target="#tab-resources">{{ lang.mailbox.resources }}</button></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" data-bs-target="#">{{ lang.mailbox.aliases }}</a>
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.aliases }}</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-mbox-aliases" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mbox-aliases">{{ lang.mailbox.aliases }}</button></li>
|
||||
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-domain-aliases" role="tab" data-bs-toggle="tab" data-bs-target="#tab-domain-aliases">{{ lang.mailbox.domain_aliases }}</button></li>
|
||||
@@ -58,7 +58,7 @@
|
||||
var lang_rl = {{ lang_rl|raw }};
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
var pagination_size = '{{ pagination_size }}';
|
||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||
var role = '{{ role }}';
|
||||
var is_dual = {{ is_dual }};
|
||||
var ALLOW_ADMIN_EMAIL_LOGIN = {{ allow_admin_email_login }};
|
||||
|
@@ -54,6 +54,7 @@
|
||||
<li class="dropdown-header">SMTP</li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":1}' href="#">{{ lang.mailbox.activate }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="dropdown-header">Sieve</li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":1}' href="#">{{ lang.mailbox.activate }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li>
|
||||
|
@@ -37,7 +37,7 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
</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="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>
|
||||
@@ -66,7 +66,7 @@ var acl = '{{ acl_json|raw }}';
|
||||
var lang = {{ lang_quarantine|raw }};
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
var pagination_size = '{{ pagination_size }}';
|
||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||
var role = '{{ role }}';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -55,7 +55,7 @@
|
||||
var lang = {{ lang_queue|raw }};
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
var pagination_size = '{{ pagination_size }}';
|
||||
var pagination_size = Math.trunc('{{ pagination_size }}');
|
||||
var table_for_domain = '{{ domain }}';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -12,19 +12,19 @@
|
||||
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tag_handling }}:</div>
|
||||
<div class="col-sm-9 col-12">
|
||||
<div class="btn-group" data-acl="{{ acl.delimiter_action }}">
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'subfolder' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'subfolder' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="delimiter_action"
|
||||
data-api-url='edit/delimiter_action'
|
||||
data-api-attr='{"tagged_mail_handler":"subfolder"}'>{{ lang.user.tag_in_subfolder }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'subject' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'subject' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="delimiter_action"
|
||||
data-api-url='edit/delimiter_action'
|
||||
data-api-attr='{"tagged_mail_handler":"subject"}'>{{ lang.user.tag_in_subject }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'none' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'none' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="delimiter_action"
|
||||
@@ -40,13 +40,13 @@
|
||||
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tls_policy }}:</div>
|
||||
<div class="col-sm-9 col-12">
|
||||
<div class="btn-group" data-acl="{{ acl.tls_policy }}">
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_in == '1' %} active"{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_in == '1' %} btn-dark"{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="tls_policy"
|
||||
data-api-url='edit/tls_policy'
|
||||
data-api-attr='{"tls_enforce_in": {% if get_tls_policy.tls_enforce_in == '1' %}0{% else %}1{% endif %} }'>{{ lang.user.tls_enforce_in }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_out == '1' %} active"{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_out == '1' %} btn-dark"{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="tls_policy"
|
||||
@@ -61,25 +61,25 @@
|
||||
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_notification }}:</div>
|
||||
<div class="col-sm-9 col-12">
|
||||
<div class="btn-group" data-acl="{{ acl.quarantine_notification }}">
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'never' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'never' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="quarantine_notification"
|
||||
data-api-url='edit/quarantine_notification'
|
||||
data-api-attr='{"quarantine_notification":"never"}'>{{ lang.user.never }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'hourly' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'hourly' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="quarantine_notification"
|
||||
data-api-url='edit/quarantine_notification'
|
||||
data-api-attr='{"quarantine_notification":"hourly"}'>{{ lang.user.hourly }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'daily' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'daily' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="quarantine_notification"
|
||||
data-api-url='edit/quarantine_notification'
|
||||
data-api-attr='{"quarantine_notification":"daily"}'>{{ lang.user.daily }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'weekly' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'weekly' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="quarantine_notification"
|
||||
@@ -93,19 +93,19 @@
|
||||
<div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_category }}:</div>
|
||||
<div class="col-sm-9 col-12">
|
||||
<div class="btn-group" data-acl="{{ acl.quarantine_category }}">
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'reject' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'reject' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="quarantine_category"
|
||||
data-api-url='edit/quarantine_category'
|
||||
data-api-attr='{"quarantine_category":"reject"}'>{{ lang.user.q_reject }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'add_header' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'add_header' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="quarantine_category"
|
||||
data-api-url='edit/quarantine_category'
|
||||
data-api-attr='{"quarantine_category":"add_header"}'>{{ lang.user.q_add_header }}</button>
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'all' %} active{% endif %}"
|
||||
<button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'all' %} btn-dark{% endif %}"
|
||||
data-action="edit_selected"
|
||||
data-item="{{ mailcow_cc_username }}"
|
||||
data-id="quarantine_category"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
var acl = '{{ acl_json|raw }}';
|
||||
var lang = {{ lang_user|raw }};
|
||||
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 user_spam_score = [{{ user_spam_score }}];
|
||||
var lang_datatables = {{ lang_datatables|raw }};
|
||||
|
@@ -20,6 +20,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
|
||||
'tfa_data' => $tfa_data,
|
||||
'fido2_data' => $fido2_data,
|
||||
'lang_user' => json_encode($lang['user']),
|
||||
'lang_datatables' => json_encode($lang['datatables']),
|
||||
];
|
||||
}
|
||||
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||
|
@@ -58,7 +58,7 @@ services:
|
||||
- redis
|
||||
|
||||
clamd-mailcow:
|
||||
image: mailcow/clamd:1.60
|
||||
image: mailcow/clamd:1.61
|
||||
restart: always
|
||||
depends_on:
|
||||
- unbound-mailcow
|
||||
@@ -106,7 +106,7 @@ services:
|
||||
- rspamd
|
||||
|
||||
php-fpm-mailcow:
|
||||
image: mailcow/phpfpm:1.82
|
||||
image: mailcow/phpfpm:1.83
|
||||
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
|
||||
depends_on:
|
||||
- redis-mailcow
|
||||
@@ -169,7 +169,7 @@ services:
|
||||
- phpfpm
|
||||
|
||||
sogo-mailcow:
|
||||
image: mailcow/sogo:1.114
|
||||
image: mailcow/sogo:1.118
|
||||
environment:
|
||||
- DBNAME=${DBNAME}
|
||||
- DBUSER=${DBUSER}
|
||||
@@ -191,7 +191,7 @@ services:
|
||||
volumes:
|
||||
- ./data/hooks/sogo:/hooks:Z
|
||||
- ./data/conf/sogo/:/etc/sogo/:z
|
||||
- ./data/web/inc/init_db.inc.php:/init_db.inc.php:Z
|
||||
- ./data/web/inc/init_db.inc.php:/init_db.inc.php:z
|
||||
- ./data/conf/sogo/custom-favicon.ico:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico:z
|
||||
- ./data/conf/sogo/custom-theme.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/theme.js:z
|
||||
- ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js:z
|
||||
@@ -216,7 +216,7 @@ services:
|
||||
- sogo
|
||||
|
||||
dovecot-mailcow:
|
||||
image: mailcow/dovecot:1.22
|
||||
image: mailcow/dovecot:1.23
|
||||
depends_on:
|
||||
- mysql-mailcow
|
||||
dns:
|
||||
@@ -389,7 +389,7 @@ services:
|
||||
acme-mailcow:
|
||||
depends_on:
|
||||
- nginx-mailcow
|
||||
image: mailcow/acme:1.83
|
||||
image: mailcow/acme:1.84
|
||||
dns:
|
||||
- ${IPV4_NETWORK:-172.22.1}.254
|
||||
environment:
|
||||
@@ -425,7 +425,7 @@ services:
|
||||
- acme
|
||||
|
||||
netfilter-mailcow:
|
||||
image: mailcow/netfilter:1.50
|
||||
image: mailcow/netfilter:1.52
|
||||
stop_grace_period: 30s
|
||||
depends_on:
|
||||
- dovecot-mailcow
|
||||
@@ -510,11 +510,10 @@ services:
|
||||
- watchdog
|
||||
|
||||
dockerapi-mailcow:
|
||||
image: mailcow/dockerapi:2.0
|
||||
image: mailcow/dockerapi:2.04
|
||||
security_opt:
|
||||
- label=disable
|
||||
restart: always
|
||||
oom_kill_disable: true
|
||||
dns:
|
||||
- ${IPV4_NETWORK:-172.22.1}.254
|
||||
environment:
|
||||
|
@@ -25,9 +25,8 @@ for bin in openssl curl docker git awk sha1sum; do
|
||||
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
|
||||
done
|
||||
|
||||
if command -v docker compose > /dev/null 2>&1; then
|
||||
version=$(docker compose version --short)
|
||||
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
|
||||
if docker compose > /dev/null 2>&1; then
|
||||
if docker compose version --short | grep "^2." > /dev/null 2>&1; then
|
||||
COMPOSE_VERSION=native
|
||||
echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m"
|
||||
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
||||
@@ -35,12 +34,12 @@ if command -v docker compose > /dev/null 2>&1; then
|
||||
echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
elif command -v docker-compose > /dev/null 2>&1; then
|
||||
version=$(docker-compose version --short)
|
||||
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
|
||||
elif docker-compose > /dev/null 2>&1; then
|
||||
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
|
||||
if docker-compose version --short | grep "^2." > /dev/null 2>&1; then
|
||||
COMPOSE_VERSION=standalone
|
||||
echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
|
||||
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
|
||||
@@ -51,9 +50,11 @@ elif command -v docker-compose > /dev/null 2>&1; then
|
||||
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||
echo -e "\e[31mPlease install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
echo -e "\e[31mPlease install it regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -204,8 +205,8 @@ DBUSER=mailcow
|
||||
|
||||
# Please use long, random alphanumeric strings (A-Za-z0-9)
|
||||
|
||||
DBPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28)
|
||||
DBROOT=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28)
|
||||
DBPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
|
||||
DBROOT=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
|
||||
|
||||
# ------------------------------
|
||||
# HTTP/S Bindings
|
||||
@@ -260,7 +261,7 @@ COMPOSE_PROJECT_NAME=mailcowdockerized
|
||||
# Switch here between native (compose plugin) and standalone
|
||||
# For more informations take a look at the mailcow docs regarding the configuration options.
|
||||
# Normally this should be untouched but if you decided to use either of those you can switch it manually here.
|
||||
# Please be aware that at least one of those variants should be installed on your maschine or mailcow will fail.
|
||||
# Please be aware that at least one of those variants should be installed on your machine or mailcow will fail.
|
||||
|
||||
DOCKER_COMPOSE_VERSION=${COMPOSE_VERSION}
|
||||
|
||||
@@ -457,6 +458,15 @@ case ${git_branch} in
|
||||
mailcow_last_git_version=""
|
||||
;;
|
||||
esac
|
||||
# if [ ${git_branch} == "master" ]; then
|
||||
# mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
# elif [ ${git_branch} == "nightly" ]; then
|
||||
# mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream}))
|
||||
# mailcow_last_git_version=""
|
||||
# else
|
||||
# mailcow_git_version=$(git rev-parse --short HEAD)
|
||||
# mailcow_last_git_version=""
|
||||
# fi
|
||||
|
||||
if [[ $SKIP_BRANCH != "y" ]]; then
|
||||
mailcow_git_commit=$(git rev-parse origin/${git_branch})
|
||||
|
@@ -26,6 +26,6 @@ services:
|
||||
- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
|
||||
|
||||
mysql-mailcow:
|
||||
image: alpine:3.10
|
||||
image: alpine:3.17
|
||||
command: /bin/true
|
||||
restart: "no"
|
||||
|
15
helper-scripts/expiry-dates.sh
Normal file → Executable file
15
helper-scripts/expiry-dates.sh
Normal file → Executable file
@@ -3,10 +3,11 @@
|
||||
[[ -f mailcow.conf ]] && source mailcow.conf
|
||||
[[ -f ../mailcow.conf ]] && source ../mailcow.conf
|
||||
|
||||
POSTFIX=$(echo | openssl s_client -connect ${MAILCOW_HOSTNAME}:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2)
|
||||
DOVECOT=$(echo | openssl s_client -connect ${MAILCOW_HOSTNAME}:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2)
|
||||
NGINX=$(echo | openssl s_client -connect ${MAILCOW_HOSTNAME}:443 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2)
|
||||
echo TLS expiry dates:
|
||||
echo Postfix: ${POSTFIX}
|
||||
echo Dovecot: ${DOVECOT}
|
||||
echo Nginx: ${NGINX}
|
||||
POSTFIX=$(echo | openssl s_client -connect ${MAILCOW_HOSTNAME}:${SMTP_PORT} -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2)
|
||||
DOVECOT=$(echo | openssl s_client -connect ${MAILCOW_HOSTNAME}:${IMAP_PORT} -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2)
|
||||
NGINX=$(echo | openssl s_client -connect ${MAILCOW_HOSTNAME}:${HTTPS_PORT} 2>/dev/null | openssl x509 -inform pem -noout -enddate | cut -d "=" -f 2)
|
||||
|
||||
echo "TLS expiry dates:"
|
||||
echo "Postfix: ${POSTFIX}"
|
||||
echo "Dovecot: ${DOVECOT}"
|
||||
echo "Nginx: ${NGINX}"
|
||||
|
@@ -19,7 +19,7 @@ read -r -p "Are you sure you want to reset the mailcow administrator account? [y
|
||||
response=${response,,} # tolower
|
||||
if [[ "$response" =~ ^(yes|y)$ ]]; then
|
||||
echo -e "\nWorking, please wait..."
|
||||
random=$(</dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-16})
|
||||
random=$(</dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c${1:-16})
|
||||
password=$(docker exec -it $(docker ps -qf name=dovecot-mailcow) doveadm pw -s SSHA256 -p ${random} | tr -d '\r')
|
||||
docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM admin WHERE username='admin';"
|
||||
docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM domain_admins WHERE username='admin';"
|
||||
|
@@ -1,10 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
|
||||
NEXTCLOUD_VERSION=25.0.2
|
||||
NEXTCLOUD_VERSION=26.0.1
|
||||
|
||||
for bin in curl dirmngr; do
|
||||
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
|
||||
echo -ne "Checking prerequisites..."
|
||||
sleep 1
|
||||
for bin in curl dirmngr tar bzip2; do
|
||||
if [[ -z $(which ${bin}) ]]; then echo -ne "\r\033[31mCannot find ${bin}, exiting...\033[0m\n"; exit 1; fi
|
||||
done
|
||||
echo -ne "\r\033[32mFound all prerequisites! Continuing...\033[0m\n"
|
||||
|
||||
[[ -z ${1} ]] && NC_HELP=y
|
||||
|
||||
@@ -46,22 +49,22 @@ if [[ ${NC_PURGE} == "y" ]]; then
|
||||
|
||||
echo -e "\033[33mDetecting Database information...\033[0m"
|
||||
if [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "Show databases" | grep "nextcloud") ]]; then
|
||||
echo -e "\033[32mFound seperate nextcloud Database (newer scheme)!\033[0m"
|
||||
echo -e "\033[32mFound seperate Nextcloud database (newer scheme)!\033[0m"
|
||||
echo -e "\033[31mPurging...\033[0m"
|
||||
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP DATABASE nextcloud;" > /dev/null
|
||||
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP USER 'nextcloud'@'%';" > /dev/null
|
||||
elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'oc_%'") && $? -eq 0 ]]; then
|
||||
echo -e "\033[32mFound nextcloud (oc) tables inside of mailcow Database (old scheme)!\033[0m"
|
||||
echo -e "\033[32mFound Nextcloud (oc) tables inside of mailcow database (old scheme)!\033[0m"
|
||||
echo -e "\033[31mPurging...\033[0m"
|
||||
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
|
||||
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'oc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null
|
||||
elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'nc_%'") && $? -eq 0 ]]; then
|
||||
echo -e "\033[32mFound nextcloud (nc) tables inside of mailcow Database (old scheme)!\033[0m"
|
||||
echo -e "\033[32mFound Nextcloud (nc) tables inside of mailcow database (old scheme)!\033[0m"
|
||||
echo -e "\033[31mPurging...\033[0m"
|
||||
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
|
||||
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null
|
||||
else
|
||||
echo -e "\033[31mError: No Nextcloud Databases/Tables found!"
|
||||
echo -e "\033[31mError: No Nextcloud databases/tables found!"
|
||||
echo -e "\033[33mNot purging anything...\033[0m"
|
||||
exit 1
|
||||
fi
|
||||
@@ -80,10 +83,10 @@ EOF
|
||||
|
||||
docker restart $(docker ps -aqf name=nginx-mailcow)
|
||||
|
||||
echo -e "\033[32mNextcloud has been sucessfully uninstalled!\033[0m"
|
||||
echo -e "\033[32mNextcloud has been uninstalled sucessfully!\033[0m"
|
||||
|
||||
elif [[ ${NC_UPDATE} == "y" ]]; then
|
||||
read -r -p "Are you sure you want to update Nextcloud (with nextclouds own updater)? [y/N] " response
|
||||
read -r -p "Are you sure you want to update Nextcloud (with Nextclouds own updater)? [y/N] " response
|
||||
response=${response,,}
|
||||
if [[ ! "$response" =~ ^(yes|y)$ ]]; then
|
||||
echo "OK, aborting."
|
||||
@@ -94,8 +97,12 @@ elif [[ ${NC_UPDATE} == "y" ]]; then
|
||||
echo -e "\033[31mError: Nextcloud occ not found. Is Nextcloud installed?\033[0m"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
|
||||
echo "Nextcloud seems not to be installed."
|
||||
if grep -Pq 'This version of Nextcloud is not compatible with (?:PHP)?(?>=?)(?:PHP)?(?>.+)' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
|
||||
echo -e "\033[31mError: This version of Nextcloud is not compatible with the current PHP version of php-fpm-mailcow, we'll fix it\033[0m"
|
||||
wget -q https://raw.githubusercontent.com/nextcloud/server/v26.0.0/lib/versioncheck.php -O ./data/web/nextcloud/lib/versioncheck.php
|
||||
echo -e "\e[33mPlease restart the update again.\e[0m"
|
||||
elif ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
|
||||
echo -e "\033[31mError: Nextcloud seems not to be installed.\033[0m"
|
||||
exit 1
|
||||
else
|
||||
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "php /web/nextcloud/updater/updater.phar"
|
||||
@@ -118,29 +125,29 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
|
||||
&& mkdir -p ./data/web/nextcloud/data \
|
||||
&& chmod +x ./data/web/nextcloud/occ
|
||||
|
||||
echo -e "\033[33mCreating Nextcloud Database...\033[0m"
|
||||
NC_DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
|
||||
echo -e "\033[33mCreating 'nextcloud' database...\033[0m"
|
||||
NC_DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
|
||||
NC_DBUSER=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};"
|
||||
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}';"
|
||||
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}'@'%';"
|
||||
sleep 2
|
||||
|
||||
echo ""
|
||||
echo -e "\033[33mInstalling Nextcloud...\033[0m"
|
||||
ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
|
||||
ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
|
||||
|
||||
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"
|
||||
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 \
|
||||
--database mysql \
|
||||
--database-host mysql \
|
||||
@@ -149,9 +156,9 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
|
||||
--database-pass ${NC_DBPASS} \
|
||||
--admin-user admin \
|
||||
--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 ""
|
||||
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings config:system:set redis host --value=redis --type=string; \
|
||||
/web/nextcloud/occ --no-warnings config:system:set redis port --value=6379 --type=integer; \
|
||||
@@ -178,7 +185,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
|
||||
#/web/nextcloud/occ --no-warnings config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert}; \
|
||||
#/web/nextcloud/occ --no-warnings config:system:set user_backends 0 class --value=OC_User_IMAP; \
|
||||
|
||||
echo -e "\r[4/4] Enabling NGINX Configuration"
|
||||
echo -e "\r[4/4] Enabling Nginx Configuration"
|
||||
cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/
|
||||
sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf
|
||||
sleep 2
|
||||
@@ -193,11 +200,11 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
|
||||
echo "* INSTALL DATE: $(date +%Y-%m-%d_%H-%M-%S) *"
|
||||
echo "******************************************"
|
||||
echo ""
|
||||
echo -e "\033[36mDatabase Name: ${NC_DBNAME}\033[0m"
|
||||
echo -e "\033[36mDatabase User: ${NC_DBUSER}\033[0m"
|
||||
echo -e "\033[36mDatabase Password: ${NC_DBPASS}\033[0m"
|
||||
echo -e "\033[36mDatabase name: ${NC_DBNAME}\033[0m"
|
||||
echo -e "\033[36mDatabase user: ${NC_DBUSER}\033[0m"
|
||||
echo -e "\033[36mDatabase password: ${NC_DBPASS}\033[0m"
|
||||
echo ""
|
||||
echo -e "\033[31mUI Admin Password: ${ADMIN_NC_PASS}\033[0m"
|
||||
echo -e "\033[31mUI admin password: ${ADMIN_NC_PASS}\033[0m"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -215,5 +222,4 @@ elif [[ ${NC_RESETPW} == "y" ]]; then
|
||||
read -p "Enter the username: " NC_USER
|
||||
done
|
||||
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ user:resetpassword ${NC_USER}
|
||||
|
||||
fi
|
||||
|
92
update.sh
92
update.sh
@@ -176,44 +176,82 @@ remove_obsolete_nginx_ports() {
|
||||
}
|
||||
|
||||
detect_docker_compose_command(){
|
||||
if ! [ "${DOCKER_COMPOSE_VERSION}" == "native" ] && ! [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
|
||||
if command -v docker compose > /dev/null 2>&1; then
|
||||
version=$(docker compose version --short)
|
||||
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
|
||||
COMPOSE_VERSION=native
|
||||
if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
|
||||
if docker compose > /dev/null 2>&1; then
|
||||
if docker compose version --short | grep "2." > /dev/null 2>&1; then
|
||||
DOCKER_COMPOSE_VERSION=native
|
||||
COMPOSE_COMMAND="docker compose"
|
||||
echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m"
|
||||
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' $SCRIPT_DIR/mailcow.conf
|
||||
sleep 2
|
||||
echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
elif command -v docker-compose > /dev/null 2>&1; then
|
||||
version=$(docker-compose version --short)
|
||||
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
|
||||
COMPOSE_VERSION=standalone
|
||||
elif docker-compose > /dev/null 2>&1; then
|
||||
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
|
||||
if docker-compose version --short | grep "^2." > /dev/null 2>&1; then
|
||||
DOCKER_COMPOSE_VERSION=standalone
|
||||
COMPOSE_COMMAND="docker-compose"
|
||||
echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
|
||||
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' $SCRIPT_DIR/mailcow.conf
|
||||
sleep 2
|
||||
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||
echo -e "\e[31mPlease install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
|
||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
elif [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then
|
||||
COMPOSE_COMMAND="docker compose"
|
||||
# Check if Native Compose works and has not been deleted
|
||||
if ! $COMPOSE_COMMAND > /dev/null 2>&1; then
|
||||
# IF it not exists/work anymore try the other command
|
||||
COMPOSE_COMMAND="docker-compose"
|
||||
if ! $COMPOSE_COMMAND > /dev/null 2>&1 || ! $COMPOSE_COMMAND --version | grep "^2." > /dev/null 2>&1; then
|
||||
# IF it cannot find Standalone in > 2.X, then script stops
|
||||
echo -e "\e[31mCannot find Docker Compose or the Version is lower then 2.X.X.\e[0m"
|
||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
# If it finds the standalone Plugin it will use this instead and change the mailcow.conf Variable accordingly
|
||||
echo -e "\e[31mFound different Docker Compose Version then declared in mailcow.conf!\e[0m"
|
||||
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable from native to standalone\e[0m"
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' $SCRIPT_DIR/mailcow.conf
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
|
||||
elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
|
||||
COMPOSE_COMMAND="docker-compose"
|
||||
# Check if Standalone Compose works and has not been deleted
|
||||
if ! $COMPOSE_COMMAND > /dev/null 2>&1 && ! $COMPOSE_COMMAND --version > /dev/null 2>&1 | grep "^2." > /dev/null 2>&1; then
|
||||
# IF it not exists/work anymore try the other command
|
||||
COMPOSE_COMMAND="docker compose"
|
||||
if ! $COMPOSE_COMMAND > /dev/null 2>&1; then
|
||||
# IF it cannot find Native in > 2.X, then script stops
|
||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
# If it finds the native Plugin it will use this instead and change the mailcow.conf Variable accordingly
|
||||
echo -e "\e[31mFound different Docker Compose Version then declared in mailcow.conf!\e[0m"
|
||||
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable from standalone to native\e[0m"
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' $SCRIPT_DIR/mailcow.conf
|
||||
sleep 2
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -323,8 +361,12 @@ while (($#)); do
|
||||
echo -e "\e[32mRunning in forced mode...\e[0m"
|
||||
FORCE=y
|
||||
;;
|
||||
-d|--dev)
|
||||
echo -e "\e[32mRunning in Developer mode...\e[0m"
|
||||
DEV=y
|
||||
;;
|
||||
--help|-h)
|
||||
echo './update.sh [-c|--check, --ours, --gc, --nightly, --prefetch, --skip-start, --skip-ping-check, --stable, -f|--force, -h|--help]
|
||||
echo './update.sh [-c|--check, --ours, --gc, --nightly, --prefetch, --skip-start, --skip-ping-check, --stable, -f|--force, -d|--dev, -h|--help]
|
||||
|
||||
-c|--check - Check for updates and exit (exit codes => 0: update available, 3: no updates)
|
||||
--ours - Use merge strategy option "ours" to solve conflicts in favor of non-mailcow code (local changes over remote changes), not recommended!
|
||||
@@ -335,6 +377,7 @@ while (($#)); do
|
||||
--skip-ping-check - Skip ICMP Check to public DNS resolvers (Use it only if you´ve blocked any ICMP Connections to your mailcow machine)
|
||||
--stable - Switch your mailcow updates to the stable (master) branch. Default unless you changed it with --nightly.
|
||||
-f|--force - Force update, do not ask questions
|
||||
-d|--dev - Enables Developer Mode (No Checkout of update.sh for tests)
|
||||
'
|
||||
exit 1
|
||||
esac
|
||||
@@ -594,7 +637,7 @@ for option in ${CONFIG_ARRAY[@]}; do
|
||||
echo "Adding new option \"${option}\" to mailcow.conf"
|
||||
echo '# Password hash algorithm' >> mailcow.conf
|
||||
echo '# Only certain password hash algorithm are supported. For a fully list of supported schemes,' >> mailcow.conf
|
||||
echo '# see https://mailcow.github.io/mailcow-dockerized-docs/models/model-passwd/' >> mailcow.conf
|
||||
echo '# see https://docs.mailcow.email/models/model-passwd/' >> mailcow.conf
|
||||
echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf
|
||||
fi
|
||||
elif [[ ${option} == "ADDITIONAL_SERVER_NAMES" ]]; then
|
||||
@@ -614,7 +657,7 @@ for option in ${CONFIG_ARRAY[@]}; do
|
||||
echo '# Optional: Leave empty for none' >> mailcow.conf
|
||||
echo '# This value is only used on first order!' >> mailcow.conf
|
||||
echo '# Setting it at a later point will require the following steps:' >> mailcow.conf
|
||||
echo '# https://mailcow.github.io/mailcow-dockerized-docs/troubleshooting/debug-reset_tls/' >> mailcow.conf
|
||||
echo '# https://docs.mailcow.email/troubleshooting/debug-reset_tls/' >> mailcow.conf
|
||||
echo 'ACME_CONTACT=' >> mailcow.conf
|
||||
fi
|
||||
elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then
|
||||
@@ -724,15 +767,17 @@ elif [ $NEW_BRANCH == "nightly" ] && [ $CURRENT_BRANCH != "nightly" ]; then
|
||||
git checkout -f ${BRANCH}
|
||||
fi
|
||||
|
||||
echo -e "\e[32mChecking for newer update script...\e[0m"
|
||||
SHA1_1=$(sha1sum update.sh)
|
||||
git fetch origin #${BRANCH}
|
||||
git checkout origin/${BRANCH} update.sh
|
||||
SHA1_2=$(sha1sum update.sh)
|
||||
if [[ ${SHA1_1} != ${SHA1_2} ]]; then
|
||||
if [ ! $DEV ]; then
|
||||
echo -e "\e[32mChecking for newer update script...\e[0m"
|
||||
SHA1_1=$(sha1sum update.sh)
|
||||
git fetch origin #${BRANCH}
|
||||
git checkout origin/${BRANCH} update.sh
|
||||
SHA1_2=$(sha1sum update.sh)
|
||||
if [[ ${SHA1_1} != ${SHA1_2} ]]; then
|
||||
echo "update.sh changed, please run this script again, exiting."
|
||||
chmod +x update.sh
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! $FORCE ]; then
|
||||
@@ -899,9 +944,6 @@ else
|
||||
echo -e "\e[33mCannot determine current git repository version...\e[0m"
|
||||
fi
|
||||
|
||||
# Set DOCKER_COMPOSE_VERSION
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=$/DOCKER_COMPOSE_VERSION='$DOCKER_COMPOSE_VERSION'/g' mailcow.conf
|
||||
|
||||
if [[ ${SKIP_START} == "y" ]]; then
|
||||
echo -e "\e[33mNot starting mailcow, please run \"$COMPOSE_COMMAND up -d --remove-orphans\" to start mailcow.\e[0m"
|
||||
else
|
||||
|
Reference in New Issue
Block a user