Merge branch 'master' into delete-disable
commit
31faee4218
@ -0,0 +1,32 @@
|
|||||||
|
from flask import redirect, url_for, Blueprint
|
||||||
|
from flask_restx import apidoc
|
||||||
|
from . import v1 as APIv1
|
||||||
|
|
||||||
|
def register(app, web_api_root):
|
||||||
|
|
||||||
|
APIv1.app = app
|
||||||
|
# register api bluprint(s)
|
||||||
|
apidoc.apidoc.url_prefix = f'{web_api_root}/v{int(APIv1.VERSION)}'
|
||||||
|
APIv1.api_token = app.config['API_TOKEN']
|
||||||
|
if app.config['API_TOKEN'] != '':
|
||||||
|
app.register_blueprint(APIv1.blueprint, url_prefix=f'{web_api_root}/v{int(APIv1.VERSION)}')
|
||||||
|
|
||||||
|
# add redirect to current api version
|
||||||
|
redirect_api = Blueprint('redirect_api', __name__)
|
||||||
|
@redirect_api.route('/')
|
||||||
|
def redir():
|
||||||
|
return redirect(url_for(f'{APIv1.blueprint.name}.root'))
|
||||||
|
app.register_blueprint(redirect_api, url_prefix=f'{web_api_root}')
|
||||||
|
|
||||||
|
# swagger ui config
|
||||||
|
app.config.SWAGGER_UI_DOC_EXPANSION = 'list'
|
||||||
|
app.config.SWAGGER_UI_OPERATION_ID = True
|
||||||
|
app.config.SWAGGER_UI_REQUEST_DURATION = True
|
||||||
|
app.config.RESTX_MASK_SWAGGER = False
|
||||||
|
else:
|
||||||
|
api = Blueprint('api', __name__)
|
||||||
|
@api.route('/', defaults={'path': ''})
|
||||||
|
@api.route('/<path:path>')
|
||||||
|
def api_token_missing(path):
|
||||||
|
return "<p>Error: API_TOKEN is not configured</p>", 500
|
||||||
|
app.register_blueprint(api, url_prefix=f'{web_api_root}')
|
@ -0,0 +1,42 @@
|
|||||||
|
from .. import models, utils
|
||||||
|
from . import v1
|
||||||
|
from flask import request
|
||||||
|
import flask
|
||||||
|
import hmac
|
||||||
|
from functools import wraps
|
||||||
|
from flask_restx import abort
|
||||||
|
from sqlalchemy.sql.expression import label
|
||||||
|
|
||||||
|
def fqdn_in_use(name):
|
||||||
|
d = models.db.session.query(label('name', models.Domain.name))
|
||||||
|
a = models.db.session.query(label('name', models.Alternative.name))
|
||||||
|
r = models.db.session.query(label('name', models.Relay.name))
|
||||||
|
u = d.union_all(a).union_all(r).filter_by(name=name)
|
||||||
|
if models.db.session.query(u.exists()).scalar():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
""" Decorator for validating api token for authentication """
|
||||||
|
def api_token_authorization(func):
|
||||||
|
@wraps(func)
|
||||||
|
def decorated_function(*args, **kwds):
|
||||||
|
client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)
|
||||||
|
if utils.limiter.should_rate_limit_ip(client_ip):
|
||||||
|
abort(429, 'Too many attempts from your IP (rate-limit)' )
|
||||||
|
if not request.headers.get('Authorization'):
|
||||||
|
abort(401, 'A valid Bearer token is expected which is provided as request header')
|
||||||
|
#Client provides 'Authentication: Bearer <token>'
|
||||||
|
if (' ' in request.headers.get('Authorization')
|
||||||
|
and not hmac.compare_digest(request.headers.get('Authorization'), 'Bearer ' + v1.api_token)):
|
||||||
|
utils.limiter.rate_limit_ip(client_ip)
|
||||||
|
flask.current_app.logger.warn(f'Invalid API token provided by {client_ip}.')
|
||||||
|
abort(403, 'A valid Bearer token is expected which is provided as request header')
|
||||||
|
#Client provides 'Authentication: <token>'
|
||||||
|
elif (' ' not in request.headers.get('Authorization')
|
||||||
|
and not hmac.compare_digest(request.headers.get('Authorization'), v1.api_token)):
|
||||||
|
utils.limiter.rate_limit_ip(client_ip)
|
||||||
|
flask.current_app.logger.warn(f'Invalid API token provided by {client_ip}.')
|
||||||
|
abort(403, 'A valid Bearer token is expected which is provided as request header')
|
||||||
|
flask.current_app.logger.info(f'Valid API token provided by {client_ip}.')
|
||||||
|
return func(*args, **kwds)
|
||||||
|
return decorated_function
|
@ -0,0 +1,43 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
from flask_restx import Api, fields
|
||||||
|
|
||||||
|
|
||||||
|
VERSION = 1.0
|
||||||
|
api_token = None
|
||||||
|
|
||||||
|
blueprint = Blueprint(f'api_v{int(VERSION)}', __name__)
|
||||||
|
|
||||||
|
authorization = {
|
||||||
|
'Bearer': {
|
||||||
|
'type': 'apiKey',
|
||||||
|
'in': 'header',
|
||||||
|
'name': 'Authorization'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api = Api(
|
||||||
|
blueprint, version=f'{VERSION:.1f}',
|
||||||
|
title='Mailu API', default_label='Mailu',
|
||||||
|
validate=True,
|
||||||
|
authorizations=authorization,
|
||||||
|
security='Bearer',
|
||||||
|
doc='/'
|
||||||
|
)
|
||||||
|
|
||||||
|
response_fields = api.model('Response', {
|
||||||
|
'code': fields.Integer,
|
||||||
|
'message': fields.String,
|
||||||
|
})
|
||||||
|
|
||||||
|
error_fields = api.model('Error', {
|
||||||
|
'errors': fields.Nested(api.model('Error_Key', {
|
||||||
|
'key': fields.String,
|
||||||
|
'message':fields.String
|
||||||
|
})),
|
||||||
|
'message': fields.String,
|
||||||
|
})
|
||||||
|
|
||||||
|
from . import domains
|
||||||
|
from . import alias
|
||||||
|
from . import relay
|
||||||
|
from . import user
|
@ -0,0 +1,126 @@
|
|||||||
|
from flask_restx import Resource, fields, marshal
|
||||||
|
from . import api, response_fields
|
||||||
|
from .. import common
|
||||||
|
from ... import models
|
||||||
|
|
||||||
|
db = models.db
|
||||||
|
|
||||||
|
alias = api.namespace('alias', description='Alias operations')
|
||||||
|
|
||||||
|
alias_fields_update = alias.model('AliasUpdate', {
|
||||||
|
'comment': fields.String(description='a comment'),
|
||||||
|
'destination': fields.List(fields.String(description='alias email address', example='user@example.com')),
|
||||||
|
'wildcard': fields.Boolean(description='enable SQL Like wildcard syntax')
|
||||||
|
})
|
||||||
|
|
||||||
|
alias_fields = alias.inherit('Alias',alias_fields_update, {
|
||||||
|
'email': fields.String(description='the alias email address', example='user@example.com', required=True),
|
||||||
|
'destination': fields.List(fields.String(description='alias email address', example='user@example.com', required=True)),
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@alias.route('')
|
||||||
|
class Aliases(Resource):
|
||||||
|
@alias.doc('list_alias')
|
||||||
|
@alias.marshal_with(alias_fields, as_list=True, skip_none=True, mask=None)
|
||||||
|
@alias.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self):
|
||||||
|
""" List aliases """
|
||||||
|
return models.Alias.query.all()
|
||||||
|
|
||||||
|
@alias.doc('create_alias')
|
||||||
|
@alias.expect(alias_fields)
|
||||||
|
@alias.response(200, 'Success', response_fields)
|
||||||
|
@alias.response(400, 'Input validation exception', response_fields)
|
||||||
|
@alias.response(409, 'Duplicate alias', response_fields)
|
||||||
|
@alias.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def post(self):
|
||||||
|
""" Create a new alias """
|
||||||
|
data = api.payload
|
||||||
|
|
||||||
|
alias_found = models.Alias.query.filter_by(email = data['email']).first()
|
||||||
|
if alias_found:
|
||||||
|
return { 'code': 409, 'message': f'Duplicate alias {data["email"]}'}, 409
|
||||||
|
|
||||||
|
alias_model = models.Alias(email=data["email"],destination=data['destination'])
|
||||||
|
if 'comment' in data:
|
||||||
|
alias_model.comment = data['comment']
|
||||||
|
if 'wildcard' in data:
|
||||||
|
alias_model.wildcard = data['wildcard']
|
||||||
|
db.session.add(alias_model)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return {'code': 200, 'message': f'Alias {data["email"]} to destination {data["destination"]} has been created'}, 200
|
||||||
|
|
||||||
|
@alias.route('/<string:alias>')
|
||||||
|
class Alias(Resource):
|
||||||
|
@alias.doc('find_alias')
|
||||||
|
@alias.response(200, 'Success', alias_fields)
|
||||||
|
@alias.response(404, 'Alias not found', response_fields)
|
||||||
|
@alias.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, alias):
|
||||||
|
""" Find alias """
|
||||||
|
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||||
|
if alias_found is None:
|
||||||
|
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||||
|
else:
|
||||||
|
return marshal(alias_found,alias_fields), 200
|
||||||
|
|
||||||
|
@alias.doc('update_alias')
|
||||||
|
@alias.expect(alias_fields_update)
|
||||||
|
@alias.response(200, 'Success', response_fields)
|
||||||
|
@alias.response(404, 'Alias not found', response_fields)
|
||||||
|
@alias.response(400, 'Input validation exception', response_fields)
|
||||||
|
@alias.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def patch(self, alias):
|
||||||
|
""" Update alias """
|
||||||
|
data = api.payload
|
||||||
|
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||||
|
if alias_found is None:
|
||||||
|
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||||
|
if 'comment' in data:
|
||||||
|
alias_found.comment = data['comment']
|
||||||
|
if 'destination' in data:
|
||||||
|
alias_found.destination = data['destination']
|
||||||
|
if 'wildcard' in data:
|
||||||
|
alias_found.wildcard = data['wildcard']
|
||||||
|
db.session.add(alias_found)
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Alias {alias} has been updated'}
|
||||||
|
|
||||||
|
@alias.doc('delete_alias')
|
||||||
|
@alias.response(200, 'Success', response_fields)
|
||||||
|
@alias.response(404, 'Alias not found', response_fields)
|
||||||
|
@alias.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def delete(self, alias):
|
||||||
|
""" Delete alias """
|
||||||
|
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||||
|
if alias_found is None:
|
||||||
|
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||||
|
db.session.delete(alias_found)
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Alias {alias} has been deleted'}, 200
|
||||||
|
|
||||||
|
@alias.route('/destination/<string:domain>')
|
||||||
|
class AliasWithDest(Resource):
|
||||||
|
@alias.doc('find_alias_filter_domain')
|
||||||
|
@alias.response(200, 'Success', alias_fields)
|
||||||
|
@alias.response(404, 'Alias or domain not found', response_fields)
|
||||||
|
@alias.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, domain):
|
||||||
|
""" Find aliases of domain """
|
||||||
|
domain_found = models.Domain.query.filter_by(name=domain).first()
|
||||||
|
if domain_found is None:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} cannot be found'}, 404
|
||||||
|
aliases_found = domain_found.aliases
|
||||||
|
if aliases_found.count == 0:
|
||||||
|
return { 'code': 404, 'message': f'No alias can be found for domain {domain}'}, 404
|
||||||
|
else:
|
||||||
|
return marshal(aliases_found, alias_fields), 200
|
@ -0,0 +1,410 @@
|
|||||||
|
import validators
|
||||||
|
from flask_restx import Resource, fields, marshal
|
||||||
|
from . import api, response_fields, user
|
||||||
|
from .. import common
|
||||||
|
from ... import models
|
||||||
|
|
||||||
|
db = models.db
|
||||||
|
|
||||||
|
dom = api.namespace('domain', description='Domain operations')
|
||||||
|
alt = api.namespace('alternative', description='Alternative operations')
|
||||||
|
|
||||||
|
domain_fields = api.model('Domain', {
|
||||||
|
'name': fields.String(description='FQDN (e.g. example.com)', example='example.com', required=True),
|
||||||
|
'comment': fields.String(description='a comment'),
|
||||||
|
'max_users': fields.Integer(description='maximum number of users', min=-1, default=-1),
|
||||||
|
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||||
|
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||||
|
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||||
|
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||||
|
})
|
||||||
|
|
||||||
|
domain_fields_update = api.model('DomainUpdate', {
|
||||||
|
'comment': fields.String(description='a comment'),
|
||||||
|
'max_users': fields.Integer(description='maximum number of users', min=-1, default=-1),
|
||||||
|
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||||
|
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||||
|
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||||
|
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||||
|
})
|
||||||
|
|
||||||
|
domain_fields_get = api.model('DomainGet', {
|
||||||
|
'name': fields.String(description='FQDN (e.g. example.com)', example='example.com', required=True),
|
||||||
|
'comment': fields.String(description='a comment'),
|
||||||
|
'max_users': fields.Integer(description='maximum number of users', min=-1, default=-1),
|
||||||
|
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||||
|
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||||
|
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||||
|
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||||
|
'dns_autoconfig': fields.List(fields.String(description='DNS client auto-configuration entry')),
|
||||||
|
'dns_mx': fields.String(Description='MX record for domain'),
|
||||||
|
'dns_spf': fields.String(Description='SPF record for domain'),
|
||||||
|
'dns_dkim': fields.String(Description='DKIM record for domain'),
|
||||||
|
'dns_dmarc': fields.String(Description='DMARC record for domain'),
|
||||||
|
'dns_dmarc_report': fields.String(Description='DMARC report record for domain'),
|
||||||
|
'dns_tlsa': fields.String(Description='TLSA record for domain'),
|
||||||
|
})
|
||||||
|
|
||||||
|
domain_fields_dns = api.model('DomainDNS', {
|
||||||
|
'dns_autoconfig': fields.List(fields.String(description='DNS client auto-configuration entry')),
|
||||||
|
'dns_mx': fields.String(Description='MX record for domain'),
|
||||||
|
'dns_spf': fields.String(Description='SPF record for domain'),
|
||||||
|
'dns_dkim': fields.String(Description='DKIM record for domain'),
|
||||||
|
'dns_dmarc': fields.String(Description='DMARC record for domain'),
|
||||||
|
'dns_dmarc_report': fields.String(Description='DMARC report record for domain'),
|
||||||
|
'dns_tlsa': fields.String(Description='TLSA record for domain'),
|
||||||
|
})
|
||||||
|
|
||||||
|
manager_fields = api.model('Manager', {
|
||||||
|
'domain_name': fields.String(description='domain managed by manager'),
|
||||||
|
'user_email': fields.String(description='email address of manager'),
|
||||||
|
})
|
||||||
|
|
||||||
|
manager_fields_create = api.model('ManagerCreate', {
|
||||||
|
'user_email': fields.String(description='email address of manager', required=True),
|
||||||
|
})
|
||||||
|
|
||||||
|
alternative_fields_update = api.model('AlternativeDomainUpdate', {
|
||||||
|
'domain': fields.String(description='domain FQDN', example='example.com', required=False),
|
||||||
|
})
|
||||||
|
|
||||||
|
alternative_fields = api.model('AlternativeDomain', {
|
||||||
|
'name': fields.String(description='alternative FQDN', example='example2.com', required=True),
|
||||||
|
'domain': fields.String(description='domain FQDN', example='example.com', required=True),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dom.route('')
|
||||||
|
class Domains(Resource):
|
||||||
|
@dom.doc('list_domain')
|
||||||
|
@dom.marshal_with(domain_fields_get, as_list=True, skip_none=True, mask=None)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self):
|
||||||
|
""" List domains """
|
||||||
|
return models.Domain.query.all()
|
||||||
|
|
||||||
|
@dom.doc('create_domain')
|
||||||
|
@dom.expect(domain_fields)
|
||||||
|
@dom.response(200, 'Success', response_fields)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def post(self):
|
||||||
|
""" Create a new domain """
|
||||||
|
data = api.payload
|
||||||
|
if not validators.domain(data['name']):
|
||||||
|
return { 'code': 400, 'message': f'Domain {data["name"]} is not a valid domain'}, 400
|
||||||
|
|
||||||
|
if common.fqdn_in_use(data['name']):
|
||||||
|
return { 'code': 409, 'message': f'Duplicate domain name {data["name"]}'}, 409
|
||||||
|
if 'alternatives' in data:
|
||||||
|
#check if duplicate alternatives are supplied
|
||||||
|
if [x for x in data['alternatives'] if data['alternatives'].count(x) >= 2]:
|
||||||
|
return { 'code': 409, 'message': f'Duplicate alternative domain names in request' }, 409
|
||||||
|
for item in data['alternatives']:
|
||||||
|
if common.fqdn_in_use(item):
|
||||||
|
return { 'code': 409, 'message': f'Duplicate alternative domain name {item}' }, 409
|
||||||
|
if not validators.domain(item):
|
||||||
|
return { 'code': 400, 'message': f'Alternative domain {item} is not a valid domain'}, 400
|
||||||
|
for item in data['alternatives']:
|
||||||
|
alternative = models.Alternative(name=item, domain_name=data['name'])
|
||||||
|
models.db.session.add(alternative)
|
||||||
|
domain_new = models.Domain(name=data['name'])
|
||||||
|
if 'comment' in data:
|
||||||
|
domain_new.comment = data['comment']
|
||||||
|
if 'max_users' in data:
|
||||||
|
domain_new.comment = data['max_users']
|
||||||
|
if 'max_aliases' in data:
|
||||||
|
domain_new.comment = data['max_aliases']
|
||||||
|
if 'max_quota_bytes' in data:
|
||||||
|
domain_new.comment = data['max_quota_bytes']
|
||||||
|
if 'signup_enabled' in data:
|
||||||
|
domain_new.comment = data['signup_enabled']
|
||||||
|
models.db.session.add(domain_new)
|
||||||
|
#apply the changes
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Domain {data["name"]} has been created'}, 200
|
||||||
|
|
||||||
|
@dom.route('/<domain>')
|
||||||
|
class Domain(Resource):
|
||||||
|
|
||||||
|
@dom.doc('find_domain')
|
||||||
|
@dom.response(200, 'Success', domain_fields)
|
||||||
|
@dom.response(404, 'Domain not found', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, domain):
|
||||||
|
""" Find domain by name """
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain_found = models.Domain.query.get(domain)
|
||||||
|
if not domain_found:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
return marshal(domain_found, domain_fields_get), 200
|
||||||
|
|
||||||
|
@dom.doc('update_domain')
|
||||||
|
@dom.expect(domain_fields_update)
|
||||||
|
@dom.response(200, 'Success', response_fields)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(404, 'Domain not found', response_fields)
|
||||||
|
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def patch(self, domain):
|
||||||
|
""" Update an existing domain """
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain_found = models.Domain.query.get(domain)
|
||||||
|
if not domain:
|
||||||
|
return { 'code': 404, 'message': f'Domain {data["name"]} does not exist'}, 404
|
||||||
|
data = api.payload
|
||||||
|
|
||||||
|
if 'alternatives' in data:
|
||||||
|
#check if duplicate alternatives are supplied
|
||||||
|
if [x for x in data['alternatives'] if data['alternatives'].count(x) >= 2]:
|
||||||
|
return { 'code': 409, 'message': f'Duplicate alternative domain names in request' }, 409
|
||||||
|
for item in data['alternatives']:
|
||||||
|
if common.fqdn_in_use(item):
|
||||||
|
return { 'code': 409, 'message': f'Duplicate alternative domain name {item}' }, 409
|
||||||
|
if not validators.domain(item):
|
||||||
|
return { 'code': 400, 'message': f'Alternative domain {item} is not a valid domain'}, 400
|
||||||
|
for item in data['alternatives']:
|
||||||
|
alternative = models.Alternative(name=item, domain_name=data['name'])
|
||||||
|
models.db.session.add(alternative)
|
||||||
|
|
||||||
|
if 'comment' in data:
|
||||||
|
domain_found.comment = data['comment']
|
||||||
|
if 'max_users' in data:
|
||||||
|
domain_found.comment = data['max_users']
|
||||||
|
if 'max_aliases' in data:
|
||||||
|
domain_found.comment = data['max_aliases']
|
||||||
|
if 'max_quota_bytes' in data:
|
||||||
|
domain_found.comment = data['max_quota_bytes']
|
||||||
|
if 'signup_enabled' in data:
|
||||||
|
domain_found.comment = data['signup_enabled']
|
||||||
|
models.db.session.add(domain_found)
|
||||||
|
|
||||||
|
#apply the changes
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Domain {domain} has been updated'}, 200
|
||||||
|
|
||||||
|
@dom.doc('delete_domain')
|
||||||
|
@dom.response(200, 'Success', response_fields)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(404, 'Domain not found', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def delete(self, domain):
|
||||||
|
""" Delete domain """
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain_found = models.Domain.query.get(domain)
|
||||||
|
if not domain:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
db.session.delete(domain_found)
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Domain {domain} has been deleted'}, 200
|
||||||
|
|
||||||
|
@dom.route('/<domain>/dkim')
|
||||||
|
class Domain(Resource):
|
||||||
|
@dom.doc('generate_dkim')
|
||||||
|
@dom.response(200, 'Success', response_fields)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(404, 'Domain not found', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def post(self, domain):
|
||||||
|
""" Generate new DKIM/DMARC keys for domain """
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain_found = models.Domain.query.get(domain)
|
||||||
|
if not domain_found:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
domain_found.generate_dkim_key()
|
||||||
|
domain_found.save_dkim_key()
|
||||||
|
return {'code': 200, 'message': f'DKIM/DMARC keys have been generated for domain {domain}'}, 200
|
||||||
|
|
||||||
|
@dom.route('/<domain>/manager')
|
||||||
|
class Manager(Resource):
|
||||||
|
@dom.doc('list_managers')
|
||||||
|
@dom.marshal_with(manager_fields, as_list=True, skip_none=True, mask=None)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(404, 'domain not found', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, domain):
|
||||||
|
""" List managers of domain """
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
if not domain:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
domain = models.Domain.query.filter_by(name=domain)
|
||||||
|
return domain.managers
|
||||||
|
|
||||||
|
@dom.doc('create_manager')
|
||||||
|
@dom.expect(manager_fields_create)
|
||||||
|
@dom.response(200, 'Success', response_fields)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(404, 'User or domain not found', response_fields)
|
||||||
|
@dom.response(409, 'Duplicate domain manager', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def post(self, domain):
|
||||||
|
""" Create a new domain manager """
|
||||||
|
data = api.payload
|
||||||
|
if not validators.email(data['user_email']):
|
||||||
|
return {'code': 400, 'message': f'Invalid email address {data["user_email"]}'}, 400
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain = models.Domain.query.get(domain)
|
||||||
|
if not domain:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
user = models.User.query.get(data['user_email'])
|
||||||
|
if not user:
|
||||||
|
return { 'code': 404, 'message': f'User {data["user_email"]} does not exist'}, 404
|
||||||
|
if user in domain.managers:
|
||||||
|
return {'code': 409, 'message': f'User {data["user_email"]} is already a manager of the domain {domain} '}, 409
|
||||||
|
domain.managers.append(user)
|
||||||
|
models.db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'User {data["user_email"]} has been added as manager of the domain {domain} '},200
|
||||||
|
|
||||||
|
@dom.route('/<domain>/manager/<email>')
|
||||||
|
class Domain(Resource):
|
||||||
|
@dom.doc('find_manager')
|
||||||
|
@dom.response(200, 'Success', manager_fields)
|
||||||
|
@dom.response(404, 'Manager not found', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, domain, email):
|
||||||
|
""" Find manager by email address """
|
||||||
|
if not validators.email(email):
|
||||||
|
return {'code': 400, 'message': f'Invalid email address {email}'}, 400
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain = models.Domain.query.get(domain)
|
||||||
|
if not domain:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
user = models.User.query.get(email)
|
||||||
|
if not user:
|
||||||
|
return { 'code': 404, 'message': f'User {email} does not exist'}, 404
|
||||||
|
if user in domain.managers:
|
||||||
|
for manager in domain.managers:
|
||||||
|
if manager.email == email:
|
||||||
|
return marshal(manager, manager_fields),200
|
||||||
|
else:
|
||||||
|
return { 'code': 404, 'message': f'User {email} is not a manager of the domain {domain}'}, 404
|
||||||
|
|
||||||
|
|
||||||
|
@dom.doc('delete_manager')
|
||||||
|
@dom.response(200, 'Success', response_fields)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(404, 'Manager not found', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def delete(self, domain, email):
|
||||||
|
if not validators.email(email):
|
||||||
|
return {'code': 400, 'message': f'Invalid email address {email}'}, 400
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain = models.Domain.query.get(domain)
|
||||||
|
if not domain:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
user = models.User.query.get(email)
|
||||||
|
if not user:
|
||||||
|
return { 'code': 404, 'message': f'User {email} does not exist'}, 404
|
||||||
|
if user in domain.managers:
|
||||||
|
domain.managers.remove(user)
|
||||||
|
models.db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'User {email} has been removed as a manager of the domain {domain} '},200
|
||||||
|
else:
|
||||||
|
return { 'code': 404, 'message': f'User {email} is not a manager of the domain {domain}'}, 404
|
||||||
|
|
||||||
|
@dom.route('/<domain>/users')
|
||||||
|
class User(Resource):
|
||||||
|
@dom.doc('list_user_domain')
|
||||||
|
@dom.marshal_with(user.user_fields_get, as_list=True, skip_none=True, mask=None)
|
||||||
|
@dom.response(400, 'Input validation exception', response_fields)
|
||||||
|
@dom.response(404, 'Domain not found', response_fields)
|
||||||
|
@dom.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, domain):
|
||||||
|
""" List users from domain """
|
||||||
|
if not validators.domain(domain):
|
||||||
|
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||||
|
domain_found = models.Domain.query.get(domain)
|
||||||
|
if not domain_found:
|
||||||
|
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||||
|
return models.User.query.filter_by(domain=domain_found).all()
|
||||||
|
|
||||||
|
@alt.route('')
|
||||||
|
class Alternatives(Resource):
|
||||||
|
|
||||||
|
@alt.doc('list_alternative')
|
||||||
|
@alt.marshal_with(alternative_fields, as_list=True, skip_none=True, mask=None)
|
||||||
|
@alt.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self):
|
||||||
|
""" List alternatives """
|
||||||
|
return models.Alternative.query.all()
|
||||||
|
|
||||||
|
|
||||||
|
@alt.doc('create_alternative')
|
||||||
|
@alt.expect(alternative_fields)
|
||||||
|
@alt.response(200, 'Success', response_fields)
|
||||||
|
@alt.response(400, 'Input validation exception', response_fields)
|
||||||
|
@alt.response(404, 'Domain not found or missing', response_fields)
|
||||||
|
@alt.response(409, 'Duplicate alternative domain name', response_fields)
|
||||||
|
@alt.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def post(self):
|
||||||
|
""" Create new alternative (for domain) """
|
||||||
|
data = api.payload
|
||||||
|
if not validators.domain(data['name']):
|
||||||
|
return { 'code': 400, 'message': f'Alternative domain {data["name"]} is not a valid domain'}, 400
|
||||||
|
if not validators.domain(data['domain']):
|
||||||
|
return { 'code': 400, 'message': f'Domain {data["domain"]} is not a valid domain'}, 400
|
||||||
|
domain = models.Domain.query.get(data['domain'])
|
||||||
|
if not domain:
|
||||||
|
return { 'code': 404, 'message': f'Domain {data["domain"]} does not exist'}, 404
|
||||||
|
if common.fqdn_in_use(data['name']):
|
||||||
|
return { 'code': 409, 'message': f'Duplicate alternative domain name {data["name"]}'}, 409
|
||||||
|
|
||||||
|
alternative = models.Alternative(name=data['name'], domain_name=data['domain'])
|
||||||
|
models.db.session.add(alternative)
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Alternative {data["name"]} for domain {data["domain"]} has been created'}, 200
|
||||||
|
|
||||||
|
@alt.route('/<string:alt>')
|
||||||
|
class Alternative(Resource):
|
||||||
|
@alt.doc('find_alternative')
|
||||||
|
@alt.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, alt):
|
||||||
|
""" Find alternative (of domain) """
|
||||||
|
if not validators.domain(alt):
|
||||||
|
return { 'code': 400, 'message': f'Alternative domain {alt} is not a valid domain'}, 400
|
||||||
|
alternative = models.Alternative.query.filter_by(name=alt).first()
|
||||||
|
if not alternative:
|
||||||
|
return{ 'code': 404, 'message': f'Alternative domain {alt} does not exist'}, 404
|
||||||
|
return marshal(alternative, alternative_fields), 200
|
||||||
|
|
||||||
|
@alt.doc('delete_alternative')
|
||||||
|
@alt.response(200, 'Success', response_fields)
|
||||||
|
@alt.response(400, 'Input validation exception', response_fields)
|
||||||
|
@alt.response(404, 'Alternative/Domain not found or missing', response_fields)
|
||||||
|
@alt.response(409, 'Duplicate domain name', response_fields)
|
||||||
|
@alt.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def delete(self, alt):
|
||||||
|
""" Delete alternative (for domain) """
|
||||||
|
if not validators.domain(alt):
|
||||||
|
return { 'code': 400, 'message': f'Alternative domain {alt} is not a valid domain'}, 400
|
||||||
|
alternative = models.Alternative.query.filter_by(name=alt).scalar()
|
||||||
|
if not alternative:
|
||||||
|
return { 'code': 404, 'message': f'Alternative domain {alt} does not exist'}, 404
|
||||||
|
domain = alternative.domain_name
|
||||||
|
db.session.delete(alternative)
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Alternative {alt} for domain {domain} has been deleted'}, 200
|
@ -0,0 +1,118 @@
|
|||||||
|
from flask_restx import Resource, fields, marshal
|
||||||
|
import validators
|
||||||
|
|
||||||
|
from . import api, response_fields
|
||||||
|
from .. import common
|
||||||
|
from ... import models
|
||||||
|
|
||||||
|
db = models.db
|
||||||
|
|
||||||
|
relay = api.namespace('relay', description='Relay operations')
|
||||||
|
|
||||||
|
relay_fields = api.model('Relay', {
|
||||||
|
'name': fields.String(description='relayed domain name', example='example.com', required=True),
|
||||||
|
'smtp': fields.String(description='remote host', example='example.com', required=False),
|
||||||
|
'comment': fields.String(description='a comment', required=False)
|
||||||
|
})
|
||||||
|
|
||||||
|
relay_fields_update = api.model('RelayUpdate', {
|
||||||
|
'smtp': fields.String(description='remote host', example='example.com', required=False),
|
||||||
|
'comment': fields.String(description='a comment', required=False)
|
||||||
|
})
|
||||||
|
|
||||||
|
@relay.route('')
|
||||||
|
class Relays(Resource):
|
||||||
|
@relay.doc('list_relays')
|
||||||
|
@relay.marshal_with(relay_fields, as_list=True, skip_none=True, mask=None)
|
||||||
|
@relay.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self):
|
||||||
|
"List relays"
|
||||||
|
return models.Relay.query.all()
|
||||||
|
|
||||||
|
@relay.doc('create_relay')
|
||||||
|
@relay.expect(relay_fields)
|
||||||
|
@relay.response(200, 'Success', response_fields)
|
||||||
|
@relay.response(400, 'Input validation exception')
|
||||||
|
@relay.response(409, 'Duplicate relay', response_fields)
|
||||||
|
@relay.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def post(self):
|
||||||
|
""" Create relay """
|
||||||
|
data = api.payload
|
||||||
|
|
||||||
|
if not validators.domain(name):
|
||||||
|
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||||
|
|
||||||
|
if common.fqdn_in_use(data['name']):
|
||||||
|
return { 'code': 409, 'message': f'Duplicate domain {data["name"]}'}, 409
|
||||||
|
relay_model = models.Relay(name=data['name'])
|
||||||
|
if 'smtp' in data:
|
||||||
|
relay_model.smtp = data['smtp']
|
||||||
|
if 'comment' in data:
|
||||||
|
relay_model.comment = data['comment']
|
||||||
|
db.session.add(relay_model)
|
||||||
|
db.session.commit()
|
||||||
|
return {'code': 200, 'message': f'Relayed domain {data["name"]} has been created'}, 200
|
||||||
|
|
||||||
|
@relay.route('/<string:name>')
|
||||||
|
class Relay(Resource):
|
||||||
|
@relay.doc('find_relay')
|
||||||
|
@relay.response(400, 'Input validation exception', response_fields)
|
||||||
|
@relay.response(404, 'Relay not found', response_fields)
|
||||||
|
@relay.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def get(self, name):
|
||||||
|
""" Find relay """
|
||||||
|
if not validators.domain(name):
|
||||||
|
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||||
|
|
||||||
|
relay_found = models.Relay.query.filter_by(name=name).first()
|
||||||
|
if relay_found is None:
|
||||||
|
return { 'code': 404, 'message': f'Relayed domain {name} cannot be found'}, 404
|
||||||
|
return marshal(relay_found, relay_fields), 200
|
||||||
|
|
||||||
|
@relay.doc('update_relay')
|
||||||
|
@relay.expect(relay_fields_update)
|
||||||
|
@relay.response(200, 'Success', response_fields)
|
||||||
|
@relay.response(400, 'Input validation exception', response_fields)
|
||||||
|
@relay.response(404, 'Relay not found', response_fields)
|
||||||
|
@relay.response(409, 'Duplicate relay', response_fields)
|
||||||
|
@relay.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def patch(self, name):
|
||||||
|
""" Update relay """
|
||||||
|
data = api.payload
|
||||||
|
|
||||||
|
if not validators.domain(name):
|
||||||
|
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||||
|
|
||||||
|
relay_found = models.Relay.query.filter_by(name=name).first()
|
||||||
|
if relay_found is None:
|
||||||
|
return { 'code': 404, 'message': f'Relayed domain {name} cannot be found'}, 404
|
||||||
|
|
||||||
|
if 'smtp' in data:
|
||||||
|
relay_found.smtp = data['smtp']
|
||||||
|
if 'comment' in data:
|
||||||
|
relay_found.comment = data['comment']
|
||||||
|
db.session.add(relay_found)
|
||||||
|
db.session.commit()
|
||||||
|
return { 'code': 200, 'message': f'Relayed domain {name} has been updated'}, 200
|
||||||
|
|
||||||
|
|
||||||
|
@relay.doc('delete_relay')
|
||||||
|
@relay.response(200, 'Success', response_fields)
|
||||||
|
@relay.response(400, 'Input validation exception', response_fields)
|
||||||
|
@relay.response(404, 'Relay not found', response_fields)
|
||||||
|
@relay.doc(security='Bearer')
|
||||||
|
@common.api_token_authorization
|
||||||
|
def delete(self, name):
|
||||||
|
""" Delete relay """
|
||||||
|
if not validators.domain(name):
|
||||||
|
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||||
|
relay_found = models.Relay.query.filter_by(name=name).first()
|
||||||
|
if relay_found is None:
|
||||||
|
return { 'code': 404, 'message': f'Relayed domain {name} cannot be found'}, 404
|
||||||
|
db.session.delete(relay_found)
|
||||||
|
db.session.commit()
|
||||||
|
return { 'code': 200, 'message': f'Relayed domain {name} has been deleted'}, 200
|
@ -0,0 +1,733 @@
|
|||||||
|
# Czech translations for Mailu.io.
|
||||||
|
# Copyright (C) 2023 S474N
|
||||||
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
|
# S474N <translate@s474n.com>, 2023.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: translate@s474n.com\n"
|
||||||
|
"POT-Creation-Date: 2022-05-22 18:47+0200\n"
|
||||||
|
"PO-Revision-Date: 2023-02-21 16:14+0100\n"
|
||||||
|
"Last-Translator: S474N <translate@s474n.com>\n"
|
||||||
|
"Language-Team: Czech\n"
|
||||||
|
"Language: cs_CZ\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n"
|
||||||
|
"Generated-By: Babel 2.3.4\n"
|
||||||
|
"X-Generator: Poedit 3.2.2\n"
|
||||||
|
|
||||||
|
#: mailu/sso/forms.py:8 mailu/ui/forms.py:79
|
||||||
|
msgid "E-mail"
|
||||||
|
msgstr "E-mail"
|
||||||
|
|
||||||
|
#: mailu/sso/forms.py:9 mailu/ui/forms.py:80 mailu/ui/forms.py:93
|
||||||
|
#: mailu/ui/forms.py:112 mailu/ui/forms.py:166
|
||||||
|
#: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:57
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Heslo"
|
||||||
|
|
||||||
|
#: mailu/sso/forms.py:10 mailu/sso/forms.py:11 mailu/sso/templates/login.html:4
|
||||||
|
#: mailu/ui/templates/sidebar.html:142
|
||||||
|
msgid "Sign in"
|
||||||
|
msgstr "Přihlásit se"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8
|
||||||
|
msgid "Admin page for"
|
||||||
|
msgstr "Admin stránka pro"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19
|
||||||
|
msgid "toggle sidebar"
|
||||||
|
msgstr "přepnout postranní panel"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37
|
||||||
|
msgid "change language"
|
||||||
|
msgstr "změnit jazyk"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94
|
||||||
|
msgid "Go to"
|
||||||
|
msgstr "Jít"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/sidebar_sso.html:9 mailu/ui/templates/client.html:4
|
||||||
|
#: mailu/ui/templates/sidebar.html:50 mailu/ui/templates/sidebar.html:107
|
||||||
|
msgid "Client setup"
|
||||||
|
msgstr "Nastavení klienta"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114
|
||||||
|
msgid "Website"
|
||||||
|
msgstr "Webová stránka"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Pomoc"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/sidebar_sso.html:35
|
||||||
|
#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:127
|
||||||
|
msgid "Register a domain"
|
||||||
|
msgstr "Registrovat doménu"
|
||||||
|
|
||||||
|
#: mailu/sso/templates/sidebar_sso.html:49 mailu/ui/forms.py:95
|
||||||
|
#: mailu/ui/templates/sidebar.html:149 mailu/ui/templates/user/signup.html:4
|
||||||
|
#: mailu/ui/templates/user/signup_domain.html:4
|
||||||
|
msgid "Sign up"
|
||||||
|
msgstr "Registrovat se"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:33 mailu/ui/forms.py:36
|
||||||
|
msgid "Invalid email address."
|
||||||
|
msgstr "Špatná mailová adresa."
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:45
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr "Potvrdit"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:48 mailu/ui/forms.py:58
|
||||||
|
#: mailu/ui/templates/domain/details.html:26
|
||||||
|
#: mailu/ui/templates/domain/list.html:19 mailu/ui/templates/relay/list.html:18
|
||||||
|
msgid "Domain name"
|
||||||
|
msgstr "Název domény"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:49
|
||||||
|
msgid "Maximum user count"
|
||||||
|
msgstr "Maximální počet uživatelů"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:50
|
||||||
|
msgid "Maximum alias count"
|
||||||
|
msgstr "Maximální počet aliasů"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:51
|
||||||
|
msgid "Maximum user quota"
|
||||||
|
msgstr "Maximální uživatelská kvóta"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:52
|
||||||
|
msgid "Enable sign-up"
|
||||||
|
msgstr "Povolit registraci"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:53 mailu/ui/forms.py:74 mailu/ui/forms.py:86
|
||||||
|
#: mailu/ui/forms.py:132 mailu/ui/forms.py:144
|
||||||
|
#: mailu/ui/templates/alias/list.html:22 mailu/ui/templates/domain/list.html:22
|
||||||
|
#: mailu/ui/templates/relay/list.html:20 mailu/ui/templates/token/list.html:20
|
||||||
|
#: mailu/ui/templates/user/list.html:24
|
||||||
|
msgid "Comment"
|
||||||
|
msgstr "Komentář"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:54 mailu/ui/forms.py:68 mailu/ui/forms.py:75
|
||||||
|
#: mailu/ui/forms.py:88 mailu/ui/forms.py:136 mailu/ui/forms.py:145
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Uložit"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:59
|
||||||
|
msgid "Initial admin"
|
||||||
|
msgstr "Hlavní admin"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:60
|
||||||
|
msgid "Admin password"
|
||||||
|
msgstr "Heslo admina"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94
|
||||||
|
msgid "Confirm password"
|
||||||
|
msgstr "Potvrdit heslo"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:63
|
||||||
|
msgid "Create"
|
||||||
|
msgstr "Vytvořit"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:67
|
||||||
|
msgid "Alternative name"
|
||||||
|
msgstr "Alternativní jméno"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:72
|
||||||
|
msgid "Relayed domain name"
|
||||||
|
msgstr "Seznam předávaných domén"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19
|
||||||
|
msgid "Remote host"
|
||||||
|
msgstr "Vzdálený hostitel"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23
|
||||||
|
#: mailu/ui/templates/user/signup_domain.html:16
|
||||||
|
msgid "Quota"
|
||||||
|
msgstr "Kvóta"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:83
|
||||||
|
msgid "Allow IMAP access"
|
||||||
|
msgstr "Povolit přístup IMAP"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:84
|
||||||
|
msgid "Allow POP3 access"
|
||||||
|
msgstr "Povolit přístup POP3"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:85 mailu/ui/forms.py:101
|
||||||
|
#: mailu/ui/templates/user/settings.html:15
|
||||||
|
msgid "Displayed name"
|
||||||
|
msgstr "Zobrazené jméno"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:87
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr "Povoleno"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:92
|
||||||
|
msgid "Email address"
|
||||||
|
msgstr "Emailová adresa"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:102
|
||||||
|
msgid "Enable spam filter"
|
||||||
|
msgstr "Povolit filtr spamu"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:103
|
||||||
|
msgid "Enable marking spam mails as read"
|
||||||
|
msgstr "Povolit označování spamových e-mailů jako přečtených"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:104
|
||||||
|
msgid "Spam filter tolerance"
|
||||||
|
msgstr "Tolerance spamového filtru"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:105
|
||||||
|
msgid "Enable forwarding"
|
||||||
|
msgstr "Povolit přeposílání"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:106
|
||||||
|
msgid "Keep a copy of the emails"
|
||||||
|
msgstr "Zachovat kopii e-mailů"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:107 mailu/ui/forms.py:143
|
||||||
|
#: mailu/ui/templates/alias/list.html:21
|
||||||
|
msgid "Destination"
|
||||||
|
msgstr "Cíl"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:108
|
||||||
|
msgid "Save settings"
|
||||||
|
msgstr "Uložit nastavení"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:113
|
||||||
|
msgid "Password check"
|
||||||
|
msgstr "Kontrola hesla"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25
|
||||||
|
msgid "Update password"
|
||||||
|
msgstr "Aktualizovat heslo"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:118
|
||||||
|
msgid "Enable automatic reply"
|
||||||
|
msgstr "Povolit automatickou odpověď"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:119
|
||||||
|
msgid "Reply subject"
|
||||||
|
msgstr "Předmět odpovědi"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:120
|
||||||
|
msgid "Reply body"
|
||||||
|
msgstr "Tělo odpovědi"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:122
|
||||||
|
msgid "Start of vacation"
|
||||||
|
msgstr "Začátek dovolené"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:123
|
||||||
|
msgid "End of vacation"
|
||||||
|
msgstr "Konec dovolené"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:124
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Aktualizovat"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:129
|
||||||
|
msgid "Your token (write it down, as it will never be displayed again)"
|
||||||
|
msgstr "Váš token (zapište si ho, protože se již nikdy nezobrazí)"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21
|
||||||
|
msgid "Authorized IP"
|
||||||
|
msgstr "Autorizovaná IP"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:140
|
||||||
|
msgid "Alias"
|
||||||
|
msgstr "Alias"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:142
|
||||||
|
msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)"
|
||||||
|
msgstr "Použít syntaxi jako SQL (např. pro doménové koše)"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:149
|
||||||
|
msgid "Admin email"
|
||||||
|
msgstr "Email admina"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Poslat"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:154
|
||||||
|
msgid "Manager email"
|
||||||
|
msgstr "E-mail manažera"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:159
|
||||||
|
msgid "Protocol"
|
||||||
|
msgstr "Protokol"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:162
|
||||||
|
msgid "Hostname or IP"
|
||||||
|
msgstr "Hostitel nebo IP"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20
|
||||||
|
#: mailu/ui/templates/client.html:45
|
||||||
|
msgid "TCP port"
|
||||||
|
msgstr "TCP port"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:164
|
||||||
|
msgid "Enable TLS"
|
||||||
|
msgstr "Povolit TLS"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:165 mailu/ui/templates/client.html:28
|
||||||
|
#: mailu/ui/templates/client.html:53 mailu/ui/templates/fetch/list.html:21
|
||||||
|
msgid "Username"
|
||||||
|
msgstr "Uživatelské jméno"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:167
|
||||||
|
msgid "Keep emails on the server"
|
||||||
|
msgstr "Zachovat e-maily na serveru"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:172
|
||||||
|
msgid "Announcement subject"
|
||||||
|
msgstr "Předmět oznámení"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:174
|
||||||
|
msgid "Announcement body"
|
||||||
|
msgstr "Tělo oznámení"
|
||||||
|
|
||||||
|
#: mailu/ui/forms.py:176
|
||||||
|
msgid "Send"
|
||||||
|
msgstr "Poslat"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/announcement.html:4
|
||||||
|
msgid "Public announcement"
|
||||||
|
msgstr "Veřejné oznámení"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80
|
||||||
|
#: mailu/ui/templates/user/settings.html:19
|
||||||
|
msgid "Antispam"
|
||||||
|
msgstr "Antispam"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/antispam.html:8
|
||||||
|
msgid "RSPAMD status page"
|
||||||
|
msgstr "Stavová stránka RSPAMD"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/client.html:8
|
||||||
|
msgid "configure your email client"
|
||||||
|
msgstr "nakonfigurovat e-mailového klienta"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/client.html:13
|
||||||
|
msgid "Incoming mail"
|
||||||
|
msgstr "Příchozí mail"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41
|
||||||
|
msgid "Mail protocol"
|
||||||
|
msgstr "Poštovní protokol"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49
|
||||||
|
msgid "Server name"
|
||||||
|
msgstr "Název serveru"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/client.html:38
|
||||||
|
msgid "Outgoing mail"
|
||||||
|
msgstr "Odchozí pošta"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/confirm.html:4
|
||||||
|
msgid "Confirm action"
|
||||||
|
msgstr "Potvrdit akci"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/confirm.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid "You are about to %(action)s. Please confirm your action."
|
||||||
|
msgstr "Chystáte se %(action)s. Potvrďte prosím vaši akci."
|
||||||
|
|
||||||
|
#: mailu/ui/templates/docker-error.html:4
|
||||||
|
msgid "Docker error"
|
||||||
|
msgstr "Chyba Dockeru"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/docker-error.html:12
|
||||||
|
msgid "An error occurred while talking to the Docker server."
|
||||||
|
msgstr "Při komunikaci se serverem Docker došlo k chybě."
|
||||||
|
|
||||||
|
#: mailu/ui/templates/macros.html:129
|
||||||
|
msgid "copy to clipboard"
|
||||||
|
msgstr "zkopírovat do schránky"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:15
|
||||||
|
msgid "My account"
|
||||||
|
msgstr "Můj účet"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Nastavení"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38
|
||||||
|
msgid "Auto-reply"
|
||||||
|
msgstr "Automatická odpověď"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37
|
||||||
|
#: mailu/ui/templates/user/list.html:39
|
||||||
|
msgid "Fetched accounts"
|
||||||
|
msgstr "Fetched účty"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4
|
||||||
|
msgid "Authentication tokens"
|
||||||
|
msgstr "Autentizační tokeny"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:56
|
||||||
|
msgid "Administration"
|
||||||
|
msgstr "Administrace"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:62
|
||||||
|
msgid "Announcement"
|
||||||
|
msgstr "Oznámení"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:68
|
||||||
|
msgid "Administrators"
|
||||||
|
msgstr "Administrátoři"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:74
|
||||||
|
msgid "Relayed domains"
|
||||||
|
msgstr "Relayované domény"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:88
|
||||||
|
msgid "Mail domains"
|
||||||
|
msgstr "Poštovní domény"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:99
|
||||||
|
msgid "Webmail"
|
||||||
|
msgstr "Webmail"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/sidebar.html:135
|
||||||
|
msgid "Sign out"
|
||||||
|
msgstr "Odhlásit se"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/working.html:4
|
||||||
|
msgid "We are still working on this feature!"
|
||||||
|
msgstr "Na této funkci stále pracujeme!"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/admin/create.html:4
|
||||||
|
msgid "Add a global administrator"
|
||||||
|
msgstr "Přidat globálního administrátora"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/admin/list.html:4
|
||||||
|
msgid "Global administrators"
|
||||||
|
msgstr "Globální administrátor"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/admin/list.html:9
|
||||||
|
msgid "Add administrator"
|
||||||
|
msgstr "Přidat administrátora"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/admin/list.html:17 mailu/ui/templates/alias/list.html:19
|
||||||
|
#: mailu/ui/templates/alternative/list.html:19
|
||||||
|
#: mailu/ui/templates/domain/list.html:17 mailu/ui/templates/fetch/list.html:19
|
||||||
|
#: mailu/ui/templates/manager/list.html:19
|
||||||
|
#: mailu/ui/templates/relay/list.html:17 mailu/ui/templates/token/list.html:19
|
||||||
|
#: mailu/ui/templates/user/list.html:19
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Akce"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/admin/list.html:18 mailu/ui/templates/alias/list.html:20
|
||||||
|
#: mailu/ui/templates/manager/list.html:20 mailu/ui/templates/user/list.html:21
|
||||||
|
msgid "Email"
|
||||||
|
msgstr "Email"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/admin/list.html:25 mailu/ui/templates/alias/list.html:32
|
||||||
|
#: mailu/ui/templates/alternative/list.html:29
|
||||||
|
#: mailu/ui/templates/domain/list.html:34 mailu/ui/templates/fetch/list.html:34
|
||||||
|
#: mailu/ui/templates/manager/list.html:27
|
||||||
|
#: mailu/ui/templates/relay/list.html:30 mailu/ui/templates/token/list.html:30
|
||||||
|
#: mailu/ui/templates/user/list.html:34
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Vymazat"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alias/create.html:4
|
||||||
|
msgid "Create alias"
|
||||||
|
msgstr "Vytvořit alias"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alias/edit.html:4
|
||||||
|
msgid "Edit alias"
|
||||||
|
msgstr "Upravit alias"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alias/list.html:4
|
||||||
|
msgid "Alias list"
|
||||||
|
msgstr "Seznam aliasů"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alias/list.html:12
|
||||||
|
msgid "Add alias"
|
||||||
|
msgstr "Přidat alias"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alias/list.html:23
|
||||||
|
#: mailu/ui/templates/alternative/list.html:21
|
||||||
|
#: mailu/ui/templates/domain/list.html:23 mailu/ui/templates/fetch/list.html:25
|
||||||
|
#: mailu/ui/templates/relay/list.html:21 mailu/ui/templates/token/list.html:22
|
||||||
|
#: mailu/ui/templates/user/list.html:25
|
||||||
|
msgid "Created"
|
||||||
|
msgstr "Vytvořeno"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alias/list.html:24
|
||||||
|
#: mailu/ui/templates/alternative/list.html:22
|
||||||
|
#: mailu/ui/templates/domain/list.html:24 mailu/ui/templates/fetch/list.html:26
|
||||||
|
#: mailu/ui/templates/relay/list.html:22 mailu/ui/templates/token/list.html:23
|
||||||
|
#: mailu/ui/templates/user/list.html:26
|
||||||
|
msgid "Last edit"
|
||||||
|
msgstr "Poslední úprava"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alias/list.html:31 mailu/ui/templates/domain/list.html:33
|
||||||
|
#: mailu/ui/templates/fetch/list.html:33 mailu/ui/templates/relay/list.html:29
|
||||||
|
#: mailu/ui/templates/user/list.html:33
|
||||||
|
msgid "Edit"
|
||||||
|
msgstr "Upravit"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alternative/create.html:4
|
||||||
|
msgid "Create alternative domain"
|
||||||
|
msgstr "Vytvořit alternativní doménu"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alternative/list.html:4
|
||||||
|
msgid "Alternative domain list"
|
||||||
|
msgstr "Seznam alternativních domén"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alternative/list.html:12
|
||||||
|
msgid "Add alternative"
|
||||||
|
msgstr "Přidat alternativu"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/alternative/list.html:20
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Jméno"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/create.html:4
|
||||||
|
#: mailu/ui/templates/domain/list.html:9
|
||||||
|
msgid "New domain"
|
||||||
|
msgstr "Nová doména"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:4
|
||||||
|
msgid "Domain details"
|
||||||
|
msgstr "Podrobnosti o doméně"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:15
|
||||||
|
msgid "Regenerate keys"
|
||||||
|
msgstr "Obnovit klíče"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:17
|
||||||
|
msgid "Generate keys"
|
||||||
|
msgstr "Generovat klíče"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:30
|
||||||
|
msgid "DNS MX entry"
|
||||||
|
msgstr "Záznam DNS MX"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:34
|
||||||
|
msgid "DNS SPF entries"
|
||||||
|
msgstr "Záznamy DNS SPF"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:40
|
||||||
|
msgid "DKIM public key"
|
||||||
|
msgstr "Veřejný klíč DKIM"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:44
|
||||||
|
msgid "DNS DKIM entry"
|
||||||
|
msgstr "Záznam DNS DKIM"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:48
|
||||||
|
msgid "DNS DMARC entry"
|
||||||
|
msgstr "Záznam DNS DMARC"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:58
|
||||||
|
msgid "DNS TLSA entry"
|
||||||
|
msgstr "Záznam DNS TLSA"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/details.html:63
|
||||||
|
msgid "DNS client auto-configuration entries"
|
||||||
|
msgstr "Položky automatické konfigurace klienta DNS"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/edit.html:4
|
||||||
|
msgid "Edit domain"
|
||||||
|
msgstr "Upravit doménu"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:4
|
||||||
|
msgid "Domain list"
|
||||||
|
msgstr "Seznam domén"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:18
|
||||||
|
msgid "Manage"
|
||||||
|
msgstr "Spravovat"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:20
|
||||||
|
msgid "Mailbox count"
|
||||||
|
msgstr "Počet poštovních schránek"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:21
|
||||||
|
msgid "Alias count"
|
||||||
|
msgstr "Počet aliasů"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:31
|
||||||
|
msgid "Details"
|
||||||
|
msgstr "Podrobnosti"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:38
|
||||||
|
msgid "Users"
|
||||||
|
msgstr "Uživatelů"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:39
|
||||||
|
msgid "Aliases"
|
||||||
|
msgstr "Aliasů"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:40
|
||||||
|
msgid "Managers"
|
||||||
|
msgstr "Manažerů"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/list.html:42
|
||||||
|
msgid "Alternatives"
|
||||||
|
msgstr "Alternativ"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/signup.html:13
|
||||||
|
msgid ""
|
||||||
|
"In order to register a new domain, you must first setup the\n"
|
||||||
|
" domain zone so that the domain <code>MX</code> points to this server"
|
||||||
|
msgstr ""
|
||||||
|
"Chcete-li zaregistrovat novou doménu, musíte nejprve nastavit\n"
|
||||||
|
" zónu domény tak, aby doménový <code>MX</code> záznam ukazovala na tento "
|
||||||
|
"server"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/domain/signup.html:18
|
||||||
|
msgid ""
|
||||||
|
"If you do not know how to setup an <code>MX</code> record for your DNS "
|
||||||
|
"zone,\n"
|
||||||
|
" please contact your DNS provider or administrator. Also, please wait a\n"
|
||||||
|
" couple minutes after the <code>MX</code> is set so the local server "
|
||||||
|
"cache\n"
|
||||||
|
" expires."
|
||||||
|
msgstr ""
|
||||||
|
"Pokud nevíte, jak nastavit <code>MX</code> záznam pro zónu DNS,\n"
|
||||||
|
" kontaktujte svého poskytovatele DNS nebo správce. Také prosím počkejte "
|
||||||
|
"a\n"
|
||||||
|
" několik minut po <code>MX</code> tak, aby vypršela v mezipaměti "
|
||||||
|
"místního\n"
|
||||||
|
" serveru."
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/create.html:4
|
||||||
|
msgid "Add a fetched account"
|
||||||
|
msgstr "Přidejte fetched účet"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/edit.html:4
|
||||||
|
msgid "Update a fetched account"
|
||||||
|
msgstr "Aktualizujte fetched účet"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:12
|
||||||
|
msgid "Add an account"
|
||||||
|
msgstr "Přidat účet"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:20
|
||||||
|
msgid "Endpoint"
|
||||||
|
msgstr "Koncový bod"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:22
|
||||||
|
msgid "Keep emails"
|
||||||
|
msgstr "Zachovat emaily"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:23
|
||||||
|
msgid "Last check"
|
||||||
|
msgstr "Poslední kontrola"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:24
|
||||||
|
msgid "Status"
|
||||||
|
msgstr "Status"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:38
|
||||||
|
msgid "yes"
|
||||||
|
msgstr "ano"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/fetch/list.html:38
|
||||||
|
msgid "no"
|
||||||
|
msgstr "ne"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/manager/create.html:4
|
||||||
|
msgid "Add a manager"
|
||||||
|
msgstr "Přidat manažera"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/manager/list.html:4
|
||||||
|
msgid "Manager list"
|
||||||
|
msgstr "Seznam manažerů"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/manager/list.html:12
|
||||||
|
msgid "Add manager"
|
||||||
|
msgstr "Přidat manažera"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/relay/create.html:4
|
||||||
|
msgid "New relay domain"
|
||||||
|
msgstr "Nová relay doména"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/relay/edit.html:4
|
||||||
|
msgid "Edit relayed domain"
|
||||||
|
msgstr "Upravit relay doménu"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/relay/list.html:4
|
||||||
|
msgid "Relayed domain list"
|
||||||
|
msgstr "Seznam relay domén"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/relay/list.html:9
|
||||||
|
msgid "New relayed domain"
|
||||||
|
msgstr "Nová relay doména"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/token/create.html:4
|
||||||
|
msgid "Create an authentication token"
|
||||||
|
msgstr "Vytvořit ověřovací token"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/token/list.html:12
|
||||||
|
msgid "New token"
|
||||||
|
msgstr "Nový token"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/create.html:4
|
||||||
|
msgid "New user"
|
||||||
|
msgstr "Nový uživatel"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/create.html:15
|
||||||
|
msgid "General"
|
||||||
|
msgstr "Všeobecné"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/create.html:23
|
||||||
|
msgid "Features and quotas"
|
||||||
|
msgstr "Funkce a kvóty"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/edit.html:4
|
||||||
|
msgid "Edit user"
|
||||||
|
msgstr "Upravit uživatele"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/list.html:4
|
||||||
|
msgid "User list"
|
||||||
|
msgstr "Seznam uživatelů"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/list.html:12
|
||||||
|
msgid "Add user"
|
||||||
|
msgstr "Přidat uživatele"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/list.html:20 mailu/ui/templates/user/settings.html:4
|
||||||
|
msgid "User settings"
|
||||||
|
msgstr "Uživatelské nastavení"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/list.html:22
|
||||||
|
msgid "Features"
|
||||||
|
msgstr "Funkce"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/password.html:4
|
||||||
|
msgid "Password update"
|
||||||
|
msgstr "Aktualizace hesla"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/reply.html:4
|
||||||
|
msgid "Automatic reply"
|
||||||
|
msgstr "Automatická odpověď"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/settings.html:27
|
||||||
|
msgid "Auto-forward"
|
||||||
|
msgstr "Automatické přeposlání"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/signup_domain.html:8
|
||||||
|
msgid "pick a domain for the new account"
|
||||||
|
msgstr "vybrat doménu pro nový účet"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/signup_domain.html:14
|
||||||
|
msgid "Domain"
|
||||||
|
msgstr "Doména"
|
||||||
|
|
||||||
|
#: mailu/ui/templates/user/signup_domain.html:15
|
||||||
|
msgid "Available slots"
|
||||||
|
msgstr "Dostupných slotů"
|
@ -1,3 +1,3 @@
|
|||||||
pip==22.3
|
pip==22.3.1
|
||||||
setuptools==65.5.0
|
setuptools==65.6.3
|
||||||
wheel==0.37.1
|
wheel==0.38.4
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
# syntax=docker/dockerfile-upstream:1.4.3
|
||||||
|
|
||||||
|
# oletools image
|
||||||
|
FROM base
|
||||||
|
|
||||||
|
ARG VERSION=local
|
||||||
|
LABEL version=$VERSION
|
||||||
|
|
||||||
|
RUN set -euxo pipefail \
|
||||||
|
; apk add --no-cache netcat-openbsd libmagic libffi \
|
||||||
|
; curl -sLo olefy.py https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py \
|
||||||
|
; chmod 755 olefy.py
|
||||||
|
|
||||||
|
RUN echo $VERSION >/version
|
||||||
|
|
||||||
|
HEALTHCHECK --start-period=60s CMD echo PING|nc -q1 127.0.0.1 11343|grep "PONG"
|
||||||
|
#EXPOSE 11343/tcp
|
||||||
|
|
||||||
|
USER nobody:nobody
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
OLEFY_BINDADDRESS="" \
|
||||||
|
OLEFY_BINDPORT="11343" \
|
||||||
|
OLEFY_OLEVBA_PATH="/app/venv/bin/olevba" \
|
||||||
|
OLEFY_PYTHON_PATH="/app/venv/bin/python3" \
|
||||||
|
OLEFY_TMPDIR="/dev/shm/" \
|
||||||
|
OLEFY_MINLENGTH="300" \
|
||||||
|
OLEFY_DEL_TMP="1" \
|
||||||
|
OLEFY_DEL_TMP_FAILED="1"
|
||||||
|
|
||||||
|
CMD /app/olefy.py
|
@ -0,0 +1,14 @@
|
|||||||
|
{% if SCAN_MACROS == 'True' %}
|
||||||
|
OLETOOLS_MACRO_MRAPTOR {
|
||||||
|
expression = "(OLETOOLS_A & OLETOOLS_W) | (OLETOOLS_A & OLETOOLS_X) | (OLETOOLS_W & OLETOOLS_X)";
|
||||||
|
message = "Rejected (malicious macro - mraptor)";
|
||||||
|
policy = "leave";
|
||||||
|
score = 20.0;
|
||||||
|
}
|
||||||
|
OLETOOLS_MACRO_SUSPICIOUS {
|
||||||
|
expression = "OLETOOLS_FLAG | OLETOOLS_VBASTOMP | OLETOOLS_A";
|
||||||
|
message = "Rejected (malicious macro)";
|
||||||
|
policy = "leave";
|
||||||
|
score = 20.0;
|
||||||
|
}
|
||||||
|
{% endif %}
|
@ -0,0 +1,64 @@
|
|||||||
|
{% if SCAN_MACROS == 'True' %}
|
||||||
|
oletools {
|
||||||
|
# default olefy settings
|
||||||
|
servers = "{{ OLETOOLS_ADDRESS }}:11343"
|
||||||
|
|
||||||
|
# needs to be set explicitly for Rspamd < 1.9.5
|
||||||
|
scan_mime_parts = true;
|
||||||
|
extended = true;
|
||||||
|
max_size = 3145728;
|
||||||
|
timeout = 20.0;
|
||||||
|
retransmits = 1;
|
||||||
|
|
||||||
|
patterns {
|
||||||
|
OLETOOLS_MACRO_FOUND= '^.....M..$';
|
||||||
|
OLETOOLS_AUTOEXEC = '^A....M..$';
|
||||||
|
OLETOOLS_FLAG = '^.....MS.$';
|
||||||
|
OLETOOLS_VBASTOMP = '^VBA Stomping$';
|
||||||
|
# see https://github.com/decalage2/oletools/blob/master/oletools/mraptor.py
|
||||||
|
OLETOOLS_A = '(?i)\b(?:Auto(?:Exec|_?Open|_?Close|Exit|New)|Document(?:_?Open|_Close|_?BeforeClose|Change|_New)|NewDocument|Workbook(?:_Open|_Activate|_Close|_BeforeClose)|\w+_(?:Painted|Painting|GotFocus|LostFocus|MouseHover|Layout|Click|Change|Resize|BeforeNavigate2|BeforeScriptExecute|DocumentComplete|DownloadBegin|DownloadComplete|FileDownload|NavigateComplete2|NavigateError|ProgressChange|PropertyChange|SetSecureLockIcon|StatusTextChange|TitleChange|MouseMove|MouseEnter|MouseLeave|OnConnecting))\b|Auto_Ope\b';
|
||||||
|
OLETOOLS_W = '(?i)\b(?:FileCopy|CopyFile|Kill|CreateTextFile|VirtualAlloc|RtlMoveMemory|URLDownloadToFileA?|AltStartupPath|WriteProcessMemory|ADODB\.Stream|WriteText|SaveToFile|SaveAs|SaveAsRTF|FileSaveAs|MkDir|RmDir|SaveSetting|SetAttr)\b|(?:\bOpen\b[^\n]+\b(?:Write|Append|Binary|Output|Random)\b)';
|
||||||
|
OLETOOLS_X = '(?i)\b(?:Shell|CreateObject|GetObject|SendKeys|RUN|CALL|MacScript|FollowHyperlink|CreateThread|ShellExecuteA?|ExecuteExcel4Macro|EXEC|REGISTER|SetTimer)\b|(?:\bDeclare\b[^\n]+\bLib\b)';
|
||||||
|
}
|
||||||
|
|
||||||
|
# mime-part regex matching in content-type or filename
|
||||||
|
mime_parts_filter_regex {
|
||||||
|
#UNKNOWN = "application\/octet-stream";
|
||||||
|
DOC2 = "application\/msword";
|
||||||
|
DOC3 = "application\/vnd\.ms-word.*";
|
||||||
|
XLS = "application\/vnd\.ms-excel.*";
|
||||||
|
PPT = "application\/vnd\.ms-powerpoint.*";
|
||||||
|
GENERIC = "application\/vnd\.openxmlformats-officedocument.*";
|
||||||
|
}
|
||||||
|
# mime-part filename extension matching (no regex)
|
||||||
|
mime_parts_filter_ext {
|
||||||
|
doc = "doc";
|
||||||
|
dot = "dot";
|
||||||
|
docx = "docx";
|
||||||
|
dotx = "dotx";
|
||||||
|
docm = "docm";
|
||||||
|
dotm = "dotm";
|
||||||
|
xls = "xls";
|
||||||
|
xlt = "xlt";
|
||||||
|
xla = "xla";
|
||||||
|
xlsx = "xlsx";
|
||||||
|
xltx = "xltx";
|
||||||
|
xlsm = "xlsm";
|
||||||
|
xltm = "xltm";
|
||||||
|
xlam = "xlam";
|
||||||
|
xlsb = "xlsb";
|
||||||
|
ppt = "ppt";
|
||||||
|
pot = "pot";
|
||||||
|
pps = "pps";
|
||||||
|
ppa = "ppa";
|
||||||
|
pptx = "pptx";
|
||||||
|
potx = "potx";
|
||||||
|
ppsx = "ppsx";
|
||||||
|
ppam = "ppam";
|
||||||
|
pptm = "pptm";
|
||||||
|
potm = "potm";
|
||||||
|
ppsm = "ppsm";
|
||||||
|
slk = "slk";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endif %}
|
@ -0,0 +1,40 @@
|
|||||||
|
{% if SCAN_MACROS == 'True' %}
|
||||||
|
# local.d/external_services_group.conf
|
||||||
|
|
||||||
|
description = "Oletools content rules";
|
||||||
|
symbols = {
|
||||||
|
"OLETOOLS" {
|
||||||
|
weight = 1.0;
|
||||||
|
description = "OLETOOLS found a Macro";
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
"OLETOOLS_MACRO_FOUND" {
|
||||||
|
weight = 0.0;
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
"OLETOOLS_AUTOEXEC" {
|
||||||
|
weight = 0.0;
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
"OLETOOLS_FLAG" {
|
||||||
|
weight = 0.0;
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
"OLETOOLS_VBASTOMP" {
|
||||||
|
weight = 0.0;
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
"OLETOOLS_A" {
|
||||||
|
weight = 0.0;
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
"OLETOOLS_W" {
|
||||||
|
weight = 0.0;
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
"OLETOOLS_X" {
|
||||||
|
weight = 0.0;
|
||||||
|
one_shot = true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
{% endif %}
|
@ -0,0 +1,68 @@
|
|||||||
|
ace
|
||||||
|
ade
|
||||||
|
adp
|
||||||
|
apk
|
||||||
|
appx
|
||||||
|
appxbundle
|
||||||
|
arj
|
||||||
|
bat
|
||||||
|
bin
|
||||||
|
cab
|
||||||
|
chm
|
||||||
|
class
|
||||||
|
cmd
|
||||||
|
com
|
||||||
|
cpl
|
||||||
|
diagcab
|
||||||
|
diagcfg
|
||||||
|
diagpack
|
||||||
|
dll
|
||||||
|
ex
|
||||||
|
ex_
|
||||||
|
exe
|
||||||
|
hlp
|
||||||
|
hta
|
||||||
|
img
|
||||||
|
ins
|
||||||
|
iso
|
||||||
|
isp
|
||||||
|
jar
|
||||||
|
jnlp
|
||||||
|
js
|
||||||
|
jse
|
||||||
|
lib
|
||||||
|
lnk
|
||||||
|
lzh
|
||||||
|
mde
|
||||||
|
msc
|
||||||
|
msi
|
||||||
|
msix
|
||||||
|
msixbundle
|
||||||
|
msp
|
||||||
|
mst
|
||||||
|
msu
|
||||||
|
nsh
|
||||||
|
ocx
|
||||||
|
ovl
|
||||||
|
pif
|
||||||
|
ps1
|
||||||
|
r01
|
||||||
|
r14
|
||||||
|
r18
|
||||||
|
r25
|
||||||
|
scr
|
||||||
|
sct
|
||||||
|
shb
|
||||||
|
shs
|
||||||
|
sys
|
||||||
|
vb
|
||||||
|
vbe
|
||||||
|
vbs
|
||||||
|
vbscript
|
||||||
|
vdl
|
||||||
|
vhd
|
||||||
|
vxd
|
||||||
|
wsc
|
||||||
|
wsf
|
||||||
|
wsh
|
||||||
|
xll
|
@ -1,17 +1,17 @@
|
|||||||
rules {
|
rules {
|
||||||
ANTISPOOF_NOAUTH {
|
ANTISPOOF_NOAUTH {
|
||||||
action = "reject";
|
action = "reject";
|
||||||
expression = "!MAILLIST & ((IS_LOCAL_DOMAIN_E & MISSING_FROM) | (IS_LOCAL_DOMAIN_H & (R_DKIM_NA & R_SPF_NA & DMARC_NA & ARC_NA)))";
|
expression = "!IS_LOCALLY_GENERATED & !MAILLIST & ((IS_LOCAL_DOMAIN_E & MISSING_FROM) | (IS_LOCAL_DOMAIN_H & (R_DKIM_NA & R_SPF_NA & DMARC_NA & ARC_NA)))";
|
||||||
message = "Rejected (anti-spoofing: noauth). Please setup DMARC with DKIM or SPF if you want to send emails from your domain from other servers.";
|
message = "Rejected (anti-spoofing: noauth). Please setup DMARC with DKIM or SPF if you want to send emails from your domain from other servers.";
|
||||||
}
|
}
|
||||||
ANTISPOOF_DMARC_ENFORCE_LOCAL {
|
ANTISPOOF_DMARC_ENFORCE_LOCAL {
|
||||||
action = "reject";
|
action = "reject";
|
||||||
expression = "!MAILLIST & (IS_LOCAL_DOMAIN_H | IS_LOCAL_DOMAIN_E) & (DMARC_POLICY_SOFTFAIL | DMARC_POLICY_REJECT | DMARC_POLICY_QUARANTINE | DMARC_NA)";
|
expression = "!IS_LOCALLY_GENERATED & !MAILLIST & (IS_LOCAL_DOMAIN_H | IS_LOCAL_DOMAIN_E) & (DMARC_POLICY_SOFTFAIL | DMARC_POLICY_REJECT | DMARC_POLICY_QUARANTINE | DMARC_NA)";
|
||||||
message = "Rejected (anti-spoofing: DMARC compliance is enforced for local domains, regardless of the policy setting)";
|
message = "Rejected (anti-spoofing: DMARC compliance is enforced for local domains, regardless of the policy setting)";
|
||||||
}
|
}
|
||||||
ANTISPOOF_AUTH_FAILED {
|
ANTISPOOF_AUTH_FAILED {
|
||||||
action = "reject";
|
action = "reject";
|
||||||
expression = "!MAILLIST & BLACKLIST_ANTISPOOF";
|
expression = "!IS_LOCALLY_GENERATED & !MAILLIST & BLACKLIST_ANTISPOOF";
|
||||||
message = "Rejected (anti-spoofing: auth-failed)";
|
message = "Rejected (anti-spoofing: auth-failed)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
{{ SUBNET }}
|
||||||
|
{{ SUBNET6 }}
|
@ -1,3 +1 @@
|
|||||||
{% if RELAYNETS %}
|
local_networks = [{{ SUBNET }}{% if SUBNET6 %}, {{ SUBNET6 }}{% endif %}{% if RELAYNETS %}, {{ RELAYNETS }}{% endif %}];
|
||||||
local_networks = [{{ RELAYNETS }}];
|
|
||||||
{% endif %}
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
Mailu RESTful API
|
||||||
|
=================
|
||||||
|
|
||||||
|
Mailu offers a RESTful API for changing the Mailu configuration.
|
||||||
|
Anything that can be configured via the Mailu web administration interface,
|
||||||
|
can also be configured via the API.
|
||||||
|
|
||||||
|
The Mailu API can be configured via the setup utility (setup.mailu.io).
|
||||||
|
It can also be manually configured via mailu.env:
|
||||||
|
|
||||||
|
* ``API`` - Expose the API interface (value: true, false)
|
||||||
|
* ``WEB_API`` - Path to the API interface
|
||||||
|
* ``API_TOKEN`` - API token for authentication
|
||||||
|
|
||||||
|
For more information refer to the detailed descriptions in the
|
||||||
|
:ref:`configuration reference <advanced_settings>`.
|
||||||
|
|
||||||
|
|
||||||
|
Swagger.json
|
||||||
|
------------
|
||||||
|
|
||||||
|
The swagger.json file can be retrieved via: https://myserver/api/v1/swagger.json
|
||||||
|
(WEB_API=/api)
|
||||||
|
The swagger.json file can be consumed in programs such as Postman for generating all API calls.
|
||||||
|
|
||||||
|
|
||||||
|
In-built SwaggerUI
|
||||||
|
------------------
|
||||||
|
The Mailu API comes with an in-built SwaggerUI. It is a web client that allows
|
||||||
|
anyone to visualize and interact with the Mailu API.
|
||||||
|
|
||||||
|
Assuming ``/api`` is configured as value for ``WEB_API``, it
|
||||||
|
is accessible via the URL: https://myserver/api/
|
@ -1,5 +1,5 @@
|
|||||||
recommonmark
|
recommonmark==0.7.1
|
||||||
Sphinx
|
Sphinx==5.2.0
|
||||||
sphinx-autobuild
|
sphinx-autobuild==2021.3.14
|
||||||
sphinx-rtd-theme
|
sphinx-rtd-theme==1.0.0
|
||||||
docutils==0.16
|
docutils==0.16
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue