Merge branch 'master' of github.com:Mailu/Mailu into fix-sso-1929
@@ -8,7 +8,7 @@
|
||||
- Mention an issue like: #001
|
||||
- Auto close an issue like: closes #001
|
||||
|
||||
## Prerequistes
|
||||
## Prerequisites
|
||||
Before we can consider review and merge, please make sure the following list is done and checked.
|
||||
If an entry in not applicable, you can check it or remove it from the list.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Main features include:
|
||||
- **Web access**, multiple Webmails and administration interface
|
||||
- **User features**, aliases, auto-reply, auto-forward, fetched accounts
|
||||
- **Admin features**, global admins, announcements, per-domain delegation, quotas
|
||||
- **Security**, enforced TLS, Letsencrypt!, outgoing DKIM, anti-virus scanner
|
||||
- **Security**, enforced TLS, DANE, MTA-STS, Letsencrypt!, outgoing DKIM, anti-virus scanner
|
||||
- **Antispam**, auto-learn, greylisting, DMARC and SPF
|
||||
- **Freedom**, all FOSS components, no tracker included
|
||||
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
# First stage to build assets
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
ARG ARCH=""
|
||||
|
||||
FROM ${ARCH}node:16 as assets
|
||||
COPY --from=balenalib/rpi-alpine:3.14 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static
|
||||
|
||||
COPY package.json ./
|
||||
RUN npm install
|
||||
RUN set -eu \
|
||||
&& npm config set update-notifier false \
|
||||
&& npm install --no-fund
|
||||
|
||||
COPY ./webpack.config.js ./
|
||||
COPY ./assets ./assets
|
||||
RUN mkdir static \
|
||||
&& ./node_modules/.bin/webpack-cli
|
||||
COPY webpack.config.js ./
|
||||
COPY assets ./assets
|
||||
RUN set -eu \
|
||||
&& sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \
|
||||
&& for l in ca da de:de_de en:en-gb es:es_es eu fr:fr_fr he hu is it:it_it ja nb_NO:no_nb nl:nl_nl pl pt:pt_pt ru sv:sv_se zh_CN:zh; do \
|
||||
cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \
|
||||
done \
|
||||
&& node_modules/.bin/webpack-cli --color
|
||||
|
||||
|
||||
# Actual application
|
||||
FROM $DISTRO
|
||||
COPY --from=balenalib/rpi-alpine:3.14 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static
|
||||
|
||||
# python3 shared with most images
|
||||
RUN apk add --no-cache \
|
||||
python3 py3-pip git bash \
|
||||
&& pip3 install --upgrade pip
|
||||
RUN set -eu \
|
||||
&& apk add --no-cache python3 py3-pip git bash \
|
||||
&& pip3 install --upgrade pip
|
||||
|
||||
RUN mkdir -p /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements-prod.txt requirements.txt
|
||||
RUN apk add --no-cache openssl curl postgresql-libs mariadb-connector-c \
|
||||
&& apk add --no-cache --virtual build-dep \
|
||||
openssl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev cargo \
|
||||
&& pip3 install -r requirements.txt \
|
||||
&& apk del --no-cache build-dep
|
||||
RUN set -eu \
|
||||
&& apk add --no-cache libressl curl postgresql-libs mariadb-connector-c \
|
||||
&& apk add --no-cache --virtual build-dep libressl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev cargo \
|
||||
&& pip3 install -r requirements.txt \
|
||||
&& apk del --no-cache build-dep
|
||||
|
||||
COPY --from=assets static ./mailu/ui/static
|
||||
COPY --from=assets static ./mailu/sso/static
|
||||
|
||||
@@ -1,23 +1,54 @@
|
||||
.select2-search--inline .select2-search__field:focus {
|
||||
border: none;
|
||||
/* mailu logo */
|
||||
.mailu-logo {
|
||||
opacity: .8;
|
||||
}
|
||||
.bg-mailu-logo {
|
||||
background-color: #2980b9!important;
|
||||
}
|
||||
|
||||
.sidebar h4 {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* user image */
|
||||
.div-circle {
|
||||
position: relative;
|
||||
width: 2.1rem;
|
||||
height: 2.1rem;
|
||||
opacity: .8;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.div-circle > i {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%)
|
||||
}
|
||||
|
||||
.sidebar-collapse .sidebar h4 {
|
||||
display: none !important;
|
||||
/* nice round preformatted configuration display */
|
||||
.pre-config {
|
||||
padding: 9px;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: anywhere;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.logo a {
|
||||
color: #fff;
|
||||
/* fieldset */
|
||||
legend {
|
||||
font-size: inherit;
|
||||
}
|
||||
fieldset:disabled :not(legend) label {
|
||||
opacity: .5;
|
||||
}
|
||||
fieldset:disabled .form-control:disabled {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
padding: unset !important;
|
||||
/* fix animation for icons in menu text */
|
||||
.sidebar .nav-link p i {
|
||||
transition: margin-left .3s linear,opacity .3s ease,visibility .3s ease;
|
||||
}
|
||||
|
||||
/* fix select2 text color */
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,70 @@
|
||||
require('./app.css');
|
||||
|
||||
import 'admin-lte/plugins/select2/js/select2.js';
|
||||
import 'admin-lte/plugins/datatables/jquery.dataTables.js';
|
||||
import 'admin-lte/plugins/datatables-bs4/js/dataTables.bootstrap4.js';
|
||||
import 'admin-lte/plugins/datatables-responsive/js/dataTables.responsive.js';
|
||||
import 'admin-lte/plugins/datatables-responsive/js/responsive.bootstrap4.js';
|
||||
import logo from './mailu.png';
|
||||
import modules from "./*.json";
|
||||
|
||||
jQuery("document").ready(function() {
|
||||
jQuery(".mailselect").select2({
|
||||
// TODO: conditionally (or lazy) load select2 and dataTable
|
||||
$('document').ready(function() {
|
||||
|
||||
// intercept anchors with data-clicked attribute and open alternate location instead
|
||||
$('[data-clicked]').click(function(e) {
|
||||
e.preventDefault();
|
||||
window.location.href = $(this).data('clicked');
|
||||
});
|
||||
|
||||
// use post for language selection
|
||||
$('#mailu-languages > a').click(function(e) {
|
||||
e.preventDefault();
|
||||
$.post({
|
||||
url: $(this).attr('href'),
|
||||
success: function() {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// allow en-/disabling of inputs in fieldset with checkbox in legend
|
||||
$('fieldset legend input[type=checkbox]').change(function() {
|
||||
var fieldset = $(this).parents('fieldset');
|
||||
if (this.checked) {
|
||||
fieldset.removeAttr('disabled');
|
||||
fieldset.find('input').not(this).removeAttr('disabled');
|
||||
} else {
|
||||
fieldset.attr('disabled', '');
|
||||
fieldset.find('input').not(this).attr('disabled', '');
|
||||
}
|
||||
});
|
||||
|
||||
// display of range input value
|
||||
$('input[type=range]').each(function() {
|
||||
var value_element = $('#'+this.id+'_value');
|
||||
if (value_element.length) {
|
||||
value_element = $(value_element[0]);
|
||||
var infinity = $(this).data('infinity');
|
||||
var step = $(this).attr('step');
|
||||
$(this).on('input', function() {
|
||||
value_element.text((infinity && this.value == 0) ? '∞' : this.value/step);
|
||||
}).trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
// init select2
|
||||
$('.mailselect').select2({
|
||||
tags: true,
|
||||
tokenSeparators: [',', ' ']
|
||||
tokenSeparators: [',', ' '],
|
||||
});
|
||||
jQuery(".dataTable").DataTable({
|
||||
"responsive": true,
|
||||
|
||||
// init dataTable
|
||||
var d = $(document.documentElement);
|
||||
$('.dataTable').DataTable({
|
||||
'responsive': true,
|
||||
language: {
|
||||
url: d.data('static') + d.attr('lang') + '.json',
|
||||
},
|
||||
});
|
||||
|
||||
// init clipboard.js
|
||||
new ClipboardJS('.btn-clip');
|
||||
|
||||
});
|
||||
|
||||
|
||||
BIN
core/admin/assets/mailu.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
@@ -1,22 +1,24 @@
|
||||
// jQuery
|
||||
import jQuery from 'jquery';
|
||||
import 'admin-lte/plugins/select2/css/select2.css';
|
||||
|
||||
// bootstrap
|
||||
// import 'bootstrap/less/bootstrap.less';
|
||||
// import 'bootstrap';
|
||||
|
||||
// FontAwesome
|
||||
import 'admin-lte/plugins/fontawesome-free/css/fontawesome.css';
|
||||
import 'admin-lte/plugins/fontawesome-free/css/regular.css';
|
||||
import 'admin-lte/plugins/fontawesome-free/css/solid.css';
|
||||
|
||||
// AdminLTE
|
||||
import 'admin-lte/plugins/jquery/jquery.min.js';
|
||||
import 'admin-lte/plugins/bootstrap/js/bootstrap.bundle.min.js';
|
||||
import 'admin-lte/build/scss/adminlte.scss';
|
||||
import 'admin-lte/plugins/datatables-bs4/css/dataTables.bootstrap4.css';
|
||||
import 'admin-lte/plugins/datatables-responsive/css/responsive.bootstrap4.css';
|
||||
import 'admin-lte/plugins/bootstrap/js/bootstrap.js';
|
||||
import 'admin-lte/build/js/AdminLTE.js';
|
||||
import 'admin-lte/build/js/Layout.js';
|
||||
import 'admin-lte/build/js/ControlSidebar.js';
|
||||
import 'admin-lte/build/js/PushMenu.js';
|
||||
|
||||
// fontawesome plugin
|
||||
import 'admin-lte/plugins/fontawesome-free/css/all.min.css';
|
||||
|
||||
// select2 plugin
|
||||
import 'admin-lte/plugins/select2/css/select2.min.css';
|
||||
import 'admin-lte/plugins/select2/js/select2.min.js';
|
||||
|
||||
// dataTables plugin
|
||||
import 'admin-lte/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css';
|
||||
import 'admin-lte/plugins/datatables-responsive/css/responsive.bootstrap4.min.css';
|
||||
import 'admin-lte/plugins/datatables/jquery.dataTables.min.js';
|
||||
import 'admin-lte/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js';
|
||||
import 'admin-lte/plugins/datatables-responsive/js/dataTables.responsive.min.js';
|
||||
import 'admin-lte/plugins/datatables-responsive/js/responsive.bootstrap4.min.js';
|
||||
|
||||
// clipboard.js
|
||||
import 'clipboard/dist/clipboard.min.js';
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ def create_app_from_config(config):
|
||||
app = flask.Flask(__name__)
|
||||
app.cli.add_command(manage.mailu)
|
||||
|
||||
# Bootstrap is used for basic JS and CSS loading
|
||||
# TODO: remove this and use statically generated assets instead
|
||||
# Bootstrap is used for error display and flash messages
|
||||
app.bootstrap = flask_bootstrap.Bootstrap(app)
|
||||
|
||||
# Initialize application extensions
|
||||
@@ -31,6 +30,15 @@ def create_app_from_config(config):
|
||||
|
||||
app.temp_token_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('WEBMAIL_TEMP_TOKEN_KEY', 'utf-8'), 'sha256').digest()
|
||||
|
||||
# Initialize list of translations
|
||||
config.translations = {
|
||||
str(locale): locale
|
||||
for locale in sorted(
|
||||
utils.babel.list_translations(),
|
||||
key=lambda l: l.get_language_name().title()
|
||||
)
|
||||
}
|
||||
|
||||
# Initialize debugging tools
|
||||
if app.config.get("DEBUG"):
|
||||
debug.toolbar.init_app(app)
|
||||
@@ -43,8 +51,8 @@ def create_app_from_config(config):
|
||||
def inject_defaults():
|
||||
signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()
|
||||
return dict(
|
||||
signup_domains=signup_domains,
|
||||
config=app.config
|
||||
signup_domains= signup_domains,
|
||||
config = app.config,
|
||||
)
|
||||
|
||||
# Import views
|
||||
|
||||
@@ -35,6 +35,7 @@ DEFAULT_CONFIG = {
|
||||
'WILDCARD_SENDERS': '',
|
||||
'TLS_FLAVOR': 'cert',
|
||||
'INBOUND_TLS_ENFORCE': False,
|
||||
'DEFER_ON_TLS_ERROR': True,
|
||||
'AUTH_RATELIMIT': '1000/minute;10000/hour',
|
||||
'AUTH_RATELIMIT_SUBNET': False,
|
||||
'DISABLE_STATISTICS': False,
|
||||
@@ -57,6 +58,8 @@ DEFAULT_CONFIG = {
|
||||
'WEBMAIL': 'none',
|
||||
'RECAPTCHA_PUBLIC_KEY': '',
|
||||
'RECAPTCHA_PRIVATE_KEY': '',
|
||||
'LOGO_URL': None,
|
||||
'LOGO_BACKGROUND': None,
|
||||
# Advanced settings
|
||||
'LOG_LEVEL': 'WARNING',
|
||||
'SESSION_KEY_BITS': 128,
|
||||
@@ -144,6 +147,9 @@ class ConfigManager(dict):
|
||||
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
||||
self.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME']))
|
||||
hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
|
||||
self.config['HOSTNAMES'] = ','.join(hostnames)
|
||||
self.config['HOSTNAME'] = hostnames[0]
|
||||
# update the app config itself
|
||||
app.config = self
|
||||
|
||||
|
||||
@@ -71,16 +71,6 @@ def handle_authentication(headers):
|
||||
}
|
||||
# Authenticated user
|
||||
elif method == "plain":
|
||||
server, port = get_server(headers["Auth-Protocol"], True)
|
||||
# According to RFC2616 section 3.7.1 and PEP 3333, HTTP headers should
|
||||
# be ASCII and are generally considered ISO8859-1. However when passing
|
||||
# the password, nginx does not transcode the input UTF string, thus
|
||||
# we need to manually decode.
|
||||
raw_user_email = urllib.parse.unquote(headers["Auth-User"])
|
||||
user_email = raw_user_email.encode("iso8859-1").decode("utf8")
|
||||
raw_password = urllib.parse.unquote(headers["Auth-Pass"])
|
||||
password = raw_password.encode("iso8859-1").decode("utf8")
|
||||
ip = urllib.parse.unquote(headers["Client-Ip"])
|
||||
service_port = int(urllib.parse.unquote(headers["Auth-Port"]))
|
||||
if service_port == 25:
|
||||
return {
|
||||
@@ -88,20 +78,33 @@ def handle_authentication(headers):
|
||||
"Auth-Error-Code": "502 5.5.1",
|
||||
"Auth-Wait": 0
|
||||
}
|
||||
user = models.User.query.get(user_email)
|
||||
if check_credentials(user, password, ip, protocol):
|
||||
return {
|
||||
"Auth-Status": "OK",
|
||||
"Auth-Server": server,
|
||||
"Auth-Port": port
|
||||
}
|
||||
# According to RFC2616 section 3.7.1 and PEP 3333, HTTP headers should
|
||||
# be ASCII and are generally considered ISO8859-1. However when passing
|
||||
# the password, nginx does not transcode the input UTF string, thus
|
||||
# we need to manually decode.
|
||||
raw_user_email = urllib.parse.unquote(headers["Auth-User"])
|
||||
raw_password = urllib.parse.unquote(headers["Auth-Pass"])
|
||||
try:
|
||||
user_email = raw_user_email.encode("iso8859-1").decode("utf8")
|
||||
password = raw_password.encode("iso8859-1").decode("utf8")
|
||||
except:
|
||||
app.logger.warn(f'Received undecodable user/password from nginx: {raw_user_email!r}/{raw_password!r}')
|
||||
else:
|
||||
status, code = get_status(protocol, "authentication")
|
||||
return {
|
||||
"Auth-Status": status,
|
||||
"Auth-Error-Code": code,
|
||||
"Auth-Wait": 0
|
||||
}
|
||||
user = models.User.query.get(user_email)
|
||||
ip = urllib.parse.unquote(headers["Client-Ip"])
|
||||
if check_credentials(user, password, ip, protocol):
|
||||
server, port = get_server(headers["Auth-Protocol"], True)
|
||||
return {
|
||||
"Auth-Status": "OK",
|
||||
"Auth-Server": server,
|
||||
"Auth-Port": port
|
||||
}
|
||||
status, code = get_status(protocol, "authentication")
|
||||
return {
|
||||
"Auth-Status": status,
|
||||
"Auth-Error-Code": code,
|
||||
"Auth-Wait": 0
|
||||
}
|
||||
# Unexpected
|
||||
return {}
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import idna
|
||||
import re
|
||||
import srslib
|
||||
|
||||
@internal.route("/postfix/dane/<domain_name>")
|
||||
def postfix_dane_map(domain_name):
|
||||
return flask.jsonify('dane-only') if utils.has_dane_record(domain_name) else flask.abort(404)
|
||||
|
||||
@internal.route("/postfix/domain/<domain_name>")
|
||||
def postfix_mailbox_domain(domain_name):
|
||||
|
||||
@@ -48,44 +48,44 @@ def advertise():
|
||||
@click.argument('localpart')
|
||||
@click.argument('domain_name')
|
||||
@click.argument('password')
|
||||
@click.option('-m', '--mode')
|
||||
@click.option('-m', '--mode', default='create', metavar='MODE', help='''\b'create' (default): create user. it's an error if user already exists
|
||||
'ifmissing': only update password if user is missing
|
||||
'update': create user or update password if user exists
|
||||
''')
|
||||
@with_appcontext
|
||||
def admin(localpart, domain_name, password, mode='create'):
|
||||
def admin(localpart, domain_name, password, mode):
|
||||
""" Create an admin user
|
||||
'mode' can be:
|
||||
- 'create' (default) Will try to create user and will raise an exception if present
|
||||
- 'ifmissing': if user exists, nothing happens, else it will be created
|
||||
- 'update': user is created or, if it exists, its password gets updated
|
||||
"""
|
||||
|
||||
if not mode in ('create', 'update', 'ifmissing'):
|
||||
raise click.ClickException(f'invalid mode: {mode!r}')
|
||||
|
||||
domain = models.Domain.query.get(domain_name)
|
||||
if not domain:
|
||||
domain = models.Domain(name=domain_name)
|
||||
db.session.add(domain)
|
||||
|
||||
user = None
|
||||
if mode == 'ifmissing' or mode == 'update':
|
||||
email = f'{localpart}@{domain_name}'
|
||||
user = models.User.query.get(email)
|
||||
|
||||
if user and mode == 'ifmissing':
|
||||
print('user %s exists, not updating' % email)
|
||||
email = f'{localpart}@{domain_name}'
|
||||
if user := models.User.query.get(email):
|
||||
if mode == 'ifmissing':
|
||||
print(f'user {email!r} exists, not updating')
|
||||
return
|
||||
|
||||
if not user:
|
||||
elif mode == 'update':
|
||||
user.set_password(password)
|
||||
db.session.commit()
|
||||
print("updated admin password")
|
||||
else:
|
||||
raise click.ClickException(f'user {email!r} exists, not created')
|
||||
else:
|
||||
user = models.User(
|
||||
localpart=localpart,
|
||||
domain=domain,
|
||||
global_admin=True
|
||||
)
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
user.set_password(password)
|
||||
db.session.commit()
|
||||
print("created admin user")
|
||||
elif mode == 'update':
|
||||
user.set_password(password)
|
||||
db.session.commit()
|
||||
print("updated admin password")
|
||||
|
||||
|
||||
|
||||
@mailu.command()
|
||||
|
||||
@@ -209,16 +209,16 @@ class Domain(Base):
|
||||
os.unlink(file_path)
|
||||
self._dkim_key_on_disk = self._dkim_key
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def dns_mx(self):
|
||||
""" return MX record for domain """
|
||||
hostname = app.config['HOSTNAMES'].split(',', 1)[0]
|
||||
hostname = app.config['HOSTNAME']
|
||||
return f'{self.name}. 600 IN MX 10 {hostname}.'
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def dns_spf(self):
|
||||
""" return SPF record for domain """
|
||||
hostname = app.config['HOSTNAMES'].split(',', 1)[0]
|
||||
hostname = app.config['HOSTNAME']
|
||||
return f'{self.name}. 600 IN TXT "v=spf1 mx a:{hostname} ~all"'
|
||||
|
||||
@property
|
||||
@@ -226,12 +226,11 @@ class Domain(Base):
|
||||
""" return DKIM record for domain """
|
||||
if self.dkim_key:
|
||||
selector = app.config['DKIM_SELECTOR']
|
||||
return (
|
||||
f'{selector}._domainkey.{self.name}. 600 IN TXT'
|
||||
f'"v=DKIM1; k=rsa; p={self.dkim_publickey}"'
|
||||
)
|
||||
txt = f'v=DKIM1; k=rsa; p={self.dkim_publickey}'
|
||||
record = ' '.join(f'"{txt[p:p+250]}"' for p in range(0, len(txt), 250))
|
||||
return f'{selector}._domainkey.{self.name}. 600 IN TXT {record}'
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def dns_dmarc(self):
|
||||
""" return DMARC record for domain """
|
||||
if self.dkim_key:
|
||||
@@ -242,6 +241,34 @@ class Domain(Base):
|
||||
ruf = f' ruf=mailto:{ruf}@{domain};' if ruf else ''
|
||||
return f'_dmarc.{self.name}. 600 IN TXT "v=DMARC1; p=reject;{rua}{ruf} adkim=s; aspf=s"'
|
||||
|
||||
@cached_property
|
||||
def dns_autoconfig(self):
|
||||
""" return list of auto configuration records (RFC6186) """
|
||||
hostname = app.config['HOSTNAME']
|
||||
protocols = [
|
||||
('submission', 587),
|
||||
('imap', 143),
|
||||
('pop3', 110),
|
||||
]
|
||||
if app.config['TLS_FLAVOR'] != 'notls':
|
||||
protocols.extend([
|
||||
('imaps', 993),
|
||||
('pop3s', 995),
|
||||
])
|
||||
return list([
|
||||
f'_{proto}._tcp.{self.name}. 600 IN SRV 1 1 {port} {hostname}.'
|
||||
for proto, port
|
||||
in protocols
|
||||
])
|
||||
|
||||
@cached_property
|
||||
def dns_tlsa(self):
|
||||
""" return TLSA record for domain when using letsencrypt """
|
||||
hostname = app.config['HOSTNAME']
|
||||
if app.config['TLS_FLAVOR'] in ('letsencrypt', 'mail-letsencrypt'):
|
||||
# current ISRG Root X1 (RSA 4096, O = Internet Security Research Group, CN = ISRG Root X1) @20210902
|
||||
return f'_25._tcp.{hostname}. 600 IN TLSA 2 1 1 0b9fa5a59eed715c26c1020c711b4f6ec42d58b0015e14337a39dad301c5afc3'
|
||||
|
||||
@property
|
||||
def dkim_key(self):
|
||||
""" return private DKIM key """
|
||||
|
||||
@@ -1,188 +1,287 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Mailu\n"
|
||||
"Project-Id-Version: Mailu\n"
|
||||
"POT-Creation-Date: 2021-02-05 16:34+0100\n"
|
||||
"PO-Revision-Date: 2020-02-17 20:23+0000\n"
|
||||
"Last-Translator: NeroPcStation <dareknowacki2001@gmail.com>\n"
|
||||
"Language-Team: Polish <https://translate.tedomum.net/projects/mailu/admin/pl/"
|
||||
">\n"
|
||||
"Last-Translator: Marcin Siennicki <marcin@siennicki.eu>\n"
|
||||
"Language: pl\n"
|
||||
"Language-Team: Polish "
|
||||
"<https://translate.tedomum.net/projects/mailu/admin/pl/>\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && "
|
||||
"(n%100<10 || n%100>=20) ? 1 : 2\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 3.3\n"
|
||||
"Generated-By: Babel 2.9.0\n"
|
||||
|
||||
#: mailu/ui/forms.py:32
|
||||
#: mailu/ui/forms.py:33 mailu/ui/forms.py:36
|
||||
msgid "Invalid email address."
|
||||
msgstr "Nieprawidłowy adres e-mail."
|
||||
|
||||
#: mailu/ui/forms.py:36
|
||||
#: mailu/ui/forms.py:45
|
||||
msgid "Confirm"
|
||||
msgstr "Zatwierdź"
|
||||
|
||||
#: mailu/ui/forms.py:40 mailu/ui/forms.py:77
|
||||
#: mailu/ui/forms.py:49 mailu/ui/forms.py:86
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: mailu/ui/forms.py:41 mailu/ui/forms.py:78 mailu/ui/forms.py:90
|
||||
#: mailu/ui/forms.py:109 mailu/ui/forms.py:162
|
||||
#: mailu/ui/forms.py:50 mailu/ui/forms.py:87 mailu/ui/forms.py:100
|
||||
#: mailu/ui/forms.py:118 mailu/ui/forms.py:172
|
||||
#: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:59
|
||||
msgid "Password"
|
||||
msgstr "Hasło"
|
||||
|
||||
#: mailu/ui/forms.py:42 mailu/ui/templates/login.html:4
|
||||
#: mailu/ui/templates/sidebar.html:111
|
||||
#: mailu/ui/forms.py:51 mailu/ui/templates/login.html:4
|
||||
#: mailu/ui/templates/sidebar.html:108
|
||||
msgid "Sign in"
|
||||
msgstr "Zaloguj"
|
||||
|
||||
#: mailu/ui/forms.py:46 mailu/ui/forms.py:56
|
||||
#: mailu/ui/forms.py:55 mailu/ui/forms.py:65
|
||||
#: mailu/ui/templates/domain/details.html:27
|
||||
#: mailu/ui/templates/domain/list.html:18 mailu/ui/templates/relay/list.html:17
|
||||
msgid "Domain name"
|
||||
msgstr "Nazwa domeny"
|
||||
|
||||
#: mailu/ui/forms.py:47
|
||||
#: mailu/ui/forms.py:56
|
||||
msgid "Maximum user count"
|
||||
msgstr "Maksymalna liczba użytkowników"
|
||||
|
||||
#: mailu/ui/forms.py:48
|
||||
#: mailu/ui/forms.py:57
|
||||
msgid "Maximum alias count"
|
||||
msgstr "Maksymalna liczba aliasów"
|
||||
|
||||
#. Needs more context - is that a verb or a noun?
|
||||
#: mailu/ui/forms.py:51 mailu/ui/forms.py:72 mailu/ui/forms.py:83
|
||||
#: mailu/ui/forms.py:128 mailu/ui/forms.py:140
|
||||
#: mailu/ui/forms.py:58
|
||||
msgid "Maximum user quota"
|
||||
msgstr "Maksymalny przydział użytkownika"
|
||||
|
||||
#: mailu/ui/forms.py:59
|
||||
msgid "Enable sign-up"
|
||||
msgstr "Włącz rejestrację"
|
||||
|
||||
#: mailu/ui/forms.py:60 mailu/ui/forms.py:81 mailu/ui/forms.py:93
|
||||
#: mailu/ui/forms.py:138 mailu/ui/forms.py:150
|
||||
#: mailu/ui/templates/alias/list.html:21 mailu/ui/templates/domain/list.html:21
|
||||
#: mailu/ui/templates/relay/list.html:19 mailu/ui/templates/token/list.html:19
|
||||
#: mailu/ui/templates/user/list.html:23
|
||||
msgid "Comment"
|
||||
msgstr "Komentarz"
|
||||
|
||||
#: mailu/ui/forms.py:52 mailu/ui/forms.py:61 mailu/ui/forms.py:66
|
||||
#: mailu/ui/forms.py:73 mailu/ui/forms.py:132 mailu/ui/forms.py:141
|
||||
msgid "Create"
|
||||
msgstr "Utwórz"
|
||||
#: mailu/ui/forms.py:61 mailu/ui/forms.py:75 mailu/ui/forms.py:82
|
||||
#: mailu/ui/forms.py:95 mailu/ui/forms.py:142 mailu/ui/forms.py:151
|
||||
msgid "Save"
|
||||
msgstr "Zapisz"
|
||||
|
||||
#: mailu/ui/forms.py:59 mailu/ui/forms.py:79 mailu/ui/forms.py:91
|
||||
#: mailu/ui/forms.py:66
|
||||
msgid "Initial admin"
|
||||
msgstr "Początkowy administrator"
|
||||
|
||||
#: mailu/ui/forms.py:67
|
||||
msgid "Admin password"
|
||||
msgstr "Hasło administratora"
|
||||
|
||||
#: mailu/ui/forms.py:68 mailu/ui/forms.py:88 mailu/ui/forms.py:101
|
||||
msgid "Confirm password"
|
||||
msgstr "Potwierdź hasło"
|
||||
|
||||
#: mailu/ui/forms.py:80 mailu/ui/templates/user/list.html:22
|
||||
#: mailu/ui/forms.py:70
|
||||
msgid "Create"
|
||||
msgstr "Utwórz"
|
||||
|
||||
#: mailu/ui/forms.py:74
|
||||
msgid "Alternative name"
|
||||
msgstr "Alternatywna nazwa"
|
||||
|
||||
#: mailu/ui/forms.py:79
|
||||
msgid "Relayed domain name"
|
||||
msgstr "Domeny przekierowywane"
|
||||
|
||||
#: mailu/ui/forms.py:80 mailu/ui/templates/relay/list.html:18
|
||||
msgid "Remote host"
|
||||
msgstr "Zdalny host"
|
||||
|
||||
#: mailu/ui/forms.py:89 mailu/ui/templates/user/list.html:22
|
||||
#: mailu/ui/templates/user/signup_domain.html:16
|
||||
msgid "Quota"
|
||||
msgstr "Maksymalna przestrzeń na dysku"
|
||||
|
||||
#: mailu/ui/forms.py:81
|
||||
#: mailu/ui/forms.py:90
|
||||
msgid "Allow IMAP access"
|
||||
msgstr "Zezwalaj na dostęp przez protokół IMAP"
|
||||
|
||||
#: mailu/ui/forms.py:82
|
||||
#: mailu/ui/forms.py:91
|
||||
msgid "Allow POP3 access"
|
||||
msgstr "Zezwalaj na dostęp przez protokół POP3"
|
||||
|
||||
#: mailu/ui/forms.py:85
|
||||
msgid "Save"
|
||||
msgstr "Zapisz"
|
||||
|
||||
#: mailu/ui/forms.py:97
|
||||
#: mailu/ui/forms.py:92 mailu/ui/forms.py:108
|
||||
#: mailu/ui/templates/user/settings.html:15
|
||||
msgid "Displayed name"
|
||||
msgstr "Nazwa wyświetlana"
|
||||
|
||||
#: mailu/ui/forms.py:98
|
||||
#: mailu/ui/forms.py:94
|
||||
msgid "Enabled"
|
||||
msgstr "Włączone"
|
||||
|
||||
#: mailu/ui/forms.py:99
|
||||
msgid "Email address"
|
||||
msgstr "Adres e-mail"
|
||||
|
||||
#: mailu/ui/forms.py:102 mailu/ui/templates/sidebar.html:114
|
||||
#: mailu/ui/templates/user/signup.html:4
|
||||
#: mailu/ui/templates/user/signup_domain.html:4
|
||||
msgid "Sign up"
|
||||
msgstr "Utwórz konto"
|
||||
|
||||
#: mailu/ui/forms.py:109
|
||||
msgid "Enable spam filter"
|
||||
msgstr "Włącz filtr antyspamowy"
|
||||
|
||||
#: mailu/ui/forms.py:80
|
||||
msgid "Spam filter threshold"
|
||||
msgstr "Próg filtra antyspamowego"
|
||||
|
||||
#: mailu/ui/forms.py:105
|
||||
msgid "Save settings"
|
||||
msgstr "Zapisz ustawienia"
|
||||
|
||||
#: mailu/ui/forms.py:110
|
||||
msgid "Password check"
|
||||
msgstr ""
|
||||
msgid "Spam filter tolerance"
|
||||
msgstr "Tolerancja filtra spamu"
|
||||
|
||||
#: mailu/ui/forms.py:111 mailu/ui/templates/sidebar.html:16
|
||||
msgid "Update password"
|
||||
msgstr "Zaktualizuj hasło"
|
||||
|
||||
#: mailu/ui/forms.py:100
|
||||
#: mailu/ui/forms.py:111
|
||||
msgid "Enable forwarding"
|
||||
msgstr "Włącz przekierowanie poczty"
|
||||
|
||||
#: mailu/ui/forms.py:103 mailu/ui/forms.py:139
|
||||
#: mailu/ui/forms.py:112
|
||||
msgid "Keep a copy of the emails"
|
||||
msgstr "Przechowuj kopię wiadomości"
|
||||
|
||||
#: mailu/ui/forms.py:113 mailu/ui/forms.py:149
|
||||
#: mailu/ui/templates/alias/list.html:20
|
||||
msgid "Destination"
|
||||
msgstr "Adres docelowy"
|
||||
|
||||
#: mailu/ui/forms.py:120
|
||||
msgid "Update"
|
||||
msgstr "Aktualizuj"
|
||||
#: mailu/ui/forms.py:114
|
||||
msgid "Save settings"
|
||||
msgstr "Zapisz ustawienia"
|
||||
|
||||
#: mailu/ui/forms.py:115
|
||||
#: mailu/ui/forms.py:119
|
||||
msgid "Password check"
|
||||
msgstr "Powtórz hasło"
|
||||
|
||||
#: mailu/ui/forms.py:120 mailu/ui/templates/sidebar.html:16
|
||||
msgid "Update password"
|
||||
msgstr "Zaktualizuj hasło"
|
||||
|
||||
#: mailu/ui/forms.py:124
|
||||
msgid "Enable automatic reply"
|
||||
msgstr "Włącz automatyczną odpowiedź"
|
||||
|
||||
#: mailu/ui/forms.py:116
|
||||
#: mailu/ui/forms.py:125
|
||||
msgid "Reply subject"
|
||||
msgstr "Temat odpowiedzi"
|
||||
|
||||
#: mailu/ui/forms.py:117
|
||||
#: mailu/ui/forms.py:126
|
||||
msgid "Reply body"
|
||||
msgstr "Treść odpowiedzi"
|
||||
|
||||
#: mailu/ui/forms.py:136
|
||||
#: mailu/ui/forms.py:128
|
||||
#, fuzzy
|
||||
msgid "Start of vacation"
|
||||
msgstr "Rozpoczęcie nieobecności"
|
||||
|
||||
#: mailu/ui/forms.py:129
|
||||
msgid "End of vacation"
|
||||
msgstr "Koniec nieobecności"
|
||||
|
||||
#: mailu/ui/forms.py:130
|
||||
msgid "Update"
|
||||
msgstr "Aktualizuj"
|
||||
|
||||
#: mailu/ui/forms.py:135
|
||||
msgid "Your token (write it down, as it will never be displayed again)"
|
||||
msgstr "Twój token (zapisz go, ponieważ nigdy więcej nie będzie wyświetlany)"
|
||||
|
||||
#: mailu/ui/forms.py:140 mailu/ui/templates/token/list.html:20
|
||||
msgid "Authorized IP"
|
||||
msgstr "Autoryzowany adres IP"
|
||||
|
||||
#: mailu/ui/forms.py:146
|
||||
msgid "Alias"
|
||||
msgstr "Alias"
|
||||
|
||||
#: mailu/ui/forms.py:138
|
||||
#: mailu/ui/forms.py:148
|
||||
msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)"
|
||||
msgstr "Używaj składni SQL LIKE (np. do adresów catch-all)"
|
||||
|
||||
#: mailu/ui/forms.py:145
|
||||
#: mailu/ui/forms.py:155
|
||||
msgid "Admin email"
|
||||
msgstr "E-mail administratora"
|
||||
|
||||
#: mailu/ui/forms.py:146 mailu/ui/forms.py:151 mailu/ui/forms.py:164
|
||||
#: mailu/ui/forms.py:156 mailu/ui/forms.py:161 mailu/ui/forms.py:174
|
||||
msgid "Submit"
|
||||
msgstr "Prześlij"
|
||||
|
||||
#: mailu/ui/forms.py:150
|
||||
#: mailu/ui/forms.py:160
|
||||
msgid "Manager email"
|
||||
msgstr "E-mail menedżera"
|
||||
|
||||
#: mailu/ui/forms.py:155
|
||||
#: mailu/ui/forms.py:165
|
||||
msgid "Protocol"
|
||||
msgstr "Protokół"
|
||||
|
||||
#: mailu/ui/forms.py:158
|
||||
#: mailu/ui/forms.py:168
|
||||
msgid "Hostname or IP"
|
||||
msgstr "Nazwa hosta lub adres IP"
|
||||
|
||||
#: mailu/ui/forms.py:159 mailu/ui/templates/client.html:20
|
||||
#: mailu/ui/forms.py:169 mailu/ui/templates/client.html:20
|
||||
#: mailu/ui/templates/client.html:47
|
||||
msgid "TCP port"
|
||||
msgstr "Port TCP"
|
||||
|
||||
#: mailu/ui/forms.py:160
|
||||
#: mailu/ui/forms.py:170
|
||||
msgid "Enable TLS"
|
||||
msgstr "Włącz TLS"
|
||||
|
||||
#: mailu/ui/forms.py:161 mailu/ui/templates/client.html:28
|
||||
#: mailu/ui/forms.py:171 mailu/ui/templates/client.html:28
|
||||
#: mailu/ui/templates/client.html:55 mailu/ui/templates/fetch/list.html:20
|
||||
msgid "Username"
|
||||
msgstr "Nazwa użytkownika"
|
||||
|
||||
#: mailu/ui/forms.py:173
|
||||
msgid "Keep emails on the server"
|
||||
msgstr "Przechowuj wiadomości na serwerze"
|
||||
|
||||
#: mailu/ui/forms.py:178
|
||||
msgid "Announcement subject"
|
||||
msgstr "Temat ogłoszenia"
|
||||
|
||||
#: mailu/ui/forms.py:180
|
||||
msgid "Announcement body"
|
||||
msgstr "Treść ogłoszenia"
|
||||
|
||||
#: mailu/ui/forms.py:182
|
||||
msgid "Send"
|
||||
msgstr "Wyślij"
|
||||
|
||||
#: mailu/ui/templates/announcement.html:4
|
||||
msgid "Public announcement"
|
||||
msgstr "Publiczne ogłoszenie"
|
||||
|
||||
#: mailu/ui/templates/client.html:4 mailu/ui/templates/sidebar.html:79
|
||||
msgid "Client setup"
|
||||
msgstr "Konfiguracja klienta"
|
||||
|
||||
#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:43
|
||||
msgid "Mail protocol"
|
||||
msgstr "Protokół poczty"
|
||||
|
||||
#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:51
|
||||
msgid "Server name"
|
||||
msgstr "Nazwa serwera"
|
||||
|
||||
#: mailu/ui/templates/confirm.html:4
|
||||
msgid "Confirm action"
|
||||
msgstr "Potwierdź wykonanie czynności"
|
||||
|
||||
#: mailu/ui/templates/confirm.html:13
|
||||
#, python-format
|
||||
msgid "You are about to %(action)s. Please confirm your action."
|
||||
msgstr "Zamierzasz wykonać następujące czynności: %(action)s. Potwierdź wykonanie czynności."
|
||||
msgstr ""
|
||||
"Zamierzasz wykonać następujące czynności: %(action)s. Potwierdź wykonanie"
|
||||
" czynności."
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:4
|
||||
msgid "Docker error"
|
||||
@@ -192,54 +291,19 @@ msgstr "Błąd Dockera"
|
||||
msgid "An error occurred while talking to the Docker server."
|
||||
msgstr "Wystąpił błąd komunikacji z serwerem Dockera."
|
||||
|
||||
#: mailu/admin/templates/login.html:6
|
||||
msgid "Your account"
|
||||
msgstr "Twoje konto"
|
||||
|
||||
#: mailu/ui/templates/login.html:8
|
||||
msgid "to access the administration tools"
|
||||
msgstr "aby uzyskać dostęp do narzędzi administracyjnych"
|
||||
|
||||
#: mailu/ui/templates/services.html:4 mailu/ui/templates/sidebar.html:39
|
||||
msgid "Services status"
|
||||
msgstr "Status usług"
|
||||
|
||||
#: mailu/ui/templates/services.html:10
|
||||
msgid "Service"
|
||||
msgstr "Usługa"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:23 mailu/ui/templates/services.html:11
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: mailu/ui/templates/services.html:12
|
||||
msgid "PID"
|
||||
msgstr "PID"
|
||||
|
||||
#: mailu/ui/templates/services.html:13
|
||||
msgid "Image"
|
||||
msgstr "Obraz"
|
||||
|
||||
#: mailu/ui/templates/services.html:14
|
||||
msgid "Started"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/services.html:15
|
||||
msgid "Last update"
|
||||
msgstr "Ostatnia aktualizacja"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:8
|
||||
#, fuzzy
|
||||
msgid "My account"
|
||||
msgstr "Moje konto"
|
||||
msgstr "Dodaj konto"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:11 mailu/ui/templates/user/list.html:34
|
||||
msgid "Settings"
|
||||
msgstr "Ustawienia"
|
||||
|
||||
#: mailu/ui/templates/user/settings.html:22
|
||||
msgid "Auto-forward"
|
||||
msgstr "Automatyczne przekierowanie"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:21 mailu/ui/templates/user/list.html:35
|
||||
msgid "Auto-reply"
|
||||
msgstr "Automatyczna odpowiedź"
|
||||
@@ -247,28 +311,60 @@ msgstr "Automatyczna odpowiedź"
|
||||
#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:26
|
||||
#: mailu/ui/templates/user/list.html:36
|
||||
msgid "Fetched accounts"
|
||||
msgstr ""
|
||||
msgstr "Zewnętrzne konta e-mail"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:105
|
||||
msgid "Sign out"
|
||||
msgstr "Wyloguj"
|
||||
#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/token/list.html:4
|
||||
msgid "Authentication tokens"
|
||||
msgstr "Tokeny uwierzytelnienia"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:35
|
||||
#: mailu/ui/templates/sidebar.html:36
|
||||
msgid "Administration"
|
||||
msgstr "Administracja"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:49
|
||||
#: mailu/ui/templates/sidebar.html:41
|
||||
msgid "Announcement"
|
||||
msgstr "Ogłoszenie"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:46
|
||||
msgid "Administrators"
|
||||
msgstr "Administratorzy"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:66
|
||||
#: mailu/ui/templates/sidebar.html:51
|
||||
msgid "Relayed domains"
|
||||
msgstr "Domeny przekierowywane"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:56 mailu/ui/templates/user/settings.html:19
|
||||
msgid "Antispam"
|
||||
msgstr "Filtr antyspamowy"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:63
|
||||
msgid "Mail domains"
|
||||
msgstr "Domeny pocztowe"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:92
|
||||
#: mailu/ui/templates/sidebar.html:69
|
||||
msgid "Go to"
|
||||
msgstr "Przejdź do"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:73
|
||||
msgid "Webmail"
|
||||
msgstr "Twoja poczta"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:84
|
||||
msgid "Website"
|
||||
msgstr "Strona internetowa"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:89
|
||||
msgid "Help"
|
||||
msgstr "Pomoc"
|
||||
|
||||
#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:95
|
||||
msgid "Register a domain"
|
||||
msgstr "Zarejestruj domenę"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:102
|
||||
msgid "Sign out"
|
||||
msgstr "Wyloguj"
|
||||
|
||||
#: mailu/ui/templates/working.html:4
|
||||
msgid "We are still working on this feature!"
|
||||
msgstr "Nadal pracujemy nad tą funkcją!"
|
||||
@@ -344,6 +440,22 @@ msgstr "Ostatnia edycja"
|
||||
msgid "Edit"
|
||||
msgstr "Edytuj"
|
||||
|
||||
#: mailu/ui/templates/alternative/create.html:4
|
||||
msgid "Create alternative domain"
|
||||
msgstr "Utwórz alternatywną domenę"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:4
|
||||
msgid "Alternative domain list"
|
||||
msgstr "Alternatywna lista domen"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:12
|
||||
msgid "Add alternative"
|
||||
msgstr "Dodaj alternatywę"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:19
|
||||
msgid "Name"
|
||||
msgstr "Nazwa"
|
||||
|
||||
#: mailu/ui/templates/domain/create.html:4
|
||||
#: mailu/ui/templates/domain/list.html:9
|
||||
msgid "New domain"
|
||||
@@ -357,6 +469,10 @@ msgstr "Szczegóły domeny"
|
||||
msgid "Regenerate keys"
|
||||
msgstr "Wygeneruj ponownie klucze"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:17
|
||||
msgid "Generate keys"
|
||||
msgstr "Wygeneruj klucze"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:31
|
||||
msgid "DNS MX entry"
|
||||
msgstr "Wpis MX DNS"
|
||||
@@ -365,15 +481,15 @@ msgstr "Wpis MX DNS"
|
||||
msgid "DNS SPF entries"
|
||||
msgstr "Wpisy SPF DNS"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:42
|
||||
#: mailu/ui/templates/domain/details.html:41
|
||||
msgid "DKIM public key"
|
||||
msgstr "Publiczny klucz DKIM"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:46
|
||||
#: mailu/ui/templates/domain/details.html:45
|
||||
msgid "DNS DKIM entry"
|
||||
msgstr "Wpis DKIM DNS"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:50
|
||||
#: mailu/ui/templates/domain/details.html:49
|
||||
msgid "DNS DMARC entry"
|
||||
msgstr "Wpis DMARC DNS"
|
||||
|
||||
@@ -413,13 +529,42 @@ msgstr "Aliasy"
|
||||
msgid "Managers"
|
||||
msgstr "Menedżerowie"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:39
|
||||
msgid "Alternatives"
|
||||
msgstr "Alternatywy"
|
||||
|
||||
#: 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 ""
|
||||
"Aby zarejestrować nową domenę, musisz najpierw skonfigurować strefę "
|
||||
"domeny, aby domena <code> MX </code> wskazywała na ten serwer"
|
||||
|
||||
#: 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 ""
|
||||
"Jeśli nie wiesz, jak skonfigurować rekord <code> MX </code> dla swojej "
|
||||
"strefy DNS,\n"
|
||||
"skontaktuj się z dostawcą DNS lub administratorem. Proszę również "
|
||||
"poczekać\n"
|
||||
"kilka minut po ustawieniu <code> MX </code> , żeby pamięć podręczna "
|
||||
"serwera lokalnego wygasła."
|
||||
|
||||
#: mailu/ui/templates/fetch/create.html:4
|
||||
msgid "Add a fetched account"
|
||||
msgstr ""
|
||||
msgstr "Dodaj zewnętrzne konto pocztowe"
|
||||
|
||||
#: mailu/ui/templates/fetch/edit.html:4
|
||||
msgid "Update a fetched account"
|
||||
msgstr ""
|
||||
msgstr "Zaktualizuj konto"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:12
|
||||
msgid "Add an account"
|
||||
@@ -427,12 +572,28 @@ msgstr "Dodaj konto"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:19
|
||||
msgid "Endpoint"
|
||||
msgstr ""
|
||||
msgstr "Serwer"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:21
|
||||
msgid "Keep emails"
|
||||
msgstr "Przechowuj wiadomości"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:22
|
||||
msgid "Last check"
|
||||
msgstr "Ostatnie sprawdzenie"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:23
|
||||
msgid "Status"
|
||||
msgstr "Stan"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:35
|
||||
msgid "yes"
|
||||
msgstr "Tak"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:35
|
||||
msgid "no"
|
||||
msgstr "Nie"
|
||||
|
||||
#: mailu/ui/templates/manager/create.html:4
|
||||
msgid "Add a manager"
|
||||
msgstr "Dodaj menedżera"
|
||||
@@ -445,34 +606,43 @@ msgstr "Lista menedżerów"
|
||||
msgid "Add manager"
|
||||
msgstr "Dodaj menedżera"
|
||||
|
||||
#: mailu/ui/forms.py:168
|
||||
msgid "Announcement subject"
|
||||
msgstr "Temat ogłoszenia"
|
||||
#: mailu/ui/templates/relay/create.html:4
|
||||
msgid "New relay domain"
|
||||
msgstr "Nowa domena do przekierowania"
|
||||
|
||||
#: mailu/ui/forms.py:170
|
||||
msgid "Announcement body"
|
||||
msgstr "Treść ogłoszenia"
|
||||
#: mailu/ui/templates/relay/edit.html:4
|
||||
#, fuzzy
|
||||
msgid "Edit relayd domain"
|
||||
msgstr "Edycja domeny"
|
||||
|
||||
#: mailu/ui/forms.py:172
|
||||
msgid "Send"
|
||||
msgstr "Wyślij"
|
||||
#: mailu/ui/templates/relay/list.html:4
|
||||
msgid "Relayed domain list"
|
||||
msgstr "Lista domen przekierowywanych"
|
||||
|
||||
#: mailu/ui/templates/announcement.html:4
|
||||
msgid "Public announcement"
|
||||
msgstr "Publiczne ogłoszenie"
|
||||
#: mailu/ui/templates/relay/list.html:9
|
||||
msgid "New relayed domain"
|
||||
msgstr "Nowa domena do przekierowania"
|
||||
|
||||
#: mailu/ui/templates/announcement.html:8
|
||||
msgid "from"
|
||||
msgstr "od"
|
||||
#: mailu/ui/templates/token/create.html:4
|
||||
msgid "Create an authentication token"
|
||||
msgstr "Utwórz token uwierzytelniający"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:44
|
||||
msgid "Announcement"
|
||||
msgstr "Ogłoszenie"
|
||||
#: mailu/ui/templates/token/list.html:12
|
||||
msgid "New token"
|
||||
msgstr "Nowy token"
|
||||
|
||||
#: mailu/ui/templates/user/create.html:4
|
||||
msgid "New user"
|
||||
msgstr "Nowy użytkownik"
|
||||
|
||||
#: mailu/ui/templates/user/create.html:15
|
||||
msgid "General"
|
||||
msgstr "Ogólne"
|
||||
|
||||
#: mailu/ui/templates/user/create.html:23
|
||||
msgid "Features and quotas"
|
||||
msgstr "Funkcje i limity"
|
||||
|
||||
#: mailu/ui/templates/user/edit.html:4
|
||||
msgid "Edit user"
|
||||
msgstr "Edytuj użytkownika"
|
||||
@@ -505,202 +675,9 @@ msgstr "Zmiana hasła"
|
||||
msgid "Automatic reply"
|
||||
msgstr "Automatyczna odpowiedź"
|
||||
|
||||
#: mailu/ui/forms.py:49
|
||||
msgid "Maximum user quota"
|
||||
msgstr "Maksymalny przydział użytkownika"
|
||||
|
||||
#: mailu/ui/forms.py:101
|
||||
msgid "Keep a copy of the emails"
|
||||
msgstr "Przechowuj kopię wiadomości"
|
||||
|
||||
#: mailu/ui/forms.py:163
|
||||
msgid "Keep emails on the server"
|
||||
msgstr "Przechowuj wiadomości na serwerze"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:21
|
||||
msgid "Keep emails"
|
||||
msgstr "Przechowuj wiadomości"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:35
|
||||
msgid "yes"
|
||||
msgstr "Tak"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:35
|
||||
msgid "no"
|
||||
msgstr "Nie"
|
||||
|
||||
#: mailu/ui/forms.py:65
|
||||
msgid "Alternative name"
|
||||
msgstr "Alternatywna nazwa"
|
||||
|
||||
#: mailu/ui/forms.py:70
|
||||
msgid "Relayed domain name"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/forms.py:71 mailu/ui/templates/relay/list.html:18
|
||||
msgid "Remote host"
|
||||
msgstr "Zdalny host"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:54
|
||||
msgid "Relayed domains"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/alternative/create.html:4
|
||||
msgid "Create alternative domain"
|
||||
msgstr "Utwórz alternatywną domenę"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:4
|
||||
msgid "Alternative domain list"
|
||||
msgstr "Alternatywna lista domen"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:12
|
||||
msgid "Add alternative"
|
||||
msgstr "Dodaj alternatywę"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:19
|
||||
msgid "Name"
|
||||
msgstr "Nazwa"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:39
|
||||
msgid "Alternatives"
|
||||
msgstr "Alternatywy"
|
||||
|
||||
#: mailu/ui/templates/relay/create.html:4
|
||||
msgid "New relay domain"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/relay/edit.html:4
|
||||
msgid "Edit relayd domain"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:4
|
||||
msgid "Relayed domain list"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:9
|
||||
msgid "New relayed domain"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/forms.py:125
|
||||
msgid "Your token (write it down, as it will never be displayed again)"
|
||||
msgstr "Twój token (zapisz go, ponieważ nigdy więcej nie będzie wyświetlany)"
|
||||
|
||||
#: mailu/ui/forms.py:130 mailu/ui/templates/token/list.html:20
|
||||
msgid "Authorized IP"
|
||||
msgstr "Autoryzowany adres IP"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/token/list.html:4
|
||||
msgid "Authentication tokens"
|
||||
msgstr "Tokeny uwierzytelnienia"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:72
|
||||
msgid "Go to"
|
||||
msgstr "Przejdź do"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:76
|
||||
msgid "Webmail"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:87
|
||||
msgid "Website"
|
||||
msgstr "Strona internetowa"
|
||||
|
||||
#: mailu/ui/templates/token/create.html:4
|
||||
msgid "Create an authentication token"
|
||||
msgstr "Utwórz token uwierzytelniający"
|
||||
|
||||
#: mailu/ui/templates/token/list.html:12
|
||||
msgid "New token"
|
||||
msgstr "Nowy token"
|
||||
|
||||
#: mailu/ui/templates/user/create.html:15
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/user/create.html:22
|
||||
msgid "Features and quotas"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/user/settings.html:14
|
||||
msgid "General settings"
|
||||
msgstr "Ustawienia ogólne"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:59 mailu/ui/templates/user/settings.html:15
|
||||
msgid "Antispam"
|
||||
msgstr "Filtr antyspamowy"
|
||||
|
||||
#: mailu/ui/forms.py:99
|
||||
msgid "Spam filter tolerance"
|
||||
msgstr "Tolerancja filtra spamu"
|
||||
|
||||
#: mailu/ui/forms.py:50
|
||||
msgid "Enable sign-up"
|
||||
msgstr "Włącz rejestrację"
|
||||
|
||||
#: mailu/ui/forms.py:57
|
||||
msgid "Initial admin"
|
||||
msgstr "Początkowy administrator"
|
||||
|
||||
#: mailu/ui/forms.py:58
|
||||
msgid "Admin password"
|
||||
msgstr "hasło administratora"
|
||||
|
||||
#: mailu/ui/forms.py:84
|
||||
msgid "Enabled"
|
||||
msgstr "Włączone"
|
||||
|
||||
#: mailu/ui/forms.py:89
|
||||
msgid "Email address"
|
||||
msgstr "Adres e-mail"
|
||||
|
||||
#: mailu/ui/forms.py:93 mailu/ui/templates/sidebar.html:117
|
||||
#: mailu/ui/templates/user/signup.html:4
|
||||
#: mailu/ui/templates/user/signup_domain.html:4
|
||||
msgid "Sign up"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/forms.py:119
|
||||
msgid "End of vacation"
|
||||
msgstr "Koniec wakacji"
|
||||
|
||||
#: mailu/ui/templates/client.html:4 mailu/ui/templates/sidebar.html:82
|
||||
msgid "Client setup"
|
||||
msgstr "Konfiguracja klienta"
|
||||
|
||||
#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:43
|
||||
msgid "Mail protocol"
|
||||
msgstr "Protokół poczty"
|
||||
|
||||
#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:51
|
||||
msgid "Server name"
|
||||
msgstr "Nazwa serwera"
|
||||
|
||||
#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:98
|
||||
msgid "Register a domain"
|
||||
msgstr "Zarejestruj domenę"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:17
|
||||
msgid "Generate keys"
|
||||
msgstr "Wygeneruj klucze"
|
||||
|
||||
#: 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 ""
|
||||
"Aby zarejestrować nową domenę, musisz najpierw skonfigurować strefę domeny, "
|
||||
"aby domena <code> MX </code> wskazywała na ten serwer"
|
||||
|
||||
#: 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 ""
|
||||
"Jeśli nie wiesz, jak skonfigurować rekord <code> MX </code> dla swojej "
|
||||
"strefy DNS,\n"
|
||||
"skontaktuj się z dostawcą DNS lub administratorem. Proszę również poczekać\n"
|
||||
"kilka minut po ustawieniu <code> MX </code> , żeby pamięć podręczna serwera "
|
||||
"lokalnego wygasła."
|
||||
#: mailu/ui/templates/user/settings.html:26
|
||||
msgid "Auto-forward"
|
||||
msgstr "Automatyczne przekierowanie"
|
||||
|
||||
#: mailu/ui/templates/user/signup_domain.html:8
|
||||
msgid "pick a domain for the new account"
|
||||
@@ -713,3 +690,40 @@ msgstr "Domena"
|
||||
#: mailu/ui/templates/user/signup_domain.html:15
|
||||
msgid "Available slots"
|
||||
msgstr "Dostępne miejsca"
|
||||
|
||||
#~ msgid "Spam filter threshold"
|
||||
#~ msgstr "Próg filtra antyspamowego"
|
||||
|
||||
#~ msgid "Your account"
|
||||
#~ msgstr "Twoje konto"
|
||||
|
||||
#~ msgid "Services status"
|
||||
#~ msgstr "Status usług"
|
||||
|
||||
#~ msgid "Service"
|
||||
#~ msgstr "Usługa"
|
||||
|
||||
#~ msgid "Status"
|
||||
#~ msgstr "Status"
|
||||
|
||||
#~ msgid "PID"
|
||||
#~ msgstr "PID"
|
||||
|
||||
#~ msgid "Image"
|
||||
#~ msgstr "Obraz"
|
||||
|
||||
#~ msgid "Started"
|
||||
#~ msgstr "Uruchomione"
|
||||
|
||||
#~ msgid "Last update"
|
||||
#~ msgstr "Ostatnia aktualizacja"
|
||||
|
||||
#~ msgid "My account"
|
||||
#~ msgstr "Moje konto"
|
||||
|
||||
#~ msgid "from"
|
||||
#~ msgstr "od"
|
||||
|
||||
#~ msgid "General settings"
|
||||
#~ msgstr "Ustawienia ogólne"
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ class UserForm(flask_wtf.FlaskForm):
|
||||
localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
||||
pw = fields.PasswordField(_('Password'))
|
||||
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
||||
quota_bytes = fields_.IntegerSliderField(_('Quota'), default=1000000000)
|
||||
quota_bytes = fields_.IntegerSliderField(_('Quota'), default=10**9)
|
||||
enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True)
|
||||
enable_pop = fields.BooleanField(_('Allow POP3 access'), default=True)
|
||||
displayed_name = fields.StringField(_('Displayed name'))
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Add a global administrator{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
{%- block content %}
|
||||
{%- call macros.card() %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ macros.form_field(form.admin, class_='mailselect') }}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Global administrators{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{%- block main_action %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.admin_create') }}">
|
||||
{% trans %}Add administrator{% endtrans %}
|
||||
</a>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -19,14 +19,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for admin in admins %}
|
||||
{%- for admin in admins %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.admin_delete', admin=admin.email) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
<td>{{ admin }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Create alias{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
{%- block content %}
|
||||
{%- call macros.card() %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ macros.form_field(form.localpart, append='<span class="input-group-text">@'+domain.name+'</span>') }}
|
||||
@@ -18,5 +18,5 @@
|
||||
{{ macros.form_field(form.comment) }}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "alias/create.html" %}
|
||||
{%- extends "alias/create.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Edit alias{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ alias }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Alias list{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain.name }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{%- block main_action %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.alias_create', domain_name=domain.name) }}">{% trans %}Add alias{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -25,7 +25,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for alias in domain.aliases %}
|
||||
{%- for alias in domain.aliases %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.alias_edit', alias=alias.email) }}" title="{% trans %}Edit{% endtrans %}"><i class="fa fa-pencil"></i></a>
|
||||
@@ -37,7 +37,7 @@
|
||||
<td>{{ alias.created_at }}</td>
|
||||
<td>{{ alias.updated_at or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "form.html" %}
|
||||
{%- extends "form.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Create alternative domain{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Alternative domain list{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain.name }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{%- block main_action %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.alternative_create', domain_name=domain.name) }}">{% trans %}Add alternative{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -22,7 +22,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for alternative in domain.alternatives %}
|
||||
{%- for alternative in domain.alternatives %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.alternative_delete', alternative=alternative.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||
@@ -30,7 +30,7 @@
|
||||
<td>{{ alternative }}</td>
|
||||
<td>{{ alternative.created_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Public announcement{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
{%- block content %}
|
||||
{%- call macros.card() %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ macros.form_field(form.announcement_subject) }}
|
||||
{{ macros.form_field(form.announcement_body, rows=10) }}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
15
core/admin/mailu/ui/templates/antispam.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{%- block title %}
|
||||
{% trans %}Antispam{% endtrans %}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block subtitle %}
|
||||
{% trans %}RSPAMD status page{% endtrans %}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block content %}
|
||||
<div class="embed-responsive embed-responsive-1by1">
|
||||
<iframe class="embed-responsive-item" src="{{ config["WEB_ADMIN"] }}/antispam/"></iframe>
|
||||
</div>
|
||||
{%- endblock %}
|
||||
@@ -1,65 +1,83 @@
|
||||
{% import "macros.html" as macros %}
|
||||
{% import "bootstrap/utils.html" as utils %}
|
||||
{%- import "macros.html" as macros %}
|
||||
{%- import "bootstrap/utils.html" as utils %}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html lang="{{ session['language'] }}" data-static="{{ url_for('.static', filename='') }}">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{% trans %}Admin page for{% endtrans %} {{ config["SITENAME"] }}">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Mailu-Admin | {{ config["SITENAME"] }}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('.static', filename='vendor.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('.static', filename='app.css') }}">
|
||||
<title>Mailu-Admin - {{ config["SITENAME"] }}</title>
|
||||
</head>
|
||||
<body class="hold-transition sidebar-mini layout-fixed">
|
||||
<div class="wrapper">
|
||||
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
|
||||
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars" title="{% trans %}toggle sidebar{% endtrans %}" aria-expanded="false"></i><span class="sr-only">{% trans %}toggle sidebar{% endtrans %}</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
{%- for page, url in path %}
|
||||
{%- if loop.index > 1 %}
|
||||
<i class="fas fa-greater-than text-xs text-gray" aria-hidden="true"></i>
|
||||
{%- endif %}
|
||||
{%- if url %}
|
||||
<a class="nav-link d-inline-block" href="{{ url }}" role="button">{{ page }}</a>
|
||||
{%- else %}
|
||||
<span class="nav-link d-inline-block">{{ page }}</span>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link" data-toggle="dropdown" href="#" aria-expanded="false">{{ session['language'] }}</a>
|
||||
<div class="dropdown-menu dropdown-menu-right p-0">
|
||||
{% for language in session['available_languages'] %}
|
||||
<a class="dropdown-item {% if language == session['language'] %}active{% endif %} " href="{{ url_for('.set_language', language=language) }}">{{ language }}</a>
|
||||
{% endfor %}
|
||||
<a class="nav-link" data-toggle="dropdown" href="#" aria-expanded="false">
|
||||
<i class="fas fa-language text-xl" aria-hidden="true" title="{% trans %}change language{% endtrans %}"></i><span class="sr-only">Language</span>
|
||||
<span class="badge badge-primary navbar-badge">{{ session['language'] }}</span></a>
|
||||
<div class="dropdown-menu dropdown-menu-right p-0" id="mailu-languages">
|
||||
{%- for locale in config.translations.values() %}
|
||||
<a class="dropdown-item{% if locale.language == session['language'] %} active{% endif %}" href="{{ url_for('.set_language', language=locale.language) }}">{{ locale.get_language_name().title() }}</a>
|
||||
{%- endfor %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<aside class="main-sidebar sidebar-dark-primary">
|
||||
<a href="{{ config["WEB_ADMIN"] }}" class="brand-link">
|
||||
<span class="brand-text font-weight-light">{{ config["SITENAME"] }}</span>
|
||||
<aside class="main-sidebar sidebar-dark-primary nav-compact elevation-4">
|
||||
<a href="{{ url_for('.domain_list' if current_user.manager_of or current_user.global_admin else '.user_settings') }}" class="brand-link bg-mailu-logo"{% if config["LOGO_BACKGROUND"] %} style="background-color:{{ config["LOGO_BACKGROUND"] }}!important;"{% endif %}>
|
||||
<img src="{{ config["LOGO_URL"] if config["LOGO_URL"] else url_for('.static', filename='mailu.png') }}" width="33" height="33" alt="Mailu" class="brand-image mailu-logo img-circle elevation-3">
|
||||
<span class="brand-text font-weight-light">{{ config["SITENAME"] }}</span>
|
||||
</a>
|
||||
{% block sidebar %}
|
||||
{% include "sidebar.html" %}
|
||||
{% endblock %}
|
||||
{%- include "sidebar.html" %}
|
||||
</aside>
|
||||
<div class="content-wrapper">
|
||||
<div class="content-wrapper text-sm">
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1 class="m-0">{% block title %}{% endblock %}</h1>
|
||||
<h1 class="m-0">{%- block title %}{%- endblock %}</h1>
|
||||
<small>{% block subtitle %}{% endblock %}</small>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
{% block main_action %}
|
||||
{% endblock %}
|
||||
{%- block main_action %}{%- endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="content">
|
||||
{{ utils.flashed_messages(container=False) }}
|
||||
{% block content %}{% endblock %}
|
||||
{{ utils.flashed_messages(container=False, default_category='success') }}
|
||||
{%- block content %}{%- endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<footer class="main-footer">
|
||||
Built with <i class="fa fa-heart"></i> using <a class="white-text" href="http://flask.pocoo.org/">Flask</a> and
|
||||
<a class="white-text" href="https://adminlte.io/themes/v3/index3.html">AdminLTE</a>
|
||||
<span class="pull-right"><i class="fa fa-code-fork"></i>on <a class="white-text" href="https://github.com/Mailu/Mailu">Github</a></a></span>
|
||||
Built with <i class="fa fa-heart text-danger" aria-hidden="true"></i><span class="sr-only">love</span>
|
||||
using <a href="https://flask.palletsprojects.com/">Flask</a>
|
||||
and <a href="https://adminlte.io/themes/v3/index3.html">AdminLTE</a>.
|
||||
<span class="fa-pull-right">
|
||||
<i class="fa fa-code-branch" aria-hidden="true"></i><span class="sr-only">fork</span>
|
||||
on <a href="https://github.com/Mailu/Mailu">Github</a>
|
||||
</span>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="{{ url_for('.static', filename='vendor.js') }}"></script>
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
<!--TODO add translations for: configure your client, Incoming mail and Outgoing mail-->
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Client setup{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
configure your email client
|
||||
{% endblock %}
|
||||
{%- block subtitle %}
|
||||
{% trans %}configure your email client{% endtrans %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table(title="Incoming mail", datatable=False) %}
|
||||
{%- block content %}
|
||||
{%- call macros.table(title=_("Incoming mail"), datatable=False) %}
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{% trans %}Mail protocol{% endtrans %}</th>
|
||||
@@ -23,20 +21,20 @@ configure your email client
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}Server name{% endtrans %}</th>
|
||||
<td><pre>{{ config["HOSTNAMES"].split(',')[0] }}</pre></td>
|
||||
<td><pre class="pre-config border bg-light">{{ config["HOSTNAMES"] }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}Username{% endtrans %}</th>
|
||||
<td><pre>{{ current_user if current_user.is_authenticated else "******" }}</pre></td>
|
||||
<td><pre class="pre-config border bg-light">{{ current_user if current_user.is_authenticated else "******" }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}Password{% endtrans %}</th>
|
||||
<td><pre>*******</pre></td>
|
||||
<td><pre class="pre-config border bg-light">*******</pre></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{% call macros.table(title="Outgoing mail", datatable=False) %}
|
||||
{%- call macros.table(title=_("Outgoing mail"), datatable=False) %}
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{% trans %}Mail protocol{% endtrans %}</th>
|
||||
@@ -48,16 +46,16 @@ configure your email client
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}Server name{% endtrans %}</th>
|
||||
<td><pre>{{ config["HOSTNAMES"].split(',')[0] }}</pre></td>
|
||||
<td><pre class="pre-config border bg-light">{{ config["HOSTNAMES"] }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}Username{% endtrans %}</th>
|
||||
<td><pre>{{ current_user if current_user.is_authenticated else "******" }}</pre></td>
|
||||
<td><pre class="pre-config border bg-light">{{ current_user if current_user.is_authenticated else "******" }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}Password{% endtrans %}</th>
|
||||
<td><pre>*******</pre></td>
|
||||
<td><pre class="pre-config border bg-light">*******</pre></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Confirm action{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ action }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card(theme="warning") %}
|
||||
{%- block content %}
|
||||
{%- call macros.card(theme="warning") %}
|
||||
<p>{% trans action %}You are about to {{ action }}. Please confirm your action.{% endtrans %}</p>
|
||||
{{ macros.form(form) }}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Docker error{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ action }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{%- block content %}
|
||||
<p>{% trans action %}An error occurred while talking to the Docker server.{% endtrans %}</p>
|
||||
<pre>{{ error }}</pre>
|
||||
{% endblock %}
|
||||
<pre class="pre-config border bg-light">{{ error }}</pre>
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}New domain{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
{%- block content %}
|
||||
{%- call macros.card() %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ macros.form_field(form.name) }}
|
||||
{{ macros.form_fields((form.max_users, form.max_aliases)) }}
|
||||
{{ macros.form_field(form.max_quota_bytes, step=1000000000, max=50000000000,
|
||||
prepend='<span class="input-group-text"><span id="quota">'+((form.max_quota_bytes.data//1000000000).__str__() if form.max_quota_bytes.data else '∞')+'</span> GiB</span>',
|
||||
oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }}
|
||||
{{ macros.form_field(form.max_quota_bytes, step=10**9, max=50*10**9, data_infinity="true",
|
||||
prepend='<span class="input-group-text"><span id="max_quota_bytes_value"></span> GB</span>') }}
|
||||
{{ macros.form_field(form.signup_enabled) }}
|
||||
{{ macros.form_field(form.comment) }}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,66 +1,69 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Domain details{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain.name }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{% if current_user.global_admin %}
|
||||
{%- block main_action %}
|
||||
{%- if current_user.global_admin %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for(".domain_genkeys", domain_name=domain.name) }}">
|
||||
{% if domain.dkim_publickey %}
|
||||
{%- if domain.dkim_publickey %}
|
||||
{% trans %}Regenerate keys{% endtrans %}
|
||||
{% else %}
|
||||
{%- else %}
|
||||
{% trans %}Generate keys{% endtrans %}
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table(datatable=False) %}
|
||||
{% set hostname = config["HOSTNAMES"].split(",")[0] %}
|
||||
{%- block content %}
|
||||
{%- call macros.table(datatable=False) %}
|
||||
<tr>
|
||||
<th>{% trans %}Domain name{% endtrans %}</th>
|
||||
<td>{{ domain.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}DNS MX entry{% endtrans %} <i class="fa {{ 'fa-check-circle' if domain.check_mx() else 'fa-exclamation-circle' }}"></i></th>
|
||||
<td><pre>{{ domain.name }}. 600 IN MX 10 {{ hostname }}.</pre></td>
|
||||
<th>{% trans %}DNS MX entry{% endtrans %} <i class="fa {{ 'fa-check-circle text-success' if domain.check_mx() else 'fa-exclamation-circle text-danger' }}"></i></th>
|
||||
<td>{{ macros.clip("dns_mx") }}<pre id="dns_mx" class="pre-config border bg-light">{{ domain.dns_mx }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}DNS SPF entries{% endtrans %}</th>
|
||||
<td><pre>
|
||||
{{ domain.name }}. 600 IN TXT "v=spf1 mx a:{{ hostname }} -all"</pre></td>
|
||||
<td>{{ macros.clip("dns_spf") }}<pre id="dns_spf" class="pre-config border bg-light">{{ domain.dns_spf }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
{% if domain.dkim_publickey %}
|
||||
{%- if domain.dkim_publickey %}
|
||||
<tr>
|
||||
<th>{% trans %}DKIM public key{% endtrans %}</th>
|
||||
<td><pre style="white-space: pre-wrap; word-wrap: break-word;">{{ domain.dkim_publickey }}</pre></td>
|
||||
<td>{{ macros.clip("dkim_key") }}<pre id="dkim_key" class="pre-config border bg-light">{{ domain.dkim_publickey }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}DNS DKIM entry{% endtrans %}</th>
|
||||
<td><pre style="white-space: pre-wrap; word-wrap: break-word;">{{ config["DKIM_SELECTOR"] }}._domainkey.{{ domain.name }}. 600 IN TXT "v=DKIM1; k=rsa; p={{ domain.dkim_publickey }}"</pre></td>
|
||||
<td>{{ macros.clip("dns_dkim") }}<pre id="dns_dkim" class="pre-config border bg-light">{{ domain.dns_dkim }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}DNS DMARC entry{% endtrans %}</th>
|
||||
<td><pre>_dmarc.{{ domain.name }}. 600 IN TXT "v=DMARC1; p=reject;{% if config["DMARC_RUA"] %} rua=mailto:{{ config["DMARC_RUA"] }}@{{ config["DOMAIN"] }};{% endif %}{% if config["DMARC_RUF"] %} ruf=mailto:{{ config["DMARC_RUF"] }}@{{ config["DOMAIN"] }};{% endif %} adkim=s; aspf=s"</pre></td>
|
||||
<td>{{ macros.clip("dns_dmark") }}<pre id="dns_dmark" class="pre-config border bg-light">{{ domain.dns_dmarc }}</pre></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
{%- set tlsa_record=domain.dns_tlsa %}
|
||||
{%- if tlsa_record %}
|
||||
<tr>
|
||||
<th>{% trans %}DNS TLSA entry{% endtrans %}</br><span class="text-secondary text-xs font-weight-normal">Let's Encrypt</br>ISRG Root X1</span></th>
|
||||
<td>{{ macros.clip("dns_tlsa") }}<pre id="dns_tlsa" class="pre-config border bg-light">{{ tlsa_record }}</pre></td>
|
||||
</tr>
|
||||
{%- endif %}
|
||||
<tr>
|
||||
<th>{% trans %}DNS client auto-configuration (RFC6186) entries{% endtrans %}</th>
|
||||
<td>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_submission._tcp.{{ domain.name }}. 600 IN SRV 1 1 587 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_imap._tcp.{{ domain.name }}. 600 IN SRV 100 1 143 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_pop3._tcp.{{ domain.name }}. 600 IN SRV 100 1 110 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
{% if config["TLS_FLAVOR"] != "notls" %}
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_submissions._tcp.{{ domain.name }}. 600 IN SRV 10 1 465 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_imaps._tcp.{{ domain.name }}. 600 IN SRV 10 1 993 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_pop3s._tcp.{{ domain.name }}. 600 IN SRV 10 1 995 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
{% endif %}</td>
|
||||
{{ macros.clip("dns_autoconfig") }}<pre id="dns_autoconfig" class="pre-config border bg-light">
|
||||
{%- for line in domain.dns_autoconfig %}
|
||||
{{ line }}
|
||||
{%- endfor -%}
|
||||
</pre></td>
|
||||
</tr>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "domain/create.html" %}
|
||||
{%- extends "domain/create.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Edit domain{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Domain list{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{% if current_user.global_admin %}
|
||||
{%- block main_action %}
|
||||
{%- if current_user.global_admin %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.domain_create') }}">{% trans %}New domain{% endtrans %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -25,22 +25,22 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for domain in current_user.get_managed_domains() %}
|
||||
{%- for domain in current_user.get_managed_domains() %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.domain_details', domain_name=domain.name) }}" title="{% trans %}Details{% endtrans %}"><i class="fa fa-list"></i></a>
|
||||
{% if current_user.global_admin %}
|
||||
{%- if current_user.global_admin %}
|
||||
<a href="{{ url_for('.domain_edit', domain_name=domain.name) }}" title="{% trans %}Edit{% endtrans %}"><i class="fas fa-pencil-alt"></i></a>
|
||||
<a href="{{ url_for('.domain_delete', domain_name=domain.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="{% trans %}Users{% endtrans %}"><i class="far fa-envelope"></i></a>
|
||||
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="{% trans %}Aliases{% endtrans %}"><i class="fa fa-at"></i></a>
|
||||
<a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="{% trans %}Managers{% endtrans %}"><i class="fa fa-user"></i></a>
|
||||
{% if current_user.global_admin %}
|
||||
{%- if current_user.global_admin %}
|
||||
<a href="{{ url_for('.alternative_list', domain_name=domain.name) }}" title="{% trans %}Alternatives{% endtrans %}"><i class="fa fa-asterisk"></i></a>
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>{{ domain.name }}</td>
|
||||
<td>{{ domain.users | count }} / {{ '∞' if domain.max_users == -1 else domain.max_users }}</td>
|
||||
@@ -49,7 +49,7 @@
|
||||
<td>{{ domain.created_at }}</td>
|
||||
<td>{{ domain.updated_at or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Register a domain{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{%- block content %}
|
||||
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{% call macros.card(title="Requirements") %}
|
||||
{%- call macros.card(title="Requirements") %}
|
||||
<p>{% trans %}In order to register a new domain, you must first setup the
|
||||
domain zone so that the domain <code>MX</code> points to this server{% endtrans %}
|
||||
(<code>{{ config["HOSTNAMES"].split(",")[0] }}</code>).
|
||||
(<code>{{ config["HOSTNAME"] }}</code>).
|
||||
</p>
|
||||
<p>
|
||||
{% trans %}If you do not know how to setup an <code>MX</code> record for your DNS zone,
|
||||
@@ -20,17 +20,17 @@
|
||||
couple minutes after the <code>MX</code> is set so the local server cache
|
||||
expires.{% endtrans %}
|
||||
</p>
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{% call macros.card() %}
|
||||
{% if form.localpart %}
|
||||
{%- call macros.card() %}
|
||||
{%- if form.localpart %}
|
||||
{{ macros.form_fields((form.localpart, form.name), append='<span class="input-group-text">@</span>') }}
|
||||
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||
{% else %}
|
||||
{%- else %}
|
||||
{{ macros.form_field(form.name) }}
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
{{ macros.form_field(form.captcha) }}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Add a fetched account{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{%- block content %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{% call macros.card(title="Remote server") %}
|
||||
{%- call macros.card(title="Remote server") %}
|
||||
{{ macros.form_field(form.protocol) }}
|
||||
{{ macros.form_fields((form.host, form.port)) }}
|
||||
{{ macros.form_field(form.tls) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{% call macros.card(title="Authentication") %}
|
||||
{%- call macros.card(title="Authentication") %}
|
||||
{{ macros.form_field(form.username) }}
|
||||
{{ macros.form_field(form.password) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{% call macros.card(title="Settings") %}
|
||||
{%- call macros.card(title="Settings") %}
|
||||
{{ macros.form_field(form.keep) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "fetch/create.html" %}
|
||||
{%- extends "fetch/create.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Update a fetched account{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Fetched accounts{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{%- block main_action %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.fetch_create', user_email=user.email) }}">{% trans %}Add an account{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -27,7 +27,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fetch in user.fetches %}
|
||||
{%- for fetch in user.fetches %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.fetch_edit', fetch_id=fetch.id) }}" title="{% trans %}Edit{% endtrans %}"><i class="fa fa-pencil"></i></a>
|
||||
@@ -41,7 +41,7 @@
|
||||
<td>{{ fetch.created_at }}</td>
|
||||
<td>{{ fetch.updated_at or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
{%- block content %}
|
||||
{%- call macros.card() %}
|
||||
{{ macros.form(form) }}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,104 +1,127 @@
|
||||
{% macro form_errors(form) %}
|
||||
{% if form.errors %}
|
||||
{% for fieldname, errors in form.errors.items() %}
|
||||
{% if bootstrap_is_hidden_field(form[fieldname]) %}
|
||||
{% for error in errors %}
|
||||
{%- macro form_errors(form) %}
|
||||
{%- if form.errors %}
|
||||
{%- for fieldname, errors in form.errors.items() %}
|
||||
{%- if bootstrap_is_hidden_field(form[fieldname]) %}
|
||||
{%- for error in errors %}
|
||||
<p class="error">{{error}}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro form_field_errors(field) %}
|
||||
{% if field.errors %}
|
||||
{% for error in field.errors %}
|
||||
{%- macro form_field_errors(field) %}
|
||||
{%- if field.errors %}
|
||||
{%- for error in field.errors %}
|
||||
<p class="help-block inline">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro form_fields(fields, prepend='', append='', label=True) %}
|
||||
{% set width = (12 / fields|length)|int %}
|
||||
{%- macro form_fields(fields, prepend='', append='', label=True) %}
|
||||
{%- set width = (12 / fields|length)|int %}
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
{% for field in fields %}
|
||||
{%- for field in fields %}
|
||||
<div class="col-lg-{{ width }} col-xs-12 {{ 'has-error' if field.errors else '' }}">
|
||||
{{ form_individual_field(field, prepend=prepend, append=append, label=label, **kwargs) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro form_individual_field(field, prepend='', append='', label=True, class_="") %}
|
||||
{% if field.type == "BooleanField" %}
|
||||
{{ field(**kwargs) }}<span> </span>
|
||||
{{ field.label if label else '' }}
|
||||
{% else %}
|
||||
{%- macro form_individual_field(field, prepend='', append='', label=True, class_="") %}
|
||||
{%- if field.type == "BooleanField" %}
|
||||
{{ field(**kwargs) }}<span> </span>{{ field.label if label else '' }}
|
||||
{%- else %}
|
||||
{{ field.label if label else '' }}{{ form_field_errors(field) }}
|
||||
{% if prepend %}<div class="input-group-prepend">{% endif %}
|
||||
{% if append %}<div class="input-group-append">{% endif %}
|
||||
{{ prepend|safe }}{{ field(class_="form-control " + class_, **kwargs) }}{{ append|safe }}
|
||||
{% if prepend or append %}</div>{% endif %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{%- if prepend %}<div class="input-group-prepend">{%- elif append %}<div class="input-group-append">{%- endif %}
|
||||
{{ prepend|safe }}{{ field(class_=("form-control " + class_) if class_ else "form-control", **kwargs) }}{{ append|safe }}
|
||||
{%- if prepend or append %}</div>{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro form_field(field) %}
|
||||
{% if field.type == 'SubmitField' %}
|
||||
{{ form_fields((field,), label=False, class="btn btn-default", **kwargs) }}
|
||||
{% else %}
|
||||
{{ form_fields((field,), **kwargs) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{%- macro form_field(field) %}
|
||||
{%- if field.type == 'SubmitField' %}
|
||||
{{- form_fields((field,), label=False, class="btn btn-default", **kwargs) }}
|
||||
{%- else %}
|
||||
{{- form_fields((field,), **kwargs) }}
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro form(form) %}
|
||||
{%- macro form(form) %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{% for field in form %}
|
||||
{% if bootstrap_is_hidden_field(field) %}
|
||||
{%- for field in form %}
|
||||
{%- if bootstrap_is_hidden_field(field) %}
|
||||
{{ field() }}
|
||||
{% else %}
|
||||
{%- else %}
|
||||
{{ form_field(field) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
</form>
|
||||
{% endmacro %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro card(title=None, theme="primary", header=True) %}
|
||||
{%- macro card(title=None, theme="primary", header=True) %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card card-outline card-{{ theme }}">
|
||||
{% if header %}
|
||||
{%- if header %}
|
||||
<div class="card-header border-0">
|
||||
{% if title %}
|
||||
{%- if title %}
|
||||
<h3 class="card-title">{{ title }}</h3>
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
<div class="card-body">
|
||||
{{ caller() }}
|
||||
{{- caller() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro table(title=None, theme="primary", datatable=True) %}
|
||||
{%- macro table(title=None, theme="primary", datatable=True) %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card card-outline card-{{ theme }}">
|
||||
{%- if title %}
|
||||
<div class="card-header border-0">
|
||||
{% if title %}
|
||||
<h3 class="card-title">{{ title }}</h3>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- endif %}
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered {% if datatable %} dataTable {% endif %}">
|
||||
{{ caller() }}
|
||||
<table class="table table-bordered{% if datatable %} dataTable{% endif %}">
|
||||
{{- caller() }}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro fieldset(title=None, field=None, enabled=None, fields=None) %}
|
||||
{%- if field or title %}
|
||||
<fieldset{% if not enabled %} disabled{% endif %}>
|
||||
{%- if field %}
|
||||
<legend>{{ form_individual_field(field) }}</legend>
|
||||
{%- else %}
|
||||
<legend>{{ title }}</legend>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{{- caller() }}
|
||||
{%- if fields %}
|
||||
{%- set kwargs = {"enabled" if enabled else "disabled": ""} %}
|
||||
{%- for field in fields %}
|
||||
{{ form_field(field, **kwargs) }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
</fieldset>
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro clip(target, title=_("copy to clipboard"), icon="copy", color="primary", action="copy") %}
|
||||
<button class="btn btn-{{ color }} btn-xs btn-clip float-right ml-2 mt-1" data-clipboard-action="{{ action }}" data-clipboard-target="#{{ target }}">
|
||||
<i class="fas fa-{{ icon }}" title="{{ title }}" aria-expanded="false"></i><span class="sr-only">{{ title }}</span>
|
||||
</button>
|
||||
{%- endmacro %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Add a manager{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
{%- block content %}
|
||||
{%- call macros.card() %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ macros.form_field(form.manager, class_='mailselect') }}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Manager list{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain.name }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{%- block main_action %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.manager_create', domain_name=domain.name) }}">{% trans %}Add manager{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -21,14 +21,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for manager in domain.managers %}
|
||||
{%- for manager in domain.managers %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.manager_delete', domain_name=domain.name, user_email=manager.email) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
<td>{{ manager }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "form.html" %}
|
||||
{%- extends "form.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}New relay domain{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "form.html" %}
|
||||
{%- extends "form.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Edit relayd domain{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ relay }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Relayed domain list{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{% if current_user.global_admin %}
|
||||
{%- block main_action %}
|
||||
{%- if current_user.global_admin %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.relay_create') }}">{% trans %}New relayed domain{% endtrans %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -23,7 +23,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for relay in relays %}
|
||||
{%- for relay in relays %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.relay_edit', relay_name=relay.name) }}" title="{% trans %}Edit{% endtrans %}"><i class="fa fa-pencil"></i></a>
|
||||
@@ -35,7 +35,7 @@
|
||||
<td>{{ relay.created_at }}</td>
|
||||
<td>{{ relay.updated_at or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,144 +1,156 @@
|
||||
<div class="sidebar">
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="sidebar text-sm">
|
||||
{%- if current_user.is_authenticated %}
|
||||
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
|
||||
<div class="image">
|
||||
<div class="div-circle elevation-2"><i class="fa fa-user text-lg text-dark"></i></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="text-center text-primary">{{ current_user }}</span>
|
||||
<a href="{{ url_for('.user_settings') }}" class="d-block">{{ current_user }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- endif %}
|
||||
<nav class="mt-2">
|
||||
<ul class="nav nav-pills nav-sidebar flex-column" role="menu">
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-header">{% trans %}My account{% endtrans %}</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.user_settings') }}" class="nav-link">
|
||||
{%- if current_user.is_authenticated %}
|
||||
<li class="nav-header text-uppercase text-primary" role="none">{% trans %}My account{% endtrans %}</li>
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.user_settings') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-wrench"></i>
|
||||
<p class="text">{% trans %}Settings{% endtrans %}</p>
|
||||
<p>{% trans %}Settings{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.user_password') }}" class="nav-link">
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.user_password') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-lock"></i>
|
||||
<p class="text">{% trans %}Update password{% endtrans %}</p>
|
||||
<p>{% trans %}Update password{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.user_reply') }}" class="nav-link">
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.user_reply') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-plane"></i>
|
||||
<p class="text">{% trans %}Auto-reply{% endtrans %}</p>
|
||||
<p>{% trans %}Auto-reply{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.fetch_list') }}" class="nav-link">
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.fetch_list') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fas fa-download"></i>
|
||||
<p class="text">{% trans %}Fetched accounts{% endtrans %}</p>
|
||||
<p>{% trans %}Fetched accounts{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.token_list') }}" class="nav-link">
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.token_list') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fas fa-ticket-alt"></i>
|
||||
<p class="text">{% trans %}Authentication tokens{% endtrans %}</p>
|
||||
<p>{% trans %}Authentication tokens{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if current_user.manager_of or current_user.global_admin %}
|
||||
<li class="nav-header">{% trans %}Administration{% endtrans %}</li>
|
||||
{% endif %}
|
||||
{% if current_user.global_admin %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.announcement') }}" class="nav-link">
|
||||
<i class="nav-icon fa fa-bullhorn"></i>
|
||||
<p class="text">{% trans %}Announcement{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.admin_list') }}" class="nav-link">
|
||||
<i class="nav-icon fa fa-user"></i>
|
||||
<p class="text">{% trans %}Administrators{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.relay_list') }}" class="nav-link">
|
||||
<i class="nav-icon fa fa-reply-all"></i>
|
||||
<p class="text">{% trans %}Relayed domains{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ config["WEB_ADMIN"] }}/antispam/" target="_blank" class="nav-link">
|
||||
<i class="nav-icon fas fa-trash-alt"></i>
|
||||
<p class="text">{% trans %}Antispam{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.manager_of or current_user.global_admin %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.domain_list') }}" class="nav-link">
|
||||
<i class="nav-icon fa fa-envelope"></i>
|
||||
<p class="text">{% trans %}Mail domains{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<li class="nav-header">{% trans %}Go to{% endtrans %}</li>
|
||||
{% if config["WEBMAIL"] != "none" %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ config["WEB_WEBMAIL"] }}" target="_blank" class="nav-link">
|
||||
<i class="nav-icon far fa-envelope"></i>
|
||||
<p class="text">{% trans %}Webmail{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.client') }}" class="nav-link">
|
||||
{%- if current_user.is_authenticated %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.client') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-laptop"></i>
|
||||
<p class="text">{% trans %}Client setup{% endtrans %}</p>
|
||||
<p>{% trans %}Client setup{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ config["WEBSITE"] }}" target="_blank" class="nav-link">
|
||||
{%- endif %}
|
||||
|
||||
{%- if current_user.manager_of or current_user.global_admin %}
|
||||
<li class="nav-header text-uppercase text-primary" role="none">{% trans %}Administration{% endtrans %}</li>
|
||||
{%- endif %}
|
||||
{%- if current_user.global_admin %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.announcement') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-bullhorn"></i>
|
||||
<p>{% trans %}Announcement{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.admin_list') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-user"></i>
|
||||
<p>{% trans %}Administrators{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.relay_list') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-reply-all"></i>
|
||||
<p>{% trans %}Relayed domains{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ config["WEB_ADMIN"] }}/antispam/" data-clicked="{{ url_for('.antispam') }}" target="_blank" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fas fa-trash-alt"></i>
|
||||
<p>{% trans %}Antispam{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{%- endif %}
|
||||
{%- if current_user.manager_of or current_user.global_admin %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.domain_list') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-envelope"></i>
|
||||
<p>{% trans %}Mail domains{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
||||
<li class="nav-header text-uppercase text-primary" role="none">{% trans %}Go to{% endtrans %}</li>
|
||||
{%- if config["WEBMAIL"] != "none" %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ config["WEB_WEBMAIL"] }}" target="_blank" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon far fa-envelope"></i>
|
||||
<p>{% trans %}Webmail{% endtrans %} <i class="fas fa-external-link-alt text-xs"></i></p>
|
||||
</a>
|
||||
</li>
|
||||
{%- endif %}
|
||||
{%- if not current_user.is_authenticated %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.client') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-laptop"></i>
|
||||
<p>{% trans %}Client setup{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{%- endif %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ config["WEBSITE"] }}" target="_blank" class="nav-link" role="menuitem" rel="noreferrer">
|
||||
<i class="nav-icon fa fa-globe"></i>
|
||||
<p class="text">{% trans %}Website{% endtrans %}</p>
|
||||
<p>{% trans %}Website{% endtrans %} <i class="fas fa-external-link-alt text-xs"></i></p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="https://mailu.io" target="_blank" class="nav-link">
|
||||
<li class="nav-item" role="none">
|
||||
<a href="https://mailu.io" target="_blank" class="nav-link" role="menuitem" rel="noreferrer">
|
||||
<i class="nav-icon fa fa-life-ring"></i>
|
||||
<p class="text">{% trans %}Help{% endtrans %}</p>
|
||||
<p>{% trans %}Help{% endtrans %} <i class="fas fa-external-link-alt text-xs"></i></p>
|
||||
</a>
|
||||
</li>
|
||||
{% if config['DOMAIN_REGISTRATION'] %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.domain_signup') }}" class="nav-link">
|
||||
{%- if config['DOMAIN_REGISTRATION'] %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.domain_signup') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-plus-square"></i>
|
||||
<p class="text">{% trans %}Register a domain{% endtrans %}</p>
|
||||
<p>{% trans %}Register a domain{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.logout') }}" class="nav-link">
|
||||
{%- endif %}
|
||||
{%- if current_user.is_authenticated %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.logout') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fas fa-sign-out-alt"></i>
|
||||
<p class="text">{% trans %}Sign out{% endtrans %}</p>
|
||||
<p>{% trans %}Sign out{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('sso.login') }}" class="nav-link">
|
||||
<i class="nav-icon fas fa-sign-in-alt"></i>
|
||||
<p class="text">{% trans %}Sign in{% endtrans %}</p>
|
||||
<p>{% trans %}Sign in{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% if signup_domains %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('.user_signup') }}" class="nav-link">
|
||||
{%- if signup_domains %}
|
||||
<li class="nav-item" role="none">
|
||||
<a href="{{ url_for('.user_signup') }}" class="nav-link" role="menuitem">
|
||||
<i class="nav-icon fa fa-user-plus"></i>
|
||||
<p class="text">{% trans %}Sign up{% endtrans %}</p>
|
||||
<p>{% trans %}Sign up{% endtrans %}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "form.html" %}
|
||||
{%- extends "form.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Create an authentication token{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Authentication tokens{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{%- block main_action %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.token_create', user_email=user.email) }}">{% trans %}New token{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -23,7 +23,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for token in user.tokens %}
|
||||
{%- for token in user.tokens %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('.token_delete', token_id=token.id) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||
@@ -32,7 +32,7 @@
|
||||
<td>{{ token.ip or "any" }}</td>
|
||||
<td>{{ token.created_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}New user{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain.name }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{%- block content %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{% call macros.card(_("General")) %}
|
||||
{%- call macros.card(_("General")) %}
|
||||
{{ macros.form_field(form.localpart, append='<span class="input-group-text">@'+domain.name+'</span>') }}
|
||||
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||
{{ macros.form_field(form.displayed_name) }}
|
||||
{{ macros.form_field(form.comment) }}
|
||||
{{ macros.form_field(form.enabled) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{% call macros.card(_("Features and quotas"), theme="success") %}
|
||||
{{ macros.form_field(form.quota_bytes, step=1000000000, max=(max_quota_bytes or domain.max_quota_bytes or 50000000000),
|
||||
prepend='<span class="input-group-text"><span id="quota">'+((form.quota_bytes.data//1000000000).__str__() if form.quota_bytes.data else '∞')+'</span> GiB</span>',
|
||||
oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }}
|
||||
{%- call macros.card(_("Features and quotas"), theme="success") %}
|
||||
{{ macros.form_field(form.quota_bytes, step=1000000000, max=(max_quota_bytes or domain.max_quota_bytes or 50*10**9), data_infinity="true",
|
||||
prepend='<span class="input-group-text"><span id="quota_bytes_value"></span> GB</span>') }}
|
||||
{{ macros.form_field(form.enable_imap) }}
|
||||
{{ macros.form_field(form.enable_pop) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "user/create.html" %}
|
||||
{%- extends "user/create.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Edit user{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}Forward emails{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ macros.form_field(form.forward_enabled,
|
||||
onchange="if(this.checked){$('#forward_destination,#forward_keep').removeAttr('disabled')}
|
||||
else{$('#forward_destination,#forward_keep').attr('disabled', '')}") }}
|
||||
{{ macros.form_field(form.forward_keep,
|
||||
**{("enabled" if user.forward_enabled else "disabled"): ""}) }}
|
||||
{{ macros.form_field(form.forward_destination,
|
||||
**{("enabled" if user.forward_enabled else "disabled"): ""}) }}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}User list{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain.name }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block main_action %}
|
||||
{%- block main_action %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for('.user_create', domain_name=domain.name) }}">{% trans %}Add user{% endtrans %}</a>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
@@ -27,8 +27,8 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in domain.users %}
|
||||
<tr {% if not user.enabled %}class="warning"{% endif %}>
|
||||
{%- for user in domain.users %}
|
||||
<tr{% if not user.enabled %} class="warning"{% endif %}>
|
||||
<td>
|
||||
<a href="{{ url_for('.user_edit', user_email=user.email) }}" title="{% trans %}Edit{% endtrans %}"><i class="fas fa-pencil-alt"></i></a>
|
||||
<a href="{{ url_for('.user_delete', user_email=user.email) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||
@@ -48,7 +48,7 @@
|
||||
<td>{{ user.created_at }}</td>
|
||||
<td>{{ user.updated_at or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "form.html" %}
|
||||
{%- extends "form.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Password update{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Automatic reply{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.card() %}
|
||||
{%- block content %}
|
||||
{%- call macros.card() %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ macros.form_field(form.reply_enabled,
|
||||
onchange="if(this.checked){$('#reply_subject,#reply_body,#reply_enddate,#reply_startdate').removeAttr('readonly')}
|
||||
else{$('#reply_subject,#reply_body,#reply_enddate,#reply_startdate').attr('readonly', '')}") }}
|
||||
{{ macros.form_field(form.reply_subject,
|
||||
**{("rw" if user.reply_enabled else "readonly"): ""}) }}
|
||||
{{ macros.form_field(form.reply_body, rows=10,
|
||||
**{("rw" if user.reply_enabled else "readonly"): ""}) }}
|
||||
{{ macros.form_field(form.reply_enddate,
|
||||
**{("rw" if user.reply_enabled else "readonly"): ""}) }}
|
||||
{{ macros.form_field(form.reply_startdate,
|
||||
**{("rw" if user.reply_enabled else "readonly"): ""}) }}
|
||||
|
||||
{%- call macros.fieldset(
|
||||
field=form.reply_enabled,
|
||||
enabled=user.reply_enabled,
|
||||
fields=[form.reply_subject, form.reply_body, form.reply_enddate, form.reply_startdate]) %}
|
||||
{%- endcall %}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}User settings{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ user }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{%- block content %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{% call macros.card(title=_("Displayed name")) %}
|
||||
|
||||
{%- call macros.card(title=_("Displayed name")) %}
|
||||
{{ macros.form_field(form.displayed_name) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{% call macros.card(title=_("Antispam")) %}
|
||||
{{ macros.form_field(form.spam_enabled) }}
|
||||
{%- call macros.card(title=_("Antispam")) %}
|
||||
{%- call macros.fieldset(field=form.spam_enabled, enabled=user.spam_enabled) %}
|
||||
{{ macros.form_field(form.spam_threshold, step=1, max=100,
|
||||
prepend='<span class="input-group-text"><span id="threshold">'+form.spam_threshold.data.__str__()+'</span> / 100</span>',
|
||||
oninput='$("#threshold").text(this.value);') }}
|
||||
{% endcall %}
|
||||
prepend='<span class="input-group-text"><span id="spam_threshold_value"></span> / 100</span>') }}
|
||||
{%- endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{% call macros.card(title=_("Auto-forward")) %}
|
||||
{{ macros.form_field(form.forward_enabled,
|
||||
onchange="if(this.checked){$('#forward_destination,#forward_keep').removeAttr('disabled')}
|
||||
else{$('#forward_destination,#forward_keep').attr('disabled', '')}") }}
|
||||
{{ macros.form_field(form.forward_keep,
|
||||
**{("enabled" if user.forward_enabled else "disabled"): ""}) }}
|
||||
{{ macros.form_field(form.forward_destination,
|
||||
**{("enabled" if user.forward_enabled else "disabled"): ""}) }}
|
||||
{% endcall %}
|
||||
{%- call macros.card(title=_("Auto-forward")) %}
|
||||
{%- call macros.fieldset(
|
||||
field=form.forward_enabled,
|
||||
enabled=user.forward_enabled,
|
||||
fields=[form.forward_keep, form.forward_destination]) %}
|
||||
{%- endcall %}
|
||||
{%- endcall %}
|
||||
|
||||
{{ macros.form_field(form.submit) }}
|
||||
</form>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Sign up{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{{ domain }}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{%- block content %}
|
||||
<form class="form" method="post" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{% call macros.card() %}
|
||||
{%- call macros.card() %}
|
||||
{{ macros.form_field(form.localpart, append='<span class="input-group-text">@'+domain.name+'</span>') }}
|
||||
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||
{% if form.captcha %}
|
||||
{%- if form.captcha %}
|
||||
{{ macros.form_field(form.captcha) }}
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
{{ macros.form_field(form.submit) }}
|
||||
{% endcall %}
|
||||
{%- endcall %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{%- block title %}
|
||||
{% trans %}Sign up{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
{%- block subtitle %}
|
||||
{% trans %}pick a domain for the new account{% endtrans %}
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call macros.table() %}
|
||||
{%- block content %}
|
||||
{%- call macros.table() %}
|
||||
<tr>
|
||||
<th>{% trans %}Domain{% endtrans %}</th>
|
||||
<th>{% trans %}Available slots{% endtrans %}</th>
|
||||
<th>{% trans %}Quota{% endtrans %}</th>
|
||||
</tr>
|
||||
{% for domain_name, domain in available_domains.items() %}
|
||||
{%- for domain_name, domain in available_domains.items() %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('.user_signup', domain_name=domain_name) }}">{{ domain_name }}</a></td>
|
||||
<td>{{ '∞' if domain.max_users == -1 else domain.max_users - (domain.users | count)}}</td>
|
||||
<td>{{ domain.max_quota_bytes or config['DEFAULT_QUOTA'] | filesizeformat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
{%- endfor %}
|
||||
{%- endcall %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{%- extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{%- block content %}
|
||||
<div class="alert alert-warning" role="alert">{% trans %}We are still working on this feature!{% endtrans %}</div>
|
||||
{% endblock %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -40,3 +40,8 @@ def webmail():
|
||||
@ui.route('/client', methods=['GET'])
|
||||
def client():
|
||||
return flask.render_template('client.html')
|
||||
|
||||
@ui.route('/antispam', methods=['GET'])
|
||||
def antispam():
|
||||
return flask.render_template('antispam.html')
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ from mailu.ui import ui, forms, access
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
@ui.route('/language/<language>', methods=['GET'])
|
||||
@ui.route('/language/<language>', methods=['POST'])
|
||||
def set_language(language=None):
|
||||
flask.session['language'] = language
|
||||
return flask.redirect(flask.url_for('.user_settings'))
|
||||
return flask.Response(status=200)
|
||||
|
||||
|
||||
@@ -129,23 +129,6 @@ def user_password(user_email):
|
||||
return flask.render_template('user/password.html', form=form, user=user)
|
||||
|
||||
|
||||
@ui.route('/user/forward', methods=['GET', 'POST'], defaults={'user_email': None})
|
||||
@ui.route('/user/forward/<path:user_email>', methods=['GET', 'POST'])
|
||||
@access.owner(models.User, 'user_email')
|
||||
def user_forward(user_email):
|
||||
user_email_or_current = user_email or flask_login.current_user.email
|
||||
user = models.User.query.get(user_email_or_current) or flask.abort(404)
|
||||
form = forms.UserForwardForm(obj=user)
|
||||
if form.validate_on_submit():
|
||||
form.populate_obj(user)
|
||||
models.db.session.commit()
|
||||
flask.flash('Forward destination updated for %s' % user)
|
||||
if user_email:
|
||||
return flask.redirect(
|
||||
flask.url_for('.user_list', domain_name=user.domain.name))
|
||||
return flask.render_template('user/forward.html', form=form, user=user)
|
||||
|
||||
|
||||
@ui.route('/user/reply', methods=['GET', 'POST'], defaults={'user_email': None})
|
||||
@ui.route('/user/reply/<path:user_email>', methods=['GET', 'POST'])
|
||||
@access.owner(models.User, 'user_email')
|
||||
|
||||
@@ -6,6 +6,9 @@ try:
|
||||
except ImportError:
|
||||
import pickle
|
||||
|
||||
import dns
|
||||
import dns.resolver
|
||||
|
||||
import hmac
|
||||
import secrets
|
||||
import time
|
||||
@@ -25,7 +28,6 @@ from itsdangerous.encoding import want_bytes
|
||||
from werkzeug.datastructures import CallbackDict
|
||||
from werkzeug.contrib import fixers
|
||||
|
||||
|
||||
# Login configuration
|
||||
login = flask_login.LoginManager()
|
||||
login.login_view = "sso.login"
|
||||
@@ -37,6 +39,34 @@ def handle_needs_login():
|
||||
flask.url_for('sso.login', next=flask.request.endpoint)
|
||||
)
|
||||
|
||||
# DNS stub configured to do DNSSEC enabled queries
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.use_edns(0, 0, 1232)
|
||||
resolver.flags = dns.flags.AD | dns.flags.RD
|
||||
|
||||
def has_dane_record(domain, timeout=10):
|
||||
try:
|
||||
result = resolver.query(f'_25._tcp.{domain}', dns.rdatatype.TLSA,dns.rdataclass.IN, lifetime=timeout)
|
||||
if result.response.flags & dns.flags.AD:
|
||||
for record in result:
|
||||
if isinstance(record, dns.rdtypes.ANY.TLSA.TLSA):
|
||||
record.validate()
|
||||
if record.usage in [2,3] and record.selector in [0,1] and record.mtype in [0,1,2]:
|
||||
return True
|
||||
except dns.resolver.NoNameservers:
|
||||
# If the DNSSEC data is invalid and the DNS resolver is DNSSEC enabled
|
||||
# we will receive this non-specific exception. The safe behaviour is to
|
||||
# accept to defer the email.
|
||||
flask.current_app.logger.warn(f'Unable to lookup the TLSA record for {domain}. Is the DNSSEC zone okay on https://dnsviz.net/d/{domain}/dnssec/?')
|
||||
return app.config['DEFER_ON_TLS_ERROR']
|
||||
except dns.exception.Timeout:
|
||||
flask.current_app.logger.warn(f'Timeout while resolving the TLSA record for {domain} ({timeout}s).')
|
||||
except dns.resolver.NXDOMAIN:
|
||||
pass # this is expected, not TLSA record is fine
|
||||
except Exception as e:
|
||||
flask.current_app.logger.error(f'Error while looking up the TLSA record for {domain} {e}')
|
||||
pass
|
||||
|
||||
# Rate limiter
|
||||
limiter = limiter.LimitWraperFactory()
|
||||
|
||||
@@ -46,15 +76,10 @@ babel = flask_babel.Babel()
|
||||
@babel.localeselector
|
||||
def get_locale():
|
||||
""" selects locale for translation """
|
||||
translations = list(map(str, babel.list_translations()))
|
||||
flask.session['available_languages'] = translations
|
||||
|
||||
try:
|
||||
language = flask.session['language']
|
||||
except KeyError:
|
||||
language = flask.request.accept_languages.best_match(translations)
|
||||
language = flask.session.get('language')
|
||||
if not language in flask.current_app.config.translations:
|
||||
language = flask.request.accept_languages.best_match(flask.current_app.config.translations.keys())
|
||||
flask.session['language'] = language
|
||||
|
||||
return language
|
||||
|
||||
|
||||
@@ -450,7 +475,7 @@ class MailuSessionExtension:
|
||||
with cleaned.get_lock():
|
||||
if not cleaned.value:
|
||||
cleaned.value = True
|
||||
flask.current_app.logger.error('cleaning')
|
||||
flask.current_app.logger.info('cleaning session store')
|
||||
MailuSessionExtension.cleanup_sessions(app)
|
||||
|
||||
app.before_first_request(cleaner)
|
||||
|
||||
@@ -12,20 +12,19 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"admin-lte": "^3.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"clipboard": "^2.0.8",
|
||||
"compression-webpack-plugin": "^8.0.1",
|
||||
"css-loader": "^6.2.0",
|
||||
"expose-loader": "^3.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"less": "^4.1.1",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"datatables.net-plugins": "^1.10.24",
|
||||
"import-glob": "^1.5.0",
|
||||
"less-loader": "^10.0.1",
|
||||
"mini-css-extract-plugin": "^2.2.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"sass": "<1.33.0",
|
||||
"sass-loader": "^12.1.0",
|
||||
"select2": "^4.0.13",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-cli": "^4.7.2"
|
||||
"terser-webpack-plugin": "^5.2.0",
|
||||
"webpack-cli": "^4.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ Flask-Migrate==2.4.0
|
||||
Flask-Script==2.0.6
|
||||
Flask-SQLAlchemy==2.4.0
|
||||
Flask-WTF==0.14.2
|
||||
gunicorn==19.9.0
|
||||
gunicorn==20.1.0
|
||||
idna==2.8
|
||||
infinity==1.4
|
||||
intervals==0.8.1
|
||||
|
||||
@@ -1,65 +1,76 @@
|
||||
var path = require("path");
|
||||
var webpack = require("webpack");
|
||||
var css = require("mini-css-extract-plugin");
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const css = require('mini-css-extract-plugin');
|
||||
const mini = require('css-minimizer-webpack-plugin');
|
||||
const terse = require('terser-webpack-plugin');
|
||||
const compress = require('compression-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
mode: 'production',
|
||||
entry: {
|
||||
app: "./assets/app.js",
|
||||
vendor: "./assets/vendor.js"
|
||||
app: {
|
||||
import: './assets/app.js',
|
||||
dependOn: 'vendor',
|
||||
},
|
||||
vendor: './assets/vendor.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, "static/"),
|
||||
filename: "[name].js"
|
||||
path: path.resolve(__dirname, 'static/'),
|
||||
filename: '[name].js',
|
||||
assetModuleFilename: '[name][ext]',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ['babel-loader']
|
||||
use: ['babel-loader', 'import-glob'],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [css.loader, 'css-loader', 'sass-loader']
|
||||
test: /\.s?css$/i,
|
||||
use: [css.loader, 'css-loader', 'sass-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [css.loader, 'css-loader', 'less-loader']
|
||||
test: /\.less$/i,
|
||||
use: [css.loader, 'css-loader', 'less-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [css.loader, 'css-loader']
|
||||
test: /\.(json|png|svg|jpg|jpeg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
// Exposes jQuery for use outside Webpack build
|
||||
test: require.resolve('jquery'),
|
||||
use: [{
|
||||
loader: 'expose-loader',
|
||||
options: {
|
||||
exposes: [
|
||||
{
|
||||
globalName: '$',
|
||||
override: true,
|
||||
},
|
||||
{
|
||||
globalName: 'jQuery',
|
||||
override: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}]
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new css({
|
||||
filename: "[name].css",
|
||||
chunkFilename: "[id].css"
|
||||
}),
|
||||
new css({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css',
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
$: "jquery",
|
||||
jQuery: "jquery"
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery',
|
||||
ClipboardJS: 'clipboard',
|
||||
}),
|
||||
new compress({
|
||||
filename: '[path][base].gz',
|
||||
algorithm: "gzip",
|
||||
exclude: /\.(png|gif|jpe?g)$/,
|
||||
threshold: 5120,
|
||||
minRatio: 0.8,
|
||||
deleteOriginalAssets: false,
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new terse(),
|
||||
new mini({
|
||||
minimizerOptions: {
|
||||
preset: [
|
||||
'default', {
|
||||
discardComments: { removeAll: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO as builder
|
||||
WORKDIR /tmp
|
||||
RUN apk add git build-base automake autoconf libtool dovecot-dev xapian-core-dev icu-dev
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
# python3 shared with most images
|
||||
RUN apk add --no-cache \
|
||||
@@ -16,6 +16,8 @@ COPY conf /conf
|
||||
COPY static /static
|
||||
COPY *.py /
|
||||
|
||||
RUN gzip -k9 /static/*.ico /static/*.txt
|
||||
|
||||
EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 10025/tcp 10143/tcp
|
||||
VOLUME ["/certs"]
|
||||
VOLUME ["/overrides"]
|
||||
|
||||
@@ -18,7 +18,7 @@ http {
|
||||
keepalive_timeout 65;
|
||||
server_tokens off;
|
||||
absolute_redirect off;
|
||||
resolver {{ RESOLVER }} valid=30s;
|
||||
resolver {{ RESOLVER }} ipv6=off valid=30s;
|
||||
|
||||
{% if REAL_IP_HEADER %}
|
||||
real_ip_header {{ REAL_IP_HEADER }};
|
||||
@@ -33,6 +33,17 @@ http {
|
||||
default $http_x_forwarded_proto;
|
||||
'' $scheme;
|
||||
}
|
||||
map $uri $expires {
|
||||
default off;
|
||||
~*\.(ico|css|js|gif|jpeg|jpg|png|woff2?|ttf|otf|svg|tiff|eot|webp)$ 97d;
|
||||
}
|
||||
|
||||
# compression
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
gzip_types text/plain text/css application/xml application/javascript
|
||||
gzip_min_length 1024;
|
||||
# TODO: figure out how to server pre-compressed assets from admin container
|
||||
|
||||
{% if KUBERNETES_INGRESS != 'true' and TLS_FLAVOR in [ 'letsencrypt', 'cert' ] %}
|
||||
# Enable the proxy for certbot if the flavor is letsencrypt and not on kubernetes
|
||||
@@ -50,6 +61,7 @@ http {
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
@@ -94,7 +106,7 @@ http {
|
||||
# Remove headers to prevent duplication and information disclosure
|
||||
proxy_hide_header X-XSS-Protection;
|
||||
proxy_hide_header X-Powered-By;
|
||||
|
||||
|
||||
add_header X-Frame-Options 'SAMEORIGIN';
|
||||
add_header X-Content-Type-Options 'nosniff';
|
||||
add_header X-Permitted-Cross-Domain-Policies 'none';
|
||||
@@ -113,7 +125,6 @@ http {
|
||||
return 403;
|
||||
}
|
||||
{% else %}
|
||||
|
||||
include /overrides/*.conf;
|
||||
|
||||
# Actual logic
|
||||
@@ -143,6 +154,7 @@ http {
|
||||
|
||||
{% if WEB_WEBMAIL != '/' and WEBROOT_REDIRECT != 'none' %}
|
||||
location / {
|
||||
expires $expires;
|
||||
{% if WEBROOT_REDIRECT %}
|
||||
try_files $uri {{ WEBROOT_REDIRECT }};
|
||||
{% else %}
|
||||
@@ -197,6 +209,7 @@ http {
|
||||
include /etc/nginx/proxy.conf;
|
||||
proxy_set_header X-Forwarded-Prefix {{ WEB_ADMIN }};
|
||||
proxy_pass http://$admin;
|
||||
expires $expires;
|
||||
}
|
||||
|
||||
location {{ WEB_ADMIN }}/antispam {
|
||||
@@ -257,7 +270,7 @@ mail {
|
||||
server_name {{ HOSTNAMES.split(",")[0] }};
|
||||
auth_http http://127.0.0.1:8000/auth/email;
|
||||
proxy_pass_error_message on;
|
||||
resolver {{ RESOLVER }} valid=30s;
|
||||
resolver {{ RESOLVER }} ipv6=off valid=30s;
|
||||
|
||||
{% if TLS and not TLS_ERROR %}
|
||||
include /etc/nginx/tls.conf;
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 924 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.8 KiB |
2
core/nginx/static/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
@@ -1,6 +1,6 @@
|
||||
# This is an idle image to dynamically replace any component if disabled.
|
||||
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
|
||||
CMD sleep 1000000d
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
# python3 shared with most images
|
||||
RUN apk add --no-cache \
|
||||
@@ -12,6 +12,10 @@ RUN pip3 install socrate==0.2.0
|
||||
RUN pip3 install "podop>0.2.5"
|
||||
|
||||
# Image specific layers under this line
|
||||
RUN apk add --no-cache --virtual .build-deps gcc musl-dev python3-dev
|
||||
RUN pip3 install --no-binary :all: postfix-mta-sts-resolver==1.0.1
|
||||
RUN apk del .build-deps gcc musl-dev python3-dev
|
||||
|
||||
RUN apk add --no-cache postfix postfix-pcre cyrus-sasl-login
|
||||
|
||||
COPY conf /conf
|
||||
|
||||
@@ -58,13 +58,17 @@ tls_ssl_options = NO_COMPRESSION, NO_TICKET
|
||||
# 2. not all will have and up-to-date TLS stack.
|
||||
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
|
||||
smtp_tls_protocols =!SSLv2,!SSLv3
|
||||
smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('may') }}
|
||||
smtp_tls_policy_maps=lmdb:/etc/postfix/tls_policy.map
|
||||
smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('dane') }}
|
||||
smtp_tls_dane_insecure_mx_policy = {{ 'dane' if DEFER_ON_TLS_ERROR else 'may' }}
|
||||
smtp_tls_policy_maps=lmdb:/etc/postfix/tls_policy.map, ${podop}dane, socketmap:unix:/tmp/mta-sts.socket:postfix
|
||||
smtp_tls_CApath = /etc/ssl/certs
|
||||
smtp_tls_session_cache_database = lmdb:/dev/shm/postfix/smtp_scache
|
||||
smtpd_tls_session_cache_database = lmdb:/dev/shm/postfix/smtpd_scache
|
||||
smtp_host_lookup = dns
|
||||
smtp_dns_support_level = dnssec
|
||||
delay_warning_time = 5m
|
||||
smtp_tls_loglevel = 1
|
||||
notify_classes = resource, software, delay
|
||||
|
||||
###############
|
||||
# Virtual
|
||||
|
||||
10
core/postfix/conf/mta-sts-daemon.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
path: "/tmp/mta-sts.socket"
|
||||
mode: 0600
|
||||
shutdown_timeout: 20
|
||||
cache:
|
||||
type: internal
|
||||
options:
|
||||
cache_size: 10000
|
||||
default_zone:
|
||||
strict_testing: {{ 'true' if DEFER_ON_TLS_ERROR else 'false' }}
|
||||
timeout: 4
|
||||
@@ -19,9 +19,10 @@ def start_podop():
|
||||
url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/"
|
||||
# TODO: Remove verbosity setting from Podop?
|
||||
run_server(0, "postfix", "/tmp/podop.socket", [
|
||||
("transport", "url", url + "transport/§"),
|
||||
("alias", "url", url + "alias/§"),
|
||||
("domain", "url", url + "domain/§"),
|
||||
("transport", "url", url + "transport/§"),
|
||||
("alias", "url", url + "alias/§"),
|
||||
("dane", "url", url + "dane/§"),
|
||||
("domain", "url", url + "domain/§"),
|
||||
("mailbox", "url", url + "mailbox/§"),
|
||||
("recipientmap", "url", url + "recipient/map/§"),
|
||||
("sendermap", "url", url + "sender/map/§"),
|
||||
@@ -30,6 +31,12 @@ def start_podop():
|
||||
("senderrate", "url", url + "sender/rate/§")
|
||||
])
|
||||
|
||||
def start_mta_sts_daemon():
|
||||
os.chmod("/root/", 0o755) # read access to /root/.netrc required
|
||||
os.setuid(getpwnam('postfix').pw_uid)
|
||||
from postfix_mta_sts_resolver import daemon
|
||||
daemon.main()
|
||||
|
||||
def is_valid_postconf_line(line):
|
||||
return not line.startswith("#") \
|
||||
and not line == ''
|
||||
@@ -68,10 +75,13 @@ for map_file in glob.glob("/overrides/*.map"):
|
||||
os.system("postmap {}".format(destination))
|
||||
os.remove(destination)
|
||||
|
||||
if not os.path.exists("/etc/postfix/tls_policy.map.db"):
|
||||
with open("/etc/postfix/tls_policy.map", "w") as f:
|
||||
for domain in ['gmail.com', 'yahoo.com', 'hotmail.com', 'aol.com', 'outlook.com', 'comcast.net', 'icloud.com', 'msn.com', 'hotmail.co.uk', 'live.com', 'yahoo.co.in', 'me.com', 'mail.ru', 'cox.net', 'yahoo.co.uk', 'verizon.net', 'ymail.com', 'hotmail.it', 'kw.com', 'yahoo.com.tw', 'mac.com', 'live.se', 'live.nl', 'yahoo.com.br', 'googlemail.com', 'libero.it', 'web.de', 'allstate.com', 'btinternet.com', 'online.no', 'yahoo.com.au', 'live.dk', 'earthlink.net', 'yahoo.fr', 'yahoo.it', 'gmx.de', 'hotmail.fr', 'shawinc.com', 'yahoo.de', 'moe.edu.sg', 'naver.com', 'bigpond.com', 'statefarm.com', 'remax.net', 'rocketmail.com', 'live.no', 'yahoo.ca', 'bigpond.net.au', 'hotmail.se', 'gmx.at', 'live.co.uk', 'mail.com', 'yahoo.in', 'yandex.ru', 'qq.com', 'charter.net', 'indeedemail.com', 'alice.it', 'hotmail.de', 'bluewin.ch', 'optonline.net', 'wp.pl', 'yahoo.es', 'hotmail.no', 'pindotmedia.com', 'orange.fr', 'live.it', 'yahoo.co.id', 'yahoo.no', 'hotmail.es', 'morganstanley.com', 'wellsfargo.com', 'wanadoo.fr', 'facebook.com', 'yahoo.se', 'fema.dhs.gov', 'rogers.com', 'yahoo.com.hk', 'live.com.au', 'nic.in', 'nab.com.au', 'ubs.com', 'shaw.ca', 'umich.edu', 'westpac.com.au', 'yahoo.com.mx', 'yahoo.com.sg', 'farmersagent.com', 'yahoo.dk', 'dhs.gov']:
|
||||
f.write(f'{domain}\tsecure\n')
|
||||
if os.path.exists("/overrides/mta-sts-daemon.yml"):
|
||||
shutil.copyfile("/overrides/mta-sts-daemon.yml", "/etc/mta-sts-daemon.yml")
|
||||
else:
|
||||
conf.jinja("/conf/mta-sts-daemon.yml", os.environ, "/etc/mta-sts-daemon.yml")
|
||||
|
||||
if not os.path.exists("/etc/postfix/tls_policy.map.lmdb"):
|
||||
open("/etc/postfix/tls_policy.map", "a").close()
|
||||
os.system("postmap /etc/postfix/tls_policy.map")
|
||||
|
||||
if "RELAYUSER" in os.environ:
|
||||
@@ -81,6 +91,7 @@ if "RELAYUSER" in os.environ:
|
||||
|
||||
# Run Podop and Postfix
|
||||
multiprocessing.Process(target=start_podop).start()
|
||||
multiprocessing.Process(target=start_mta_sts_daemon).start()
|
||||
os.system("/usr/libexec/postfix/post-install meta_directory=/etc/postfix create-missing")
|
||||
# Before starting postfix, we need to check permissions on /queue
|
||||
# in the event that postfix,postdrop id have changed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
# python3 shared with most images
|
||||
RUN apk add --no-cache \
|
||||
|
||||
@@ -72,8 +72,12 @@ mail in following format: ``[HOST]:PORT``.
|
||||
``RELAYUSER`` and ``RELAYPASSWORD`` can be used when authentication is needed.
|
||||
|
||||
By default postfix uses "opportunistic TLS" for outbound mail. This can be changed
|
||||
by setting ``OUTBOUND_TLS_LEVEL`` to ``encrypt`` or ``secure``. This setting is highly recommended
|
||||
if you are using a relayhost that supports TLS.
|
||||
by setting ``OUTBOUND_TLS_LEVEL`` to ``encrypt`` or ``secure``. This setting is
|
||||
highly recommended if you are using a relayhost that supports TLS but discouraged
|
||||
otherwise. ``DEFER_ON_TLS_ERROR`` (default: True) controls whether incomplete
|
||||
policies (DANE without DNSSEC or "testing" MTA-STS policies) will be taken into
|
||||
account and whether emails will be defered if the additional checks enforced by
|
||||
those policies fail.
|
||||
|
||||
Similarily by default nginx uses "opportunistic TLS" for inbound mail. This can be changed
|
||||
by setting ``INBOUND_TLS_ENFORCE`` to ``True``. Please note that this is forbidden for
|
||||
@@ -124,6 +128,13 @@ Both ``SITENAME`` and ``WEBSITE`` are customization options for the panel menu
|
||||
in the admin interface, while ``SITENAME`` is a customization option for
|
||||
every Web interface.
|
||||
|
||||
- ``LOGO_BACKGROUND`` sets a custom background colour for the brand logo in the topleft of the main admin interface.
|
||||
For a list of colour codes refer to this page of `w3schools`_.
|
||||
|
||||
- ``LOGO_URL`` sets a URL for a custom logo. This logo replaces the Mailu logo in the topleft of the main admin interface.
|
||||
|
||||
.. _`w3schools`: https://www.w3schools.com/cssref/css_colors.asp
|
||||
|
||||
.. _admin_account:
|
||||
|
||||
Admin account - automatic creation
|
||||
|
||||
41
docs/faq.rst
@@ -369,6 +369,31 @@ How do I use webdav (radicale)?
|
||||
.. _`575`: https://github.com/Mailu/Mailu/issues/575
|
||||
.. _`1591`: https://github.com/Mailu/Mailu/issues/1591
|
||||
|
||||
How do I setup a MTA-STS policy?
|
||||
````````````````````````````````
|
||||
|
||||
Mailu can serve an `MTA-STS policy`_; To configure it you will need to:
|
||||
|
||||
1. add ``mta-sts.example.com`` to the ``HOSTNAMES`` configuration variable (and ensure that a valid SSL certificate is available for it; this may mean restarting your smtp container)
|
||||
|
||||
2. configure an override with the policy itself; for example, your ``overrides/nginx/mta-sts.conf`` could read:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
location ^~ /.well-known/mta-sts.txt {
|
||||
return 200 "version: STSv1
|
||||
mode: enforce
|
||||
max_age: 1296000
|
||||
mx: mailu.example.com\r\n";
|
||||
}
|
||||
|
||||
3. setup the appropriate DNS/CNAME record (``mta-sts.example.com`` -> ``mailu.example.com``) and DNS/TXT record (``_mta-sts.example.com`` -> ``v=STSv1; id=1``) paying attention to the ``TTL`` as this is used by MTA-STS.
|
||||
|
||||
*issue reference:* `1798`_.
|
||||
|
||||
.. _`1798`: https://github.com/Mailu/Mailu/issues/1798
|
||||
.. _`MTA-STS policy`: https://datatracker.ietf.org/doc/html/rfc8461
|
||||
|
||||
Technical issues
|
||||
----------------
|
||||
|
||||
@@ -397,6 +422,22 @@ Any mail related connection is proxied by nginx. Therefore the SMTP Banner is al
|
||||
|
||||
.. _`1368`: https://github.com/Mailu/Mailu/issues/1368
|
||||
|
||||
My emails are getting defered, what can I do?
|
||||
`````````````````````````````````````````````
|
||||
|
||||
Emails are asynchronous and it's not abnormal for them to be defered sometimes. That being said, Mailu enforces secure connections where possible using DANE and MTA-STS, both of which have the potential to delay indefinitely delivery if something is misconfigured.
|
||||
|
||||
If delivery to a specific domain fails because their DANE records are invalid or their TLS configuration inadequate (expired certificate, ...), you can assist delivery by downgrading the security level for that domain by creating an override at ``overrides/postfix/tls_policy.map`` as follow:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
domain.example.com may
|
||||
domain.example.org encrypt
|
||||
|
||||
The syntax and options are as described in `postfix's documentation`_. Re-creating the smtp container will be required for changes to take effect.
|
||||
|
||||
.. _`postfix's documentation`: http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps
|
||||
|
||||
403 - Access Denied Errors
|
||||
---------------------------
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
# python3 shared with most images
|
||||
RUN apk add --no-cache \
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
###############
|
||||
|
||||
DatabaseDirectory /data
|
||||
LogSyslog yes
|
||||
UpdateLogFile /dev/stdout
|
||||
LogTime yes
|
||||
PidFile /run/clamav/freshclam.pid
|
||||
PidFile /run/freshclam.pid
|
||||
DatabaseOwner root
|
||||
|
||||
###############
|
||||
@@ -15,5 +15,4 @@ DatabaseOwner root
|
||||
DatabaseMirror database.clamav.net
|
||||
ScriptedUpdates yes
|
||||
NotifyClamd /etc/clamav/clamd.conf
|
||||
SafeBrowsing yes
|
||||
Bytecode yes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
|
||||
# python3 shared with most images
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
# python3 shared with most images
|
||||
RUN apk add --no-cache \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
|
||||
# python3 shared with most images
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.12
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
# python3 shared with most images
|
||||
RUN apk add --no-cache \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG DISTRO=alpine:3.14
|
||||
ARG DISTRO=alpine:3.14.2
|
||||
FROM $DISTRO
|
||||
|
||||
RUN mkdir -p /app
|
||||
|
||||
1
towncrier/newsfragments/1798.feature
Normal file
@@ -0,0 +1 @@
|
||||
Implement MTA-STS and DANE validation. Introduce DEFER_ON_TLS_ERROR (default: True) to harden or loosen the policy enforcement.
|
||||
33
towncrier/newsfragments/1966.feature
Normal file
@@ -0,0 +1,33 @@
|
||||
AdminLTE3 design optimizations, asset compression and caching
|
||||
|
||||
- fixed copy of qemu-arm-static for alpine
|
||||
- added 'set -eu' safeguard
|
||||
- silenced npm update notification
|
||||
- added color to webpack call
|
||||
- changed Admin-LTE default blue
|
||||
- AdminLTE 3 style tweaks
|
||||
- localized datatables
|
||||
- moved external javascript code to vendor.js
|
||||
- added mailu logo
|
||||
- moved all inline javascript to app.js
|
||||
- added iframe display of rspamd page
|
||||
- updated language-selector to display full language names and use post
|
||||
- added fieldset to group and en/disable input fields
|
||||
- added clipboard copy buttons
|
||||
- cleaned external javascript imports
|
||||
- pre-split first hostname for further use
|
||||
- cache dns_* properties of domain object (immutable during runtime)
|
||||
- fixed and splitted dns_dkim property of domain object (space missing)
|
||||
- added autoconfig and tlsa properties to domain object
|
||||
- suppressed extra vertical spacing in jinja2 templates
|
||||
- improved accessibility for screen reader
|
||||
- deleted unused/broken /user/forward route
|
||||
- updated gunicorn to 20.1.0 to get rid of buffering error at startup
|
||||
- switched webpack to production mode
|
||||
- added css and javascript minimization
|
||||
- added pre-compression of assets (gzip)
|
||||
- removed obsolete dependencies
|
||||
- switched from node-sass to dart-sass
|
||||
- changed startup cleaning message from error to info
|
||||
- move client config to "my account" section when logged in
|
||||
|
||||
@@ -38,7 +38,8 @@ RUN apt-get update && apt-get install -y \
|
||||
&& sed -i 's,^php_value.*post_max_size,#&,g' .htaccess \
|
||||
&& sed -i 's,^php_value.*upload_max_filesize,#&,g' .htaccess \
|
||||
&& chown -R www-data: logs temp \
|
||||
&& rm -rf /var/lib/apt/lists
|
||||
&& rm -rf /var/lib/apt/lists \
|
||||
&& a2enmod rewrite deflate expires headers
|
||||
|
||||
COPY php.ini /php.ini
|
||||
COPY config.inc.php /var/www/html/config/
|
||||
|
||||