Merge pull request #4910 from mailcow/staging

2022-12 The Bootstrap 5 Update
This commit is contained in:
Niklas Meyer 2022-12-24 13:49:28 +01:00 committed by GitHub
commit 355da03fba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
199 changed files with 59990 additions and 6953 deletions

View File

@ -26,21 +26,21 @@ body:
attributes: attributes:
label: Description label: Description
description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI. description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI.
render: text render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Logs:" label: "Logs:"
description: "Please take a look at the [official documentation](https://docs.mailcow.email/troubleshooting/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks." description: "Please take a look at the [official documentation](https://docs.mailcow.email/troubleshooting/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks."
render: text render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Steps to reproduce:" label: "Steps to reproduce:"
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful." description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
render: text render: plain text
placeholder: |- placeholder: |-
1. ... 1. ...
2. ... 2. ...
@ -117,41 +117,41 @@ body:
attributes: attributes:
label: "Logs of git diff:" label: "Logs of git diff:"
description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:" description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:"
render: text render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Logs of iptables -L -vn:" label: "Logs of iptables -L -vn:"
description: "#### Output of `iptables -L -vn`" description: "#### Output of `iptables -L -vn`"
render: text render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Logs of ip6tables -L -vn:" label: "Logs of ip6tables -L -vn:"
description: "#### Output of `ip6tables -L -vn`" description: "#### Output of `ip6tables -L -vn`"
render: text render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Logs of iptables -L -vn -t nat:" label: "Logs of iptables -L -vn -t nat:"
description: "#### Output of `iptables -L -vn -t nat`" description: "#### Output of `iptables -L -vn -t nat`"
render: text render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Logs of ip6tables -L -vn -t nat:" label: "Logs of ip6tables -L -vn -t nat:"
description: "#### Output of `ip6tables -L -vn -t nat`" description: "#### Output of `ip6tables -L -vn -t nat`"
render: text render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "DNS check:" label: "DNS check:"
description: "#### Output of `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network)" description: "#### Output of `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network)"
render: text render: plain text
validations: validations:
required: true required: true

13
.github/renovate.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"enabled": true,
"timezone": "Europe/Berlin",
"dependencyDashboard": false,
"dependencyDashboardTitle": "Renovate Dashboard",
"commitBody": "Signed-off-by: milkmaker <milkmaker@mailcow.de>",
"rebaseWhen": "auto",
"assignees": [
"@magiccc"
],
"baseBranches": ["staging"],
"enabledManagers": ["github-actions"]
}

View File

@ -33,13 +33,11 @@ jobs:
run: | run: |
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
sudo service docker start sudo service docker start
sudo curl -L https://github.com/docker/compose/releases/download/v$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- name: Prepair Image Builds - name: Prepair Image Builds
run: | run: |
cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml
- name: Build Docker Images - name: Build Docker Images
run: | run: |
docker-compose build ${image} docker compose build ${image}
env: env:
image: ${{ matrix.images }} image: ${{ matrix.images }}

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM clamav/clamav:0.105.1_base FROM clamav/clamav:1.0_base
LABEL maintainer "André Peters <andre.peters@servercow.de>" LABEL maintainer "André Peters <andre.peters@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
@ -8,11 +8,14 @@ RUN apk add --update --no-cache python3 \
py3-pip \ py3-pip \
openssl \ openssl \
tzdata \ tzdata \
py3-psutil \
&& pip3 install --upgrade pip \ && pip3 install --upgrade pip \
docker \ fastapi \
flask \ uvicorn \
flask-restful aiodocker \
redis
COPY docker-entrypoint.sh /app/
COPY dockerapi.py /app/ COPY dockerapi.py /app/
CMD ["python3", "-u", "/app/dockerapi.py"] ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]

View File

@ -0,0 +1,9 @@
#!/bin/bash
`openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout /app/dockerapi_key.pem \
-out /app/dockerapi_cert.pem \
-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`

View File

