mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-06-13 18:10:26 +00:00
Compare commits
283 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a80b5b7dd0 | |||
| 392967d664 | |||
| 3b3c2b7141 | |||
| f55c3c0887 | |||
| f423ad77f3 | |||
| 8ba1e1ba9e | |||
| 55576084fc | |||
| 03311b06c9 | |||
| b5c3d01834 | |||
| f398ecbe39 | |||
| 8f1ae0f099 | |||
| c8bee57732 | |||
| 85641794c3 | |||
| 849decaa59 | |||
| 6e88550f92 | |||
| 7c52483887 | |||
| 0aa520c030 | |||
| 548999f163 | |||
| 63df547306 | |||
| 547d2ca308 | |||
| 46b995f9e3 | |||
| 1fdf704cb4 | |||
| 5ec9c4c750 | |||
| afed94cc0e | |||
| 6f48c5ace0 | |||
| 9a7e1c2b5a | |||
| 2ef7539d55 | |||
| 4e52542e33 | |||
| a1895ad924 | |||
| d5a2c96887 | |||
| 3f30fe3113 | |||
| d89f24a1a3 | |||
| 413354ff29 | |||
| a28ba5bebb | |||
| b93375b671 | |||
| b568a33581 | |||
| b05ef8edac | |||
| 015f9b663f | |||
| b6167257c9 | |||
| 687fe044b2 | |||
| cfa47eb873 | |||
| 7079000ee0 | |||
| f60c4f39ee | |||
| 473713219f | |||
| 03ed81dc3f | |||
| 53543ccf26 | |||
| 6c6fde8e2e | |||
| 61e23b6b81 | |||
| 6c649debc9 | |||
| 87b0683f77 | |||
| 59c1e7a18a | |||
| 4f9dad5dd3 | |||
| adc6a0054c | |||
| 8a70cdb48b | |||
| bb4bc11383 | |||
| a366494c34 | |||
| 99de302ec9 | |||
| 907912046f | |||
| 2c0d379dc5 | |||
| 5b8efeb2ba | |||
| f1c93fa337 | |||
| a94a29a6ac | |||
| 7e3d736ee1 | |||
| 437534556e | |||
| ce4b9c98dc | |||
| c134078d60 | |||
| a8bc6aff2e | |||
| 0b627017e0 | |||
| eb3be80286 | |||
| 1fda71e4fa | |||
| a02bd4beff | |||
| d7f3ee16aa | |||
| 87e3c91c26 | |||
| 33a38e6fde | |||
| 3d8f45db43 | |||
| 40df25dcf0 | |||
| 5de151a966 | |||
| 115d0681a7 | |||
| 1c403a6d60 | |||
| e67ba60863 | |||
| 0c0ec7be58 | |||
| a72b3689b0 | |||
| c4c76e0945 | |||
| 1a793e0b7e | |||
| d0562ddbd9 | |||
| 3851a48ea0 | |||
| 40dcf86846 | |||
| 257e104d2b | |||
| 3f2a9b6973 | |||
| ed365c35e7 | |||
| 24ff70759a | |||
| c55c38f77b | |||
| 934bc15fae | |||
| c2c994bfbb | |||
| b1c2ffba6e | |||
| b4a56052c5 | |||
| 69d15df221 | |||
| e5752755d1 | |||
| d98cfe0fc7 | |||
| 1a1955c1c2 | |||
| 0303dbc1d2 | |||
| acee742822 | |||
| 8d792fbd62 | |||
| d132a51a4d | |||
| 2111115a73 | |||
| 160c9caee3 | |||
| 33de788453 | |||
| f86f5657d9 | |||
| e02a92a0d0 | |||
| 5ae9605e77 | |||
| 88fbec1e53 | |||
| d098e7b9e6 | |||
| a8930e8060 | |||
| e26501261e | |||
| 89bc11ce0f | |||
| 4b096962a9 | |||
| c64fdf9aa3 | |||
| 9caaaa6498 | |||
| 105a7a4c74 | |||
| 09782e5b47 | |||
| 8d75b570c8 | |||
| 21121f9827 | |||
| 8e87e76dcf | |||
| 2629f3d865 | |||
| 8e5cd90707 | |||
| 9ffa810054 | |||
| db9562e843 | |||
| 3540075b61 | |||
| d0ba061f7a | |||
| 871ae5d7d2 | |||
| 633ebe5e8d | |||
| 1b7cc830ca | |||
| d48193fd0e | |||
| bb69f39976 | |||
| f059db54d0 | |||
| e4e8abb1b9 | |||
| 1a207f4d88 | |||
| 25d6e0bbd0 | |||
| 8e5323023a | |||
| 6d9805109a | |||
| 1822d56efb | |||
| 1e3766e2f1 | |||
| 718dcb69be | |||
| 372b1c7bbc | |||
| 9ba5c13702 | |||
| 30e241babe | |||
| 956b170674 | |||
| 2c52753adb | |||
| 095d59c01b | |||
| 1a2f145b28 | |||
| 930473a980 | |||
| 1db8990271 | |||
| 025fd03310 | |||
| e468c59dfc | |||
| 340ef866d2 | |||
| 533bd36572 | |||
| 5bf29e6ac1 | |||
| d6c3c58f42 | |||
| b050cb9864 | |||
| e176724775 | |||
| 8f9ed9e0df | |||
| 003eecf131 | |||
| 180b9fc8d2 | |||
| 5d3491c801 | |||
| c45684b986 | |||
| 5c886d2f4e | |||
| 9f39af46aa | |||
| 7cda9f063f | |||
| 5e7583c5e6 | |||
| a1fb962215 | |||
| 57d849a51b | |||
| 3000da6b88 | |||
| db75cbbcb0 | |||
| 22acbb6b57 | |||
| 31cb0f7db1 | |||
| 6d17b9f504 | |||
| 0f337971ff | |||
| 6cf2775e7e | |||
| dabf9104ed | |||
| 952ddb18fd | |||
| 34d990a800 | |||
| 020cb21b35 | |||
| 525364ba65 | |||
| 731fabef58 | |||
| c10be77a1b | |||
| a8bc4e3f37 | |||
| 815572f200 | |||
| 23fc54f2cf | |||
| 11407973b1 | |||
| b9867e3fe0 | |||
| 3814c3294f | |||
| 9c44b5e546 | |||
| cd635ec813 | |||
| 03831149f8 | |||
| d8fd023cdb | |||
| 521120a448 | |||
| ec8d298c36 | |||
| 03580cbf39 | |||
| 2b009c71c1 | |||
| b903cf3888 | |||
| cf239dd6b2 | |||
| a0723f60d2 | |||
| da8e496430 | |||
| 722134e474 | |||
| cb1a11e551 | |||
| 8984509f58 | |||
| 0f0d43b253 | |||
| 0f6956572e | |||
| 29892dc694 | |||
| 14265f3de8 | |||
| 0863bffdd2 | |||
| 3b748a30cc | |||
| 5619175108 | |||
| 6e9c024b3c | |||
| 8cd4ae1e34 | |||
| 689856b186 | |||
| 7b645303d6 | |||
| 408381bddb | |||
| 380cdab6fc | |||
| 03b7a8d639 | |||
| bf6a61fa2d | |||
| 1de47072f8 | |||
| c0c46b7cf5 | |||
| 42a91af7ac | |||
| 6e1ee638ff | |||
| 61c8afa088 | |||
| c873a14127 | |||
| 06cce79806 | |||
| 0927c5df57 | |||
| e691d2c782 | |||
| 67510adb9e | |||
| 490d553dfc | |||
| 70aab7568e | |||
| f82aba3e26 | |||
| f80940efdc | |||
| 6f875398c0 | |||
| 7a582afbdc | |||
| 38cd376228 | |||
| 74bcec45f1 | |||
| 9700b3251f | |||
| 88b8d50cd5 | |||
| 55b0191050 | |||
| 33c97fb318 | |||
| 23d33ad5a8 | |||
| bd6c98047a | |||
| 73d6a29ae1 | |||
| 173e39c859 | |||
| c0745c5cde | |||
| 1a6f93327e | |||
| 3c68a53170 | |||
| e38c27ed67 | |||
| 8eaf8bbbde | |||
| e015c7dbca | |||
| 58452abcdf | |||
| 2cbf0da137 | |||
| f295b8cd91 | |||
| 97a492b891 | |||
| aabcd10539 | |||
| ee607dc3cc | |||
| 1265302a8e | |||
| b5acf56e20 | |||
| 9752313d24 | |||
| fe4a418af4 | |||
| e5f03e8526 | |||
| fb60c4a150 | |||
| 6b82284a41 | |||
| 192f67cd41 | |||
| fd203abd47 | |||
| 6b65f0fc74 | |||
| 856b3b62f2 | |||
| 0372a2150d | |||
| f3322c0577 | |||
| c2bcc4e086 | |||
| 6e79c48640 | |||
| d7dfa95e1b | |||
| cf1cc24e33 | |||
| 6824a5650f | |||
| 73570cc8b5 | |||
| 959dcb9980 | |||
| 2eafd89412 | |||
| 1606658cb1 | |||
| 54ba66733e | |||
| f6847e6f8c |
@@ -1,7 +1,7 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ❓ Community-driven support
|
||||
url: https://mailcow.github.io/mailcow-dockerized-docs/#get-support
|
||||
url: https://docs.mailcow.email/#get-support
|
||||
about: Please use the community forum for questions or assistance
|
||||
- name: 🚨 Report a security vulnerability
|
||||
url: https://www.servercow.de/anfrage?lang=en
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"baseBranches": ["staging"],
|
||||
"enabledManagers": ["github-actions", "regex", "docker-compose"],
|
||||
"ignorePaths": [
|
||||
"data\/web\/inc\/lib\/vendor\/matthiasmullie\/minify\/**"
|
||||
"data\/web\/inc\/lib\/vendor\/**"
|
||||
],
|
||||
"regexManagers": [
|
||||
{
|
||||
@@ -24,7 +24,7 @@
|
||||
{
|
||||
"fileMatch": ["(^|/)Dockerfile[^/]*$"],
|
||||
"matchStrings": [
|
||||
"#\\srenovate:\\sdatasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?\\s(ENV|ARG) .*?_VERSION=(?<currentValue>.*)\\s"
|
||||
"#\\srenovate:\\sdatasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?( extractVersion=(?<extractVersion>.*?))?\\s(ENV|ARG) .*?_VERSION=(?<currentValue>.*)\\s"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
|
||||
steps:
|
||||
- name: Send message
|
||||
uses: thollander/actions-comment-pull-request@v2.3.1
|
||||
uses: thollander/actions-comment-pull-request@v2.4.3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
|
||||
message: |
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- "watchdog-mailcow"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Docker
|
||||
run: |
|
||||
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run the Action
|
||||
|
||||
@@ -11,24 +11,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: data/Dockerfiles/backup/Dockerfile
|
||||
push: true
|
||||
tags: mailcow/backup:latest
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Update postscreen_access.cidr
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Monthly
|
||||
- cron: "0 0 1 * *"
|
||||
workflow_dispatch: # Allow to run workflow manually
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
|
||||
jobs:
|
||||
Update-postscreen_access_cidr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate postscreen_access.cidr
|
||||
run: |
|
||||
bash helper-scripts/update_postscreen_whitelist.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
||||
commit-message: update postscreen_access.cidr
|
||||
committer: milkmaker <milkmaker@mailcow.de>
|
||||
author: milkmaker <milkmaker@mailcow.de>
|
||||
signoff: false
|
||||
branch: update/postscreen_access.cidr
|
||||
base: staging
|
||||
delete-branch: true
|
||||
add-paths: |
|
||||
data/conf/postfix/postscreen_access.cidr
|
||||
title: '[Postfix] update postscreen_access.cidr'
|
||||
body: |
|
||||
This PR updates the postscreen_access.cidr using GitHub Actions and [helper-scripts/update_postscreen_whitelist.sh](https://github.com/mailcow/mailcow-dockerized/blob/master/helper-scripts/update_postscreen_whitelist.sh)
|
||||
@@ -36,6 +36,8 @@ data/conf/postfix/extra.cf
|
||||
data/conf/postfix/sni.map
|
||||
data/conf/postfix/sni.map.db
|
||||
data/conf/postfix/sql
|
||||
data/conf/postfix/dns_blocklists.cf
|
||||
data/conf/postfix/dnsbl_reply.map
|
||||
data/conf/rspamd/custom/*
|
||||
data/conf/rspamd/local.d/*
|
||||
data/conf/rspamd/override.d/*
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ When a problem occurs, then always for a reason! What you want to do in such a c
|
||||
1. Read your logs; follow them to see what the reason for your problem is.
|
||||
2. Follow the leads given to you in your logfiles and start investigating.
|
||||
3. Restarting the troubled service or the whole stack to see if the problem persists.
|
||||
4. Read the [documentation](https://mailcow.github.io/mailcow-dockerized-docs/) of the troubled service and search its bugtracker for your problem.
|
||||
4. Read the [documentation](https://docs.mailcow.email/) of the troubled service and search its bugtracker for your problem.
|
||||
5. Search our [issues](https://github.com/mailcow/mailcow-dockerized/issues) for your problem.
|
||||
6. [Create an issue](https://github.com/mailcow/mailcow-dockerized/issues/new/choose) over at our GitHub repository if you think your problem might be a bug or a missing feature you badly need. But please make sure, that you include **all the logs** and a full description to your problem.
|
||||
7. Ask your questions in our community-driven [support channels](https://mailcow.github.io/mailcow-dockerized-docs/#community-support-and-chat).
|
||||
7. Ask your questions in our community-driven [support channels](https://docs.mailcow.email/#community-support-and-chat).
|
||||
|
||||
@@ -13,7 +13,7 @@ Or just spread the word: moo.
|
||||
|
||||
## Info, documentation and support
|
||||
|
||||
Please see [the official documentation](https://mailcow.github.io/mailcow-dockerized-docs/) for installation and support instructions. 🐄
|
||||
Please see [the official documentation](https://docs.mailcow.email/) for installation and support instructions. 🐄
|
||||
|
||||
🐛 **If you found a critical security issue, please mail us to [info at servercow.de](mailto:info@servercow.de).**
|
||||
|
||||
@@ -25,7 +25,7 @@ Please see [the official documentation](https://mailcow.github.io/mailcow-docker
|
||||
|
||||
[Telegram mailcow Off-Topic channel](https://t.me/mailcowOfftopic)
|
||||
|
||||
[Official Twitter Account](https://twitter.com/mailcow_email)
|
||||
[Official 𝕏 (Twitter) Account](https://twitter.com/mailcow_email)
|
||||
|
||||
Telegram desktop clients are available for [multiple platforms](https://desktop.telegram.org). You can search the groups history for keywords.
|
||||
|
||||
@@ -38,4 +38,4 @@ mailcow is a registered word mark of The Infrastructure Company GmbH, Parkstr. 4
|
||||
|
||||
The project is managed and maintained by The Infrastructure Company GmbH.
|
||||
|
||||
Originated from @andryyy (André)
|
||||
Originated from @andryyy (André)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM alpine:3.17
|
||||
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
RUN apk upgrade --no-cache \
|
||||
&& apk add --update --no-cache \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM clamav/clamav:1.0.1-1_base
|
||||
FROM clamav/clamav:1.0.3_base
|
||||
|
||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
RUN apk upgrade --no-cache \
|
||||
&& apk add --update --no-cache \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM alpine:3.17
|
||||
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -14,9 +14,12 @@ RUN apk add --update --no-cache python3 \
|
||||
uvicorn \
|
||||
aiodocker \
|
||||
docker \
|
||||
redis
|
||||
aioredis
|
||||
RUN mkdir /app/modules
|
||||
|
||||
COPY docker-entrypoint.sh /app/
|
||||
COPY dockerapi.py /app/
|
||||
COPY main.py /app/main.py
|
||||
COPY modules/ /app/modules/
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
|
||||
CMD exec python main.py
|
||||
@@ -6,4 +6,4 @@
|
||||
-subj /CN=dockerapi/O=mailcow \
|
||||
-addext subjectAltName=DNS:dockerapi`
|
||||
|
||||
`uvicorn --host 0.0.0.0 --port 443 --ssl-certfile=/app/dockerapi_cert.pem --ssl-keyfile=/app/dockerapi_key.pem dockerapi:app`
|
||||
exec "$@"
|
||||
|
||||
@@ -1,547 +0,0 @@
|
||||
from fastapi import FastAPI, Response, Request
|
||||
import aiodocker
|
||||
import docker
|
||||
import psutil
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
import os
|
||||
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")
|
||||
async def get_host_update_stats():
|
||||
global host_stats_isUpdating
|
||||
|
||||
if host_stats_isUpdating == False:
|
||||
asyncio.create_task(get_host_stats())
|
||||
host_stats_isUpdating = True
|
||||
|
||||
while True:
|
||||
if redis_client.exists('host_stats'):
|
||||
break
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
|
||||
stats = json.loads(redis_client.get('host_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
@app.get("/containers/{container_id}/json")
|
||||
async def get_container(container_id : str):
|
||||
if container_id and container_id.isalnum():
|
||||
try:
|
||||
for container in (await async_docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
container_info = await container.show()
|
||||
return Response(content=json.dumps(container_info, indent=4), media_type="application/json")
|
||||
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "no container found"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "no or invalid id defined"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
@app.get("/containers/json")
|
||||
async def get_containers():
|
||||
containers = {}
|
||||
try:
|
||||
for container in (await async_docker_client.containers.list()):
|
||||
container_info = await container.show()
|
||||
containers.update({container_info['Id']: container_info})
|
||||
return Response(content=json.dumps(containers, indent=4), media_type="application/json")
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
@app.post("/containers/{container_id}/{post_action}")
|
||||
async def post_containers(container_id : str, post_action : str, request: Request):
|
||||
try :
|
||||
request_json = await request.json()
|
||||
except Exception as err:
|
||||
request_json = {}
|
||||
|
||||
if container_id and container_id.isalnum() and post_action:
|
||||
try:
|
||||
"""Dispatch container_post api call"""
|
||||
if post_action == 'exec':
|
||||
if not request_json or not 'cmd' in request_json:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "cmd is missing"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
if not request_json or not 'task' in request_json:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "task is missing"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ])
|
||||
else:
|
||||
api_call_method_name = '__'.join(['container_post', str(post_action) ])
|
||||
|
||||
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"))
|
||||
|
||||
|
||||
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:
|
||||
logger.error("error - container_post: %s" % str(e))
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
else:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "invalid container id or missing action"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
@app.post("/container/{container_id}/stats/update")
|
||||
async def post_container_update_stats(container_id : str):
|
||||
global containerIds_to_update
|
||||
|
||||
# start update task for container if no task is running
|
||||
if container_id not in containerIds_to_update:
|
||||
asyncio.create_task(get_container_stats(container_id))
|
||||
containerIds_to_update.append(container_id)
|
||||
|
||||
while True:
|
||||
if redis_client.exists(container_id + '_stats'):
|
||||
break
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
stats = json.loads(redis_client.get(container_id + '_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
|
||||
|
||||
class DockerUtils:
|
||||
def __init__(self, docker_client):
|
||||
self.docker_client = docker_client
|
||||
|
||||
# api call: container_post - post_action: stop
|
||||
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
|
||||
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
|
||||
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
|
||||
def container_post__top(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
|
||||
res = { 'type': 'success', 'msg': container.top()}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: stats
|
||||
def container_post__stats(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
|
||||
for stat in container.stats(decode=True, stream=True):
|
||||
res = { 'type': 'success', 'msg': stat}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: delete
|
||||
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));
|
||||
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: hold
|
||||
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 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
|
||||
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));
|
||||
|
||||
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
|
||||
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 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
|
||||
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 self.docker_client.containers.list(filters={"id": container_id}):
|
||||
for i in flagged_qids:
|
||||
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
|
||||
# todo: check each exit code
|
||||
res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: list
|
||||
def container_post__exec__mailq__list(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
|
||||
return exec_run_handler('utf8_text_only', mailq_return)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: flush
|
||||
def container_post__exec__mailq__flush(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
|
||||
return exec_run_handler('generic', postqueue_r)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete
|
||||
def container_post__exec__mailq__super_delete(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
|
||||
return exec_run_handler('generic', postsuper_r)
|
||||
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan
|
||||
def container_post__exec__system__fts_rescan(self, container_id, request_json):
|
||||
if 'username' in request_json:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
|
||||
if rescan_return.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
if 'all' in request_json:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
|
||||
if rescan_return.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: exec - cmd: system - task: df
|
||||
def container_post__exec__system__df(self, container_id, request_json):
|
||||
if 'dir' in request_json:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
|
||||
if df_return.exit_code == 0:
|
||||
return df_return.output.decode('utf-8').rstrip()
|
||||
else:
|
||||
return "0,0,0,0,0,0"
|
||||
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
|
||||
def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
|
||||
if sql_return.exit_code == 0:
|
||||
matched = False
|
||||
for line in sql_return.output.decode('utf-8').split("\n"):
|
||||
if 'is already upgraded to' in line:
|
||||
matched = True
|
||||
if matched:
|
||||
res = { 'type': 'success', 'msg':'mysql_upgrade: already upgraded', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
container.restart()
|
||||
res = { 'type': 'warning', 'msg':'mysql_upgrade: upgrade was applied', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'error', 'msg': 'mysql_upgrade: error running command', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql
|
||||
def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
|
||||
if sql_return.exit_code == 0:
|
||||
res = { 'type': 'info', 'msg': 'mysql_tzinfo_to_sql: command completed successfully', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'error', 'msg': 'mysql_tzinfo_to_sql: error running command', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: exec - cmd: reload - task: dovecot
|
||||
def container_post__exec__reload__dovecot(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
|
||||
return exec_run_handler('generic', reload_return)
|
||||
# api call: container_post - post_action: exec - cmd: reload - task: postfix
|
||||
def container_post__exec__reload__postfix(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
|
||||
return exec_run_handler('generic', reload_return)
|
||||
# api call: container_post - post_action: exec - cmd: reload - task: nginx
|
||||
def container_post__exec__reload__nginx(self, container_id, request_json):
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
|
||||
return exec_run_handler('generic', reload_return)
|
||||
# api call: container_post - post_action: exec - cmd: sieve - task: list
|
||||
def container_post__exec__sieve__list(self, container_id, request_json):
|
||||
if 'username' in request_json:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
|
||||
return exec_run_handler('utf8_text_only', sieve_return)
|
||||
# api call: container_post - post_action: exec - cmd: sieve - task: print
|
||||
def container_post__exec__sieve__print(self, container_id, request_json):
|
||||
if 'username' in request.json and 'script_name' in request_json:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
|
||||
sieve_return = container.exec_run(cmd)
|
||||
return exec_run_handler('utf8_text_only', sieve_return)
|
||||
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup
|
||||
def container_post__exec__maildir__cleanup(self, container_id, request_json):
|
||||
if 'maildir' in request_json:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
sane_name = re.sub(r'\W+', '', request_json['maildir'])
|
||||
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
|
||||
def container_post__exec__rspamd__worker_password(self, container_id, request_json):
|
||||
if 'raw' in request_json:
|
||||
for container in self.docker_client.containers.list(filters={"id": container_id}):
|
||||
cmd = "/usr/bin/rspamadm pw -e -p '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
|
||||
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
|
||||
|
||||
matched = False
|
||||
for line in cmd_response.split("\n"):
|
||||
if '$2$' in line:
|
||||
hash = line.strip()
|
||||
hash_out = re.search('\$2\$.+$', hash).group(0)
|
||||
rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
|
||||
rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
|
||||
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
|
||||
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
|
||||
if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
|
||||
container.restart()
|
||||
matched = True
|
||||
if matched:
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully' }
|
||||
logger.info('success changing Rspamd password')
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
logger.error('failed changing Rspamd password')
|
||||
res = { 'type': 'danger', 'msg': 'command did not complete' }
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
|
||||
def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
|
||||
|
||||
def recv_socket_data(c_socket, timeout):
|
||||
c_socket.setblocking(0)
|
||||
total_data=[]
|
||||
data=''
|
||||
begin=time.time()
|
||||
while True:
|
||||
if total_data and time.time()-begin > timeout:
|
||||
break
|
||||
elif time.time()-begin > timeout*2:
|
||||
break
|
||||
try:
|
||||
data = c_socket.recv(8192)
|
||||
if data:
|
||||
total_data.append(data.decode('utf-8'))
|
||||
#change the beginning time for measurement
|
||||
begin=time.time()
|
||||
else:
|
||||
#sleep for sometime to indicate a gap
|
||||
time.sleep(0.1)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
return ''.join(total_data)
|
||||
|
||||
|
||||
try :
|
||||
socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock
|
||||
if not cmd.endswith("\n"):
|
||||
cmd = cmd + "\n"
|
||||
socket.send(cmd.encode('utf-8'))
|
||||
data = recv_socket_data(socket, timeout)
|
||||
socket.close()
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.error("error - exec_cmd_container: %s" % str(e))
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
def exec_run_handler(type, output):
|
||||
if type == 'generic':
|
||||
if output.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully' }
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': '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=output.output.decode('utf-8'), media_type="text/plain")
|
||||
|
||||
async def get_host_stats(wait=5):
|
||||
global host_stats_isUpdating
|
||||
|
||||
try:
|
||||
system_time = datetime.now()
|
||||
host_stats = {
|
||||
"cpu": {
|
||||
"cores": psutil.cpu_count(),
|
||||
"usage": psutil.cpu_percent()
|
||||
},
|
||||
"memory": {
|
||||
"total": psutil.virtual_memory().total,
|
||||
"usage": psutil.virtual_memory().percent,
|
||||
"swap": psutil.swap_memory()
|
||||
},
|
||||
"uptime": time.time() - psutil.boot_time(),
|
||||
"system_time": system_time.strftime("%d.%m.%Y %H:%M:%S")
|
||||
}
|
||||
|
||||
redis_client.set('host_stats', json.dumps(host_stats), ex=10)
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
|
||||
await asyncio.sleep(wait)
|
||||
host_stats_isUpdating = False
|
||||
|
||||
async def get_container_stats(container_id, wait=5, stop=False):
|
||||
global containerIds_to_update
|
||||
|
||||
if container_id and container_id.isalnum():
|
||||
try:
|
||||
for container in (await async_docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
res = await container.stats(stream=False)
|
||||
|
||||
if redis_client.exists(container_id + '_stats'):
|
||||
stats = json.loads(redis_client.get(container_id + '_stats'))
|
||||
else:
|
||||
stats = []
|
||||
stats.append(res[0])
|
||||
if len(stats) > 3:
|
||||
del stats[0]
|
||||
redis_client.set(container_id + '_stats', json.dumps(stats), ex=60)
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
else:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "no or invalid id defined"
|
||||
}
|
||||
|
||||
await asyncio.sleep(wait)
|
||||
if stop == True:
|
||||
# update task was called second time, stop
|
||||
containerIds_to_update.remove(container_id)
|
||||
else:
|
||||
# call update task a second time
|
||||
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')
|
||||
@@ -0,0 +1,260 @@
|
||||
import os
|
||||
import sys
|
||||
import uvicorn
|
||||
import json
|
||||
import uuid
|
||||
import async_timeout
|
||||
import asyncio
|
||||
import aioredis
|
||||
import aiodocker
|
||||
import docker
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
from fastapi import FastAPI, Response, Request
|
||||
from modules.DockerApi import DockerApi
|
||||
|
||||
dockerapi = None
|
||||
app = FastAPI()
|
||||
|
||||
# Define Routes
|
||||
@app.get("/host/stats")
|
||||
async def get_host_update_stats():
|
||||
global dockerapi
|
||||
|
||||
if dockerapi.host_stats_isUpdating == False:
|
||||
asyncio.create_task(dockerapi.get_host_stats())
|
||||
dockerapi.host_stats_isUpdating = True
|
||||
|
||||
while True:
|
||||
if await dockerapi.redis_client.exists('host_stats'):
|
||||
break
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
stats = json.loads(await dockerapi.redis_client.get('host_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
@app.get("/containers/{container_id}/json")
|
||||
async def get_container(container_id : str):
|
||||
global dockerapi
|
||||
|
||||
if container_id and container_id.isalnum():
|
||||
try:
|
||||
for container in (await dockerapi.async_docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
container_info = await container.show()
|
||||
return Response(content=json.dumps(container_info, indent=4), media_type="application/json")
|
||||
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "no container found"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "no or invalid id defined"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
@app.get("/containers/json")
|
||||
async def get_containers():
|
||||
global dockerapi
|
||||
|
||||
containers = {}
|
||||
try:
|
||||
for container in (await dockerapi.async_docker_client.containers.list()):
|
||||
container_info = await container.show()
|
||||
containers.update({container_info['Id']: container_info})
|
||||
return Response(content=json.dumps(containers, indent=4), media_type="application/json")
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
@app.post("/containers/{container_id}/{post_action}")
|
||||
async def post_containers(container_id : str, post_action : str, request: Request):
|
||||
global dockerapi
|
||||
|
||||
try :
|
||||
request_json = await request.json()
|
||||
except Exception as err:
|
||||
request_json = {}
|
||||
|
||||
if container_id and container_id.isalnum() and post_action:
|
||||
try:
|
||||
"""Dispatch container_post api call"""
|
||||
if post_action == 'exec':
|
||||
if not request_json or not 'cmd' in request_json:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "cmd is missing"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
if not request_json or not 'task' in request_json:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "task is missing"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ])
|
||||
else:
|
||||
api_call_method_name = '__'.join(['container_post', str(post_action) ])
|
||||
|
||||
api_call_method = getattr(dockerapi, 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"))
|
||||
|
||||
dockerapi.logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id))
|
||||
return api_call_method(request_json, container_id=container_id)
|
||||
except Exception as e:
|
||||
dockerapi.logger.error("error - container_post: %s" % str(e))
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
else:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "invalid container id or missing action"
|
||||
}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
|
||||
@app.post("/container/{container_id}/stats/update")
|
||||
async def post_container_update_stats(container_id : str):
|
||||
global dockerapi
|
||||
|
||||
# start update task for container if no task is running
|
||||
if container_id not in dockerapi.containerIds_to_update:
|
||||
asyncio.create_task(dockerapi.get_container_stats(container_id))
|
||||
dockerapi.containerIds_to_update.append(container_id)
|
||||
|
||||
while True:
|
||||
if await dockerapi.redis_client.exists(container_id + '_stats'):
|
||||
break
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
# Events
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
global dockerapi
|
||||
|
||||
# Initialize a custom logger
|
||||
logger = logging.getLogger("dockerapi")
|
||||
logger.setLevel(logging.INFO)
|
||||
# Configure the logger to output logs to the terminal
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
logger.info("Init APP")
|
||||
|
||||
# Init redis client
|
||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
||||
redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0")
|
||||
else:
|
||||
redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0")
|
||||
|
||||
# Init docker clients
|
||||
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')
|
||||
|
||||
dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger)
|
||||
|
||||
logger.info("Subscribe to redis channel")
|
||||
# Subscribe to redis channel
|
||||
dockerapi.pubsub = redis.pubsub()
|
||||
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
||||
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
global dockerapi
|
||||
|
||||
# Close docker connections
|
||||
dockerapi.sync_docker_client.close()
|
||||
await dockerapi.async_docker_client.close()
|
||||
|
||||
# Close redis
|
||||
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
||||
await dockerapi.redis_client.close()
|
||||
|
||||
# PubSub Handler
|
||||
async def handle_pubsub_messages(channel: aioredis.client.PubSub):
|
||||
global dockerapi
|
||||
|
||||
while True:
|
||||
try:
|
||||
async with async_timeout.timeout(60):
|
||||
message = await channel.get_message(ignore_subscribe_messages=True, timeout=30)
|
||||
if message is not None:
|
||||
# Parse message
|
||||
data_json = json.loads(message['data'].decode('utf-8'))
|
||||
dockerapi.logger.info(f"PubSub Received - {json.dumps(data_json)}")
|
||||
|
||||
# Handle api_call
|
||||
if 'api_call' in data_json:
|
||||
# api_call: container_post
|
||||
if data_json['api_call'] == "container_post":
|
||||
if 'post_action' in data_json and 'container_name' in data_json:
|
||||
try:
|
||||
"""Dispatch container_post api call"""
|
||||
request_json = {}
|
||||
if data_json['post_action'] == 'exec':
|
||||
if 'request' in data_json:
|
||||
request_json = data_json['request']
|
||||
if 'cmd' in request_json:
|
||||
if 'task' in request_json:
|
||||
api_call_method_name = '__'.join(['container_post', str(data_json['post_action']), str(request_json['cmd']), str(request_json['task']) ])
|
||||
else:
|
||||
dockerapi.logger.error("api call: task missing")
|
||||
else:
|
||||
dockerapi.logger.error("api call: cmd missing")
|
||||
else:
|
||||
dockerapi.logger.error("api call: request missing")
|
||||
else:
|
||||
api_call_method_name = '__'.join(['container_post', str(data_json['post_action'])])
|
||||
|
||||
if api_call_method_name:
|
||||
api_call_method = getattr(dockerapi, api_call_method_name)
|
||||
if api_call_method:
|
||||
dockerapi.logger.info("api call: %s, container_name: %s" % (api_call_method_name, data_json['container_name']))
|
||||
api_call_method(request_json, container_name=data_json['container_name'])
|
||||
else:
|
||||
dockerapi.logger.error("api call not found: %s, container_name: %s" % (api_call_method_name, data_json['container_name']))
|
||||
except Exception as e:
|
||||
dockerapi.logger.error("container_post: %s" % str(e))
|
||||
else:
|
||||
dockerapi.logger.error("api call: missing container_name, post_action or request")
|
||||
else:
|
||||
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
|
||||
else:
|
||||
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
|
||||
|
||||
await asyncio.sleep(0.0)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(
|
||||
app,
|
||||
host="0.0.0.0",
|
||||
port=443,
|
||||
ssl_certfile="/app/dockerapi_cert.pem",
|
||||
ssl_keyfile="/app/dockerapi_key.pem",
|
||||
log_level="info",
|
||||
loop="none"
|
||||
)
|
||||
@@ -0,0 +1,487 @@
|
||||
import psutil
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
import asyncio
|
||||
import platform
|
||||
from datetime import datetime
|
||||
from fastapi import FastAPI, Response, Request
|
||||
|
||||
class DockerApi:
|
||||
def __init__(self, redis_client, sync_docker_client, async_docker_client, logger):
|
||||
self.redis_client = redis_client
|
||||
self.sync_docker_client = sync_docker_client
|
||||
self.async_docker_client = async_docker_client
|
||||
self.logger = logger
|
||||
|
||||
self.host_stats_isUpdating = False
|
||||
self.containerIds_to_update = []
|
||||
|
||||
# api call: container_post - post_action: stop
|
||||
def container_post__stop(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||
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
|
||||
def container_post__start(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||
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
|
||||
def container_post__restart(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||
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
|
||||
def container_post__top(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||
res = { 'type': 'success', 'msg': container.top()}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: stats
|
||||
def container_post__stats(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||
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
|
||||
def container_post__exec__mailq__delete(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
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))
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return self.exec_run_handler('generic', postsuper_r)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: hold
|
||||
def container_post__exec__mailq__hold(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
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 self.sync_docker_client.containers.list(filters=filters):
|
||||
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return self.exec_run_handler('generic', postsuper_r)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: cat
|
||||
def container_post__exec__mailq__cat(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
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))
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
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 self.exec_run_handler('utf8_text_only', postcat_return)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: unhold
|
||||
def container_post__exec__mailq__unhold(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
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 self.sync_docker_client.containers.list(filters=filters):
|
||||
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||
return self.exec_run_handler('generic', postsuper_r)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: deliver
|
||||
def container_post__exec__mailq__deliver(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
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 self.sync_docker_client.containers.list(filters=filters):
|
||||
for i in flagged_qids:
|
||||
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
|
||||
# todo: check each exit code
|
||||
res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: list
|
||||
def container_post__exec__mailq__list(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
|
||||
return self.exec_run_handler('utf8_text_only', mailq_return)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: flush
|
||||
def container_post__exec__mailq__flush(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
|
||||
return self.exec_run_handler('generic', postqueue_r)
|
||||
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete
|
||||
def container_post__exec__mailq__super_delete(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
|
||||
return self.exec_run_handler('generic', postsuper_r)
|
||||
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan
|
||||
def container_post__exec__system__fts_rescan(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
if 'username' in request_json:
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
|
||||
if rescan_return.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
if 'all' in request_json:
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
|
||||
if rescan_return.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: exec - cmd: system - task: df
|
||||
def container_post__exec__system__df(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
if 'dir' in request_json:
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
|
||||
if df_return.exit_code == 0:
|
||||
return df_return.output.decode('utf-8').rstrip()
|
||||
else:
|
||||
return "0,0,0,0,0,0"
|
||||
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
|
||||
def container_post__exec__system__mysql_upgrade(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
|
||||
if sql_return.exit_code == 0:
|
||||
matched = False
|
||||
for line in sql_return.output.decode('utf-8').split("\n"):
|
||||
if 'is already upgraded to' in line:
|
||||
matched = True
|
||||
if matched:
|
||||
res = { 'type': 'success', 'msg':'mysql_upgrade: already upgraded', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
container.restart()
|
||||
res = { 'type': 'warning', 'msg':'mysql_upgrade: upgrade was applied', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'error', 'msg': 'mysql_upgrade: error running command', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql
|
||||
def container_post__exec__system__mysql_tzinfo_to_sql(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
|
||||
if sql_return.exit_code == 0:
|
||||
res = { 'type': 'info', 'msg': 'mysql_tzinfo_to_sql: command completed successfully', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': 'error', 'msg': 'mysql_tzinfo_to_sql: error running command', 'text': sql_return.output.decode('utf-8')}
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
# api call: container_post - post_action: exec - cmd: reload - task: dovecot
|
||||
def container_post__exec__reload__dovecot(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
|
||||
return self.exec_run_handler('generic', reload_return)
|
||||
# api call: container_post - post_action: exec - cmd: reload - task: postfix
|
||||
def container_post__exec__reload__postfix(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
|
||||
return self.exec_run_handler('generic', reload_return)
|
||||
# api call: container_post - post_action: exec - cmd: reload - task: nginx
|
||||
def container_post__exec__reload__nginx(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
|
||||
return self.exec_run_handler('generic', reload_return)
|
||||
# api call: container_post - post_action: exec - cmd: sieve - task: list
|
||||
def container_post__exec__sieve__list(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
if 'username' in request_json:
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
|
||||
return self.exec_run_handler('utf8_text_only', sieve_return)
|
||||
# api call: container_post - post_action: exec - cmd: sieve - task: print
|
||||
def container_post__exec__sieve__print(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
if 'username' in request_json and 'script_name' in request_json:
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
|
||||
sieve_return = container.exec_run(cmd)
|
||||
return self.exec_run_handler('utf8_text_only', sieve_return)
|
||||
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup
|
||||
def container_post__exec__maildir__cleanup(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
if 'maildir' in request_json:
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
sane_name = re.sub(r'\W+', '', request_json['maildir'])
|
||||
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 self.exec_run_handler('generic', maildir_cleanup)
|
||||
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
|
||||
def container_post__exec__rspamd__worker_password(self, request_json, **kwargs):
|
||||
if 'container_id' in kwargs:
|
||||
filters = {"id": kwargs['container_id']}
|
||||
elif 'container_name' in kwargs:
|
||||
filters = {"name": kwargs['container_name']}
|
||||
|
||||
if 'raw' in request_json:
|
||||
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||
cmd = "/usr/bin/rspamadm pw -e -p '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
|
||||
cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd")
|
||||
|
||||
matched = False
|
||||
for line in cmd_response.split("\n"):
|
||||
if '$2$' in line:
|
||||
hash = line.strip()
|
||||
hash_out = re.search('\$2\$.+$', hash).group(0)
|
||||
rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
|
||||
rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
|
||||
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
|
||||
cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd")
|
||||
if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
|
||||
container.restart()
|
||||
matched = True
|
||||
if matched:
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully' }
|
||||
self.logger.info('success changing Rspamd password')
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
self.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")
|
||||
|
||||
# Collect host stats
|
||||
async def get_host_stats(self, wait=5):
|
||||
try:
|
||||
system_time = datetime.now()
|
||||
host_stats = {
|
||||
"cpu": {
|
||||
"cores": psutil.cpu_count(),
|
||||
"usage": psutil.cpu_percent()
|
||||
},
|
||||
"memory": {
|
||||
"total": psutil.virtual_memory().total,
|
||||
"usage": psutil.virtual_memory().percent,
|
||||
"swap": psutil.swap_memory()
|
||||
},
|
||||
"uptime": time.time() - psutil.boot_time(),
|
||||
"system_time": system_time.strftime("%d.%m.%Y %H:%M:%S"),
|
||||
"architecture": platform.machine()
|
||||
}
|
||||
|
||||
await self.redis_client.set('host_stats', json.dumps(host_stats), ex=10)
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
|
||||
await asyncio.sleep(wait)
|
||||
self.host_stats_isUpdating = False
|
||||
# Collect container stats
|
||||
async def get_container_stats(self, container_id, wait=5, stop=False):
|
||||
if container_id and container_id.isalnum():
|
||||
try:
|
||||
for container in (await self.async_docker_client.containers.list()):
|
||||
if container._id == container_id:
|
||||
res = await container.stats(stream=False)
|
||||
|
||||
if await self.redis_client.exists(container_id + '_stats'):
|
||||
stats = json.loads(await self.redis_client.get(container_id + '_stats'))
|
||||
else:
|
||||
stats = []
|
||||
stats.append(res[0])
|
||||
if len(stats) > 3:
|
||||
del stats[0]
|
||||
await self.redis_client.set(container_id + '_stats', json.dumps(stats), ex=60)
|
||||
except Exception as e:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": str(e)
|
||||
}
|
||||
else:
|
||||
res = {
|
||||
"type": "danger",
|
||||
"msg": "no or invalid id defined"
|
||||
}
|
||||
|
||||
await asyncio.sleep(wait)
|
||||
if stop == True:
|
||||
# update task was called second time, stop
|
||||
self.containerIds_to_update.remove(container_id)
|
||||
else:
|
||||
# call update task a second time
|
||||
await self.get_container_stats(container_id, wait=0, stop=True)
|
||||
|
||||
def exec_cmd_container(self, container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
|
||||
def recv_socket_data(c_socket, timeout):
|
||||
c_socket.setblocking(0)
|
||||
total_data=[]
|
||||
data=''
|
||||
begin=time.time()
|
||||
while True:
|
||||
if total_data and time.time()-begin > timeout:
|
||||
break
|
||||
elif time.time()-begin > timeout*2:
|
||||
break
|
||||
try:
|
||||
data = c_socket.recv(8192)
|
||||
if data:
|
||||
total_data.append(data.decode('utf-8'))
|
||||
#change the beginning time for measurement
|
||||
begin=time.time()
|
||||
else:
|
||||
#sleep for sometime to indicate a gap
|
||||
time.sleep(0.1)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
return ''.join(total_data)
|
||||
|
||||
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:
|
||||
self.logger.error("error - exec_cmd_container: %s" % str(e))
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
|
||||
def exec_run_handler(self, type, output):
|
||||
if type == 'generic':
|
||||
if output.exit_code == 0:
|
||||
res = { 'type': 'success', 'msg': 'command completed successfully' }
|
||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||
else:
|
||||
res = { 'type': '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=output.output.decode('utf-8'), media_type="text/plain")
|
||||
@@ -1,10 +1,10 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced
|
||||
ARG DOVECOT=2.3.20
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
|
||||
# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG DOVECOT=2.3.21
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.16
|
||||
ENV LC_ALL C
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ function auth_password_verify(req, pass)
|
||||
VALUES ("%s", 0, "%s", "%s")]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip)))
|
||||
cur:close()
|
||||
con:close()
|
||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
||||
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||
end
|
||||
row = cur:fetch (row, "a")
|
||||
end
|
||||
@@ -180,13 +180,13 @@ function auth_password_verify(req, pass)
|
||||
if tostring(req.real_rip) == "__IPV4_SOGO__" then
|
||||
cur:close()
|
||||
con:close()
|
||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
||||
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||
elseif row.has_prot_access == "1" then
|
||||
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
|
||||
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
|
||||
cur:close()
|
||||
con:close()
|
||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
||||
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||
end
|
||||
end
|
||||
row = cur:fetch (row, "a")
|
||||
|
||||
@@ -75,7 +75,8 @@ my $sth = $dbh->prepare("SELECT id,
|
||||
custom_params,
|
||||
subscribeall,
|
||||
timeout1,
|
||||
timeout2
|
||||
timeout2,
|
||||
dry
|
||||
FROM imapsync
|
||||
WHERE active = 1
|
||||
AND is_running = 0
|
||||
@@ -111,13 +112,16 @@ while ($row = $sth->fetchrow_arrayref()) {
|
||||
$subscribeall = @$row[18];
|
||||
$timeout1 = @$row[19];
|
||||
$timeout2 = @$row[20];
|
||||
$dry = @$row[21];
|
||||
|
||||
if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; }
|
||||
|
||||
my $template = $run_dir . '/imapsync.XXXXXXX';
|
||||
my $passfile1 = File::Temp->new(TEMPLATE => $template);
|
||||
my $passfile2 = File::Temp->new(TEMPLATE => $template);
|
||||
|
||||
|
||||
binmode( $passfile1, ":utf8" );
|
||||
|
||||
print $passfile1 "$password1\n";
|
||||
print $passfile2 trim($master_pass) . "\n";
|
||||
|
||||
@@ -148,6 +152,7 @@ while ($row = $sth->fetchrow_arrayref()) {
|
||||
"--host2", "localhost",
|
||||
"--user2", $user2 . '*' . trim($master_user),
|
||||
"--passfile2", $passfile2->filename,
|
||||
($dry eq "1" ? ('--dry') : ()),
|
||||
'--no-modulesversion',
|
||||
'--noreleasecheck'];
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ENV XTABLES_LIBDIR /usr/lib/xtables
|
||||
ENV PYTHON_IPTABLES_XTABLES_VERSION 12
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
FROM php:8.2-fpm-alpine3.17
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
|
||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
ARG APCU_PECL_VERSION=5.1.22
|
||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced
|
||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG IMAGICK_PECL_VERSION=3.7.0
|
||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced
|
||||
ARG MAILPARSE_PECL_VERSION=3.1.4
|
||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced
|
||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
ARG MAILPARSE_PECL_VERSION=3.1.6
|
||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
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.5
|
||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG REDIS_PECL_VERSION=6.0.1
|
||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG COMPOSER_VERSION=2.6.5
|
||||
|
||||
RUN apk add -U --no-cache autoconf \
|
||||
aspell-dev \
|
||||
@@ -110,4 +110,4 @@ COPY ./docker-entrypoint.sh /
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["php-fpm"]
|
||||
CMD ["php-fpm"]
|
||||
|
||||
@@ -172,6 +172,24 @@ BEGIN
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
DROP EVENT IF EXISTS clean_sasl_log;
|
||||
DELIMITER //
|
||||
CREATE EVENT clean_sasl_log
|
||||
ON SCHEDULE EVERY 1 DAY DO
|
||||
BEGIN
|
||||
DELETE sasl_log.* FROM sasl_log
|
||||
LEFT JOIN (
|
||||
SELECT username, service, MAX(datetime) AS lastdate
|
||||
FROM sasl_log
|
||||
GROUP BY username, service
|
||||
) AS last ON sasl_log.username = last.username AND sasl_log.service = last.service
|
||||
WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY) AND datetime < lastdate;
|
||||
DELETE FROM sasl_log
|
||||
WHERE username NOT IN (SELECT username FROM mailbox) AND
|
||||
datetime < DATE_SUB(NOW(), INTERVAL 31 DAY);
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
EOF
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL C
|
||||
@@ -17,10 +17,10 @@ RUN groupadd -g 102 postfix \
|
||||
ca-certificates \
|
||||
curl \
|
||||
dirmngr \
|
||||
dnsutils \
|
||||
dnsutils \
|
||||
gnupg \
|
||||
libsasl2-modules \
|
||||
mariadb-client \
|
||||
mariadb-client \
|
||||
perl \
|
||||
postfix \
|
||||
postfix-mysql \
|
||||
@@ -32,7 +32,7 @@ RUN groupadd -g 102 postfix \
|
||||
syslog-ng \
|
||||
syslog-ng-core \
|
||||
syslog-ng-mod-redis \
|
||||
tzdata \
|
||||
tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& touch /etc/default/locale \
|
||||
&& printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \
|
||||
|
||||
@@ -393,12 +393,101 @@ query = SELECT goto FROM spamalias
|
||||
AND validity >= UNIX_TIMESTAMP()
|
||||
EOF
|
||||
|
||||
sed -i '/User overrides/q' /opt/postfix/conf/main.cf
|
||||
echo >> /opt/postfix/conf/main.cf
|
||||
touch /opt/postfix/conf/extra.cf
|
||||
sed -i '/myhostname/d' /opt/postfix/conf/extra.cf
|
||||
echo -e "myhostname = ${MAILCOW_HOSTNAME}\n$(cat /opt/postfix/conf/extra.cf)" > /opt/postfix/conf/extra.cf
|
||||
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
|
||||
cat <<EOF > /opt/postfix/conf/dns_blocklists.cf
|
||||
# This file can be edited.
|
||||
# Delete this file and restart postfix container to revert any changes.
|
||||
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
|
||||
hostkarma.junkemailfilter.com=127.0.0.1*-2
|
||||
list.dnswl.org=127.0.[0..255].0*-2
|
||||
list.dnswl.org=127.0.[0..255].1*-4
|
||||
list.dnswl.org=127.0.[0..255].2*-6
|
||||
list.dnswl.org=127.0.[0..255].3*-8
|
||||
ix.dnsbl.manitu.net*2
|
||||
bl.spamcop.net*2
|
||||
bl.suomispam.net*2
|
||||
hostkarma.junkemailfilter.com=127.0.0.2*3
|
||||
hostkarma.junkemailfilter.com=127.0.0.4*2
|
||||
hostkarma.junkemailfilter.com=127.0.1.2*1
|
||||
backscatter.spameatingmonkey.net*2
|
||||
bl.ipv6.spameatingmonkey.net*2
|
||||
bl.spameatingmonkey.net*2
|
||||
b.barracudacentral.org=127.0.0.2*7
|
||||
bl.mailspike.net=127.0.0.2*5
|
||||
bl.mailspike.net=127.0.0.[10;11;12]*4
|
||||
dnsbl.sorbs.net=127.0.0.10*8
|
||||
dnsbl.sorbs.net=127.0.0.5*6
|
||||
dnsbl.sorbs.net=127.0.0.7*3
|
||||
dnsbl.sorbs.net=127.0.0.8*2
|
||||
dnsbl.sorbs.net=127.0.0.6*2
|
||||
dnsbl.sorbs.net=127.0.0.9*2
|
||||
EOF
|
||||
fi
|
||||
DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S')
|
||||
|
||||
if [ ! -z "$DNSBL_CONFIG" ]; then
|
||||
echo -e "\e[33mChecking if ASN for your IP is listed for Spamhaus Bad ASN List...\e[0m"
|
||||
if [ -n "$SPAMHAUS_DQS_KEY" ]; then
|
||||
echo -e "\e[32mDetected SPAMHAUS_DQS_KEY variable from mailcow.conf...\e[0m"
|
||||
echo -e "\e[33mUsing DQS Blocklists from Spamhaus!\e[0m"
|
||||
SPAMHAUS_DNSBL_CONFIG=$(cat <<EOF
|
||||
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.[4..7]*6
|
||||
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.[10;11]*8
|
||||
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.3*4
|
||||
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.2*3
|
||||
postscreen_dnsbl_reply_map = texthash:/opt/postfix/conf/dnsbl_reply.map
|
||||
EOF
|
||||
|
||||
cat <<EOF > /opt/postfix/conf/dnsbl_reply.map
|
||||
# Autogenerated by mailcow, using Spamhaus DQS reply domains
|
||||
${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net sbl.spamhaus.org
|
||||
${SPAMHAUS_DQS_KEY}.xbl.dq.spamhaus.net xbl.spamhaus.org
|
||||
${SPAMHAUS_DQS_KEY}.pbl.dq.spamhaus.net pbl.spamhaus.org
|
||||
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net zen.spamhaus.org
|
||||
${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net dbl.spamhaus.org
|
||||
${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net zrd.spamhaus.org
|
||||
EOF
|
||||
)
|
||||
else
|
||||
if [ -f "/opt/postfix/conf/dnsbl_reply.map" ]; then
|
||||
rm /opt/postfix/conf/dnsbl_reply.map
|
||||
fi
|
||||
response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email")
|
||||
if [ "$response" -eq 503 ]; then
|
||||
echo -e "\e[31mThe AS of your IP is listed as a banned AS from Spamhaus!\e[0m"
|
||||
echo -e "\e[33mNo SPAMHAUS_DQS_KEY found... Skipping Spamhaus blocklists entirely!\e[0m"
|
||||
SPAMHAUS_DNSBL_CONFIG=""
|
||||
elif [ "$response" -eq 200 ]; then
|
||||
echo -e "\e[32mThe AS of your IP is NOT listed as a banned AS from Spamhaus!\e[0m"
|
||||
echo -e "\e[33mUsing the open Spamhaus blocklists.\e[0m"
|
||||
SPAMHAUS_DNSBL_CONFIG=$(cat <<EOF
|
||||
zen.spamhaus.org=127.0.0.[10;11]*8
|
||||
zen.spamhaus.org=127.0.0.[4..7]*6
|
||||
zen.spamhaus.org=127.0.0.3*4
|
||||
zen.spamhaus.org=127.0.0.2*3
|
||||
EOF
|
||||
)
|
||||
|
||||
else
|
||||
echo -e "\e[31mWe couldn't determine your AS... (maybe DNS/Network issue?) Response Code: $response\e[0m"
|
||||
echo -e "\e[33mDeactivating Spamhaus DNS Blocklists to be on the safe site!\e[0m"
|
||||
SPAMHAUS_DNSBL_CONFIG=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Reset main.cf
|
||||
sed -i '/Overrides/q' /opt/postfix/conf/main.cf
|
||||
echo >> /opt/postfix/conf/main.cf
|
||||
# Append postscreen dnsbl sites to main.cf
|
||||
if [ ! -z "$DNSBL_CONFIG" ]; then
|
||||
echo -e "${DNSBL_CONFIG}\n${SPAMHAUS_DNSBL_CONFIG}" >> /opt/postfix/conf/main.cf
|
||||
fi
|
||||
# Append user overrides
|
||||
echo -e "\n# User Overrides" >> /opt/postfix/conf/main.cf
|
||||
touch /opt/postfix/conf/extra.cf
|
||||
sed -i '/\$myhostname/! { /myhostname/d }' /opt/postfix/conf/extra.cf
|
||||
echo -e "myhostname = ${MAILCOW_HOSTNAME}\n$(cat /opt/postfix/conf/extra.cf)" > /opt/postfix/conf/extra.cf
|
||||
cat /opt/postfix/conf/extra.cf >> /opt/postfix/conf/main.cf
|
||||
|
||||
if [ ! -f /opt/postfix/conf/custom_transport.pcre ]; then
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "Andre Peters <andre.peters@tinc.gmbh>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG CODENAME=bullseye
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.16
|
||||
ENV LC_ALL C
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ FROM solr:7.7-slim
|
||||
|
||||
USER root
|
||||
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.16
|
||||
|
||||
COPY solr.sh /
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM alpine:3.17
|
||||
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
RUN apk add --update --no-cache \
|
||||
curl \
|
||||
@@ -18,6 +18,11 @@ EXPOSE 53/udp 53/tcp
|
||||
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
# healthcheck (nslookup)
|
||||
COPY healthcheck.sh /healthcheck.sh
|
||||
RUN chmod +x /healthcheck.sh
|
||||
HEALTHCHECK --interval=30s --timeout=10s CMD [ "/healthcheck.sh" ]
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["/usr/sbin/unbound"]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
nslookup mailcow.email 127.0.0.1 1> /dev/null
|
||||
|
||||
if [ $? == 0 ]; then
|
||||
echo "DNS resolution is working!"
|
||||
exit 0
|
||||
else
|
||||
echo "DNS resolution is not working correctly..."
|
||||
echo "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -86,7 +86,7 @@ server {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {
|
||||
location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+)\.php(?:$|\/) {
|
||||
fastcgi_split_path_info ^(.+?\.php)(\/.*|)$;
|
||||
set $path_info $fastcgi_path_info;
|
||||
try_files $fastcgi_script_name =404;
|
||||
@@ -105,7 +105,7 @@ server {
|
||||
fastcgi_read_timeout 1200;
|
||||
}
|
||||
|
||||
location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
|
||||
location ~ ^\/(?:updater|ocs-provider)(?:$|\/) {
|
||||
try_files $uri/ =404;
|
||||
index index.php;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ mail_plugins = </etc/dovecot/mail_plugins
|
||||
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
|
||||
mail_attachment_dir = /var/attachments
|
||||
mail_attachment_min_size = 128k
|
||||
# Significantly speeds up very large mailboxes, but is only safe to enable if
|
||||
# you do not manually modify the files in the `cur` directories in
|
||||
# mailcowdockerized_vmail-vol-1.
|
||||
# https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-performance/
|
||||
maildir_very_dirty_syncs = yes
|
||||
|
||||
# Dovecot 2.2
|
||||
#ssl_protocols = !SSLv3
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_redirect off;
|
||||
error_page 403 /_rspamderror.php;
|
||||
error_page 401 /_rspamderror.php;
|
||||
}
|
||||
proxy_pass http://rspamd:11334/;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g;
|
||||
server_names_hash_bucket_size 64;
|
||||
server_names_hash_max_size 512;
|
||||
server_names_hash_bucket_size 128;
|
||||
|
||||
map $http_x_forwarded_proto $client_req_scheme {
|
||||
default $scheme;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
listen ${HTTPS_PORT} ssl http2;
|
||||
listen [::]:${HTTPS_PORT} ssl http2;
|
||||
listen ${HTTPS_PORT} ssl;
|
||||
listen [::]:${HTTPS_PORT} ssl;
|
||||
http2 on;
|
||||
|
||||
@@ -40,34 +40,6 @@ postscreen_blacklist_action = drop
|
||||
postscreen_cache_cleanup_interval = 24h
|
||||
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
|
||||
postscreen_dnsbl_action = enforce
|
||||
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
|
||||
hostkarma.junkemailfilter.com=127.0.0.1*-2
|
||||
list.dnswl.org=127.0.[0..255].0*-2
|
||||
list.dnswl.org=127.0.[0..255].1*-4
|
||||
list.dnswl.org=127.0.[0..255].2*-6
|
||||
list.dnswl.org=127.0.[0..255].3*-8
|
||||
ix.dnsbl.manitu.net*2
|
||||
bl.spamcop.net*2
|
||||
bl.suomispam.net*2
|
||||
hostkarma.junkemailfilter.com=127.0.0.2*3
|
||||
hostkarma.junkemailfilter.com=127.0.0.4*2
|
||||
hostkarma.junkemailfilter.com=127.0.1.2*1
|
||||
backscatter.spameatingmonkey.net*2
|
||||
bl.ipv6.spameatingmonkey.net*2
|
||||
bl.spameatingmonkey.net*2
|
||||
b.barracudacentral.org=127.0.0.2*7
|
||||
bl.mailspike.net=127.0.0.2*5
|
||||
bl.mailspike.net=127.0.0.[10;11;12]*4
|
||||
dnsbl.sorbs.net=127.0.0.10*8
|
||||
dnsbl.sorbs.net=127.0.0.5*6
|
||||
dnsbl.sorbs.net=127.0.0.7*3
|
||||
dnsbl.sorbs.net=127.0.0.8*2
|
||||
dnsbl.sorbs.net=127.0.0.6*2
|
||||
dnsbl.sorbs.net=127.0.0.9*2
|
||||
zen.spamhaus.org=127.0.0.[10;11]*8
|
||||
zen.spamhaus.org=127.0.0.[4..7]*6
|
||||
zen.spamhaus.org=127.0.0.3*4
|
||||
zen.spamhaus.org=127.0.0.2*3
|
||||
postscreen_dnsbl_threshold = 6
|
||||
postscreen_dnsbl_ttl = 5m
|
||||
postscreen_greet_action = enforce
|
||||
@@ -197,4 +169,4 @@ smtps_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||
parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients
|
||||
|
||||
# DO NOT EDIT ANYTHING BELOW #
|
||||
# User overrides #
|
||||
# Overrides #
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
# Whitelist generated by Postwhite v3.4 on Mon 21 Mar 2022 06:50:26 PM CET
|
||||
# Whitelist generated by Postwhite v3.4 on Wed Nov 1 00:14:24 UTC 2023
|
||||
# https://github.com/stevejenkins/postwhite/
|
||||
# 1898 total rules
|
||||
# 2030 total rules
|
||||
2a00:1450:4000::/36 permit
|
||||
2a01:111:f400::/48 permit
|
||||
2a01:111:f403::/48 permit
|
||||
2a01:4180:4050:0400::/64 permit
|
||||
2a01:4180:4050:0800::/64 permit
|
||||
2a01:4180:4051:0400::/64 permit
|
||||
2a01:4180:4051:0800::/64 permit
|
||||
2a01:111:f403:8000::/50 permit
|
||||
2a01:111:f403::/49 permit
|
||||
2a01:111:f403:c000::/51 permit
|
||||
2a01:111:f403:f000::/52 permit
|
||||
2a02:a60:0:5::/64 permit
|
||||
2c0f:fb50:4000::/36 permit
|
||||
2.207.151.53 permit
|
||||
3.14.230.16 permit
|
||||
3.70.123.177 permit
|
||||
3.93.157.0/24 permit
|
||||
3.129.120.190 permit
|
||||
3.210.190.0/24 permit
|
||||
8.20.114.31 permit
|
||||
8.25.194.0/23 permit
|
||||
8.25.196.0/23 permit
|
||||
@@ -19,41 +24,53 @@
|
||||
13.70.32.43 permit
|
||||
13.72.50.45 permit
|
||||
13.74.143.28 permit
|
||||
13.77.161.179 permit
|
||||
13.78.233.182 permit
|
||||
13.92.31.129 permit
|
||||
13.110.208.0/21 permit
|
||||
13.110.209.0/24 permit
|
||||
13.110.216.0/22 permit
|
||||
13.110.224.0/20 permit
|
||||
13.111.0.0/16 permit
|
||||
17.41.0.0/16 permit
|
||||
15.200.21.50 permit
|
||||
15.200.44.248 permit
|
||||
15.200.201.185 permit
|
||||
17.57.155.0/24 permit
|
||||
17.57.156.0/24 permit
|
||||
17.58.0.0/16 permit
|
||||
17.110.0.0/15 permit
|
||||
17.142.0.0/15 permit
|
||||
17.162.0.0/15 permit
|
||||
17.164.0.0/16 permit
|
||||
17.171.37.0/24 permit
|
||||
17.172.0.0/16 permit
|
||||
17.179.168.0/23 permit
|
||||
18.156.89.250 permit
|
||||
18.157.243.190 permit
|
||||
18.194.95.56 permit
|
||||
18.198.96.88 permit
|
||||
20.47.149.138 permit
|
||||
20.48.0.0/12 permit
|
||||
18.208.124.128/25 permit
|
||||
18.216.232.154 permit
|
||||
18.234.1.244 permit
|
||||
18.236.40.242 permit
|
||||
20.51.6.32/30 permit
|
||||
20.52.52.2 permit
|
||||
20.52.128.133 permit
|
||||
20.59.80.4/30 permit
|
||||
20.63.210.192/28 permit
|
||||
20.64.0.0/10 permit
|
||||
20.69.8.108/30 permit
|
||||
20.70.246.20 permit
|
||||
20.76.201.171 permit
|
||||
20.83.222.104/30 permit
|
||||
20.88.157.184/30 permit
|
||||
20.94.180.64/28 permit
|
||||
20.97.34.220/30 permit
|
||||
20.98.148.156/30 permit
|
||||
20.98.194.68/30 permit
|
||||
20.105.209.76/30 permit
|
||||
20.107.239.64/30 permit
|
||||
20.112.250.133 permit
|
||||
20.118.139.208/30 permit
|
||||
20.185.213.160/27 permit
|
||||
20.185.213.224/27 permit
|
||||
20.185.214.0/27 permit
|
||||
20.185.214.2 permit
|
||||
20.185.214.32/27 permit
|
||||
20.185.214.64/27 permit
|
||||
20.192.0.0/10 permit
|
||||
23.100.85.1 permit
|
||||
20.231.239.246 permit
|
||||
20.236.44.162 permit
|
||||
23.103.224.0/19 permit
|
||||
23.249.208.0/20 permit
|
||||
23.251.224.0/19 permit
|
||||
@@ -78,46 +95,34 @@
|
||||
27.123.206.56/29 permit
|
||||
27.123.206.76/30 permit
|
||||
27.123.206.80/28 permit
|
||||
34.194.25.167 permit
|
||||
34.194.144.120 permit
|
||||
31.25.48.222 permit
|
||||
34.195.217.107 permit
|
||||
34.202.239.6 permit
|
||||
34.212.163.75 permit
|
||||
34.215.104.144 permit
|
||||
34.225.212.172 permit
|
||||
34.247.168.44 permit
|
||||
35.161.32.253 permit
|
||||
35.167.93.243 permit
|
||||
35.176.132.251 permit
|
||||
35.190.247.0/24 permit
|
||||
35.191.0.0/16 permit
|
||||
37.188.97.188 permit
|
||||
37.218.248.47 permit
|
||||
37.218.249.47 permit
|
||||
37.218.251.62 permit
|
||||
39.156.163.64/29 permit
|
||||
40.71.187.0/24 permit
|
||||
40.76.4.15 permit
|
||||
40.77.102.222 permit
|
||||
40.92.0.0/15 permit
|
||||
40.97.116.82 permit
|
||||
40.97.128.194 permit
|
||||
40.97.148.226 permit
|
||||
40.97.153.146 permit
|
||||
40.97.156.114 permit
|
||||
40.97.160.2 permit
|
||||
40.97.161.50 permit
|
||||
40.97.164.146 permit
|
||||
40.92.0.0/16 permit
|
||||
40.107.0.0/16 permit
|
||||
40.112.65.63 permit
|
||||
40.112.72.205 permit
|
||||
40.113.200.201 permit
|
||||
40.117.80.0/24 permit
|
||||
40.121.71.46 permit
|
||||
41.74.192.0/22 permit
|
||||
41.74.196.0/22 permit
|
||||
41.74.200.0/23 permit
|
||||
41.74.204.0/23 permit
|
||||
41.74.206.0/24 permit
|
||||
42.159.163.81 permit
|
||||
42.159.163.82 permit
|
||||
42.159.163.83 permit
|
||||
43.228.184.0/22 permit
|
||||
44.206.138.57 permit
|
||||
44.209.42.157 permit
|
||||
44.236.56.93 permit
|
||||
44.238.220.251 permit
|
||||
46.19.168.0/23 permit
|
||||
46.226.48.0/21 permit
|
||||
46.228.36.37 permit
|
||||
46.228.36.38/31 permit
|
||||
@@ -167,6 +172,8 @@
|
||||
46.243.88.175 permit
|
||||
46.243.88.176 permit
|
||||
46.243.88.177 permit
|
||||
46.243.95.179 permit
|
||||
46.243.95.180 permit
|
||||
50.18.45.249 permit
|
||||
50.18.121.236 permit
|
||||
50.18.121.248 permit
|
||||
@@ -178,31 +185,36 @@
|
||||
50.31.32.0/19 permit
|
||||
50.31.156.96/27 permit
|
||||
50.31.205.0/24 permit
|
||||
51.4.71.62 permit
|
||||
51.4.72.0/24 permit
|
||||
51.4.80.0/27 permit
|
||||
51.5.72.0/24 permit
|
||||
51.5.80.0/27 permit
|
||||
51.137.58.21 permit
|
||||
51.140.75.55 permit
|
||||
51.144.100.179 permit
|
||||
51.163.158.0/24 permit
|
||||
51.163.159.21 permit
|
||||
52.5.230.59 permit
|
||||
52.27.5.72 permit
|
||||
52.27.28.47 permit
|
||||
52.33.191.91 permit
|
||||
52.28.63.81 permit
|
||||
52.36.138.31 permit
|
||||
52.37.142.146 permit
|
||||
52.38.191.253 permit
|
||||
52.41.64.145 permit
|
||||
52.58.216.183 permit
|
||||
52.59.143.3 permit
|
||||
52.60.41.5 permit
|
||||
52.60.115.116 permit
|
||||
52.61.91.9 permit
|
||||
52.71.0.205 permit
|
||||
52.82.172.0/22 permit
|
||||
52.94.124.0/28 permit
|
||||
52.95.48.152/29 permit
|
||||
52.95.49.88/29 permit
|
||||
52.96.91.34 permit
|
||||
52.96.111.82 permit
|
||||
52.96.172.98 permit
|
||||
52.96.214.50 permit
|
||||
52.96.222.194 permit
|
||||
52.96.222.226 permit
|
||||
52.96.223.2 permit
|
||||
52.96.228.130 permit
|
||||
52.96.229.242 permit
|
||||
52.100.0.0/14 permit
|
||||
52.103.0.0/17 permit
|
||||
52.119.213.144/28 permit
|
||||
52.160.39.140 permit
|
||||
52.165.175.144 permit
|
||||
@@ -214,23 +226,29 @@
|
||||
52.222.73.83 permit
|
||||
52.222.73.120 permit
|
||||
52.222.75.85 permit
|
||||
52.222.89.228 permit
|
||||
52.234.172.96/28 permit
|
||||
52.236.28.240/28 permit
|
||||
52.237.141.173 permit
|
||||
52.244.206.214 permit
|
||||
52.247.53.144 permit
|
||||
52.250.107.196 permit
|
||||
52.250.126.174 permit
|
||||
52.251.55.143 permit
|
||||
54.90.148.255 permit
|
||||
54.156.255.69 permit
|
||||
54.172.97.247 permit
|
||||
54.174.52.0/24 permit
|
||||
54.174.53.128/30 permit
|
||||
54.174.57.0/24 permit
|
||||
54.174.59.0/24 permit
|
||||
54.174.60.0/23 permit
|
||||
54.174.63.0/24 permit
|
||||
54.186.193.102 permit
|
||||
54.191.223.5 permit
|
||||
54.191.223.56 permit
|
||||
54.194.61.95 permit
|
||||
54.195.113.45 permit
|
||||
54.213.20.246 permit
|
||||
54.214.39.184 permit
|
||||
54.216.77.168 permit
|
||||
54.221.227.204 permit
|
||||
54.240.0.0/18 permit
|
||||
54.240.64.0/19 permit
|
||||
54.240.96.0/19 permit
|
||||
@@ -238,7 +256,9 @@
|
||||
54.244.54.130 permit
|
||||
54.244.242.0/24 permit
|
||||
54.246.232.180 permit
|
||||
54.255.61.23 permit
|
||||
62.13.128.0/24 permit
|
||||
62.13.128.150 permit
|
||||
62.13.129.128/25 permit
|
||||
62.13.136.0/22 permit
|
||||
62.13.140.0/22 permit
|
||||
@@ -247,29 +267,32 @@
|
||||
62.13.150.0/23 permit
|
||||
62.13.152.0/23 permit
|
||||
62.17.146.128/26 permit
|
||||
62.140.7.0/24 permit
|
||||
62.140.10.21 permit
|
||||
62.179.121.0/24 permit
|
||||
62.201.172.0/27 permit
|
||||
62.201.172.32/27 permit
|
||||
62.253.227.114 permit
|
||||
63.32.13.159 permit
|
||||
63.80.14.0/23 permit
|
||||
63.111.28.137 permit
|
||||
63.128.21.0/24 permit
|
||||
63.143.57.128/25 permit
|
||||
63.143.59.128/25 permit
|
||||
64.18.0.0/20 permit
|
||||
64.20.241.45 permit
|
||||
64.34.47.128/27 permit
|
||||
64.34.57.192/26 permit
|
||||
64.69.212.0/24 permit
|
||||
64.71.149.160/28 permit
|
||||
64.79.155.0/24 permit
|
||||
64.79.155.192 permit
|
||||
64.79.155.193 permit
|
||||
64.79.155.205 permit
|
||||
64.79.155.206 permit
|
||||
64.89.44.85 permit
|
||||
64.89.45.80 permit
|
||||
64.89.45.194 permit
|
||||
64.89.45.196 permit
|
||||
64.95.144.196 permit
|
||||
64.127.115.252 permit
|
||||
64.132.88.0/23 permit
|
||||
64.132.92.0/24 permit
|
||||
64.135.77.0/24 permit
|
||||
64.135.83.0/24 permit
|
||||
64.147.123.17 permit
|
||||
64.147.123.18 permit
|
||||
64.147.123.19 permit
|
||||
@@ -290,6 +313,7 @@
|
||||
64.207.219.71 permit
|
||||
64.207.219.72 permit
|
||||
64.207.219.73 permit
|
||||
64.207.219.75 permit
|
||||
64.207.219.77 permit
|
||||
64.207.219.78 permit
|
||||
64.207.219.79 permit
|
||||
@@ -300,9 +324,6 @@
|
||||
64.207.219.142 permit
|
||||
64.207.219.143 permit
|
||||
64.233.160.0/19 permit
|
||||
65.38.115.76 permit
|
||||
65.38.115.84 permit
|
||||
65.39.215.0/24 permit
|
||||
65.52.80.137 permit
|
||||
65.54.51.64/26 permit
|
||||
65.54.61.64/26 permit
|
||||
@@ -342,6 +363,10 @@
|
||||
66.111.4.225 permit
|
||||
66.111.4.229 permit
|
||||
66.111.4.230 permit
|
||||
66.119.150.192/26 permit
|
||||
66.135.202.0/27 permit
|
||||
66.135.215.0/24 permit
|
||||
66.135.222.1 permit
|
||||
66.162.193.226/31 permit
|
||||
66.163.184.0/21 permit
|
||||
66.163.184.0/24 permit
|
||||
@@ -373,7 +398,8 @@
|
||||
66.196.81.234 permit
|
||||
66.211.168.230/31 permit
|
||||
66.211.170.86/31 permit
|
||||
66.211.170.88/30 permit
|
||||
66.211.170.88/29 permit
|
||||
66.211.184.0/23 permit
|
||||
66.218.74.64/30 permit
|
||||
66.218.74.68/31 permit
|
||||
66.218.75.112/30 permit
|
||||
@@ -445,6 +471,8 @@
|
||||
68.142.230.72/30 permit
|
||||
68.142.230.76/31 permit
|
||||
68.142.230.78 permit
|
||||
68.232.140.138 permit
|
||||
68.232.157.143 permit
|
||||
68.232.192.0/20 permit
|
||||
69.63.178.128/25 permit
|
||||
69.63.181.0/24 permit
|
||||
@@ -452,6 +480,10 @@
|
||||
69.65.42.195 permit
|
||||
69.65.49.192/29 permit
|
||||
69.72.32.0/20 permit
|
||||
69.72.40.93 permit
|
||||
69.72.40.94/31 permit
|
||||
69.72.40.96/30 permit
|
||||
69.72.47.205 permit
|
||||
69.147.84.227 permit
|
||||
69.162.98.0/24 permit
|
||||
69.169.224.0/20 permit
|
||||
@@ -460,7 +492,7 @@
|
||||
70.37.151.128/25 permit
|
||||
70.42.149.0/24 permit
|
||||
70.42.149.35 permit
|
||||
72.3.185.0/24 permit
|
||||
72.3.237.64/28 permit
|
||||
72.14.192.0/18 permit
|
||||
72.21.192.0/19 permit
|
||||
72.21.217.142 permit
|
||||
@@ -522,15 +554,11 @@
|
||||
72.30.239.228/31 permit
|
||||
72.30.239.244/30 permit
|
||||
72.30.239.248/31 permit
|
||||
72.32.154.0/24 permit
|
||||
72.32.217.0/24 permit
|
||||
72.32.243.0/24 permit
|
||||
72.34.168.76 permit
|
||||
72.34.168.80 permit
|
||||
72.34.168.85 permit
|
||||
72.34.168.86 permit
|
||||
72.52.72.32/28 permit
|
||||
72.52.72.36 permit
|
||||
74.6.128.0/21 permit
|
||||
74.6.128.0/24 permit
|
||||
74.6.129.0/24 permit
|
||||
@@ -558,8 +586,11 @@
|
||||
74.112.67.243 permit
|
||||
74.125.0.0/16 permit
|
||||
74.202.227.40 permit
|
||||
74.208.4.192/26 permit
|
||||
74.208.5.64/26 permit
|
||||
74.208.122.0/26 permit
|
||||
74.209.250.0/24 permit
|
||||
74.209.250.12 permit
|
||||
76.223.128.0/19 permit
|
||||
76.223.176.0/20 permit
|
||||
77.238.176.0/22 permit
|
||||
77.238.176.0/24 permit
|
||||
@@ -582,8 +613,15 @@
|
||||
77.238.189.142 permit
|
||||
77.238.189.146/31 permit
|
||||
77.238.189.148/30 permit
|
||||
81.7.169.128/25 permit
|
||||
81.223.46.0/27 permit
|
||||
84.16.77.1 permit
|
||||
82.165.159.0/24 permit
|
||||
82.165.159.0/26 permit
|
||||
82.165.229.31 permit
|
||||
82.165.229.130 permit
|
||||
82.165.230.21 permit
|
||||
82.165.230.22 permit
|
||||
84.116.36.0/24 permit
|
||||
85.158.136.0/21 permit
|
||||
86.61.88.25 permit
|
||||
87.198.219.130 permit
|
||||
@@ -624,11 +662,9 @@
|
||||
87.248.117.201 permit
|
||||
87.248.117.202 permit
|
||||
87.248.117.205 permit
|
||||
87.252.219.254 permit
|
||||
87.253.232.0/21 permit
|
||||
89.22.108.0/24 permit
|
||||
91.220.42.0/24 permit
|
||||
94.236.119.0/26 permit
|
||||
91.211.240.0/22 permit
|
||||
94.245.112.0/27 permit
|
||||
94.245.112.10/31 permit
|
||||
95.131.104.0/21 permit
|
||||
@@ -638,6 +674,7 @@
|
||||
96.43.148.64/28 permit
|
||||
96.43.148.64/31 permit
|
||||
96.43.151.64/28 permit
|
||||
98.97.248.0/21 permit
|
||||
98.136.44.181 permit
|
||||
98.136.44.182/31 permit
|
||||
98.136.44.184 permit
|
||||
@@ -1142,23 +1179,20 @@
|
||||
98.139.245.212/31 permit
|
||||
99.78.197.208/28 permit
|
||||
103.2.140.0/22 permit
|
||||
103.9.8.121 permit
|
||||
103.9.8.122 permit
|
||||
103.9.8.123 permit
|
||||
103.9.96.0/22 permit
|
||||
103.13.69.0/24 permit
|
||||
103.28.42.0/24 permit
|
||||
103.47.204.0/22 permit
|
||||
103.96.21.0/24 permit
|
||||
103.96.23.0/24 permit
|
||||
103.151.192.0/23 permit
|
||||
103.237.104.0/22 permit
|
||||
103.168.172.128/27 permit
|
||||
104.43.243.237 permit
|
||||
104.44.112.128/25 permit
|
||||
104.47.0.0/17 permit
|
||||
104.47.20.0/23 permit
|
||||
104.47.75.0/24 permit
|
||||
104.47.108.0/23 permit
|
||||
104.130.96.0/28 permit
|
||||
104.130.122.0/23 permit
|
||||
104.214.25.77 permit
|
||||
104.215.148.63 permit
|
||||
104.215.186.3 permit
|
||||
104.245.209.192/26 permit
|
||||
106.10.144.64/27 permit
|
||||
106.10.144.100/31 permit
|
||||
@@ -1320,9 +1354,9 @@
|
||||
117.120.16.0/21 permit
|
||||
119.42.242.52/31 permit
|
||||
119.42.242.156 permit
|
||||
121.244.91.48 permit
|
||||
122.15.156.182 permit
|
||||
123.126.78.64/29 permit
|
||||
124.47.150.0/24 permit
|
||||
124.47.189.0/24 permit
|
||||
124.108.96.0/24 permit
|
||||
124.108.96.24/31 permit
|
||||
124.108.96.28/31 permit
|
||||
@@ -1335,20 +1369,40 @@
|
||||
128.127.70.0/26 permit
|
||||
128.245.0.0/20 permit
|
||||
128.245.64.0/20 permit
|
||||
128.245.176.0/20 permit
|
||||
128.245.240.0/24 permit
|
||||
128.245.241.0/24 permit
|
||||
128.245.242.0/24 permit
|
||||
128.245.242.16 permit
|
||||
128.245.242.17 permit
|
||||
128.245.242.18 permit
|
||||
128.245.243.0/24 permit
|
||||
128.245.244.0/24 permit
|
||||
128.245.245.0/24 permit
|
||||
128.245.246.0/24 permit
|
||||
128.245.247.0/24 permit
|
||||
128.245.248.0/21 permit
|
||||
129.41.77.70 permit
|
||||
129.41.169.249 permit
|
||||
129.80.5.164 permit
|
||||
129.80.67.121 permit
|
||||
129.145.74.12 permit
|
||||
129.146.88.28 permit
|
||||
129.146.147.105 permit
|
||||
129.146.236.58 permit
|
||||
129.151.67.221 permit
|
||||
129.153.62.216 permit
|
||||
129.153.104.71 permit
|
||||
129.153.168.146 permit
|
||||
129.153.190.200 permit
|
||||
129.153.194.228 permit
|
||||
129.159.87.137 permit
|
||||
129.213.195.191 permit
|
||||
130.61.9.72 permit
|
||||
130.162.39.83 permit
|
||||
130.211.0.0/22 permit
|
||||
130.248.172.0/24 permit
|
||||
130.248.173.0/24 permit
|
||||
131.107.0.0/16 permit
|
||||
131.253.30.0/24 permit
|
||||
131.253.121.0/26 permit
|
||||
131.253.121.20 permit
|
||||
131.253.121.52 permit
|
||||
132.145.13.209 permit
|
||||
132.226.26.225 permit
|
||||
132.226.49.32 permit
|
||||
@@ -1358,9 +1412,13 @@
|
||||
134.170.141.64/26 permit
|
||||
134.170.143.0/24 permit
|
||||
134.170.174.0/24 permit
|
||||
135.84.80.192/26 permit
|
||||
135.84.80.0/24 permit
|
||||
135.84.81.0/24 permit
|
||||
135.84.82.0/24 permit
|
||||
135.84.83.0/24 permit
|
||||
135.84.216.0/22 permit
|
||||
136.143.160.0/24 permit
|
||||
136.143.161.0/24 permit
|
||||
136.143.182.0/23 permit
|
||||
136.143.184.0/24 permit
|
||||
136.143.188.0/24 permit
|
||||
@@ -1369,34 +1427,52 @@
|
||||
136.147.176.0/20 permit
|
||||
136.147.176.0/24 permit
|
||||
136.147.182.0/24 permit
|
||||
136.179.50.206 permit
|
||||
138.91.172.26 permit
|
||||
139.60.152.0/22 permit
|
||||
139.178.64.159 permit
|
||||
139.178.64.195 permit
|
||||
139.138.35.44 permit
|
||||
139.138.46.121 permit
|
||||
139.138.46.176 permit
|
||||
139.138.46.219 permit
|
||||
139.138.57.55 permit
|
||||
139.138.58.119 permit
|
||||
139.180.17.0/24 permit
|
||||
141.148.159.229 permit
|
||||
141.193.32.0/23 permit
|
||||
143.55.224.0/21 permit
|
||||
143.55.232.0/22 permit
|
||||
143.55.236.0/22 permit
|
||||
143.244.80.0/20 permit
|
||||
144.24.6.140 permit
|
||||
144.34.8.247 permit
|
||||
144.34.9.247 permit
|
||||
144.34.32.247 permit
|
||||
144.34.33.247 permit
|
||||
144.178.36.0/24 permit
|
||||
144.178.38.0/24 permit
|
||||
145.253.228.160/29 permit
|
||||
145.253.239.128/29 permit
|
||||
146.20.112.0/26 permit
|
||||
146.20.113.0/24 permit
|
||||
146.20.191.0/24 permit
|
||||
146.20.215.0/24 permit
|
||||
146.101.78.0/24 permit
|
||||
147.75.65.173 permit
|
||||
147.75.65.174 permit
|
||||
147.75.98.190 permit
|
||||
146.20.215.182 permit
|
||||
146.88.28.0/24 permit
|
||||
147.160.158.0/24 permit
|
||||
147.243.1.47 permit
|
||||
147.243.1.48 permit
|
||||
147.243.1.153 permit
|
||||
147.243.128.24 permit
|
||||
147.243.128.26 permit
|
||||
148.105.0.14 permit
|
||||
148.105.0.0/16 permit
|
||||
148.105.8.0/21 permit
|
||||
149.72.0.0/16 permit
|
||||
149.72.248.236 permit
|
||||
149.97.173.180 permit
|
||||
150.230.98.160 permit
|
||||
152.67.105.195 permit
|
||||
152.69.200.236 permit
|
||||
155.248.208.51 permit
|
||||
157.55.0.192/26 permit
|
||||
157.55.1.128/26 permit
|
||||
157.55.2.0/25 permit
|
||||
@@ -1412,37 +1488,54 @@
|
||||
157.56.232.0/21 permit
|
||||
157.56.240.0/20 permit
|
||||
157.56.248.0/21 permit
|
||||
157.58.30.128/25 permit
|
||||
157.58.196.96/29 permit
|
||||
157.58.249.3 permit
|
||||
157.151.208.65 permit
|
||||
157.255.1.64/29 permit
|
||||
158.101.211.207 permit
|
||||
158.120.80.0/21 permit
|
||||
158.247.16.0/20 permit
|
||||
159.92.154.0/24 permit
|
||||
159.92.155.0/24 permit
|
||||
159.92.157.0/24 permit
|
||||
159.92.157.16 permit
|
||||
159.92.157.17 permit
|
||||
159.92.157.18 permit
|
||||
159.92.158.0/24 permit
|
||||
159.92.159.0/24 permit
|
||||
159.92.160.0/24 permit
|
||||
159.92.161.0/24 permit
|
||||
159.92.162.0/24 permit
|
||||
159.92.163.0/24 permit
|
||||
159.92.164.0/22 permit
|
||||
159.92.168.0/21 permit
|
||||
159.112.240.0/20 permit
|
||||
159.112.242.162 permit
|
||||
159.135.132.128/25 permit
|
||||
159.135.140.80/29 permit
|
||||
159.135.224.0/20 permit
|
||||
159.135.228.10 permit
|
||||
159.183.0.0/16 permit
|
||||
160.1.62.192 permit
|
||||
161.38.192.0/20 permit
|
||||
161.38.204.0/22 permit
|
||||
161.71.32.0/19 permit
|
||||
161.71.64.0/20 permit
|
||||
162.208.119.181 permit
|
||||
162.247.216.0/22 permit
|
||||
163.47.180.0/22 permit
|
||||
163.47.180.0/23 permit
|
||||
163.114.130.16 permit
|
||||
163.114.132.120 permit
|
||||
165.173.128.0/24 permit
|
||||
166.78.68.0/22 permit
|
||||
166.78.68.221 permit
|
||||
166.78.69.146 permit
|
||||
166.78.69.169 permit
|
||||
166.78.69.170 permit
|
||||
166.78.71.131 permit
|
||||
167.89.0.0/17 permit
|
||||
167.89.46.159 permit
|
||||
167.89.54.103 permit
|
||||
167.89.64.9 permit
|
||||
167.89.65.0 permit
|
||||
167.89.65.53 permit
|
||||
@@ -1457,10 +1550,17 @@
|
||||
167.216.129.210 permit
|
||||
167.216.131.180 permit
|
||||
167.220.67.232/29 permit
|
||||
167.220.67.238 permit
|
||||
168.138.5.36 permit
|
||||
168.138.73.51 permit
|
||||
168.245.0.0/17 permit
|
||||
168.245.12.252 permit
|
||||
168.245.46.9 permit
|
||||
168.245.127.231 permit
|
||||
169.148.129.0/24 permit
|
||||
169.148.131.0/24 permit
|
||||
169.148.144.0/25 permit
|
||||
170.10.68.0/22 permit
|
||||
170.10.128.0/24 permit
|
||||
170.10.129.0/24 permit
|
||||
170.10.133.0/24 permit
|
||||
172.217.0.0/19 permit
|
||||
@@ -1475,10 +1575,8 @@
|
||||
173.194.0.0/16 permit
|
||||
173.203.79.182 permit
|
||||
173.203.81.39 permit
|
||||
173.224.160.128/25 permit
|
||||
173.224.160.188 permit
|
||||
173.224.161.128/25 permit
|
||||
173.228.155.0/24 permit
|
||||
173.224.165.0/26 permit
|
||||
174.36.84.8/29 permit
|
||||
174.36.84.16/29 permit
|
||||
174.36.84.32/29 permit
|
||||
@@ -1491,27 +1589,27 @@
|
||||
174.36.114.152/29 permit
|
||||
174.37.67.28/30 permit
|
||||
174.129.203.189 permit
|
||||
175.41.215.51 permit
|
||||
176.32.105.0/24 permit
|
||||
176.32.127.0/24 permit
|
||||
178.236.10.128/26 permit
|
||||
180.189.28.0/24 permit
|
||||
182.50.76.0/22 permit
|
||||
182.50.78.64/28 permit
|
||||
183.240.219.64/29 permit
|
||||
185.4.120.0/23 permit
|
||||
185.4.122.0/24 permit
|
||||
185.12.80.0/22 permit
|
||||
185.28.196.0/22 permit
|
||||
185.58.84.93 permit
|
||||
185.58.85.0/24 permit
|
||||
185.58.86.0/24 permit
|
||||
185.72.128.75 permit
|
||||
185.72.128.76 permit
|
||||
185.72.128.80 permit
|
||||
185.80.93.204 permit
|
||||
185.80.93.227 permit
|
||||
185.80.95.31 permit
|
||||
185.90.20.0/22 permit
|
||||
185.189.236.0/22 permit
|
||||
185.211.120.0/22 permit
|
||||
185.250.236.0/22 permit
|
||||
185.250.239.148 permit
|
||||
185.250.239.168 permit
|
||||
185.250.239.190 permit
|
||||
188.125.68.132 permit
|
||||
188.125.68.152/31 permit
|
||||
188.125.68.156 permit
|
||||
@@ -1563,7 +1661,7 @@
|
||||
188.125.85.238 permit
|
||||
188.172.128.0/20 permit
|
||||
192.0.64.0/18 permit
|
||||
192.28.128.0/18 permit
|
||||
192.18.139.154 permit
|
||||
192.30.252.0/22 permit
|
||||
192.64.236.0/24 permit
|
||||
192.64.237.0/24 permit
|
||||
@@ -1579,17 +1677,17 @@
|
||||
192.254.113.10 permit
|
||||
192.254.113.101 permit
|
||||
192.254.114.176 permit
|
||||
192.254.118.63 permit
|
||||
193.7.206.0/25 permit
|
||||
193.7.207.0/25 permit
|
||||
193.109.254.0/23 permit
|
||||
193.122.128.100 permit
|
||||
193.123.56.63 permit
|
||||
194.19.134.0/25 permit
|
||||
194.64.234.128/27 permit
|
||||
194.64.234.129 permit
|
||||
194.104.109.0/24 permit
|
||||
194.104.111.0/24 permit
|
||||
194.106.220.0/23 permit
|
||||
194.113.24.0/22 permit
|
||||
194.154.193.192/27 permit
|
||||
195.130.217.0/24 permit
|
||||
195.4.92.0/23 permit
|
||||
195.54.172.0/23 permit
|
||||
195.234.109.226 permit
|
||||
195.245.230.0/23 permit
|
||||
198.2.128.0/18 permit
|
||||
@@ -1605,19 +1703,24 @@
|
||||
198.37.144.0/20 permit
|
||||
198.37.152.186 permit
|
||||
198.61.254.0/23 permit
|
||||
198.61.254.21 permit
|
||||
198.61.254.231 permit
|
||||
198.74.56.28 permit
|
||||
198.178.234.57 permit
|
||||
198.244.48.0/20 permit
|
||||
198.244.60.0/22 permit
|
||||
198.245.80.0/20 permit
|
||||
198.245.81.0/24 permit
|
||||
199.15.176.173 permit
|
||||
199.15.212.0/22 permit
|
||||
199.15.213.187 permit
|
||||
199.15.226.37 permit
|
||||
199.16.156.0/22 permit
|
||||
199.33.145.1 permit
|
||||
199.33.145.32 permit
|
||||
199.34.22.36 permit
|
||||
199.59.148.0/22 permit
|
||||
199.67.84.0/24 permit
|
||||
199.67.86.0/24 permit
|
||||
199.67.88.0/24 permit
|
||||
199.101.161.130 permit
|
||||
199.101.162.0/25 permit
|
||||
199.122.120.0/21 permit
|
||||
@@ -1630,8 +1733,10 @@
|
||||
202.177.148.110 permit
|
||||
203.31.36.0/22 permit
|
||||
203.32.4.25 permit
|
||||
203.55.21.0/24 permit
|
||||
203.81.17.0/24 permit
|
||||
203.122.32.250 permit
|
||||
203.145.57.160/27 permit
|
||||
203.188.194.32 permit
|
||||
203.188.194.151 permit
|
||||
203.188.194.203 permit
|
||||
@@ -1666,28 +1771,32 @@
|
||||
203.209.230.76/31 permit
|
||||
204.11.168.0/21 permit
|
||||
204.13.11.48/29 permit
|
||||
204.13.11.48/30 permit
|
||||
204.14.232.0/21 permit
|
||||
204.14.232.64/28 permit
|
||||
204.14.234.64/28 permit
|
||||
204.29.186.0/23 permit
|
||||
204.75.142.0/24 permit
|
||||
204.79.197.212 permit
|
||||
204.92.114.187 permit
|
||||
204.92.114.203 permit
|
||||
204.92.114.204/31 permit
|
||||
204.141.32.0/23 permit
|
||||
204.141.42.0/23 permit
|
||||
204.153.121.0/24 permit
|
||||
204.220.160.0/20 permit
|
||||
204.232.168.0/24 permit
|
||||
205.139.110.0/24 permit
|
||||
205.201.128.0/20 permit
|
||||
205.201.131.128/25 permit
|
||||
205.201.134.128/25 permit
|
||||
205.201.136.0/23 permit
|
||||
205.201.137.229 permit
|
||||
205.201.139.0/24 permit
|
||||
205.207.104.0/22 permit
|
||||
205.207.104.108 permit
|
||||
205.220.167.17 permit
|
||||
205.220.167.98 permit
|
||||
205.220.179.17 permit
|
||||
205.220.179.98 permit
|
||||
205.251.233.32 permit
|
||||
205.251.233.36 permit
|
||||
206.25.247.143 permit
|
||||
@@ -1715,7 +1824,6 @@
|
||||
207.67.98.192/27 permit
|
||||
207.68.176.0/26 permit
|
||||
207.68.176.96/27 permit
|
||||
207.82.80.0/24 permit
|
||||
207.126.144.0/20 permit
|
||||
207.171.160.0/19 permit
|
||||
207.211.30.64/26 permit
|
||||
@@ -1723,6 +1831,7 @@
|
||||
207.211.31.0/25 permit
|
||||
207.211.41.113 permit
|
||||
207.218.90.0/24 permit
|
||||
207.218.90.122 permit
|
||||
207.250.68.0/24 permit
|
||||
208.40.232.70 permit
|
||||
208.43.21.28/30 permit
|
||||
@@ -1758,8 +1867,10 @@
|
||||
208.71.42.212/31 permit
|
||||
208.71.42.214 permit
|
||||
208.72.249.240/29 permit
|
||||
208.74.204.0/22 permit
|
||||
208.74.204.9 permit
|
||||
208.75.120.0/22 permit
|
||||
208.75.121.246 permit
|
||||
208.75.122.246 permit
|
||||
208.82.237.96/29 permit
|
||||
208.82.237.104/31 permit
|
||||
@@ -1773,14 +1884,12 @@
|
||||
209.46.117.168 permit
|
||||
209.46.117.179 permit
|
||||
209.61.151.0/24 permit
|
||||
209.61.151.236 permit
|
||||
209.61.151.249 permit
|
||||
209.61.151.251 permit
|
||||
209.67.98.46 permit
|
||||
209.67.98.59 permit
|
||||
209.85.128.0/17 permit
|
||||
212.4.136.0/26 permit
|
||||
212.25.240.80 permit
|
||||
212.25.240.83 permit
|
||||
212.25.240.84/31 permit
|
||||
212.25.240.88 permit
|
||||
212.82.96.0/24 permit
|
||||
212.82.96.32/27 permit
|
||||
212.82.96.64/29 permit
|
||||
@@ -1821,8 +1930,12 @@
|
||||
212.82.111.228/31 permit
|
||||
212.82.111.230 permit
|
||||
212.123.28.40 permit
|
||||
213.167.75.0/25 permit
|
||||
213.167.81.0/25 permit
|
||||
212.227.15.0/24 permit
|
||||
212.227.15.0/25 permit
|
||||
212.227.17.0/27 permit
|
||||
212.227.126.128/25 permit
|
||||
213.46.255.0/24 permit
|
||||
213.165.64.0/23 permit
|
||||
213.199.128.139 permit
|
||||
213.199.128.145 permit
|
||||
213.199.138.181 permit
|
||||
@@ -1861,6 +1974,10 @@
|
||||
216.46.168.0/24 permit
|
||||
216.58.192.0/19 permit
|
||||
216.66.217.240/29 permit
|
||||
216.71.138.33 permit
|
||||
216.71.152.207 permit
|
||||
216.71.154.29 permit
|
||||
216.71.155.89 permit
|
||||
216.74.162.13 permit
|
||||
216.74.162.14 permit
|
||||
216.82.240.0/20 permit
|
||||
@@ -1870,33 +1987,48 @@
|
||||
216.109.114.0/24 permit
|
||||
216.109.114.32/27 permit
|
||||
216.109.114.64/29 permit
|
||||
216.113.160.0/24 permit
|
||||
216.113.172.0/25 permit
|
||||
216.113.175.0/24 permit
|
||||
216.128.126.97 permit
|
||||
216.136.162.65 permit
|
||||
216.136.162.120/29 permit
|
||||
216.136.168.80/28 permit
|
||||
216.145.221.0/24 permit
|
||||
216.198.0.0/18 permit
|
||||
216.203.30.55 permit
|
||||
216.203.33.178/31 permit
|
||||
216.205.24.0/24 permit
|
||||
216.239.32.0/19 permit
|
||||
217.72.192.64/26 permit
|
||||
217.72.192.248/29 permit
|
||||
217.72.207.0/27 permit
|
||||
217.77.141.52 permit
|
||||
217.77.141.59 permit
|
||||
217.175.194.0/24 permit
|
||||
222.73.195.64/29 permit
|
||||
223.165.113.0/24 permit
|
||||
223.165.115.0/24 permit
|
||||
223.165.118.0/23 permit
|
||||
223.165.120.0/23 permit
|
||||
2001:0868:0100:0600::/64 permit
|
||||
2001:4860:4000::/36 permit
|
||||
2001:748:100:40::2:0/112 permit
|
||||
2404:6800:4000::/36 permit
|
||||
2603:1010:3:3::5b permit
|
||||
2603:1020:201:10::10f permit
|
||||
2603:1030:20e:3::23c permit
|
||||
2603:1030:b:3::152 permit
|
||||
2603:1030:c02:8::14 permit
|
||||
2607:f8b0:4000::/36 permit
|
||||
2620:109:c003:104::215 permit
|
||||
2620:109:c003:104::/64 permit
|
||||
2620:109:c006:104::215 permit
|
||||
2620:109:c003:104::215 permit
|
||||
2620:109:c006:104::/64 permit
|
||||
2620:109:c006:104::215 permit
|
||||
2620:109:c00d:104::/64 permit
|
||||
2620:10d:c090:450::120 permit
|
||||
2620:10d:c091:450::16 permit
|
||||
2620:119:50c0:207::215 permit
|
||||
2620:10d:c090:400::8:1 permit
|
||||
2620:10d:c091:400::8:1 permit
|
||||
2620:119:50c0:207::/64 permit
|
||||
2620:119:50c0:207::215 permit
|
||||
2800:3f0:4000::/36 permit
|
||||
194.25.134.0/24 permit # t-online.de
|
||||
|
||||
@@ -27,4 +27,5 @@
|
||||
#197518 2 #Rackmarkt SL, Spain
|
||||
#197695 2 #Domain names registrar REG.RU Ltd, Russia
|
||||
#198068 2 #P.A.G.M. OU, Estonia
|
||||
#201942 5 #Soltia Consulting SL, Spain
|
||||
#201942 5 #Soltia Consulting SL, Spain
|
||||
#213373 4 #IP Connect Inc
|
||||
@@ -68,3 +68,39 @@ WL_FWD_HOST {
|
||||
ENCRYPTED_CHAT {
|
||||
expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP";
|
||||
}
|
||||
|
||||
CLAMD_SPAM_FOUND {
|
||||
expression = "CLAM_SECI_SPAM & !MAILCOW_WHITE";
|
||||
description = "Probably Spam, Securite Spam Flag set through ClamAV";
|
||||
score = 5;
|
||||
}
|
||||
|
||||
CLAMD_BAD_PDF {
|
||||
expression = "CLAM_SECI_PDF & !MAILCOW_WHITE";
|
||||
description = "Bad PDF Found, Securite bad PDF Flag set through ClamAV";
|
||||
score = 8;
|
||||
}
|
||||
|
||||
CLAMD_BAD_JPG {
|
||||
expression = "CLAM_SECI_JPG & !MAILCOW_WHITE";
|
||||
description = "Bad JPG Found, Securite bad JPG Flag set through ClamAV";
|
||||
score = 8;
|
||||
}
|
||||
|
||||
CLAMD_ASCII_MALWARE {
|
||||
expression = "CLAM_SECI_ASCII & !MAILCOW_WHITE";
|
||||
description = "ASCII malware found, Securite ASCII malware Flag set through ClamAV";
|
||||
score = 8;
|
||||
}
|
||||
|
||||
CLAMD_HTML_MALWARE {
|
||||
expression = "CLAM_SECI_HTML & !MAILCOW_WHITE";
|
||||
description = "HTML malware found, Securite HTML malware Flag set through ClamAV";
|
||||
score = 8;
|
||||
}
|
||||
|
||||
CLAMD_JS_MALWARE {
|
||||
expression = "CLAM_SECI_JS & !MAILCOW_WHITE";
|
||||
description = "JS malware found, Securite JS malware Flag set through ClamAV";
|
||||
score = 8;
|
||||
}
|
||||
@@ -221,6 +221,16 @@ rspamd_config:register_symbol({
|
||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||
|
||||
local function remove_moo_tag()
|
||||
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
||||
if moo_tag_header then
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['X-Moo-Tag'] = 0},
|
||||
})
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
|
||||
local tag = tagged_rcpt[1].options[1]
|
||||
rspamd_logger.infox("found tag: %s", tag)
|
||||
@@ -229,6 +239,7 @@ rspamd_config:register_symbol({
|
||||
|
||||
if action ~= 'no action' and action ~= 'greylist' then
|
||||
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
||||
remove_moo_tag()
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -243,6 +254,7 @@ rspamd_config:register_symbol({
|
||||
local function tag_callback_subfolder(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_logger.infox("Add X-Moo-Tag header")
|
||||
task:set_milter_reply({
|
||||
@@ -261,6 +273,7 @@ rspamd_config:register_symbol({
|
||||
)
|
||||
if not redis_ret_subfolder then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
end
|
||||
|
||||
else
|
||||
@@ -268,7 +281,10 @@ rspamd_config:register_symbol({
|
||||
local sbj = task:get_header('Subject')
|
||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['Subject'] = 1},
|
||||
remove_headers = {
|
||||
['Subject'] = 1,
|
||||
['X-Moo-Tag'] = 0
|
||||
},
|
||||
add_headers = {['Subject'] = new_sbj}
|
||||
})
|
||||
end
|
||||
@@ -284,6 +300,7 @@ rspamd_config:register_symbol({
|
||||
)
|
||||
if not redis_ret_subject then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
end
|
||||
|
||||
end
|
||||
@@ -295,6 +312,7 @@ rspamd_config:register_symbol({
|
||||
if #rcpt_split == 2 then
|
||||
if rcpt_split[1] == 'postmaster' then
|
||||
rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
@@ -307,7 +325,8 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
remove_moo_tag()
|
||||
end
|
||||
end,
|
||||
priority = 19
|
||||
@@ -340,6 +359,10 @@ rspamd_config:register_symbol({
|
||||
if not bcc_dest then
|
||||
return -- stop
|
||||
end
|
||||
-- dot stuff content before sending
|
||||
local email_content = tostring(task:get_content())
|
||||
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
||||
-- send mail
|
||||
lua_smtp.sendmail({
|
||||
task = task,
|
||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||
@@ -347,8 +370,8 @@ rspamd_config:register_symbol({
|
||||
from = task:get_from(stp)[1].addr,
|
||||
recipients = bcc_dest,
|
||||
helo = 'bcc',
|
||||
timeout = 10,
|
||||
}, task:get_content(), sendmail_cb)
|
||||
timeout = 20,
|
||||
}, email_content, sendmail_cb)
|
||||
end
|
||||
|
||||
-- determine from
|
||||
@@ -499,3 +522,150 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'MOO_FOOTER',
|
||||
type = 'prefilter',
|
||||
callback = function(task)
|
||||
local lua_mime = require "lua_mime"
|
||||
local lua_util = require "lua_util"
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local rspamd_redis = require "rspamd_redis"
|
||||
local ucl = require "ucl"
|
||||
local redis_params = rspamd_parse_redis_server('footer')
|
||||
local envfrom = task:get_from(1)
|
||||
local uname = task:get_user()
|
||||
if not envfrom or not uname then
|
||||
return false
|
||||
end
|
||||
local uname = uname:lower()
|
||||
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
||||
|
||||
local function newline(task)
|
||||
local t = task:get_newlines_type()
|
||||
|
||||
if t == 'cr' then
|
||||
return '\r'
|
||||
elseif t == 'lf' then
|
||||
return '\n'
|
||||
end
|
||||
|
||||
return '\r\n'
|
||||
end
|
||||
local function redis_cb_footer(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||
else
|
||||
-- parse json string
|
||||
local parser = ucl.parser()
|
||||
local res,err = parser:parse_string(data)
|
||||
if not res then
|
||||
rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||
else
|
||||
local footer = parser:get_object()
|
||||
|
||||
if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then
|
||||
rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s", uname, footer.html, footer.plain)
|
||||
|
||||
local envfrom_mime = task:get_from(2)
|
||||
local from_name = ""
|
||||
if envfrom_mime and envfrom_mime[1].name then
|
||||
from_name = envfrom_mime[1].name
|
||||
elseif envfrom and envfrom[1].name then
|
||||
from_name = envfrom[1].name
|
||||
end
|
||||
|
||||
local replacements = {
|
||||
auth_user = uname,
|
||||
from_user = envfrom[1].user,
|
||||
from_name = from_name,
|
||||
from_addr = envfrom[1].addr,
|
||||
from_domain = envfrom[1].domain:lower()
|
||||
}
|
||||
if footer.html then
|
||||
footer.html = lua_util.jinja_template(footer.html, replacements, true)
|
||||
end
|
||||
if footer.plain then
|
||||
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
|
||||
end
|
||||
|
||||
-- add footer
|
||||
local out = {}
|
||||
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
||||
|
||||
local seen_cte
|
||||
local newline_s = newline(task)
|
||||
|
||||
local function rewrite_ct_cb(name, hdr)
|
||||
if rewrite.need_rewrite_ct then
|
||||
if name:lower() == 'content-type' then
|
||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
||||
out[#out + 1] = nct
|
||||
return
|
||||
elseif name:lower() == 'content-transfer-encoding' then
|
||||
out[#out + 1] = string.format('%s: %s',
|
||||
'Content-Transfer-Encoding', 'quoted-printable')
|
||||
seen_cte = true
|
||||
return
|
||||
end
|
||||
end
|
||||
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
||||
end
|
||||
|
||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
||||
|
||||
if not seen_cte and rewrite.need_rewrite_ct then
|
||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
||||
end
|
||||
|
||||
-- End of headers
|
||||
out[#out + 1] = newline_s
|
||||
|
||||
if rewrite.out then
|
||||
for _,o in ipairs(rewrite.out) do
|
||||
out[#out + 1] = o
|
||||
end
|
||||
else
|
||||
out[#out + 1] = task:get_rawbody()
|
||||
end
|
||||
local out_parts = {}
|
||||
for _,o in ipairs(out) do
|
||||
if type(o) ~= 'table' then
|
||||
out_parts[#out_parts + 1] = o
|
||||
out_parts[#out_parts + 1] = newline_s
|
||||
else
|
||||
local removePrefix = "--\x0D\x0AContent-Type"
|
||||
if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then
|
||||
o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1)
|
||||
end
|
||||
out_parts[#out_parts + 1] = o[1]
|
||||
if o[2] then
|
||||
out_parts[#out_parts + 1] = newline_s
|
||||
end
|
||||
end
|
||||
end
|
||||
task:set_message(out_parts)
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_footer = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
env_from_domain, -- hash key
|
||||
false, -- is write
|
||||
redis_cb_footer, --callback
|
||||
'HGET', -- command
|
||||
{"DOMAIN_WIDE_FOOTER", env_from_domain} -- arguments
|
||||
)
|
||||
if not redis_ret_footer then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load footer for domain")
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
priority = 1
|
||||
})
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
//SoDebugBaseURL = YES;
|
||||
//ImapDebugEnabled = YES;
|
||||
//SOGoEASDebugEnabled = YES;
|
||||
SOGoEASSearchInBody = YES; // Experimental. Enabled since 2023-10
|
||||
//LDAPDebugEnabled = YES;
|
||||
//PGDebugEnabled = YES;
|
||||
//MySQL4DebugEnabled = YES;
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
<pre>BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all</pre>
|
||||
<pre>docker compose down --volumes ; docker compose up -d</pre>
|
||||
<p>Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">here</a> for a list.</p>
|
||||
<br>Click to learn more about <a style="color:red;text-decoration:none;" href="https://mailcow.github.io/mailcow-dockerized-docs/#get-support" target="_blank">getting support.</a>
|
||||
<br>Click to learn more about <a style="color:red;text-decoration:none;" href="https://docs.mailcow.email/#get-support" target="_blank">getting support.</a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -80,6 +80,11 @@ foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map) {
|
||||
];
|
||||
}
|
||||
|
||||
// cors settings
|
||||
$cors_settings = cors('get');
|
||||
$cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']);
|
||||
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
|
||||
|
||||
$template = 'admin.twig';
|
||||
$template_data = [
|
||||
'tfa_data' => $tfa_data,
|
||||
@@ -103,9 +108,11 @@ $template_data = [
|
||||
'rsettings' => $rsettings,
|
||||
'rspamd_regex_maps' => $rspamd_regex_maps,
|
||||
'logo_specs' => customize('get', 'main_logo_specs'),
|
||||
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
|
||||
'ip_check' => customize('get', 'ip_check'),
|
||||
'password_complexity' => password_complexity('get'),
|
||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||
'cors_settings' => $cors_settings,
|
||||
'lang_admin' => json_encode($lang['admin']),
|
||||
'lang_datatables' => json_encode($lang['datatables'])
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
openapi: 3.0.0
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
description: >-
|
||||
mailcow is complete e-mailing solution with advanced antispam, antivirus,
|
||||
@@ -5602,6 +5602,50 @@ 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/cors:
|
||||
post:
|
||||
responses:
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
- type: "success"
|
||||
log: ["cors", "edit", {"allowed_origins": ["*", "mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}]
|
||||
msg: "cors_headers_edited"
|
||||
description: OK
|
||||
headers: { }
|
||||
tags:
|
||||
- Cross-Origin Resource Sharing (CORS)
|
||||
description: >-
|
||||
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
|
||||
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
|
||||
By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain.
|
||||
operationId: Edit Cross-Origin Resource Sharing (CORS) settings
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
example:
|
||||
attr:
|
||||
allowed_origins: ["*", "mail.mailcow.tld"]
|
||||
allowed_methods: ["POST", "GET", "DELETE", "PUT"]
|
||||
properties:
|
||||
attr:
|
||||
type: object
|
||||
properties:
|
||||
allowed_origins:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
allowed_methods:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
summary: Edit Cross-Origin Resource Sharing (CORS) settings
|
||||
|
||||
tags:
|
||||
- name: Domains
|
||||
@@ -5646,3 +5690,5 @@ tags:
|
||||
description: Get the status of your cow
|
||||
- name: Ratelimits
|
||||
description: Edit domain ratelimits
|
||||
- name: Cross-Origin Resource Sharing (CORS)
|
||||
description: Manage Cross-Origin Resource Sharing (CORS) settings
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
window.ui = SwaggerUIBundle({
|
||||
urls: [{url: "/api/openapi.yaml", name: "mailcow API"}],
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
@@ -15,5 +15,4 @@ window.onload = function() {
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -42,11 +42,6 @@ table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before,
|
||||
table.dataTable td.dt-control:before {
|
||||
background-color: #979797 !important;
|
||||
}
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
||||
background-color: #fbfbfb;
|
||||
}
|
||||
table.dataTable.table-striped>tbody>tr>td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -357,6 +357,7 @@ button[aria-expanded='true'] > .caret {
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 16px;
|
||||
background-color: #d5d5d5;
|
||||
}
|
||||
|
||||
@@ -370,3 +371,22 @@ 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;
|
||||
}
|
||||
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
|
||||
color: #fff;
|
||||
background-color: #555;
|
||||
background-image: none;
|
||||
border-color: #4d4d4d;
|
||||
}
|
||||
.btn-check:checked+.btn-light:focus, .btn-check:active+.btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show>.btn-light.dropdown-toggle:focus,
|
||||
.btn-check:focus+.btn-light, .btn-light:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-group>.btn:not(:last-of-type) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.badge.bg-info > a,
|
||||
.badge.bg-danger > a {
|
||||
color: #fff !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.responsive-tabs .tab-pane {
|
||||
.responsive-tabs .tab-pane:not(.rsettings) {
|
||||
display: block !important;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -206,6 +206,19 @@
|
||||
.senders-mw220 {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
|
||||
table.dataTable td.dt-control:before {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
line-height: 2rem;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
li .dtr-data {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 350px) {
|
||||
|
||||
@@ -1,90 +1,128 @@
|
||||
body {
|
||||
background-color: #414141;
|
||||
color: #e0e0e0;
|
||||
background-color: #1c1c1e;
|
||||
color: #f2f2f7;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid #1c1c1c;
|
||||
background-color: #3a3a3a;
|
||||
border: 1px solid #2c2c2e;
|
||||
background-color: #2c2c2e;
|
||||
}
|
||||
|
||||
legend {
|
||||
color: #f5f5f5;
|
||||
color: #f2f2f7;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
color: #bbb;
|
||||
background-color: #2c2c2c;
|
||||
color: #8e8e93;
|
||||
background-color: #1c1c1e;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
--bs-card-color: #bbb;
|
||||
}
|
||||
|
||||
.btn-secondary, .paginate_button, .page-link, .btn-light {
|
||||
color: #fff !important;
|
||||
background-color: #7a7a7a !important;
|
||||
border-color: #5c5c5c !important;
|
||||
color: #f2f2f7 !important;
|
||||
background-color: #5e5e5e !important;
|
||||
border-color: #4c4c4e !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;
|
||||
}
|
||||
.alert-secondary {
|
||||
color: #fff !important;
|
||||
background-color: #7a7a7a !important;
|
||||
border-color: #5c5c5c !important;
|
||||
}
|
||||
.bg-secondary {
|
||||
color: #fff !important;
|
||||
background-color: #7a7a7a !important;
|
||||
}
|
||||
.alert-secondary, .alert-secondary a, .alert-secondary .alert-link {
|
||||
color: #fff;
|
||||
}
|
||||
.page-item.active .page-link {
|
||||
background-color: #158cba !important;
|
||||
border-color: #127ba3 !important;
|
||||
color: #f2f2f7 !important;
|
||||
background-color: #242424 !important;
|
||||
border-color: #1c1c1e !important;
|
||||
}
|
||||
|
||||
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
|
||||
background-color: #7a7a7a;
|
||||
border-color: #5c5c5c !important;
|
||||
color: #fff;
|
||||
background-color: #444444;
|
||||
border-color: #4c4c4e !important;
|
||||
color: #f2f2f7;
|
||||
}
|
||||
|
||||
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
|
||||
border-color: #5e5e5e !important;
|
||||
}
|
||||
|
||||
.alert-secondary {
|
||||
color: #f2f2f7 !important;
|
||||
background-color: #5e5e5e !important;
|
||||
border-color: #4c4c4e !important;
|
||||
}
|
||||
|
||||
.bg-secondary {
|
||||
color: #f2f2f7 !important;
|
||||
background-color: #5e5e5e !important;
|
||||
}
|
||||
|
||||
.alert-secondary, .alert-secondary a, .alert-secondary .alert-link {
|
||||
color: #f2f2f7;
|
||||
}
|
||||
|
||||
.page-item.active .page-link {
|
||||
background-color: #3e3e3e !important;
|
||||
border-color: #3e3e3e !important;
|
||||
}
|
||||
|
||||
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
|
||||
background-color: #5e5e5e;
|
||||
border-color: #4c4c4e !important;
|
||||
color: #f2f2f7;
|
||||
}
|
||||
|
||||
.btn-secondary:disabled, .btn-secondary.disabled {
|
||||
border-color: #7a7a7a !important;
|
||||
border-color: #5e5e5e !important;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #414141;
|
||||
--bs-modal-color: #bbb;
|
||||
background-color: #2c2c2e;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 1px solid #161616;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
color: white;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.modal .btn-close {
|
||||
filter: invert(1) grayscale(100%) brightness(200%);
|
||||
}
|
||||
|
||||
.navbar.bg-light {
|
||||
background-color: #222222 !important;
|
||||
border-color: #181818;
|
||||
background-color: #1c1c1e !important;
|
||||
border-color: #2c2c2e;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #ccc !important;
|
||||
color: #8e8e93 !important;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.nav-tabs, .nav-tabs .nav-link {
|
||||
border-color: #444444 !important;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active {
|
||||
border-bottom-color: #414141;
|
||||
border-bottom-color: #1c1c1e !important;
|
||||
}
|
||||
|
||||
.card .nav-tabs .nav-link:not(.disabled):hover, .card .nav-tabs .nav-link:not(.disabled):focus, .card .nav-tabs .nav-link.active {
|
||||
border-bottom-color: #2c2c2e !important;
|
||||
}
|
||||
|
||||
.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
|
||||
color: #ccc !important;
|
||||
color: #f2f2f7 !important;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
background-color: #585858;
|
||||
border: 1px solid #333;
|
||||
background-color: #424242;
|
||||
border: 1px solid #282828;
|
||||
}
|
||||
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
|
||||
color: #fafafa;
|
||||
@@ -97,7 +135,7 @@ legend {
|
||||
color: #d4d4d4 !important;
|
||||
}
|
||||
tbody tr {
|
||||
color: #555;
|
||||
color: #ccc;
|
||||
}
|
||||
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {
|
||||
color: #ccc;
|
||||
@@ -106,18 +144,15 @@ tbody tr {
|
||||
color: #ccc;
|
||||
}
|
||||
.list-group-item {
|
||||
background-color: #333;
|
||||
background-color: #282828;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
.table-striped>tbody>tr:nth-of-type(odd) {
|
||||
background-color: #333;
|
||||
background-color: #424242;
|
||||
}
|
||||
table.dataTable>tbody>tr.child ul.dtr-details>li {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.13);
|
||||
}
|
||||
tbody tr {
|
||||
color: #ccc;
|
||||
}
|
||||
.label.label-last-login {
|
||||
color: #ccc !important;
|
||||
background-color: #555 !important;
|
||||
@@ -133,20 +168,20 @@ div.numberedtextarea-number {
|
||||
}
|
||||
.well {
|
||||
border: 1px solid #555;
|
||||
background-color: #333;
|
||||
background-color: #282828;
|
||||
}
|
||||
pre {
|
||||
color: #ccc;
|
||||
background-color: #333;
|
||||
background-color: #282828;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
input.form-control, textarea.form-control {
|
||||
color: #e2e2e2 !important;
|
||||
background-color: #555 !important;
|
||||
background-color: #424242 !important;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
input.form-control:focus, textarea.form-control {
|
||||
background-color: #555 !important;
|
||||
background-color: #424242 !important;
|
||||
}
|
||||
input.form-control:disabled, textarea.form-disabled {
|
||||
color: #a8a8a8 !important;
|
||||
@@ -154,16 +189,14 @@ input.form-control:disabled, textarea.form-disabled {
|
||||
}
|
||||
.input-group-addon {
|
||||
color: #ccc;
|
||||
background-color: #555 !important;
|
||||
background-color: #424242 !important;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.input-group-text {
|
||||
color: #ccc;
|
||||
background-color: #242424;
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.list-group-item {
|
||||
color: #ccc;
|
||||
}
|
||||
@@ -175,11 +208,11 @@ input.form-control:disabled, textarea.form-disabled {
|
||||
}
|
||||
.dropdown-item.active:hover {
|
||||
color: #fff !important;
|
||||
background-color: #31b1e4;
|
||||
background-color: #007aff;
|
||||
}
|
||||
.form-select {
|
||||
color: #e2e2e2!important;
|
||||
background-color: #555!important;
|
||||
background-color: #424242!important;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
@@ -191,31 +224,6 @@ input.form-control:disabled, textarea.form-disabled {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
|
||||
.table-secondary {
|
||||
--bs-table-bg: #7a7a7a;
|
||||
--bs-table-striped-bg: #e4e4e4;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #d8d8d8;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #dedede;
|
||||
--bs-table-hover-color: #000;
|
||||
color: #000;
|
||||
border-color: #d8d8d8;
|
||||
}
|
||||
|
||||
.table-light {
|
||||
--bs-table-bg: #f6f6f6;
|
||||
--bs-table-striped-bg: #eaeaea;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #dddddd;
|
||||
--bs-table-active-color: #000;
|
||||
--bs-table-hover-bg: #e4e4e4;
|
||||
--bs-table-hover-color: #000;
|
||||
color: #000;
|
||||
border-color: #dddddd;
|
||||
}
|
||||
|
||||
.form-control-plaintext {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
@@ -289,12 +297,12 @@ a:hover {
|
||||
}
|
||||
|
||||
.tag-box {
|
||||
background-color: #555;
|
||||
border: 1px solid #999;
|
||||
background-color: #282828;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
.tag-input {
|
||||
color: #fff;
|
||||
background-color: #555;
|
||||
background-color: #282828;
|
||||
}
|
||||
.tag-add {
|
||||
color: #ccc;
|
||||
@@ -303,43 +311,24 @@ a:hover {
|
||||
color: #d1d1d1;
|
||||
}
|
||||
|
||||
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
||||
background-color: #7a7a7a !important;
|
||||
}
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
|
||||
background-color: #7a7a7a !important;
|
||||
border: 1.5px solid #5c5c5c !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
|
||||
background-color: #949494;
|
||||
}
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
||||
background-color: #444444;
|
||||
}
|
||||
|
||||
.btn-check-label {
|
||||
color: #fff;
|
||||
}
|
||||
.btn-outline-secondary:hover {
|
||||
background-color: #c3c3c3;
|
||||
background-color: #5c5c5c;
|
||||
}
|
||||
.btn.btn-outline-secondary {
|
||||
color: #fff !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-color: #7a7a7a !important;
|
||||
}
|
||||
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
||||
background-color: #9b9b9b !important;
|
||||
background-color: #7a7a7a !important;
|
||||
}
|
||||
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
|
||||
color: #f2f2f7 !important;
|
||||
background-color: #242424 !important;
|
||||
border-color: #1c1c1e !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn-input-missing,
|
||||
.btn-input-missing:hover,
|
||||
.btn-input-missing:active,
|
||||
@@ -347,27 +336,119 @@ table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
||||
.btn-input-missing:active:hover,
|
||||
.btn-input-missing:active:focus {
|
||||
color: #fff !important;
|
||||
background-color: #ff2f24 !important;
|
||||
border-color: #e21207 !important;
|
||||
background-color: #ff3b30 !important;
|
||||
border-color: #ff3b30 !important;
|
||||
}
|
||||
|
||||
.inputMissingAttr {
|
||||
border-color: #FF4136 !important;
|
||||
border-color: #ff4136 !important;
|
||||
}
|
||||
|
||||
|
||||
.list-group-details {
|
||||
background: #444444;
|
||||
background: #555;
|
||||
}
|
||||
.list-group-header {
|
||||
background: #333;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
span.mail-address-item {
|
||||
background-color: #333;
|
||||
background-color: #444;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #555;
|
||||
padding: 2px 7px;
|
||||
display: inline-block;
|
||||
margin: 2px 6px 2px 0;
|
||||
}
|
||||
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
||||
background-color: #7a7a7a !important;
|
||||
}
|
||||
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
|
||||
background-color: #7a7a7a !important;
|
||||
border: 1.5px solid #5c5c5c !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
|
||||
background-color: #949494;
|
||||
}
|
||||
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
|
||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
||||
background-color: #414141;
|
||||
}
|
||||
|
||||
table.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
||||
.table-secondary {
|
||||
--bs-table-bg: #282828;
|
||||
--bs-table-striped-bg: #343434;
|
||||
--bs-table-striped-color: #f2f2f7;
|
||||
--bs-table-active-bg: #4c4c4c;
|
||||
--bs-table-active-color: #f2f2f7;
|
||||
--bs-table-hover-bg: #3a3a3a;
|
||||
--bs-table-hover-color: #f2f2f7;
|
||||
color: #ccc;
|
||||
border-color: #3a3a3a;
|
||||
}
|
||||
|
||||
.table-light {
|
||||
--bs-table-bg: #3a3a3a;
|
||||
--bs-table-striped-bg: #444444;
|
||||
--bs-table-striped-color: #f2f2f7;
|
||||
--bs-table-active-bg: #5c5c5c;
|
||||
--bs-table-active-color: #f2f2f7;
|
||||
--bs-table-hover-bg: #4c4c4c;
|
||||
--bs-table-hover-color: #f2f2f7;
|
||||
color: #ccc;
|
||||
border-color: #4c4c4c;
|
||||
}
|
||||
|
||||
.table-bordered {
|
||||
border-color: #3a3a3a;
|
||||
}
|
||||
|
||||
.table-bordered th,
|
||||
.table-bordered td {
|
||||
border-color: #3a3a3a !important;
|
||||
}
|
||||
|
||||
.table-bordered thead th,
|
||||
.table-bordered thead td {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
|
||||
.table-striped>tbody>tr:nth-of-type(odd)>td,
|
||||
.table-striped>tbody>tr:nth-of-type(odd)>th {
|
||||
background-color: #282828;
|
||||
}
|
||||
|
||||
.table-hover>tbody>tr:hover {
|
||||
background-color: #343434;
|
||||
}
|
||||
|
||||
.table>:not(caption)>*>* {
|
||||
border-color: #5c5c5c;
|
||||
--bs-table-color-state:#bbb;
|
||||
--bs-table-bg: #3a3a3a;
|
||||
}
|
||||
.text-muted {
|
||||
--bs-secondary-color: #8e8e93;
|
||||
}
|
||||
input::placeholder {
|
||||
color: #8e8e93 !important;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%238e8e93' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
|
||||
}
|
||||
.btn-light, .btn-light:hover {
|
||||
background-image: none;
|
||||
}
|
||||
+9
-5
@@ -47,6 +47,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
$quota_notification_bcc = quota_notification_bcc('get', $domain);
|
||||
$rl = ratelimit('get', 'domain', $domain);
|
||||
$rlyhosts = relayhost('get');
|
||||
$domain_footer = mailbox('get', 'domain_wide_footer', $domain);
|
||||
$template = 'edit/domain.twig';
|
||||
$template_data = [
|
||||
'acl' => $_SESSION['acl'],
|
||||
@@ -56,23 +57,26 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'rlyhosts' => $rlyhosts,
|
||||
'dkim' => dkim('details', $domain),
|
||||
'domain_details' => $result,
|
||||
'domain_footer' => $domain_footer,
|
||||
];
|
||||
}
|
||||
}
|
||||
elseif (isset($_GET["template"])){
|
||||
$domain_template = mailbox('get', 'domain_templates', $_GET["template"]);
|
||||
elseif (isset($_GET['template'])){
|
||||
$domain_template = mailbox('get', 'domain_templates', $_GET['template']);
|
||||
if ($domain_template){
|
||||
$template_data = [
|
||||
'template' => $domain_template
|
||||
'template' => $domain_template,
|
||||
'rl' => ['frame' => $domain_template['attributes']['rl_frame']],
|
||||
];
|
||||
$template = 'edit/domain-templates.twig';
|
||||
$result = true;
|
||||
}
|
||||
else {
|
||||
$mailbox_template = mailbox('get', 'mailbox_templates', $_GET["template"]);
|
||||
$mailbox_template = mailbox('get', 'mailbox_templates', $_GET['template']);
|
||||
if ($mailbox_template){
|
||||
$template_data = [
|
||||
'template' => $mailbox_template
|
||||
'template' => $mailbox_template,
|
||||
'rl' => ['frame' => $mailbox_template['attributes']['rl_frame']],
|
||||
];
|
||||
$template = 'edit/mailbox-templates.twig';
|
||||
$result = true;
|
||||
|
||||
@@ -49,7 +49,9 @@ function bcc($_action, $_data = null, $_attr = null) {
|
||||
}
|
||||
elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) {
|
||||
$mailbox = mailbox('get', 'mailbox_details', $local_dest);
|
||||
if ($mailbox === false && array_key_exists($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) {
|
||||
$shared_aliases = mailbox('get', 'shared_aliases');
|
||||
$direct_aliases = mailbox('get', 'direct_aliases');
|
||||
if ($mailbox === false && in_array($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $_attr),
|
||||
|
||||
@@ -24,9 +24,10 @@ function customize($_action, $_item, $_data = null) {
|
||||
}
|
||||
switch ($_item) {
|
||||
case 'main_logo':
|
||||
if (in_array($_data['main_logo']['type'], array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png', 'image/png', 'image/svg+xml'))) {
|
||||
case 'main_logo_dark':
|
||||
if (in_array($_data[$_item]['type'], array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png', 'image/png', 'image/svg+xml'))) {
|
||||
try {
|
||||
if (file_exists($_data['main_logo']['tmp_name']) !== true) {
|
||||
if (file_exists($_data[$_item]['tmp_name']) !== true) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
@@ -34,7 +35,7 @@ function customize($_action, $_item, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$image = new Imagick($_data['main_logo']['tmp_name']);
|
||||
$image = new Imagick($_data[$_item]['tmp_name']);
|
||||
if ($image->valid() !== true) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -63,7 +64,7 @@ function customize($_action, $_item, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$redis->Set('MAIN_LOGO', 'data:' . $_data['main_logo']['type'] . ';base64,' . base64_encode(file_get_contents($_data['main_logo']['tmp_name'])));
|
||||
$redis->Set(strtoupper($_item), 'data:' . $_data[$_item]['type'] . ';base64,' . base64_encode(file_get_contents($_data[$_item]['tmp_name'])));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -201,8 +202,9 @@ function customize($_action, $_item, $_data = null) {
|
||||
}
|
||||
switch ($_item) {
|
||||
case 'main_logo':
|
||||
case 'main_logo_dark':
|
||||
try {
|
||||
if ($redis->del('MAIN_LOGO')) {
|
||||
if ($redis->del(strtoupper($_item))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
@@ -239,8 +241,9 @@ function customize($_action, $_item, $_data = null) {
|
||||
return ($app_links) ? $app_links : false;
|
||||
break;
|
||||
case 'main_logo':
|
||||
case 'main_logo_dark':
|
||||
try {
|
||||
return $redis->get('MAIN_LOGO');
|
||||
return $redis->get(strtoupper($_item));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -277,9 +280,14 @@ function customize($_action, $_item, $_data = null) {
|
||||
}
|
||||
break;
|
||||
case 'main_logo_specs':
|
||||
case 'main_logo_dark_specs':
|
||||
try {
|
||||
$image = new Imagick();
|
||||
$img_data = explode('base64,', customize('get', 'main_logo'));
|
||||
if($_item == 'main_logo_specs') {
|
||||
$img_data = explode('base64,', customize('get', 'main_logo'));
|
||||
} else {
|
||||
$img_data = explode('base64,', customize('get', 'main_logo_dark'));
|
||||
}
|
||||
if ($img_data[1]) {
|
||||
$image->readImageBlob(base64_decode($img_data[1]));
|
||||
return $image->identifyImage();
|
||||
|
||||
@@ -192,5 +192,16 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
case 'broadcast':
|
||||
$request = array(
|
||||
"api_call" => "container_post",
|
||||
"container_name" => $service_name,
|
||||
"post_action" => $attr1,
|
||||
"request" => $attr2
|
||||
);
|
||||
|
||||
$redis->publish("MC_CHANNEL", json_encode($request));
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,8 +526,9 @@ function logger($_data = false) {
|
||||
':remote' => get_remote_ip()
|
||||
));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Do nothing
|
||||
catch (PDOException $e) {
|
||||
# handle the exception here, as the exception handler function results in a white page
|
||||
error_log($e->getMessage(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2131,6 +2132,120 @@ function rspamd_ui($action, $data = null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
function cors($action, $data = null) {
|
||||
global $redis;
|
||||
|
||||
switch ($action) {
|
||||
case "edit":
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
|
||||
$allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins;
|
||||
foreach ($allowed_origins as $origin) {
|
||||
if (!filter_var($origin, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) && $origin != '*') {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => 'cors_invalid_origin'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$allowed_methods = isset($data['allowed_methods']) ? $data['allowed_methods'] : array('GET', 'POST', 'PUT', 'DELETE');
|
||||
$allowed_methods = !is_array($allowed_methods) ? array_map('trim', preg_split( "/( |,|;|\n)/", $allowed_methods)) : $allowed_methods;
|
||||
$available_methods = array('GET', 'POST', 'PUT', 'DELETE');
|
||||
foreach ($allowed_methods as $method) {
|
||||
if (!in_array($method, $available_methods)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => 'cors_invalid_method'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$redis->hMSet('CORS_SETTINGS', array(
|
||||
'allowed_origins' => implode(', ', $allowed_origins),
|
||||
'allowed_methods' => implode(', ', $allowed_methods)
|
||||
));
|
||||
} catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => 'cors_headers_edited'
|
||||
);
|
||||
return true;
|
||||
break;
|
||||
case "get":
|
||||
try {
|
||||
$cors_settings = $redis->hMGet('CORS_SETTINGS', array('allowed_origins', 'allowed_methods'));
|
||||
} catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
}
|
||||
|
||||
$cors_settings = !$cors_settings ? array('allowed_origins' => $_SERVER['SERVER_NAME'], 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings;
|
||||
$cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? $_SERVER['SERVER_NAME'] : $cors_settings['allowed_origins'];
|
||||
$cors_settings['allowed_methods'] = empty($cors_settings['allowed_methods']) ? 'GET, POST, PUT, DELETE, OPTION' : $cors_settings['allowed_methods'];
|
||||
|
||||
return $cors_settings;
|
||||
break;
|
||||
case "set_headers":
|
||||
$cors_settings = cors('get');
|
||||
// check if requested origin is in allowed origins
|
||||
$allowed_origins = explode(', ', $cors_settings['allowed_origins']);
|
||||
$cors_settings['allowed_origins'] = $allowed_origins[0];
|
||||
if (in_array('*', $allowed_origins)){
|
||||
$cors_settings['allowed_origins'] = '*';
|
||||
} else if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
||||
$cors_settings['allowed_origins'] = $_SERVER['HTTP_ORIGIN'];
|
||||
}
|
||||
// always allow OPTIONS for preflight request
|
||||
$cors_settings["allowed_methods"] = empty($cors_settings["allowed_methods"]) ? 'OPTIONS' : $cors_settings["allowed_methods"] . ', ' . 'OPTIONS';
|
||||
|
||||
header('Access-Control-Allow-Origin: ' . $cors_settings['allowed_origins']);
|
||||
header('Access-Control-Allow-Methods: '. $cors_settings['allowed_methods']);
|
||||
header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin');
|
||||
|
||||
// Access-Control settings requested, this is just a preflight request
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' &&
|
||||
isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) &&
|
||||
isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
|
||||
|
||||
$allowed_methods = explode(', ', $cors_settings["allowed_methods"]);
|
||||
if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true))
|
||||
// method allowed send 200 OK
|
||||
http_response_code(200);
|
||||
else
|
||||
// method not allowed send 405 METHOD NOT ALLOWED
|
||||
http_response_code(405);
|
||||
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function get_logs($application, $lines = false) {
|
||||
if ($lines === false) {
|
||||
|
||||
@@ -325,6 +325,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$timeout2 = intval($_data['timeout2']);
|
||||
$skipcrossduplicates = intval($_data['skipcrossduplicates']);
|
||||
$automap = intval($_data['automap']);
|
||||
$dry = intval($_data['dry']);
|
||||
$port1 = $_data['port1'];
|
||||
$host1 = strtolower($_data['host1']);
|
||||
$password1 = $_data['password1'];
|
||||
@@ -435,8 +436,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`)
|
||||
VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `dry`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`)
|
||||
VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :dry, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)");
|
||||
$stmt->execute(array(
|
||||
':user2' => $username,
|
||||
':custom_params' => $custom_params,
|
||||
@@ -450,6 +451,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':skipcrossduplicates' => $skipcrossduplicates,
|
||||
':maxbytespersecond' => $maxbytespersecond,
|
||||
':subscribeall' => $subscribeall,
|
||||
':dry' => $dry,
|
||||
':subfolder2' => $subfolder2,
|
||||
':host1' => $host1,
|
||||
':authmech1' => 'PLAIN',
|
||||
@@ -1250,9 +1252,27 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
));
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `user_acl`
|
||||
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
|
||||
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
|
||||
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
|
||||
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
':username' => $username,
|
||||
':spam_alias' => 0,
|
||||
':tls_policy' => 0,
|
||||
':spam_score' => 0,
|
||||
':spam_policy' => 0,
|
||||
':delimiter_action' => 0,
|
||||
':syncjobs' => 0,
|
||||
':eas_reset' => 0,
|
||||
':sogo_profile_reset' => 0,
|
||||
':pushover' => 0,
|
||||
':quarantine' => 0,
|
||||
':quarantine_attachments' => 0,
|
||||
':quarantine_notification' => 0,
|
||||
':quarantine_category' => 0,
|
||||
':app_passwds' => 0
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1533,20 +1553,20 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
|
||||
} else {
|
||||
$_data['acl'] = (array)$_data['acl'];
|
||||
$attr['acl_spam_alias'] = 1;
|
||||
$attr['acl_tls_policy'] = 1;
|
||||
$attr['acl_spam_score'] = 1;
|
||||
$attr['acl_spam_policy'] = 1;
|
||||
$attr['acl_delimiter_action'] = 1;
|
||||
$attr['acl_spam_alias'] = 0;
|
||||
$attr['acl_tls_policy'] = 0;
|
||||
$attr['acl_spam_score'] = 0;
|
||||
$attr['acl_spam_policy'] = 0;
|
||||
$attr['acl_delimiter_action'] = 0;
|
||||
$attr['acl_syncjobs'] = 0;
|
||||
$attr['acl_eas_reset'] = 1;
|
||||
$attr['acl_eas_reset'] = 0;
|
||||
$attr['acl_sogo_profile_reset'] = 0;
|
||||
$attr['acl_pushover'] = 1;
|
||||
$attr['acl_quarantine'] = 1;
|
||||
$attr['acl_quarantine_attachments'] = 1;
|
||||
$attr['acl_quarantine_notification'] = 1;
|
||||
$attr['acl_quarantine_category'] = 1;
|
||||
$attr['acl_app_passwds'] = 1;
|
||||
$attr['acl_pushover'] = 0;
|
||||
$attr['acl_quarantine'] = 0;
|
||||
$attr['acl_quarantine_attachments'] = 0;
|
||||
$attr['acl_quarantine_notification'] = 0;
|
||||
$attr['acl_quarantine_category'] = 0;
|
||||
$attr['acl_app_passwds'] = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2013,6 +2033,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$success = (isset($_data['success'])) ? NULL : $is_now['success'];
|
||||
$delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
|
||||
$subscribeall = (isset($_data['subscribeall'])) ? intval($_data['subscribeall']) : $is_now['subscribeall'];
|
||||
$dry = (isset($_data['dry'])) ? intval($_data['dry']) : $is_now['dry'];
|
||||
$delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
|
||||
$delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2'];
|
||||
$automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap'];
|
||||
@@ -2146,6 +2167,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`timeout1` = :timeout1,
|
||||
`timeout2` = :timeout2,
|
||||
`subscribeall` = :subscribeall,
|
||||
`dry` = :dry,
|
||||
`active` = :active
|
||||
WHERE `id` = :id");
|
||||
$stmt->execute(array(
|
||||
@@ -2171,6 +2193,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':timeout1' => $timeout1,
|
||||
':timeout2' => $timeout2,
|
||||
':subscribeall' => $subscribeall,
|
||||
':dry' => $dry,
|
||||
':active' => $active,
|
||||
));
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -3320,6 +3343,45 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'domain_wide_footer':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'domain_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$footers = array();
|
||||
$footers['html'] = isset($_data['footer_html']) ? $_data['footer_html'] : '';
|
||||
$footers['plain'] = isset($_data['footer_plain']) ? $_data['footer_plain'] : '';
|
||||
try {
|
||||
$redis->hSet('DOMAIN_WIDE_FOOTER', $domain, json_encode($footers));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('domain_footer_modified', htmlspecialchars($domain))
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'get':
|
||||
@@ -3965,6 +4027,39 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
return $aliasdomaindata;
|
||||
break;
|
||||
case 'shared_aliases':
|
||||
$shared_aliases = array();
|
||||
$stmt = $pdo->query("SELECT `address` FROM `alias`
|
||||
WHERE `goto` REGEXP ','
|
||||
AND `address` NOT LIKE '@%'
|
||||
AND `goto` != `address`");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$domain = explode("@", $row['address'])[1];
|
||||
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||
$shared_aliases[] = $row['address'];
|
||||
}
|
||||
}
|
||||
|
||||
return $shared_aliases;
|
||||
break;
|
||||
case 'direct_aliases':
|
||||
$direct_aliases = array();
|
||||
$stmt = $pdo->query("SELECT `address` FROM `alias`
|
||||
WHERE `goto` NOT LIKE '%,%'
|
||||
AND `address` NOT LIKE '@%'
|
||||
AND `goto` != `address`");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
while($row = array_shift($rows)) {
|
||||
$domain = explode("@", $row['address'])[1];
|
||||
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||
$direct_aliases[] = $row['address'];
|
||||
}
|
||||
}
|
||||
|
||||
return $direct_aliases;
|
||||
break;
|
||||
case 'domains':
|
||||
$domains = array();
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||
@@ -4399,6 +4494,40 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
return $resourcedata;
|
||||
break;
|
||||
case 'domain_wide_footer':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'domain_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$footers = $redis->hGet('DOMAIN_WIDE_FOOTER', $domain);
|
||||
$footers = json_decode($footers, true);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $footers;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
@@ -4897,13 +5026,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) {
|
||||
$maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part'];
|
||||
$exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir);
|
||||
$maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
|
||||
if ($maildir_gc['type'] != 'success') {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'warning',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg']
|
||||
);
|
||||
|
||||
if (getenv("CLUSTERMODE") == "replication") {
|
||||
// broadcast to each dovecot container
|
||||
docker('broadcast', 'dovecot-mailcow', 'exec', $exec_fields);
|
||||
} else {
|
||||
$maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
|
||||
if ($maildir_gc['type'] != 'success') {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'warning',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -4956,9 +5091,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
));
|
||||
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
|
||||
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as OR `send_as` = :send_as");
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
':logged_in_as' => $username,
|
||||
':send_as' => $username
|
||||
));
|
||||
// fk, better safe than sorry
|
||||
$stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username");
|
||||
|
||||
@@ -40,6 +40,7 @@ $globalVariables = [
|
||||
'ui_texts' => $UI_TEXTS,
|
||||
'css_path' => '/cache/'.basename($CSSPath),
|
||||
'logo' => customize('get', 'main_logo'),
|
||||
'logo_dark' => customize('get', 'main_logo_dark'),
|
||||
'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 = "14022023_1000";
|
||||
$db_version = "15112023_1536";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -704,6 +704,7 @@ function init_db_schema() {
|
||||
"timeout1" => "SMALLINT NOT NULL DEFAULT '600'",
|
||||
"timeout2" => "SMALLINT NOT NULL DEFAULT '600'",
|
||||
"subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"dry" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"returned_text" => "LONGTEXT",
|
||||
"last_run" => "TIMESTAMP NULL DEFAULT NULL",
|
||||
|
||||
+4
-4
@@ -19,10 +19,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
@@ -52,10 +52,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
|
||||
Vendored
+3
-3
@@ -12,7 +12,7 @@ jobs:
|
||||
dependency-version: [prefer-lowest, prefer-stable]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer update --no-progress --ignore-platform-reqs
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Install PHP with extensions"
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Install PHP with extensions"
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Set-up PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composercache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Run DOCtor-RST"
|
||||
uses: docker://oskarstark/doctor-rst
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
headline: lang.sieve_preset_8
|
||||
content: |
|
||||
require "fileinto";
|
||||
require "mailbox";
|
||||
require "variables";
|
||||
require "subaddress";
|
||||
require "envelope";
|
||||
require "duplicate";
|
||||
require "imap4flags";
|
||||
if header :matches "To" "*mail@domain.tld*" {
|
||||
redirect "anothermail@anotherdomain.tld";
|
||||
setflag "\\seen"; /* Mark mail as read */
|
||||
fileInto "INBOX/SubFolder"; /* Move mail on subfolder after */
|
||||
} else {
|
||||
# The rest goes into INBOX
|
||||
# default is "implicit keep", we do it explicitly here
|
||||
keep;
|
||||
}
|
||||
@@ -63,7 +63,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
unset($_SESSION['index_query_string']);
|
||||
if (in_array('mobileconfig', $http_parameters)) {
|
||||
if (in_array('only_email', $http_parameters)) {
|
||||
header("Location: /mobileconfig.php?email_only");
|
||||
header("Location: /mobileconfig.php?only_email");
|
||||
die();
|
||||
}
|
||||
header("Location: /mobileconfig.php");
|
||||
@@ -120,10 +120,14 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
|
||||
if (isset($_POST["submit_main_logo"])) {
|
||||
if ($_FILES['main_logo']['error'] == 0) {
|
||||
customize('add', 'main_logo', $_FILES);
|
||||
}
|
||||
if ($_FILES['main_logo_dark']['error'] == 0) {
|
||||
customize('add', 'main_logo_dark', $_FILES);
|
||||
}
|
||||
}
|
||||
if (isset($_POST["reset_main_logo"])) {
|
||||
customize('delete', 'main_logo');
|
||||
customize('delete', 'main_logo_dark');
|
||||
}
|
||||
// Some actions will not be available via API
|
||||
if (isset($_POST["license_validate_now"])) {
|
||||
|
||||
+93
-88
@@ -90,15 +90,18 @@ $AVAILABLE_LANGUAGES = array(
|
||||
'es-es' => 'Español (Spanish)',
|
||||
'fi-fi' => 'Suomi (Finish)',
|
||||
'fr-fr' => 'Français (French)',
|
||||
'gr-gr' => 'Ελληνικά (Greek)',
|
||||
'hu-hu' => 'Magyar (Hungarian)',
|
||||
'it-it' => 'Italiano (Italian)',
|
||||
'ko-kr' => '한국어 (Korean)',
|
||||
'lv-lv' => 'latviešu (Latvian)',
|
||||
'nl-nl' => 'Nederlands (Dutch)',
|
||||
'pl-pl' => 'Język Polski (Polish)',
|
||||
'pt-br' => 'Português brasileiro (Brazilian Portuguese)',
|
||||
'pt-pt' => 'Português (Portuguese)',
|
||||
'ro-ro' => 'Română (Romanian)',
|
||||
'ru-ru' => 'Pусский (Russian)',
|
||||
'si-si' => 'Slovenščina (Slovenian)',
|
||||
'sk-sk' => 'Slovenčina (Slovak)',
|
||||
'sv-se' => 'Svenska (Swedish)',
|
||||
'tr-tr' => 'Türkçe (Turkish)',
|
||||
@@ -233,118 +236,120 @@ $RSPAMD_MAPS = array(
|
||||
|
||||
$IMAPSYNC_OPTIONS = array(
|
||||
'whitelist' => array(
|
||||
'abort',
|
||||
'authmd51',
|
||||
'authmd52',
|
||||
'authmech1',
|
||||
'authmech2',
|
||||
'authuser1',
|
||||
'authuser2',
|
||||
'debugcontent',
|
||||
'disarmreadreceipts',
|
||||
'logdir',
|
||||
'debugcrossduplicates',
|
||||
'maxsize',
|
||||
'minsize',
|
||||
'minage',
|
||||
'search',
|
||||
'noabletosearch',
|
||||
'pidfile',
|
||||
'pidfilelocking',
|
||||
'search1',
|
||||
'search2',
|
||||
'sslargs1',
|
||||
'sslargs2',
|
||||
'syncduplicates',
|
||||
'usecache',
|
||||
'synclabels',
|
||||
'truncmess',
|
||||
'domino2',
|
||||
'expunge1',
|
||||
'filterbuggyflags',
|
||||
'justconnect',
|
||||
'justfolders',
|
||||
'maxlinelength',
|
||||
'useheader',
|
||||
'noabletosearch1',
|
||||
'nolog',
|
||||
'prefix1',
|
||||
'prefix2',
|
||||
'sep1',
|
||||
'sep2',
|
||||
'nofoldersizesatend',
|
||||
'justfoldersizes',
|
||||
'proxyauth1',
|
||||
'skipemptyfolders',
|
||||
'include',
|
||||
'subfolder1',
|
||||
'subscribed',
|
||||
'subscribe',
|
||||
'debug',
|
||||
'debugcontent',
|
||||
'debugcrossduplicates',
|
||||
'debugflags',
|
||||
'debugfolders',
|
||||
'debugimap',
|
||||
'debugimap1',
|
||||
'debugimap2',
|
||||
'debugmemory',
|
||||
'debugssl',
|
||||
'delete1emptyfolders',
|
||||
'delete2folders',
|
||||
'disarmreadreceipts',
|
||||
'domain1',
|
||||
'domain2',
|
||||
'domino1',
|
||||
'domino2',
|
||||
'dry',
|
||||
'errorsmax',
|
||||
'exchange1',
|
||||
'exchange2',
|
||||
'exitwhenover',
|
||||
'expunge1',
|
||||
'f1f2',
|
||||
'filterbuggyflags',
|
||||
'folder',
|
||||
'folderfirst',
|
||||
'folderlast',
|
||||
'folderrec',
|
||||
'gmail1',
|
||||
'gmail2',
|
||||
'idatefromheader',
|
||||
'include',
|
||||
'inet4',
|
||||
'inet6',
|
||||
'justconnect',
|
||||
'justfolders',
|
||||
'justfoldersizes',
|
||||
'justlogin',
|
||||
'keepalive1',
|
||||
'keepalive2',
|
||||
'log',
|
||||
'logdir',
|
||||
'logfile',
|
||||
'maxbytesafter',
|
||||
'maxlinelength',
|
||||
'maxmessagespersecond',
|
||||
'maxsize',
|
||||
'maxsleep',
|
||||
'minage',
|
||||
'minsize',
|
||||
'noabletosearch',
|
||||
'noabletosearch1',
|
||||
'noabletosearch2',
|
||||
'noexpunge1',
|
||||
'noexpunge2',
|
||||
'nofoldersizesatend',
|
||||
'noid',
|
||||
'nolog',
|
||||
'nomixfolders',
|
||||
'noresyncflags',
|
||||
'nossl1',
|
||||
'nouidexpunge2',
|
||||
'syncinternaldates',
|
||||
'idatefromheader',
|
||||
'useuid',
|
||||
'debugflags',
|
||||
'debugimap',
|
||||
'delete1emptyfolders',
|
||||
'delete2folders',
|
||||
'gmail2',
|
||||
'office1',
|
||||
'testslive6',
|
||||
'debugimap1',
|
||||
'errorsmax',
|
||||
'tests',
|
||||
'gmail1',
|
||||
'maxmessagespersecond',
|
||||
'maxbytesafter',
|
||||
'maxsleep',
|
||||
'abort',
|
||||
'resyncflags',
|
||||
'resynclabels',
|
||||
'syncacls',
|
||||
'nossl2',
|
||||
'nosyncacls',
|
||||
'notls1',
|
||||
'notls2',
|
||||
'nouidexpunge2',
|
||||
'nousecache',
|
||||
'office2',
|
||||
'testslive',
|
||||
'debugmemory',
|
||||
'exitwhenover',
|
||||
'noid',
|
||||
'noexpunge1',
|
||||
'authmd51',
|
||||
'logfile',
|
||||
'proxyauth2',
|
||||
'domain1',
|
||||
'domain2',
|
||||
'oauthaccesstoken1',
|
||||
'oauthaccesstoken2',
|
||||
'oauthdirect1',
|
||||
'oauthdirect2',
|
||||
'folder',
|
||||
'folderrec',
|
||||
'folderfirst',
|
||||
'folderlast',
|
||||
'nomixfolders',
|
||||
'authmd52',
|
||||
'debugfolders',
|
||||
'nossl2',
|
||||
'office1',
|
||||
'office2',
|
||||
'pidfile',
|
||||
'pidfilelocking',
|
||||
'prefix1',
|
||||
'prefix2',
|
||||
'proxyauth1',
|
||||
'proxyauth2',
|
||||
'resyncflags',
|
||||
'resynclabels',
|
||||
'search',
|
||||
'search1',
|
||||
'search2',
|
||||
'sep1',
|
||||
'sep2',
|
||||
'showpasswords',
|
||||
'skipemptyfolders',
|
||||
'ssl2',
|
||||
'sslargs1',
|
||||
'sslargs2',
|
||||
'subfolder1',
|
||||
'subscribe',
|
||||
'subscribed',
|
||||
'syncacls',
|
||||
'syncduplicates',
|
||||
'syncinternaldates',
|
||||
'synclabels',
|
||||
'tests',
|
||||
'testslive',
|
||||
'testslive6',
|
||||
'tls2',
|
||||
'notls2',
|
||||
'debugssl',
|
||||
'notls1',
|
||||
'inet4',
|
||||
'inet6',
|
||||
'log',
|
||||
'showpasswords'
|
||||
'truncmess',
|
||||
'usecache',
|
||||
'useheader',
|
||||
'useuid'
|
||||
),
|
||||
'blacklist' => array(
|
||||
'skipmess',
|
||||
|
||||
@@ -15801,7 +15801,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
|
||||
paginationEl.empty();
|
||||
}
|
||||
else {
|
||||
paginationEl = hostEl.html('<ul/>').children('ul').addClass('pagination');
|
||||
paginationEl = hostEl.html('<ul/>').children('ul').addClass('pagination pagination-sm');
|
||||
}
|
||||
|
||||
attach(
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
const LOCALE = undefined;
|
||||
const DATETIME_FORMAT = {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit"
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
// mailcow alert box generator
|
||||
window.mailcow_alert_box = function(message, type) {
|
||||
@@ -111,10 +121,21 @@ $(document).ready(function() {
|
||||
if (lastTab) {
|
||||
$('[data-bs-target="#' + lastTab + '"]').click();
|
||||
var tab = $('[id^="' + lastTab + '"]');
|
||||
$(tab).find('.card-body.collapse').collapse('show');
|
||||
$(tab).find('.card-body.collapse:first').collapse('show');
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// responsive tabs, scroll to opened tab
|
||||
$(document).on("shown.bs.collapse shown.bs.tab", function (e) {
|
||||
var target = $(e.target);
|
||||
if($(window).width() <= 767) {
|
||||
var offset = target.offset().top - 60;
|
||||
$("html, body").stop().animate({
|
||||
scrollTop: offset
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// IE fix to hide scrollbars when table body is empty
|
||||
$('tbody').filter(function (index) {
|
||||
@@ -304,19 +325,28 @@ $(document).ready(function() {
|
||||
$('#dark-mode-toggle').click(toggleDarkMode);
|
||||
if ($('#dark-mode-theme').length) {
|
||||
$('#dark-mode-toggle').prop('checked', true);
|
||||
$('.main-logo').addClass('d-none');
|
||||
$('.main-logo-dark').removeClass('d-none');
|
||||
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png');
|
||||
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png');
|
||||
} else {
|
||||
$('.main-logo').removeClass('d-none');
|
||||
$('.main-logo-dark').addClass('d-none');
|
||||
}
|
||||
function toggleDarkMode(){
|
||||
if($('#dark-mode-theme').length){
|
||||
$('#dark-mode-theme').remove();
|
||||
$('#dark-mode-toggle').prop('checked', false);
|
||||
$('.main-logo').removeClass('d-none');
|
||||
$('.main-logo-dark').addClass('d-none');
|
||||
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png');
|
||||
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}else{
|
||||
$('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">');
|
||||
$('#dark-mode-toggle').prop('checked', true);
|
||||
$('.main-logo').addClass('d-none');
|
||||
$('.main-logo-dark').removeClass('d-none');
|
||||
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png');
|
||||
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
|
||||
+22
-22
@@ -510,14 +510,14 @@ jQuery(function($){
|
||||
if (table == 'relayhoststable') {
|
||||
$.each(data, function (i, item) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="#" data-bs-toggle="modal" data-bs-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="sender-dependent" class="btn btn-xs btn-xs-third btn-secondary"><i class="bi bi-caret-right-fill"></i> Test</a>' +
|
||||
'<a href="/edit/relayhost/' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-rlyhost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="#" data-bs-toggle="modal" data-bs-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="sender-dependent" class="btn btn-xs btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-caret-right-fill"></i> Test</a>' +
|
||||
'<a href="/edit/relayhost/' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-rlyhost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; }
|
||||
else if (item.used_by_domains == '') { item.in_use_by = item.used_by_mailboxes; }
|
||||
else { item.in_use_by = item.used_by_mailboxes + '<hr style="margin:5px 0px 5px 0px;">' + item.used_by_domains; }
|
||||
item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
|
||||
});
|
||||
} else if (table == 'transportstable') {
|
||||
$.each(data, function (i, item) {
|
||||
@@ -528,49 +528,49 @@ jQuery(function($){
|
||||
item.username = '<i style="color:#' + intToRGB(hashCode(item.nexthop)) + ';" class="bi bi-square-fill"></i> ' + item.username;
|
||||
}
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="#" data-bs-toggle="modal" data-bs-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="transport-map" class="btn btn-xs btn-xs-third btn-secondary"><i class="bi bi-caret-right-fill"></i> Test</a>' +
|
||||
'<a href="/edit/transport/' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-transport" data-api-url="delete/transport" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="#" data-bs-toggle="modal" data-bs-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="transport-map" class="btn btn-xs btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-caret-right-fill"></i> Test</a>' +
|
||||
'<a href="/edit/transport/' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-transport" data-api-url="delete/transport" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="transports" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="transports" name="multi_select" value="' + item.id + '" />';
|
||||
});
|
||||
} else if (table == 'queuetable') {
|
||||
$.each(data, function (i, item) {
|
||||
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
|
||||
rcpts = $.map(item.recipients, function(i) {
|
||||
return escapeHtml(i);
|
||||
});
|
||||
item.recipients = rcpts.join('<hr style="margin:1px!important">');
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.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-xs-lg btn-secondary">' + lang.queue_show_message + '</a>' +
|
||||
'</div>';
|
||||
});
|
||||
} else if (table == 'forwardinghoststable') {
|
||||
$.each(data, function (i, item) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-xs-lg btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />';
|
||||
});
|
||||
} else if (table == 'oauth2clientstable') {
|
||||
$.each(data, function (i, item) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit.php?oauth2client=' + encodeURI(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-oauth2-client" data-api-url="delete/oauth2-client" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit.php?oauth2client=' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-oauth2-client" data-api-url="delete/oauth2-client" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.scope = "profile";
|
||||
item.grant_types = 'refresh_token password authorization_code';
|
||||
item.chkbox = '<input type="checkbox" data-id="oauth2_clients" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="oauth2_clients" name="multi_select" value="' + item.id + '" />';
|
||||
});
|
||||
} else if (table == 'domainadminstable') {
|
||||
$.each(data, function (i, item) {
|
||||
item.selected_domains = escapeHtml(item.selected_domains);
|
||||
item.selected_domains = item.selected_domains.toString().replace(/,/g, "<br>");
|
||||
item.chkbox = '<input type="checkbox" data-id="domain_admins" name="multi_select" value="' + item.username + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_admins" name="multi_select" value="' + item.username + '" />';
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-xs-third btn-success"><i class="bi bi-person-fill"></i> Login</a>' +
|
||||
'<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-success"><i class="bi bi-person-fill"></i> Login</a>' +
|
||||
'</div>';
|
||||
});
|
||||
} else if (table == 'adminstable') {
|
||||
@@ -580,10 +580,10 @@ jQuery(function($){
|
||||
} else {
|
||||
item.usr = item.username;
|
||||
}
|
||||
item.chkbox = '<input type="checkbox" data-id="admins" name="multi_select" value="' + item.username + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="admins" name="multi_select" value="' + item.username + '" />';
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/admin/' + encodeURI(item.username) + '" 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-admin" data-api-url="delete/admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/admin/' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-admin" data-api-url="delete/admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
});
|
||||
}
|
||||
|
||||
+16
-18
@@ -1,13 +1,3 @@
|
||||
const LOCALE = undefined;
|
||||
const DATETIME_FORMAT = {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit"
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
// Parse seconds ago to date
|
||||
// Get "now" timestamp
|
||||
@@ -43,7 +33,7 @@ $(document).ready(function() {
|
||||
if (mailcow_info.branch === "master"){
|
||||
check_update(mailcow_info.version_tag, mailcow_info.project_url);
|
||||
}
|
||||
$("#maiclow_version").click(function(){
|
||||
$("#mailcow_version").click(function(){
|
||||
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master")
|
||||
return;
|
||||
|
||||
@@ -829,13 +819,10 @@ jQuery(function($){
|
||||
url: '/api/v1/get/rspamd/actions',
|
||||
async: true,
|
||||
success: function(data){
|
||||
console.log(data);
|
||||
|
||||
var total = 0;
|
||||
$(data).map(function(){total += this[1];});
|
||||
var labels = $.makeArray($(data).map(function(){return this[0] + ' ' + Math.round(this[1]/total * 100) + '%';}));
|
||||
var values = $.makeArray($(data).map(function(){return this[1];}));
|
||||
console.log(values);
|
||||
|
||||
var graphdata = {
|
||||
labels: labels,
|
||||
@@ -951,12 +938,15 @@ jQuery(function($){
|
||||
title: 'Score',
|
||||
data: 'score',
|
||||
defaultContent: '',
|
||||
class: 'text-nowrap',
|
||||
createdCell: function(td, cellData) {
|
||||
$(td).attr({
|
||||
"data-order": cellData.sortBy,
|
||||
"data-sort": cellData.sortBy
|
||||
});
|
||||
$(td).html(cellData.value);
|
||||
},
|
||||
render: function (data) {
|
||||
return data.value;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -979,7 +969,9 @@ jQuery(function($){
|
||||
"data-order": cellData.sortBy,
|
||||
"data-sort": cellData.sortBy
|
||||
});
|
||||
$(td).html(cellData.value);
|
||||
},
|
||||
render: function (data) {
|
||||
return data.value;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1181,7 +1173,7 @@ jQuery(function($){
|
||||
|
||||
if (table = $('#' + log_table).DataTable()) {
|
||||
var heading = $('#' + log_table).closest('.card').find('.card-header');
|
||||
var load_rows = (table.data().length + 1) + '-' + (table.data().length + new_nrows)
|
||||
var load_rows = (table.data().count() + 1) + '-' + (table.data().count() + 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; }
|
||||
@@ -1302,6 +1294,12 @@ function update_stats(timeout=5){
|
||||
$("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%");
|
||||
$("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB");
|
||||
$("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%");
|
||||
if (data.architecture == "aarch64"){
|
||||
$("#host_architecture").html('<span data-bs-toggle="tooltip" data-bs-placement="top" title="' + lang_debug.wip +'">' + data.architecture + ' ⚠️</span>');
|
||||
}
|
||||
else {
|
||||
$("#host_architecture").html(data.architecture);
|
||||
}
|
||||
|
||||
// update cpu and mem chart
|
||||
var cpu_chart = Chart.getChart("host_cpu_chart");
|
||||
@@ -1686,7 +1684,7 @@ function showVersionModal(title, version){
|
||||
function parseGithubMarkdownLinks(inputText) {
|
||||
var replacedText, replacePattern1;
|
||||
|
||||
replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
|
||||
replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])(?![^<]*>)/gim;
|
||||
replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => {
|
||||
if (matched.includes('github.com')){
|
||||
// return short link if it's github link
|
||||
|
||||
@@ -93,10 +93,10 @@ jQuery(function($){
|
||||
dataSrc: function(data){
|
||||
$.each(data, function (i, item) {
|
||||
if (!validateEmail(item.object)) {
|
||||
item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
|
||||
}
|
||||
else {
|
||||
item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -154,10 +154,10 @@ jQuery(function($){
|
||||
dataSrc: function(data){
|
||||
$.each(data, function (i, item) {
|
||||
if (!validateEmail(item.object)) {
|
||||
item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
|
||||
}
|
||||
else {
|
||||
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+122
-74
@@ -466,16 +466,16 @@ jQuery(function($){
|
||||
|
||||
item.def_quota_for_mbox = humanFileSize(item.def_quota_for_mbox);
|
||||
item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox);
|
||||
item.chkbox = '<input type="checkbox" data-id="domain" name="multi_select" value="' + encodeURIComponent(item.domain_name) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain" name="multi_select" value="' + encodeURIComponent(item.domain_name) + '" />';
|
||||
item.action = '<div class="btn-group">';
|
||||
if (role == "admin") {
|
||||
item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURIComponent(item.domain_name) + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="#dnsInfoModal" class="btn btn-sm btn-info" data-bs-toggle="modal" data-domain="' + encodeURIComponent(item.domain_name) + '"><i class="bi bi-globe2"></i> DNS</a></div>';
|
||||
item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURIComponent(item.domain_name) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="#dnsInfoModal" class="btn btn-sm btn-xs-lg btn-info" data-bs-toggle="modal" data-domain="' + encodeURIComponent(item.domain_name) + '"><i class="bi bi-globe2"></i> DNS</a></div>';
|
||||
}
|
||||
else {
|
||||
item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#dnsInfoModal" class="btn btn-xs btn-xs-half btn-info" data-bs-toggle="modal" data-domain="' + encodeURIComponent(item.domain_name) + '"><i class="bi bi-globe2"></i> DNS</a></div>';
|
||||
item.action += '<a href="/edit/domain/' + encodeURIComponent(item.domain_name) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#dnsInfoModal" class="btn btn-sm btn-xs-lg btn-xs-half btn-info" data-bs-toggle="modal" data-domain="' + encodeURIComponent(item.domain_name) + '"><i class="bi bi-globe2"></i> DNS</a></div>';
|
||||
}
|
||||
|
||||
if (Array.isArray(item.tags)){
|
||||
@@ -650,7 +650,7 @@ jQuery(function($){
|
||||
url: "/api/v1/get/domain/template/all",
|
||||
dataSrc: function(json){
|
||||
$.each(json, function (i, item) {
|
||||
item.chkbox = '<input type="checkbox" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="domain_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
||||
|
||||
item.attributes.def_quota_for_mbox = humanFileSize(item.attributes.def_quota_for_mbox);
|
||||
item.attributes.max_quota_for_mbox = humanFileSize(item.attributes.max_quota_for_mbox);
|
||||
@@ -671,13 +671,13 @@ jQuery(function($){
|
||||
|
||||
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>' +
|
||||
'<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
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>' +
|
||||
'<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg 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-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
@@ -851,8 +851,9 @@ jQuery(function($){
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
initComplete: function(){
|
||||
initComplete: function(settings, json){
|
||||
hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table');
|
||||
filterByDomain(json, 8, table);
|
||||
},
|
||||
ajax: {
|
||||
type: "GET",
|
||||
@@ -880,7 +881,7 @@ jQuery(function($){
|
||||
}
|
||||
}
|
||||
*/
|
||||
item.chkbox = '<input type="checkbox" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />';
|
||||
if (item.attributes.passwd_update != '0') {
|
||||
var last_pw_change = new Date(item.attributes.passwd_update.replace(/-/g, "/"));
|
||||
item.last_pw_change = last_pw_change.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
||||
@@ -912,18 +913,18 @@ jQuery(function($){
|
||||
if (acl_data.login_as === 1) {
|
||||
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="login_as btn btn-sm btn-xs-half btn-success"><i class="bi bi-person-fill"></i> Login</a>';
|
||||
'<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="login_as btn btn-sm btn-xs-lg btn-xs-half btn-success"><i class="bi bi-person-fill"></i> Login</a>';
|
||||
if (ALLOW_ADMIN_EMAIL_LOGIN) {
|
||||
item.action += '<a href="/sogo-auth.php?login=' + encodeURIComponent(item.username) + '" class="login_as btn btn-sm btn-xs-half btn-primary" target="_blank"><i class="bi bi-envelope-fill"></i> SOGo</a>';
|
||||
item.action += '<a href="/sogo-auth.php?login=' + encodeURIComponent(item.username) + '" class="login_as btn btn-sm btn-xs-lg btn-xs-half btn-primary" target="_blank"><i class="bi bi-envelope-fill"></i> SOGo</a>';
|
||||
}
|
||||
item.action += '</div>';
|
||||
}
|
||||
else {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" 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-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>' +
|
||||
'<a href="/edit/mailbox/' + encodeURIComponent(item.username) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
item.in_use = {
|
||||
@@ -1148,7 +1149,7 @@ jQuery(function($){
|
||||
url: "/api/v1/get/mailbox/template/all",
|
||||
dataSrc: function(json){
|
||||
$.each(json, function (i, item) {
|
||||
item.chkbox = '<input type="checkbox" data-id="mailbox_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailbox_template" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
||||
|
||||
item.template = escapeHtml(item.template);
|
||||
if (item.attributes.rl_frame === "s"){
|
||||
@@ -1190,13 +1191,13 @@ jQuery(function($){
|
||||
|
||||
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>' +
|
||||
'<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
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/mailbox/template" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg 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/mailbox/template" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
@@ -1362,8 +1363,9 @@ jQuery(function($){
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
initComplete: function(){
|
||||
initComplete: function(settings, json){
|
||||
hideTableExpandCollapseBtn('#tab-resources', '#resource_table');
|
||||
filterByDomain(json, 5, table);
|
||||
},
|
||||
ajax: {
|
||||
type: "GET",
|
||||
@@ -1378,10 +1380,10 @@ jQuery(function($){
|
||||
item.multiple_bookings = '<span id="active-script" class="badge fs-6 bg-danger">' + lang.booking_custom_short + ' (' + item.multiple_bookings + ')</span>';
|
||||
}
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/resource/' + encodeURIComponent(item.name) + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-resource" data-api-url="delete/resource" data-item="' + item.name + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/resource/' + encodeURIComponent(item.name) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-resource" data-api-url="delete/resource" data-item="' + item.name + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="resource" name="multi_select" value="' + encodeURIComponent(item.name) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="resource" name="multi_select" value="' + encodeURIComponent(item.name) + '" />';
|
||||
item.name = escapeHtml(item.name);
|
||||
item.description = escapeHtml(item.description);
|
||||
});
|
||||
@@ -1458,30 +1460,37 @@ jQuery(function($){
|
||||
}
|
||||
function draw_bcc_table() {
|
||||
$.get("/api/v1/get/bcc-destination-options", function(data){
|
||||
var optgroup = "";
|
||||
// Domains
|
||||
var optgroup = "<optgroup label='" + lang.domains + "'>";
|
||||
$.each(data.domains, function(index, domain){
|
||||
optgroup += "<option value='" + domain + "'>" + domain + "</option>";
|
||||
});
|
||||
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 += "</optgroup>"
|
||||
$('#bcc-local-dest').append(optgroup);
|
||||
// Mailboxes and aliases
|
||||
$.each(data.mailboxes, function(mailbox, aliases){
|
||||
var optgroup = "<optgroup label='" + mailbox + "'>";
|
||||
$.each(aliases, function(index, alias){
|
||||
optgroup += "<option value='" + alias + "'>" + alias + "</option>";
|
||||
if (data.domains && data.domains.length > 0) {
|
||||
optgroup = "<optgroup label='" + lang.domains + "'>";
|
||||
$.each(data.domains, function(index, domain){
|
||||
optgroup += "<option value='" + domain + "'>" + domain + "</option>";
|
||||
});
|
||||
optgroup += "</optgroup>";
|
||||
$('#bcc-local-dest').append(optgroup);
|
||||
});
|
||||
// Finish
|
||||
}
|
||||
// Alias domains
|
||||
if (data.alias_domains && data.alias_domains.length > 0) {
|
||||
optgroup = "<optgroup label='" + lang.domain_aliases + "'>";
|
||||
$.each(data.alias_domains, function(index, alias_domain){
|
||||
optgroup += "<option value='" + alias_domain + "'>" + alias_domain + "</option>";
|
||||
});
|
||||
optgroup += "</optgroup>"
|
||||
$('#bcc-local-dest').append(optgroup);
|
||||
}
|
||||
// Mailboxes and aliases
|
||||
if (data.mailboxes && Object.keys(data.mailboxes).length > 0) {
|
||||
$.each(data.mailboxes, function(mailbox, aliases){
|
||||
optgroup = "<optgroup label='" + mailbox + "'>";
|
||||
$.each(aliases, function(index, alias){
|
||||
optgroup += "<option value='" + alias + "'>" + alias + "</option>";
|
||||
});
|
||||
optgroup += "</optgroup>";
|
||||
$('#bcc-local-dest').append(optgroup);
|
||||
});
|
||||
}
|
||||
// Recreate picker
|
||||
$('#bcc-local-dest').selectpicker('refresh');
|
||||
});
|
||||
|
||||
@@ -1502,8 +1511,9 @@ jQuery(function($){
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
initComplete: function(settings, json){
|
||||
hideTableExpandCollapseBtn('#collapse-tab-bcc', '#bcc_table');
|
||||
filterByDomain(json, 6, table);
|
||||
},
|
||||
ajax: {
|
||||
type: "GET",
|
||||
@@ -1511,10 +1521,10 @@ jQuery(function($){
|
||||
dataSrc: function(json){
|
||||
$.each(json, function (i, item) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/bcc/' + item.id + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-bcc" data-api-url="delete/bcc" data-item="' + item.id + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/bcc/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-bcc" data-api-url="delete/bcc" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="bcc" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="bcc" name="multi_select" value="' + item.id + '" />';
|
||||
item.local_dest = escapeHtml(item.local_dest);
|
||||
item.bcc_dest = escapeHtml(item.bcc_dest);
|
||||
if (item.type == 'sender') {
|
||||
@@ -1625,10 +1635,10 @@ jQuery(function($){
|
||||
item.recipient_map_old = escapeHtml(item.recipient_map_old);
|
||||
item.recipient_map_new = escapeHtml(item.recipient_map_new);
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/recipient_map/' + item.id + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-recipient_map" data-api-url="delete/recipient_map" data-item="' + item.id + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/recipient_map/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-recipient_map" data-api-url="delete/recipient_map" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="recipient_map" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="recipient_map" name="multi_select" value="' + item.id + '" />';
|
||||
});
|
||||
|
||||
return json;
|
||||
@@ -1727,10 +1737,10 @@ jQuery(function($){
|
||||
item.parameters = '<code>' + escapeHtml(item.parameters) + '</code>';
|
||||
}
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/tls_policy_map/' + item.id + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-tls-policy-map" data-api-url="delete/tls-policy-map" data-item="' + item.id + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/tls_policy_map/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-tls-policy-map" data-api-url="delete/tls-policy-map" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="tls-policy-map" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="tls-policy-map" name="multi_select" value="' + item.id + '" />';
|
||||
});
|
||||
|
||||
return json;
|
||||
@@ -1816,8 +1826,9 @@ jQuery(function($){
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order: [[2, 'desc']],
|
||||
initComplete: function(){
|
||||
initComplete: function(settings, json){
|
||||
hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table');
|
||||
filterByDomain(json, 5, table);
|
||||
},
|
||||
ajax: {
|
||||
type: "GET",
|
||||
@@ -1825,10 +1836,10 @@ jQuery(function($){
|
||||
dataSrc: function(json){
|
||||
$.each(json, function (i, item) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/alias/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-alias" data-api-url="delete/alias" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/alias/' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-alias" data-api-url="delete/alias" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="alias" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="alias" name="multi_select" value="' + encodeURIComponent(item.id) + '" />';
|
||||
item.goto = escapeHtml(item.goto.replace(/,/g, " "));
|
||||
if (item.public_comment !== null) {
|
||||
item.public_comment = escapeHtml(item.public_comment);
|
||||
@@ -1951,7 +1962,7 @@ 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();
|
||||
});
|
||||
@@ -1984,11 +1995,11 @@ jQuery(function($){
|
||||
item.alias_domain = escapeHtml(item.alias_domain);
|
||||
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/aliasdomain/' + encodeURIComponent(item.alias_domain) + '" class="btn btn-sm btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-alias-domain" data-api-url="delete/alias-domain" data-item="' + encodeURIComponent(item.alias_domain) + '" class="btn btn-sm btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="#dnsInfoModal" class="btn btn-sm btn-xs-third btn-info" data-bs-toggle="modal" data-domain="' + encodeURIComponent(item.alias_domain) + '"><i class="bi bi-globe2"></i> DNS</a></div>' +
|
||||
'<a href="/edit/aliasdomain/' + encodeURIComponent(item.alias_domain) + '" class="btn btn-sm btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-alias-domain" data-api-url="delete/alias-domain" data-item="' + encodeURIComponent(item.alias_domain) + '" class="btn btn-sm btn-xs-lg btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="#dnsInfoModal" class="btn btn-sm btn-xs-lg btn-xs-third btn-info" data-bs-toggle="modal" data-domain="' + encodeURIComponent(item.alias_domain) + '"><i class="bi bi-globe2"></i> DNS</a></div>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="alias-domain" name="multi_select" value="' + encodeURIComponent(item.alias_domain) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="alias-domain" name="multi_select" value="' + encodeURIComponent(item.alias_domain) + '" />';
|
||||
if(item.parent_is_backupmx == '1') {
|
||||
item.target_domain = '<span><a href="/edit/domain/' + item.target_domain + '">' + item.target_domain + '</a> <div class="badge fs-6 bg-warning">' + lang.alias_domain_backupmx + '</div></span>';
|
||||
} else {
|
||||
@@ -2086,10 +2097,10 @@ jQuery(function($){
|
||||
}
|
||||
item.server_w_port = escapeHtml(item.user1) + '@' + escapeHtml(item.host1) + ':' + escapeHtml(item.port1);
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
|
||||
if (item.is_running == 1) {
|
||||
item.is_running = '<span id="active-script" class="badge fs-6 bg-success">' + lang.running + '</span>';
|
||||
} else {
|
||||
@@ -2240,10 +2251,10 @@ jQuery(function($){
|
||||
item.script_data = '<pre class="text-break" style="margin:0px">' + escapeHtml(item.script_data) + '</pre>'
|
||||
item.filter_type = '<div class="badge fs-6 bg-secondary">' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '</div>'
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="/edit/filter/' + item.id + '" class="btn btn-sm btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-filter" data-api-url="delete/filter" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'<a href="/edit/filter/' + item.id + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-filter" data-api-url="delete/filter" data-item="' + encodeURIComponent(item.id) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="filter_item" name="multi_select" value="' + item.id + '" />'
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="filter_item" name="multi_select" value="' + item.id + '" />'
|
||||
});
|
||||
|
||||
return json;
|
||||
@@ -2322,20 +2333,57 @@ jQuery(function($){
|
||||
else
|
||||
$(tab).find(".table_collapse_option").hide();
|
||||
}
|
||||
|
||||
function filterByDomain(json, column, table){
|
||||
var tableId = $(table.table().container()).attr('id');
|
||||
// Create the `select` element
|
||||
var select = $('<select class="btn btn-sm btn-xs-lg btn-light text-start mx-2"><option value="">'+lang.all_domains+'</option></select>')
|
||||
.insertBefore(
|
||||
$('#'+tableId+' .dataTables_filter > label > input')
|
||||
)
|
||||
.on( 'change', function(){
|
||||
table.column(column)
|
||||
.search($(this).val())
|
||||
.draw();
|
||||
});
|
||||
|
||||
// get all domains
|
||||
var domains = [];
|
||||
json.forEach(obj => {
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if(key === 'domain') {
|
||||
domains.push(value)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// get unique domain list
|
||||
domains = domains.filter(function(value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
});
|
||||
|
||||
// add domains to select
|
||||
domains.forEach(function(domain) {
|
||||
select.append($('<option>' + domain + '</option>'));
|
||||
});
|
||||
}
|
||||
|
||||
// detect element visibility changes
|
||||
function onVisible(element, callback) {
|
||||
$(document).ready(function() {
|
||||
element_object = document.querySelector(element);
|
||||
let element_object = document.querySelector(element);
|
||||
if (element_object === null) return;
|
||||
|
||||
new IntersectionObserver((entries, observer) => {
|
||||
let observer = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if(entry.intersectionRatio > 0) {
|
||||
callback(element_object);
|
||||
observer.unobserve(element_object);
|
||||
}
|
||||
});
|
||||
}).observe(element_object);
|
||||
})
|
||||
|
||||
observer.observe(element_object);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ jQuery(function($){
|
||||
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><i class="bi bi-file-earmark-text"></i> ' + lang.show_item + '</a>' +
|
||||
'</div>';
|
||||
}
|
||||
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="qitems" name="multi_select" value="' + item.id + '" />';
|
||||
});
|
||||
|
||||
return data;
|
||||
@@ -220,7 +220,7 @@ jQuery(function($){
|
||||
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>');
|
||||
$('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? escapeHtml(value.options.join(', ')) : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
|
||||
});
|
||||
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||
}
|
||||
@@ -295,3 +295,7 @@ jQuery(function($){
|
||||
$(".table_collapse_option").hide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ jQuery(function($){
|
||||
url: "/api/v1/get/mailq/all",
|
||||
dataSrc: function(data){
|
||||
$.each(data, function (i, item) {
|
||||
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
|
||||
rcpts = $.map(item.recipients, function(i) {
|
||||
return escapeHtml(i);
|
||||
});
|
||||
|
||||
+31
-18
@@ -127,6 +127,20 @@ jQuery(function($){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createSortableDate(td, cellData, date_string = false) {
|
||||
if (date_string)
|
||||
var date = new Date(cellData);
|
||||
else
|
||||
var date = new Date(cellData ? cellData * 1000 : 0);
|
||||
|
||||
var timestamp = date.getTime();
|
||||
$(td).attr({
|
||||
"data-order": timestamp,
|
||||
"data-sort": timestamp
|
||||
});
|
||||
$(td).html(date.toLocaleDateString(LOCALE, DATETIME_FORMAT));
|
||||
}
|
||||
function draw_tla_table() {
|
||||
// just recalc width if instance already exists
|
||||
if ($.fn.DataTable.isDataTable('#tla_table') ) {
|
||||
@@ -144,6 +158,7 @@ jQuery(function($){
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order: [[4, 'desc']],
|
||||
ajax: {
|
||||
type: "GET",
|
||||
url: "/api/v1/get/time_limited_aliases",
|
||||
@@ -154,11 +169,11 @@ jQuery(function($){
|
||||
item.action = '<div class="btn-group">' +
|
||||
'<a href="#" data-action="delete_selected" data-id="single-tla" data-api-url="delete/time_limited_alias" data-item="' + encodeURIComponent(item.address) + '" class="btn btn-xs btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
|
||||
item.address = escapeHtml(item.address);
|
||||
}
|
||||
else {
|
||||
item.chkbox = '<input type="checkbox" disabled />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||
item.action = '<span>-</span>';
|
||||
}
|
||||
});
|
||||
@@ -191,18 +206,16 @@ jQuery(function($){
|
||||
title: lang.alias_valid_until,
|
||||
data: 'validity',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
var date = new Date(data ? data * 1000 : 0);
|
||||
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
||||
createdCell: function(td, cellData) {
|
||||
createSortableDate(td, cellData)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.created_on,
|
||||
data: 'created',
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
var date = new Date(data.replace(/-/g, "/"));
|
||||
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
||||
createdCell: function(td, cellData) {
|
||||
createSortableDate(td, cellData, true)
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -250,11 +263,11 @@ jQuery(function($){
|
||||
'<a href="/edit/syncjob/' + 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-syncjob" data-api-url="delete/syncjob" data-item="' + item.id + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
|
||||
}
|
||||
else {
|
||||
item.action = '<span>-</span>';
|
||||
item.chkbox = '<input type="checkbox" disabled />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||
}
|
||||
if (item.is_running == 1) {
|
||||
item.is_running = '<span id="active-script" class="badge fs-6 bg-success">' + lang.running + '</span>';
|
||||
@@ -407,11 +420,11 @@ jQuery(function($){
|
||||
'<a href="/edit/app-passwd/' + 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-apppasswd" data-api-url="delete/app-passwd" data-item="' + item.id + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" data-id="apppasswd" name="multi_select" value="' + item.id + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="apppasswd" name="multi_select" value="' + item.id + '" />';
|
||||
}
|
||||
else {
|
||||
item.action = '<span>-</span>';
|
||||
item.chkbox = '<input type="checkbox" disabled />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -490,13 +503,13 @@ jQuery(function($){
|
||||
console.log(data);
|
||||
$.each(data, function (i, item) {
|
||||
if (validateEmail(item.object)) {
|
||||
item.chkbox = '<input type="checkbox" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
||||
}
|
||||
else {
|
||||
item.chkbox = '<input type="checkbox" disabled title="' + lang.spamfilter_table_domain_policy + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled title="' + lang.spamfilter_table_domain_policy + '" />';
|
||||
}
|
||||
if (acl_data.spam_policy === 0) {
|
||||
item.chkbox = '<input type="checkbox" disabled />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -561,13 +574,13 @@ jQuery(function($){
|
||||
console.log(data);
|
||||
$.each(data, function (i, item) {
|
||||
if (validateEmail(item.object)) {
|
||||
item.chkbox = '<input type="checkbox" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
||||
}
|
||||
else {
|
||||
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang.spamfilter_table_domain_policy + '" />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled tooltip="' + lang.spamfilter_table_domain_policy + '" />';
|
||||
}
|
||||
if (acl_data.spam_policy === 0) {
|
||||
item.chkbox = '<input type="checkbox" disabled />';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+20
-14
@@ -2,9 +2,9 @@
|
||||
/*
|
||||
see /api
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||
cors("set_headers");
|
||||
header('Content-Type: application/json');
|
||||
error_reporting(0);
|
||||
|
||||
function api_log($_data) {
|
||||
@@ -288,18 +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 "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;
|
||||
@@ -1867,6 +1867,9 @@ if (isset($_GET['query'])) {
|
||||
case "quota_notification_bcc":
|
||||
process_edit_return(quota_notification_bcc('edit', $attr));
|
||||
break;
|
||||
case "domain-wide-footer":
|
||||
process_edit_return(mailbox('edit', 'domain_wide_footer', $attr));
|
||||
break;
|
||||
case "mailq":
|
||||
process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr)));
|
||||
break;
|
||||
@@ -1946,6 +1949,9 @@ if (isset($_GET['query'])) {
|
||||
process_edit_return(edit_user_account($attr));
|
||||
}
|
||||
break;
|
||||
case "cors":
|
||||
process_edit_return(cors('edit', $attr));
|
||||
break;
|
||||
// return no route found if no case is matched
|
||||
default:
|
||||
http_response_code(404);
|
||||
|
||||
@@ -3,7 +3,23 @@
|
||||
"bcc_maps": "BCC maps",
|
||||
"filters": "Filtres",
|
||||
"recipient_maps": "Recipient maps",
|
||||
"syncjobs": "Feines de sincronització"
|
||||
"syncjobs": "Feines de sincronització",
|
||||
"quarantine_category": "Canvia la categoria de les notificacions de quarantena",
|
||||
"quarantine_notification": "Canvia les notificacions de quarantena",
|
||||
"sogo_profile_reset": "Restableix el prefil SOGo",
|
||||
"alias_domains": "Afegir àlies de domini",
|
||||
"app_passwds": "Gestiona les contrasenyes de les aplicacions",
|
||||
"domain_desc": "Canvia la descripció del domini",
|
||||
"eas_reset": "Restableix els dispositius EAS",
|
||||
"login_as": "Inicia sessió com a usuari de la bústia de correu",
|
||||
"prohibited": "Prohibit per ACL",
|
||||
"protocol_access": "Canvia el protocol d'accés",
|
||||
"quarantine": "Accions de quarantena",
|
||||
"quarantine_attachments": "Fitxers adjunts en quarantena",
|
||||
"spam_alias": "Àlies temporals",
|
||||
"spam_score": "Puntuació de correu brossa",
|
||||
"tls_policy": "Política TLS",
|
||||
"unlimited_quota": "Quota ilimitada per bústies de correo"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "All other filters will be deactivated, when active is checked.",
|
||||
@@ -55,7 +71,9 @@
|
||||
"target_domain": "Domini destí:",
|
||||
"username": "Username",
|
||||
"validate": "Validar",
|
||||
"validation_success": "Validated successfully"
|
||||
"validation_success": "Validated successfully",
|
||||
"app_name": "Nom de l'aplicació",
|
||||
"app_password": "Afegir contrasenya a l'aplicació"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Accés",
|
||||
@@ -259,7 +277,7 @@
|
||||
},
|
||||
"footer": {
|
||||
"cancel": "Cancel·lar",
|
||||
"confirm_delete": "Confirma l'esborrat ",
|
||||
"confirm_delete": "Confirma l'esborrat",
|
||||
"delete_now": "Esborrar ara",
|
||||
"delete_these_items": "Si et plau confirma els canvis al objecte amb id:",
|
||||
"loading": "Si et plau espera ...",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"alias_domain": "Doménový alias",
|
||||
"alias_domain_info": "<small>Platné názvy domén (oddělené čárkami).</small>",
|
||||
"app_name": "Název aplikace",
|
||||
"app_passwd_protocols": "Povolené protokoly pro hesla aplikací",
|
||||
"app_password": "Přidat heslo aplikace",
|
||||
"automap": "Pokusit se automaticky mapovat složky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
|
||||
"backup_mx_options": "Možnosti záložního MX",
|
||||
@@ -146,6 +147,8 @@
|
||||
"ays": "Opravdu chcete pokračovat?",
|
||||
"ban_list_info": "Seznam blokovaných IP adres je zobrazen níže: <b>síť (zbývající čas blokování) - [akce]</b>.<br />IP adresy zařazené pro odblokování budou z aktivního seznamu odebrány během několika sekund.<br />Červeně označené položky jsou pernamentní bloky z blacklistu.",
|
||||
"change_logo": "Změnit logo",
|
||||
"logo_normal_label": "Normální",
|
||||
"logo_dark_label": "Inverzní pro tmavý režim",
|
||||
"configuration": "Nastavení",
|
||||
"convert_html_to_text": "Převést HTML do prostého textu",
|
||||
"credentials_transport_warning": "<b>Upozornění</b>: Přidání položky do transportní mapy aktualizuje také přihlašovací údaje všech záznamů s odpovídajícím skokem.",
|
||||
@@ -205,6 +208,9 @@
|
||||
"include_exclude": "Zahrnout/Vyloučit",
|
||||
"include_exclude_info": "Ve výchozím nastavení (bez výběru), jsou adresovány <b>všechny mailové schránky</b>",
|
||||
"includes": "Zahrnout tyto přijemce",
|
||||
"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>",
|
||||
"ip_check_opt_in": "Přihlásit se k používání služby třetí strany <strong>ipv4.mailcow.email</strong> a <strong>ipv6.mailcow.email</strong> pro zjištění externích IP adres.",
|
||||
"is_mx_based": "Na základě MX",
|
||||
"last_applied": "Naposledy použité",
|
||||
"license_info": "Licence není povinná, pomůžete však dalšímu vývoji.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Registrujte si své GUID</a>, nebo si <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">zaplaťte podporu pro svou instalaci mailcow.</a>",
|
||||
@@ -231,6 +237,7 @@
|
||||
"oauth2_renew_secret": "Vytvořit nový tajný klíč",
|
||||
"oauth2_revoke_tokens": "Odvolat všechny klientské tokeny",
|
||||
"optional": "volitelné",
|
||||
"options": "Možnosti",
|
||||
"password": "Heslo",
|
||||
"password_length": "Délka hesla",
|
||||
"password_policy": "Politika hesel",
|
||||
@@ -336,9 +343,7 @@
|
||||
"verify": "Ověřit",
|
||||
"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>"
|
||||
"f2b_max_ban_time": "Maximální délka banu (s)"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
|
||||
@@ -442,6 +447,9 @@
|
||||
"target_domain_invalid": "Cílová doména %s je neplatná",
|
||||
"targetd_not_found": "Cílová doména %s nenalezena",
|
||||
"targetd_relay_domain": "Cílová doména %s je předávaná",
|
||||
"template_exists": "Šablona %s již existuje",
|
||||
"template_id_invalid": "Šablona ID %s je neplatná",
|
||||
"template_name_invalid": "Název šablony je neplatný",
|
||||
"temp_error": "Dočasná chyba",
|
||||
"text_empty": "Text nesmí být prázdný",
|
||||
"tfa_token_invalid": "Neplatný TFA token",
|
||||
@@ -459,6 +467,29 @@
|
||||
"value_missing": "Prosím, uveďte všechny hodnoty",
|
||||
"yotp_verification_failed": "Yubico OTP ověření selhalo: %s"
|
||||
},
|
||||
"datatables": {
|
||||
"emptyTable": "Tabulka neobsahuje žádná data",
|
||||
"info": "Zobrazuji _START_ až _END_ z celkem _TOTAL_ záznamů",
|
||||
"infoEmpty": "Zobrazuji 0 až 0 z 0 záznamů",
|
||||
"infoFiltered": "(filtrováno z celkem _MAX_ záznamů)",
|
||||
"loadingRecords": "Načítám...",
|
||||
"zeroRecords": "Žádné záznamy nebyly nalezeny",
|
||||
"paginate": {
|
||||
"first": "První",
|
||||
"last": "Poslední",
|
||||
"next": "Další",
|
||||
"previous": "Předchozí"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": aktivujte pro seřazení vzestupně",
|
||||
"sortDescending": ": aktivujte pro seřazení sestupně"
|
||||
},
|
||||
"lengthMenu": "Zobrazit _MENU_ výsledků",
|
||||
"processing": "Zpracovávání...",
|
||||
"search": "Vyhledávání:",
|
||||
"decimal": ",",
|
||||
"thousands": " "
|
||||
},
|
||||
"debug": {
|
||||
"chart_this_server": "Graf (tento server)",
|
||||
"containers_info": "Informace o kontejnerech",
|
||||
@@ -491,13 +522,14 @@
|
||||
"dns_records": "DNS záznamy",
|
||||
"dns_records_24hours": "Upozornění: Změnám v systému DNS může trvat až 24 hodin, než se zde správně zobrazí jejich aktuální stav. Můžete zde snadno zjistit, jak nastavit DNS záznamy a zda jsou všechny záznamy správně uloženy.",
|
||||
"dns_records_data": "Správný záznam",
|
||||
"dns_records_docs": "Přečtěte si prosím <a target=\"_blank\" href=\"https://mailcow.github.io/mailcow-dockerized-docs/prerequisite/prerequisite-dns/\">dokumentaci</a>.",
|
||||
"dns_records_docs": "Přečtěte si prosím <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">dokumentaci</a>.",
|
||||
"dns_records_name": "Název",
|
||||
"dns_records_status": "Současný stav",
|
||||
"dns_records_type": "Typ",
|
||||
"optional": "Tento záznam je volitelný."
|
||||
},
|
||||
"edit": {
|
||||
"acl": "ACL (Oprávnění)",
|
||||
"active": "Aktivní",
|
||||
"admin": "Upravit administrátora",
|
||||
"advanced_settings": "Pokročilá nastavení",
|
||||
@@ -507,6 +539,7 @@
|
||||
"allowed_protocols": "Povolené protokoly",
|
||||
"app_name": "Název aplikace",
|
||||
"app_passwd": "Heslo aplikace",
|
||||
"app_passwd_protocols": "Povolené protokoly pro hesla aplikací",
|
||||
"automap": "Pokusit se automaticky mapovat složky (\"Sent items\", \"Sent\" => \"Sent\" atd.)",
|
||||
"backup_mx_options": "Možnosti záložního MX",
|
||||
"bcc_dest_format": "Cíl kopie musí být jedna platná email adresa. Pokud potřebujete posílat kopie na více adres, vytvořte Alias a použijte jej zde.",
|
||||
@@ -541,7 +574,7 @@
|
||||
"inactive": "Neaktivní",
|
||||
"kind": "Druh",
|
||||
"last_modified": "Naposledy změněn",
|
||||
"lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (<code>.*google\\.com</code> směřuje veškerou poštu na MX které jsou cílem pro google.com přes tento skok)",
|
||||
"lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (<code>.*\\.google\\.com</code> směřuje veškerou poštu na MX které jsou cílem pro google.com přes tento skok)",
|
||||
"mailbox": "Úprava mailové schránky",
|
||||
"mailbox_quota_def": "Výchozí kvóta schránky",
|
||||
"mailbox_relayhost_info": "Aplikované jen na uživatelskou schránku a přímé aliasy, přepisuje předávající server domény.",
|
||||
@@ -590,6 +623,8 @@
|
||||
"sieve_desc": "Krátký popis",
|
||||
"sieve_type": "Typ filtru",
|
||||
"skipcrossduplicates": "Přeskočit duplicitní zprávy (\"první přijde, první mele\")",
|
||||
"sogo_access": "Udělit přímý přihlašovací přístup do služby SOGo",
|
||||
"sogo_access_info": "Jednotné přihlášení (SSO) z mail UI zůstává funkční. Toto nastavení neovlivňuje přístup ke všem ostatním službám ani neodstraňuje či nemění stávající profil uživatele SOGo.",
|
||||
"sogo_visible": "Alias dostupný v SOGo",
|
||||
"sogo_visible_info": "Tato volba určuje objekty, jež lze zobrazit v SOGo (sdílené nebo nesdílené aliasy, jež ukazuje alespoň na jednu schránku).",
|
||||
"spam_alias": "Vytvořit nebo změnit dočasné aliasy",
|
||||
@@ -642,6 +677,7 @@
|
||||
"apps": "Aplikace",
|
||||
"debug": "Systémové informace",
|
||||
"email": "E-Mail",
|
||||
"mailcow_system": "Systém",
|
||||
"mailcow_config": "Nastavení",
|
||||
"quarantine": "Karanténa",
|
||||
"restart_netfilter": "Restartovat netfilter",
|
||||
@@ -677,6 +713,7 @@
|
||||
"add_mailbox": "Přidat mailovou schránku",
|
||||
"add_recipient_map_entry": "Přidat mapu příjemce",
|
||||
"add_resource": "Přidat zdroj",
|
||||
"add_template": "Přidat šablonu",
|
||||
"add_tls_policy_map": "Přidat mapu TLS pravidel",
|
||||
"address_rewriting": "Přepisování adres",
|
||||
"alias": "Alias",
|
||||
@@ -719,6 +756,7 @@
|
||||
"domain": "Doména",
|
||||
"domain_admins": "Správci domén",
|
||||
"domain_aliases": "Doménové aliasy",
|
||||
"domain_templates": "Šablony domén",
|
||||
"domain_quota": "Kvóta",
|
||||
"domain_quota_total": "Celková kvóta domény",
|
||||
"domains": "Domény",
|
||||
@@ -747,6 +785,7 @@
|
||||
"mailbox_defaults": "Výchozí nastavení",
|
||||
"mailbox_defaults_info": "Definuje výchozí nastavení pro nové schránky",
|
||||
"mailbox_defquota": "Výchozí velikost schránky",
|
||||
"mailbox_templates": "Šablony schránek",
|
||||
"mailbox_quota": "Max. velikost schránky",
|
||||
"mailboxes": "Mailové schránky",
|
||||
"max_aliases": "Max. počet aliasů",
|
||||
@@ -814,6 +853,8 @@
|
||||
"table_size_show_n": "Zobrazit %s položek",
|
||||
"target_address": "Cílová adresa",
|
||||
"target_domain": "Cílová doména",
|
||||
"templates": "Šablony",
|
||||
"template": "Šablona",
|
||||
"tls_enforce_in": "Vynutit TLS pro příchozí",
|
||||
"tls_enforce_out": "Vynutit TLS pro odchozí",
|
||||
"tls_map_dest": "Cíl",
|
||||
@@ -978,6 +1019,9 @@
|
||||
"settings_map_added": "Přidána položka mapování nastavení",
|
||||
"settings_map_removed": "Položka mapování nastavení: %s smazána",
|
||||
"sogo_profile_reset": "SOGo profil uživatele %s vyresetován",
|
||||
"template_added": "Přidána šablona %s",
|
||||
"template_modified": "Změny šablony %s byly uloženy",
|
||||
"template_removed": "Šablona ID %s byla odstraněna",
|
||||
"tls_policy_map_entry_deleted": "Položka mapy TLS pravidel ID %s smazána",
|
||||
"tls_policy_map_entry_saved": "Položka mapy TLS pravidel \"%s\" uložena",
|
||||
"ui_texts": "Změny UI textů uloženy",
|
||||
@@ -1027,13 +1071,16 @@
|
||||
"alias_valid_until": "Platný do",
|
||||
"aliases_also_send_as": "Smí odesílat také jako uživatel",
|
||||
"aliases_send_as_all": "Nekontrolovat přístup odesílatele pro následující doménu(y) a jejich aliasy domény:",
|
||||
"allowed_protocols": "Povolené protokoly",
|
||||
"app_hint": "Hesla aplikací jsou alternativní heslo pro přihlášení k IMAP, SMTP, CalDAV, CardDAV a EAS. Uživatelské jméno zůstává stejné.<br>SOGo však nelze s heslem aplikace použít.",
|
||||
"app_name": "Název aplikace",
|
||||
"app_passwds": "Hesla aplikací",
|
||||
"apple_connection_profile": "Profil připojení Apple",
|
||||
"apple_connection_profile_complete": "Tento profil obsahuje parametry připojení k IMAP, SMTP, CalDAV (kalendáře) a CardDAV (kontakty) pro zařízení Apple.",
|
||||
"apple_connection_profile_mailonly": "Tento profil obsahuje parametry připojení k IMAP a SMTP pro zařízení Apple.",
|
||||
"apple_connection_profile_with_app_password": "Nové heslo aplikace se vygeneruje a přidá do profilu, takže při nastavování zařízení není třeba zadávat žádné heslo. Soubor nesdílejte, protože poskytuje plný přístup k vaší poštovní schránce.",
|
||||
"change_password": "Změnit heslo",
|
||||
"change_password_hint_app_passwords": "Váš účet má %d hesel aplikací, která nebudou změněna. Chcete-li je spravovat, přejděte na kartu Hesla aplikací.",
|
||||
"clear_recent_successful_connections": "Vymazat nedávné úspěšné přihlášení",
|
||||
"client_configuration": "Zobrazit průvodce nastavením e-mailových klientů a smartphonů",
|
||||
"create_app_passwd": "Vytvořit heslo aplikace",
|
||||
@@ -1044,6 +1091,7 @@
|
||||
"delete_ays": "Potvrďte odstranění.",
|
||||
"direct_aliases": "Přímé aliasy",
|
||||
"direct_aliases_desc": "Na přímé aliasy se uplatňuje filtr spamu a nastavení pravidel TLS",
|
||||
"direct_protocol_access": "Tento uživatel mailové schránky má <b>přímý externí přístup</b> k následujícím protokolům a aplikacím. Toto nastavení je řízeno správcem. Pro udělení přístupu k jednotlivým protokolům a aplikacím lze vytvořit hesla aplikací.<br>Tlačítko \" Přihlaste se do webmailu\" zajišťuje jednotné přihlášení k SOGo a je vždy k dispozici.",
|
||||
"eas_reset": "Smazat mezipaměť zařízení ActiveSync",
|
||||
"eas_reset_help": "Obnovení mezipaměti zařízení pomůže zpravidla obnovit poškozený profil služby ActiveSync.<br><b>Upozornění:</b> Všechna data budou opětovně stažena!",
|
||||
"eas_reset_now": "Smazat",
|
||||
@@ -1137,15 +1185,15 @@
|
||||
"spamfilter_yellow": "Žlutá: tato zpráva může být spam, bude označena jako spam a přesunuta do složky nevyžádané pošty",
|
||||
"status": "Stav",
|
||||
"sync_jobs": "Synchronizační úlohy",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikací",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživatelské jméno nebo heslo",
|
||||
"syncjob_EXIT_CONNECTION_FAILURE": "Problém se spojením",
|
||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nelze se připojit ke vzdálenému serveru",
|
||||
"syncjob_EXIT_OVERQUOTA": "Cílová schránka je plná",
|
||||
"syncjob_EXIT_TLS_FAILURE": "Problém se šifrovaným spojením",
|
||||
"syncjob_EX_OK": "Úspěch",
|
||||
"syncjob_check_log": "Zkontrolujte záznam",
|
||||
"syncjob_last_run_result": "Výsledek posledního spuštění",
|
||||
"syncjob_EX_OK": "Úspěch",
|
||||
"syncjob_EXIT_CONNECTION_FAILURE": "Problém se spojením",
|
||||
"syncjob_EXIT_TLS_FAILURE": "Problém se šifrovaným spojením",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problém s autentifikací",
|
||||
"syncjob_EXIT_OVERQUOTA": "Cílová schránka je plná",
|
||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nelze se připojit ke vzdálenému serveru",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Chybné uživatelské jméno nebo heslo",
|
||||
"tag_handling": "Zacházení s označkovanou poštou",
|
||||
"tag_help_example": "Příklad e-mailové adresy se značkou: me<b>+Facebook</b>@example.org",
|
||||
"tag_help_explain": "V podsložce: v doručené poště bude vytvořena nová podsložka pojmenovaná po značce zprávy (\"INBOX / Facebook\").<br>\r\nV předmětu: název značky bude přidáván k předmětu mailu, například: \"[Facebook] Moje zprávy\".",
|
||||
@@ -1165,6 +1213,7 @@
|
||||
"week": "týden",
|
||||
"weekly": "Každý týden",
|
||||
"weeks": "týdny",
|
||||
"with_app_password": "s heslem aplikace",
|
||||
"year": "rok",
|
||||
"years": "let"
|
||||
},
|
||||
|
||||
@@ -459,7 +459,7 @@
|
||||
"cname_from_a": "Værdi afledt af A / AAAA-post. Dette understøttes, så længe posten peger på den korrekte ressource.",
|
||||
"dns_records": "DNS-poster",
|
||||
"dns_records_24hours": "Bemærk, at ændringer, der foretages i DNS, kan tage op til 24 timer for at få deres aktuelle status korrekt reflekteret på denne side. Det er beregnet som en måde for dig let at se, hvordan du konfigurerer dine DNS-poster og kontrollere, om alle dine poster er korrekt gemt i DNS.",
|
||||
"dns_records_docs": "Se også <a target=\"_blank\" href=\"https://mailcow.github.io/mailcow-dockerized-docs/prerequisite/prerequisite-dns/\">dokumentationen</a>.",
|
||||
"dns_records_docs": "Se også <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">dokumentationen</a>.",
|
||||
"dns_records_data": "Korrekte data",
|
||||
"dns_records_name": "Navn",
|
||||
"dns_records_status": "Nuværende tilstand",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"domain": "Domain",
|
||||
"domain_matches_hostname": "Domain %s darf nicht dem Hostnamen entsprechen",
|
||||
"domain_quota_m": "Domain-Speicherplatz gesamt (MiB)",
|
||||
"dry": "Synchronisation simulieren",
|
||||
"enc_method": "Verschlüsselung",
|
||||
"exclude": "Elemente ausschließen (Regex)",
|
||||
"full_name": "Vor- und Nachname",
|
||||
@@ -147,6 +148,7 @@
|
||||
"change_logo": "Logo ändern",
|
||||
"configuration": "Konfiguration",
|
||||
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
|
||||
"cors_settings": "CORS Einstellungen",
|
||||
"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.",
|
||||
"customer_id": "Kunde",
|
||||
"customize": "UI-Anpassung",
|
||||
@@ -216,7 +218,7 @@
|
||||
"loading": "Bitte warten...",
|
||||
"login_time": "Zeit",
|
||||
"logo_info": "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.",
|
||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||
"main_name": "\"mailcow UI\" Name",
|
||||
"merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.",
|
||||
"message": "Nachricht",
|
||||
@@ -342,7 +344,9 @@
|
||||
"api_read_only": "Schreibgeschützter Zugriff",
|
||||
"api_read_write": "Lese-Schreib-Zugriff",
|
||||
"oauth2_apps": "OAuth2 Apps",
|
||||
"queue_unban": "entsperren"
|
||||
"queue_unban": "entsperren",
|
||||
"allowed_methods": "Access-Control-Allow-Methods",
|
||||
"allowed_origins": "Access-Control-Allow-Origin"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
|
||||
@@ -358,6 +362,8 @@
|
||||
"bcc_exists": "Ein BCC-Map-Eintrag %s existiert bereits als Typ %s",
|
||||
"bcc_must_be_email": "BCC-Ziel %s ist keine gültige E-Mail-Adresse",
|
||||
"comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten",
|
||||
"cors_invalid_method": "Allow-Methods enthält eine ungültige Methode",
|
||||
"cors_invalid_origin": "Allow-Origins enthält eine ungültige Origin",
|
||||
"defquota_empty": "Standard-Quota darf nicht 0 sein",
|
||||
"demo_mode_enabled": "Demo Mode ist aktiviert",
|
||||
"description_invalid": "Ressourcenbeschreibung für %s ist ungültig",
|
||||
@@ -498,6 +504,7 @@
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"architecture": "Architektur",
|
||||
"chart_this_server": "Chart (dieser Server)",
|
||||
"containers_info": "Container-Information",
|
||||
"container_running": "Läuft",
|
||||
@@ -534,14 +541,15 @@
|
||||
"update_available": "Es ist ein Update verfügbar",
|
||||
"no_update_available": "Das System ist auf aktuellem Stand",
|
||||
"update_failed": "Es konnte nicht nach einem Update gesucht werden",
|
||||
"username": "Benutzername"
|
||||
"username": "Benutzername",
|
||||
"wip": "Aktuell noch in Arbeit"
|
||||
},
|
||||
"diagnostics": {
|
||||
"cname_from_a": "Wert abgeleitet von A/AAAA-Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt.",
|
||||
"dns_records": "DNS-Einträge",
|
||||
"dns_records_24hours": "Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.",
|
||||
"dns_records_data": "Korrekte Daten",
|
||||
"dns_records_docs": "Die <a target=\"_blank\" href=\"https://mailcow.github.io/mailcow-dockerized-docs/prerequisite/prerequisite-dns/\">Online-Dokumentation</a> enthält weitere Informationen zur DNS-Konfiguration.",
|
||||
"dns_records_docs": "Die <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">Online-Dokumentation</a> enthält weitere Informationen zur DNS-Konfiguration.",
|
||||
"dns_records_name": "Name",
|
||||
"dns_records_status": "Aktueller Status",
|
||||
"dns_records_type": "Typ",
|
||||
@@ -574,6 +582,10 @@
|
||||
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
||||
"domain": "Domain bearbeiten",
|
||||
"domain_admin": "Domain-Administrator bearbeiten",
|
||||
"domain_footer": "Domain wide footer",
|
||||
"domain_footer_html": "HTML footer",
|
||||
"domain_footer_info": "Domain wide footer werden allen ausgehenden E-Mails hinzugefügt, die einer Adresse innerhalb dieser Domain gehört.<br>Die folgenden Variablen können für den Footer benutzt werden:",
|
||||
"domain_footer_plain": "PLAIN footer",
|
||||
"domain_quota": "Domain Speicherplatz gesamt (MiB)",
|
||||
"domains": "Domains",
|
||||
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen",
|
||||
@@ -593,7 +605,7 @@
|
||||
"inactive": "Inaktiv",
|
||||
"kind": "Art",
|
||||
"last_modified": "Zuletzt geändert",
|
||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||
"mailbox": "Mailbox bearbeiten",
|
||||
"mailbox_quota_def": "Standard-Quota einer Mailbox",
|
||||
"mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.",
|
||||
@@ -846,7 +858,7 @@
|
||||
"sieve_preset_5": "Auto-Responder (Vacation, Urlaub)",
|
||||
"sieve_preset_6": "E-Mails mit Nachricht abweisen",
|
||||
"sieve_preset_7": "Weiterleiten und behalten oder verwerfen",
|
||||
"sieve_preset_8": "Nachricht verwerfen, wenn Absender und Alias-Ziel identisch sind.",
|
||||
"sieve_preset_8": "E-Mail eines bestimmten Absenders umleiten, als gelesen markieren und in Unterordner sortieren",
|
||||
"sieve_preset_header": "Beispielinhalte zur Einsicht stehen nachstehend bereit. Siehe auch <a href=\"https://de.wikipedia.org/wiki/Sieve\" target=\"_blank\">Wikipedia</a>.",
|
||||
"sogo_visible": "Alias Sichtbarkeit in SOGo",
|
||||
"sogo_visible_n": "Alias in SOGo verbergen",
|
||||
@@ -959,7 +971,7 @@
|
||||
"queue": {
|
||||
"delete": "Queue löschen",
|
||||
"flush": "Queue flushen",
|
||||
"info": "In der Mailqueue befinden sich alle E-Mails, welche auf eine Zustellung warten. Sollte eine E-Mail eine längere Zeit innerhalb der Mailqueue stecken wird diese automatisch vom System gelöscht.<br>Die Fehlermeldung der jeweiligen Mail gibt aufschluss darüber, warum diese nicht zugestellt werden konnte",
|
||||
"info": "In der Mailqueue befinden sich alle E-Mails, welche auf eine Zustellung warten. Sollte eine E-Mail eine längere Zeit innerhalb der Mailqueue stecken wird diese automatisch vom System gelöscht.<br>Die Fehlermeldung der jeweiligen Mail gibt Aufschluss darüber, warum diese nicht zugestellt werden konnte",
|
||||
"legend": "Funktionen der Mailqueue Aktionen:",
|
||||
"ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
|
||||
"deliver_mail": "Ausliefern",
|
||||
@@ -996,6 +1008,7 @@
|
||||
"bcc_deleted": "BCC-Map-Einträge gelöscht: %s",
|
||||
"bcc_edited": "BCC-Map-Eintrag %s wurde geändert",
|
||||
"bcc_saved": "BCC- Map-Eintrag wurde gespeichert",
|
||||
"cors_headers_edited": "CORS Einstellungen wurden erfolgreich gespeichert",
|
||||
"db_init_complete": "Datenbankinitialisierung abgeschlossen",
|
||||
"delete_filter": "Filter-ID %s wurde gelöscht",
|
||||
"delete_filters": "Filter gelöscht: %s",
|
||||
@@ -1009,6 +1022,7 @@
|
||||
"domain_admin_added": "Domain-Administrator %s wurde angelegt",
|
||||
"domain_admin_modified": "Änderungen an Domain-Administrator %s wurden gespeichert",
|
||||
"domain_admin_removed": "Domain-Administrator %s wurde entfernt",
|
||||
"domain_footer_modified": "Änderungen an Domain Footer %s wurden gespeichert",
|
||||
"domain_modified": "Änderungen an Domain %s wurden gespeichert",
|
||||
"domain_removed": "Domain %s wurde entfernt",
|
||||
"dovecot_restart_success": "Dovecot wurde erfolgreich neu gestartet",
|
||||
@@ -1112,7 +1126,7 @@
|
||||
"apple_connection_profile_mailonly": "Dieses Verbindungsprofil beinhaltet IMAP- und SMTP-Konfigurationen für ein Apple-Gerät.",
|
||||
"apple_connection_profile_with_app_password": "Es wird ein neues App-Passwort erzeugt und in das Profil eingefügt, damit bei der Einrichtung kein Passwort eingegeben werden muss. Geben Sie das Profil nicht weiter, da es einen vollständigen Zugriff auf Ihr Postfach ermöglicht.",
|
||||
"change_password": "Passwort ändern",
|
||||
"change_password_hint_app_passwords": "Ihre Mailbox hat {{number_of_app_passwords}} App-Passwörter, die nicht geändert werden. Um diese zu verwalten, gehen Sie bitte zum App-Passwörter-Tab.",
|
||||
"change_password_hint_app_passwords": "Ihre Mailbox hat %d App-Passwörter, die nicht geändert werden. Um diese zu verwalten, gehen Sie bitte zum App-Passwörter-Tab.",
|
||||
"clear_recent_successful_connections": "Alle erfolgreichen Verbindungen bereinigen",
|
||||
"client_configuration": "Konfigurationsanleitungen für E-Mail-Programme und Smartphones anzeigen",
|
||||
"create_app_passwd": "Erstelle App-Passwort",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"domain": "Domain",
|
||||
"domain_matches_hostname": "Domain %s matches hostname",
|
||||
"domain_quota_m": "Total domain quota (MiB)",
|
||||
"dry": "Simulate synchronization",
|
||||
"enc_method": "Encryption method",
|
||||
"exclude": "Exclude objects (regex)",
|
||||
"full_name": "Full name",
|
||||
@@ -133,6 +134,8 @@
|
||||
"admins": "Administrators",
|
||||
"admins_ldap": "LDAP Administrators",
|
||||
"advanced_settings": "Advanced settings",
|
||||
"allowed_methods": "Access-Control-Allow-Methods",
|
||||
"allowed_origins": "Access-Control-Allow-Origin",
|
||||
"api_allow_from": "Allow API access from these IPs/CIDR network notations",
|
||||
"api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
|
||||
"api_key": "API key",
|
||||
@@ -147,8 +150,11 @@
|
||||
"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",
|
||||
"logo_normal_label": "Normal",
|
||||
"logo_dark_label": "Inverted for dark mode",
|
||||
"configuration": "Configuration",
|
||||
"convert_html_to_text": "Convert HTML to plain text",
|
||||
"cors_settings": "CORS Settings",
|
||||
"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.",
|
||||
"customer_id": "Customer ID",
|
||||
"customize": "Customize",
|
||||
@@ -218,7 +224,7 @@
|
||||
"loading": "Please wait...",
|
||||
"login_time": "Login time",
|
||||
"logo_info": "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"main_name": "\"mailcow UI\" name",
|
||||
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
|
||||
"message": "Message",
|
||||
@@ -358,6 +364,8 @@
|
||||
"bcc_exists": "A BCC map %s exists for type %s",
|
||||
"bcc_must_be_email": "BCC destination %s is not a valid email address",
|
||||
"comment_too_long": "Comment too long, max 160 chars allowed",
|
||||
"cors_invalid_method": "Invalid Allow-Method specified",
|
||||
"cors_invalid_origin": "Invalid Allow-Origin specified",
|
||||
"defquota_empty": "Default quota per mailbox must not be 0.",
|
||||
"demo_mode_enabled": "Demo Mode is enabled",
|
||||
"description_invalid": "Resource description for %s is invalid",
|
||||
@@ -498,6 +506,7 @@
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"architecture": "Architecture",
|
||||
"chart_this_server": "Chart (this server)",
|
||||
"containers_info": "Container information",
|
||||
"container_running": "Running",
|
||||
@@ -534,14 +543,15 @@
|
||||
"update_available": "There is an update available",
|
||||
"no_update_available": "The System is on the latest version",
|
||||
"update_failed": "Could not check for an Update",
|
||||
"username": "Username"
|
||||
"username": "Username",
|
||||
"wip": "Currently Work in Progress"
|
||||
},
|
||||
"diagnostics": {
|
||||
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.",
|
||||
"dns_records": "DNS Records",
|
||||
"dns_records_24hours": "Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.",
|
||||
"dns_records_data": "Correct Data",
|
||||
"dns_records_docs": "Please also consult <a target=\"_blank\" href=\"https://mailcow.github.io/mailcow-dockerized-docs/prerequisite/prerequisite-dns/\">the documentation</a>.",
|
||||
"dns_records_docs": "Please also consult <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">the documentation</a>.",
|
||||
"dns_records_name": "Name",
|
||||
"dns_records_status": "Current State",
|
||||
"dns_records_type": "Type",
|
||||
@@ -574,6 +584,17 @@
|
||||
"disable_login": "Disallow login (incoming mail is still accepted)",
|
||||
"domain": "Edit domain",
|
||||
"domain_admin": "Edit domain administrator",
|
||||
"domain_footer": "Domain wide footer",
|
||||
"domain_footer_html": "HTML footer",
|
||||
"domain_footer_info": "Domain-wide footers are added to all outgoing emails associated with an address within this domain. <br> The following variables can be used for the footer:",
|
||||
"domain_footer_info_vars": {
|
||||
"auth_user": "{= auth_user =} - Authenticated Username specified by an MTA",
|
||||
"from_user": "{= from_user =} - From user part of envelope, e.g for \"moo@mailcow.tld\" it returns \"moo\"",
|
||||
"from_name": "{= from_name =} - From name of envelope, e.g for \"Mailcow <moo@mailcow.tld>\" it returns \"Mailcow\"",
|
||||
"from_addr": "{= from_addr =} - From address part of envelope",
|
||||
"from_domain": "{= from_domain =} - From domain part of envelope"
|
||||
},
|
||||
"domain_footer_plain": "PLAIN footer",
|
||||
"domain_quota": "Domain quota",
|
||||
"domains": "Domains",
|
||||
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
|
||||
@@ -593,7 +614,7 @@
|
||||
"inactive": "Inactive",
|
||||
"kind": "Kind",
|
||||
"last_modified": "Last modified",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"mailbox": "Edit mailbox",
|
||||
"mailbox_quota_def": "Default mailbox quota",
|
||||
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
|
||||
@@ -853,7 +874,7 @@
|
||||
"sieve_preset_5": "Auto responder (vacation)",
|
||||
"sieve_preset_6": "Reject mail with reponse",
|
||||
"sieve_preset_7": "Redirect and keep/drop",
|
||||
"sieve_preset_8": "Discard message sent to an alias address the sender is part of",
|
||||
"sieve_preset_8": "Redirect e-mail from a specific sender, mark as read and sort into subfolder",
|
||||
"sieve_preset_header": "Please see the example presets below. For more details see <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedia</a>.",
|
||||
"sogo_visible": "Alias is visible in SOGo",
|
||||
"sogo_visible_n": "Hide alias in SOGo",
|
||||
@@ -1003,6 +1024,7 @@
|
||||
"bcc_deleted": "BCC map entries deleted: %s",
|
||||
"bcc_edited": "BCC map entry %s edited",
|
||||
"bcc_saved": "BCC map entry saved",
|
||||
"cors_headers_edited": "CORS settings have been saved",
|
||||
"db_init_complete": "Database initialization completed",
|
||||
"delete_filter": "Deleted filters ID %s",
|
||||
"delete_filters": "Deleted filters: %s",
|
||||
@@ -1016,6 +1038,7 @@
|
||||
"domain_admin_added": "Domain administrator %s has been added",
|
||||
"domain_admin_modified": "Changes to domain administrator %s have been saved",
|
||||
"domain_admin_removed": "Domain administrator %s has been removed",
|
||||
"domain_footer_modified": "Changes to domain footer %s have been saved",
|
||||
"domain_modified": "Changes to domain %s have been saved",
|
||||
"domain_removed": "Domain %s has been removed",
|
||||
"dovecot_restart_success": "Dovecot was restarted successfully",
|
||||
@@ -1119,7 +1142,7 @@
|
||||
"apple_connection_profile_mailonly": "This connection profile includes IMAP and SMTP configuration parameters for an Apple device.",
|
||||
"apple_connection_profile_with_app_password": "A new app password is generated and added to the profile so that no password needs to be entered when setting up your device. Please do not share the file as it grants full access to your mailbox.",
|
||||
"change_password": "Change password",
|
||||
"change_password_hint_app_passwords": "Your account has {{number_of_app_passwords}} app passwords that will not be changed. To manage these, go to the App passwords tab.",
|
||||
"change_password_hint_app_passwords": "Your account has %d app passwords that will not be changed. To manage these, go to the App passwords tab.",
|
||||
"clear_recent_successful_connections": "Clear seen successful connections",
|
||||
"client_configuration": "Show configuration guides for email clients and smartphones",
|
||||
"create_app_passwd": "Create app password",
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
"tls_policy": "Póliza de TLS",
|
||||
"unlimited_quota": "Cuota ilimitada para buzones",
|
||||
"app_passwds": "Gestionar las contraseñas de aplicaciones",
|
||||
"domain_desc": "Cambiar descripción del dominio"
|
||||
"domain_desc": "Cambiar descripción del dominio",
|
||||
"protocol_access": "Cambiar protocolo de acceso",
|
||||
"quarantine_category": "Cambiar categoría de las notificaciones de cuarentena"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "Todos los demás filtros se desactivarán cuando este filtro se active.",
|
||||
@@ -85,7 +87,13 @@
|
||||
"timeout2": "Tiempo de espera para la conexión al host local",
|
||||
"username": "Usuario",
|
||||
"validate": "Validar",
|
||||
"validation_success": "Validado exitosamente"
|
||||
"validation_success": "Validado exitosamente",
|
||||
"inactive": "Inactivo",
|
||||
"app_name": "Nombre de la App",
|
||||
"app_password": "Añadir contraseña para la app",
|
||||
"public_comment": "Comentarios públicos",
|
||||
"disable_login": "Desactivar login (el correo entrante seguirá activo)",
|
||||
"comment_info": "Los comentarios privados no son visibles al usuario, mientras que los comentarios públicos aparecerán sobre la información general del usuario"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Acceso",
|
||||
@@ -114,7 +122,7 @@
|
||||
"app_name": "Nombre de la app",
|
||||
"apps_name": "Nombre \"mailcow Apps\"",
|
||||
"arrival_time": "Tiempo de llegada (hora del servidor)",
|
||||
"ban_list_info": "La lista de IPs bloqueadas sigue a continuación: <b> red (tiempo de prohibición restante) - [acciones]</b>.<br/> Las IPs en cola para ser desbloquadas se eliminarán de la lista de bloqueos en unos pocos segundos. <br/> Las etiquetas rojas indican bloqueos permanentes permanentes mediante la inclusión en una lista negra.",
|
||||
"ban_list_info": "Lista de IPs bloqueadas: <b>red (tiempo de prohibición restante) - [acciones]</b>.<br />Las IPs en cola para ser desbloqueadas se eliminarán de la lista de bloqueos en unos pocos segundos.<br />Las etiquetas rojas indican bloqueos permanentes mediante la inclusión en la lista negra.",
|
||||
"change_logo": "Cambiar logo",
|
||||
"configuration": "Configuración",
|
||||
"credentials_transport_warning": "<b>Advertencia</b>: al agregar una nueva entrada de ruta de transporte se actualizarán las credenciales para todas las entradas con una columna de \"siguiente destino\" coincidente.",
|
||||
@@ -432,7 +440,7 @@
|
||||
},
|
||||
"header": {
|
||||
"administration": "Administración",
|
||||
"debug": "Información del sistema",
|
||||
"debug": "Información",
|
||||
"email": "E-Mail",
|
||||
"mailcow_config": "Configuración",
|
||||
"quarantine": "Cuarentena",
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Vous pouvez définir des cartes de transport vers une destination personnalisée pour ce domaine. sinon, une recherche MX sera effectuée.",
|
||||
"relay_unknown_only": "Relayer uniquement les boîtes inexistantes. Les boîtes existantes seront livrées localement.",
|
||||
"relayhost_wrapped_tls_info": "Veuillez <b>ne pas</b> utiliser des ports TLS wrappés (généralement utilisés sur le port 465).<br>\r\nUtilisez n'importe quel port non encapsulé et lancez STARTTLS. Une politique TLS pour appliquer TLS peut être créée dans \"Cartes de politique TLS\".",
|
||||
"select": "Veuillez sélectionner...",
|
||||
"select": "Veuillez sélectionner…",
|
||||
"select_domain": "Sélectionner d'abord un domaine",
|
||||
"sieve_desc": "Description courte",
|
||||
"sieve_type": "Type de filtre",
|
||||
@@ -207,7 +207,7 @@
|
||||
"last_applied": "Dernière application",
|
||||
"license_info": "Une licence n’est pas requise, mais contribue au développement.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Enregistrer votre GUID ici</a> or <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">acheter le support pour votre intallation Mailcow.</a>",
|
||||
"link": "Lien",
|
||||
"loading": "Veuillez patienter...",
|
||||
"loading": "Veuillez patienter…",
|
||||
"logo_info": "Votre image sera redimensionnée à une hauteur de 40 pixels pour la barre de navigation du haut et à un maximum de 250 pixels en largeur pour la page d'accueil. Un graphique extensible est fortement recommandé.",
|
||||
"lookup_mx": "Faire correspondre la destination à MX (.outlook.com pour acheminer tous les messages ciblés vers un MX * .outlook.com sur ce tronçon)",
|
||||
"main_name": "\"mailcow UI\" nom",
|
||||
@@ -326,7 +326,11 @@
|
||||
"password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules",
|
||||
"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>"
|
||||
"ip_check_disabled": "La vérification IP est désactivée. Vous pouvez l'activer sous<br> <strong>Système > Configuration > Options > Personnaliser</strong>",
|
||||
"logo_normal_label": "Normal",
|
||||
"logo_dark_label": "Inversé pour le mode sombre",
|
||||
"allowed_methods": "Access-Control-Allow-Methods",
|
||||
"allowed_origins": "Access-Control-Allow-Origin"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Accès refusé ou données de formulaire non valides",
|
||||
@@ -479,7 +483,7 @@
|
||||
"cname_from_a": "Valeur dérivée de l’enregistrement A/AAAA. Ceci est supporté tant que l’enregistrement indique la bonne ressource.",
|
||||
"dns_records": "Enregistrements DNS",
|
||||
"dns_records_24hours": "Veuillez noter que les modifications apportées au DNS peuvent prendre jusqu’à 24 heures pour que leurs états actuels soient correctement reflétés sur cette page. Il est conçu comme un moyen pour vous de voir facilement comment configurer vos enregistrements DNS et de vérifier si tous vos enregistrements sont correctement stockés dans les DNS.",
|
||||
"dns_records_docs": "Veuillez également consulter <a target=\"_blank\" href=\"https://mailcow.github.io/mailcow-dockerized-docs/prerequisite/prerequisite-dns/\">la documentation</a>.",
|
||||
"dns_records_docs": "Veuillez également consulter <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">la documentation</a>.",
|
||||
"dns_records_data": "Données correcte",
|
||||
"dns_records_name": "Nom",
|
||||
"dns_records_status": "Etat courant",
|
||||
@@ -588,7 +592,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": {
|
||||
@@ -598,9 +602,9 @@
|
||||
"delete_these_items": "Veuillez confirmer les modifications apportées à l’identifiant d’objet suivant",
|
||||
"hibp_nok": "Trouvé ! Il s’agit d’un mot de passe potentiellement dangereux !",
|
||||
"hibp_ok": "Aucune correspondance trouvée.",
|
||||
"loading": "Veuillez patienter...",
|
||||
"loading": "Veuillez patienter…",
|
||||
"restart_container": "Redémarrer le conteneur",
|
||||
"restart_container_info": "<b>Important:</b> Un redémarrage en douceur peut prendre un certain temps, veuillez attendre qu’il soit terminé..",
|
||||
"restart_container_info": "<b>Important:</b> Un redémarrage en douceur peut prendre un certain temps, veuillez attendre qu’il soit terminé.",
|
||||
"restart_now": "Redémarrer maintenant",
|
||||
"restarting_container": "Redémarrage du conteneur, cela peut prendre un certain temps"
|
||||
},
|
||||
@@ -935,7 +939,7 @@
|
||||
"disable_tfa": "Désactiver TFA jusqu’à la prochaine ouverture de session réussie",
|
||||
"enter_qr_code": "Votre code TOTP si votre appareil ne peut pas scanner les codes QR",
|
||||
"error_code": "Code d'erreur",
|
||||
"init_webauthn": "Initialisation, veuillez patienter...",
|
||||
"init_webauthn": "Initialisation, veuillez patienter…",
|
||||
"key_id": "Un identifiant pour votre Périphérique",
|
||||
"key_id_totp": "Un identifiant pour votre clé",
|
||||
"none": "Désactiver",
|
||||
@@ -948,8 +952,8 @@
|
||||
"tfa_token_invalid": "Token TFA invalide",
|
||||
"totp": "OTP (One Time Password = Mot de passe à usage unique : Google Authenticator, Authy, etc.)",
|
||||
"webauthn": "Authentification WebAuthn",
|
||||
"waiting_usb_auth": "<i>En attente d’un périphérique USB...</i><br><br>S’il vous plaît appuyez maintenant sur le bouton de votre périphérique USB WebAuthn.",
|
||||
"waiting_usb_register": "<i>En attente d’un périphérique USB...</i><br><br>Veuillez entrer votre mot de passe ci-dessus et confirmer votre inscription WebAuthn en appuyant sur le bouton de votre périphérique USB WebAuthn.",
|
||||
"waiting_usb_auth": "<i>En attente d’un périphérique USB…</i><br><br>S’il vous plaît appuyez maintenant sur le bouton de votre périphérique USB WebAuthn.",
|
||||
"waiting_usb_register": "<i>En attente d’un périphérique USB…</i><br><br>Veuillez entrer votre mot de passe ci-dessus et confirmer votre inscription WebAuthn en appuyant sur le bouton de votre périphérique USB WebAuthn.",
|
||||
"yubi_otp": "Authentification OTP Yubico"
|
||||
},
|
||||
"fido2": {
|
||||
@@ -999,9 +1003,9 @@
|
||||
"eas_reset": "Réinitialiser le cache de l’appareil Activesync",
|
||||
"eas_reset_help": "Dans de nombreux cas, une réinitialisation du cache de l’appareil aidera à récupérer un profil Activesync cassé.<br><b>Attention :</b> Tous les éléments seront à nouveau téléchargés !",
|
||||
"eas_reset_now": "Réinitialiser maintenant",
|
||||
"edit": "Editer",
|
||||
"email": "Email",
|
||||
"email_and_dav": "Email, calendriers et contacts",
|
||||
"edit": "Éditer",
|
||||
"email": "E-mail",
|
||||
"email_and_dav": "E-mail, calendriers et contacts",
|
||||
"encryption": "Cryptage",
|
||||
"excludes": "Exclut",
|
||||
"expire_in": "Expire dans",
|
||||
@@ -1015,7 +1019,7 @@
|
||||
"is_catch_all": "Attrape-tout pour le domaine(s)",
|
||||
"last_mail_login": "Dernière connexion mail",
|
||||
"last_run": "Dernière exécution",
|
||||
"loading": "Chargement...",
|
||||
"loading": "Chargement…",
|
||||
"mailbox_details": "Détails de la boîte",
|
||||
"messages": "messages",
|
||||
"never": "jamais",
|
||||
@@ -1051,7 +1055,7 @@
|
||||
"shared_aliases": "Adresses alias partagées",
|
||||
"shared_aliases_desc": "Les alias partagés ne sont pas affectés par les paramètres spécifiques à l’utilisateur tels que le filtre anti-spam ou la politique de chiffrement. Les filtres anti-spam correspondants ne peuvent être effectués que par un administrateur en tant que politique de domaine.",
|
||||
"show_sieve_filters": "Afficher le filtre de tamis actif de l’utilisateur",
|
||||
"sogo_profile_reset": "Remise é zéro du profil SOGo",
|
||||
"sogo_profile_reset": "Remise à zéro du profil SOGo",
|
||||
"sogo_profile_reset_help": "Ceci détruira un profil Sogo des utilisateurs et <b>supprimera toutes les données de contact et de calendrier irrécupérables</b>.",
|
||||
"sogo_profile_reset_now": "Remise à zéro du profil maintenant",
|
||||
"spam_aliases": "Alias de courriel temporaire",
|
||||
@@ -1096,7 +1100,8 @@
|
||||
"weeks": "semaines",
|
||||
"months": "mois",
|
||||
"year": "année",
|
||||
"years": "années"
|
||||
"years": "années",
|
||||
"with_app_password": "avec le mot de passe de l'application"
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"user": {
|
||||
"verify": "Επαλήθευση",
|
||||
"week": "εβδομάδα",
|
||||
"weekly": "Εβδομαδιαία",
|
||||
"weeks": "Εβδομάδες",
|
||||
"with_app_password": "με κωδικό εφαρμογής",
|
||||
"year": "χρόνος",
|
||||
"years": "χρόνια"
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Αδυναμία διαγραφής συνδεδεμένου χρήστη",
|
||||
"dovecot_restart_failed": "Απέτυχε η επανεκκίνηση του Dovecot, παρακαλώ ελέγξτε τα αρχεία καταγραφής.",
|
||||
"no_active_admin": "Αδυναμία απενεργοποίησης του τελευταίου ενεργού διαχειριστή",
|
||||
"domain_added_sogo_failed": "Προστέθηκε το όνομα χώρου αλλά απέτυχε η επανεκκίνηση του SOGo.",
|
||||
"hash_not_found": "Η κατακερματισμένη τιμή (hash value) δεν βρέθηκε ή έχει είδη διαγραφεί.",
|
||||
"ip_invalid": "Παραλείφθηκε μη έγκυρη διεύθυνση IP: %s",
|
||||
"is_not_primary_alias": "Παραλείφθηκε μη πρωτεύον ψευδώνυμο %s"
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,11 @@
|
||||
"transport_dest_format": "Szintaxis: pelda.hu, .pelda.hu, *, fiok@pelda.hu (több érték esetén vesszővel elválasztva)",
|
||||
"upload": "Feltöltés",
|
||||
"username": "Felhasználónév",
|
||||
"verify": "Ellenőrzés"
|
||||
"verify": "Ellenőrzés",
|
||||
"activate_api": "API aktiválása",
|
||||
"activate_send": "Küldés gomb aktiválása",
|
||||
"add": "Hozzáad",
|
||||
"active": "Aktív"
|
||||
},
|
||||
"edit": {
|
||||
"active": "Aktív",
|
||||
@@ -385,9 +389,23 @@
|
||||
"acl": {
|
||||
"delimiter_action": "Elhatárolás",
|
||||
"alias_domains": "Alias domainek hozzáadása",
|
||||
"app_passwds": "Alkalmazás jelszavak kezelése"
|
||||
"app_passwds": "Alkalmazás jelszavak kezelése",
|
||||
"domain_desc": "Domain leírás módosítása",
|
||||
"filters": "Szűrők",
|
||||
"login_as": "Bejelentkezés mint",
|
||||
"quarantine": "Karantén műveletek",
|
||||
"bcc_maps": "BCC címek",
|
||||
"domain_relayhost": "Relayhost megváltoztatása egy domainhez",
|
||||
"protocol_access": "Protokoll-hozzáférés módosítása",
|
||||
"quarantine_attachments": "Karantén mellékletek",
|
||||
"quarantine_category": "Karantén értesítési kategória módosítása",
|
||||
"quarantine_notification": "Karantén értesítések módosítása"
|
||||
},
|
||||
"diagnostics": {
|
||||
"dns_records": "DNS bejegyzések"
|
||||
},
|
||||
"add": {
|
||||
"username": "Felhasználónév",
|
||||
"validation_success": "Sikeres ellenőrzés"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,10 +134,10 @@
|
||||
"admins_ldap": "Amministratori LDAP",
|
||||
"advanced_settings": "Impostazioni avanzate",
|
||||
"api_allow_from": "Allow API access from these IPs/CIDR network notations",
|
||||
"api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
|
||||
"api_info": "Questa API è in modifica. La documentazione può essere trovata su <a href=\"/api\">/api</a>",
|
||||
"api_key": "Chiave API",
|
||||
"api_skip_ip_check": "Salta il controllo dell'IP per l'API",
|
||||
"app_links": "App links",
|
||||
"app_links": "Link dell'app",
|
||||
"app_name": "Nome dell'app",
|
||||
"apps_name": "Nome \"mailcow Apps\"",
|
||||
"arrival_time": "Ora di arrivo (ora del server)",
|
||||
@@ -213,7 +213,7 @@
|
||||
"loading": "Caricamento in corso...",
|
||||
"login_time": "Ora di accesso",
|
||||
"logo_info": "La tua immagine verrà ridimensionata a 40px di altezza, quando verrà usata nella barra di navigazione in alto, ed ad una larghezza massima di 250px nella schermata iniziale. È altamente consigliato l'utilizzo di un'immagine modulabile.",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"main_name": "Nome \"mailcow UI\"",
|
||||
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
|
||||
"message": "Messaggio",
|
||||
@@ -338,7 +338,8 @@
|
||||
"oauth2_apps": "App OAuth2",
|
||||
"oauth2_add_client": "Aggiungere il client OAuth2",
|
||||
"rsettings_preset_4": "Disattivare Rspamd per un dominio",
|
||||
"options": "Opzioni"
|
||||
"options": "Opzioni",
|
||||
"cors_settings": "Impostazioni CORS"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Accesso negato o form di login non corretto",
|
||||
@@ -505,7 +506,7 @@
|
||||
"dns_records": "Record DNS",
|
||||
"dns_records_24hours": "Tieni presente che le modifiche apportate ai record DNS potrebbero richiedere fino a 24 ore per poter essere visualizzate correttamente in questa pagina. Tutto ciò è da intendersi come un modo per voi di vedere come configurare i record DNS e per controllare se tutti i record DNS sono stati inseriti correttamente.",
|
||||
"dns_records_data": "Dati corretti",
|
||||
"dns_records_docs": "Si prega di consultare anche <a target=\"_blank\" href=\"https://mailcow.github.io/mailcow-dockerized-docs/prerequisite/prerequisite-dns/\">la documentazione</a>.",
|
||||
"dns_records_docs": "Si prega di consultare anche <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">la documentazione</a>.",
|
||||
"dns_records_name": "Nome",
|
||||
"dns_records_status": "Stato attuale",
|
||||
"dns_records_type": "Tipo",
|
||||
@@ -554,7 +555,7 @@
|
||||
"hostname": "Hostname",
|
||||
"inactive": "Inattivo",
|
||||
"kind": "Genere",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
"mailbox": "Modifica casella di posta",
|
||||
"mailbox_quota_def": "Default mailbox quota",
|
||||
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
|
||||
@@ -1194,7 +1195,7 @@
|
||||
"weeks": "settimane",
|
||||
"year": "anno",
|
||||
"years": "anni",
|
||||
"change_password_hint_app_passwords": "Il tuo account ha {{number_of_app_passwords}} password delle app che non verranno modificate. Per gestirle, vai alla scheda App passwords.",
|
||||
"change_password_hint_app_passwords": "Il tuo account ha %d password delle app che non verranno modificate. Per gestirle, vai alla scheda App passwords.",
|
||||
"syncjob_check_log": "Controlla i log",
|
||||
"syncjob_last_run_result": "Risultato dell'ultima esecuzione",
|
||||
"open_logs": "Apri i log",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user