@ -1,419 +1,623 @@
#!/usr/bin/env python3 from fastapi import FastAPI, Response, Request
import aiodocker
from flask import Flask import psutil
from flask_restful import Resource, Api import sys
from flask import jsonify import re
from flask import Response
from flask import request
from threading import Thread
import docker
import uuid
import signal
import time import time
import os import os
import re import json
import sys import asyncio
import ssl import redis
import socket from datetime import datetime
import subprocess
import traceback
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
app = Flask(__name__)
api = Api(app)
class containers_get(Resource): containerIds_to_update = []
def get(self): host_stats_isUpdating = False
containers = {} app = FastAPI()
@app.get("/host/stats")
async def get_host_update_stats():
global host_stats_isUpdating
if host_stats_isUpdating == False:
print("start host stats task")
asyncio.create_task(get_host_stats())
host_stats_isUpdating = True
while True:
if redis_client.exists('host_stats'):
break
print("wait for host_stats results")
await asyncio.sleep(1.5)
print("host stats pulled")
stats = json.loads(redis_client.get('host_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
@app.get("/containers/{container_id}/json")
async def get_container(container_id : str):
if container_id and container_id.isalnum():
try: try:
for container in docker_client.containers.list(all=True): for container in (await async_docker_client.containers.list()):
containers.update({container.attrs['Id']: container.attrs}) if container._id == container_id:
return containers 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: except Exception as e:
return jsonify(type='danger', msg=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": "no or invalid id defined"
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
class container_get(Resource): @app.get("/containers/json")
def get(self, container_id): async def get_containers():
if container_id and container_id.isalnum(): containers = {}
try: try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in (await async_docker_client.containers.list()):
return container.attrs container_info = await container.show()
except Exception as e: containers.update({container_info['Id']: container_info})
return jsonify(type='danger', msg=str(e)) return Response(content=json.dumps(containers, indent=4), media_type="application/json")
else: except Exception as e:
return jsonify(type='danger', msg='no or invalid id defined') res = {
"type": "danger",
"msg": str(e)
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
class container_post(Resource): @app.post("/containers/{container_id}/{post_action}")
def post(self, container_id, post_action): async def post_containers(container_id : str, post_action : str, request: Request):
if container_id and container_id.isalnum() and post_action: try :
try: request_json = await request.json()
"""Dispatch container_post api call""" except Exception as err:
if post_action == 'exec': request_json = {}
if not request.json or not 'cmd' in request.json:
return jsonify(type='danger', msg='cmd is missing')
if not request.json or not 'task' in request.json:
return jsonify(type='danger', msg='task is missing')
api_call_method_name = '__'.join(['container_post', str(post_action), str(request.json['cmd']), str(request.json['task']) ]) if container_id and container_id.isalnum() and post_action:
else: try:
api_call_method_name = '__'.join(['container_post', str(post_action) ]) """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 = getattr(self, api_call_method_name, lambda container_id: jsonify(type='danger', msg='container_post - unknown api call')) 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(async_docker_client)
api_call_method = getattr(docker_utils, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json"))
print("api call: %s, container_id: %s" % (api_call_method_name, container_id)) print("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return api_call_method(container_id) return await api_call_method(container_id, request_json)
except Exception as e: except Exception as e:
print("error - container_post: %s" % str(e)) print("error - container_post: %s" % str(e))
return jsonify(type='danger', msg=str(e)) res = {
"type": "danger",
"msg": str(e)
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else: else:
return jsonify(type='danger', msg='invalid container id or missing action') 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 # api call: container_post - post_action: stop
def container_post__stop(self, container_id): async def container_post__stop(self, container_id, request_json):
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
container.stop() if container._id == container_id:
return jsonify(type='success', msg='command completed successfully') await 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 # api call: container_post - post_action: start
def container_post__start(self, container_id): async def container_post__start(self, container_id, request_json):
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
container.start() if container._id == container_id:
return jsonify(type='success', msg='command completed successfully') await 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 # api call: container_post - post_action: restart
def container_post__restart(self, container_id): async def container_post__restart(self, container_id, request_json):
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
container.restart() if container._id == container_id:
return jsonify(type='success', msg='command completed successfully') await 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 # api call: container_post - post_action: top
def container_post__top(self, container_id): async def container_post__top(self, container_id, request_json):
for container in docker_client.containers.list(all=True, filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
return jsonify(type='success', msg=container.top()) if container._id == container_id:
ps_exec = await container.exec("ps")
async with ps_exec.start(detach=False) as stream:
ps_return = await stream.read_out()
exec_details = await ps_exec.inspect()
# api call: container_post - post_action: stats if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
def container_post__stats(self, container_id): res = {
for container in docker_client.containers.list(all=True, filters={"id": container_id}): 'type': 'success',
for stat in container.stats(decode=True, stream=True): 'msg': ps_return.data.decode('utf-8')
return jsonify(type='success', msg=stat ) }
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'danger',
'msg': ''
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: mailq - task: delete # api call: container_post - post_action: exec - cmd: mailq - task: delete
def container_post__exec__mailq__delete(self, container_id): async def container_post__exec__mailq__delete(self, container_id, request_json):
if 'items' in request.json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-d %s' % i for i in filtered_qids] flagged_qids = ['-d %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids)); sanitized_string = str(' '.join(flagged_qids))
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) if container._id == container_id:
return exec_run_handler('generic', postsuper_r) postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: hold # api call: container_post - post_action: exec - cmd: mailq - task: hold
def container_post__exec__mailq__hold(self, container_id): async def container_post__exec__mailq__hold(self, container_id, request_json):
if 'items' in request.json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-h %s' % i for i in filtered_qids] flagged_qids = ['-h %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids)); sanitized_string = str(' '.join(flagged_qids))
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) if container._id == container_id:
return exec_run_handler('generic', postsuper_r) postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: cat # api call: container_post - post_action: exec - cmd: mailq - task: cat
def container_post__exec__mailq__cat(self, container_id): async def container_post__exec__mailq__cat(self, container_id, request_json):
if 'items' in request.json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
sanitized_string = str(' '.join(filtered_qids)); sanitized_string = str(' '.join(filtered_qids))
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') if container._id == container_id:
if not postcat_return: postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
postcat_return = 'err: invalid' return await exec_run_handler('utf8_text_only', postcat_exec)
return exec_run_handler('utf8_text_only', postcat_return)
# api call: container_post - post_action: exec - cmd: mailq - task: unhold # api call: container_post - post_action: exec - cmd: mailq - task: unhold
def container_post__exec__mailq__unhold(self, container_id): async def container_post__exec__mailq__unhold(self, container_id, request_json):
if 'items' in request.json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-H %s' % i for i in filtered_qids] flagged_qids = ['-H %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids)); sanitized_string = str(' '.join(flagged_qids))
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) if container._id == container_id:
return exec_run_handler('generic', postsuper_r) postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: deliver # api call: container_post - post_action: exec - cmd: mailq - task: deliver
def container_post__exec__mailq__deliver(self, container_id): async def container_post__exec__mailq__deliver(self, container_id, request_json):
if 'items' in request.json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request.json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-i %s' % i for i in filtered_qids] flagged_qids = ['-i %s' % i for i in filtered_qids]
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
for i in flagged_qids: if container._id == container_id:
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') for i in flagged_qids:
# todo: check each exit code postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
return jsonify(type='success', msg=str("Scheduled immediate delivery")) async with postsuper_r_exec.start(detach=False) as stream:
postsuper_r_return = await stream.read_out()
# todo: check each exit code
res = {
'type': 'success',
'msg': 'Scheduled immediate delivery'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: mailq - task: list # api call: container_post - post_action: exec - cmd: mailq - task: list
def container_post__exec__mailq__list(self, container_id): async def container_post__exec__mailq__list(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix') if container._id == container_id:
return exec_run_handler('utf8_text_only', mailq_return) mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix')
return await exec_run_handler('utf8_text_only', mailq_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: flush # api call: container_post - post_action: exec - cmd: mailq - task: flush
def container_post__exec__mailq__flush(self, container_id): async def container_post__exec__mailq__flush(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix') if container._id == container_id:
return exec_run_handler('generic', postqueue_r) postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix')
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete # api call: container_post - post_action: exec - cmd: mailq - task: super_delete
def container_post__exec__mailq__super_delete(self, container_id): async def container_post__exec__mailq__super_delete(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"]) if container._id == container_id:
return exec_run_handler('generic', postsuper_r) postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan # api call: container_post - post_action: exec - cmd: system - task: fts_rescan
def container_post__exec__system__fts_rescan(self, container_id): async def container_post__exec__system__fts_rescan(self, container_id, request_json):
if 'username' in request.json: if 'username' in request_json:
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail') if container._id == container_id:
if rescan_return.exit_code == 0: rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
return jsonify(type='success', msg='fts_rescan: rescan triggered') async with rescan_exec.start(detach=False) as stream:
else: rescan_return = await stream.read_out()
return jsonify(type='warning', msg='fts_rescan error')
if 'all' in request.json: exec_details = await rescan_exec.inspect()
for container in docker_client.containers.list(filters={"id": container_id}): if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail') res = {
if rescan_return.exit_code == 0: 'type': 'success',
return jsonify(type='success', msg='fts_rescan: rescan triggered') 'msg': 'fts_rescan: rescan triggered'
else: }
return jsonify(type='warning', msg='fts_rescan error') return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'warning',
'msg': 'fts_rescan error'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if 'all' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
async with rescan_exec.start(detach=False) as stream:
rescan_return = await stream.read_out()
exec_details = await rescan_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'success',
'msg': 'fts_rescan: rescan triggered'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'warning',
'msg': 'fts_rescan error'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: system - task: df # api call: container_post - post_action: exec - cmd: system - task: df
def container_post__exec__system__df(self, container_id): async def container_post__exec__system__df(self, container_id, request_json):
if 'dir' in request.json: if 'dir' in request_json:
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
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 container._id == container_id:
if df_return.exit_code == 0: df_exec = await container.exec(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
return df_return.output.decode('utf-8').rstrip() async with df_exec.start(detach=False) as stream:
else: df_return = await stream.read_out()
return "0,0,0,0,0,0"
print(df_return)
print(await df_exec.inspect())
exec_details = await df_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
return df_return.data.decode('utf-8').rstrip()
else:
return "0,0,0,0,0,0"
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade # api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
def container_post__exec__system__mysql_upgrade(self, container_id): async def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql') if container._id == container_id:
if sql_return.exit_code == 0: sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
matched = False async with sql_exec.start(detach=False) as stream:
for line in sql_return.output.decode('utf-8').split("\n"): sql_return = await stream.read_out()
if 'is already upgraded to' in line:
matched = True exec_details = await sql_exec.inspect()
if matched: if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
return jsonify(type='success', msg='mysql_upgrade: already upgraded', text=sql_return.output.decode('utf-8')) matched = False
for line in sql_return.data.decode('utf-8').split("\n"):
if 'is already upgraded to' in line:
matched = True
if matched:
res = {
'type': 'success',
'msg': 'mysql_upgrade: already upgraded',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
await container.restart()
res = {
'type': 'warning',
'msg': 'mysql_upgrade: upgrade was applied',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else: else:
container.restart() res = {
return jsonify(type='warning', msg='mysql_upgrade: upgrade was applied', text=sql_return.output.decode('utf-8')) 'type': 'error',
else: 'msg': 'mysql_upgrade: error running command',
return jsonify(type='error', msg='mysql_upgrade: error running command', text=sql_return.output.decode('utf-8')) 'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql # 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): async def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
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 container._id == container_id:
if sql_return.exit_code == 0: sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
return jsonify(type='info', msg='mysql_tzinfo_to_sql: command completed successfully', text=sql_return.output.decode('utf-8')) async with sql_exec.start(detach=False) as stream:
else: sql_return = await stream.read_out()
return jsonify(type='error', msg='mysql_tzinfo_to_sql: error running command', text=sql_return.output.decode('utf-8'))
exec_details = await sql_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'info',
'msg': 'mysql_tzinfo_to_sql: command completed successfully',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'error',
'msg': 'mysql_tzinfo_to_sql: error running command',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: reload - task: dovecot # api call: container_post - post_action: exec - cmd: reload - task: dovecot
def container_post__exec__reload__dovecot(self, container_id): async def container_post__exec__reload__dovecot(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"]) if container._id == container_id:
return exec_run_handler('generic', reload_return) reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: reload - task: postfix # api call: container_post - post_action: exec - cmd: reload - task: postfix
def container_post__exec__reload__postfix(self, container_id): async def container_post__exec__reload__postfix(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"]) if container._id == container_id:
return exec_run_handler('generic', reload_return) reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: reload - task: nginx # api call: container_post - post_action: exec - cmd: reload - task: nginx
def container_post__exec__reload__nginx(self, container_id): async def container_post__exec__reload__nginx(self, container_id, request_json):
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"]) if container._id == container_id:
return exec_run_handler('generic', reload_return) reload_exec = await container.exec(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: sieve - task: list # api call: container_post - post_action: exec - cmd: sieve - task: list
def container_post__exec__sieve__list(self, container_id): async def container_post__exec__sieve__list(self, container_id, request_json):
if 'username' in request.json: if 'username' in request_json:
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"]) if container._id == container_id:
return exec_run_handler('utf8_text_only', sieve_return) sieve_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
return await exec_run_handler('utf8_text_only', sieve_exec)
# api call: container_post - post_action: exec - cmd: sieve - task: print # api call: container_post - post_action: exec - cmd: sieve - task: print
def container_post__exec__sieve__print(self, container_id): async def container_post__exec__sieve__print(self, container_id, request_json):
if 'username' in request.json and 'script_name' in request.json: if 'username' in request_json and 'script_name' in request_json:
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"] if container._id == container_id:
sieve_return = container.exec_run(cmd) cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
return exec_run_handler('utf8_text_only', sieve_return) sieve_exec = await container.exec(cmd)
return await exec_run_handler('utf8_text_only', sieve_exec)
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup # api call: container_post - post_action: exec - cmd: maildir - task: cleanup
def container_post__exec__maildir__cleanup(self, container_id): async def container_post__exec__maildir__cleanup(self, container_id, request_json):
if 'maildir' in request.json: if 'maildir' in request_json:
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
sane_name = re.sub(r'\W+', '', request.json['maildir']) if container._id == container_id:
cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"] sane_name = re.sub(r'\W+', '', request_json['maildir'])
maildir_cleanup = container.exec_run(cmd, user='vmail') cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"]
return exec_run_handler('generic', maildir_cleanup) maildir_cleanup_exec = await container.exec(cmd, user='vmail')
return await exec_run_handler('generic', maildir_cleanup_exec)
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
def container_post__exec__rspamd__worker_password(self, container_id): async def container_post__exec__rspamd__worker_password(self, container_id, request_json):
if 'raw' in request.json: if 'raw' in request_json:
for container in docker_client.containers.list(filters={"id": container_id}): for container in (await self.docker_client.containers.list()):
cmd = "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null" if container._id == container_id:
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
matched = False cmd = "./set_worker_password.sh '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
for line in cmd_response.split("\n"): rspamd_password_exec = await container.exec(cmd, user='_rspamd')
if '$2$' in line: async with rspamd_password_exec.start(detach=False) as stream:
hash = line.strip() rspamd_password_return = await stream.read_out()
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" matched = False
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename) if "OK" in rspamd_password_return.data.decode('utf-8'):
cmd_response = exec_cmd_container(container, cmd, user="_rspamd") matched = True
await container.restart()
if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response: if matched:
container.restart() res = {
matched = True 'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'danger',
'msg': 'command did not complete'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if matched:
return jsonify(type='success', msg='command completed successfully')
else:
return jsonify(type='danger', msg='command did not complete')
def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
def recv_socket_data(c_socket, timeout): async def exec_run_handler(type, exec_obj):
c_socket.setblocking(0) async with exec_obj.start(detach=False) as stream:
total_data=[]; exec_return = await stream.read_out()
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 : if exec_return == None:
socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock exec_return = ""
if not cmd.endswith("\n"): else:
cmd = cmd + "\n" exec_return = exec_return.data.decode('utf-8')
socket.send(cmd.encode('utf-8'))
data = recv_socket_data(socket, timeout)
socket.close()
return data
except Exception as e: if type == 'generic':
print("error - exec_cmd_container: %s" % str(e)) exec_details = await exec_obj.inspect()
traceback.print_exc(file=sys.stdout) if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
def exec_run_handler(type, output): "type": "success",
if type == 'generic': "msg": "command completed successfully"
if output.exit_code == 0: }
return jsonify(type='success', msg='command completed successfully') return Response(content=json.dumps(res, indent=4), media_type="application/json")
else: else:
return jsonify(type='danger', msg='command failed: ' + output.output.decode('utf-8')) res = {
"type": "success",
"msg": "'command failed: " + exec_return
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if type == 'utf8_text_only': if type == 'utf8_text_only':
r = Response(response=output.output.decode('utf-8'), status=200, mimetype="text/plain") return Response(content=exec_return, media_type="text/plain")
r.headers["Content-Type"] = "text/plain; charset=utf-8"
return r
class GracefulKiller: async def get_host_stats(wait=5):
kill_now = False global host_stats_isUpdating
def __init__(self):
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
def exit_gracefully(self, signum, frame):
self.kill_now = True
def create_self_signed_cert():
process = subprocess.Popen(
"openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout /app/dockerapi_key.pem -out /app/dockerapi_cert.pem -subj /CN=dockerapi/O=mailcow -addext subjectAltName=DNS:dockerapi".split(),
stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=False
)
process.wait()
def startFlaskAPI():
create_self_signed_cert()
try: try:
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) system_time = datetime.now()
ctx.check_hostname = False host_stats = {
ctx.load_cert_chain(certfile='/app/dockerapi_cert.pem', keyfile='/app/dockerapi_key.pem') "cpu": {
except: "cores": psutil.cpu_count(),
print ("Cannot initialize TLS, retrying in 5s...") "usage": psutil.cpu_percent()
time.sleep(5) },
app.run(debug=False, host='0.0.0.0', port=443, threaded=True, ssl_context=ctx) "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")
}
api.add_resource(containers_get, '/containers/json') redis_client.set('host_stats', json.dumps(host_stats), ex=10)
api.add_resource(container_get, '/containers/<string:container_id>/json') except Exception as e:
api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>') res = {
"type": "danger",
"msg": str(e)
}
print(json.dumps(res, indent=4))
if __name__ == '__main__': await asyncio.sleep(wait)
api_thread = Thread(target=startFlaskAPI) host_stats_isUpdating = False
api_thread.daemon = True
api_thread.start()
killer = GracefulKiller() async def get_container_stats(container_id, wait=5, stop=False):
while True: global containerIds_to_update
time.sleep(1)
if killer.kill_now: if container_id and container_id.isalnum():
break try:
print ("Stopping dockerapi-mailcow") 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)
}
print(json.dumps(res, indent=4))
else:
res = {
"type": "danger",
"msg": "no or invalid id defined"
}
print(json.dumps(res, indent=4))
await asyncio.sleep(wait)
if stop == True:
# 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)
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV XTABLES_LIBDIR /usr/lib/xtables ENV XTABLES_LIBDIR /usr/lib/xtables

View File

@ -97,9 +97,9 @@ def refreshF2bregex():
f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+' f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+'
f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+' f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+'
f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+' f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'
f2bregex[6] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),' f2bregex[6] = '-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'
f2bregex[7] = '-login: Aborted login \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' f2bregex[7] = '-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[8] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' f2bregex[8] = '-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False)) r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
WORKDIR /app WORKDIR /app

View File

@ -1,4 +1,4 @@
FROM php:8.1-fpm-alpine3.16 FROM php:8.1-fpm-alpine3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV APCU_PECL 5.1.22 ENV APCU_PECL 5.1.22

View File

@ -26,6 +26,7 @@ RUN apt-get update && apt-get install -y \
COPY settings.conf /etc/rspamd/settings.conf COPY settings.conf /etc/rspamd/settings.conf
COPY metadata_exporter.lua /usr/share/rspamd/plugins/metadata_exporter.lua COPY metadata_exporter.lua /usr/share/rspamd/plugins/metadata_exporter.lua
COPY set_worker_password.sh /set_worker_password.sh
COPY docker-entrypoint.sh /docker-entrypoint.sh COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@ -0,0 +1,12 @@
#!/bin/bash
password_file='/etc/rspamd/override.d/worker-controller-password.inc'
password_hash=`/usr/bin/rspamadm pw -e -p $1`
echo 'enable_password = "'$password_hash'";' > $password_file
if grep -q "$password_hash" "$password_file"; then
echo "OK"
else
echo "ERROR"
fi

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "André Peters <andre.peters@servercow.de>" LABEL maintainer "André Peters <andre.peters@servercow.de>"
# Installation # Installation

View File

@ -3,7 +3,6 @@
/.*episerver.*/i /.*episerver.*/i
/.*supergewinne.*/i /.*supergewinne.*/i
/List-Unsubscribe.*nbps\.eu/i /List-Unsubscribe.*nbps\.eu/i
/X-Mailer: AWeber.*/i
/.*regiofinder.*/i /.*regiofinder.*/i
/.*EmailSocket.*/i /.*EmailSocket.*/i
/List-Unsubscribe:.*respread.*/i /List-Unsubscribe:.*respread.*/i

View File

@ -54,6 +54,7 @@ $rcpts = $headers['X-Rspamd-Rcpt'];
$sender = $headers['X-Rspamd-From']; $sender = $headers['X-Rspamd-From'];
$ip = $headers['X-Rspamd-Ip']; $ip = $headers['X-Rspamd-Ip'];
$subject = $headers['X-Rspamd-Subject']; $subject = $headers['X-Rspamd-Subject'];
$messageid= $json_body->message_id;
$priority = 0; $priority = 0;
$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); $symbols_array = json_decode($headers['X-Rspamd-Symbols'], true);
@ -245,13 +246,13 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
"token" => $api_data['token'], "token" => $api_data['token'],
"user" => $api_data['key'], "user" => $api_data['key'],
"title" => sprintf("%s", str_replace( "title" => sprintf("%s", str_replace(
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}'), array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'),
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address), $title) array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title)
), ),
"priority" => $priority, "priority" => $priority,
"message" => sprintf("%s", str_replace( "message" => sprintf("%s", str_replace(
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '\n'), array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'),
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, PHP_EOL), $text) array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text)
), ),
"sound" => $attributes['sound'] ?? "pushover" "sound" => $attributes['sound'] ?? "pushover"
); );

View File

@ -13,12 +13,12 @@
Please check the logs or contact support if the error persists.</p> Please check the logs or contact support if the error persists.</p>
<h2>Quick debugging</h2> <h2>Quick debugging</h2>
<p>Check Nginx and PHP logs:</p> <p>Check Nginx and PHP logs:</p>
<pre>docker-compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre> <pre>docker compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre>
<p>Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:</p> <p>Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:</p>
<pre>source mailcow.conf ; docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre> <pre>source mailcow.conf ; docker compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre>
<p>In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (<b>NEVER</b> do this with a production system, it will remove <b>ALL</b> data):</p> <p>In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (<b>NEVER</b> do this with a production system, it will remove <b>ALL</b> data):</p>
<pre>BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all</pre> <pre>BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all</pre>
<pre>docker-compose down --volumes ; docker-compose up -d</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> <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://mailcow.github.io/mailcow-dockerized-docs/#get-support" target="_blank">getting support.</a>
</body> </body>

View File

@ -83,7 +83,6 @@ foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map) {
]; ];
} }
$template = 'admin.twig'; $template = 'admin.twig';
$template_data = [ $template_data = [
'tfa_data' => $tfa_data, 'tfa_data' => $tfa_data,
@ -91,7 +90,6 @@ $template_data = [
'fido2_cid' => @$_SESSION['fido2_cid'], 'fido2_cid' => @$_SESSION['fido2_cid'],
'fido2_data' => $fido2_data, 'fido2_data' => $fido2_data,
'gal' => @$_SESSION['gal'], 'gal' => @$_SESSION['gal'],
'license_guid' => license('guid'),
'api' => [ 'api' => [
'ro' => admin_api('ro', 'get'), 'ro' => admin_api('ro', 'get'),
'rw' => admin_api('rw', 'get'), 'rw' => admin_api('rw', 'get'),
@ -112,6 +110,7 @@ $template_data = [
'password_complexity' => password_complexity('get'), 'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables'])
]; ];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
@media (max-width:1050px){.navbar-header{float:none}.navbar-left,.navbar-nav,.navbar-right{float:none!important}.navbar-toggle{display:block}.navbar-collapse{border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-collapse.collapse{display:none!important}.navbar-nav{margin-top:7.5px}.navbar-nav>li{float:none}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px}.collapse.in{display:block!important}.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}} @media (max-width:1050px){.navbar-header{float:none}.navbar-left,.navbar-nav,.navbar-right{float:none!important}.navbar-toggle{display:block}.navbar-collapse{border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-nav{margin-top:7.5px}.navbar-nav>li{float:none}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px}.collapse.in{display:block!important}.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}}

View File

@ -0,0 +1,487 @@
/*!
* Bootstrap-select v1.14.0-beta2 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2021 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
@-webkit-keyframes bs-notify-fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
@-o-keyframes bs-notify-fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
@keyframes bs-notify-fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
select.bs-select-hidden,
.bootstrap-select > select.bs-select-hidden,
select.selectpicker {
display: none !important;
}
.bootstrap-select {
width: 220px \0;
/*IE9 and below*/
vertical-align: middle;
}
.bootstrap-select > .dropdown-toggle {
position: relative;
width: 100%;
text-align: right;
white-space: nowrap;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.bootstrap-select > .dropdown-toggle:after {
margin-top: -1px;
}
.bootstrap-select > .dropdown-toggle.bs-placeholder,
.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder:active {
color: #999;
}
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:hover,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:focus,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:active,
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:active {
color: rgba(255, 255, 255, 0.5);
}
.bootstrap-select > select {
position: absolute !important;
bottom: 0;
left: 50%;
display: block !important;
width: 0.5px !important;
height: 100% !important;
padding: 0 !important;
opacity: 0 !important;
border: none;
z-index: 0 !important;
}
.bootstrap-select > select.mobile-device {
top: 0;
left: 0;
display: block !important;
width: 100% !important;
z-index: 2 !important;
}
.has-error .bootstrap-select .dropdown-toggle,
.error .bootstrap-select .dropdown-toggle,
.bootstrap-select.is-invalid .dropdown-toggle,
.was-validated .bootstrap-select select:invalid + .dropdown-toggle {
border-color: #b94a48;
}
.bootstrap-select.is-valid .dropdown-toggle,
.was-validated .bootstrap-select select:valid + .dropdown-toggle {
border-color: #28a745;
}
.bootstrap-select.fit-width {
width: auto !important;
}
.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
width: 220px;
}
.bootstrap-select > select.mobile-device:focus + .dropdown-toggle,
.bootstrap-select .dropdown-toggle:focus {
outline: thin dotted #333333 !important;
outline: 5px auto -webkit-focus-ring-color !important;
outline-offset: -2px;
}
.bootstrap-select.form-control {
margin-bottom: 0;
padding: 0;
border: none;
height: auto;
}
:not(.input-group) > .bootstrap-select.form-control:not([class*="col-"]) {
width: 100%;
}
.bootstrap-select.form-control.input-group-btn {
float: none;
z-index: auto;
}
.form-inline .bootstrap-select,
.form-inline .bootstrap-select.form-control:not([class*="col-"]) {
width: auto;
}
.bootstrap-select:not(.input-group-btn),
.bootstrap-select[class*="col-"] {
float: none;
display: inline-block;
margin-left: 0;
}
.bootstrap-select.dropdown-menu-right,
.bootstrap-select[class*="col-"].dropdown-menu-right,
.row .bootstrap-select[class*="col-"].dropdown-menu-right {
float: right;
}
.form-inline .bootstrap-select,
.form-horizontal .bootstrap-select,
.form-group .bootstrap-select {
margin-bottom: 0;
}
.form-group-lg .bootstrap-select.form-control,
.form-group-sm .bootstrap-select.form-control {
padding: 0;
}
.form-group-lg .bootstrap-select.form-control .dropdown-toggle,
.form-group-sm .bootstrap-select.form-control .dropdown-toggle {
height: 100%;
font-size: inherit;
line-height: inherit;
border-radius: inherit;
}
.bootstrap-select.form-control-sm .dropdown-toggle,
.bootstrap-select.form-control-lg .dropdown-toggle {
font-size: inherit;
line-height: inherit;
border-radius: inherit;
}
.bootstrap-select.form-control-sm .dropdown-toggle {
padding: 0.25rem 0.5rem;
}
.bootstrap-select.form-control-lg .dropdown-toggle {
padding: 0.5rem 1rem;
}
.form-inline .bootstrap-select .form-control {
width: 100%;
}
.bootstrap-select.disabled,
.bootstrap-select > .disabled {
cursor: not-allowed;
}
.bootstrap-select.disabled:focus,
.bootstrap-select > .disabled:focus {
outline: none !important;
}
.bootstrap-select.bs-container {
position: absolute;
top: 0;
left: 0;
height: 0 !important;
padding: 0 !important;
}
.bootstrap-select.bs-container .dropdown-menu {
z-index: 1060;
}
.bootstrap-select .dropdown-toggle .filter-option {
position: static;
top: 0;
left: 0;
float: left;
height: 100%;
width: 100%;
text-align: left;
overflow: hidden;
-webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
}
.bs3.bootstrap-select .dropdown-toggle .filter-option {
padding-right: inherit;
}
.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option {
position: absolute;
padding-top: inherit;
padding-bottom: inherit;
padding-left: inherit;
float: none;
}
.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner {
padding-right: inherit;
}
.bootstrap-select .dropdown-toggle .filter-option-inner-inner {
overflow: hidden;
}
.bootstrap-select .dropdown-toggle .filter-expand {
width: 0 !important;
float: left;
opacity: 0 !important;
overflow: hidden;
}
.bootstrap-select .dropdown-toggle .caret {
position: absolute;
top: 50%;
right: 12px;
margin-top: -2px;
vertical-align: middle;
}
.bootstrap-select .dropdown-toggle .bs-select-clear-selected {
position: relative;
display: block;
margin-right: 5px;
text-align: center;
}
.bs3.bootstrap-select .dropdown-toggle .bs-select-clear-selected {
padding-right: inherit;
}
.bootstrap-select .dropdown-toggle .bs-select-clear-selected span {
position: relative;
top: -webkit-calc(((-1em / 1.5) + 1ex) / 2);
top: calc(((-1em / 1.5) + 1ex) / 2);
pointer-events: none;
}
.bs3.bootstrap-select .dropdown-toggle .bs-select-clear-selected span {
top: auto;
}
.bootstrap-select .dropdown-toggle.bs-placeholder .bs-select-clear-selected {
display: none;
}
.input-group .bootstrap-select.form-control .dropdown-toggle {
border-radius: inherit;
}
.bootstrap-select[class*="col-"] .dropdown-toggle {
width: 100%;
}
.bootstrap-select .dropdown-menu {
min-width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bootstrap-select .dropdown-menu > .inner:focus {
outline: none !important;
}
.bootstrap-select .dropdown-menu.inner {
position: static;
float: none;
border: 0;
padding: 0;
margin: 0;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.bootstrap-select .dropdown-menu li {
position: relative;
}
.bootstrap-select .dropdown-menu li.active small {
color: rgba(255, 255, 255, 0.5) !important;
}
.bootstrap-select .dropdown-menu li.disabled a {
cursor: not-allowed;
}
.bootstrap-select .dropdown-menu li a {
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.bootstrap-select .dropdown-menu li a.opt {
position: relative;
padding-left: 2.25em;
}
.bootstrap-select .dropdown-menu li a span.check-mark {
display: none;
}
.bootstrap-select .dropdown-menu li a span.text {
display: inline-block;
}
.bootstrap-select .dropdown-menu li small {
padding-left: 0.5em;
}
.bootstrap-select .dropdown-menu .notify {
position: absolute;
bottom: 5px;
width: 96%;
margin: 0 2%;
min-height: 26px;
padding: 3px 5px;
background: #f5f5f5;
border: 1px solid #e3e3e3;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
pointer-events: none;
opacity: 0.9;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bootstrap-select .dropdown-menu .notify.fadeOut {
-webkit-animation: 300ms linear 750ms forwards bs-notify-fadeOut;
-o-animation: 300ms linear 750ms forwards bs-notify-fadeOut;
animation: 300ms linear 750ms forwards bs-notify-fadeOut;
}
.bootstrap-select .no-results {
padding: 3px;
background: #f5f5f5;
margin: 0 5px;
white-space: nowrap;
}
.bootstrap-select.fit-width .dropdown-toggle .filter-option {
position: static;
display: inline;
padding: 0;
}
.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,
.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner {
display: inline;
}
.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before {
content: '\00a0';
}
.bootstrap-select.fit-width .dropdown-toggle .caret {
position: static;
top: auto;
margin-top: -1px;
}
.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark {
position: absolute;
display: inline-block;
right: 15px;
top: 5px;
}
.bootstrap-select.show-tick .dropdown-menu li a span.text {
margin-right: 34px;
}
.bootstrap-select .bs-ok-default:after {
content: '';
display: block;
width: 0.5em;
height: 1em;
border-style: solid;
border-width: 0 0.26em 0.26em 0;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle,
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle {
z-index: 1061;
}
.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before {
content: '';
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid rgba(204, 204, 204, 0.2);
position: absolute;
bottom: -4px;
left: 9px;
display: none;
}
.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after {
content: '';
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid white;
position: absolute;
bottom: -4px;
left: 10px;
display: none;
}
.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before {
bottom: auto;
top: -4px;
border-top: 7px solid rgba(204, 204, 204, 0.2);
border-bottom: 0;
}
.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after {
bottom: auto;
top: -4px;
border-top: 6px solid white;
border-bottom: 0;
}
.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before {
right: 12px;
left: auto;
}
.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after {
right: 13px;
left: auto;
}
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:before,
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:before,
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:after,
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:after {
display: block;
}
.bs-searchbox,
.bs-actionsbox,
.bs-donebutton {
padding: 4px 8px;
}
.bs-actionsbox {
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bs-actionsbox .btn-group {
display: block;
}
.bs-actionsbox .btn-group button {
width: 50%;
}
.bs-donebutton {
float: left;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bs-donebutton .btn-group {
display: block;
}
.bs-donebutton .btn-group button {
width: 100%;
}
.bs-searchbox + .bs-actionsbox {
padding: 0 8px 4px;
}
.bs-searchbox .form-control {
margin-bottom: 0;
width: 100%;
float: none;
}
/*# sourceMappingURL=bootstrap-select.css.map */

File diff suppressed because one or more lines are too long

View File

@ -1,323 +0,0 @@
table.footable-details,
table.footable > thead > tr.footable-filtering > th div.form-group {
margin-bottom: 0;
}
table.footable,
table.footable-details {
position: relative;
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
table.footable-hide-fouc {
display: none;
}
table > tbody > tr > td > span.footable-toggle {
margin-right: 8px;
opacity: 0.3;
}
table > tbody > tr > td > span.footable-toggle.last-column {
margin-left: 8px;
float: right;
}
table.table-condensed > tbody > tr > td > span.footable-toggle {
margin-right: 5px;
}
table.footable-details > tbody > tr > th:nth-child(1) {
min-width: 40px;
width: 120px;
}
table.footable-details > tbody > tr > td:nth-child(2) {
word-break: break-all;
}
table.footable-details > tbody > tr:first-child > td,
table.footable-details > tbody > tr:first-child > th,
table.footable-details > tfoot > tr:first-child > td,
table.footable-details > tfoot > tr:first-child > th,
table.footable-details > thead > tr:first-child > td,
table.footable-details > thead > tr:first-child > th {
border-top-width: 0;
}
table.footable-details.table-bordered > tbody > tr:first-child > td,
table.footable-details.table-bordered > tbody > tr:first-child > th,
table.footable-details.table-bordered > tfoot > tr:first-child > td,
table.footable-details.table-bordered > tfoot > tr:first-child > th,
table.footable-details.table-bordered > thead > tr:first-child > td,
table.footable-details.table-bordered > thead > tr:first-child > th {
border-top-width: 1px;
}
div.footable-loader {
vertical-align: middle;
text-align: center;
height: 300px;
position: relative;
}
div.footable-loader > span.fooicon {
display: inline-block;
opacity: 0.3;
font-size: 30px;
line-height: 32px;
width: 32px;
height: 32px;
margin-top: -16px;
margin-left: -16px;
position: absolute;
top: 50%;
left: 50%;
-webkit-animation: fooicon-spin-r 2s infinite linear;
animation: fooicon-spin-r 2s infinite linear;
}
table.footable > tbody > tr.footable-empty > td {
vertical-align: middle;
text-align: center;
font-size: 30px;
}
table.footable > tbody > tr > td,
table.footable > tbody > tr > th {
display: none;
}
table.footable > tbody > tr.footable-detail-row > td,
table.footable > tbody > tr.footable-detail-row > th,
table.footable > tbody > tr.footable-empty > td,
table.footable > tbody > tr.footable-empty > th {
display: table-cell;
}
@-webkit-keyframes fooicon-spin-r {
0% {
-webkit-transform: rotate(0);
transform: rotate(0);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fooicon-spin-r {
0% {
-webkit-transform: rotate(0);
transform: rotate(0);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
.fooicon {
position: relative;
top: 0px;
display: inline-block;
font-family: "bootstrap-icons" !important;
font-style: normal;
font-weight: 400;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@-moz-document url-prefix() {
.fooicon {
top: 2px;
}
}
.fooicon:after,
.fooicon:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.fooicon-loader:before {
content: "\f130";
}
.fooicon-plus:before {
content: "\f4fd";
}
.fooicon-minus:before {
content: "\f2e9";
}
.fooicon-search:before {
content: "\f52a";
}
.fooicon-remove:before {
content: "\f62a";
}
.fooicon-sort:before {
content: "\f3c6";
}
.fooicon-sort-asc:before {
content: "\f575";
}
.fooicon-sort-desc:before {
content: "\f57b";
}
.fooicon-pencil:before {
content: "\f4c9";
}
.fooicon-trash:before {
content: "\f62a";
}
.fooicon-eye-close:before {
content: "\f33f";
}
.fooicon-flash:before {
content: "\f46e";
}
.fooicon-cog:before {
content: "\f3e2";
}
.fooicon-stats:before {
content: "\f359";
}
table.footable > thead > tr.footable-filtering > th {
border-bottom-width: 1px;
font-weight: 400;
}
.footable-filtering-external.footable-filtering-right,
table.footable.footable-filtering-right > thead > tr.footable-filtering > th,
table.footable > thead > tr.footable-filtering > th {
text-align: right;
}
.footable-filtering-external.footable-filtering-left,
table.footable.footable-filtering-left > thead > tr.footable-filtering > th {
text-align: left;
}
.footable-filtering-external.footable-filtering-center,
.footable-paging-external.footable-paging-center,
table.footable-paging-center > tfoot > tr.footable-paging > td,
table.footable.footable-filtering-center > thead > tr.footable-filtering > th,
table.footable > tfoot > tr.footable-paging > td {
text-align: center;
}
table.footable > thead > tr.footable-filtering > th div.form-group + div.form-group {
margin-top: 5px;
}
table.footable > thead > tr.footable-filtering > th div.input-group {
width: 100%;
}
.footable-filtering-external ul.dropdown-menu > li > a.checkbox,
table.footable > thead > tr.footable-filtering > th ul.dropdown-menu > li > a.checkbox {
margin: 0;
display: block;
position: relative;
}
.footable-filtering-external ul.dropdown-menu > li > a.checkbox > label,
table.footable > thead > tr.footable-filtering > th ul.dropdown-menu > li > a.checkbox > label {
display: block;
padding-left: 20px;
}
.footable-filtering-external ul.dropdown-menu > li > a.checkbox input[type="checkbox"],
table.footable > thead > tr.footable-filtering > th ul.dropdown-menu > li > a.checkbox input[type="checkbox"] {
position: absolute;
margin-left: -20px;
}
@media (min-width: 768px) {
table.footable > thead > tr.footable-filtering > th div.input-group {
width: auto;
}
table.footable > thead > tr.footable-filtering > th div.form-group {
margin-left: 2px;
margin-right: 2px;
}
table.footable > thead > tr.footable-filtering > th div.form-group + div.form-group {
margin-top: 0;
}
}
table.footable > tbody > tr > td.footable-sortable,
table.footable > tbody > tr > th.footable-sortable,
table.footable > tfoot > tr > td.footable-sortable,
table.footable > tfoot > tr > th.footable-sortable,
table.footable > thead > tr > td.footable-sortable,
table.footable > thead > tr > th.footable-sortable {
position: relative;
padding-right: 30px;
cursor: pointer;
}
td.footable-sortable > span.fooicon,
th.footable-sortable > span.fooicon {
position: absolute;
right: 6px;
top: 50%;
margin-top: -7px;
opacity: 0;
transition: opacity 0.3s ease-in;
}
td.footable-sortable.footable-asc > span.fooicon,
td.footable-sortable.footable-desc > span.fooicon,
td.footable-sortable:hover > span.fooicon,
th.footable-sortable.footable-asc > span.fooicon,
th.footable-sortable.footable-desc > span.fooicon,
th.footable-sortable:hover > span.fooicon {
opacity: 1;
}
table.footable-sorting-disabled td.footable-sortable.footable-asc > span.fooicon,
table.footable-sorting-disabled td.footable-sortable.footable-desc > span.fooicon,
table.footable-sorting-disabled td.footable-sortable:hover > span.fooicon,
table.footable-sorting-disabled th.footable-sortable.footable-asc > span.fooicon,
table.footable-sorting-disabled th.footable-sortable.footable-desc > span.fooicon,
table.footable-sorting-disabled th.footable-sortable:hover > span.fooicon {
opacity: 0;
visibility: hidden;
}
.footable-paging-external ul.pagination,
table.footable > tfoot > tr.footable-paging > td > ul.pagination {
margin: 10px 0 0;
}
.footable-paging-external span.label,
table.footable > tfoot > tr.footable-paging > td > span.label {
display: inline-block;
margin: 0 0 10px;
padding: 4px 10px;
}
.footable-paging-external.footable-paging-left,
table.footable-paging-left > tfoot > tr.footable-paging > td {
text-align: left;
}
.footable-paging-external.footable-paging-right,
table.footable-editing-right td.footable-editing,
table.footable-editing-right tr.footable-editing,
table.footable-paging-right > tfoot > tr.footable-paging > td {
text-align: right;
}
ul.pagination > li.footable-page {
display: none;
}
ul.pagination > li.footable-page.visible {
display: inline;
}
td.footable-editing {
width: 90px;
max-width: 90px;
}
table.footable-editing-no-delete td.footable-editing,
table.footable-editing-no-edit td.footable-editing,
table.footable-editing-no-view td.footable-editing {
width: 70px;
max-width: 70px;
}
table.footable-editing-no-delete.footable-editing-no-view td.footable-editing,
table.footable-editing-no-edit.footable-editing-no-delete td.footable-editing,
table.footable-editing-no-edit.footable-editing-no-view td.footable-editing {
width: 50px;
max-width: 50px;
}
table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view td.footable-editing,
table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view th.footable-editing {
width: 0;
max-width: 0;
display: none !important;
}
table.footable-editing-left td.footable-editing,
table.footable-editing-left tr.footable-editing {
text-align: left;
}
table.footable-editing button.footable-add,
table.footable-editing button.footable-hide,
table.footable-editing-show button.footable-show,
table.footable-editing.footable-editing-always-show button.footable-hide,
table.footable-editing.footable-editing-always-show button.footable-show,
table.footable-editing.footable-editing-always-show.footable-editing-no-add tr.footable-editing {
display: none;
}
table.footable-editing.footable-editing-always-show button.footable-add,
table.footable-editing.footable-editing-show button.footable-add,
table.footable-editing.footable-editing-show button.footable-hide {
display: inline-block;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,693 @@
/*
* This combined file was created by the DataTables downloader builder:
* https://datatables.net/download
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs5/dt-1.12.0/r-2.3.0/sl-1.4.0
*
* Included libraries:
* DataTables 1.12.0, Responsive 2.3.0, Select 1.4.0
*/
@charset "UTF-8";
table.dataTable td.dt-control {
text-align: center;
cursor: pointer;
}
table.dataTable td.dt-control:before {
height: 1em;
width: 1em;
margin-top: -9px;
display: inline-block;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #31b131;
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "-";
background-color: #d33333;
}
table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled,
table.dataTable thead > tr > td.sorting,
table.dataTable thead > tr > td.sorting_asc,
table.dataTable thead > tr > td.sorting_desc,
table.dataTable thead > tr > td.sorting_asc_disabled,
table.dataTable thead > tr > td.sorting_desc_disabled {
cursor: pointer;
position: relative;
padding-right: 26px;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
position: absolute;
display: block;
opacity: 0.125;
right: 10px;
line-height: 9px;
font-size: 0.9em;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%;
content: "▴";
}
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%;
content: "▾";
}
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:after {
opacity: 0.6;
}
table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before {
display: none;
}
table.dataTable thead > tr > th:active,
table.dataTable thead > tr > td:active {
outline: none;
}
div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after,
div.dataTables_scrollBody table.dataTable thead > tr > td:before,
div.dataTables_scrollBody table.dataTable thead > tr > td:after {
display: none;
}
div.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
margin-top: -26px;
text-align: center;
padding: 2px;
}
div.dataTables_processing > div:last-child {
position: relative;
width: 80px;
height: 15px;
margin: 1em auto;
}
div.dataTables_processing > div:last-child > div {
position: absolute;
top: 0;
width: 13px;
height: 13px;
border-radius: 50%;
background: rgba(13, 110, 253, 0.9);
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
div.dataTables_processing > div:last-child > div:nth-child(1) {
left: 8px;
animation: datatables-loader-1 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(2) {
left: 8px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(3) {
left: 32px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(4) {
left: 56px;
animation: datatables-loader-3 0.6s infinite;
}
@keyframes datatables-loader-1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes datatables-loader-3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes datatables-loader-2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th,
table.dataTable thead td,
table.dataTable tfoot th,
table.dataTable tfoot td {
text-align: left;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
/*! Bootstrap 5 integration for DataTables
*
* ©2020 SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit
*/
table.dataTable {
clear: both;
margin-top: 6px !important;
margin-bottom: 6px !important;
max-width: none !important;
border-collapse: separate !important;
border-spacing: 0;
}
table.dataTable td,
table.dataTable th {
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
table.dataTable td.dataTables_empty,
table.dataTable th.dataTables_empty {
text-align: center;
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: none;
}
table.dataTable > tbody > tr {
background-color: transparent;
}
table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9);
color: white;
}
table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05);
}
table.dataTable.table-striped > tbody > tr.odd.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
}
table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.075);
}
table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
}
div.dataTables_wrapper div.dataTables_length label {
font-weight: normal;
text-align: left;
white-space: nowrap;
}
div.dataTables_wrapper div.dataTables_length select {
width: auto;
display: inline-block;
}
div.dataTables_wrapper div.dataTables_filter {
text-align: right;
}
div.dataTables_wrapper div.dataTables_filter label {
font-weight: normal;
white-space: nowrap;
text-align: left;
}
div.dataTables_wrapper div.dataTables_filter input {
margin-left: 0.5em;
display: inline-block;
width: auto;
}
div.dataTables_wrapper div.dataTables_info {
padding-top: 0.85em;
}
div.dataTables_wrapper div.dataTables_paginate {
margin: 0;
white-space: nowrap;
text-align: right;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
margin: 2px 0;
white-space: nowrap;
justify-content: flex-end;
}
div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table {
border-top: none;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table > thead .sorting:before,
div.dataTables_scrollBody > table > thead .sorting_asc:before,
div.dataTables_scrollBody > table > thead .sorting_desc:before,
div.dataTables_scrollBody > table > thead .sorting:after,
div.dataTables_scrollBody > table > thead .sorting_asc:after,
div.dataTables_scrollBody > table > thead .sorting_desc:after {
display: none;
}
div.dataTables_scrollBody > table > tbody tr:first-child th,
div.dataTables_scrollBody > table > tbody tr:first-child td {
border-top: none;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner {
box-sizing: content-box;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
margin-top: 0 !important;
border-top: none;
}
@media screen and (max-width: 767px) {
div.dataTables_wrapper div.dataTables_length,
div.dataTables_wrapper div.dataTables_filter,
div.dataTables_wrapper div.dataTables_info,
div.dataTables_wrapper div.dataTables_paginate {
text-align: center;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
justify-content: center !important;
}
}
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px;
}
table.dataTable.table-sm .sorting:before,
table.dataTable.table-sm .sorting_asc:before,
table.dataTable.table-sm .sorting_desc:before {
top: 5px;
right: 0.85em;
}
table.dataTable.table-sm .sorting:after,
table.dataTable.table-sm .sorting_asc:after,
table.dataTable.table-sm .sorting_desc:after {
top: 5px;
}
table.table-bordered.dataTable {
border-right-width: 0;
}
table.table-bordered.dataTable thead tr:first-child th,
table.table-bordered.dataTable thead tr:first-child td {
border-top-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-left-width: 0;
}
table.table-bordered.dataTable th:first-child, table.table-bordered.dataTable th:first-child,
table.table-bordered.dataTable td:first-child,
table.table-bordered.dataTable td:first-child {
border-left-width: 1px;
}
table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,
table.table-bordered.dataTable td:last-child,
table.table-bordered.dataTable td:last-child {
border-right-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-bottom-width: 1px;
}
div.dataTables_scrollHead table.table-bordered {
border-bottom-width: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row {
margin: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:first-child {
padding-left: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-child {
padding-right: 0;
}
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 {
cursor: default !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before {
display: none !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control {
position: relative;
padding-left: 30px;
cursor: pointer;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before {
top: 50%;
left: 5px;
height: 1em;
width: 1em;
margin-top: -9px;
display: block;
position: absolute;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #0d6efd;
}
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th.dtr-control:before {
content: "-";
background-color: #d33333;
}
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control,
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control {
padding-left: 27px;
}
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control:before {
left: 4px;
height: 14px;
width: 14px;
border-radius: 14px;
line-height: 14px;
text-indent: 3px;
}
table.dataTable.dtr-column > tbody > tr > td.dtr-control,
table.dataTable.dtr-column > tbody > tr > th.dtr-control,
table.dataTable.dtr-column > tbody > tr > td.control,
table.dataTable.dtr-column > tbody > tr > th.control {
position: relative;
cursor: pointer;
}
table.dataTable.dtr-column > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-column > tbody > tr > th.dtr-control:before,
table.dataTable.dtr-column > tbody > tr > td.control:before,
table.dataTable.dtr-column > tbody > tr > th.control:before {
top: 50%;
left: 50%;
height: 0.8em;
width: 0.8em;
margin-top: -0.5em;
margin-left: -0.5em;
display: block;
position: absolute;
color: white;
border: 0.15em solid white;
border-radius: 1em;
box-shadow: 0 0 0.2em #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: "Courier New", Courier, monospace;
line-height: 1em;
content: "+";
background-color: #0d6efd;
}
table.dataTable.dtr-column > tbody > tr.parent td.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent th.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent td.control:before,
table.dataTable.dtr-column > tbody > tr.parent th.control:before {
content: "-";
background-color: #d33333;
}
table.dataTable > tbody > tr.child {
padding: 0.5em 1em;
}
table.dataTable > tbody > tr.child:hover {
background: transparent !important;
}
table.dataTable > tbody > tr.child ul.dtr-details {
display: inline-block;
list-style-type: none;
margin: 0;
padding: 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li {
border-bottom: 1px solid #efefef;
padding: 0.5em 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:first-child {
padding-top: 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:last-child {
border-bottom: none;
}
table.dataTable > tbody > tr.child span.dtr-title {
display: inline-block;
min-width: 75px;
font-weight: bold;
}
div.dtr-modal {
position: fixed;
box-sizing: border-box;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 100;
padding: 10em 1em;
}
div.dtr-modal div.dtr-modal-display {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 50%;
height: 50%;
overflow: auto;
margin: auto;
z-index: 102;
overflow: auto;
background-color: #f5f5f7;
border: 1px solid black;
border-radius: 0.5em;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
}
div.dtr-modal div.dtr-modal-content {
position: relative;
padding: 1em;
}
div.dtr-modal div.dtr-modal-close {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border: 1px solid #eaeaea;
background-color: #f9f9f9;
text-align: center;
border-radius: 3px;
cursor: pointer;
z-index: 12;
}
div.dtr-modal div.dtr-modal-close:hover {
background-color: #eaeaea;
}
div.dtr-modal div.dtr-modal-background {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 101;
background: rgba(0, 0, 0, 0.6);
}
@media screen and (max-width: 767px) {
div.dtr-modal div.dtr-modal-display {
width: 95%;
}
}
div.dtr-bs-modal table.table tr:first-child td {
border-top: none;
}
table.dataTable.table-bordered th.dtr-control.dtr-hidden + *,
table.dataTable.table-bordered td.dtr-control.dtr-hidden + * {
border-left-width: 1px;
}
table.dataTable > tbody > tr > .selected {
background-color: rgba(13, 110, 253, 0.9);
color: white;
}
table.dataTable tbody td.select-checkbox,
table.dataTable tbody th.select-checkbox {
position: relative;
}
table.dataTable tbody td.select-checkbox:before, table.dataTable tbody td.select-checkbox:after,
table.dataTable tbody th.select-checkbox:before,
table.dataTable tbody th.select-checkbox:after {
display: block;
position: absolute;
top: 1.2em;
left: 50%;
width: 12px;
height: 12px;
box-sizing: border-box;
}
table.dataTable tbody td.select-checkbox:before,
table.dataTable tbody th.select-checkbox:before {
content: " ";
margin-top: -5px;
margin-left: -6px;
border: 1px solid black;
border-radius: 3px;
}
table.dataTable tr.selected td.select-checkbox:before,
table.dataTable tr.selected th.select-checkbox:before {
border: 1px solid white;
}
table.dataTable tr.selected td.select-checkbox:after,
table.dataTable tr.selected th.select-checkbox:after {
content: "✓";
font-size: 20px;
margin-top: -19px;
margin-left: -6px;
text-align: center;
text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9;
}
table.dataTable.compact tbody td.select-checkbox:before,
table.dataTable.compact tbody th.select-checkbox:before {
margin-top: -12px;
}
table.dataTable.compact tr.selected td.select-checkbox:after,
table.dataTable.compact tr.selected th.select-checkbox:after {
margin-top: -16px;
}
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0.5em;
}
@media screen and (max-width: 640px) {
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0;
display: block;
}
}
table.dataTable.table-sm tbody td.select-checkbox::before {
margin-top: -9px;
}

View File

@ -1 +0,0 @@
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}

View File

@ -1,7 +1,7 @@
@font-face { @font-face {
font-family: "bootstrap-icons"; font-family: "bootstrap-icons";
src: url("/fonts/bootstrap-icons.woff2?45695e8b569b2b0178db2713ca47065c") format("woff2"), src: url("/fonts/bootstrap-icons.woff2?524846017b983fc8ded9325d94ed40f3") format("woff2"),
url("/fonts/bootstrap-icons.woff?45695e8b569b2b0178db2713ca47065c") format("woff"); url("/fonts/bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff");
} }
.bi::before, .bi::before,
@ -19,6 +19,7 @@ url("/fonts/bootstrap-icons.woff?45695e8b569b2b0178db2713ca47065c") format("woff
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.bi-123::before { content: "\f67f"; }
.bi-alarm-fill::before { content: "\f101"; } .bi-alarm-fill::before { content: "\f101"; }
.bi-alarm::before { content: "\f102"; } .bi-alarm::before { content: "\f102"; }
.bi-align-bottom::before { content: "\f103"; } .bi-align-bottom::before { content: "\f103"; }
@ -1425,3 +1426,279 @@ url("/fonts/bootstrap-icons.woff?45695e8b569b2b0178db2713ca47065c") format("woff
.bi-webcam-fill::before { content: "\f67c"; } .bi-webcam-fill::before { content: "\f67c"; }
.bi-webcam::before { content: "\f67d"; } .bi-webcam::before { content: "\f67d"; }
.bi-yin-yang::before { content: "\f67e"; } .bi-yin-yang::before { content: "\f67e"; }
.bi-bandaid-fill::before { content: "\f680"; }
.bi-bandaid::before { content: "\f681"; }
.bi-bluetooth::before { content: "\f682"; }
.bi-body-text::before { content: "\f683"; }
.bi-boombox::before { content: "\f684"; }
.bi-boxes::before { content: "\f685"; }
.bi-dpad-fill::before { content: "\f686"; }
.bi-dpad::before { content: "\f687"; }
.bi-ear-fill::before { content: "\f688"; }
.bi-ear::before { content: "\f689"; }
.bi-envelope-check-1::before { content: "\f68a"; }
.bi-envelope-check-fill::before { content: "\f68b"; }
.bi-envelope-check::before { content: "\f68c"; }
.bi-envelope-dash-1::before { content: "\f68d"; }
.bi-envelope-dash-fill::before { content: "\f68e"; }
.bi-envelope-dash::before { content: "\f68f"; }
.bi-envelope-exclamation-1::before { content: "\f690"; }
.bi-envelope-exclamation-fill::before { content: "\f691"; }
.bi-envelope-exclamation::before { content: "\f692"; }
.bi-envelope-plus-fill::before { content: "\f693"; }
.bi-envelope-plus::before { content: "\f694"; }
.bi-envelope-slash-1::before { content: "\f695"; }
.bi-envelope-slash-fill::before { content: "\f696"; }
.bi-envelope-slash::before { content: "\f697"; }
.bi-envelope-x-1::before { content: "\f698"; }
.bi-envelope-x-fill::before { content: "\f699"; }
.bi-envelope-x::before { content: "\f69a"; }
.bi-explicit-fill::before { content: "\f69b"; }
.bi-explicit::before { content: "\f69c"; }
.bi-git::before { content: "\f69d"; }
.bi-infinity::before { content: "\f69e"; }
.bi-list-columns-reverse::before { content: "\f69f"; }
.bi-list-columns::before { content: "\f6a0"; }
.bi-meta::before { content: "\f6a1"; }
.bi-mortorboard-fill::before { content: "\f6a2"; }
.bi-mortorboard::before { content: "\f6a3"; }
.bi-nintendo-switch::before { content: "\f6a4"; }
.bi-pc-display-horizontal::before { content: "\f6a5"; }
.bi-pc-display::before { content: "\f6a6"; }
.bi-pc-horizontal::before { content: "\f6a7"; }
.bi-pc::before { content: "\f6a8"; }
.bi-playstation::before { content: "\f6a9"; }
.bi-plus-slash-minus::before { content: "\f6aa"; }
.bi-projector-fill::before { content: "\f6ab"; }
.bi-projector::before { content: "\f6ac"; }
.bi-qr-code-scan::before { content: "\f6ad"; }
.bi-qr-code::before { content: "\f6ae"; }
.bi-quora::before { content: "\f6af"; }
.bi-quote::before { content: "\f6b0"; }
.bi-robot::before { content: "\f6b1"; }
.bi-send-check-fill::before { content: "\f6b2"; }
.bi-send-check::before { content: "\f6b3"; }
.bi-send-dash-fill::before { content: "\f6b4"; }
.bi-send-dash::before { content: "\f6b5"; }
.bi-send-exclamation-1::before { content: "\f6b6"; }
.bi-send-exclamation-fill::before { content: "\f6b7"; }
.bi-send-exclamation::before { content: "\f6b8"; }
.bi-send-fill::before { content: "\f6b9"; }
.bi-send-plus-fill::before { content: "\f6ba"; }
.bi-send-plus::before { content: "\f6bb"; }
.bi-send-slash-fill::before { content: "\f6bc"; }
.bi-send-slash::before { content: "\f6bd"; }
.bi-send-x-fill::before { content: "\f6be"; }
.bi-send-x::before { content: "\f6bf"; }
.bi-send::before { content: "\f6c0"; }
.bi-steam::before { content: "\f6c1"; }
.bi-terminal-dash-1::before { content: "\f6c2"; }
.bi-terminal-dash::before { content: "\f6c3"; }
.bi-terminal-plus::before { content: "\f6c4"; }
.bi-terminal-split::before { content: "\f6c5"; }
.bi-ticket-detailed-fill::before { content: "\f6c6"; }
.bi-ticket-detailed::before { content: "\f6c7"; }
.bi-ticket-fill::before { content: "\f6c8"; }
.bi-ticket-perforated-fill::before { content: "\f6c9"; }
.bi-ticket-perforated::before { content: "\f6ca"; }
.bi-ticket::before { content: "\f6cb"; }
.bi-tiktok::before { content: "\f6cc"; }
.bi-window-dash::before { content: "\f6cd"; }
.bi-window-desktop::before { content: "\f6ce"; }
.bi-window-fullscreen::before { content: "\f6cf"; }
.bi-window-plus::before { content: "\f6d0"; }
.bi-window-split::before { content: "\f6d1"; }
.bi-window-stack::before { content: "\f6d2"; }
.bi-window-x::before { content: "\f6d3"; }
.bi-xbox::before { content: "\f6d4"; }
.bi-ethernet::before { content: "\f6d5"; }
.bi-hdmi-fill::before { content: "\f6d6"; }
.bi-hdmi::before { content: "\f6d7"; }
.bi-usb-c-fill::before { content: "\f6d8"; }
.bi-usb-c::before { content: "\f6d9"; }
.bi-usb-fill::before { content: "\f6da"; }
.bi-usb-plug-fill::before { content: "\f6db"; }
.bi-usb-plug::before { content: "\f6dc"; }
.bi-usb-symbol::before { content: "\f6dd"; }
.bi-usb::before { content: "\f6de"; }
.bi-boombox-fill::before { content: "\f6df"; }
.bi-displayport-1::before { content: "\f6e0"; }
.bi-displayport::before { content: "\f6e1"; }
.bi-gpu-card::before { content: "\f6e2"; }
.bi-memory::before { content: "\f6e3"; }
.bi-modem-fill::before { content: "\f6e4"; }
.bi-modem::before { content: "\f6e5"; }
.bi-motherboard-fill::before { content: "\f6e6"; }
.bi-motherboard::before { content: "\f6e7"; }
.bi-optical-audio-fill::before { content: "\f6e8"; }
.bi-optical-audio::before { content: "\f6e9"; }
.bi-pci-card::before { content: "\f6ea"; }
.bi-router-fill::before { content: "\f6eb"; }
.bi-router::before { content: "\f6ec"; }
.bi-ssd-fill::before { content: "\f6ed"; }
.bi-ssd::before { content: "\f6ee"; }
.bi-thunderbolt-fill::before { content: "\f6ef"; }
.bi-thunderbolt::before { content: "\f6f0"; }
.bi-usb-drive-fill::before { content: "\f6f1"; }
.bi-usb-drive::before { content: "\f6f2"; }
.bi-usb-micro-fill::before { content: "\f6f3"; }
.bi-usb-micro::before { content: "\f6f4"; }
.bi-usb-mini-fill::before { content: "\f6f5"; }
.bi-usb-mini::before { content: "\f6f6"; }
.bi-cloud-haze2::before { content: "\f6f7"; }
.bi-device-hdd-fill::before { content: "\f6f8"; }
.bi-device-hdd::before { content: "\f6f9"; }
.bi-device-ssd-fill::before { content: "\f6fa"; }
.bi-device-ssd::before { content: "\f6fb"; }
.bi-displayport-fill::before { content: "\f6fc"; }
.bi-mortarboard-fill::before { content: "\f6fd"; }
.bi-mortarboard::before { content: "\f6fe"; }
.bi-terminal-x::before { content: "\f6ff"; }
.bi-arrow-through-heart-fill::before { content: "\f700"; }
.bi-arrow-through-heart::before { content: "\f701"; }
.bi-badge-sd-fill::before { content: "\f702"; }
.bi-badge-sd::before { content: "\f703"; }
.bi-bag-heart-fill::before { content: "\f704"; }
.bi-bag-heart::before { content: "\f705"; }
.bi-balloon-fill::before { content: "\f706"; }
.bi-balloon-heart-fill::before { content: "\f707"; }
.bi-balloon-heart::before { content: "\f708"; }
.bi-balloon::before { content: "\f709"; }
.bi-box2-fill::before { content: "\f70a"; }
.bi-box2-heart-fill::before { content: "\f70b"; }
.bi-box2-heart::before { content: "\f70c"; }
.bi-box2::before { content: "\f70d"; }
.bi-braces-asterisk::before { content: "\f70e"; }
.bi-calendar-heart-fill::before { content: "\f70f"; }
.bi-calendar-heart::before { content: "\f710"; }
.bi-calendar2-heart-fill::before { content: "\f711"; }
.bi-calendar2-heart::before { content: "\f712"; }
.bi-chat-heart-fill::before { content: "\f713"; }
.bi-chat-heart::before { content: "\f714"; }
.bi-chat-left-heart-fill::before { content: "\f715"; }
.bi-chat-left-heart::before { content: "\f716"; }
.bi-chat-right-heart-fill::before { content: "\f717"; }
.bi-chat-right-heart::before { content: "\f718"; }
.bi-chat-square-heart-fill::before { content: "\f719"; }
.bi-chat-square-heart::before { content: "\f71a"; }
.bi-clipboard-check-fill::before { content: "\f71b"; }
.bi-clipboard-data-fill::before { content: "\f71c"; }
.bi-clipboard-fill::before { content: "\f71d"; }
.bi-clipboard-heart-fill::before { content: "\f71e"; }
.bi-clipboard-heart::before { content: "\f71f"; }
.bi-clipboard-minus-fill::before { content: "\f720"; }
.bi-clipboard-plus-fill::before { content: "\f721"; }
.bi-clipboard-pulse::before { content: "\f722"; }
.bi-clipboard-x-fill::before { content: "\f723"; }
.bi-clipboard2-check-fill::before { content: "\f724"; }
.bi-clipboard2-check::before { content: "\f725"; }
.bi-clipboard2-data-fill::before { content: "\f726"; }
.bi-clipboard2-data::before { content: "\f727"; }
.bi-clipboard2-fill::before { content: "\f728"; }
.bi-clipboard2-heart-fill::before { content: "\f729"; }
.bi-clipboard2-heart::before { content: "\f72a"; }
.bi-clipboard2-minus-fill::before { content: "\f72b"; }
.bi-clipboard2-minus::before { content: "\f72c"; }
.bi-clipboard2-plus-fill::before { content: "\f72d"; }
.bi-clipboard2-plus::before { content: "\f72e"; }
.bi-clipboard2-pulse-fill::before { content: "\f72f"; }
.bi-clipboard2-pulse::before { content: "\f730"; }
.bi-clipboard2-x-fill::before { content: "\f731"; }
.bi-clipboard2-x::before { content: "\f732"; }
.bi-clipboard2::before { content: "\f733"; }
.bi-emoji-kiss-fill::before { content: "\f734"; }
.bi-emoji-kiss::before { content: "\f735"; }
.bi-envelope-heart-fill::before { content: "\f736"; }
.bi-envelope-heart::before { content: "\f737"; }
.bi-envelope-open-heart-fill::before { content: "\f738"; }
.bi-envelope-open-heart::before { content: "\f739"; }
.bi-envelope-paper-fill::before { content: "\f73a"; }
.bi-envelope-paper-heart-fill::before { content: "\f73b"; }
.bi-envelope-paper-heart::before { content: "\f73c"; }
.bi-envelope-paper::before { content: "\f73d"; }
.bi-filetype-aac::before { content: "\f73e"; }
.bi-filetype-ai::before { content: "\f73f"; }
.bi-filetype-bmp::before { content: "\f740"; }
.bi-filetype-cs::before { content: "\f741"; }
.bi-filetype-css::before { content: "\f742"; }
.bi-filetype-csv::before { content: "\f743"; }
.bi-filetype-doc::before { content: "\f744"; }
.bi-filetype-docx::before { content: "\f745"; }
.bi-filetype-exe::before { content: "\f746"; }
.bi-filetype-gif::before { content: "\f747"; }
.bi-filetype-heic::before { content: "\f748"; }
.bi-filetype-html::before { content: "\f749"; }
.bi-filetype-java::before { content: "\f74a"; }
.bi-filetype-jpg::before { content: "\f74b"; }
.bi-filetype-js::before { content: "\f74c"; }
.bi-filetype-jsx::before { content: "\f74d"; }
.bi-filetype-key::before { content: "\f74e"; }
.bi-filetype-m4p::before { content: "\f74f"; }
.bi-filetype-md::before { content: "\f750"; }
.bi-filetype-mdx::before { content: "\f751"; }
.bi-filetype-mov::before { content: "\f752"; }
.bi-filetype-mp3::before { content: "\f753"; }
.bi-filetype-mp4::before { content: "\f754"; }
.bi-filetype-otf::before { content: "\f755"; }
.bi-filetype-pdf::before { content: "\f756"; }
.bi-filetype-php::before { content: "\f757"; }
.bi-filetype-png::before { content: "\f758"; }
.bi-filetype-ppt-1::before { content: "\f759"; }
.bi-filetype-ppt::before { content: "\f75a"; }
.bi-filetype-psd::before { content: "\f75b"; }
.bi-filetype-py::before { content: "\f75c"; }
.bi-filetype-raw::before { content: "\f75d"; }
.bi-filetype-rb::before { content: "\f75e"; }
.bi-filetype-sass::before { content: "\f75f"; }
.bi-filetype-scss::before { content: "\f760"; }
.bi-filetype-sh::before { content: "\f761"; }
.bi-filetype-svg::before { content: "\f762"; }
.bi-filetype-tiff::before { content: "\f763"; }
.bi-filetype-tsx::before { content: "\f764"; }
.bi-filetype-ttf::before { content: "\f765"; }
.bi-filetype-txt::before { content: "\f766"; }
.bi-filetype-wav::before { content: "\f767"; }
.bi-filetype-woff::before { content: "\f768"; }
.bi-filetype-xls-1::before { content: "\f769"; }
.bi-filetype-xls::before { content: "\f76a"; }
.bi-filetype-xml::before { content: "\f76b"; }
.bi-filetype-yml::before { content: "\f76c"; }
.bi-heart-arrow::before { content: "\f76d"; }
.bi-heart-pulse-fill::before { content: "\f76e"; }
.bi-heart-pulse::before { content: "\f76f"; }
.bi-heartbreak-fill::before { content: "\f770"; }
.bi-heartbreak::before { content: "\f771"; }
.bi-hearts::before { content: "\f772"; }
.bi-hospital-fill::before { content: "\f773"; }
.bi-hospital::before { content: "\f774"; }
.bi-house-heart-fill::before { content: "\f775"; }
.bi-house-heart::before { content: "\f776"; }
.bi-incognito::before { content: "\f777"; }
.bi-magnet-fill::before { content: "\f778"; }
.bi-magnet::before { content: "\f779"; }
.bi-person-heart::before { content: "\f77a"; }
.bi-person-hearts::before { content: "\f77b"; }
.bi-phone-flip::before { content: "\f77c"; }
.bi-plugin::before { content: "\f77d"; }
.bi-postage-fill::before { content: "\f77e"; }
.bi-postage-heart-fill::before { content: "\f77f"; }
.bi-postage-heart::before { content: "\f780"; }
.bi-postage::before { content: "\f781"; }
.bi-postcard-fill::before { content: "\f782"; }
.bi-postcard-heart-fill::before { content: "\f783"; }
.bi-postcard-heart::before { content: "\f784"; }
.bi-postcard::before { content: "\f785"; }
.bi-search-heart-fill::before { content: "\f786"; }
.bi-search-heart::before { content: "\f787"; }
.bi-sliders2-vertical::before { content: "\f788"; }
.bi-sliders2::before { content: "\f789"; }
.bi-trash3-fill::before { content: "\f78a"; }
.bi-trash3::before { content: "\f78b"; }
.bi-valentine::before { content: "\f78c"; }
.bi-valentine2::before { content: "\f78d"; }
.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; }
.bi-wrench-adjustable-circle::before { content: "\f78f"; }
.bi-wrench-adjustable::before { content: "\f790"; }
.bi-filetype-json::before { content: "\f791"; }
.bi-filetype-pptx::before { content: "\f792"; }
.bi-filetype-xlsx::before { content: "\f793"; }

View File

@ -63,6 +63,17 @@
.navbar-nav { .navbar-nav {
margin: 0; margin: 0;
} }
.navbar-nav .nav-item {
flex-direction: column;
display: flex;
padding: 0 10px !important;
}
.navbar-nav .nav-link {
height: 44px;
display: flex;
align-items: center;
padding: 0 10px !important;
}
.navbar-fixed-bottom .navbar-collapse, .navbar-fixed-bottom .navbar-collapse,
.navbar-fixed-top .navbar-collapse { .navbar-fixed-top .navbar-collapse {
max-height: 1000px max-height: 1000px
@ -75,6 +86,12 @@
display: inline-block; display: inline-block;
font-size: inherit; font-size: inherit;
} }
.btn-group-xs > .btn, .btn-xs {
padding: .25rem .4rem;
font-size: .875rem;
line-height: 1rem;
border-radius: .2rem;
}
.icon-spin { .icon-spin {
animation-name: spin; animation-name: spin;
animation-duration: 2000ms; animation-duration: 2000ms;
@ -105,13 +122,22 @@
transform: rotate(359deg); transform: rotate(359deg);
} }
} }
pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;} @keyframes blink {
.footable-sortable { 50% {
-webkit-user-select: none; color: transparent
-moz-user-select: none; }
-ms-user-select: none;
user-select: none;
} }
.loader-dot {
animation: 1s blink infinite
}
.loader-dot:nth-child(2) {
animation-delay: 250ms
}
.loader-dot:nth-child(3) {
animation-delay: 500ms
}
pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}
/* Fix modal moving content left */ /* Fix modal moving content left */
body.modal-open { body.modal-open {
overflow: inherit; overflow: inherit;
@ -166,19 +192,11 @@ legend {
top: 0; right: 0; bottom: 0; left: 0; top: 0; right: 0; bottom: 0; left: 0;
opacity: 0.7; opacity: 0.7;
} }
#top {
padding-top: 70px;
}
.bootstrap-select.btn-group .no-results { .bootstrap-select.btn-group .no-results {
display: none; display: none;
} }
.dropdown-desc { .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
display: block; color: rgb(197, 197, 197) !important;
padding: 3px 10px;
clear: both;
font-weight: bold;
color: #5a5a5a;
white-space: nowrap;
} }
.haveibeenpwned { .haveibeenpwned {
cursor: pointer; cursor: pointer;
@ -206,7 +224,7 @@ legend {
flex-direction: column; flex-direction: column;
} }
.footer .version { .footer .version {
margin-left: auto; margin-left: auto;
margin-top: 20px; margin-top: 20px;
} }
.slave-info { .slave-info {
@ -225,15 +243,8 @@ legend {
.btn-input-missing:active:hover, .btn-input-missing:active:hover,
.btn-input-missing:active:focus { .btn-input-missing:active:focus {
color: #000 !important; color: #000 !important;
background-color: #ff4136; background-color: #ff2f24 !important;
border-color: #ff291c; border-color: #e21207 !important;
}
table.footable>tbody>tr.footable-empty>td {
font-style:italic;
font-size: 1rem;
}
table>tbody>tr>td>span.footable-toggle {
opacity: 0.75;
} }
.navbar-nav > li { .navbar-nav > li {
font-size: 1rem !important; font-size: 1rem !important;
@ -260,18 +271,11 @@ code {
margin-right: 5px; margin-right: 5px;
} }
.list-group-item.webauthn-authenticator-selection, .dropdown-header {
.list-group-item.totp-authenticator-selection, font-weight: 600;
.list-group-item.yubi_otp-authenticator-selection {
border-radius: 0px !important;
}
.pending-tfa-collapse {
padding: 10px;
background: #fbfbfb;
border: 1px solid #ededed;
min-height: 110px;
} }
.tag-box { .tag-box {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -295,7 +299,7 @@ code {
} }
.tag-input { .tag-input {
margin-left: 10px; margin-left: 10px;
border: 0; border: 0 !important;
flex: 1; flex: 1;
height: 24px; height: 24px;
min-width: 150px; min-width: 150px;
@ -308,3 +312,61 @@ code {
align-items: center; align-items: center;
display: inline-flex; display: inline-flex;
} }
#dnstable {
overflow-x: auto!important;
}
.well {
border: 1px solid #dfdfdf;
background-color: #f9f9f9;
padding: 10px;
}
.btn-check-label {
color: #555;
}
.caret {
transform: rotate(0deg);
}
a[aria-expanded='true'] > .caret,
button[aria-expanded='true'] > .caret {
transform: rotate(-180deg);
}
.list-group-details {
background: #fff;
}
.list-group-header {
background: #f7f7f7;
}
.bg-primary, .alert-primary, .btn-primary {
background-color: #0F688D !important;
border-color: #0d526d !important;
}
.bg-info, .alert-info, .btn-info {
background-color: #148DBC !important;
border-color: #127ea8 !important;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: rgb(137 137 137)!important;
}
.progress {
background-color: #d5d5d5;
}
.btn-outline-secondary:hover {
background-color: #f0f0f0;
}
.btn.btn-outline-secondary {
border-color: #cfcfcf !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: #f0f0f0 !important;
}

View File

@ -1,7 +1,3 @@
.space20 {
margin-bottom: 20px;
}
.btn-xs-lg>.lang-sm:after { .btn-xs-lg>.lang-sm:after {
margin-left: 4px; margin-left: 4px;
} }
@ -10,110 +6,67 @@
max-width: 350px; max-width: 350px;
} }
.panel-login .apps .btn { .card-login .apps .btn {
width: auto; width: auto;
float: left; float: left;
margin-right: 10px; margin-right: 10px;
margin-top: auto; margin-top: auto;
} }
.panel-login .apps .btn:hover { .card-login .apps .btn:hover {
margin-top: 1px !important; margin-top: 1px !important;
border-bottom-width: 3px; border-bottom-width: 3px;
} }
.responsive-tabs .nav-tabs {
display: none;
}
.dataTables_paginate.paging_simple_numbers .pagination {
display: flex;
flex-wrap: wrap;
}
@media (min-width: 768px) {
.responsive-tabs .nav-tabs {
display: flex;
}
.responsive-tabs .card .card-body.collapse {
display: block;
}
}
@media (max-width: 767px) { @media (max-width: 767px) {
.panel-login .apps .btn { .responsive-tabs .tab-pane {
display: block !important;
opacity: 1;
}
.card-login .apps .btn {
width: 100%; width: 100%;
float: none; float: none;
margin-bottom: 10px; margin-bottom: 10px;
} }
.panel-login .apps .btn { .card-login .apps .btn {
border-bottom-width: 4px; border-bottom-width: 4px;
} }
.media-clearfix::after {
clear: both;
box-sizing: border-box;
}
.media-clearfix::before {
display: table;
content: " ";
box-sizing: border-box;
}
.xs-show { .xs-show {
display: block !important; display: block !important;
} }
.js-tabcollapse-panel-group .panel{
border: none;
box-shadow: none;
}
.js-tabcollapse-panel-group .panel-body {
padding: 10px 0;
}
.js-tabcollapse-panel-group .js-tabcollapse-panel-body .panel-body {
padding: 0;
}
.js-tabcollapse-panel-body .panel-heading {
display: none;
}
.js-tabcollapse-panel-body .well,
.panel-body .form-inline.well {
border: none;
padding: 0;
margin: 0;
box-shadow: none;
background-color: #fff;
}
.js-tabcollapse-panel-heading {
display: block;
height: 37px;
line-height: 37px;
text-indent: 15px;
}
.js-tabcollapse-panel-heading:hover {
text-decoration: none;
}
.js-tabcollapse-panel-heading {
position: relative;
}
.js-tabcollapse-panel-heading:after {
content: '';
display: block;
position: absolute;
top: 17px;
right: 17px;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-bottom: 4px dashed;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
}
.js-tabcollapse-panel-heading.collapsed:after {
border-bottom: none;
border-top: 4px dashed;
}
.recent-login-success { .recent-login-success {
font-size: 14px; font-size: 14px;
margin-top: 10px !important; margin-top: 10px !important;
} }
.pull-xs-right { .pull-xs-right {
float: right !important; float: right !important;
} }
.pull-xs-right .dropdown-menu { .pull-xs-right .dropdown-menu {
right: 0; right: 0;
left: auto; left: auto;
} }
.text-xs-left { .text-xs-left {
text-align: left; text-align: left;
@ -125,36 +78,29 @@
font-weight: normal; font-weight: normal;
text-align: justify; text-align: justify;
} }
.help-block { .btn.d-block {
text-align: justify;
}
.btn.visible-xs-block {
width: 100%; width: 100%;
float: none;
white-space: normal; white-space: normal;
} }
.btn-group.footable-actions .btn.btn-xs-half, .btn.btn-xs-half,
.btn.visible-xs-block.btn-xs-half { .btn.d-block.btn-xs-half {
width: 50%; width: 50%;
float: left;
} }
.btn-group.footable-actions .btn.btn-xs-third, .btn.btn-xs-third,
.btn.visible-xs-block.btn-xs-third { .btn.d-block.btn-xs-third {
width: 33.33%; width: 33.33%;
float: left;
} }
.btn-group.footable-actions .btn.btn-xs-quart, .btn.btn-xs-quart,
.btn.visible-xs-block.btn-xs-quart { .btn.d-block.btn-xs-quart {
width: 25%; width: 25%;
float: left;
} }
.btn.visible-xs-block.btn-sm, .btn.d-block.btn-sm,
.btn-xs-lg { .btn-xs-lg {
padding: 15px 16px 13px; padding: .5rem 1rem;
line-height: 15px; line-height: 20px;
} }
.input-xs-lg { .input-xs-lg {
height: 47px; height: 47px;
padding: 13px 16px; padding: 13px 16px;
} }
.btn-group:not(.input-group-btn) { .btn-group:not(.input-group-btn) {
@ -167,27 +113,27 @@
.btn-group.nowrap .dropdown-menu { .btn-group.nowrap .dropdown-menu {
width: 100%; width: 100%;
} }
.panel-login .btn-group { .card-login .btn-group {
display: block; display: block;
} }
.mass-actions-user .btn-group { .mass-actions-user .btn-group {
float: none; float: none;
} }
div[class^='mass-actions'] .dropdown-menu, div[class^='mass-actions'] .dropdown-menu,
.panel-xs-lg .dropdown-menu, .card-xs-lg .dropdown-menu,
.dropdown-menu.login { .dropdown-menu.login {
width: 100%; width: 100%;
} }
div[class^='mass-actions'] .btn-group .dropdown-menu { div[class^='mass-actions'] .btn-group .dropdown-menu {
top: 50%; top: 50%;
} }
div[class^='mass-actions'] .btn-group .btn-group .dropdown-menu, div[class^='mass-actions'] .btn-group .btn-group .dropdown-menu,
div.mass-actions-quarantine .btn-group .dropdown-menu, div.mass-actions-quarantine .btn-group .dropdown-menu,
.panel-xs-lg .dropdown-menu { .card-xs-lg .dropdown-menu {
top: 100%; top: 100%;
} }
div[class^='mass-actions'] .dropdown-menu>li>a, div[class^='mass-actions'] .dropdown-menu>li>a,
.panel-xs-lg .dropdown-menu>li>a, .card-xs-lg .dropdown-menu>li>a,
.dropdown-menu.login>li>a { .dropdown-menu.login>li>a {
padding: 8px 20px; padding: 8px 20px;
} }
@ -195,9 +141,6 @@
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
} }
.space20 {
margin-bottom: 10px;
}
.top100 { .top100 {
top: 100% !important; top: 100% !important;
} }
@ -210,34 +153,17 @@
.btn-xs-lg>.lang-sm:after { .btn-xs-lg>.lang-sm:after {
top: 1px; top: 1px;
} }
table.footable>tfoot>tr.footable-paging>td {
text-align: left;
}
.footable-first-visible {
min-width: 55px;
}
table>tbody>tr>td>span.footable-toggle {
font-size: 24px;
margin-right: 14px !important;
}
table>tbody>tr>td>span.footable-toggle + input {
position: absolute;
left: 38px;
}
.pagination { .pagination {
margin-bottom: 5px; margin-bottom: 5px;
} }
tr.footable-filtering>th>form {
width: 270px;
}
.mass-actions-mailbox { .mass-actions-mailbox {
padding: 0; padding: 0;
} }
.panel-xs-lg .panel-heading { .card-xs-lg .card-header {
height: 66px; height: 66px;
line-height: 47px; line-height: 47px;
} }
.panel-xs-lg .btn-group .btn { .card-xs-lg .btn-group .btn {
padding-right: 5px; padding-right: 5px;
padding-left: 5px; padding-left: 5px;
} }
@ -250,55 +176,29 @@
.bootstrap-select { .bootstrap-select {
max-width: 100%; max-width: 100%;
} }
.img-responsive {
margin: 0 auto;
}
.btn-group.footable-actions {
position: absolute;
width: 90vw !important;
left: 0;
height: 36px;
margin-top: -8px;
}
.btn-group.footable-actions .btn {
padding: 10px 16px 7px;
line-height: 15px;
display: block;
width: 100%;
}
.btn-group.footable-actions:after {
content: "";
display: block;
clear: both;
}
.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text { .bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text {
margin-right: 14px; margin-right: 14px;
white-space: normal; white-space: normal;
} }
.clearfix {
flex-basis: 100%;
height: 0;
}
.btn-group > .btn-group { .btn-group > .btn-group {
flex-basis: 100%; flex-basis: 100%;
} }
.btn-group .btn { .btn-group .btn {
display: flex !important; display: flex !important;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.btn-group .btn i { .btn-group .btn i {
margin-right: 5px; margin-right: 5px;
} }
.btn-group .btn .caret { .card-login .btn-group .btn {
margin-left: 5px;
}
.panel-login .btn-group .btn {
display: block !important; display: block !important;
} }
.panel-login .clearfix {
height: auto; .dt-sm-head-hidden .dtr-title {
display: none !important;
} }
} }
@media (max-width: 350px) { @media (max-width: 350px) {
@ -306,3 +206,9 @@
max-width: 250px; max-width: 250px;
} }
} }
@media (min-width: 1400px) {
.container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {
max-width: 1600px;
}
}

View File

@ -0,0 +1,80 @@
.dataTables_info {
margin: 15px 0 !important;
padding: 0px !important;
}
.dataTables_paginate, .dataTables_length, .dataTables_filter {
margin: 15px 0 !important;
}
.dtr-details {
width: 100%;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #F2F2F2;
}
td.child>ul>li {
display: flex;
}
table.dataTable>tbody>tr.child ul.dtr-details>li {
border-bottom: 1px solid rgba(0, 0, 0, 0.129);
padding: 0.5em 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: #5e5e5e;
}
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 {
background-color: #979797 !important;
border: 1.5px solid #616161 !important;
border-radius: 2px !important;
color: #fff;
height: 1em;
width: 1em;
line-height: 1.25em;
border-radius: 0px;
box-shadow: none;
font-size: 14px;
transition: 0.5s all;
}
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
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;
}
table.dataTable.table-striped>tbody>tr>td>input[type="checkbox"] {
margin-top: 7px;
}
td.dtr-col-lg {
min-width: 350px;
word-break: break-word;
}
td.dtr-col-md {
min-width: 250px;
word-break: break-word;
}
td.dtr-col-sm {
min-width: 125px;
word-break: break-word;
}
.dt-data-w100 .dtr-data {
width: 100%;
}
li .dtr-data {
word-break: break-all;
flex: 1;
padding-left: 5px;
padding-right: 5px;
}
table.dataTable>tbody>tr.child span.dtr-title {
width: 30%;
max-width: 250px;
}

View File

@ -25,7 +25,6 @@ body.modal-open {
} }
.mass-actions-admin { .mass-actions-admin {
user-select: none; user-select: none;
padding:10px 0 10px 0;
} }
.inputMissingAttr { .inputMissingAttr {
border-color: #FF4136; border-color: #FF4136;

View File

@ -26,7 +26,6 @@
} }
.mass-actions-debug { .mass-actions-debug {
user-select: none; user-select: none;
padding:10px 0 10px 10px;
} }
.inputMissingAttr { .inputMissingAttr {
border-color: #FF4136; border-color: #FF4136;

View File

@ -21,7 +21,6 @@
} }
.mass-actions-user { .mass-actions-user {
user-select: none; user-select: none;
padding:10px 0 10px 0;
} }
.inputMissingAttr { .inputMissingAttr {
border-color: #FF4136; border-color: #FF4136;

View File

@ -32,7 +32,6 @@
} }
.mass-actions-mailbox { .mass-actions-mailbox {
user-select: none; user-select: none;
padding:10px 0 10px 10px;
} }
.inputMissingAttr { .inputMissingAttr {
border-color: #FF4136; border-color: #FF4136;

View File

@ -35,7 +35,6 @@
.mass-actions-quarantine { .mass-actions-quarantine {
user-select: none; user-select: none;
padding: 10px;
} }
.inputMissingAttr { .inputMissingAttr {

View File

@ -21,7 +21,6 @@
} }
.mass-actions-user { .mass-actions-user {
user-select: none; user-select: none;
padding:10px 0;
} }
.inputMissingAttr { .inputMissingAttr {
border-color: #FF4136; border-color: #FF4136;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,360 @@
body {
background-color: #414141;
color: #e0e0e0;
}
.card {
border: 1px solid #1c1c1c;
background-color: #3a3a3a;
}
legend {
color: #f5f5f5;
}
.card-header {
color: #bbb;
background-color: #2c2c2c;
border-color: transparent;
}
.btn-secondary, .paginate_button, .page-link, .btn-light {
color: #fff !important;
background-color: #7a7a7a !important;
border-color: #5c5c5c !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;
}
.btn-secondary:focus, .btn-secondary:hover, .btn-group.open .dropdown-toggle.btn-secondary {
background-color: #7a7a7a;
border-color: #5c5c5c !important;
color: #fff;
}
.btn-secondary:disabled, .btn-secondary.disabled {
border-color: #7a7a7a !important;
}
.modal-content {
background-color: #414141;
}
.modal-header {
border-bottom: 1px solid #161616;
}
.modal-title {
color: white;
}
.modal .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
.navbar.bg-light {
background-color: #222222 !important;
border-color: #181818;
}
.nav-link {
color: #ccc !important;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
background: none;
}
.nav-tabs .nav-link:not(.disabled):hover, .nav-tabs .nav-link:not(.disabled):focus, .nav-tabs .nav-link.active {
border-bottom-color: #414141;
}
.table, .table-striped>tbody>tr:nth-of-type(odd)>*, tbody tr {
color: #ccc !important;
}
.dropdown-menu {
background-color: #585858;
border: 1px solid #333;
}
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
color: #fafafa;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder:active, .bootstrap-select>.dropdown-toggle.bs-placeholder:focus, .bootstrap-select>.dropdown-toggle.bs-placeholder:hover {
color: #fff;
}
.bootstrap-select>.dropdown-toggle.bs-placeholder, .bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary {
color: #d4d4d4 !important;
}
tbody tr {
color: #555;
}
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover {
color: #ccc;
}
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover {
color: #ccc;
}
.list-group-item {
background-color: #333;
border: 1px solid #555;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #333;
}
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;
}
.progress {
background-color: #555;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
color: #ccc;
}
div.numberedtextarea-number {
color: #999;
}
.well {
border: 1px solid #555;
background-color: #333;
}
pre {
color: #ccc;
background-color: #333;
border: 1px solid #555;
}
input.form-control, textarea.form-control {
color: #e2e2e2 !important;
background-color: #555 !important;
border: 1px solid #999;
}
input.form-control:focus, textarea.form-control {
background-color: #555 !important;
}
input.form-control:disabled, textarea.form-disabled {
color: #a8a8a8 !important;
background-color: #6e6e6e !important;
}
.input-group-addon {
color: #ccc;
background-color: #555 !important;
border: 1px solid #999;
}
.input-group-text {
color: #ccc;
background-color: #242424;
}
.list-group-item {
color: #ccc;
}
.dropdown-item {
color: #ccc;
}
.dropdown-item:hover {
color: #616161 !important;
}
.dropdown-item.active:hover {
color: #fff !important;
background-color: #31b1e4;
}
.form-select {
color: #e2e2e2!important;
background-color: #555!important;
border: 1px solid #999;
}
.responsive-tabs .card-header button[data-bs-toggle="collapse"] {
color: #c7c7c7;
}
.navbar-toggler {
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;
}
.breadcrumb {
color: #fff !important;
background-color: #7a7a7a !important;
border-color: #5c5c5c !important;
}
a {
color: #6fc7e9;
text-decoration: underline;
}
a:hover {
color: #3daedb;
}
.breadcrumb-item.active {
color: #ccc;
}
.list-group-item.disabled, .list-group-item:disabled {
color: #fff;
background-color: #a8a8a8 !important;
border-color: #a8a8a8;
}
.card.bg-light .card-title {
color: #000 !important;
}
.card.bg-light .card-text {
color: #000;
}
.accordion-item {
background-color: #3a3a3a;
}
.accordion-button {
color: #bbb;
background-color: #2c2c2c;
}
.accordion-button:not(.collapsed) {
color: #6fc7e9;
background-color: #2c2c2c;
}
.accordion-button::after {
background-image: none;
}
.accordion-button:not(.collapsed)::after {
background-image: none;
}
.toast-header {
background-color: #4c4c4c;
}
.toast-body {
background-color: #626262;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff !important;
}
.tag-box {
background-color: #555;
border: 1px solid #999;
}
.tag-input {
color: #fff;
background-color: #555;
}
.tag-add {
color: #ccc;
}
.tag-add: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;
}
.btn.btn-outline-secondary {
color: #fff !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;
}
.btn-input-missing,
.btn-input-missing:hover,
.btn-input-missing:active,
.btn-input-missing:focus,
.btn-input-missing:active:hover,
.btn-input-missing:active:focus {
color: #fff !important;
background-color: #ff2f24 !important;
border-color: #e21207 !important;
}
.inputMissingAttr {
border-color: #FF4136 !important;
}
.list-group-details {
background: #444444;
}
.list-group-header {
background: #333;
}

View File

@ -44,15 +44,24 @@ foreach ($containers as $container => $container_info) {
$containers[$container]['State']['StartedAtHR'] = $started; $containers[$container]['State']['StartedAtHR'] = $started;
} }
// get mailcow data
$hostname = getenv('MAILCOW_HOSTNAME');
$timezone = getenv('TZ');
$template = 'debug.twig'; $template = 'debug.twig';
$template_data = [ $template_data = [
'log_lines' => getenv('LOG_LINES'), 'log_lines' => getenv('LOG_LINES'),
'vmail_df' => $vmail_df, 'vmail_df' => $vmail_df,
'hostname' => $hostname,
'timezone' => $timezone,
'license_guid' => license('guid'),
'solr_status' => $solr_status, 'solr_status' => $solr_status,
'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60), 'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60),
'clamd_status' => $clamd_status, 'clamd_status' => $clamd_status,
'containers' => $containers, 'containers' => $containers,
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),
'lang_debug' => json_encode($lang['debug']),
'lang_datatables' => json_encode($lang['datatables']),
]; ];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@ -38,24 +38,46 @@ if (isset($_SESSION['mailcow_cc_role'])) {
$template = 'edit/admin.twig'; $template = 'edit/admin.twig';
$template_data = ['admin' => $admin]; $template_data = ['admin' => $admin];
} }
elseif (isset($_GET['domain']) && elseif (isset($_GET['domain'])) {
is_valid_domain_name($_GET["domain"]) && if (is_valid_domain_name($_GET["domain"]) &&
!empty($_GET["domain"])) { !empty($_GET["domain"])) {
$domain = $_GET["domain"]; // edit domain
$result = mailbox('get', 'domain_details', $domain); $domain = $_GET["domain"];
$quota_notification_bcc = quota_notification_bcc('get', $domain); $result = mailbox('get', 'domain_details', $domain);
$rl = ratelimit('get', 'domain', $domain); $quota_notification_bcc = quota_notification_bcc('get', $domain);
$rlyhosts = relayhost('get'); $rl = ratelimit('get', 'domain', $domain);
$template = 'edit/domain.twig'; $rlyhosts = relayhost('get');
$template = 'edit/domain.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'domain' => $domain,
'quota_notification_bcc' => $quota_notification_bcc,
'rl' => $rl,
'rlyhosts' => $rlyhosts,
'dkim' => dkim('details', $domain),
'domain_details' => $result,
];
}
}
elseif (isset($_GET["template"])){
$domain_template = mailbox('get', 'domain_templates', $_GET["template"]);
if ($domain_template){
$template_data = [ $template_data = [
'acl' => $_SESSION['acl'], 'template' => $domain_template
'domain' => $domain,
'quota_notification_bcc' => $quota_notification_bcc,
'rl' => $rl,
'rlyhosts' => $rlyhosts,
'dkim' => dkim('details', $domain),
'domain_details' => $result,
]; ];
$template = 'edit/domain-templates.twig';
$result = true;
}
else {
$mailbox_template = mailbox('get', 'mailbox_templates', $_GET["template"]);
if ($mailbox_template){
$template_data = [
'template' => $mailbox_template
];
$template = 'edit/mailbox-templates.twig';
$result = true;
}
}
} }
elseif (isset($_GET['oauth2client']) && elseif (isset($_GET['oauth2client']) &&
is_numeric($_GET["oauth2client"]) && is_numeric($_GET["oauth2client"]) &&
@ -79,29 +101,32 @@ if (isset($_SESSION['mailcow_cc_role'])) {
'dkim' => dkim('details', $alias_domain), 'dkim' => dkim('details', $alias_domain),
]; ];
} }
elseif (isset($_GET['mailbox']) && filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) { elseif (isset($_GET['mailbox'])){
$mailbox = html_entity_decode(rawurldecode($_GET["mailbox"])); if(filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
$result = mailbox('get', 'mailbox_details', $mailbox); // edit mailbox
$rl = ratelimit('get', 'mailbox', $mailbox); $mailbox = html_entity_decode(rawurldecode($_GET["mailbox"]));
$pushover_data = pushover('get', $mailbox); $result = mailbox('get', 'mailbox_details', $mailbox);
$quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox); $rl = ratelimit('get', 'mailbox', $mailbox);
$quarantine_category = mailbox('get', 'quarantine_category', $mailbox); $pushover_data = pushover('get', $mailbox);
$get_tls_policy = mailbox('get', 'tls_policy', $mailbox); $quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox);
$rlyhosts = relayhost('get'); $quarantine_category = mailbox('get', 'quarantine_category', $mailbox);
$template = 'edit/mailbox.twig'; $get_tls_policy = mailbox('get', 'tls_policy', $mailbox);
$template_data = [ $rlyhosts = relayhost('get');
'acl' => $_SESSION['acl'], $template = 'edit/mailbox.twig';
'mailbox' => $mailbox, $template_data = [
'rl' => $rl, 'acl' => $_SESSION['acl'],
'pushover_data' => $pushover_data, 'mailbox' => $mailbox,
'quarantine_notification' => $quarantine_notification, 'rl' => $rl,
'quarantine_category' => $quarantine_category, 'pushover_data' => $pushover_data,
'get_tls_policy' => $get_tls_policy, 'quarantine_notification' => $quarantine_notification,
'rlyhosts' => $rlyhosts, 'quarantine_category' => $quarantine_category,
'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox), 'get_tls_policy' => $get_tls_policy,
'user_acls' => acl('get', 'user', $mailbox), 'rlyhosts' => $rlyhosts,
'mailbox_details' => $result 'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox),
]; 'user_acls' => acl('get', 'user', $mailbox),
'mailbox_details' => $result
];
}
} }
elseif (isset($_GET['relayhost']) && is_numeric($_GET["relayhost"]) && !empty($_GET["relayhost"])) { elseif (isset($_GET['relayhost']) && is_numeric($_GET["relayhost"]) && !empty($_GET["relayhost"])) {
$relayhost = intval($_GET["relayhost"]); $relayhost = intval($_GET["relayhost"]);
@ -189,5 +214,6 @@ $js_minifier->add('/web/js/site/pwgen.js');
$template_data['result'] = $result; $template_data['result'] = $result;
$template_data['return_to'] = $_SESSION['return_to']; $template_data['return_to'] = $_SESSION['return_to'];
$template_data['lang_user'] = json_encode($lang['user']); $template_data['lang_user'] = json_encode($lang['user']);
$template_data['lang_datatables'] = json_encode($lang['datatables']);
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -436,7 +436,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
} }
?> ?>
</table> </table>
<a id='download-zonefile' class="btn btn-sm btn-default visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a> <a id='download-zonefile' class="btn btn-sm btn-secondary visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline mb-4" style="margin-top:10px" data-zonefile="<?=base64_encode($dns_data);?>" download='<?=$_GET['domain'];?>.txt' type='text/csv'>Download</a>
<script> <script>
var zonefile_dl_link = document.getElementById('download-zonefile'); var zonefile_dl_link = document.getElementById('download-zonefile');
var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile')); var zonefile = atob(zonefile_dl_link.getAttribute('data-zonefile'));

View File

@ -2,8 +2,18 @@
function customize($_action, $_item, $_data = null) { function customize($_action, $_item, $_data = null) {
global $redis; global $redis;
global $lang; global $lang;
switch ($_action) { switch ($_action) {
case 'add': case 'add':
// disable functionality when demo mode is enabled
if ($GLOBALS["DEMO_MODE"]) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'demo_mode_enabled'
);
return false;
}
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -72,6 +82,15 @@ function customize($_action, $_item, $_data = null) {
} }
break; break;
case 'edit': case 'edit':
// disable functionality when demo mode is enabled
if ($GLOBALS["DEMO_MODE"]) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'demo_mode_enabled'
);
return false;
}
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -116,6 +135,7 @@ function customize($_action, $_item, $_data = null) {
$ui_announcement_text = $_data['ui_announcement_text']; $ui_announcement_text = $_data['ui_announcement_text'];
$ui_announcement_type = (in_array($_data['ui_announcement_type'], array('info', 'warning', 'danger'))) ? $_data['ui_announcement_type'] : false; $ui_announcement_type = (in_array($_data['ui_announcement_type'], array('info', 'warning', 'danger'))) ? $_data['ui_announcement_type'] : false;
$ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0); $ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0);
try { try {
$redis->set('TITLE_NAME', htmlspecialchars($title_name)); $redis->set('TITLE_NAME', htmlspecialchars($title_name));
$redis->set('MAIN_NAME', htmlspecialchars($main_name)); $redis->set('MAIN_NAME', htmlspecialchars($main_name));
@ -143,6 +163,15 @@ function customize($_action, $_item, $_data = null) {
} }
break; break;
case 'delete': case 'delete':
// disable functionality when demo mode is enabled
if ($GLOBALS["DEMO_MODE"]) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'demo_mode_enabled'
);
return false;
}
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',

View File

@ -1,6 +1,7 @@
<?php <?php
function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $extra_headers = null) { function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $extra_headers = null) {
global $DOCKER_TIMEOUT; global $DOCKER_TIMEOUT;
global $redis;
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: application/json' )); curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: application/json' ));
// We are using our mail certificates for dockerapi, the names will not match, the certs are trusted anyway // We are using our mail certificates for dockerapi, the names will not match, the certs are trusted anyway
@ -32,6 +33,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
} }
} }
return false; return false;
break;
case 'containers': case 'containers':
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json'); curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
@ -51,6 +53,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
if (strtolower($container['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { if (strtolower($container['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) {
$out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State']; $out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config']; $out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($container['Id']);
} }
} }
} }
@ -94,6 +97,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
unset($container['Config']['Env']); unset($container['Config']['Env']);
$out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State']; $out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config']; $out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config'];
$out[$container['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($container['Id']);
} }
} }
} }
@ -103,6 +107,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
unset($container['Config']['Env']); unset($container['Config']['Env']);
$out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['State'] = $decoded_response['State']; $out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['State'] = $decoded_response['State'];
$out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Config'] = $decoded_response['Config']; $out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Config'] = $decoded_response['Config'];
$out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($decoded_response['Id']);
} }
} }
} }
@ -146,5 +151,46 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
} }
} }
break; break;
case 'container_stats':
if (empty($service_name)){
return false;
}
$container_id = $service_name;
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/container/' . $container_id . '/stats/update');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
curl_close($curl);
return $err;
}
else {
curl_close($curl);
$stats = json_decode($response, true);
if (!empty($stats)) return $stats;
}
return false;
break;
case 'host_stats':
curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/host/stats');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
curl_close($curl);
return $err;
}
else {
curl_close($curl);
$stats = json_decode($response, true);
if (!empty($stats)) return $stats;
}
return false;
break;
} }
} }

View File

@ -251,7 +251,7 @@ function password_check($password1, $password2) {
return true; return true;
} }
function last_login($action, $username, $sasl_limit_days = 7) { function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
global $pdo; global $pdo;
global $redis; global $redis;
$sasl_limit_days = intval($sasl_limit_days); $sasl_limit_days = intval($sasl_limit_days);
@ -319,8 +319,11 @@ function last_login($action, $username, $sasl_limit_days = 7) {
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs` $stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :username AND JSON_EXTRACT(`call`, "$[1]") = :username
AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET 1'); AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET :offset');
$stmt->execute(array(':username' => $username)); $stmt->execute(array(
':username' => $username,
':offset' => $ui_offset
));
$ui = $stmt->fetch(PDO::FETCH_ASSOC); $ui = $stmt->fetch(PDO::FETCH_ASSOC);
} }
else { else {

View File

@ -1020,6 +1020,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (empty($name)) { if (empty($name)) {
$name = $local_part; $name = $local_part;
} }
if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access'];
$_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
}
$active = intval($_data['active']); $active = intval($_data['active']);
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); $force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
@ -1200,10 +1207,63 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => $domain, ':domain' => $domain,
':active' => $active ':active' => $active
)); ));
$stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username if (isset($_data['acl'])) {
)); $_data['acl'] = (array)$_data['acl'];
$_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
$_data['tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
$_data['spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
$_data['spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
$_data['delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
$_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
$_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
$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,
':spam_alias' => $_data['spam_alias'],
':tls_policy' => $_data['tls_policy'],
':spam_score' => $_data['spam_score'],
':spam_policy' => $_data['spam_policy'],
':delimiter_action' => $_data['delimiter_action'],
':syncjobs' => $_data['syncjobs'],
':eas_reset' => $_data['eas_reset'],
':sogo_profile_reset' => $_data['sogo_profile_reset'],
':pushover' => $_data['pushover'],
':quarantine' => $_data['quarantine'],
':quarantine_attachments' => $_data['quarantine_attachments'],
':quarantine_notification' => $_data['quarantine_notification'],
':quarantine_category' => $_data['quarantine_category'],
':app_passwds' => $_data['app_passwds']
));
}
else {
$stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username
));
}
if (isset($_data['rl_frame']) && isset($_data['rl_value'])){
ratelimit('edit', 'mailbox', array(
'object' => $username,
'rl_frame' => $_data['rl_frame'],
'rl_value' => $_data['rl_value']
));
}
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@ -1322,6 +1382,190 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('resource_added', htmlspecialchars($name)) 'msg' => array('resource_added', htmlspecialchars($name))
); );
break; break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (empty($_data["template"])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_name_invalid'
);
return false;
}
// check if template name exists, return false
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "domain",
":template" => $_data["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => array('template_exists', $_data["template"])
);
return false;
}
// check attributes
$attr = array();
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr['max_num_aliases_for_domain'] = (!empty($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 400;
$attr['max_num_mboxes_for_domain'] = (!empty($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 10;
$attr['def_quota_for_mbox'] = (!empty($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 3072 * 1048576;
$attr['max_quota_for_mbox'] = (!empty($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 10240 * 1048576;
$attr['max_quota_for_domain'] = (!empty($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 10240 * 1048576;
$attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1;
$attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
$attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
$attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
$attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
$attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
$attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
// save template
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "domain",
":template" => $_data["template"],
":attributes" => json_encode($attr)
));
// success
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_added', $_data["template"])
);
return true;
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (empty($_data["template"])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_name_invalid'
);
return false;
}
// check if template name exists, return false
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "mailbox",
":template" => $_data["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => array('template_exists', $_data["template"])
);
return false;
}
// check attributes
$attr = array();
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']);
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1;
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access'];
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
}
else {
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
}
if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl'];
$attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
$attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
$attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$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_syncjobs'] = 0;
$attr['acl_eas_reset'] = 1;
$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;
}
// save template
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "mailbox",
":template" => $_data["template"],
":attributes" => json_encode($attr)
));
// success
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_added', $_data["template"])
);
return true;
break;
} }
break; break;
case 'edit': case 'edit':
@ -2472,6 +2716,79 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
} }
break; break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
$is_now = mailbox("get", "domain_templates", $id);
if (empty($is_now) ||
$is_now["type"] != "domain"){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_id_invalid'
);
continue;
}
// check name
if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
// keep template name of Default template
$_data["template"] = $is_now["template"];
}
else {
$_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
}
// check attributes
$attr = array();
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0;
$attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0;
$attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0;
$attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0;
$attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0;
$attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1;
$attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
$attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
$attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
$attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
$attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
$attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
// update template
$stmt = $pdo->prepare("UPDATE `templates`
SET `template` = :template, `attributes` = :attributes
WHERE id = :id");
$stmt->execute(array(
":id" => $id ,
":template" => $_data["template"] ,
":attributes" => json_encode($attr)
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_modified', $_data["template"])
);
return true;
break;
case 'mailbox': case 'mailbox':
if (!is_array($_data['username'])) { if (!is_array($_data['username'])) {
$usernames = array(); $usernames = array();
@ -2814,6 +3131,110 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
} }
break; break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
$is_now = mailbox("get", "mailbox_templates", $id);
if (empty($is_now) ||
$is_now["type"] != "mailbox"){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra),
'msg' => 'template_id_invalid'
);
continue;
}
// check name
if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
// keep template name of Default template
$_data["template"] = $is_now["template"];
}
else {
$_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
}
// check attributes
$attr = array();
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags'];
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
$attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value'];
$attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update'];
$attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access'];
$attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active'];
$attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access'];
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
}
else {
foreach ($is_now as $key => $value){
$attr[$key] = $is_now[$key];
}
}
if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl'];
$attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
$attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0;
$attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0;
$attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0;
$attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0;
$attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0;
$attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
} else {
foreach ($is_now as $key => $value){
$attr[$key] = $is_now[$key];
}
}
// update template
$stmt = $pdo->prepare("UPDATE `templates`
SET `template` = :template, `attributes` = :attributes
WHERE id = :id");
$stmt->execute(array(
":id" => $id ,
":template" => $_data["template"] ,
":attributes" => json_encode($attr)
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_modified', $_data["template"])
);
return true;
break;
case 'resource': case 'resource':
if (!is_array($_data['name'])) { if (!is_array($_data['name'])) {
$names = array(); $names = array();
@ -3606,6 +4027,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailboxes`, `mailboxes`,
`defquota`, `defquota`,
`maxquota`, `maxquota`,
`created`,
`modified`,
`quota`, `quota`,
`relayhost`, `relayhost`,
`relay_all_recipients`, `relay_all_recipients`,
@ -3678,6 +4101,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$domaindata['relay_all_recipients_int'] = $row['relay_all_recipients']; $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients'];
$domaindata['relay_unknown_only'] = $row['relay_unknown_only']; $domaindata['relay_unknown_only'] = $row['relay_unknown_only'];
$domaindata['relay_unknown_only_int'] = $row['relay_unknown_only']; $domaindata['relay_unknown_only_int'] = $row['relay_unknown_only'];
$domaindata['created'] = $row['created'];
$domaindata['modified'] = $row['modified'];
$stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias` $stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias`
WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2)) WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2))
AND `address` NOT IN ( AND `address` NOT IN (
@ -3711,6 +4136,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $domaindata; return $domaindata;
break; break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$_data = (isset($_data)) ? intval($_data) : null;
if (isset($_data)){
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `id` = :id AND type = :type");
$stmt->execute(array(
":id" => $_data,
":type" => "domain"
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
return false;
}
$row["attributes"] = json_decode($row["attributes"], true);
return $row;
}
else {
$stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)){
return false;
}
foreach($rows as $key => $row){
$rows[$key]["attributes"] = json_decode($row["attributes"], true);
}
return $rows;
}
break;
case 'mailbox_details': case 'mailbox_details':
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false; return false;
@ -3725,6 +4187,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`domain`, `mailbox`.`domain`,
`mailbox`.`local_part`, `mailbox`.`local_part`,
`mailbox`.`quota`, `mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`quota2`.`bytes`, `quota2`.`bytes`,
`attributes`, `attributes`,
`quota2`.`messages` `quota2`.`messages`
@ -3743,6 +4207,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`domain`, `mailbox`.`domain`,
`mailbox`.`local_part`, `mailbox`.`local_part`,
`mailbox`.`quota`, `mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`quota2replica`.`bytes`, `quota2replica`.`bytes`,
`attributes`, `attributes`,
`quota2replica`.`messages` `quota2replica`.`messages`
@ -3769,6 +4235,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['attributes'] = json_decode($row['attributes'], true); $mailboxdata['attributes'] = json_decode($row['attributes'], true);
$mailboxdata['quota_used'] = intval($row['bytes']); $mailboxdata['quota_used'] = intval($row['bytes']);
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100); $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['created'] = $row['created'];
$mailboxdata['modified'] = $row['modified'];
if ($mailboxdata['percent_in_use'] === '- ') { if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "info"; $mailboxdata['percent_class'] = "info";
@ -3856,6 +4324,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $mailboxdata; return $mailboxdata;
break; break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$_data = (isset($_data)) ? intval($_data) : null;
if (isset($_data)){
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `id` = :id AND type = :type");
$stmt->execute(array(
":id" => $_data,
":type" => "mailbox"
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
return false;
}
$row["attributes"] = json_decode($row["attributes"], true);
return $row;
}
else {
$stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'mailbox'");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)){
return false;
}
foreach($rows as $key => $row){
$rows[$key]["attributes"] = json_decode($row["attributes"], true);
}
return $rows;
}
break;
case 'resource_details': case 'resource_details':
$resourcedata = array(); $resourcedata = array();
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
@ -4224,6 +4729,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
} }
break; break;
case 'domain_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
// delete template
$stmt = $pdo->prepare("DELETE FROM `templates`
WHERE id = :id AND type = :type AND NOT template = :template");
$stmt->execute(array(
":id" => $id,
":type" => "domain",
":template" => "Default"
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('template_removed', htmlspecialchars($id))
);
return true;
}
break;
case 'alias': case 'alias':
if (!is_array($_data['id'])) { if (!is_array($_data['id'])) {
$ids = array(); $ids = array();
@ -4518,6 +5059,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
} }
break; break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['ids'])) {
$ids = array();
$ids[] = $_data['ids'];
}
else {
$ids = $_data['ids'];
}
foreach ($ids as $id) {
// delete template
$stmt = $pdo->prepare("DELETE FROM `templates`
WHERE id = :id AND type = :type AND NOT template = :template");
$stmt->execute(array(
":id" => $id,
":type" => "mailbox",
":template" => "Default"
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_removed'
);
return true;
break;
case 'resource': case 'resource':
if (!is_array($_data['name'])) { if (!is_array($_data['name'])) {
$names = array(); $names = array();

View File

@ -39,7 +39,6 @@ $globalVariables = [
'dual_login' => @$_SESSION['dual-login'], 'dual_login' => @$_SESSION['dual-login'],
'ui_texts' => $UI_TEXTS, 'ui_texts' => $UI_TEXTS,
'css_path' => '/cache/'.basename($CSSPath), 'css_path' => '/cache/'.basename($CSSPath),
'theme' => strtolower(trim($DEFAULT_THEME)),
'logo' => customize('get', 'main_logo'), 'logo' => customize('get', 'main_logo'),
'available_languages' => $AVAILABLE_LANGUAGES, 'available_languages' => $AVAILABLE_LANGUAGES,
'lang' => $lang, 'lang' => $lang,
@ -49,6 +48,7 @@ $globalVariables = [
'app_links' => customize('get', 'app_links'), 'app_links' => customize('get', 'app_links'),
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'), 'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
'uri' => $_SERVER['REQUEST_URI'], 'uri' => $_SERVER['REQUEST_URI'],
'last_login' => last_login('get', $_SESSION['mailcow_cc_username'], 7, 0)['ui']['time']
]; ];
foreach ($globalVariables as $globalVariableName => $globalVariableValue) { foreach ($globalVariables as $globalVariableName => $globalVariableValue) {

View File

@ -3,7 +3,7 @@ function init_db_schema() {
try { try {
global $pdo; global $pdo;
$db_version = "17112022_2115"; $db_version = "23122022_1445";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -225,6 +225,22 @@ function init_db_schema() {
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
), ),
"templates" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"template" => "VARCHAR(255) NOT NULL",
"type" => "VARCHAR(255) NOT NULL",
"attributes" => "JSON",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"domain" => array( "domain" => array(
// Todo: Move some attributes to json // Todo: Move some attributes to json
"cols" => array( "cols" => array(
@ -1293,6 +1309,95 @@ function init_db_schema() {
// Fix domain_admins // Fix domain_admins
$pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';");
// add default templates
$default_domain_template = array(
"template" => "Default",
"type" => "domain",
"attributes" => array(
"tags" => array(),
"max_num_aliases_for_domain" => 400,
"max_num_mboxes_for_domain" => 10,
"def_quota_for_mbox" => 3072 * 1048576,
"max_quota_for_mbox" => 10240 * 1048576,
"max_quota_for_domain" => 10240 * 1048576,
"rl_frame" => "s",
"rl_value" => "",
"active" => 1,
"gal" => 1,
"backupmx" => 0,
"relay_all_recipients" => 0,
"relay_unknown_only" => 0,
"dkim_selector" => "dkim",
"key_size" => 2048,
"max_quota_for_domain" => 10240 * 1048576,
)
);
$default_mailbox_template = array(
"template" => "Default",
"type" => "mailbox",
"attributes" => array(
"tags" => array(),
"quota" => 0,
"quarantine_notification" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_notification']),
"quarantine_category" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_category']),
"rl_frame" => "s",
"rl_value" => "",
"force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']),
"sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']),
"active" => 1,
"tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']),
"tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']),
"imap_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['imap_access']),
"pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']),
"smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']),
"sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']),
"acl_spam_alias" => 1,
"acl_tls_policy" => 1,
"acl_spam_score" => 1,
"acl_spam_policy" => 1,
"acl_delimiter_action" => 1,
"acl_syncjobs" => 0,
"acl_eas_reset" => 1,
"acl_sogo_profile_reset" => 0,
"acl_pushover" => 1,
"acl_quarantine" => 1,
"acl_quarantine_attachments" => 1,
"acl_quarantine_notification" => 1,
"acl_quarantine_category" => 1,
"acl_app_passwds" => 1,
)
);
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "domain",
":template" => $default_domain_template["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "domain",
":template" => $default_domain_template["template"],
":attributes" => json_encode($default_domain_template["attributes"])
));
}
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
$stmt->execute(array(
":type" => "mailbox",
":template" => $default_mailbox_template["template"]
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)");
$stmt->execute(array(
":type" => "mailbox",
":template" => $default_mailbox_template["template"],
":attributes" => json_encode($default_mailbox_template["attributes"])
));
}
if (php_sapi_name() == "cli") { if (php_sapi_name() == "cli") {
echo "DB initialization completed" . PHP_EOL; echo "DB initialization completed" . PHP_EOL;
} else { } else {

View File

@ -2,6 +2,8 @@
// check for development mode // check for development mode
$DEV_MODE = (getenv('DEV_MODE') == 'y'); $DEV_MODE = (getenv('DEV_MODE') == 'y');
// check for demo mode
$DEMO_MODE = (getenv('DEMO_MODE') == 'y');
// Slave does not serve UI // Slave does not serve UI
/* if (!preg_match('/y|yes/i', getenv('MASTER'))) { /* if (!preg_match('/y|yes/i', getenv('MASTER'))) {
@ -44,21 +46,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/CSSminifierExtended.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/array_merge_real.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/array_merge_real.php';
// Minify JS
use MatthiasMullie\Minify;
$js_minifier = new JSminifierExtended();
$js_dir = array_diff(scandir('/web/js/build'), array('..', '.'));
foreach ($js_dir as $js_file) {
$js_minifier->add('/web/js/build/' . $js_file);
}
// Minify CSS
$css_minifier = new CSSminifierExtended();
$css_dir = array_diff(scandir('/web/css/build'), array('..', '.'));
foreach ($css_dir as $css_file) {
$css_minifier->add('/web/css/build/' . $css_file);
}
// U2F API + T/HOTP API // U2F API + T/HOTP API
// u2f - deprecated, should be removed // u2f - deprecated, should be removed
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']); $u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
@ -278,6 +265,7 @@ if(file_exists($langFile)) {
$lang = array_merge_real($lang, json_decode(file_get_contents($langFile), true)); $lang = array_merge_real($lang, json_decode(file_get_contents($langFile), true));
} }
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.admin.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.admin.inc.php';
@ -312,4 +300,29 @@ if (isset($_SESSION['mailcow_cc_role'])) {
// } // }
acl('to_session'); acl('to_session');
} }
// init frontend
// Minify JS
use MatthiasMullie\Minify;
$js_minifier = new JSminifierExtended();
$js_dir = array_diff(scandir('/web/js/build'), array('..', '.'));
// Minify CSS
$css_minifier = new CSSminifierExtended();
$css_dir = array_diff(scandir('/web/css/build'), array('..', '.'));
// get customized ui data
$UI_TEXTS = customize('get', 'ui_texts'); $UI_TEXTS = customize('get', 'ui_texts');
// minify bootstrap theme
if (file_exists('/web/css/themes/'.$UI_THEME.'-bootstrap.css'))
$css_minifier->add('/web/css/themes/'.$UI_THEME.'-bootstrap.css');
else
$css_minifier->add('/web/css/themes/lumen-bootstrap.css');
// minify css build files
foreach ($css_dir as $css_file) {
$css_minifier->add('/web/css/build/' . $css_file);
}
// minify js build files
foreach ($js_dir as $js_file) {
$js_minifier->add('/web/js/build/' . $js_file);
}

View File

@ -107,12 +107,10 @@ $AVAILABLE_LANGUAGES = array(
'zh-tw' => '繁體中文 (Traditional Chinese)', 'zh-tw' => '繁體中文 (Traditional Chinese)',
); );
// Change theme (default: lumen) // default theme is lumen
// Needs to be one of those: cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable, sandstone, // additional themes can be found here: https://bootswatch.com/
// simplex, slate, spacelab, superhero, united, yeti // copy them to data/web/css/themes/{THEME-NAME}-bootstrap.css
// See https://bootswatch.com/ $UI_THEME = "lumen";
// WARNING: Only lumen is loaded locally. Enabling any other theme, will download external sources.
$DEFAULT_THEME = 'lumen';
// Show DKIM private keys - false by default // Show DKIM private keys - false by default
$SHOW_DKIM_PRIV_KEYS = false; $SHOW_DKIM_PRIV_KEYS = false;

View File

@ -8,7 +8,7 @@ if (isset($_SESSION['mailcow_cc_role']) && isset($_SESSION['oauth2_request'])) {
exit(); exit();
} }
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') { elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin'); header('Location: /debug');
exit(); exit();
} }
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {

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 it is too large Load Diff

File diff suppressed because one or more lines are too long

7
data/web/js/build/004-moment.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
/**
* This plug-in for DataTables represents the ultimate option in extensibility
* for sorting date / time strings correctly. It uses
* [Moment.js](http://momentjs.com) to create automatic type detection and
* sorting plug-ins for DataTables based on a given format. This way, DataTables
* will automatically detect your temporal information and sort it correctly.
*
* For usage instructions, please see the DataTables blog
* post that [introduces it](//datatables.net/blog/2014-12-18).
*
* @name Ultimate Date / Time sorting
* @summary Sort date and time in any format using Moment.js
* @author [Allan Jardine](//datatables.net)
* @depends DataTables 1.10+, Moment.js 1.7+
*
* @example
* $.fn.dataTable.moment( 'HH:mm MMM D, YY' );
* $.fn.dataTable.moment( 'dddd, MMMM Do, YYYY' );
*
* $('#example').DataTable();
*/
(function (factory) {
if (typeof define === "function" && define.amd) {
define(["jquery", "moment", "datatables.net"], factory);
} else {
factory(jQuery, moment);
}
}(function ($, moment) {
function strip (d) {
if ( typeof d === 'string' ) {
// Strip HTML tags and newline characters if possible
d = d.replace(/(<.*?>)|(\r?\n|\r)/g, '');
// Strip out surrounding white space
d = d.trim();
}
return d;
}
$.fn.dataTable.moment = function ( format, locale, reverseEmpties ) {
var types = $.fn.dataTable.ext.type;
// Add type detection
types.detect.unshift( function ( d ) {
d = strip(d);
// Null and empty values are acceptable
if ( d === '' || d === null ) {
return 'moment-'+format;
}
return moment( d, format, locale, true ).isValid() ?
'moment-'+format :
null;
} );
// Add sorting method - use an integer for the sorting
types.order[ 'moment-'+format+'-pre' ] = function ( d ) {
d = strip(d);
return !moment(d, format, locale, true).isValid() ?
(reverseEmpties ? -Infinity : Infinity) :
parseInt( moment( d, format, locale, true ).format( 'x' ), 10 );
};
};
}));

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

13269
data/web/js/build/009-chart.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,778 +0,0 @@
//Copyright 2014-2015 Google Inc. All rights reserved.
//Use of this source code is governed by a BSD-style
//license that can be found in the LICENSE file or at
//https://developers.google.com/open-source/licenses/bsd
// ref: https://github.com/google/u2f-ref-code/blob/master/u2f-gae-demo/war/js/u2f-api.js
/**
* @fileoverview The U2F api.
*/
'use strict';
/**
* Modification:
* Wrap implementation so that we can exit if window.u2f is already supplied by the browser (see below).
*/
(function (root) {
/**
* Modification:
* Only continue load this library if window.u2f is not already supplied by the browser.
*/
var browserImplementsU2f = !!((typeof root.u2f !== 'undefined') && root.u2f.register);
if (browserImplementsU2f) {
root.u2f.isSupported = true;
return;
}
/**
* Namespace for the U2F api.
* @type {Object}
*/
var u2f = root.u2f || {};
/**
* Modification:
* Check if browser supports U2F API before this wrapper was added.
*/
u2f.isSupported = !!(((typeof u2f !== 'undefined') && u2f.register) || ((typeof chrome !== 'undefined') && chrome.runtime));
/**
* FIDO U2F Javascript API Version
* @number
*/
var js_api_version;
/**
* The U2F extension id
* @const {string}
*/
// The Chrome packaged app extension ID.
// Uncomment this if you want to deploy a server instance that uses
// the package Chrome app and does not require installing the U2F Chrome extension.
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
// The U2F Chrome extension ID.
// Uncomment this if you want to deploy a server instance that uses
// the U2F Chrome extension to authenticate.
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
/**
* Message types for messsages to/from the extension
* @const
* @enum {string}
*/
u2f.MessageTypes = {
'U2F_REGISTER_REQUEST': 'u2f_register_request',
'U2F_REGISTER_RESPONSE': 'u2f_register_response',
'U2F_SIGN_REQUEST': 'u2f_sign_request',
'U2F_SIGN_RESPONSE': 'u2f_sign_response',
'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
};
/**
* Response status codes
* @const
* @enum {number}
*/
u2f.ErrorCodes = {
'OK': 0,
'OTHER_ERROR': 1,
'BAD_REQUEST': 2,
'CONFIGURATION_UNSUPPORTED': 3,
'DEVICE_INELIGIBLE': 4,
'TIMEOUT': 5
};
/**
* A message for registration requests
* @typedef {{
* type: u2f.MessageTypes,
* appId: ?string,
* timeoutSeconds: ?number,
* requestId: ?number
* }}
*/
u2f.U2fRequest;
/**
* A message for registration responses
* @typedef {{
* type: u2f.MessageTypes,
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
* requestId: ?number
* }}
*/
u2f.U2fResponse;
/**
* An error object for responses
* @typedef {{
* errorCode: u2f.ErrorCodes,
* errorMessage: ?string
* }}
*/
u2f.Error;
/**
* Data object for a single sign request.
* @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
*/
u2f.Transport;
/**
* Data object for a single sign request.
* @typedef {Array<u2f.Transport>}
*/
u2f.Transports;
/**
* Data object for a single sign request.
* @typedef {{
* version: string,
* challenge: string,
* keyHandle: string,
* appId: string
* }}
*/
u2f.SignRequest;
/**
* Data object for a sign response.
* @typedef {{
* keyHandle: string,
* signatureData: string,
* clientData: string
* }}
*/
u2f.SignResponse;
/**
* Data object for a registration request.
* @typedef {{
* version: string,
* challenge: string
* }}
*/
u2f.RegisterRequest;
/**
* Data object for a registration response.
* @typedef {{
* version: string,
* keyHandle: string,
* transports: Transports,
* appId: string
* }}
*/
u2f.RegisterResponse;
/**
* Data object for a registered key.
* @typedef {{
* version: string,
* keyHandle: string,
* transports: ?Transports,
* appId: ?string
* }}
*/
u2f.RegisteredKey;
/**
* Data object for a get API register response.
* @typedef {{
* js_api_version: number
* }}
*/
u2f.GetJsApiVersionResponse;
//Low level MessagePort API support
/**
* Sets up a MessagePort to the U2F extension using the
* available mechanisms.
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
*/
u2f.getMessagePort = function (callback) {
if (typeof chrome != 'undefined' && chrome.runtime) {
// The actual message here does not matter, but we need to get a reply
// for the callback to run. Thus, send an empty signature request
// in order to get a failure response.
var msg = {
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
signRequests: []
};
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function () {
if (!chrome.runtime.lastError) {
// We are on a whitelisted origin and can talk directly
// with the extension.
u2f.getChromeRuntimePort_(callback);
} else {
// chrome.runtime was available, but we couldn't message
// the extension directly, use iframe
u2f.getIframePort_(callback);
}
});
} else if (u2f.isAndroidChrome_()) {
u2f.getAuthenticatorPort_(callback);
} else if (u2f.isIosChrome_()) {
u2f.getIosPort_(callback);
} else {
// chrome.runtime was not available at all, which is normal
// when this origin doesn't have access to any extensions.
u2f.getIframePort_(callback);
}
};
/**
* Detect chrome running on android based on the browser's useragent.
* @private
*/
u2f.isAndroidChrome_ = function () {
var userAgent = navigator.userAgent;
return userAgent.indexOf('Chrome') != -1 &&
userAgent.indexOf('Android') != -1;
};
/**
* Detect chrome running on iOS based on the browser's platform.
* @private
*/
u2f.isIosChrome_ = function () {
return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
};
/**
* Connects directly to the extension via chrome.runtime.connect.
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
* @private
*/
u2f.getChromeRuntimePort_ = function (callback) {
var port = chrome.runtime.connect(u2f.EXTENSION_ID,
{ 'includeTlsChannelId': true });
setTimeout(function () {
callback(new u2f.WrappedChromeRuntimePort_(port));
}, 0);
};
/**
* Return a 'port' abstraction to the Authenticator app.
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
* @private
*/
u2f.getAuthenticatorPort_ = function (callback) {
setTimeout(function () {
callback(new u2f.WrappedAuthenticatorPort_());
}, 0);
};
/**
* Return a 'port' abstraction to the iOS client app.
* @param {function(u2f.WrappedIosPort_)} callback
* @private
*/
u2f.getIosPort_ = function (callback) {
setTimeout(function () {
callback(new u2f.WrappedIosPort_());
}, 0);
};
/**
* A wrapper for chrome.runtime.Port that is compatible with MessagePort.
* @param {Port} port
* @constructor
* @private
*/
u2f.WrappedChromeRuntimePort_ = function (port) {
this.port_ = port;
};
/**
* Format and return a sign request compliant with the JS API version supported by the extension.
* @param {Array<u2f.SignRequest>} signRequests
* @param {number} timeoutSeconds
* @param {number} reqId
* @return {Object}
*/
u2f.formatSignRequest_ =
function (appId, challenge, registeredKeys, timeoutSeconds, reqId) {
if (js_api_version === undefined || js_api_version < 1.1) {
// Adapt request to the 1.0 JS API
var signRequests = [];
for (var i = 0; i < registeredKeys.length; i++) {
signRequests[i] = {
version: registeredKeys[i].version,
challenge: challenge,
keyHandle: registeredKeys[i].keyHandle,
appId: appId
};
}
return {
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
signRequests: signRequests,
timeoutSeconds: timeoutSeconds,
requestId: reqId
};
}
// JS 1.1 API
return {
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
appId: appId,
challenge: challenge,
registeredKeys: registeredKeys,
timeoutSeconds: timeoutSeconds,
requestId: reqId
};
};
/**
* Format and return a register request compliant with the JS API version supported by the extension..
* @param {Array<u2f.SignRequest>} signRequests
* @param {Array<u2f.RegisterRequest>} signRequests
* @param {number} timeoutSeconds
* @param {number} reqId
* @return {Object}
*/
u2f.formatRegisterRequest_ =
function (appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
if (js_api_version === undefined || js_api_version < 1.1) {
// Adapt request to the 1.0 JS API
for (var i = 0; i < registerRequests.length; i++) {
registerRequests[i].appId = appId;
}
var signRequests = [];
for (var i = 0; i < registeredKeys.length; i++) {
signRequests[i] = {
version: registeredKeys[i].version,
challenge: registerRequests[0],
keyHandle: registeredKeys[i].keyHandle,
appId: appId
};
}
return {
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
signRequests: signRequests,
registerRequests: registerRequests,
timeoutSeconds: timeoutSeconds,
requestId: reqId
};
}
// JS 1.1 API
return {
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
appId: appId,
registerRequests: registerRequests,
registeredKeys: registeredKeys,
timeoutSeconds: timeoutSeconds,
requestId: reqId
};
};
/**
* Posts a message on the underlying channel.
* @param {Object} message
*/
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function (message) {
this.port_.postMessage(message);
};
/**
* Emulates the HTML 5 addEventListener interface. Works only for the
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
* @param {string} eventName
* @param {function({data: Object})} handler
*/
u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
function (eventName, handler) {
var name = eventName.toLowerCase();
if (name == 'message' || name == 'onmessage') {
this.port_.onMessage.addListener(function (message) {
// Emulate a minimal MessageEvent object
handler({ 'data': message });
});
} else {
console.error('WrappedChromeRuntimePort only supports onMessage');
}
};
/**
* Wrap the Authenticator app with a MessagePort interface.
* @constructor
* @private
*/
u2f.WrappedAuthenticatorPort_ = function () {
this.requestId_ = -1;
this.requestObject_ = null;
}
/**
* Launch the Authenticator intent.
* @param {Object} message
*/
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function (message) {
var intentUrl =
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
';S.request=' + encodeURIComponent(JSON.stringify(message)) +
';end';
document.location = intentUrl;
};
/**
* Tells what type of port this is.
* @return {String} port type
*/
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function () {
return "WrappedAuthenticatorPort_";
};
/**
* Emulates the HTML 5 addEventListener interface.
* @param {string} eventName
* @param {function({data: Object})} handler
*/
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function (eventName, handler) {
var name = eventName.toLowerCase();
if (name == 'message') {
var self = this;
/* Register a callback to that executes when
* chrome injects the response. */
window.addEventListener(
'message', self.onRequestUpdate_.bind(self, handler), false);
} else {
console.error('WrappedAuthenticatorPort only supports message');
}
};
/**
* Callback invoked when a response is received from the Authenticator.
* @param function({data: Object}) callback
* @param {Object} message message Object
*/
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
function (callback, message) {
var messageObject = JSON.parse(message.data);
var intentUrl = messageObject['intentURL'];
var errorCode = messageObject['errorCode'];
var responseObject = null;
if (messageObject.hasOwnProperty('data')) {
responseObject = /** @type {Object} */ (
JSON.parse(messageObject['data']));
}
callback({ 'data': responseObject });
};
/**
* Base URL for intents to Authenticator.
* @const
* @private
*/
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
/**
* Wrap the iOS client app with a MessagePort interface.
* @constructor
* @private
*/
u2f.WrappedIosPort_ = function () { };
/**
* Launch the iOS client app request
* @param {Object} message
*/
u2f.WrappedIosPort_.prototype.postMessage = function (message) {
var str = JSON.stringify(message);
var url = "u2f://auth?" + encodeURI(str);
location.replace(url);
};
/**
* Tells what type of port this is.
* @return {String} port type
*/
u2f.WrappedIosPort_.prototype.getPortType = function () {
return "WrappedIosPort_";
};
/**
* Emulates the HTML 5 addEventListener interface.
* @param {string} eventName
* @param {function({data: Object})} handler
*/
u2f.WrappedIosPort_.prototype.addEventListener = function (eventName, handler) {
var name = eventName.toLowerCase();
if (name !== 'message') {
console.error('WrappedIosPort only supports message');
}
};
/**
* Sets up an embedded trampoline iframe, sourced from the extension.
* @param {function(MessagePort)} callback
* @private
*/
u2f.getIframePort_ = function (callback) {
// Create the iframe
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
var iframe = document.createElement('iframe');
iframe.src = iframeOrigin + '/u2f-comms.html';
iframe.setAttribute('style', 'display:none');
document.body.appendChild(iframe);
var channel = new MessageChannel();
var ready = function (message) {
if (message.data == 'ready') {
channel.port1.removeEventListener('message', ready);
callback(channel.port1);
} else {
console.error('First event on iframe port was not "ready"');
}
};
channel.port1.addEventListener('message', ready);
channel.port1.start();
iframe.addEventListener('load', function () {
// Deliver the port to the iframe and initialize
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
});
};
//High-level JS API
/**
* Default extension response timeout in seconds.
* @const
*/
u2f.EXTENSION_TIMEOUT_SEC = 30;
/**
* A singleton instance for a MessagePort to the extension.
* @type {MessagePort|u2f.WrappedChromeRuntimePort_}
* @private
*/
u2f.port_ = null;
/**
* Callbacks waiting for a port
* @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
* @private
*/
u2f.waitingForPort_ = [];
/**
* A counter for requestIds.
* @type {number}
* @private
*/
u2f.reqCounter_ = 0;
/**
* A map from requestIds to client callbacks
* @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
* |function((u2f.Error|u2f.SignResponse)))>}
* @private
*/
u2f.callbackMap_ = {};
/**
* Creates or retrieves the MessagePort singleton to use.
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
* @private
*/
u2f.getPortSingleton_ = function (callback) {
if (u2f.port_) {
callback(u2f.port_);
} else {
if (u2f.waitingForPort_.length == 0) {
u2f.getMessagePort(function (port) {
u2f.port_ = port;
u2f.port_.addEventListener('message',
/** @type {function(Event)} */(u2f.responseHandler_));
// Careful, here be async callbacks. Maybe.
while (u2f.waitingForPort_.length)
u2f.waitingForPort_.shift()(u2f.port_);
});
}
u2f.waitingForPort_.push(callback);
}
};
/**
* Handles response messages from the extension.
* @param {MessageEvent.<u2f.Response>} message
* @private
*/
u2f.responseHandler_ = function (message) {
var response = message.data;
var reqId = response['requestId'];
if (!reqId || !u2f.callbackMap_[reqId]) {
console.error('Unknown or missing requestId in response.');
return;
}
var cb = u2f.callbackMap_[reqId];
delete u2f.callbackMap_[reqId];
cb(response['responseData']);
};
/**
* Dispatches an array of sign requests to available U2F tokens.
* If the JS API version supported by the extension is unknown, it first sends a
* message to the extension to find out the supported API version and then it sends
* the sign request.
* @param {string=} appId
* @param {string=} challenge
* @param {Array<u2f.RegisteredKey>} registeredKeys
* @param {function((u2f.Error|u2f.SignResponse))} callback
* @param {number=} opt_timeoutSeconds
*/
u2f.sign = function (appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
if (js_api_version === undefined) {
// Send a message to get the extension to JS API version, then send the actual sign request.
u2f.getApiVersion(
function (response) {
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
console.log("Extension JS API Version: ", js_api_version);
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
});
} else {
// We know the JS API version. Send the actual sign request in the supported API version.
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
}
};
/**
* Dispatches an array of sign requests to available U2F tokens.
* @param {string=} appId
* @param {string=} challenge
* @param {Array<u2f.RegisteredKey>} registeredKeys
* @param {function((u2f.Error|u2f.SignResponse))} callback
* @param {number=} opt_timeoutSeconds
*/
u2f.sendSignRequest = function (appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
u2f.getPortSingleton_(function (port) {
var reqId = ++u2f.reqCounter_;
u2f.callbackMap_[reqId] = callback;
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
port.postMessage(req);
});
};
/**
* Dispatches register requests to available U2F tokens. An array of sign
* requests identifies already registered tokens.
* If the JS API version supported by the extension is unknown, it first sends a
* message to the extension to find out the supported API version and then it sends
* the register request.
* @param {string=} appId
* @param {Array<u2f.RegisterRequest>} registerRequests
* @param {Array<u2f.RegisteredKey>} registeredKeys
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
* @param {number=} opt_timeoutSeconds
*/
u2f.register = function (appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
if (js_api_version === undefined) {
// Send a message to get the extension to JS API version, then send the actual register request.
u2f.getApiVersion(
function (response) {
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
console.log("Extension JS API Version: ", js_api_version);
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
callback, opt_timeoutSeconds);
});
} else {
// We know the JS API version. Send the actual register request in the supported API version.
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
callback, opt_timeoutSeconds);
}
};
/**
* Dispatches register requests to available U2F tokens. An array of sign
* requests identifies already registered tokens.
* @param {string=} appId
* @param {Array<u2f.RegisterRequest>} registerRequests
* @param {Array<u2f.RegisteredKey>} registeredKeys
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
* @param {number=} opt_timeoutSeconds
*/
u2f.sendRegisterRequest = function (appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
u2f.getPortSingleton_(function (port) {
var reqId = ++u2f.reqCounter_;
u2f.callbackMap_[reqId] = callback;
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
var req = u2f.formatRegisterRequest_(
appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
port.postMessage(req);
});
};
/**
* Dispatches a message to the extension to find out the supported
* JS API version.
* If the user is on a mobile phone and is thus using Google Authenticator instead
* of the Chrome extension, don't send the request and simply return 0.
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
* @param {number=} opt_timeoutSeconds
*/
u2f.getApiVersion = function (callback, opt_timeoutSeconds) {
u2f.getPortSingleton_(function (port) {
// If we are using Android Google Authenticator or iOS client app,
// do not fire an intent to ask which JS API version to use.
if (port.getPortType) {
var apiVersion;
switch (port.getPortType()) {
case 'WrappedIosPort_':
case 'WrappedAuthenticatorPort_':
apiVersion = 1.1;
break;
default:
apiVersion = 0;
break;
}
callback({ 'js_api_version': apiVersion });
return;
}
var reqId = ++u2f.reqCounter_;
u2f.callbackMap_[reqId] = callback;
var req = {
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
requestId: reqId
};
port.postMessage(req);
});
};
/**
* Modification:
* Assign u2f back to window (root) scope.
*/
root.u2f = u2f;
}(this));

View File

@ -11,7 +11,7 @@ $(document).ready(function() {
} else { } else {
var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent(); var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent();
if (parent_btn_grp.hasClass('btn-group')) { if (parent_btn_grp.hasClass('btn-group')) {
parent_btn_grp.replaceWith('<button class="btn btn-default btn-sm" disabled>' + lang_footer.loading + '</a>'); parent_btn_grp.replaceWith('<button class="btn btn-secondary btn-sm" disabled>' + lang_footer.loading + '</a>');
} }
$(elem).text(lang_footer.loading); $(elem).text(lang_footer.loading);
$(elem).attr('data-submitted', '1'); $(elem).attr('data-submitted', '1');
@ -355,11 +355,8 @@ $(document).ready(function() {
data_array[i] = decodeURIComponent(data_array[i]); data_array[i] = decodeURIComponent(data_array[i]);
$("#ItemsToDelete").append("<li>" + escapeHtml(data_array[i]) + "</li>"); $("#ItemsToDelete").append("<li>" + escapeHtml(data_array[i]) + "</li>");
} }
}); })
$('#ConfirmDeleteModal').modal({ $('#ConfirmDeleteModal').modal('show')
backdrop: 'static',
keyboard: false
})
.one('click', '#IsConfirmed', function(e) { .one('click', '#IsConfirmed', function(e) {
if (is_active($('#IsConfirmed'))) { return false; } if (is_active($('#IsConfirmed'))) { return false; }
$.ajax({ $.ajax({
@ -383,4 +380,18 @@ $(document).ready(function() {
$('#ConfirmDeleteModal').modal('hide'); $('#ConfirmDeleteModal').modal('hide');
}); });
}); });
// toggle jquery datatables child rows
$('button[data-datatables-expand], a[data-datatables-expand]').on('click', function (e) {
e.preventDefault();
var tableId = e.target.getAttribute("data-datatables-expand");
var table = $("#" + tableId).DataTable();
table.rows(':not(.parent)').nodes().to$().find('td:first-child').trigger('click');
});
$('button[data-datatables-collapse], a[data-datatables-collapse]').on('click', function (e) {
e.preventDefault();
var tableId = e.target.getAttribute("data-datatables-collapse");
var table = $("#" + tableId).DataTable();
table.rows('.parent').nodes().to$().find('td:first-child').trigger('click');
});
}); });

View File

@ -1,239 +0,0 @@
!function ($) {
"use strict";
// TABCOLLAPSE CLASS DEFINITION
// ======================
var TabCollapse = function (el, options) {
this.options = options;
this.$tabs = $(el);
this._accordionVisible = false; //content is attached to tabs at first
this._initAccordion();
this._checkStateOnResize();
// checkState() has gone to setTimeout for making it possible to attach listeners to
// shown-accordion.bs.tabcollapse event on page load.
// See https://github.com/flatlogic/bootstrap-tabcollapse/issues/23
var that = this;
setTimeout(function() {
that.checkState();
}, 0);
};
TabCollapse.DEFAULTS = {
accordionClass: 'visible-xs',
tabsClass: 'hidden-xs',
accordionTemplate: function(heading, groupId, parentId, active) {
return '<div class="panel panel-default">' +
' <div class="panel-heading">' +
' <h4 class="panel-title">' +
' </h4>' +
' </div>' +
' <div id="' + groupId + '" class="panel-collapse collapse ' + (active ? 'in' : '') + '">' +
' <div class="panel-body js-tabcollapse-panel-body">' +
' </div>' +
' </div>' +
'</div>'
}
};
TabCollapse.prototype.checkState = function(){
if (this.$tabs.is(':visible') && this._accordionVisible){
this.showTabs();
this._accordionVisible = false;
} else if (this.$accordion.is(':visible') && !this._accordionVisible){
this.showAccordion();
this._accordionVisible = true;
}
};
TabCollapse.prototype.showTabs = function(){
var view = this;
this.$tabs.trigger($.Event('show-tabs.bs.tabcollapse'));
var $panelHeadings = this.$accordion.find('.js-tabcollapse-panel-heading').detach();
$panelHeadings.each(function() {
var $panelHeading = $(this),
$parentLi = $panelHeading.data('bs.tabcollapse.parentLi');
var $oldHeading = view._panelHeadingToTabHeading($panelHeading);
$parentLi.removeClass('active');
if ($parentLi.parent().hasClass('dropdown-menu') && !$parentLi.siblings('li').hasClass('active')) {
$parentLi.parent().parent().removeClass('active');
}
if (!$oldHeading.hasClass('collapsed')) {
$parentLi.addClass('active');
$('.tab-pane').removeClass('active');
$($panelHeading.attr('href')).addClass('active');
if ($parentLi.parent().hasClass('dropdown-menu')) {
$parentLi.parent().parent().addClass('active');
}
} else {
$oldHeading.removeClass('collapsed');
}
$parentLi.append($panelHeading);
});
if (!$('li').hasClass('active')) {
$('li').first().addClass('active')
}
var $panelBodies = this.$accordion.find('.js-tabcollapse-panel-body');
$panelBodies.each(function(){
var $panelBody = $(this),
$tabPane = $panelBody.data('bs.tabcollapse.tabpane');
$tabPane.append($panelBody.contents().detach());
});
this.$accordion.html('');
if(this.options.updateLinks) {
var $tabContents = this.getTabContentElement();
$tabContents.find('[data-toggle-was="tab"], [data-toggle-was="pill"]').each(function() {
var $el = $(this);
var href = $el.attr('href').replace(/-collapse$/g, '');
$el.attr({
'data-toggle': $el.attr('data-toggle-was'),
'data-toggle-was': '',
'data-parent': '',
href: href
});
});
}
this.$tabs.trigger($.Event('shown-tabs.bs.tabcollapse'));
};
TabCollapse.prototype.getTabContentElement = function(){
var $tabContents = $(this.options.tabContentSelector);
if($tabContents.length === 0) {
$tabContents = this.$tabs.siblings('.tab-content');
}
return $tabContents;
};
TabCollapse.prototype.showAccordion = function(){
this.$tabs.trigger($.Event('show-accordion.bs.tabcollapse'));
var $headings = this.$tabs.find('li:not(.dropdown) [data-toggle="tab"], li:not(.dropdown) [data-toggle="pill"]'),
view = this;
$headings.each(function(){
var $heading = $(this),
$parentLi = $heading.parent();
$heading.data('bs.tabcollapse.parentLi', $parentLi);
view.$accordion.append(view._createAccordionGroup(view.$accordion.attr('id'), $heading.detach()));
});
if(this.options.updateLinks) {
var parentId = this.$accordion.attr('id');
var $selector = this.$accordion.find('.js-tabcollapse-panel-body');
$selector.find('[data-toggle="tab"], [data-toggle="pill"]').each(function() {
var $el = $(this);
var href = $el.attr('href') + '-collapse';
$el.attr({
'data-toggle-was': $el.attr('data-toggle'),
'data-toggle': 'collapse',
'data-parent': '#' + parentId,
href: href
});
});
}
this.$tabs.trigger($.Event('shown-accordion.bs.tabcollapse'));
};
TabCollapse.prototype._panelHeadingToTabHeading = function($heading) {
var href = $heading.attr('href').replace(/-collapse$/g, '');
$heading.attr({
'data-toggle': 'tab',
'href': href,
'data-parent': ''
});
return $heading;
};
TabCollapse.prototype._tabHeadingToPanelHeading = function($heading, groupId, parentId, active) {
$heading.addClass('js-tabcollapse-panel-heading ' + (active ? '' : 'collapsed'));
$heading.attr({
'data-toggle': 'collapse',
'data-parent': '#' + parentId,
'href': '#' + groupId
});
return $heading;
};
TabCollapse.prototype._checkStateOnResize = function(){
var view = this;
$(window).resize(function(){
clearTimeout(view._resizeTimeout);
view._resizeTimeout = setTimeout(function(){
view.checkState();
}, 100);
});
};
TabCollapse.prototype._initAccordion = function(){
var randomString = function() {
var result = "",
possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ ) {
result += possible.charAt(Math.floor(Math.random() * possible.length));
}
return result;
};
var srcId = this.$tabs.attr('id'),
accordionId = (srcId ? srcId : randomString()) + '-accordion';
this.$accordion = $('<div class="panel-group ' + this.options.accordionClass + '" id="' + accordionId +'"></div>');
this.$tabs.after(this.$accordion);
this.$tabs.addClass(this.options.tabsClass);
this.getTabContentElement().addClass(this.options.tabsClass);
};
TabCollapse.prototype._createAccordionGroup = function(parentId, $heading){
var tabSelector = $heading.attr('data-target'),
active = $heading.data('bs.tabcollapse.parentLi').is('.active');
if (!tabSelector) {
tabSelector = $heading.attr('href');
tabSelector = tabSelector && tabSelector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
}
var $tabPane = $(tabSelector),
groupId = $tabPane.attr('id') + '-collapse',
$panel = $(this.options.accordionTemplate($heading, groupId, parentId, active));
$panel.find('.panel-heading > .panel-title').append(this._tabHeadingToPanelHeading($heading, groupId, parentId, active));
$panel.find('.panel-body').append($tabPane.contents().detach())
.data('bs.tabcollapse.tabpane', $tabPane);
return $panel;
};
// TABCOLLAPSE PLUGIN DEFINITION
// =======================
$.fn.tabCollapse = function (option) {
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.tabcollapse');
var options = $.extend({}, TabCollapse.DEFAULTS, $this.data(), typeof option === 'object' && option);
if (!data) $this.data('bs.tabcollapse', new TabCollapse(this, options));
});
};
$.fn.tabCollapse.Constructor = TabCollapse;
}(window.jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -55,14 +55,14 @@ $(document).ready(function() {
// tooltips // tooltips
$(function () { $(function () {
$('[data-toggle="tooltip"]').tooltip() $('[data-bs-toggle="tooltip"]').tooltip()
}); });
// remember last navigation pill // remember last navigation pill
(function () { (function () {
'use strict'; 'use strict';
if ($('a[data-toggle="tab"]').length) { // remember desktop tabs
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { $('button[data-bs-toggle="tab"]').on('click', function (e) {
if ($(this).data('dont-remember') == 1) { if ($(this).data('dont-remember') == 1) {
return true; return true;
} }
@ -71,8 +71,28 @@ $(document).ready(function() {
if (id) { if (id) {
key += ':' + id; key += ':' + id;
} }
localStorage.setItem(key, $(e.target).attr('href'));
var tab_id = $(e.target).attr('data-bs-target').substring(1);
localStorage.setItem(key, tab_id);
}); });
// remember mobile tabs
$('button[data-bs-target^="#collapse-tab-"]').on('click', function (e) {
// only remember tab if its being opened
if ($(this).hasClass('collapsed')) return false;
var tab_id = $(this).closest('div[role="tabpanel"]').attr('id');
if ($(this).data('dont-remember') == 1) {
return true;
}
var id = $(this).parents('[role="tablist"]').attr('id');;
var key = 'lastTag';
if (id) {
key += ':' + id;
}
localStorage.setItem(key, tab_id);
});
// open last tab
$('[role="tablist"]').each(function (idx, elem) { $('[role="tablist"]').each(function (idx, elem) {
var id = $(elem).attr('id'); var id = $(elem).attr('id');
var key = 'lastTag'; var key = 'lastTag';
@ -81,10 +101,11 @@ $(document).ready(function() {
} }
var lastTab = localStorage.getItem(key); var lastTab = localStorage.getItem(key);
if (lastTab) { if (lastTab) {
$('[href="' + lastTab + '"]').tab('show'); $('[data-bs-target="#' + lastTab + '"]').click();
var tab = $('[id^="' + lastTab + '"]');
$(tab).find('.card-body.collapse').collapse('show');
} }
}); });
}
})(); })();
// IE fix to hide scrollbars when table body is empty // IE fix to hide scrollbars when table body is empty
@ -119,7 +140,7 @@ $(document).ready(function() {
shake(password_field); shake(password_field);
} }
else { else {
$(hibp_result).attr('class', 'hibp-out label label-info'); $(hibp_result).attr('class', 'hibp-out badge fs-5 bg-info');
$(hibp_result).text(lang_footer.loading); $(hibp_result).text(lang_footer.loading);
var password_digest = $.sha1($(password_field).val()) var password_digest = $.sha1($(password_field).val())
var digest_five = password_digest.substring(0, 5).toUpperCase(); var digest_five = password_digest.substring(0, 5).toUpperCase();
@ -130,16 +151,16 @@ $(document).ready(function() {
type: 'GET', type: 'GET',
success: function(res) { success: function(res) {
if (res.search(compl_digest) > -1){ if (res.search(compl_digest) > -1){
$(hibp_result).removeClass('label label-info').addClass('label label-danger'); $(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-danger');
$(hibp_result).text(lang_footer.hibp_nok) $(hibp_result).text(lang_footer.hibp_nok)
} else { } else {
$(hibp_result).removeClass('label label-info').addClass('label label-success'); $(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-success');
$(hibp_result).text(lang_footer.hibp_ok) $(hibp_result).text(lang_footer.hibp_ok)
} }
$(hibp_field).removeClass('task-running'); $(hibp_field).removeClass('task-running');
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
$(hibp_result).removeClass('label label-info').addClass('label label-warning'); $(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-warning');
$(hibp_result).text('API error: ' + xhr.responseText) $(hibp_result).text('API error: ' + xhr.responseText)
$(hibp_field).removeClass('task-running'); $(hibp_field).removeClass('task-running');
} }
@ -150,8 +171,8 @@ $(document).ready(function() {
// Disable disallowed inputs // Disable disallowed inputs
$('[data-acl="0"]').each(function(event){ $('[data-acl="0"]').each(function(event){
if ($(this).is("a")) { if ($(this).is("a")) {
$(this).removeAttr("data-toggle"); $(this).removeAttr("data-bs-toggle");
$(this).removeAttr("data-target"); $(this).removeAttr("data-bs-target");
$(this).removeAttr("data-action"); $(this).removeAttr("data-action");
$(this).click(function(event) { $(this).click(function(event) {
event.preventDefault(); event.preventDefault();
@ -166,8 +187,8 @@ $(document).ready(function() {
if ($(this).hasClass('btn-group')) { if ($(this).hasClass('btn-group')) {
$(this).find('a').each(function(){ $(this).find('a').each(function(){
$(this).removeClass('dropdown-toggle') $(this).removeClass('dropdown-toggle')
.removeAttr('data-toggle') .removeAttr('data-bs-toggle')
.removeAttr('data-target') .removeAttr('data-bs-target')
.removeAttr('data-action') .removeAttr('data-action')
.removeAttr('id') .removeAttr('id')
.attr("disabled", true); .attr("disabled", true);
@ -182,7 +203,7 @@ $(document).ready(function() {
} else if ($(this).hasClass('input-group')) { } else if ($(this).hasClass('input-group')) {
$(this).find('input').each(function() { $(this).find('input').each(function() {
$(this).removeClass('dropdown-toggle') $(this).removeClass('dropdown-toggle')
.removeAttr('data-toggle') .removeAttr('data-bs-toggle')
.attr("disabled", true); .attr("disabled", true);
$(this).click(function(event) { $(this).click(function(event) {
event.preventDefault(); event.preventDefault();
@ -226,7 +247,7 @@ $(document).ready(function() {
$('#containerName').text(container); $('#containerName').text(container);
$('#triggerRestartContainer').click(function(){ $('#triggerRestartContainer').click(function(){
$(this).prop("disabled",true); $(this).prop("disabled",true);
$(this).html('<i class="bi bi-arrow-repeat icon-spin"></i> '); $(this).html('<div class="spinner-border text-white" role="status"><span class="visually-hidden">Loading...</span></div>');
$('#statusTriggerRestartContainer').html(lang_footer.restarting_container); $('#statusTriggerRestartContainer').html(lang_footer.restarting_container);
$.ajax({ $.ajax({
method: 'get', method: 'get',
@ -253,26 +274,11 @@ $(document).ready(function() {
}); });
}) })
// responsive tabs // Jquery Datatables, enable responsive plugin and date sort plugin
$('.responsive-tabs').tabCollapse({ $.extend($.fn.dataTable.defaults, {
tabsClass: 'hidden-xs', responsive: true
accordionClass: 'js-tabcollapse-panel-group visible-xs'
});
$(document).on("shown.bs.collapse shown.bs.tab", function (e) {
var target = $(e.target);
if($(window).width() <= 767) {
var offset = target.offset().top - 112;
$("html, body").stop().animate({
scrollTop: offset
}, 100);
}
if(target.hasClass('panel-collapse')){
var id = e.target.id.replace(/-collapse$/g, '');
if(id){
localStorage.setItem('lastTag', '#'+id);
}
}
}); });
$.fn.dataTable.moment('dd:mm:YYYY');
// tag boxes // tag boxes
$('.tag-box .tag-add').click(function(){ $('.tag-box .tag-add').click(function(){
@ -284,40 +290,65 @@ $(document).ready(function() {
addTag(this); addTag(this);
} }
}); });
function addTag(tagAddElem){
var tagboxElem = $(tagAddElem).parent();
var tagInputElem = $(tagboxElem).find(".tag-input")[0];
var tagValuesElem = $(tagboxElem).find(".tag-values")[0];
var tag = escapeHtml($(tagInputElem).val()); // Dark Mode Loader
if (!tag) return; $('#dark-mode-toggle').click(toggleDarkMode);
var value_tags = []; if ($('#dark-mode-theme').length) {
try { $('#dark-mode-toggle').prop('checked', true);
value_tags = JSON.parse($(tagValuesElem).val()); if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png');
} catch {} if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png');
if (!Array.isArray(value_tags)) value_tags = []; }
if (value_tags.includes(tag)) return; function toggleDarkMode(){
if($('#dark-mode-theme').length){
$('<span class="badge badge-primary tag-badge btn-badge"><i class="bi bi-tag-fill"></i> ' + tag + '</span>').insertBefore('.tag-input').click(function(){ $('#dark-mode-theme').remove();
var del_tag = unescapeHtml($(this).text()); $('#dark-mode-toggle').prop('checked', false);
var del_tags = []; if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png');
try { if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png');
del_tags = JSON.parse($(tagValuesElem).val()); localStorage.setItem('theme', 'light');
} catch {} }else{
if (Array.isArray(del_tags)){ $('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">');
del_tags.splice(del_tags.indexOf(del_tag), 1); $('#dark-mode-toggle').prop('checked', true);
$(tagValuesElem).val(JSON.stringify(del_tags)); 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');
$(this).remove(); localStorage.setItem('theme', 'dark');
}); }
value_tags.push($(tagInputElem).val());
$(tagValuesElem).val(JSON.stringify(value_tags));
$(tagInputElem).val('');
} }
}); });
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery // https://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
function escapeHtml(n){var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"}; return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} function escapeHtml(n){var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"}; return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function unescapeHtml(t){var n={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x2F;":"/","&#x60;":"`","&#x3D;":"="};return String(t).replace(/&amp;|&lt;|&gt;|&quot;|&#39;|&#x2F|&#x60|&#x3D;/g,function(t){return n[t]})} function unescapeHtml(t){var n={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x2F;":"/","&#x60;":"`","&#x3D;":"="};return String(t).replace(/&amp;|&lt;|&gt;|&quot;|&#39;|&#x2F|&#x60|&#x3D;/g,function(t){return n[t]})}
function addTag(tagAddElem, tag = null){
var tagboxElem = $(tagAddElem).parent();
var tagInputElem = $(tagboxElem).find(".tag-input")[0];
var tagValuesElem = $(tagboxElem).find(".tag-values")[0];
if (!tag)
tag = $(tagInputElem).val();
if (!tag) return;
var value_tags = [];
try {
value_tags = JSON.parse($(tagValuesElem).val());
} catch {}
if (!Array.isArray(value_tags)) value_tags = [];
if (value_tags.includes(tag)) return;
$('<span class="badge bg-primary tag-badge btn-badge"><i class="bi bi-tag-fill"></i> ' + escapeHtml(tag) + '</span>').insertBefore('.tag-input').click(function(){
var del_tag = unescapeHtml($(this).text());
var del_tags = [];
try {
del_tags = JSON.parse($(tagValuesElem).val());
} catch {}
if (Array.isArray(del_tags)){
del_tags.splice(del_tags.indexOf(del_tag), 1);
$(tagValuesElem).val(JSON.stringify(del_tags));
}
$(this).remove();
});
value_tags.push(tag);
$(tagValuesElem).val(JSON.stringify(value_tags));
$(tagInputElem).val('');
}

View File

@ -53,237 +53,426 @@ jQuery(function($){
$("#show_rspamd_global_filters").click(function() { $("#show_rspamd_global_filters").click(function() {
$.get("inc/ajax/show_rspamd_global_filters.php"); $.get("inc/ajax/show_rspamd_global_filters.php");
$("#confirm_show_rspamd_global_filters").hide(); $("#confirm_show_rspamd_global_filters").hide();
$("#rspamd_global_filters").removeClass("hidden"); $("#rspamd_global_filters").removeClass("d-none");
}); });
$("#super_delete").click(function() { return confirm(lang.queue_ays); }); $("#super_delete").click(function() { return confirm(lang.queue_ays); });
$(".refresh_table").on('click', function(e) { $(".refresh_table").on('click', function(e) {
e.preventDefault(); e.preventDefault();
var table_name = $(this).data('table'); var table_name = $(this).data('table');
$('#' + table_name).find("tr.footable-empty").remove(); $('#' + table_name).DataTable().ajax.reload();
draw_table = $(this).data('draw');
eval(draw_table + '()');
}); });
function table_admin_ready(ft, name) {
heading = ft.$el.parents('.panel').find('.panel-heading')
var ft_paging = ft.use(FooTable.Paging)
$(heading).children('.table-lines').text(function(){
return ft_paging.totalRows;
})
}
function draw_domain_admins() { function draw_domain_admins() {
ft_domainadmins = FooTable.init('#domainadminstable', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#domainadminstable') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, $('#domainadminstable').DataTable().columns.adjust().responsive.recalc();
{"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}}, return;
{"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"}, }
{"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}}, $('#domainadminstable').DataTable({
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} processing: true,
], serverSide: false,
"rows": $.ajax({ language: lang_datatables,
dataType: 'json', ajax: {
url: '/api/v1/get/domain-admin/all', type: "GET",
jsonp: false, url: "/api/v1/get/domain-admin/all",
error: function () { dataSrc: function(data){
console.log('Cannot draw domain admin table');
},
success: function (data) {
return process_table_data(data, 'domainadminstable'); return process_table_data(data, 'domainadminstable');
} }
}), },
"empty": lang.empty, columns: [
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, {
"state": {"enabled": true}, // placeholder, so checkbox will not block child row toggle
"filtering": {"enabled": true,"delay": 1200,"position": "left","connectors": false,"placeholder": lang.filter_table}, title: '',
"sorting": {"enabled": true}, data: null,
"toggleSelector": "table tbody span.footable-toggle" searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: lang.username,
data: 'username',
defaultContent: ''
},
{
title: lang.admin_domains,
data: 'selected_domains',
defaultContent: '',
},
{
title: "TFA",
data: 'tfa_active',
defaultContent: '',
render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>'
}
},
{
title: lang.active,
data: 'active',
defaultContent: '',
render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>'
}
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
],
initComplete: function(settings, json){
}
}); });
} }
function draw_oauth2_clients() { function draw_oauth2_clients() {
ft_oauth2clientstable = FooTable.init('#oauth2clientstable', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#oauth2clientstable') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, $('#oauth2clientstable').DataTable().columns.adjust().responsive.recalc();
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, return;
{"name":"client_id","type":"text","title":lang.oauth2_client_id,"style":{"width":"200px"}}, }
{"name":"client_secret","title":lang.oauth2_client_secret,"breakpoints":"xs sm md","style":{"width":"200px"}},
{"name":"redirect_uri","title":lang.oauth2_redirect_uri, "type": "text"}, $('#oauth2clientstable').DataTable({
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} processing: true,
], serverSide: false,
"rows": $.ajax({ language: lang_datatables,
dataType: 'json', ajax: {
url: '/api/v1/get/oauth2-client/all', type: "GET",
jsonp: false, url: "/api/v1/get/oauth2-client/all",
error: function () { dataSrc: function(data){
console.log('Cannot draw oauth2 clients table');
},
success: function (data) {
return process_table_data(data, 'oauth2clientstable'); return process_table_data(data, 'oauth2clientstable');
} }
}), },
"empty": lang.empty, columns: [
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, {
"sorting": {"enabled": true}, // placeholder, so checkbox will not block child row toggle
"toggleSelector": "table tbody span.footable-toggle" title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'id',
defaultContent: ''
},
{
title: lang.oauth2_client_id,
data: 'client_id',
defaultContent: ''
},
{
title: lang.oauth2_client_secret,
data: 'client_secret',
defaultContent: ''
},
{
title: lang.oauth2_redirect_uri,
data: 'redirect_uri',
defaultContent: ''
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
]
}); });
} }
function draw_admins() { function draw_admins() {
ft_admins = FooTable.init('#adminstable', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#adminstable') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, $('#adminstable').DataTable().columns.adjust().responsive.recalc();
{"sorted": true,"name":"usr","title":lang.username,"style":{"width":"250px"}}, return;
{"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}}, }
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} $('#adminstable').DataTable({
], processing: true,
"rows": $.ajax({ serverSide: false,
dataType: 'json', language: lang_datatables,
url: '/api/v1/get/admin/all', ajax: {
jsonp: false, type: "GET",
error: function () { url: "/api/v1/get/admin/all",
console.log('Cannot draw admin table'); dataSrc: function(data){
},
success: function (data) {
return process_table_data(data, 'adminstable'); return process_table_data(data, 'adminstable');
} }
}), },
"empty": lang.empty, columns: [
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, {
"filtering": {"enabled": false}, // placeholder, so checkbox will not block child row toggle
"state": {"enabled": true}, title: '',
"sorting": {"enabled": true}, data: null,
"toggleSelector": "table tbody span.footable-toggle" searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: lang.username,
data: 'username',
defaultContent: ''
},
{
title: "TFA",
data: 'tfa_active',
defaultContent: '',
render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>'
}
},
{
title: lang.active,
data: 'active',
defaultContent: '',
render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>'
}
},
{
title: lang.action,
data: 'action',
defaultContent: '',
className: 'text-md-end dt-sm-head-hidden dt-body-right'
},
]
}); });
} }
function draw_fwd_hosts() { function draw_fwd_hosts() {
ft_forwardinghoststable = FooTable.init('#forwardinghoststable', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#forwardinghoststable') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, $('#forwardinghoststable').DataTable().columns.adjust().responsive.recalc();
{"name":"host","type":"text","title":lang.host,"style":{"width":"250px"}}, return;
{"name":"source","title":lang.source,"breakpoints":"xs sm"}, }
{"name":"keep_spam","title":lang.spamfilter, "type": "text","style":{"maxWidth":"80px","width":"80px"},"formatter": function(value){return 'yes'==value?'<i class="bi bi-x-lg"></i>':'no'==value&&'<i class="bi bi-check-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} $('#forwardinghoststable').DataTable({
], processing: true,
"rows": $.ajax({ serverSide: false,
dataType: 'json', language: lang_datatables,
url: '/api/v1/get/fwdhost/all', ajax: {
jsonp: false, type: "GET",
error: function () { url: "/api/v1/get/fwdhost/all",
console.log('Cannot draw forwarding hosts table'); dataSrc: function(data){
},
success: function (data) {
return process_table_data(data, 'forwardinghoststable'); return process_table_data(data, 'forwardinghoststable');
} }
}), },
"empty": lang.empty, columns: [
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, {
"sorting": {"enabled": true}, // placeholder, so checkbox will not block child row toggle
"toggleSelector": "table tbody span.footable-toggle" title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: lang.host,
data: 'host',
defaultContent: ''
},
{
title: lang.source,
data: 'source',
defaultContent: ''
},
{
title: lang.spamfilter,
data: 'keep_spam',
defaultContent: ''
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
]
}); });
} }
function draw_relayhosts() { function draw_relayhosts() {
ft_relayhoststable = FooTable.init('#relayhoststable', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#relayhoststable') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, $('#relayhoststable').DataTable().columns.adjust().responsive.recalc();
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, return;
{"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}}, }
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"in_use_by","title":lang.in_use_by,"style":{"min-width":"200px","width":"200px"}, "type": "text","breakpoints":"xs sm"}, $('#relayhoststable').DataTable({
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}}, processing: true,
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} serverSide: false,
], language: lang_datatables,
"rows": $.ajax({ ajax: {
dataType: 'json', type: "GET",
url: '/api/v1/get/relayhost/all', url: "/api/v1/get/relayhost/all",
jsonp: false, dataSrc: function(data){
error: function () {
console.log('Cannot draw forwarding hosts table');
},
success: function (data) {
return process_table_data(data, 'relayhoststable'); return process_table_data(data, 'relayhoststable');
} }
}), },
"empty": lang.empty, columns: [
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, {
"sorting": {"enabled": true}, // placeholder, so checkbox will not block child row toggle
"toggleSelector": "table tbody span.footable-toggle" title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'id',
defaultContent: ''
},
{
title: lang.host,
data: 'hostname',
defaultContent: ''
},
{
title: lang.username,
data: 'username',
defaultContent: ''
},
{
title: lang.in_use_by,
data: 'in_use_by',
defaultContent: ''
},
{
title: lang.active,
data: 'active',
defaultContent: '',
render: function (data, type) {
if(data == 1) return '<i class="bi bi-check-lg"></i>';
else return '<i class="bi bi-x-lg"></i>'
}
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
]
}); });
} }
function draw_transport_maps() { function draw_transport_maps() {
ft_transportstable = FooTable.init('#transportstable', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#transportstable') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, $('#transportstable').DataTable().columns.adjust().responsive.recalc();
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}}, return;
{"name":"destination","type":"text","title":lang.destination,"style":{"min-width":"300px","width":"300px"}}, }
{"name":"nexthop","type":"text","title":lang.nexthop,"style":{"min-width":"200px","width":"200px"}},
{"name":"username","title":lang.username,"breakpoints":"xs sm"}, $('#transportstable').DataTable({
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}}, processing: true,
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} serverSide: false,
], language: lang_datatables,
"rows": $.ajax({ ajax: {
dataType: 'json', type: "GET",
url: '/api/v1/get/transport/all', url: "/api/v1/get/transport/all",
jsonp: false, dataSrc: function(data){
error: function () {
console.log('Cannot draw transports table');
},
success: function (data) {
return process_table_data(data, 'transportstable'); return process_table_data(data, 'transportstable');
} }
}), },
"empty": lang.empty, columns: [
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, {
"sorting": {"enabled": true}, // placeholder, so checkbox will not block child row toggle
"toggleSelector": "table tbody span.footable-toggle", title: '',
"on": { data: null,
"ready.ft.table": function(e, ft){ searchable: false,
$('.mx-info').tooltip(); orderable: false,
} defaultContent: ''
} },
}); {
} title: '',
function draw_queue() { data: 'chkbox',
ft_queuetable = FooTable.init('#queuetable', { searchable: false,
"columns": [ orderable: false,
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, defaultContent: ''
{"name":"queue_id","type":"text","title":"QID","style":{"width":"50px"}}, },
{"name":"queue_name","type":"text","title":"Queue","style":{"width":"120px"}}, {
{"name":"arrival_time","sorted": true,"direction": "DESC","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});},"title":lang.arrival_time,"style":{"width":"170px"}}, title: 'ID',
{"name":"message_size","style":{"whiteSpace":"nowrap"},"title":lang.message_size,"formatter": function(value){ data: 'id',
return humanFileSize(value); defaultContent: ''
}}, },
{"name":"sender","title":lang.sender, "type": "text","breakpoints":"xs sm"}, {
{"name":"recipients","title":lang.recipients, "type": "text","style":{"word-break":"break-all","min-width":"300px"},"breakpoints":"xs sm md"}, title: lang.destination,
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} data: 'destination',
], defaultContent: ''
"rows": $.ajax({ },
dataType: 'json', {
url: '/api/v1/get/mailq/all', title: lang.nexthop,
jsonp: false, data: 'nexthop',
error: function () { defaultContent: ''
console.log('Cannot draw forwarding hosts table'); },
}, {
success: function (data) { title: lang.username,
return process_table_data(data, 'queuetable'); data: 'username',
} defaultContent: ''
}), },
"empty": lang.empty, {
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size}, title: lang.active,
"sorting": {"enabled": true}, data: 'active',
"toggleSelector": "table tbody span.footable-toggle", defaultContent: '',
"on": { render: function (data, type) {
"ready.ft.table": function(e, ft){ if(data == 1) return '<i class="bi bi-check-lg"></i>';
table_admin_ready(ft, 'queuetable'); else return '<i class="bi bi-x-lg"></i>'
} }
} },
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
]
}); });
} }
function process_table_data(data, table) { function process_table_data(data, table) {
if (table == 'relayhoststable') { if (table == 'relayhoststable') {
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="sender-dependent" class="btn btn-xs btn-xs-third btn-default"><i class="bi bi-caret-right-fill"></i> Test</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-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-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</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-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>' +
'</div>'; '</div>';
if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; } if (item.used_by_mailboxes == '') { item.in_use_by = item.used_by_domains; }
@ -294,14 +483,14 @@ jQuery(function($){
} else if (table == 'transportstable') { } else if (table == 'transportstable') {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (item.is_mx_based) { if (item.is_mx_based) {
item.destination = '<i class="bi bi-info-circle-fill text-info mx-info" data-toggle="tooltip" title="' + lang.is_mx_based + '"></i> <code>' + item.destination + '</code>'; item.destination = '<i class="bi bi-info-circle-fill text-info mx-info" data-bs-toggle="tooltip" title="' + lang.is_mx_based + '"></i> <code>' + item.destination + '</code>';
} }
if (item.username) { if (item.username) {
item.username = '<i style="color:#' + intToRGB(hashCode(item.nexthop)) + ';" class="bi bi-square-fill"></i> ' + item.username; item.username = '<i style="color:#' + intToRGB(hashCode(item.nexthop)) + ';" class="bi bi-square-fill"></i> ' + item.username;
} }
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="transport-map" class="btn btn-xs btn-xs-third btn-default"><i class="bi bi-caret-right-fill"></i> Test</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-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-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</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-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>' +
'</div>'; '</div>';
item.chkbox = '<input type="checkbox" data-id="transports" name="multi_select" value="' + item.id + '" />'; item.chkbox = '<input type="checkbox" data-id="transports" name="multi_select" value="' + item.id + '" />';
@ -313,21 +502,21 @@ jQuery(function($){
return escapeHtml(i); return escapeHtml(i);
}); });
item.recipients = rcpts.join('<hr style="margin:1px!important">'); item.recipients = rcpts.join('<hr style="margin:1px!important">');
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" data-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-default">' + lang.queue_show_message + '</a>' + '<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.queue_show_message + '</a>' +
'</div>'; '</div>';
}); });
} else if (table == 'forwardinghoststable') { } else if (table == 'forwardinghoststable') {
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.action = '<div class="btn-group footable-actions">' + 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-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
'</div>'; '</div>';
item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />'; item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />';
}); });
} else if (table == 'oauth2clientstable') { } else if (table == 'oauth2clientstable') {
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="/edit.php?oauth2client=' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' + '<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="#" 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>' +
'</div>'; '</div>';
item.scope = "profile"; item.scope = "profile";
@ -339,8 +528,8 @@ jQuery(function($){
item.selected_domains = escapeHtml(item.selected_domains); item.selected_domains = escapeHtml(item.selected_domains);
item.selected_domains = item.selected_domains.toString().replace(/,/g, "<br>"); 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" data-id="domain_admins" name="multi_select" value="' + item.username + '" />';
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-third btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' + '<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="#" 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="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-xs-third btn-success"><i class="bi bi-person-fill"></i> Login</a>' +
'</div>'; '</div>';
@ -353,22 +542,38 @@ jQuery(function($){
item.usr = item.username; item.usr = item.username;
} }
item.chkbox = '<input type="checkbox" data-id="admins" name="multi_select" value="' + item.username + '" />'; item.chkbox = '<input type="checkbox" data-id="admins" name="multi_select" value="' + item.username + '" />';
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="/edit/admin/' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-half btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' + '<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="#" 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>' +
'</div>'; '</div>';
}); });
} }
return data return data
}; };
// Initial table drawings
draw_domain_admins(); // detect element visibility changes
draw_admins(); function onVisible(element, callback) {
draw_fwd_hosts(); $(document).ready(function() {
draw_relayhosts(); element_object = document.querySelector(element);
draw_oauth2_clients(); if (element_object === null) return;
draw_transport_maps();
draw_queue(); new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if(entry.intersectionRatio > 0) {
callback(element_object);
}
});
}).observe(element_object);
});
}
// Draw Table if tab is active
onVisible("[id^=adminstable]", () => draw_admins());
onVisible("[id^=domainadminstable]", () => draw_domain_admins());
onVisible("[id^=oauth2clientstable]", () => draw_oauth2_clients());
onVisible("[id^=forwardinghoststable]", () => draw_fwd_hosts());
onVisible("[id^=relayhoststable]", () => draw_relayhosts());
onVisible("[id^=transportstable]", () => draw_transport_maps());
$('body').on('click', 'span.footable-toggle', function () { $('body').on('click', 'span.footable-toggle', function () {
event.stopPropagation(); event.stopPropagation();
@ -427,27 +632,11 @@ jQuery(function($){
$('#transport_type').val(button.data('transport-type')); $('#transport_type').val(button.data('transport-type'));
} }
}) })
// Queue item
$('#showQueuedMsg').on('show.bs.modal', function (e) {
$('#queue_msg_content').text(lang.loading);
button = $(e.relatedTarget)
if (button != null) {
$('#queue_id').text(button.data('queue-id'));
}
$.ajax({
type: 'GET',
url: '/api/v1/get/postcat/' + button.data('queue-id'),
dataType: 'text',
complete: function (data) {
$('#queue_msg_content').text(data.responseText);
}
});
})
$('#test_transport').on('click', function (e) { $('#test_transport').on('click', function (e) {
e.preventDefault(); e.preventDefault();
prev = $('#test_transport').text(); prev = $('#test_transport').text();
$(this).prop("disabled",true); $(this).prop("disabled",true);
$(this).html('<i class="bi bi-arrow-repeat icon-spin"></i> '); $(this).html('<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div> ');
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: 'inc/ajax/transport_check.php', url: 'inc/ajax/transport_check.php',
@ -481,13 +670,13 @@ jQuery(function($){
function add_table_row(table_id, type) { function add_table_row(table_id, type) {
var row = $('<tr />'); var row = $('<tr />');
if (type == "app_link") { if (type == "app_link") {
cols = '<td><input class="input-sm input-xs-lg form-control" data-id="app_links" type="text" name="app" required></td>'; cols = '<td><input class="input-sm input-xs-lg form-control" data-id="app_links" type="text" name="app" required></td>';
cols += '<td><input class="input-sm input-xs-lg form-control" data-id="app_links" type="text" name="href" required></td>'; cols += '<td><input class="input-sm input-xs-lg form-control" data-id="app_links" type="text" name="href" required></td>';
cols += '<td><a href="#" role="button" class="btn btn-sm btn-xs-lg btn-default" type="button">' + lang.remove_row + '</a></td>'; cols += '<td><a href="#" role="button" class="btn btn-sm btn-xs-lg btn-secondary h-100 w-100" type="button">' + lang.remove_row + '</a></td>';
} else if (type == "f2b_regex") { } else if (type == "f2b_regex") {
cols = '<td><input style="text-align:center" class="input-sm input-xs-lg form-control" data-id="f2b_regex" type="text" value="+" disabled></td>'; cols = '<td><input style="text-align:center" class="input-sm input-xs-lg form-control" data-id="f2b_regex" type="text" value="+" disabled></td>';
cols += '<td><input class="input-sm input-xs-lg form-control regex-input" data-id="f2b_regex" type="text" name="regex" required></td>'; cols += '<td><input class="input-sm input-xs-lg form-control regex-input" data-id="f2b_regex" type="text" name="regex" required></td>';
cols += '<td><a href="#" role="button" class="btn btn-sm btn-xs-lg btn-default" type="button">' + lang.remove_row + '</a></td>'; cols += '<td><a href="#" role="button" class="btn btn-sm btn-xs-lg btn-secondary h-100 w-100" type="button">' + lang.remove_row + '</a></td>';
} }
row.append(cols); row.append(cols);
table_id.append(row); table_id.append(row);
@ -507,4 +696,3 @@ jQuery(function($){
add_table_row($('#f2b_regex_table'), "f2b_regex"); add_table_row($('#f2b_regex_table'), "f2b_regex");
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -57,6 +57,17 @@ $(document).ready(function() {
$("#multiple_bookings_custom").bind("change keypress keyup blur", function() { $("#multiple_bookings_custom").bind("change keypress keyup blur", function() {
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
}); });
// load tags
if ($('#tags').length){
var tagsEl = $('#tags').parent().find('.tag-values')[0];
console.log($(tagsEl).val())
var tags = JSON.parse($(tagsEl).val());
$(tagsEl).val("");
for (var i = 0; i < tags.length; i++)
addTag($('#tags'), tags[i]);
}
}); });
jQuery(function($){ jQuery(function($){
@ -66,22 +77,14 @@ jQuery(function($){
return re.test(email); return re.test(email);
} }
function draw_wl_policy_domain_table() { function draw_wl_policy_domain_table() {
ft_wl_policy_mailbox_table = FooTable.init('#wl_policy_domain_table', { $('#wl_policy_domain_table').DataTable({
"columns": [ processing: true,
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, serverSide: false,
{"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false}, language: lang_datatables,
{"sorted": true,"name":"value","title":lang_user.spamfilter_table_rule}, ajax: {
{"name":"object","title":"Scope"} type: "GET",
],
"empty": lang_user.empty,
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/policy_wl_domain/' + table_for_domain, url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
jsonp: false, dataSrc: function(data){
error: function () {
console.log('Cannot draw mailbox policy wl table');
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (!validateEmail(item.object)) { 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" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
@ -90,35 +93,53 @@ jQuery(function($){
item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />'; item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
} }
}); });
return data;
} }
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
}, },
"sorting": { columns: [
"enabled": true {
} // placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang_user.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title: 'Scope',
data: 'object',
defaultContent: ''
}
]
}); });
} }
function draw_bl_policy_domain_table() { function draw_bl_policy_domain_table() {
ft_bl_policy_mailbox_table = FooTable.init('#bl_policy_domain_table', { $('#bl_policy_domain_table').DataTable({
"columns": [ processing: true,
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"}, serverSide: false,
{"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false}, language: lang_datatables,
{"sorted": true,"name":"value","title":lang_user.spamfilter_table_rule}, ajax: {
{"name":"object","title":"Scope"} type: "GET",
],
"empty": lang_user.empty,
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/policy_bl_domain/' + table_for_domain, url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
jsonp: false, dataSrc: function(data){
error: function () {
console.log('Cannot draw mailbox policy bl table');
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (!validateEmail(item.object)) { 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" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
@ -127,18 +148,63 @@ jQuery(function($){
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />'; item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
} }
}); });
return data;
} }
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
}, },
"sorting": { columns: [
"enabled": true {
} // placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang_user.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title: 'Scope',
data: 'object',
defaultContent: ''
}
]
}); });
} }
draw_wl_policy_domain_table();
draw_bl_policy_domain_table();
// detect element visibility changes
function onVisible(element, callback) {
$(document).ready(function() {
element_object = document.querySelector(element);
if (element_object === null) return;
new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if(entry.intersectionRatio > 0) {
callback(element_object);
observer.disconnect();
}
});
}).observe(element_object);
});
}
// Draw Table if tab is active
onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table());
onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table());
}); });

View File

@ -1,3 +1,5 @@
$(document).ready(function() { $(document).ready(function() {
var theme = localStorage.getItem("theme");
localStorage.clear(); localStorage.clear();
localStorage.setItem("theme", theme);
}); });

File diff suppressed because it is too large Load Diff

View File

@ -40,17 +40,17 @@ jQuery(function($){
if (value.score > 0) highlightClass = 'negative' if (value.score > 0) highlightClass = 'negative'
else if (value.score < 0) highlightClass = 'positive' else if (value.score < 0) highlightClass = 'positive'
else highlightClass = 'neutral' else highlightClass = 'neutral'
$('#qid_detail_symbols').append('<span data-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
}); });
$('[data-toggle="tooltip"]').tooltip() $('[data-bs-toggle="tooltip"]').tooltip()
} }
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action === "add header") { if (data.action === "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action label label-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action === "reject") { } else if (data.action === "reject") {
$('#qid_detail_score').append('<span class="label-rspamd-action label label-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
} else if (data.action === "rewrite subject") { } else if (data.action === "rewrite subject") {
$('#qid_detail_score').append('<span class="label-rspamd-action label label-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
} }
} }
if (typeof data.recipients !== 'undefined') { if (typeof data.recipients !== 'undefined') {

View File

@ -7,67 +7,20 @@ jQuery(function($){
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"}; var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
// Set paging
$('[data-page-size]').on('click', function(e){
e.preventDefault();
var new_size = $(this).data('page-size');
var parent_ul = $(this).closest('ul');
var table_id = $(parent_ul).data('table-id');
FooTable.get('#' + table_id).pageSize(new_size);
//$(this).parent().addClass('active').siblings().removeClass('active')
heading = $(this).parents('.panel').find('.panel-heading')
var n_results = $(heading).children('.table-lines').text().split(' / ')[1];
$(heading).children('.table-lines').text(function(){
if (new_size > n_results) {
new_size = n_results;
}
return new_size + ' / ' + n_results;
})
});
$(".refresh_table").on('click', function(e) { $(".refresh_table").on('click', function(e) {
e.preventDefault(); e.preventDefault();
var table_name = $(this).data('table'); var table_name = $(this).data('table');
$('#' + table_name).find("tr.footable-empty").remove(); $('#' + table_name).DataTable().ajax.reload();
draw_table = $(this).data('draw');
eval(draw_table + '()');
}); });
function table_quarantine_ready(ft, name) {
$('.refresh_table').prop("disabled", false);
heading = ft.$el.parents('.panel').find('.panel-heading')
var ft_paging = ft.use(FooTable.Paging)
$(heading).children('.table-lines').text(function(){
var total_rows = ft_paging.totalRows;
var size = ft_paging.size;
if (size > total_rows) {
size = total_rows;
}
return size + ' / ' + total_rows;
})
}
function draw_quarantine_table() { function draw_quarantine_table() {
ft_quarantinetable = FooTable.init('#quarantinetable', { $('#quarantinetable').DataTable({
"columns": [ processing: true,
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, serverSide: false,
{"name":"id","type":"ID","filterable": false,"sorted": true,"direction":"DESC","title":"ID","style":{"width":"50px"}}, language: lang_datatables,
{"name":"qid","breakpoints":"all","type":"text","title":lang.qid,"style":{"width":"125px"}}, ajax: {
{"name":"sender","title":lang.sender}, type: "GET",
{"name":"subject","title":lang.subj, "type": "text"}, url: "/api/v1/get/quarantine/all",
{"name":"rspamdaction","title":lang.rspamd_result, "type": "html"}, dataSrc: function(data){
{"name":"rcpt","title":lang.rcpt, "type": "text"},
{"name":"virus","title":lang.danger, "type": "text"},
{"name":"score","title": lang.spam_score, "type": "text"},
{"name":"notified","title":lang.notified, "type": "text"},
{"name":"created","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});},"title":lang.received,"style":{"width":"170px"}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"min-width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/quarantine/all',
jsonp: false,
error: function () {
console.log('Cannot draw quarantine table');
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (item.subject === null) { if (item.subject === null) {
item.subject = ''; item.subject = '';
@ -78,16 +31,16 @@ jQuery(function($){
item.score = '-'; item.score = '-';
} }
if (item.virus_flag > 0) { if (item.virus_flag > 0) {
item.virus = '<span class="label label-danger">' + lang.high_danger + '</span>'; item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>';
} else { } else {
item.virus = '<span class="label label-default">' + lang.neutral_danger + '</span>'; item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>';
} }
if (item.action === "reject") { if (item.action === "reject") {
item.rspamdaction = '<span class="label label-danger">' + lang.rejected + '</span>'; item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>';
} else if (item.action === "add header") { } else if (item.action === "add header") {
item.rspamdaction = '<span class="label label-warning">' + lang.junk_folder + '</span>'; item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>';
} else if (item.action === "rewrite subject") { } else if (item.action === "rewrite subject") {
item.rspamdaction = '<span class="label label-warning">' + lang.rewrite_subject + '</span>'; item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>';
} }
if(item.notified > 0) { if(item.notified > 0) {
item.notified = '&#10004;'; item.notified = '&#10004;';
@ -95,7 +48,7 @@ jQuery(function($){
item.notified = '&#10006;'; item.notified = '&#10006;';
} }
if (acl_data.login_as === 1) { if (acl_data.login_as === 1) {
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' + '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' +
'<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" 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="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
'</div>'; '</div>';
@ -107,25 +60,87 @@ jQuery(function($){
} }
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />'; item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
}); });
}
}), return data;
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": pagination_size},
"state": {"enabled": true},
"sorting": {"enabled": true},
"filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
"toggleSelector": "table tbody span.footable-toggle",
"on": {
"destroy.ft.table": function(e, ft){
$('.refresh_table').attr('disabled', 'true');
},
"ready.ft.table": function(e, ft){
table_quarantine_ready(ft, 'quarantinetable');
},
"after.ft.filtering": function(e, ft){
table_quarantine_ready(ft, 'quarantinetable');
} }
}, },
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'id',
defaultContent: ''
},
{
title: lang.qid,
data: 'qid',
defaultContent: ''
},
{
title: lang.sender,
data: 'sender',
defaultContent: ''
},
{
title: lang.subj,
data: 'sender',
defaultContent: ''
},
{
title: lang.rspamd_result,
data: 'rspamdaction',
defaultContent: ''
},
{
title: lang.rcpt,
data: 'rcpt',
defaultContent: ''
},
{
title: lang.danger,
data: 'virus',
defaultContent: ''
},
{
title: lang.spam_score,
data: 'score',
defaultContent: ''
},
{
title: lang.notified,
data: 'notified',
defaultContent: ''
},
{
title: lang.received,
data: 'created',
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"});
}
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
]
}); });
} }
@ -175,9 +190,9 @@ jQuery(function($){
if (value.score > 0) highlightClass = 'negative' if (value.score > 0) highlightClass = 'negative'
else if (value.score < 0) highlightClass = 'positive' else if (value.score < 0) highlightClass = 'positive'
else highlightClass = 'neutral' else highlightClass = 'neutral'
$('#qid_detail_symbols').append('<span data-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>'); $('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
}); });
$('[data-toggle="tooltip"]').tooltip() $('[data-bs-toggle="tooltip"]').tooltip()
} }
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) { $.each(data.fuzzy_hashes, function (index, value) {
@ -188,11 +203,11 @@ jQuery(function($){
} }
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action == "add header") { if (data.action == "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action label label-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action == "reject") { } else if (data.action == "reject") {
$('#qid_detail_score').append('<span class="label-rspamd-action label label-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
} else if (data.action == "rewrite subject") { } else if (data.action == "rewrite subject") {
$('#qid_detail_score').append('<span class="label-rspamd-action label label-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
} }
} }
if (typeof data.recipients !== 'undefined') { if (typeof data.recipients !== 'undefined') {

123
data/web/js/site/queue.js Normal file
View File

@ -0,0 +1,123 @@
jQuery(function($){
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
$('#' + table_name).DataTable().ajax.reload();
});
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
// Queue item
$('#showQueuedMsg').on('show.bs.modal', function (e) {
$('#queue_msg_content').text(lang.loading);
button = $(e.relatedTarget)
if (button != null) {
$('#queue_id').text(button.data('queue-id'));
}
$.ajax({
type: 'GET',
url: '/api/v1/get/postcat/' + button.data('queue-id'),
dataType: 'text',
complete: function (data) {
console.log(data);
$('#queue_msg_content').text(data.responseText);
}
});
})
function draw_queue() {
// just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#queuetable') ) {
$('#queuetable').DataTable().columns.adjust().responsive.recalc();
return;
}
$('#queuetable').DataTable({
processing: true,
serverSide: false,
language: lang_datatables,
ajax: {
type: "GET",
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 + '" />';
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>' +
'</div>';
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'QID',
data: 'queue_id',
defaultContent: ''
},
{
title: 'Queue',
data: 'queue_name',
defaultContent: ''
},
{
title: lang_admin.arrival_time,
data: 'arrival_time',
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"});
}
},
{
title: lang_admin.message_size,
data: 'message_size',
defaultContent: '',
render: function (data, type){
return humanFileSize(data);
}
},
{
title: lang_admin.sender,
data: 'sender',
defaultContent: ''
},
{
title: lang_admin.recipients,
data: 'recipients',
defaultContent: ''
},
{
title: lang_admin.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
]
});
}
draw_queue();
})

View File

@ -101,7 +101,7 @@ jQuery(function($){
$.each(data.sasl, function (i, item) { $.each(data.sasl, function (i, item) {
var datetime = new Date(item.datetime.replace(/-/g, "/")); var datetime = new Date(item.datetime.replace(/-/g, "/"));
var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
var service = '<div class="label label-default">' + item.service.toUpperCase() + '</div>'; var service = '<div class="badge fs-6 bg-secondary">' + item.service.toUpperCase() + '</div>';
var app_password = item.app_password ? ' <a href="/edit/app-passwd/' + item.app_password + '"><i class="bi bi-app-indicator"></i> ' + escapeHtml(item.app_password_name || "App") + '</a>' : ''; var app_password = item.app_password ? ' <a href="/edit/app-passwd/' + item.app_password + '"><i class="bi bi-app-indicator"></i> ' + escapeHtml(item.app_password_name || "App") + '</a>' : '';
var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.he.net/ip/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>"; var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.he.net/ip/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>";
var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : ''; var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : '';
@ -128,26 +128,24 @@ jQuery(function($){
} }
function draw_tla_table() { function draw_tla_table() {
ft_tla_table = FooTable.init('#tla_table', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#tla_table') ) {
{"name":"chkbox","title":"","style":{"min-width":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, $('#tla_table').DataTable().columns.adjust().responsive.recalc();
{"name":"address","title":lang.alias}, return;
{"name":"validity","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});},"title":lang.alias_valid_until,"style":{"width":"170px"}}, }
{"sorted": true,"sortValue": function(value){res = new Date(value);return res.getTime();},"direction":"DESC","name":"created","formatter":function date_format(datetime) { var date = new Date(datetime.replace(/-/g, "/")); return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});},"title":lang.created_on,"style":{"width":"170px"}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} $('#tla_table').DataTable({
], processing: true,
"empty": lang.empty, serverSide: false,
"rows": $.ajax({ language: lang_datatables,
dataType: 'json', ajax: {
url: '/api/v1/get/time_limited_aliases', type: "GET",
jsonp: false, url: "/api/v1/get/time_limited_aliases",
error: function () { dataSrc: function(data){
console.log('Cannot draw tla table'); console.log(data);
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (acl_data.spam_alias === 1) { if (acl_data.spam_alias === 1) {
item.action = '<div class="btn-group footable-actions">' + 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>' + '<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>'; '</div>';
item.chkbox = '<input type="checkbox" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />'; item.chkbox = '<input type="checkbox" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
@ -158,49 +156,77 @@ jQuery(function($){
item.action = '<span>-</span>'; item.action = '<span>-</span>';
} }
}); });
return data;
} }
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
}, },
"state": {"enabled": true}, columns: [
"sorting": { {
"enabled": true // placeholder, so checkbox will not block child row toggle
}, title: '',
"toggleSelector": "table tbody span.footable-toggle" data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: lang.alias,
data: 'address',
defaultContent: ''
},
{
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"});
}
},
{
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"});
}
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
}
]
}); });
} }
function draw_sync_job_table() { function draw_sync_job_table() {
ft_syncjob_table = FooTable.init('#sync_job_table', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#sync_job_table') ) {
{"name":"chkbox","title":"","style":{"min-width":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, $('#sync_job_table').DataTable().columns.adjust().responsive.recalc();
{"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, return;
{"name":"server_w_port","title":"Server","breakpoints":"xs sm md","style":{"word-break":"break-all"}}, }
{"name":"enc1","title":lang.encryption,"breakpoints":"all"},
{"name":"user1","title":lang.username}, $('#sync_job_table').DataTable({
{"name":"exclude","title":lang.excludes,"breakpoints":"all"}, processing: true,
{"name":"mins_interval","title":lang.interval + " (min)","breakpoints":"all"}, serverSide: false,
{"name":"last_run","title":lang.last_run,"breakpoints":"xs sm md"}, language: lang_datatables,
{"name":"exit_status","filterable": false,"title":lang.syncjob_last_run_result}, ajax: {
{"name":"log","title":"Log"}, type: "GET",
{"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"260px","width":"260px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"empty": lang.empty,
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log', url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
jsonp: false, dataSrc: function(data){
error: function () { console.log(data);
console.log('Cannot draw sync job table');
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.user1 = escapeHtml(item.user1); item.user1 = escapeHtml(item.user1);
item.log = '<a href="#syncjobLogModal" data-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>' item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
if (!item.exclude > 0) { if (!item.exclude > 0) {
item.exclude = '-'; item.exclude = '-';
} else { } else {
@ -208,8 +234,8 @@ jQuery(function($){
} }
item.server_w_port = escapeHtml(item.user1 + '@' + item.host1 + ':' + item.port1); item.server_w_port = escapeHtml(item.user1 + '@' + item.host1 + ':' + item.port1);
if (acl_data.syncjobs === 1) { if (acl_data.syncjobs === 1) {
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="/edit/syncjob/' + item.id + '" class="btn btn-xs btn-xs-half btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' + '<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>' + '<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>'; '</div>';
item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />'; item.chkbox = '<input type="checkbox" data-id="syncjob" name="multi_select" value="' + item.id + '" />';
@ -219,9 +245,9 @@ jQuery(function($){
item.chkbox = '<input type="checkbox" disabled />'; item.chkbox = '<input type="checkbox" disabled />';
} }
if (item.is_running == 1) { if (item.is_running == 1) {
item.is_running = '<span id="active-script" class="label label-success">' + lang.running + '</span>'; item.is_running = '<span id="active-script" class="badge fs-6 bg-success">' + lang.running + '</span>';
} else { } else {
item.is_running = '<span id="inactive-script" class="label label-warning">' + lang.waiting + '</span>'; item.is_running = '<span id="inactive-script" class="badge fs-6 bg-warning">' + lang.waiting + '</span>';
} }
if (!item.last_run > 0) { if (!item.last_run > 0) {
item.last_run = lang.waiting; item.last_run = lang.waiting;
@ -239,39 +265,115 @@ jQuery(function($){
} }
item.exit_status = item.success + ' ' + item.exit_status; item.exit_status = item.success + ' ' + item.exit_status;
}); });
return data;
} }
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
}, },
"state": {"enabled": true}, columns: [
"sorting": { {
"enabled": true // placeholder, so checkbox will not block child row toggle
}, title: '',
"toggleSelector": "table tbody span.footable-toggle" data: null,
searchable: false,
orderable: false,
defaultContent: '',
responsivePriority: 1
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: '',
responsivePriority: 2
},
{
title: 'ID',
data: 'id',
defaultContent: '',
responsivePriority: 3
},
{
title: 'Server',
data: 'server_w_port',
defaultContent: ''
},
{
title: lang.username,
data: 'user1',
defaultContent: '',
responsivePriority: 3
},
{
title: lang.last_run,
data: 'last_run',
defaultContent: ''
},
{
title: lang.syncjob_last_run_result,
data: 'exit_status',
defaultContent: ''
},
{
title: 'Log',
data: 'log',
defaultContent: ''
},
{
title: lang.active,
data: 'active',
defaultContent: '',
render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'
}
},
{
title: lang.status,
data: 'is_running',
defaultContent: '',
responsivePriority: 5
},
{
title: lang.encryption,
data: 'enc1',
defaultContent: ''
},
{
title: lang.excludes,
data: 'exclude',
defaultContent: ''
},
{
title: lang.interval + " (min)",
data: 'mins_interval',
defaultContent: ''
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: '',
responsivePriority: 5
}
]
}); });
} }
function draw_app_passwd_table() { function draw_app_passwd_table() {
ft_apppasswd_table = FooTable.init('#app_passwd_table', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#app_passwd_table') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, $('#app_passwd_table').DataTable().columns.adjust().responsive.recalc();
{"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, return;
{"name":"name","title":lang.app_name}, }
{"name":"protocols","title":lang.allowed_protocols},
{"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}}, $('#app_passwd_table').DataTable({
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} processing: true,
], serverSide: false,
"empty": lang.empty, language: lang_datatables,
"rows": $.ajax({ ajax: {
dataType: 'json', type: "GET",
url: '/api/v1/get/app-passwd/all', url: '/api/v1/get/app-passwd/all',
jsonp: false, dataSrc: function(data){
error: function () { console.log(data);
console.log('Cannot draw app passwd table');
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
item.name = escapeHtml(item.name) item.name = escapeHtml(item.name)
item.protocols = [] item.protocols = []
@ -283,8 +385,8 @@ jQuery(function($){
if (item.sieve_access == 1) { item.protocols.push("<code>Sieve</code>"); } if (item.sieve_access == 1) { item.protocols.push("<code>Sieve</code>"); }
item.protocols = item.protocols.join(" ") item.protocols = item.protocols.join(" ")
if (acl_data.app_passwds === 1) { if (acl_data.app_passwds === 1) {
item.action = '<div class="btn-group footable-actions">' + item.action = '<div class="btn-group">' +
'<a href="/edit/app-passwd/' + item.id + '" class="btn btn-xs btn-xs-half btn-default"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' + '<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>' + '<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>'; '</div>';
item.chkbox = '<input type="checkbox" data-id="apppasswd" name="multi_select" value="' + item.id + '" />'; item.chkbox = '<input type="checkbox" data-id="apppasswd" name="multi_select" value="' + item.id + '" />';
@ -294,37 +396,74 @@ jQuery(function($){
item.chkbox = '<input type="checkbox" disabled />'; item.chkbox = '<input type="checkbox" disabled />';
} }
}); });
return data;
} }
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
}, },
"state": {"enabled": true}, columns: [
"sorting": { {
"enabled": true // placeholder, so checkbox will not block child row toggle
}, title: '',
"toggleSelector": "table tbody span.footable-toggle" data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'id',
defaultContent: ''
},
{
title: lang.app_name,
data: 'name',
defaultContent: ''
},
{
title: lang.allowed_protocols,
data: 'protocols',
defaultContent: ''
},
{
title: lang.active,
data: 'active',
defaultContent: '',
render: function (data, type) {
return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>'
}
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
}
]
}); });
} }
function draw_wl_policy_mailbox_table() { function draw_wl_policy_mailbox_table() {
ft_wl_policy_mailbox_table = FooTable.init('#wl_policy_mailbox_table', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#wl_policy_mailbox_table') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, $('#wl_policy_mailbox_table').DataTable().columns.adjust().responsive.recalc();
{"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false}, return;
{"sorted": true,"name":"value","title":lang.spamfilter_table_rule}, }
{"name":"object","title":"Scope"}
], $('#wl_policy_mailbox_table').DataTable({
"empty": lang.empty, processing: true,
"rows": $.ajax({ serverSide: false,
dataType: 'json', language: lang_datatables,
ajax: {
type: "GET",
url: '/api/v1/get/policy_wl_mailbox', url: '/api/v1/get/policy_wl_mailbox',
jsonp: false, dataSrc: function(data){
error: function () { console.log(data);
console.log('Cannot draw mailbox policy wl table');
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (validateEmail(item.object)) { 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" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
@ -336,36 +475,60 @@ jQuery(function($){
item.chkbox = '<input type="checkbox" disabled />'; item.chkbox = '<input type="checkbox" disabled />';
} }
}); });
return data;
} }
}),
"state": {"enabled": true},
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
}, },
"sorting": { columns: [
"enabled": true {
} // placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title:'Scope',
data: 'object',
defaultContent: ''
}
]
}); });
} }
function draw_bl_policy_mailbox_table() { function draw_bl_policy_mailbox_table() {
ft_bl_policy_mailbox_table = FooTable.init('#bl_policy_mailbox_table', { // just recalc width if instance already exists
"columns": [ if ($.fn.DataTable.isDataTable('#bl_policy_mailbox_table') ) {
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, $('#bl_policy_mailbox_table').DataTable().columns.adjust().responsive.recalc();
{"name":"prefid","style":{"maxWidth":"40px","width":"40px"},"title":"ID","filterable": false,"sortable": false}, return;
{"sorted": true,"name":"value","title":lang.spamfilter_table_rule}, }
{"name":"object","title":"Scope"}
], $('#bl_policy_mailbox_table').DataTable({
"empty": lang.empty, processing: true,
"rows": $.ajax({ serverSide: false,
dataType: 'json', language: lang_datatables,
ajax: {
type: "GET",
url: '/api/v1/get/policy_bl_mailbox', url: '/api/v1/get/policy_bl_mailbox',
jsonp: false, dataSrc: function(data){
error: function () { console.log(data);
console.log('Cannot draw mailbox policy bl table');
},
success: function (data) {
$.each(data, function (i, item) { $.each(data, function (i, item) {
if (validateEmail(item.object)) { 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" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />';
@ -377,31 +540,45 @@ jQuery(function($){
item.chkbox = '<input type="checkbox" disabled />'; item.chkbox = '<input type="checkbox" disabled />';
} }
}); });
return data;
} }
}),
"paging": {
"enabled": true,
"limit": 5,
"size": pagination_size
}, },
"state": {"enabled": true}, columns: [
"sorting": { {
"enabled": true // placeholder, so checkbox will not block child row toggle
} title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title:'Scope',
data: 'object',
defaultContent: ''
}
]
}); });
} }
$('body').on('click', 'span.footable-toggle', function () {
event.stopPropagation();
})
draw_sync_job_table();
draw_app_passwd_table();
draw_tla_table();
draw_wl_policy_mailbox_table();
draw_bl_policy_mailbox_table();
last_logins('get');
// FIDO2 friendly name modal // FIDO2 friendly name modal
$('#fido2ChangeFn').on('show.bs.modal', function (e) { $('#fido2ChangeFn').on('show.bs.modal', function (e) {
rename_link = $(e.relatedTarget) rename_link = $(e.relatedTarget)
@ -433,4 +610,28 @@ jQuery(function($){
$('#userFilterModal').on('hidden.bs.modal', function () { $('#userFilterModal').on('hidden.bs.modal', function () {
$('#user_sieve_filter').text(lang.loading); $('#user_sieve_filter').text(lang.loading);
}); });
// detect element visibility changes
function onVisible(element, callback) {
$(document).ready(function() {
element_object = document.querySelector(element);
if (element_object === null) return;
new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if(entry.intersectionRatio > 0) {
callback(element_object);
}
});
}).observe(element_object);
});
}
// Load only if the tab is visible
onVisible("[id^=tla_table]", () => draw_tla_table());
onVisible("[id^=bl_policy_mailbox_table]", () => draw_bl_policy_mailbox_table());
onVisible("[id^=wl_policy_mailbox_table]", () => draw_wl_policy_mailbox_table());
onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
onVisible("[id^=app_passwd_table]", () => draw_app_passwd_table());
last_logins('get');
}); });

View File

@ -230,13 +230,27 @@ if (isset($_GET['query'])) {
process_add_return(rsettings('add', $attr)); process_add_return(rsettings('add', $attr));
break; break;
case "mailbox": case "mailbox":
process_add_return(mailbox('add', 'mailbox', $attr)); switch ($object) {
case "template":
process_add_return(mailbox('add', 'mailbox_templates', $attr));
break;
default:
process_add_return(mailbox('add', 'mailbox', $attr));
break;
}
break; break;
case "oauth2-client": case "oauth2-client":
process_add_return(oauth2('add', 'client', $attr)); process_add_return(oauth2('add', 'client', $attr));
break; break;
case "domain": case "domain":
process_add_return(mailbox('add', 'domain', $attr)); switch ($object) {
case "template":
process_add_return(mailbox('add', 'domain_templates', $attr));
break;
default:
process_add_return(mailbox('add', 'domain', $attr));
break;
}
break; break;
case "resource": case "resource":
process_add_return(mailbox('add', 'resource', $attr)); process_add_return(mailbox('add', 'resource', $attr));
@ -519,7 +533,16 @@ if (isset($_GET['query'])) {
echo '{}'; echo '{}';
} }
break; break;
case "template":
switch ($extra){
case "all":
process_get_return(mailbox('get', 'domain_templates'));
break;
default:
process_get_return(mailbox('get', 'domain_templates', $extra));
break;
}
break;
default: default:
$data = mailbox('get', 'domain_details', $object); $data = mailbox('get', 'domain_details', $object);
process_get_return($data); process_get_return($data);
@ -992,7 +1015,16 @@ if (isset($_GET['query'])) {
echo '{}'; echo '{}';
} }
break; break;
case "template":
switch ($extra){
case "all":
process_get_return(mailbox('get', 'mailbox_templates'));
break;
default:
process_get_return(mailbox('get', 'mailbox_templates', $extra));
break;
}
break;
default: default:
$tags = null; $tags = null;
if (isset($_GET['tags']) && $_GET['tags'] != '') if (isset($_GET['tags']) && $_GET['tags'] != '')
@ -1472,6 +1504,10 @@ if (isset($_GET['query'])) {
} }
echo json_encode($temp, JSON_UNESCAPED_SLASHES); echo json_encode($temp, JSON_UNESCAPED_SLASHES);
break; break;
case "container":
$container_stats = docker('container_stats', $extra);
echo json_encode($container_stats);
break;
case "vmail": case "vmail":
$exec_fields_vmail = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail'); $exec_fields_vmail = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
$vmail_df = explode(',', json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields_vmail), true)); $vmail_df = explode(',', json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields_vmail), true));
@ -1483,29 +1519,53 @@ if (isset($_GET['query'])) {
'used_percent' => $vmail_df[4] 'used_percent' => $vmail_df[4]
); );
echo json_encode($temp, JSON_UNESCAPED_SLASHES); echo json_encode($temp, JSON_UNESCAPED_SLASHES);
break; break;
case "solr": case "solr":
$solr_status = solr_status(); $solr_status = solr_status();
$solr_size = ($solr_status['status']['dovecot-fts']['index']['size']); $solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
$solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']); $solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
if (strtolower(getenv('SKIP_SOLR')) != 'n') { if (strtolower(getenv('SKIP_SOLR')) != 'n') {
$solr_enabled = false; $solr_enabled = false;
} }
else { else {
$solr_enabled = true; $solr_enabled = true;
} }
echo json_encode(array( echo json_encode(array(
'type' => 'info', 'type' => 'info',
'solr_enabled' => $solr_enabled, 'solr_enabled' => $solr_enabled,
'solr_size' => $solr_size, 'solr_size' => $solr_size,
'solr_documents' => $solr_documents 'solr_documents' => $solr_documents
)); ));
break; break;
case "version": case "host":
echo json_encode(array( if (!$extra){
'version' => $GLOBALS['MAILCOW_GIT_VERSION'] $stats = docker("host_stats");
)); echo json_encode($stats);
break; }
else if ($extra == "ip") {
// get public ips
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
$ipv4 = curl_exec($curl);
curl_setopt($curl, CURLOPT_URL, 'http://ipv6.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
$ipv6 = curl_exec($curl);
$ips = array(
"ipv4" => $ipv4,
"ipv6" => $ipv6
);
curl_close($curl);
echo json_encode($ips);
}
break;
case "version":
echo json_encode(array(
'version' => $GLOBALS['MAILCOW_GIT_VERSION']
));
break;
} }
} }
break; break;
@ -1613,6 +1673,9 @@ if (isset($_GET['query'])) {
case "tag": case "tag":
process_delete_return(mailbox('delete', 'tags_domain', array('tags' => $items, 'domain' => $extra))); process_delete_return(mailbox('delete', 'tags_domain', array('tags' => $items, 'domain' => $extra)));
break; break;
case "template":
process_delete_return(mailbox('delete', 'domain_templates', array('ids' => $items)));
break;
default: default:
process_delete_return(mailbox('delete', 'domain', array('domain' => $items))); process_delete_return(mailbox('delete', 'domain', array('domain' => $items)));
} }
@ -1625,6 +1688,9 @@ if (isset($_GET['query'])) {
case "tag": case "tag":
process_delete_return(mailbox('delete', 'tags_mailbox', array('tags' => $items, 'username' => $extra))); process_delete_return(mailbox('delete', 'tags_mailbox', array('tags' => $items, 'username' => $extra)));
break; break;
case "template":
process_delete_return(mailbox('delete', 'mailbox_templates', array('ids' => $items)));
break;
default: default:
process_delete_return(mailbox('delete', 'mailbox', array('username' => $items))); process_delete_return(mailbox('delete', 'mailbox', array('username' => $items)));
} }
@ -1786,7 +1852,14 @@ if (isset($_GET['query'])) {
process_edit_return(mailbox('edit', 'time_limited_alias', array_merge(array('address' => $items), $attr))); process_edit_return(mailbox('edit', 'time_limited_alias', array_merge(array('address' => $items), $attr)));
break; break;
case "mailbox": case "mailbox":
process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr))); switch ($object) {
case "template":
process_edit_return(mailbox('edit', 'mailbox_templates', array_merge(array('ids' => $items), $attr)));
break;
default:
process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr)));
break;
}
break; break;
case "syncjob": case "syncjob":
process_edit_return(mailbox('edit', 'syncjob', array_merge(array('id' => $items), $attr))); process_edit_return(mailbox('edit', 'syncjob', array_merge(array('id' => $items), $attr)));
@ -1798,7 +1871,14 @@ if (isset($_GET['query'])) {
process_edit_return(mailbox('edit', 'resource', array_merge(array('name' => $items), $attr))); process_edit_return(mailbox('edit', 'resource', array_merge(array('name' => $items), $attr)));
break; break;
case "domain": case "domain":
process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr))); switch ($object) {
case "template":
process_edit_return(mailbox('edit', 'domain_templates', array_merge(array('ids' => $items), $attr)));
break;
default:
process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr)));
break;
}
break; break;
case "rl-domain": case "rl-domain":
process_edit_return(ratelimit('edit', 'domain', array_merge(array('object' => $items), $attr))); process_edit_return(ratelimit('edit', 'domain', array_merge(array('object' => $items), $attr)));

View File

@ -269,8 +269,8 @@
"header": { "header": {
"administration": "Administració", "administration": "Administració",
"debug": "Debug", "debug": "Debug",
"mailboxes": "Bústies", "email": "E-Mail",
"mailcow_settings": "Configuració", "mailcow_config": "Configuració",
"quarantine": "Quarantena", "quarantine": "Quarantena",
"restart_sogo": "Reiniciar SOGo", "restart_sogo": "Reiniciar SOGo",
"user_settings": "Preferències d'usuari" "user_settings": "Preferències d'usuari"
@ -316,6 +316,7 @@
"bcc_type": "BCC type", "bcc_type": "BCC type",
"deactivate": "Desactivar", "deactivate": "Desactivar",
"description": "Descripció", "description": "Descripció",
"dkim_key_length": "Mida de la clau DKIM (bits)",
"domain": "Domini", "domain": "Domini",
"domain_admins": "Administradores de dominio", "domain_admins": "Administradores de dominio",
"domain_aliases": "Àlies de domini", "domain_aliases": "Àlies de domini",
@ -327,6 +328,7 @@
"filter_table": "Filtrar taula", "filter_table": "Filtrar taula",
"filters": "Filtres", "filters": "Filtres",
"fname": "Nom complert", "fname": "Nom complert",
"force_pw_update": "Forçar l'actualització de la contrassenya al proper login",
"in_use": "En ús (%)", "in_use": "En ús (%)",
"inactive": "Inactiu", "inactive": "Inactiu",
"kind": "Tipus", "kind": "Tipus",
@ -334,6 +336,9 @@
"last_run_reset": "Executar a continuació", "last_run_reset": "Executar a continuació",
"mailbox_quota": "Mida màx. de quota", "mailbox_quota": "Mida màx. de quota",
"mailboxes": "Bústies", "mailboxes": "Bústies",
"max_aliases": "Màx. àlies possibles",
"max_mailboxes": "Màx. bústies possibles",
"max_quota": "Màx. quota per bústia",
"mins_interval": "Intèrval (min)", "mins_interval": "Intèrval (min)",
"msg_num": "Missatge #", "msg_num": "Missatge #",
"multiple_bookings": "Múltiples reserves", "multiple_bookings": "Múltiples reserves",
@ -346,6 +351,7 @@
"recipient_map_new": "Nou destinatari", "recipient_map_new": "Nou destinatari",
"recipient_map_old": "Destinatari original", "recipient_map_old": "Destinatari original",
"recipient_maps": "Recipient maps", "recipient_maps": "Recipient maps",
"relay_all": "Retransmetre tods els recipients",
"remove": "Esborrar", "remove": "Esborrar",
"resources": "Recursos", "resources": "Recursos",
"running": "Executant-se", "running": "Executant-se",
@ -386,6 +392,10 @@
"text_plain_content": "Contingut (text/plain)", "text_plain_content": "Contingut (text/plain)",
"toggle_all": "Marcar tots" "toggle_all": "Marcar tots"
}, },
"queue": {
"queue_command_success": "Queue command completed successfully",
"queue_manager": "Queue Manager"
},
"start": { "start": {
"help": "Mostrar/Ocultar panell d'ajuda", "help": "Mostrar/Ocultar panell d'ajuda",
"mailcow_apps_detail": "Tria una aplicació (de moment només SOGo) per a accedir als teus correus, calendari, contactes i més.", "mailcow_apps_detail": "Tria una aplicació (de moment només SOGo) per a accedir als teus correus, calendari, contactes i més.",

View File

@ -87,7 +87,7 @@
"relay_all": "Předávání všech příjemců", "relay_all": "Předávání všech příjemců",
"relay_all_info": "<small>Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.</small>", "relay_all_info": "<small>Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.</small>",
"relay_domain": "Předávání domény", "relay_domain": "Předávání domény",
"relay_transport_info": "<div class=\"label label-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.",
"relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.", "relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.",
"relayhost_wrapped_tls_info": "<b>Nepoužívejte</b> prosím porty s aktivním protokolem TLS (většinou port 465).<br>\r\nPoužívejte porty bez TLS a pak pošlete příkaz STARTTLS. Pravidlo k vynucení užití TLS lze vytvořit pomocí mapy TLS pravidel.", "relayhost_wrapped_tls_info": "<b>Nepoužívejte</b> prosím porty s aktivním protokolem TLS (většinou port 465).<br>\r\nPoužívejte porty bez TLS a pak pošlete příkaz STARTTLS. Pravidlo k vynucení užití TLS lze vytvořit pomocí mapy TLS pravidel.",
"select": "Prosím vyberte...", "select": "Prosím vyberte...",
@ -150,7 +150,6 @@
"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.", "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.",
"customer_id": "ID zákazníka", "customer_id": "ID zákazníka",
"customize": "Přizpůsobení", "customize": "Přizpůsobení",
"delete_queue": "Smazat vše",
"destination": "Cíl", "destination": "Cíl",
"dkim_add_key": "Přidat ARC/DKIM klíč", "dkim_add_key": "Přidat ARC/DKIM klíč",
"dkim_domains_selector": "Selektor", "dkim_domains_selector": "Selektor",
@ -187,7 +186,6 @@
"f2b_retry_window": "Časový horizont pro maximum pokusů (s)", "f2b_retry_window": "Časový horizont pro maximum pokusů (s)",
"f2b_whitelist": "Sítě/hostitelé na whitelistu", "f2b_whitelist": "Sítě/hostitelé na whitelistu",
"filter_table": "Tabulka filtrů", "filter_table": "Tabulka filtrů",
"flush_queue": "Vyprázdnit frontu (opětovně doručit)",
"forwarding_hosts": "Předávající servery", "forwarding_hosts": "Předávající servery",
"forwarding_hosts_add_hint": "Lze zadat IPv4/IPv6 adresy, sítě ve formátu CIDR, názvy serverů (budou převedeny na IP adresy) nebo názvy domén (budou převedeny na IP pomocí SPF záznamů, příp. MX záznamů).", "forwarding_hosts_add_hint": "Lze zadat IPv4/IPv6 adresy, sítě ve formátu CIDR, názvy serverů (budou převedeny na IP adresy) nebo názvy domén (budou převedeny na IP pomocí SPF záznamů, příp. MX záznamů).",
"forwarding_hosts_hint": "Příchozí zprávy od zde uvedených serverů jsou bezpodmínečně přijaty. U těchto serverů se nekontroluje DNSBL a nepoužije greylisting. Spam od těchto serverů se nikdy neodmítá, ale občas může skončit ve složce se spamem. Nejčastěji se zde uvádějí mailové servery, jež předávají příchozí e-maily na tento mailový server.", "forwarding_hosts_hint": "Příchozí zprávy od zde uvedených serverů jsou bezpodmínečně přijaty. U těchto serverů se nekontroluje DNSBL a nepoužije greylisting. Spam od těchto serverů se nikdy neodmítá, ale občas může skončit ve složce se spamem. Nejčastěji se zde uvádějí mailové servery, jež předávají příchozí e-maily na tento mailový server.",
@ -257,13 +255,6 @@
"quarantine_release_format_att": "Jako příloha", "quarantine_release_format_att": "Jako příloha",
"quarantine_release_format_raw": "Nezměněný originál", "quarantine_release_format_raw": "Nezměněný originál",
"quarantine_retention_size": "Počet zadržených zpráv na mailovou schránku<br />0 znamená <b>neaktivní</b>.", "quarantine_retention_size": "Počet zadržených zpráv na mailovou schránku<br />0 znamená <b>neaktivní</b>.",
"queue_ays": "Potvrďte prosím, že chcete odstranit všechny položky z aktuální fronty.",
"queue_deliver_mail": "Doručit",
"queue_hold_mail": "Zadržet",
"queue_manager": "Správce fronty",
"queue_show_message": "Zobrazit zprávu",
"queue_unban": "odblokovat",
"queue_unhold_mail": "Propustit",
"quota_notification_html": "Šablona upozornění:<br><small>Ponechte prázdné, aby se obnovila výchozí šablona.</small>", "quota_notification_html": "Šablona upozornění:<br><small>Ponechte prázdné, aby se obnovila výchozí šablona.</small>",
"quota_notification_sender": "Odesílatel upozornění", "quota_notification_sender": "Odesílatel upozornění",
"quota_notification_subject": "Předmět upozornění", "quota_notification_subject": "Předmět upozornění",
@ -516,6 +507,7 @@
"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.", "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.",
"client_id": "ID klienta", "client_id": "ID klienta",
"client_secret": "Tajný klíč klienta", "client_secret": "Tajný klíč klienta",
"created_on": "Vytvoreno",
"comment_info": "Soukromý komentář se nezobrazí uživateli; veřejný komentář se zobrazí jako nápověda při zastavení se kurzorem v přehledu uživatelů", "comment_info": "Soukromý komentář se nezobrazí uživateli; veřejný komentář se zobrazí jako nápověda při zastavení se kurzorem v přehledu uživatelů",
"delete1": "Odstranit ze zdrojové schránky, po dokončení přenosu", "delete1": "Odstranit ze zdrojové schránky, po dokončení přenosu",
"delete2": "Odstranit zprávy v cílové schránce, pokud nejsou ve zdrojové", "delete2": "Odstranit zprávy v cílové schránce, pokud nejsou ve zdrojové",
@ -543,6 +535,7 @@
"hostname": "Jméno hostitele", "hostname": "Jméno hostitele",
"inactive": "Neaktivní", "inactive": "Neaktivní",
"kind": "Druh", "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": "Úprava mailové schránky",
"mailbox_quota_def": "Výchozí kvóta schránky", "mailbox_quota_def": "Výchozí kvóta schránky",
@ -579,7 +572,7 @@
"relay_all": "Předávání všech příjemců", "relay_all": "Předávání všech příjemců",
"relay_all_info": "<small>Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.</small>", "relay_all_info": "<small>Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.</small>",
"relay_domain": "Předávání domény", "relay_domain": "Předávání domény",
"relay_transport_info": "<div class=\"label label-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.",
"relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.", "relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.",
"relayhost": "Předávání podle odesílatele", "relayhost": "Předávání podle odesílatele",
"remove": "Smazat", "remove": "Smazat",
@ -587,7 +580,7 @@
"save": "Uložit změny", "save": "Uložit změny",
"scope": "Rozsah", "scope": "Rozsah",
"sender_acl": "Povolit odesílání jako", "sender_acl": "Povolit odesílání jako",
"sender_acl_disabled": "<span class=\"label label-danger\">Kontrola odesílatele vypnuta</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Kontrola odesílatele vypnuta</span>",
"sender_acl_info": "Má-li uživatel schránky A dovoleno odesílat jako uživatel schránky B, nezobrazuje se adresa odesílatele B v seznamu \"Od\" v SOGo automaticky.<br>\r\n Uživatel schránky A musí nejdříve v SOGo vytvořit pověření, jež umožní uživateli B vybrat svou adresu jako odesílatele. Tento mechanismus neplatí pro aliasy.", "sender_acl_info": "Má-li uživatel schránky A dovoleno odesílat jako uživatel schránky B, nezobrazuje se adresa odesílatele B v seznamu \"Od\" v SOGo automaticky.<br>\r\n Uživatel schránky A musí nejdříve v SOGo vytvořit pověření, jež umožní uživateli B vybrat svou adresu jako odesílatele. Tento mechanismus neplatí pro aliasy.",
"sieve_desc": "Krátký popis", "sieve_desc": "Krátký popis",
"sieve_type": "Typ filtru", "sieve_type": "Typ filtru",
@ -643,8 +636,8 @@
"administration": "Hlavní nastavení", "administration": "Hlavní nastavení",
"apps": "Aplikace", "apps": "Aplikace",
"debug": "Systémové informace", "debug": "Systémové informace",
"mailboxes": "Nastavení mailů", "email": "E-Mail",
"mailcow_settings": "Nastavení", "mailcow_config": "Nastavení",
"quarantine": "Karanténa", "quarantine": "Karanténa",
"restart_netfilter": "Restartovat netfilter", "restart_netfilter": "Restartovat netfilter",
"restart_sogo": "Restartovat SOGo", "restart_sogo": "Restartovat SOGo",
@ -710,15 +703,19 @@
"booking_ltnull": "Neomezeno, ale po rezervaci se ukazuje jako obsazené", "booking_ltnull": "Neomezeno, ale po rezervaci se ukazuje jako obsazené",
"booking_lt0_short": "Volný limit", "booking_lt0_short": "Volný limit",
"catch_all": "Doménový koš", "catch_all": "Doménový koš",
"created_on": "Vytvoreno",
"daily": "Každý den", "daily": "Každý den",
"deactivate": "Vypnout", "deactivate": "Vypnout",
"description": "Popis", "description": "Popis",
"disable_login": "Zakázat přihlášení (ale stále přijímat poštu)", "disable_login": "Zakázat přihlášení (ale stále přijímat poštu)",
"disable_x": "Vypnout", "disable_x": "Vypnout",
"dkim_domains_selector": "Selektor",
"dkim_key_length": "Délka DKIM klíče (v bitech)",
"domain": "Doména", "domain": "Doména",
"domain_admins": "Správci domén", "domain_admins": "Správci domén",
"domain_aliases": "Doménové aliasy", "domain_aliases": "Doménové aliasy",
"domain_quota": "Kvóta", "domain_quota": "Kvóta",
"domain_quota_total": "Celková kvóta domény",
"domains": "Domény", "domains": "Domény",
"edit": "Upravit", "edit": "Upravit",
"empty": "Žádné výsledky", "empty": "Žádné výsledky",
@ -727,6 +724,8 @@
"filter_table": "Filtrovat tabulku", "filter_table": "Filtrovat tabulku",
"filters": "Filtry", "filters": "Filtry",
"fname": "Celé jméno", "fname": "Celé jméno",
"force_pw_update": "Vynutit změnu hesla při příštím přihlášení",
"gal": "Globální seznam adres",
"goto_ham": "Učit se jako <b>ham</b>", "goto_ham": "Učit se jako <b>ham</b>",
"goto_spam": "Učit se jako <b>spam</b>", "goto_spam": "Učit se jako <b>spam</b>",
"hourly": "Každou hodinu", "hourly": "Každou hodinu",
@ -735,6 +734,7 @@
"insert_preset": "Vložit ukázkovou položku \"%s\"", "insert_preset": "Vložit ukázkovou položku \"%s\"",
"kind": "Druh", "kind": "Druh",
"last_mail_login": "Poslední přihlášení", "last_mail_login": "Poslední přihlášení",
"last_modified": "Naposledy změněn",
"last_pw_change": "Naposledy změněno heslo", "last_pw_change": "Naposledy změněno heslo",
"last_run": "Naposledy spuštěno", "last_run": "Naposledy spuštěno",
"last_run_reset": "Znovu naplánovat", "last_run_reset": "Znovu naplánovat",
@ -744,6 +744,9 @@
"mailbox_defquota": "Výchozí velikost schránky", "mailbox_defquota": "Výchozí velikost schránky",
"mailbox_quota": "Max. velikost schránky", "mailbox_quota": "Max. velikost schránky",
"mailboxes": "Mailové schránky", "mailboxes": "Mailové schránky",
"max_aliases": "Max. počet aliasů",
"max_mailboxes": "Max. počet mailových schránek",
"max_quota": "Max. kvóta mailové schránky",
"mins_interval": "Interval (min)", "mins_interval": "Interval (min)",
"msg_num": "Počet zpráv", "msg_num": "Počet zpráv",
"multiple_bookings": "Vícenásobné rezervace", "multiple_bookings": "Vícenásobné rezervace",
@ -769,6 +772,7 @@
"recipient_map_old": "Původní příjemce", "recipient_map_old": "Původní příjemce",
"recipient_map_old_info": "Původní příjemce musí být platná emailová adresa nebo název domény.", "recipient_map_old_info": "Původní příjemce musí být platná emailová adresa nebo název domény.",
"recipient_maps": "Mapy příjemců", "recipient_maps": "Mapy příjemců",
"relay_all": "Předávání všech příjemců",
"remove": "Smazat", "remove": "Smazat",
"resources": "Zdroje", "resources": "Zdroje",
"running": "Běží", "running": "Běží",
@ -884,6 +888,10 @@
"toggle_all": "Označit vše", "toggle_all": "Označit vše",
"type": "Typ" "type": "Typ"
}, },
"queue": {
"queue_deliver_mail": "Doručit",
"queue_manager": "Správce fronty"
},
"ratelimit": { "ratelimit": {
"disabled": "Vypnuto", "disabled": "Vypnuto",
"second": "zpráv za sekundu", "second": "zpráv za sekundu",
@ -1097,7 +1105,7 @@
"running": "Běží", "running": "Běží",
"save": "Uložit změny", "save": "Uložit změny",
"save_changes": "Uložit změny", "save_changes": "Uložit změny",
"sender_acl_disabled": "<span class=\"label label-danger\">Kontrola odesílatele vypnuta</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Kontrola odesílatele vypnuta</span>",
"shared_aliases": "Sdílené aliasy", "shared_aliases": "Sdílené aliasy",
"shared_aliases_desc": "Na sdílené aliasy se neuplatňuje uživatelské nastavení jako filtr spamu nebo pravidla šifrování. Nastavení filtrování spamu může provádět jen správce pro celou doménu.", "shared_aliases_desc": "Na sdílené aliasy se neuplatňuje uživatelské nastavení jako filtr spamu nebo pravidla šifrování. Nastavení filtrování spamu může provádět jen správce pro celou doménu.",
"show_sieve_filters": "Zobrazit aktivní sieve filtr uživatele", "show_sieve_filters": "Zobrazit aktivní sieve filtr uživatele",

View File

@ -83,7 +83,7 @@
"relay_all": "Send alle modtagere videre", "relay_all": "Send alle modtagere videre",
"relay_all_info": "↪ Hvis du vælger <b> ikke </b> at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.", "relay_all_info": "↪ Hvis du vælger <b> ikke </b> at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.",
"relay_domain": "Send dette domæne videre", "relay_domain": "Send dette domæne videre",
"relay_transport_info": "<div class=\"label label-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.",
"relay_unknown_only": "Videresend kun ikke-eksisterende postkasser. Eksisterende postkasser leveres lokalt.", "relay_unknown_only": "Videresend kun ikke-eksisterende postkasser. Eksisterende postkasser leveres lokalt.",
"relayhost_wrapped_tls_info": "Vær sød <b>ikke</b> at bruge TLS-indpakkede porte (bruges mest på port 465) .<br>\r\nBrug en ikke-pakket port, og udgiv STARTTLS. En TLS-politik til at håndhæve TLS kan oprettes i \"TLS policy maps\".", "relayhost_wrapped_tls_info": "Vær sød <b>ikke</b> at bruge TLS-indpakkede porte (bruges mest på port 465) .<br>\r\nBrug en ikke-pakket port, og udgiv STARTTLS. En TLS-politik til at håndhæve TLS kan oprettes i \"TLS policy maps\".",
"select": "Vælg venligst...", "select": "Vælg venligst...",
@ -141,7 +141,6 @@
"credentials_transport_warning": "<b>Advarsel</b>: Tilføjelse af en ny transportkortpost opdaterer legitimationsoplysningerne for alle poster med en matchende nexthop-kolonne.", "credentials_transport_warning": "<b>Advarsel</b>: Tilføjelse af en ny transportkortpost opdaterer legitimationsoplysningerne for alle poster med en matchende nexthop-kolonne.",
"customer_id": "Kunde ID", "customer_id": "Kunde ID",
"customize": "Tilpas", "customize": "Tilpas",
"delete_queue": "Slet alt",
"destination": "Bestemmelsessted", "destination": "Bestemmelsessted",
"dkim_add_key": "Tilføj ARC/DKIM nøgle", "dkim_add_key": "Tilføj ARC/DKIM nøgle",
"dkim_domains_selector": "Vælger", "dkim_domains_selector": "Vælger",
@ -178,7 +177,6 @@
"f2b_retry_window": "Genindlæs vindue om (s) for max. forsøg", "f2b_retry_window": "Genindlæs vindue om (s) for max. forsøg",
"f2b_whitelist": "Hvidlisted netværk/vært", "f2b_whitelist": "Hvidlisted netværk/vært",
"filter_table": "Filtertabel", "filter_table": "Filtertabel",
"flush_queue": "Tøm kø",
"forwarding_hosts": "Videresendelse af værter", "forwarding_hosts": "Videresendelse af værter",
"forwarding_hosts_add_hint": "Du kan enten angive IPv4 / IPv6-adresser, netværk i CIDR-notation, værtsnavne (som løses til IP-adresser) eller domænenavne (som løses til IP-adresser ved at spørge SPF-poster eller i mangel af MX-poster).", "forwarding_hosts_add_hint": "Du kan enten angive IPv4 / IPv6-adresser, netværk i CIDR-notation, værtsnavne (som løses til IP-adresser) eller domænenavne (som løses til IP-adresser ved at spørge SPF-poster eller i mangel af MX-poster).",
"forwarding_hosts_hint": "Indgående beskeder accepteres ubetinget fra værter, der er anført her. Disse værter kontrolleres derefter ikke mod DNSBL'er eller udsættes for gråt notering. Spam modtaget fra dem afvises aldrig, men det kan eventuelt arkiveres i Junk-mappen. Den mest almindelige anvendelse til dette er at specificere mailservere, hvor du har oprettet en regel, der videresender indgående e-mails til din mailcow-server. ", "forwarding_hosts_hint": "Indgående beskeder accepteres ubetinget fra værter, der er anført her. Disse værter kontrolleres derefter ikke mod DNSBL'er eller udsættes for gråt notering. Spam modtaget fra dem afvises aldrig, men det kan eventuelt arkiveres i Junk-mappen. Den mest almindelige anvendelse til dette er at specificere mailservere, hvor du har oprettet en regel, der videresender indgående e-mails til din mailcow-server. ",
@ -237,13 +235,6 @@
"quarantine_release_format_att": "Som vedhæftet fil", "quarantine_release_format_att": "Som vedhæftet fil",
"quarantine_release_format_raw": "Umodificeret original", "quarantine_release_format_raw": "Umodificeret original",
"quarantine_retention_size": "Tilbageholdelse pr. Postkasse:<br><small>0 angiver <b>inaktiv</b>.</small>", "quarantine_retention_size": "Tilbageholdelse pr. Postkasse:<br><small>0 angiver <b>inaktiv</b>.</small>",
"queue_ays": "Bekræft venligst, at du vil slette alle emner fra den aktuelle kø.",
"queue_deliver_mail": "Aflevere",
"queue_hold_mail": "Hold",
"queue_manager": "Køadministrator",
"queue_unban": "kø ikke udeluk",
"queue_unhold_mail": "Unhold",
"queue_show_message": "Vis besked",
"quota_notification_html": "Notifikations-e-mail-skabelon:<br><small>Lad det være tomt for at gendanne standardskabelonen.</small>", "quota_notification_html": "Notifikations-e-mail-skabelon:<br><small>Lad det være tomt for at gendanne standardskabelonen.</small>",
"quota_notification_sender": "Afsender af underretnings-e-mail", "quota_notification_sender": "Afsender af underretnings-e-mail",
"quota_notification_subject": "Underretningens e-mail-emne", "quota_notification_subject": "Underretningens e-mail-emne",
@ -535,7 +526,7 @@
"relay_all": "Send alle modtagere videre", "relay_all": "Send alle modtagere videre",
"relay_all_info": "↪ Hvis du vælger <b>ikke</b> for at videresende alle modtagere skal du tilføje en (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.", "relay_all_info": "↪ Hvis du vælger <b>ikke</b> for at videresende alle modtagere skal du tilføje en (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.",
"relay_domain": "Send dette domæne videre", "relay_domain": "Send dette domæne videre",
"relay_transport_info": "<div class=\"label label-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis den ikke er indstillet, foretages der et MX-opslag.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis den ikke er indstillet, foretages der et MX-opslag.",
"relay_unknown_only": "Videresend kun ikke-eksisterende postkasser. Eksisterende postkasser leveres lokalt.", "relay_unknown_only": "Videresend kun ikke-eksisterende postkasser. Eksisterende postkasser leveres lokalt.",
"relayhost": "Afsenderafhængige transporter", "relayhost": "Afsenderafhængige transporter",
"remove": "Fjerne", "remove": "Fjerne",
@ -543,7 +534,7 @@
"save": "Gem ændringer", "save": "Gem ændringer",
"scope": "Anvendelsesområde", "scope": "Anvendelsesområde",
"sender_acl": "Tillad at sende som", "sender_acl": "Tillad at sende som",
"sender_acl_disabled": "<span class=\"label label-danger\">Afsenderkontrol er deaktiveret</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Afsenderkontrol er deaktiveret</span>",
"sender_acl_info": "Hvis postkassebruger A har tilladelse til at sende som postkassebruger B, vises afsenderadressen ikke automatisk som valgbar \"from\" felt i SOGo.<br>\r\n Postkassebruger B skal oprette en delegation i SOGo for at tillade postkassebruger A at vælge deres adresse som afsender. For at delegere en postkasse i SOGo skal du bruge menuen (tre prikker) til højre for dit postkassens navn øverst til venstre, mens du er i postvisningen. Denne adfærd gælder ikke for aliasadresser.", "sender_acl_info": "Hvis postkassebruger A har tilladelse til at sende som postkassebruger B, vises afsenderadressen ikke automatisk som valgbar \"from\" felt i SOGo.<br>\r\n Postkassebruger B skal oprette en delegation i SOGo for at tillade postkassebruger A at vælge deres adresse som afsender. For at delegere en postkasse i SOGo skal du bruge menuen (tre prikker) til højre for dit postkassens navn øverst til venstre, mens du er i postvisningen. Denne adfærd gælder ikke for aliasadresser.",
"sieve_desc": "Kort beskrivelse", "sieve_desc": "Kort beskrivelse",
"sieve_type": "Filtertype", "sieve_type": "Filtertype",
@ -581,8 +572,8 @@
"administration": "Konfiguration og detailer", "administration": "Konfiguration og detailer",
"apps": "Apps", "apps": "Apps",
"debug": "Systemoplysninger", "debug": "Systemoplysninger",
"mailboxes": "Mailopsætning", "email": "E-Mail",
"mailcow_settings": "Konfiguration", "mailcow_config": "Konfiguration",
"quarantine": "Karantæne", "quarantine": "Karantæne",
"restart_netfilter": "Genstart netfilter", "restart_netfilter": "Genstart netfilter",
"restart_sogo": "Genstart SOGo", "restart_sogo": "Genstart SOGo",
@ -650,10 +641,13 @@
"description": "Beskrivelse", "description": "Beskrivelse",
"disable_login": "Tillad ikke login (indgående mail accepteres stadig)", "disable_login": "Tillad ikke login (indgående mail accepteres stadig)",
"disable_x": "Deaktiver", "disable_x": "Deaktiver",
"dkim_domains_selector": "Vælger",
"dkim_key_length": "DKIM nøgle længde (bits)",
"domain": "Domæne", "domain": "Domæne",
"domain_admins": "Domæneadministratorer", "domain_admins": "Domæneadministratorer",
"domain_aliases": "Domænealiaser", "domain_aliases": "Domænealiaser",
"domain_quota": "Kvote", "domain_quota": "Kvote",
"domain_quota_total": "Samlet domænekvote",
"domains": "Domains", "domains": "Domains",
"edit": "Edit", "edit": "Edit",
"empty": "Ingen resultater", "empty": "Ingen resultater",
@ -662,6 +656,8 @@
"filter_table": "Filtertabel", "filter_table": "Filtertabel",
"filters": "Filtre", "filters": "Filtre",
"fname": "Fulde navn", "fname": "Fulde navn",
"force_pw_update": "Tving adgangskodeopdatering til næste login",
"gal": "Global adresseliste",
"hourly": "Hver time", "hourly": "Hver time",
"in_use": "I brug (%)", "in_use": "I brug (%)",
"inactive": "Inaktiv", "inactive": "Inaktiv",
@ -676,6 +672,9 @@
"mailboxes": "Postkasser", "mailboxes": "Postkasser",
"mailbox_defaults": "Standardindstillinger", "mailbox_defaults": "Standardindstillinger",
"mailbox_defaults_info": "Definer standardindstillinger for nye postkasser.", "mailbox_defaults_info": "Definer standardindstillinger for nye postkasser.",
"max_aliases": "Maks. mulige aliasser",
"max_mailboxes": "Maks. mulige postkasser",
"max_quota": "Maks. kvote pr. postkasse",
"mins_interval": "Interval (min)", "mins_interval": "Interval (min)",
"msg_num": "Besked #", "msg_num": "Besked #",
"multiple_bookings": "Flere bookinger", "multiple_bookings": "Flere bookinger",
@ -803,6 +802,10 @@
"text_plain_content": "Indhold (text/plain)", "text_plain_content": "Indhold (text/plain)",
"toggle_all": "Skift alt" "toggle_all": "Skift alt"
}, },
"queue": {
"queue_deliver_mail": "Aflevere",
"queue_manager": "Køadministrator"
},
"start": { "start": {
"help": "Vis / skjul hjælpepanel", "help": "Vis / skjul hjælpepanel",
"imap_smtp_server_auth_info": "Brug din fulde e-mail-adresse og PLAIN-godkendelsesmekanismen.<br>\r\nDine login-data bliver krypteret af den obligatoriske kryptering på serversiden.", "imap_smtp_server_auth_info": "Brug din fulde e-mail-adresse og PLAIN-godkendelsesmekanismen.<br>\r\nDine login-data bliver krypteret af den obligatoriske kryptering på serversiden.",
@ -1005,7 +1008,7 @@
"running": "Kører", "running": "Kører",
"save": "Gem ændring", "save": "Gem ændring",
"save_changes": "Gem ændringer", "save_changes": "Gem ændringer",
"sender_acl_disabled": "<span class=\"label label-danger\">Afsender tjek er slået fra</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Afsender tjek er slået fra</span>",
"shared_aliases": "Delte aliasadresser", "shared_aliases": "Delte aliasadresser",
"shared_aliases_desc": "Delt alias påvirkes ikke af brugerspecifikke indstillinger såsom spamfilter eller krypteringspolitik. Tilselementnde spamfiltre kan kun foretages af en administrator som en politik, der dækker hele domænet.", "shared_aliases_desc": "Delt alias påvirkes ikke af brugerspecifikke indstillinger såsom spamfilter eller krypteringspolitik. Tilselementnde spamfiltre kan kun foretages af en administrator som en politik, der dækker hele domænet.",
"show_sieve_filters": "Vis det aktive brugerfilter", "show_sieve_filters": "Vis det aktive brugerfilter",

View File

@ -88,7 +88,7 @@
"relay_all": "Alle Empfänger-Adressen relayen", "relay_all": "Alle Empfänger-Adressen relayen",
"relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellt werden.", "relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellt werden.",
"relay_domain": "Diese Domain relayen", "relay_domain": "Diese Domain relayen",
"relay_transport_info": "<div class=\"label label-info\">Info</div> Transport-Maps können erstellt werden, um individuelle Ziele für eine Relay-Domain zu definieren.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Transport-Maps können erstellt werden, um individuelle Ziele für eine Relay-Domain zu definieren.",
"relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.", "relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.",
"relayhost_wrapped_tls_info": "Bitte <b>keine</b> \"TLS-wrapped Ports\" verwenden (etwa SMTPS via Port 465/tcp).<br>\r\nDer Transport wird stattdessen STARTTLS anfordern, um TLS zu verwenden. TLS kann unter \"TLS Policy Maps\" erzwungen werden.", "relayhost_wrapped_tls_info": "Bitte <b>keine</b> \"TLS-wrapped Ports\" verwenden (etwa SMTPS via Port 465/tcp).<br>\r\nDer Transport wird stattdessen STARTTLS anfordern, um TLS zu verwenden. TLS kann unter \"TLS Policy Maps\" erzwungen werden.",
"select": "Bitte auswählen", "select": "Bitte auswählen",
@ -150,7 +150,6 @@
"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.", "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", "customer_id": "Kunde",
"customize": "UI-Anpassung", "customize": "UI-Anpassung",
"delete_queue": "Alle löschen",
"destination": "Ziel", "destination": "Ziel",
"dkim_add_key": "ARC/DKIM-Key hinzufügen", "dkim_add_key": "ARC/DKIM-Key hinzufügen",
"dkim_domains_selector": "Selector", "dkim_domains_selector": "Selector",
@ -187,7 +186,6 @@
"f2b_retry_window": "Wiederholungen im Zeitraum von (s)", "f2b_retry_window": "Wiederholungen im Zeitraum von (s)",
"f2b_whitelist": "Whitelist für Netzwerke und Hosts", "f2b_whitelist": "Whitelist für Netzwerke und Hosts",
"filter_table": "Tabelle filtern", "filter_table": "Tabelle filtern",
"flush_queue": "Flush Queue",
"forwarding_hosts": "Weiterleitungs-Hosts", "forwarding_hosts": "Weiterleitungs-Hosts",
"forwarding_hosts_add_hint": "Sie können entweder IPv4-/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.", "forwarding_hosts_add_hint": "Sie können entweder IPv4-/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.",
"forwarding_hosts_hint": "Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.", "forwarding_hosts_hint": "Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.",
@ -230,6 +228,7 @@
"oauth2_renew_secret": "Neues Client Secret generieren", "oauth2_renew_secret": "Neues Client Secret generieren",
"oauth2_revoke_tokens": "Alle Client Tokens entfernen", "oauth2_revoke_tokens": "Alle Client Tokens entfernen",
"optional": "Optional", "optional": "Optional",
"options": "Einstellungen",
"password": "Passwort", "password": "Passwort",
"password_length": "Passwortlänge", "password_length": "Passwortlänge",
"password_policy": "Passwortrichtlinie", "password_policy": "Passwortrichtlinie",
@ -255,13 +254,6 @@
"quarantine_release_format_att": "Als Anhang", "quarantine_release_format_att": "Als Anhang",
"quarantine_release_format_raw": "Unverändertes Original", "quarantine_release_format_raw": "Unverändertes Original",
"quarantine_retention_size": "Rückhaltungen pro Mailbox:<br><small>0 bedeutet <b>inaktiv</b>.</small>", "quarantine_retention_size": "Rückhaltungen pro Mailbox:<br><small>0 bedeutet <b>inaktiv</b>.</small>",
"queue_ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
"queue_deliver_mail": "Ausliefern",
"queue_hold_mail": "Zurückhalten",
"queue_manager": "Queue Manager",
"queue_show_message": "Nachricht anzeigen",
"queue_unban": "Entsperren einreihen",
"queue_unhold_mail": "Freigeben",
"quota_notification_html": "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>", "quota_notification_html": "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>",
"quota_notification_sender": "Benachrichtigungs-E-Mail Absender", "quota_notification_sender": "Benachrichtigungs-E-Mail Absender",
"quota_notification_subject": "Benachrichtigungs-E-Mail Betreff", "quota_notification_subject": "Benachrichtigungs-E-Mail Betreff",
@ -361,6 +353,7 @@
"bcc_must_be_email": "BCC-Ziel %s ist keine gültige E-Mail-Adresse", "bcc_must_be_email": "BCC-Ziel %s ist keine gültige E-Mail-Adresse",
"comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten", "comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten",
"defquota_empty": "Standard-Quota darf nicht 0 sein", "defquota_empty": "Standard-Quota darf nicht 0 sein",
"demo_mode_enabled": "Demo Mode ist aktiviert",
"description_invalid": "Ressourcenbeschreibung für %s ist ungültig", "description_invalid": "Ressourcenbeschreibung für %s ist ungültig",
"dkim_domain_or_sel_exists": "Ein DKIM-Key für die Domain \"%s\" existiert und wird nicht überschrieben", "dkim_domain_or_sel_exists": "Ein DKIM-Key für die Domain \"%s\" existiert und wird nicht überschrieben",
"dkim_domain_or_sel_invalid": "DKIM-Domain oder Selector nicht korrekt: %s", "dkim_domain_or_sel_invalid": "DKIM-Domain oder Selector nicht korrekt: %s",
@ -465,9 +458,39 @@
"value_missing": "Bitte alle Felder ausfüllen", "value_missing": "Bitte alle Felder ausfüllen",
"yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s" "yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s"
}, },
"datatables": {
"collapse_all": "Alle Einklappen",
"decimal": "",
"emptyTable": "Keine Daten in der Tabelle vorhanden",
"expand_all": "Alle Ausklappen",
"info": "_START_ bis _END_ von _TOTAL_ Einträgen",
"infoEmpty": "0 bis 0 von 0 Einträgen",
"infoFiltered": "(gefiltert von _MAX_ Einträgen)",
"infoPostFix": "",
"thousands": ".",
"lengthMenu": "_MENU_ Einträge anzeigen",
"loadingRecords": "Wird geladen...",
"processing": "Bitte warten...",
"search": "Suchen",
"zeroRecords": "Keine Einträge vorhanden.",
"paginate": {
"first": "Erste",
"previous": "Zurück",
"next": "Nächste",
"last": "Letzte"
},
"aria": {
"sortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren",
"sortDescending": ": aktivieren, um Spalte absteigend zu sortieren"
}
},
"debug": { "debug": {
"chart_this_server": "Chart (dieser Server)", "chart_this_server": "Chart (dieser Server)",
"containers_info": "Container-Information", "containers_info": "Container-Information",
"container_running": "Läuft",
"container_stopped": "Angehalten",
"cores": "Kerne",
"current_time": "Systemzeit",
"disk_usage": "Festplattennutzung", "disk_usage": "Festplattennutzung",
"docs": "Dokumente", "docs": "Dokumente",
"external_logs": "Externe Logs", "external_logs": "Externe Logs",
@ -478,6 +501,7 @@
"log_info": "<p>mailcow <b>in-memory Logs</b> werden in Redis Listen gespeichert, die maximale Anzahl der Einträge pro Anwendung richtet sich nach LOG_LINES (%d).\r\n <br>In-memory Logs sind vergänglich und nicht zur ständigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon.\r\n <br>Das in-memory Protokoll versteht sich als schnelle Übersicht zum Debugging eines Containers, für komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p>\r\n <p><b>Externe Logs</b> werden via API externer Applikationen bezogen.</p>\r\n <p><b>Statische Logs</b> sind weitestgehend Aktivitätsprotokolle, die nicht in den Docker Daemon geschrieben werden, jedoch permanent verfügbar sein müssen (ausgeschlossen API Logs).</p>", "log_info": "<p>mailcow <b>in-memory Logs</b> werden in Redis Listen gespeichert, die maximale Anzahl der Einträge pro Anwendung richtet sich nach LOG_LINES (%d).\r\n <br>In-memory Logs sind vergänglich und nicht zur ständigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon.\r\n <br>Das in-memory Protokoll versteht sich als schnelle Übersicht zum Debugging eines Containers, für komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p>\r\n <p><b>Externe Logs</b> werden via API externer Applikationen bezogen.</p>\r\n <p><b>Statische Logs</b> sind weitestgehend Aktivitätsprotokolle, die nicht in den Docker Daemon geschrieben werden, jedoch permanent verfügbar sein müssen (ausgeschlossen API Logs).</p>",
"login_time": "Zeit", "login_time": "Zeit",
"logs": "Protokolle", "logs": "Protokolle",
"memory": "Arbeitsspeicher",
"online_users": "Benutzer online", "online_users": "Benutzer online",
"restart_container": "Neustart", "restart_container": "Neustart",
"service": "Dienst", "service": "Dienst",
@ -489,7 +513,11 @@
"static_logs": "Statische Logs", "static_logs": "Statische Logs",
"success": "Erfolg", "success": "Erfolg",
"system_containers": "System & Container", "system_containers": "System & Container",
"uptime": "Uptime", "timezone": "Zeitzone",
"uptime": "Betriebszeit",
"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"
}, },
"diagnostics": { "diagnostics": {
@ -521,6 +549,7 @@
"client_id": "Client-ID", "client_id": "Client-ID",
"client_secret": "Client-Secret", "client_secret": "Client-Secret",
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.", "comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
"created_on": "Erstellt am",
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server", "delete1": "Lösche Nachricht nach Übertragung vom Quell-Server",
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind", "delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind",
"delete2duplicates": "Lösche Duplikate im Ziel", "delete2duplicates": "Lösche Duplikate im Ziel",
@ -547,6 +576,7 @@
"hostname": "Servername", "hostname": "Servername",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"kind": "Art", "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": "Mailbox bearbeiten",
"mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_quota_def": "Standard-Quota einer Mailbox",
@ -584,7 +614,7 @@
"relay_all": "Alle Empfänger-Adressen relayen", "relay_all": "Alle Empfänger-Adressen relayen",
"relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.", "relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.",
"relay_domain": "Diese Domain relayen", "relay_domain": "Diese Domain relayen",
"relay_transport_info": "<div class=\"label label-info\">Info</div> Transport Maps können erstellt werden, um individuelle Ziele für eine Relay Domain zu definieren.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Transport Maps können erstellt werden, um individuelle Ziele für eine Relay Domain zu definieren.",
"relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.", "relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.",
"relayhost": "Senderabhängige Transport Maps", "relayhost": "Senderabhängige Transport Maps",
"remove": "Entfernen", "remove": "Entfernen",
@ -592,7 +622,7 @@
"save": "Änderungen speichern", "save": "Änderungen speichern",
"scope": "Scope", "scope": "Scope",
"sender_acl": "Darf Nachrichten versenden als", "sender_acl": "Darf Nachrichten versenden als",
"sender_acl_disabled": "<span class=\"label label-danger\">Absenderprüfung deaktiviert</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Absenderprüfung deaktiviert</span>",
"sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.", "sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.",
"sieve_desc": "Kurze Beschreibung", "sieve_desc": "Kurze Beschreibung",
"sieve_type": "Filtertyp", "sieve_type": "Filtertyp",
@ -649,9 +679,9 @@
"header": { "header": {
"administration": "Server-Konfiguration", "administration": "Server-Konfiguration",
"apps": "Apps", "apps": "Apps",
"debug": "Systeminformation", "debug": "Information",
"mailboxes": "E-Mail-Setup", "email": "E-Mail",
"mailcow_settings": "Konfiguration", "mailcow_config": "Konfiguration",
"quarantine": "Quarantäne", "quarantine": "Quarantäne",
"restart_netfilter": "Netfilter neustarten", "restart_netfilter": "Netfilter neustarten",
"restart_sogo": "SOGo neustarten", "restart_sogo": "SOGo neustarten",
@ -685,6 +715,7 @@
"add_filter": "Filter erstellen", "add_filter": "Filter erstellen",
"add_mailbox": "Mailbox hinzufügen", "add_mailbox": "Mailbox hinzufügen",
"add_recipient_map_entry": "Empfängerumschreibung hinzufügen", "add_recipient_map_entry": "Empfängerumschreibung hinzufügen",
"add_template": "Vorlage hinzufügen",
"add_resource": "Ressource hinzufügen", "add_resource": "Ressource hinzufügen",
"add_tls_policy_map": "TLS-Richtlinieneintrag hinzufügen", "add_tls_policy_map": "TLS-Richtlinieneintrag hinzufügen",
"address_rewriting": "Adressumschreibung", "address_rewriting": "Adressumschreibung",
@ -715,15 +746,20 @@
"booking_custom_short": "Hartes Limit", "booking_custom_short": "Hartes Limit",
"booking_ltnull": "Unbegrenzt, jedoch anzeigen, wenn gebucht", "booking_ltnull": "Unbegrenzt, jedoch anzeigen, wenn gebucht",
"booking_lt0_short": "Weiches Limit", "booking_lt0_short": "Weiches Limit",
"created_on": "Erstellt am",
"daily": "Täglich", "daily": "Täglich",
"deactivate": "Deaktivieren", "deactivate": "Deaktivieren",
"description": "Beschreibung", "description": "Beschreibung",
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)", "disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
"disable_x": "Deaktivieren", "disable_x": "Deaktivieren",
"dkim_domains_selector": "Selector",
"dkim_key_length": "DKIM-Schlüssellänge (bits)",
"domain": "Domain", "domain": "Domain",
"domain_admins": "Domain-Administratoren", "domain_admins": "Domain-Administratoren",
"domain_aliases": "Domain-Aliasse", "domain_aliases": "Domain-Aliasse",
"domain_templates": "Domainweite Vorlagen",
"domain_quota": "Gesamtspeicher", "domain_quota": "Gesamtspeicher",
"domain_quota_total": "Domain-Speicherplatz gesamt",
"domains": "Domains", "domains": "Domains",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"empty": "Keine Einträge vorhanden", "empty": "Keine Einträge vorhanden",
@ -732,12 +768,15 @@
"filter_table": "Filtern", "filter_table": "Filtern",
"filters": "Filter", "filters": "Filter",
"fname": "Name", "fname": "Name",
"force_pw_update": "Erzwinge Passwortänderung bei nächstem Login",
"gal": "Globales Adressbuch",
"hourly": "Stündlich", "hourly": "Stündlich",
"in_use": "Prozentualer Gebrauch", "in_use": "Prozentualer Gebrauch",
"inactive": "Inaktiv", "inactive": "Inaktiv",
"insert_preset": "Beispiel \"%s\" laden", "insert_preset": "Beispiel \"%s\" laden",
"kind": "Art", "kind": "Art",
"last_mail_login": "Letzter Mail-Login", "last_mail_login": "Letzter Mail-Login",
"last_modified": "Zuletzt geändert",
"last_pw_change": "Letzte Passwortänderung", "last_pw_change": "Letzte Passwortänderung",
"last_run": "Letzte Ausführung", "last_run": "Letzte Ausführung",
"last_run_reset": "Als nächstes ausführen", "last_run_reset": "Als nächstes ausführen",
@ -745,8 +784,12 @@
"mailbox_defaults": "Standardeinstellungen", "mailbox_defaults": "Standardeinstellungen",
"mailbox_defaults_info": "Steuert die Standardeinstellungen für neue Mailboxen.", "mailbox_defaults_info": "Steuert die Standardeinstellungen für neue Mailboxen.",
"mailbox_defquota": "Standard-Quota", "mailbox_defquota": "Standard-Quota",
"mailbox_templates": "Mailboxweite Vorlagen",
"mailbox_quota": "Max. Größe einer Mailbox", "mailbox_quota": "Max. Größe einer Mailbox",
"mailboxes": "Mailboxen", "mailboxes": "Mailboxen",
"max_aliases": "Max. mögliche Aliasse",
"max_mailboxes": "Max. mögliche Mailboxen",
"max_quota": "Max. Größe per Mailbox",
"mins_interval": "Intervall (min)", "mins_interval": "Intervall (min)",
"msg_num": "Anzahl Nachrichten", "msg_num": "Anzahl Nachrichten",
"multiple_bookings": "Mehrfachbuchen", "multiple_bookings": "Mehrfachbuchen",
@ -770,6 +813,8 @@
"recipient_map_old": "Original-Empfänger", "recipient_map_old": "Original-Empfänger",
"recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.", "recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
"recipient_maps": "Empfängerumschreibungen", "recipient_maps": "Empfängerumschreibungen",
"relay_all": "Alle Empfänger-Adressen relayen",
"relay_unknown": "Unbekannte Mailboxen relayen",
"remove": "Entfernen", "remove": "Entfernen",
"resources": "Ressourcen", "resources": "Ressourcen",
"running": "In Ausführung", "running": "In Ausführung",
@ -796,6 +841,8 @@
"table_size_show_n": "Zeige %s Einträge", "table_size_show_n": "Zeige %s Einträge",
"target_address": "Ziel-Adresse", "target_address": "Ziel-Adresse",
"target_domain": "Ziel-Domain", "target_domain": "Ziel-Domain",
"templates": "Vorlagen",
"template": "Vorlage",
"tls_enforce_in": "TLS eingehend erzwingen", "tls_enforce_in": "TLS eingehend erzwingen",
"tls_enforce_out": "TLS ausgehend erzwingen", "tls_enforce_out": "TLS ausgehend erzwingen",
"tls_map_dest": "Ziel", "tls_map_dest": "Ziel",
@ -891,6 +938,22 @@
"toggle_all": "Alle auswählen", "toggle_all": "Alle auswählen",
"type": "Typ" "type": "Typ"
}, },
"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",
"legend": "Funktionen der Mailqueue Aktionen:",
"ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
"deliver_mail": "Ausliefern",
"deliver_mail_legend": "Versucht eine erneute Zustellung der ausgwählten Mails.",
"hold_mail": "Zurückhalten",
"hold_mail_legend": "Hält die ausgewählten Mails zurück. (Verhindert weitere Zustellversuche)",
"queue_manager": "Queue Manager",
"show_message": "Nachricht anzeigen",
"unban": "queue unban",
"unhold_mail": "Freigeben",
"unhold_mail_legend": "Gibt ausgewählte Mails zur Auslieferung frei. (Erfordert vorheriges Zurückhalten)"
},
"start": { "start": {
"help": "Hilfe ein-/ausblenden", "help": "Hilfe ein-/ausblenden",
"imap_smtp_server_auth_info": "Bitte verwenden Sie Ihre vollständige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.<br>\r\nIhre Anmeldedaten werden durch die obligatorische Verschlüsselung entgegen des Begriffes \"PLAIN\" nicht unverschlüsselt übertragen.", "imap_smtp_server_auth_info": "Bitte verwenden Sie Ihre vollständige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.<br>\r\nIhre Anmeldedaten werden durch die obligatorische Verschlüsselung entgegen des Begriffes \"PLAIN\" nicht unverschlüsselt übertragen.",
@ -966,6 +1029,9 @@
"saved_settings": "Regel wurde gespeichert", "saved_settings": "Regel wurde gespeichert",
"settings_map_added": "Regel wurde gespeichert", "settings_map_added": "Regel wurde gespeichert",
"settings_map_removed": "Regeln wurden entfernt: %s", "settings_map_removed": "Regeln wurden entfernt: %s",
"template_added": "Template %s hinzugefügt",
"template_modified": "Änderungen am Template %s wurden gespeichert",
"template_removed": "Template ID %s wurde gelöscht",
"sogo_profile_reset": "ActiveSync-Gerät des Benutzers %s wurde zurückgesetzt", "sogo_profile_reset": "ActiveSync-Gerät des Benutzers %s wurde zurückgesetzt",
"tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht", "tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht",
"tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert", "tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert",
@ -1103,7 +1169,7 @@
"running": "Wird ausgeführt", "running": "Wird ausgeführt",
"save": "Änderungen speichern", "save": "Änderungen speichern",
"save_changes": "Änderungen speichern", "save_changes": "Änderungen speichern",
"sender_acl_disabled": "<span class=\"label label-danger\">Absenderprüfung deaktiviert</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Absenderprüfung deaktiviert</span>",
"shared_aliases": "Geteilte Alias-Adressen", "shared_aliases": "Geteilte Alias-Adressen",
"shared_aliases_desc": "Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen, wie die des Spam-Filters oder der Verschlüsselungsrichtlinie, berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.", "shared_aliases_desc": "Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen, wie die des Spam-Filters oder der Verschlüsselungsrichtlinie, berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.",
"show_sieve_filters": "Zeige aktiven Filter des Benutzers", "show_sieve_filters": "Zeige aktiven Filter des Benutzers",

View File

@ -88,7 +88,7 @@
"relay_all": "Relay all recipients", "relay_all": "Relay all recipients",
"relay_all_info": "↪ If you choose <b>not</b> to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.", "relay_all_info": "↪ If you choose <b>not</b> to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.",
"relay_domain": "Relay this domain", "relay_domain": "Relay this domain",
"relay_transport_info": "<div class=\"label label-info\">Info</div> You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.",
"relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.", "relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.",
"relayhost_wrapped_tls_info": "Please do <b>not</b> use TLS-wrapped ports (mostly used on port 465).<br>\r\nUse any non-wrapped port and issue STARTTLS. A TLS policy to enforce TLS can be created in \"TLS policy maps\".", "relayhost_wrapped_tls_info": "Please do <b>not</b> use TLS-wrapped ports (mostly used on port 465).<br>\r\nUse any non-wrapped port and issue STARTTLS. A TLS policy to enforce TLS can be created in \"TLS policy maps\".",
"select": "Please select...", "select": "Please select...",
@ -152,7 +152,6 @@
"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.", "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", "customer_id": "Customer ID",
"customize": "Customize", "customize": "Customize",
"delete_queue": "Delete all",
"destination": "Destination", "destination": "Destination",
"dkim_add_key": "Add ARC/DKIM key", "dkim_add_key": "Add ARC/DKIM key",
"dkim_domains_selector": "Selector", "dkim_domains_selector": "Selector",
@ -189,7 +188,6 @@
"f2b_retry_window": "Retry window (s) for max. attempts", "f2b_retry_window": "Retry window (s) for max. attempts",
"f2b_whitelist": "Whitelisted networks/hosts", "f2b_whitelist": "Whitelisted networks/hosts",
"filter_table": "Filter table", "filter_table": "Filter table",
"flush_queue": "Flush queue",
"forwarding_hosts": "Forwarding Hosts", "forwarding_hosts": "Forwarding Hosts",
"forwarding_hosts_add_hint": "You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).", "forwarding_hosts_add_hint": "You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).",
"forwarding_hosts_hint": "Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.", "forwarding_hosts_hint": "Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.",
@ -234,6 +232,7 @@
"oauth2_renew_secret": "Generate new client secret", "oauth2_renew_secret": "Generate new client secret",
"oauth2_revoke_tokens": "Revoke all client tokens", "oauth2_revoke_tokens": "Revoke all client tokens",
"optional": "optional", "optional": "optional",
"options": "Options",
"password": "Password", "password": "Password",
"password_length": "Password length", "password_length": "Password length",
"password_policy": "Password policy", "password_policy": "Password policy",
@ -259,13 +258,6 @@
"quarantine_release_format_att": "As attachment", "quarantine_release_format_att": "As attachment",
"quarantine_release_format_raw": "Unmodified original", "quarantine_release_format_raw": "Unmodified original",
"quarantine_retention_size": "Retentions per mailbox:<br><small>0 indicates <b>inactive</b>.</small>", "quarantine_retention_size": "Retentions per mailbox:<br><small>0 indicates <b>inactive</b>.</small>",
"queue_ays": "Please confirm you want to delete all items from the current queue.",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_manager": "Queue manager",
"queue_show_message": "Show message",
"queue_unban": "queue unban",
"queue_unhold_mail": "Unhold",
"quota_notification_html": "Notification email template:<br><small>Leave empty to restore default template.</small>", "quota_notification_html": "Notification email template:<br><small>Leave empty to restore default template.</small>",
"quota_notification_sender": "Notification email sender", "quota_notification_sender": "Notification email sender",
"quota_notification_subject": "Notification email subject", "quota_notification_subject": "Notification email subject",
@ -361,6 +353,7 @@
"bcc_must_be_email": "BCC destination %s is not a valid email address", "bcc_must_be_email": "BCC destination %s is not a valid email address",
"comment_too_long": "Comment too long, max 160 chars allowed", "comment_too_long": "Comment too long, max 160 chars allowed",
"defquota_empty": "Default quota per mailbox must not be 0.", "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", "description_invalid": "Resource description for %s is invalid",
"dkim_domain_or_sel_exists": "A DKIM key for \"%s\" exists and will not be overwritten", "dkim_domain_or_sel_exists": "A DKIM key for \"%s\" exists and will not be overwritten",
"dkim_domain_or_sel_invalid": "DKIM domain or selector invalid: %s", "dkim_domain_or_sel_invalid": "DKIM domain or selector invalid: %s",
@ -448,6 +441,9 @@
"target_domain_invalid": "Target domain %s is invalid", "target_domain_invalid": "Target domain %s is invalid",
"targetd_not_found": "Target domain %s not found", "targetd_not_found": "Target domain %s not found",
"targetd_relay_domain": "Target domain %s is a relay domain", "targetd_relay_domain": "Target domain %s is a relay domain",
"template_exists": "Template %s already exists",
"template_id_invalid": "Template ID %s invalid",
"template_name_invalid": "Template name invalid",
"temp_error": "Temporary error", "temp_error": "Temporary error",
"text_empty": "Text must not be empty", "text_empty": "Text must not be empty",
"tfa_token_invalid": "TFA token invalid", "tfa_token_invalid": "TFA token invalid",
@ -465,9 +461,39 @@
"value_missing": "Please provide all values", "value_missing": "Please provide all values",
"yotp_verification_failed": "Yubico OTP verification failed: %s" "yotp_verification_failed": "Yubico OTP verification failed: %s"
}, },
"datatables": {
"collapse_all": "Collapse All",
"decimal": "",
"emptyTable": "No data available in table",
"expand_all": "Expand All",
"info": "Showing _START_ to _END_ of _TOTAL_ entries",
"infoEmpty": "Showing 0 to 0 of 0 entries",
"infoFiltered": "(filtered from _MAX_ total entries)",
"infoPostFix": "",
"thousands": ",",
"lengthMenu": "Show _MENU_ entries",
"loadingRecords": "Loading...",
"processing": "Please wait...",
"search": "Search:",
"zeroRecords": "No matching records found",
"paginate": {
"first": "First",
"last": "Last",
"next": "Next",
"previous": "Previous"
},
"aria": {
"sortAscending": ": activate to sort column ascending",
"sortDescending": ": activate to sort column descending"
}
},
"debug": { "debug": {
"chart_this_server": "Chart (this server)", "chart_this_server": "Chart (this server)",
"containers_info": "Container information", "containers_info": "Container information",
"container_running": "Running",
"container_stopped": "Stopped",
"cores": "Cores",
"current_time": "System Time",
"disk_usage": "Disk usage", "disk_usage": "Disk usage",
"docs": "Docs", "docs": "Docs",
"external_logs": "External logs", "external_logs": "External logs",
@ -478,6 +504,7 @@
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"login_time": "Time", "login_time": "Time",
"logs": "Logs", "logs": "Logs",
"memory": "Memory",
"online_users": "Users online", "online_users": "Users online",
"restart_container": "Restart", "restart_container": "Restart",
"service": "Service", "service": "Service",
@ -489,7 +516,11 @@
"static_logs": "Static logs", "static_logs": "Static logs",
"success": "Success", "success": "Success",
"system_containers": "System & Containers", "system_containers": "System & Containers",
"timezone": "Timezone",
"uptime": "Uptime", "uptime": "Uptime",
"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"
}, },
"diagnostics": { "diagnostics": {
@ -521,6 +552,7 @@
"client_id": "Client ID", "client_id": "Client ID",
"client_secret": "Client secret", "client_secret": "Client secret",
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview", "comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a user's overview",
"created_on": "Created on",
"delete1": "Delete from source when completed", "delete1": "Delete from source when completed",
"delete2": "Delete messages on destination that are not on source", "delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Delete duplicates on destination", "delete2duplicates": "Delete duplicates on destination",
@ -547,6 +579,7 @@
"hostname": "Hostname", "hostname": "Hostname",
"inactive": "Inactive", "inactive": "Inactive",
"kind": "Kind", "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": "Edit mailbox",
"mailbox_quota_def": "Default mailbox quota", "mailbox_quota_def": "Default mailbox quota",
@ -585,7 +618,7 @@
"relay_all": "Relay all recipients", "relay_all": "Relay all recipients",
"relay_all_info": "↪ If you choose <b>not</b> to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.", "relay_all_info": "↪ If you choose <b>not</b> to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.",
"relay_domain": "Relay this domain", "relay_domain": "Relay this domain",
"relay_transport_info": "<div class=\"label label-info\">Info</div> You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.", "relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.",
"relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.", "relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.",
"relayhost": "Sender-dependent transports", "relayhost": "Sender-dependent transports",
"remove": "Remove", "remove": "Remove",
@ -593,7 +626,7 @@
"save": "Save changes", "save": "Save changes",
"scope": "Scope", "scope": "Scope",
"sender_acl": "Allow to send as", "sender_acl": "Allow to send as",
"sender_acl_disabled": "<span class=\"label label-danger\">Sender check is disabled</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sender check is disabled</span>",
"sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.", "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
"sieve_desc": "Short description", "sieve_desc": "Short description",
"sieve_type": "Filter type", "sieve_type": "Filter type",
@ -650,9 +683,10 @@
"header": { "header": {
"administration": "Configuration & Details", "administration": "Configuration & Details",
"apps": "Apps", "apps": "Apps",
"debug": "System Information", "debug": "Information",
"mailboxes": "Mail Setup", "email": "E-Mail",
"mailcow_settings": "Configuration", "mailcow_system": "System",
"mailcow_config": "Configuration",
"quarantine": "Quarantine", "quarantine": "Quarantine",
"restart_netfilter": "Restart netfilter", "restart_netfilter": "Restart netfilter",
"restart_sogo": "Restart SOGo", "restart_sogo": "Restart SOGo",
@ -687,6 +721,7 @@
"add_mailbox": "Add mailbox", "add_mailbox": "Add mailbox",
"add_recipient_map_entry": "Add recipient map", "add_recipient_map_entry": "Add recipient map",
"add_resource": "Add resource", "add_resource": "Add resource",
"add_template": "Add Template",
"add_tls_policy_map": "Add TLS policy map", "add_tls_policy_map": "Add TLS policy map",
"address_rewriting": "Address rewriting", "address_rewriting": "Address rewriting",
"alias": "Alias", "alias": "Alias",
@ -718,15 +753,20 @@
"booking_ltnull": "Unlimited, but show as busy when booked", "booking_ltnull": "Unlimited, but show as busy when booked",
"booking_lt0_short": "Soft limit", "booking_lt0_short": "Soft limit",
"catch_all": "Catch-All", "catch_all": "Catch-All",
"created_on": "Created on",
"daily": "Daily", "daily": "Daily",
"deactivate": "Deactivate", "deactivate": "Deactivate",
"description": "Description", "description": "Description",
"disable_login": "Disallow login (incoming mail is still accepted)", "disable_login": "Disallow login (incoming mail is still accepted)",
"disable_x": "Disable", "disable_x": "Disable",
"dkim_domains_selector": "Selector",
"dkim_key_length": "DKIM key length (bits)",
"domain": "Domain", "domain": "Domain",
"domain_admins": "Domain administrators", "domain_admins": "Domain administrators",
"domain_aliases": "Domain aliases", "domain_aliases": "Domain aliases",
"domain_templates": "Domain Templates",
"domain_quota": "Quota", "domain_quota": "Quota",
"domain_quota_total": "Total domain quota",
"domains": "Domains", "domains": "Domains",
"edit": "Edit", "edit": "Edit",
"empty": "No results", "empty": "No results",
@ -735,6 +775,8 @@
"filter_table": "Filter table", "filter_table": "Filter table",
"filters": "Filters", "filters": "Filters",
"fname": "Full name", "fname": "Full name",
"force_pw_update": "Force password update at next login",
"gal": "Global Address List",
"goto_ham": "Learn as <b>ham</b>", "goto_ham": "Learn as <b>ham</b>",
"goto_spam": "Learn as <b>spam</b>", "goto_spam": "Learn as <b>spam</b>",
"hourly": "Hourly", "hourly": "Hourly",
@ -743,6 +785,7 @@
"insert_preset": "Insert example preset \"%s\"", "insert_preset": "Insert example preset \"%s\"",
"kind": "Kind", "kind": "Kind",
"last_mail_login": "Last mail login", "last_mail_login": "Last mail login",
"last_modified": "Last modified",
"last_pw_change": "Last password change", "last_pw_change": "Last password change",
"last_run": "Last run", "last_run": "Last run",
"last_run_reset": "Schedule next", "last_run_reset": "Schedule next",
@ -750,8 +793,12 @@
"mailbox_defaults": "Default settings", "mailbox_defaults": "Default settings",
"mailbox_defaults_info": "Define default settings for new mailboxes.", "mailbox_defaults_info": "Define default settings for new mailboxes.",
"mailbox_defquota": "Default mailbox size", "mailbox_defquota": "Default mailbox size",
"mailbox_templates": "Mailbox Templates",
"mailbox_quota": "Max. size of a mailbox", "mailbox_quota": "Max. size of a mailbox",
"mailboxes": "Mailboxes", "mailboxes": "Mailboxes",
"max_aliases": "Max. aliases",
"max_mailboxes": "Max. possible mailboxes",
"max_quota": "Max. quota per mailbox",
"mins_interval": "Interval (min)", "mins_interval": "Interval (min)",
"msg_num": "Message #", "msg_num": "Message #",
"multiple_bookings": "Multiple bookings", "multiple_bookings": "Multiple bookings",
@ -777,6 +824,8 @@
"recipient_map_old": "Original recipient", "recipient_map_old": "Original recipient",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.", "recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_maps": "Recipient maps", "recipient_maps": "Recipient maps",
"relay_all": "Relay all recipients",
"relay_unknown": "Relay unknown mailboxes",
"remove": "Remove", "remove": "Remove",
"resources": "Resources", "resources": "Resources",
"running": "Running", "running": "Running",
@ -813,6 +862,8 @@
"table_size_show_n": "Show %s items", "table_size_show_n": "Show %s items",
"target_address": "Goto address", "target_address": "Goto address",
"target_domain": "Target domain", "target_domain": "Target domain",
"templates": "Templates",
"template": "Template",
"tls_enforce_in": "Enforce TLS incoming", "tls_enforce_in": "Enforce TLS incoming",
"tls_enforce_out": "Enforce TLS outgoing", "tls_enforce_out": "Enforce TLS outgoing",
"tls_map_dest": "Destination", "tls_map_dest": "Destination",
@ -892,6 +943,22 @@
"toggle_all": "Toggle all", "toggle_all": "Toggle all",
"type": "Type" "type": "Type"
}, },
"queue": {
"delete": "Delete all",
"flush": "Flush queue",
"info" : "The mail queue contains all e-mails that are waiting for delivery. If an email is stuck in the mail queue for a long time, it is automatically deleted by the system.<br>The error message of the respective mail gives information about why the mail could not be delivered.",
"legend": "Mail queue actions functions:",
"ays": "Please confirm you want to delete all items from the current queue.",
"deliver_mail": "Deliver",
"deliver_mail_legend": "Attempts to redeliver selected mails.",
"hold_mail": "Hold",
"hold_mail_legend": "Holds the selected mails. (Prevents further delivery attempts)",
"queue_manager": "Queue Manager",
"show_message": "Show message",
"unban": "queue unban",
"unhold_mail": "Unhold",
"unhold_mail_legend": "Releases selected mails for delivery. (Requires prior hold)"
},
"ratelimit": { "ratelimit": {
"disabled": "Disabled", "disabled": "Disabled",
"second": "msgs / second", "second": "msgs / second",
@ -975,6 +1042,9 @@
"settings_map_added": "Added settings map entry", "settings_map_added": "Added settings map entry",
"settings_map_removed": "Removed settings map ID %s", "settings_map_removed": "Removed settings map ID %s",
"sogo_profile_reset": "SOGo profile for user %s was reset", "sogo_profile_reset": "SOGo profile for user %s was reset",
"template_added": "Added template %s",
"template_modified": "Changes to template %s have been saved",
"template_removed": "Template ID %s has been deleted",
"tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted", "tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted",
"tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved", "tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved",
"ui_texts": "Saved changes to UI texts", "ui_texts": "Saved changes to UI texts",
@ -1113,7 +1183,7 @@
"running": "Running", "running": "Running",
"save": "Save changes", "save": "Save changes",
"save_changes": "Save changes", "save_changes": "Save changes",
"sender_acl_disabled": "<span class=\"label label-danger\">Sender check is disabled</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sender check is disabled</span>",
"shared_aliases": "Shared alias addresses", "shared_aliases": "Shared alias addresses",
"shared_aliases_desc": "Shared aliases are not affected by user specific settings such as the spam filter or encryption policy. Corresponding spam filters can only be made by an administrator as a domain-wide policy.", "shared_aliases_desc": "Shared aliases are not affected by user specific settings such as the spam filter or encryption policy. Corresponding spam filters can only be made by an administrator as a domain-wide policy.",
"show_sieve_filters": "Show active user sieve filter", "show_sieve_filters": "Show active user sieve filter",

View File

@ -119,7 +119,6 @@
"configuration": "Configuración", "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.", "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.",
"customize": "Personalizar", "customize": "Personalizar",
"delete_queue": "Eliminar todos",
"destination": "Destino", "destination": "Destino",
"dkim_add_key": "Agregar registro ARC/DKIM", "dkim_add_key": "Agregar registro ARC/DKIM",
"dkim_domains_selector": "Selector", "dkim_domains_selector": "Selector",
@ -151,7 +150,6 @@
"f2b_retry_window": "Ventana de tiempo entre reintentos", "f2b_retry_window": "Ventana de tiempo entre reintentos",
"f2b_whitelist": "Redes y hosts en lista blanca", "f2b_whitelist": "Redes y hosts en lista blanca",
"filter_table": "Filtrar tabla", "filter_table": "Filtrar tabla",
"flush_queue": "Vaciar la cola",
"forwarding_hosts": "Hosts de reenvío", "forwarding_hosts": "Hosts de reenvío",
"forwarding_hosts_add_hint": "Se puede especificar direcciones IPv4 / IPv6, redes en notación CIDR, nombres de host (que se resolverán en direcciones IP) o dominios (que se resolverán en direcciones IP consultando registros SPF o, en su defecto, registros MX)", "forwarding_hosts_add_hint": "Se puede especificar direcciones IPv4 / IPv6, redes en notación CIDR, nombres de host (que se resolverán en direcciones IP) o dominios (que se resolverán en direcciones IP consultando registros SPF o, en su defecto, registros MX)",
"forwarding_hosts_hint": "Los mensajes entrantes son aceptados incondicionalmente de cualquiera de los hosts enumerados aquí. Estos hosts no se comprueban con listas DNSBL o se someten a greylisting. El spam recibido de ellos nunca se rechaza, pero opcionalmente se puede archivar en la carpeta de correo no deseado. El uso más común para esto es especificar los servidores de correo en los que ha configurado una regla que reenvía los correos electrónicos entrantes al servidor mailcow.", "forwarding_hosts_hint": "Los mensajes entrantes son aceptados incondicionalmente de cualquiera de los hosts enumerados aquí. Estos hosts no se comprueban con listas DNSBL o se someten a greylisting. El spam recibido de ellos nunca se rechaza, pero opcionalmente se puede archivar en la carpeta de correo no deseado. El uso más común para esto es especificar los servidores de correo en los que ha configurado una regla que reenvía los correos electrónicos entrantes al servidor mailcow.",
@ -192,12 +190,6 @@
"quarantine_release_format_att": "Como adjunto", "quarantine_release_format_att": "Como adjunto",
"quarantine_release_format_raw": "Original sin modificar", "quarantine_release_format_raw": "Original sin modificar",
"quarantine_retention_size": "Retenciones por buzón:<br><small>0 indica <b>inactivo</b>.</small>", "quarantine_retention_size": "Retenciones por buzón:<br><small>0 indica <b>inactivo</b>.</small>",
"queue_ays": "Confirme que desea eliminar todos los elementos de la cola actual.",
"queue_deliver_mail": "Entregar",
"queue_hold_mail": "Retener",
"queue_manager": "Administrador de cola",
"queue_unban": "Encolar desbloqueo",
"queue_unhold_mail": "Liberar retención",
"quota_notification_html": "Plantilla del email de notificación:<br><small>Dejar en blanco para usar la planilla predeterminada.</small>", "quota_notification_html": "Plantilla del email de notificación:<br><small>Dejar en blanco para usar la planilla predeterminada.</small>",
"quota_notification_sender": "Remitente del email de notificación", "quota_notification_sender": "Remitente del email de notificación",
"quota_notification_subject": "Asunto del email de notificación", "quota_notification_subject": "Asunto del email de notificación",
@ -391,6 +383,7 @@
"hostname": "Hostname", "hostname": "Hostname",
"inactive": "Inactivo", "inactive": "Inactivo",
"kind": "Tipo", "kind": "Tipo",
"last_modified": "Última modificación",
"mailbox": "Editar buzón", "mailbox": "Editar buzón",
"mailbox_quota_def": "Cuota de buzón predeterminada", "mailbox_quota_def": "Cuota de buzón predeterminada",
"max_aliases": "Máx. alias:", "max_aliases": "Máx. alias:",
@ -438,8 +431,8 @@
"header": { "header": {
"administration": "Administración", "administration": "Administración",
"debug": "Información del sistema", "debug": "Información del sistema",
"mailboxes": "Buzones", "email": "E-Mail",
"mailcow_settings": "Configuración", "mailcow_config": "Configuración",
"quarantine": "Cuarentena", "quarantine": "Cuarentena",
"restart_sogo": "Reiniciar SOGo", "restart_sogo": "Reiniciar SOGo",
"user_settings": "Configuraciones de usuario" "user_settings": "Configuraciones de usuario"
@ -496,10 +489,13 @@
"deactivate": "Desactivar", "deactivate": "Desactivar",
"description": "Descripción", "description": "Descripción",
"disable_x": "Desactivar", "disable_x": "Desactivar",
"dkim_domains_selector": "Selector",
"dkim_key_length": "Longitud de la llave DKIM (bits)",
"domain": "Dominio", "domain": "Dominio",
"domain_admins": "Administradores por dominio", "domain_admins": "Administradores por dominio",
"domain_aliases": "Alias de dominio", "domain_aliases": "Alias de dominio",
"domain_quota": "Cuota", "domain_quota": "Cuota",
"domain_quota_total": "Cuota total del dominio",
"domains": "Dominios", "domains": "Dominios",
"edit": "Editar", "edit": "Editar",
"empty": "Sin resultados", "empty": "Sin resultados",
@ -508,14 +504,20 @@
"filter_table": "Filtrar tabla", "filter_table": "Filtrar tabla",
"filters": "Filtros", "filters": "Filtros",
"fname": "Nombre completo", "fname": "Nombre completo",
"force_pw_update": "Forzar cambio de contraseña en el próximo inicio de sesión",
"gal": "Lista global de direcciones (GAL)",
"hourly": "Cada hora", "hourly": "Cada hora",
"in_use": "En uso (%)", "in_use": "En uso (%)",
"inactive": "Inactivo", "inactive": "Inactivo",
"kind": "Tipo", "kind": "Tipo",
"last_modified": "Última modificación",
"last_run": "Última ejecución", "last_run": "Última ejecución",
"mailbox_defquota": "Tamaño de buzón predeterminado", "mailbox_defquota": "Tamaño de buzón predeterminado",
"mailbox_quota": "Tamaño máx. de cuota", "mailbox_quota": "Tamaño máx. de cuota",
"mailboxes": "Buzones", "mailboxes": "Buzones",
"max_aliases": "Máx. alias posibles",
"max_mailboxes": "Máx. buzones posibles",
"max_quota": "Máx. cuota por buzón",
"mins_interval": "Intervalo (min)", "mins_interval": "Intervalo (min)",
"msg_num": "Mensaje #", "msg_num": "Mensaje #",
"multiple_bookings": "Reservas multiples", "multiple_bookings": "Reservas multiples",
@ -531,6 +533,7 @@
"recipient_map_old": "Destinatario original", "recipient_map_old": "Destinatario original",
"recipient_map_old_info": "El destino original de una regla de destinatario debe ser una dirección de correo electrónico válida o un nombre de dominio.", "recipient_map_old_info": "El destino original de una regla de destinatario debe ser una dirección de correo electrónico válida o un nombre de dominio.",
"recipient_maps": "Reglas de destinatario", "recipient_maps": "Reglas de destinatario",
"relay_all": "Retransmitir todos los destinatarios",
"remove": "Eliminar", "remove": "Eliminar",
"resources": "Recursos", "resources": "Recursos",
"running": "En marcha", "running": "En marcha",
@ -598,6 +601,10 @@
"text_plain_content": "Contenido (text/plain)", "text_plain_content": "Contenido (text/plain)",
"toggle_all": "Seleccionar todos" "toggle_all": "Seleccionar todos"
}, },
"queue": {
"queue_deliver_mail": "Entregar",
"queue_manager": "Administrador de cola"
},
"start": { "start": {
"help": "Mostrar/Ocultar panel de ayuda", "help": "Mostrar/Ocultar panel de ayuda",
"imap_smtp_server_auth_info": "Por favor utiliza tu dirección de correo completa y el mecanismo de autenticación PLAIN.<br>\r\nTus datos para iniciar sesión serán cifrados por el cifrado obligatorio del servidor", "imap_smtp_server_auth_info": "Por favor utiliza tu dirección de correo completa y el mecanismo de autenticación PLAIN.<br>\r\nTus datos para iniciar sesión serán cifrados por el cifrado obligatorio del servidor",

Some files were not shown because too many files have changed in this diff Show More