mirror of
https://github.com/Mailu/Mailu.git
synced 2024-12-14 10:53:30 +02:00
AdminLTE3 optimizations & 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 (core/admin/Dockerfile) - AdminLTE 3 style tweaks (core/admin/assets/app.css) (core/admin/mailu/ui/templates/base.html) (core/admin/mailu/ui/templates/sidebar.html) - localized datatables (core/admin/Dockerfile) (core/admin/assets/app.js) (core/admin/package.json) - moved external javascript code to vendor.js (core/admin/assets/app.js) (core/admin/assets/vendor.js) (core/admin/webpack.config.js) - added mailu logo (core/admin/assets/app.js) (core/admin/assets/app.css) (core/admin/assets/mailu.png) - moved all inline javascript to app.js (core/admin/assets/app.js) (core/admin/mailu/ui/templates/domain/create.html) (core/admin/mailu/ui/templates/user/create.html) - added iframe display of rspamd page (core/admin/assets/app.js) (core/admin/mailu/ui/views/base.py) (core/admin/mailu/ui/templates/sidebar.html) (core/admin/mailu/ui/templates/antispam.html) - updated language-selector to display full language names and use post (core/admin/assets/app.js) (core/admin/mailu/__init__.py) (core/admin/mailu/utils.py) (core/admin/mailu/ui/views/languages.py) - added fieldset to group and en/disable input fields (core/admin/assets/app.js) (core/admin/mailu/ui/templates/macros.html) (core/admin/mailu/ui/templates/user/settings.html) (core/admin/mailu/ui/templates/user/reply.html) - added clipboard copy buttons (core/admin/assets/app.js) (core/admin/assets/vendor.js) (core/admin/mailu/ui/templates/macros.html) (core/admin/mailu/ui/templates/domain/details.html) - cleaned external javascript imports (core/admin/assets/vendor.js) - pre-split first hostname for further use (core/admin/mailu/__init__.py) (core/admin/mailu/models.py) (core/admin/mailu/ui/templates/client.html) (core/admin/mailu/ui/templates/domain/signup.html) - cache dns_* properties of domain object (immutable during runtime) (core/admin/mailu/models.py) (core/admin/mailu/ui/templates/domain/details.html) - fixed and splitted dns_dkim property of domain object (space missing) - added autoconfig and tlsa properties to domain object (core/admin/mailu/models.py) - suppressed extra vertical spacing in jinja2 templates - improved accessibility for screen reader (core/admin/mailu/ui/templates/**.html) - deleted unused/broken /user/forward route (core/admin/mailu/ui/templates/user/forward.html) (core/admin/mailu/ui/views/users.py) - updated gunicorn to 20.1.0 to get rid of buffering error at startup (core/admin/requirements-prod.txt) - switched webpack to production mode (core/admin/webpack.config.js) - added css and javascript minimization - added pre-compression of assets (gzip) (core/admin/webpack.config.js) (core/admin/package.json) - removed obsolte dependencies - switched from node-sass to dart-sass (core/admin/package.json) - changed startup cleaning message from error to info (core/admin/mailu/utils.py) - move client config to "my account" section when logged in (core/admin/mailu/ui/templates/sidebar.html)
This commit is contained in:
parent
f4e7ce0990
commit
34df8b3168
@ -3,31 +3,38 @@ ARG DISTRO=alpine:3.14
|
|||||||
ARG ARCH=""
|
ARG ARCH=""
|
||||||
|
|
||||||
FROM ${ARCH}node:16 as assets
|
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 ./
|
COPY package.json ./
|
||||||
RUN npm install
|
RUN set -eu \
|
||||||
|
&& npm config set update-notifier false \
|
||||||
|
&& npm install --no-fund
|
||||||
|
|
||||||
COPY ./webpack.config.js ./
|
COPY webpack.config.js ./
|
||||||
COPY ./assets ./assets
|
COPY assets ./assets
|
||||||
RUN mkdir static \
|
RUN set -eu \
|
||||||
&& ./node_modules/.bin/webpack-cli
|
&& sed -i 's/#007bff/#367fa9/' 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
|
# Actual application
|
||||||
FROM $DISTRO
|
FROM $DISTRO
|
||||||
|
COPY --from=balenalib/rpi-alpine:3.14 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static
|
||||||
|
|
||||||
# python3 shared with most images
|
# python3 shared with most images
|
||||||
RUN apk add --no-cache \
|
RUN set -eu \
|
||||||
python3 py3-pip git bash \
|
&& apk add --no-cache python3 py3-pip git bash \
|
||||||
&& pip3 install --upgrade pip
|
&& pip3 install --upgrade pip
|
||||||
|
|
||||||
RUN mkdir -p /app
|
RUN mkdir -p /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY requirements-prod.txt requirements.txt
|
COPY requirements-prod.txt requirements.txt
|
||||||
RUN apk add --no-cache openssl curl postgresql-libs mariadb-connector-c \
|
RUN set -eu \
|
||||||
&& apk add --no-cache --virtual build-dep \
|
&& apk add --no-cache openssl curl postgresql-libs mariadb-connector-c \
|
||||||
openssl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev cargo \
|
&& 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 \
|
&& pip3 install -r requirements.txt \
|
||||||
&& apk del --no-cache build-dep
|
&& apk del --no-cache build-dep
|
||||||
|
|
||||||
|
@ -1,23 +1,51 @@
|
|||||||
.select2-search--inline .select2-search__field:focus {
|
/* mailu logo */
|
||||||
border: none;
|
.mailu-logo {
|
||||||
|
opacity: .8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar h4 {
|
/* user image */
|
||||||
padding-left: 5px;
|
.div-circle {
|
||||||
padding-right: 5px;
|
position: relative;
|
||||||
overflow: hidden;
|
width: 2.1rem;
|
||||||
text-overflow: ellipsis;
|
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 {
|
/* nice round preformatted configuration display */
|
||||||
display: none !important;
|
.pre-config {
|
||||||
|
padding: 9px;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: anywhere;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo a {
|
/* fieldset */
|
||||||
color: #fff;
|
legend {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
fieldset:disabled :not(legend) label {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
fieldset:disabled .form-control:disabled {
|
||||||
|
color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-toggle {
|
/* fix animation for icons in menu text */
|
||||||
padding: unset !important;
|
.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');
|
require('./app.css');
|
||||||
|
|
||||||
import 'admin-lte/plugins/select2/js/select2.js';
|
import logo from './mailu.png';
|
||||||
import 'admin-lte/plugins/datatables/jquery.dataTables.js';
|
import modules from "./*.json";
|
||||||
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';
|
|
||||||
|
|
||||||
jQuery("document").ready(function() {
|
// TODO: conditionally (or lazy) load select2 and dataTable
|
||||||
jQuery(".mailselect").select2({
|
$('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,
|
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
BIN
core/admin/assets/mailu.png
Normal file
Binary file not shown.
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
|
// 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/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/AdminLTE.js';
|
||||||
import 'admin-lte/build/js/Layout.js';
|
|
||||||
import 'admin-lte/build/js/ControlSidebar.js';
|
// fontawesome plugin
|
||||||
import 'admin-lte/build/js/PushMenu.js';
|
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 = flask.Flask(__name__)
|
||||||
app.cli.add_command(manage.mailu)
|
app.cli.add_command(manage.mailu)
|
||||||
|
|
||||||
# Bootstrap is used for basic JS and CSS loading
|
# Bootstrap is used for error display and flash messages
|
||||||
# TODO: remove this and use statically generated assets instead
|
|
||||||
app.bootstrap = flask_bootstrap.Bootstrap(app)
|
app.bootstrap = flask_bootstrap.Bootstrap(app)
|
||||||
|
|
||||||
# Initialize application extensions
|
# 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()
|
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
|
# Initialize debugging tools
|
||||||
if app.config.get("DEBUG"):
|
if app.config.get("DEBUG"):
|
||||||
debug.toolbar.init_app(app)
|
debug.toolbar.init_app(app)
|
||||||
@ -44,7 +52,7 @@ def create_app_from_config(config):
|
|||||||
signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()
|
signup_domains = models.Domain.query.filter_by(signup_enabled=True).all()
|
||||||
return dict(
|
return dict(
|
||||||
signup_domains= signup_domains,
|
signup_domains= signup_domains,
|
||||||
config=app.config
|
config = app.config,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Import views
|
# Import views
|
||||||
|
@ -143,6 +143,7 @@ class ConfigManager(dict):
|
|||||||
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
||||||
self.config['SESSION_COOKIE_HTTPONLY'] = True
|
self.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||||
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME']))
|
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME']))
|
||||||
|
self.config['HOSTNAME'] = self.config['HOSTNAMES'].split(',', 1)[0].strip()
|
||||||
# update the app config itself
|
# update the app config itself
|
||||||
app.config = self
|
app.config = self
|
||||||
|
|
||||||
|
@ -209,16 +209,16 @@ class Domain(Base):
|
|||||||
os.unlink(file_path)
|
os.unlink(file_path)
|
||||||
self._dkim_key_on_disk = self._dkim_key
|
self._dkim_key_on_disk = self._dkim_key
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def dns_mx(self):
|
def dns_mx(self):
|
||||||
""" return MX record for domain """
|
""" 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}.'
|
return f'{self.name}. 600 IN MX 10 {hostname}.'
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def dns_spf(self):
|
def dns_spf(self):
|
||||||
""" return SPF record for domain """
|
""" 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"'
|
return f'{self.name}. 600 IN TXT "v=spf1 mx a:{hostname} ~all"'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -226,12 +226,11 @@ class Domain(Base):
|
|||||||
""" return DKIM record for domain """
|
""" return DKIM record for domain """
|
||||||
if self.dkim_key:
|
if self.dkim_key:
|
||||||
selector = app.config['DKIM_SELECTOR']
|
selector = app.config['DKIM_SELECTOR']
|
||||||
return (
|
txt = f'v=DKIM1; k=rsa; p={self.dkim_publickey}'
|
||||||
f'{selector}._domainkey.{self.name}. 600 IN TXT'
|
record = ' '.join(f'"{txt[p:p+250]}"' for p in range(0, len(txt), 250))
|
||||||
f'"v=DKIM1; k=rsa; p={self.dkim_publickey}"'
|
return f'{selector}._domainkey.{self.name}. 600 IN TXT {record}'
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def dns_dmarc(self):
|
def dns_dmarc(self):
|
||||||
""" return DMARC record for domain """
|
""" return DMARC record for domain """
|
||||||
if self.dkim_key:
|
if self.dkim_key:
|
||||||
@ -242,6 +241,34 @@ class Domain(Base):
|
|||||||
ruf = f' ruf=mailto:{ruf}@{domain};' if ruf else ''
|
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"'
|
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 True: #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
|
@property
|
||||||
def dkim_key(self):
|
def dkim_key(self):
|
||||||
""" return private DKIM key """
|
""" return private DKIM key """
|
||||||
@ -285,7 +312,7 @@ class Domain(Base):
|
|||||||
def check_mx(self):
|
def check_mx(self):
|
||||||
""" checks if MX record for domain points to mailu host """
|
""" checks if MX record for domain points to mailu host """
|
||||||
try:
|
try:
|
||||||
hostnames = set(app.config['HOSTNAMES'].split(','))
|
hostnames = set(fqdn.strip() for fqdn in app.config['HOSTNAMES'].split(','))
|
||||||
return any(
|
return any(
|
||||||
rset.exchange.to_text().rstrip('.') in hostnames
|
rset.exchange.to_text().rstrip('.') in hostnames
|
||||||
for rset in dns.resolver.query(self.name, 'MX')
|
for rset in dns.resolver.query(self.name, 'MX')
|
||||||
|
@ -88,7 +88,7 @@ class UserForm(flask_wtf.FlaskForm):
|
|||||||
localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
||||||
pw = fields.PasswordField(_('Password'))
|
pw = fields.PasswordField(_('Password'))
|
||||||
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
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_imap = fields.BooleanField(_('Allow IMAP access'), default=True)
|
||||||
enable_pop = fields.BooleanField(_('Allow POP3 access'), default=True)
|
enable_pop = fields.BooleanField(_('Allow POP3 access'), default=True)
|
||||||
displayed_name = fields.StringField(_('Displayed name'))
|
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 %}
|
{% trans %}Add a global administrator{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.admin, class_='mailselect') }}
|
{{ macros.form_field(form.admin, class_='mailselect') }}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Global administrators{% endtrans %}
|
{% trans %}Global administrators{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block main_action %}
|
{%- block main_action %}
|
||||||
<a class="btn btn-primary float-right" href="{{ url_for('.admin_create') }}">
|
<a class="btn btn-primary float-right" href="{{ url_for('.admin_create') }}">
|
||||||
{% trans %}Add administrator{% endtrans %}
|
{% trans %}Add administrator{% endtrans %}
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -19,14 +19,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for admin in admins %}
|
{%- for admin in admins %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.admin_delete', admin=admin.email) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
<a href="{{ url_for('.admin_delete', admin=admin.email) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ admin }}</td>
|
<td>{{ admin }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Create alias{% endtrans %}
|
{% trans %}Create alias{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain }}
|
{{ domain }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.localpart, append='<span class="input-group-text">@'+domain.name+'</span>') }}
|
{{ 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.comment) }}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "alias/create.html" %}
|
{%- extends "alias/create.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Edit alias{% endtrans %}
|
{% trans %}Edit alias{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ alias }}
|
{{ alias }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Alias list{% endtrans %}
|
{% trans %}Alias list{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain.name }}
|
{{ 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>
|
<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 %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for alias in domain.aliases %}
|
{%- for alias in domain.aliases %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.alias_edit', alias=alias.email) }}" title="{% trans %}Edit{% endtrans %}"><i class="fa fa-pencil"></i></a>
|
<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.created_at }}</td>
|
||||||
<td>{{ alias.updated_at or '' }}</td>
|
<td>{{ alias.updated_at or '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "form.html" %}
|
{%- extends "form.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Create alternative domain{% endtrans %}
|
{% trans %}Create alternative domain{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain }}
|
{{ domain }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Alternative domain list{% endtrans %}
|
{% trans %}Alternative domain list{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain.name }}
|
{{ 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>
|
<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 %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for alternative in domain.alternatives %}
|
{%- for alternative in domain.alternatives %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.alternative_delete', alternative=alternative.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
<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 }}</td>
|
||||||
<td>{{ alternative.created_at }}</td>
|
<td>{{ alternative.created_at }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Public announcement{% endtrans %}
|
{% trans %}Public announcement{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.announcement_subject) }}
|
{{ macros.form_field(form.announcement_subject) }}
|
||||||
{{ macros.form_field(form.announcement_body, rows=10) }}
|
{{ macros.form_field(form.announcement_body, rows=10) }}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
15
core/admin/mailu/ui/templates/antispam.html
Normal file
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 "macros.html" as macros %}
|
||||||
{% import "bootstrap/utils.html" as utils %}
|
{%- import "bootstrap/utils.html" as utils %}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="{{ session['language'] }}" data-static="{{ url_for('.static', filename='') }}">
|
||||||
<head>
|
<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='vendor.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('.static', filename='app.css') }}">
|
<link rel="stylesheet" href="{{ url_for('.static', filename='app.css') }}">
|
||||||
<title>Mailu-Admin - {{ config["SITENAME"] }}</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="hold-transition sidebar-mini layout-fixed">
|
<body class="hold-transition sidebar-mini layout-fixed">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
|
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link" data-toggle="dropdown" href="#" aria-expanded="false">{{ session['language'] }}</a>
|
<a class="nav-link" data-toggle="dropdown" href="#" aria-expanded="false">
|
||||||
<div class="dropdown-menu dropdown-menu-right p-0">
|
<i class="fas fa-language text-xl" aria-hidden="true" title="{% trans %}change language{% endtrans %}"></i><span class="sr-only">Language</span>
|
||||||
{% for language in session['available_languages'] %}
|
<span class="badge badge-primary navbar-badge">{{ session['language'] }}</span></a>
|
||||||
<a class="dropdown-item {% if language == session['language'] %}active{% endif %} " href="{{ url_for('.set_language', language=language) }}">{{ language }}</a>
|
<div class="dropdown-menu dropdown-menu-right p-0" id="mailu-languages">
|
||||||
{% endfor %}
|
{%- 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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<aside class="main-sidebar sidebar-dark-primary">
|
<aside class="main-sidebar sidebar-dark-primary nav-compact elevation-4">
|
||||||
<a href="{{ config["WEB_ADMIN"] }}" class="brand-link">
|
<a href="{{ url_for('.domain_list') }}" class="brand-link bg-primary">
|
||||||
|
<img src="{{ 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>
|
<span class="brand-text font-weight-light">{{ config["SITENAME"] }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% block sidebar %}
|
{%- include "sidebar.html" %}
|
||||||
{% include "sidebar.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
</aside>
|
</aside>
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper text-sm">
|
||||||
<section class="content-header">
|
<section class="content-header">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-sm-6">
|
<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>
|
<small>{% block subtitle %}{% endblock %}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
{% block main_action %}
|
{%- block main_action %}{%- endblock %}
|
||||||
{% endblock %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ utils.flashed_messages(container=False) }}
|
{{ utils.flashed_messages(container=False) }}
|
||||||
{% block content %}{% endblock %}
|
{%- block content %}{%- endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer class="main-footer">
|
<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
|
Built with <i class="fa fa-heart text-danger" aria-hidden="true"></i><span class="sr-only">love</span>
|
||||||
<a class="white-text" href="https://adminlte.io/themes/v3/index3.html">AdminLTE</a>
|
using <a href="https://flask.palletsprojects.com/">Flask</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>
|
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>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ url_for('.static', filename='vendor.js') }}"></script>
|
<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 %}
|
{% trans %}Client setup{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
configure your email client
|
{% trans %}configure your email client{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.table(title="Incoming mail", datatable=False) %}
|
{%- call macros.table(title=_("Incoming mail"), datatable=False) %}
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Mail protocol{% endtrans %}</th>
|
<th>{% trans %}Mail protocol{% endtrans %}</th>
|
||||||
@ -23,20 +21,20 @@ configure your email client
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Server name{% endtrans %}</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Username{% endtrans %}</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Password{% endtrans %}</th>
|
<th>{% trans %}Password{% endtrans %}</th>
|
||||||
<td><pre>*******</pre></td>
|
<td><pre class="pre-config border bg-light">*******</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{% call macros.table(title="Outgoing mail", datatable=False) %}
|
{%- call macros.table(title=_("Outgoing mail"), datatable=False) %}
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Mail protocol{% endtrans %}</th>
|
<th>{% trans %}Mail protocol{% endtrans %}</th>
|
||||||
@ -48,16 +46,16 @@ configure your email client
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Server name{% endtrans %}</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Username{% endtrans %}</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Password{% endtrans %}</th>
|
<th>{% trans %}Password{% endtrans %}</th>
|
||||||
<td><pre>*******</pre></td>
|
<td><pre class="pre-config border bg-light">*******</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Confirm action{% endtrans %}
|
{% trans %}Confirm action{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ action }}
|
{{ action }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card(theme="warning") %}
|
{%- call macros.card(theme="warning") %}
|
||||||
<p>{% trans action %}You are about to {{ action }}. Please confirm your action.{% endtrans %}</p>
|
<p>{% trans action %}You are about to {{ action }}. Please confirm your action.{% endtrans %}</p>
|
||||||
{{ macros.form(form) }}
|
{{ macros.form(form) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Docker error{% endtrans %}
|
{% trans %}Docker error{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ action }}
|
{{ action }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
<p>{% trans action %}An error occurred while talking to the Docker server.{% endtrans %}</p>
|
<p>{% trans action %}An error occurred while talking to the Docker server.{% endtrans %}</p>
|
||||||
<pre>{{ error }}</pre>
|
<pre class="pre-config border bg-light">{{ error }}</pre>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}New domain{% endtrans %}
|
{% trans %}New domain{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.name) }}
|
{{ macros.form_field(form.name) }}
|
||||||
{{ macros.form_fields((form.max_users, form.max_aliases)) }}
|
{{ macros.form_fields((form.max_users, form.max_aliases)) }}
|
||||||
{{ macros.form_field(form.max_quota_bytes, step=1000000000, max=50000000000,
|
{{ 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="quota">'+((form.max_quota_bytes.data//1000000000).__str__() if form.max_quota_bytes.data else '∞')+'</span> GiB</span>',
|
prepend='<span class="input-group-text"><span id="max_quota_bytes_value"></span> GB</span>') }}
|
||||||
oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }}
|
|
||||||
{{ macros.form_field(form.signup_enabled) }}
|
{{ macros.form_field(form.signup_enabled) }}
|
||||||
{{ macros.form_field(form.comment) }}
|
{{ macros.form_field(form.comment) }}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,66 +1,69 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Domain details{% endtrans %}
|
{% trans %}Domain details{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain.name }}
|
{{ domain.name }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block main_action %}
|
{%- block main_action %}
|
||||||
{% if current_user.global_admin %}
|
{%- if current_user.global_admin %}
|
||||||
<a class="btn btn-primary float-right" href="{{ url_for(".domain_genkeys", domain_name=domain.name) }}">
|
<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 %}
|
{% trans %}Regenerate keys{% endtrans %}
|
||||||
{% else %}
|
{%- else %}
|
||||||
{% trans %}Generate keys{% endtrans %}
|
{% trans %}Generate keys{% endtrans %}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.table(datatable=False) %}
|
{%- call macros.table(datatable=False) %}
|
||||||
{% set hostname = config["HOSTNAMES"].split(",")[0] %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Domain name{% endtrans %}</th>
|
<th>{% trans %}Domain name{% endtrans %}</th>
|
||||||
<td>{{ domain.name }}</td>
|
<td>{{ domain.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}DNS MX entry{% endtrans %} <i class="fa {{ 'fa-check-circle' if domain.check_mx() else 'fa-exclamation-circle' }}"></i></th>
|
<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><pre>{{ domain.name }}. 600 IN MX 10 {{ hostname }}.</pre></td>
|
<td>{{ macros.clip("dns_mx") }}<pre id="dns_mx" class="pre-config border bg-light">{{ domain.dns_mx }}</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}DNS SPF entries{% endtrans %}</th>
|
<th>{% trans %}DNS SPF entries{% endtrans %}</th>
|
||||||
<td><pre>
|
<td>{{ macros.clip("dns_spf") }}<pre id="dns_spf" class="pre-config border bg-light">{{ domain.dns_spf }}</pre>
|
||||||
{{ domain.name }}. 600 IN TXT "v=spf1 mx a:{{ hostname }} -all"</pre></td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if domain.dkim_publickey %}
|
{%- if domain.dkim_publickey %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}DKIM public key{% endtrans %}</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}DNS DKIM entry{% endtrans %}</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}DNS DMARC entry{% endtrans %}</th>
|
<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>
|
</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>
|
<tr>
|
||||||
<th>{% trans %}DNS client auto-configuration (RFC6186) entries{% endtrans %}</th>
|
<th>{% trans %}DNS client auto-configuration (RFC6186) entries{% endtrans %}</th>
|
||||||
<td>
|
<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>
|
{{ macros.clip("dns_autoconfig") }}<pre id="dns_autoconfig" class="pre-config border bg-light">
|
||||||
<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>
|
{%- for line in domain.dns_autoconfig %}
|
||||||
<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>
|
{{ line }}
|
||||||
{% if config["TLS_FLAVOR"] != "notls" %}
|
{%- endfor -%}
|
||||||
<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></td>
|
||||||
<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>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "domain/create.html" %}
|
{%- extends "domain/create.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Edit domain{% endtrans %}
|
{% trans %}Edit domain{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain }}
|
{{ domain }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Domain list{% endtrans %}
|
{% trans %}Domain list{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block main_action %}
|
{%- block main_action %}
|
||||||
{% if current_user.global_admin %}
|
{%- if current_user.global_admin %}
|
||||||
<a class="btn btn-primary float-right" href="{{ url_for('.domain_create') }}">{% trans %}New domain{% endtrans %}</a>
|
<a class="btn btn-primary float-right" href="{{ url_for('.domain_create') }}">{% trans %}New domain{% endtrans %}</a>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -25,22 +25,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for domain in current_user.get_managed_domains() %}
|
{%- for domain in current_user.get_managed_domains() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.domain_details', domain_name=domain.name) }}" title="{% trans %}Details{% endtrans %}"><i class="fa fa-list"></i></a>
|
<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_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>
|
<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>
|
||||||
<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('.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('.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>
|
<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>
|
<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>
|
||||||
<td>{{ domain.name }}</td>
|
<td>{{ domain.name }}</td>
|
||||||
<td>{{ domain.users | count }} / {{ '∞' if domain.max_users == -1 else domain.max_users }}</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.created_at }}</td>
|
||||||
<td>{{ domain.updated_at or '' }}</td>
|
<td>{{ domain.updated_at or '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Register a domain{% endtrans %}
|
{% trans %}Register a domain{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
|
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ 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
|
<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 %}
|
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>
|
||||||
<p>
|
<p>
|
||||||
{% trans %}If you do not know how to setup an <code>MX</code> record for your DNS zone,
|
{% 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
|
couple minutes after the <code>MX</code> is set so the local server cache
|
||||||
expires.{% endtrans %}
|
expires.{% endtrans %}
|
||||||
</p>
|
</p>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
{% if form.localpart %}
|
{%- if form.localpart %}
|
||||||
{{ macros.form_fields((form.localpart, form.name), append='<span class="input-group-text">@</span>') }}
|
{{ macros.form_fields((form.localpart, form.name), append='<span class="input-group-text">@</span>') }}
|
||||||
{{ macros.form_fields((form.pw, form.pw2)) }}
|
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||||
{% else %}
|
{%- else %}
|
||||||
{{ macros.form_field(form.name) }}
|
{{ macros.form_field(form.name) }}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{{ macros.form_field(form.captcha) }}
|
{{ macros.form_field(form.captcha) }}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Add a fetched account{% endtrans %}
|
{% trans %}Add a fetched account{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ user }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{% call macros.card(title="Remote server") %}
|
{%- call macros.card(title="Remote server") %}
|
||||||
{{ macros.form_field(form.protocol) }}
|
{{ macros.form_field(form.protocol) }}
|
||||||
{{ macros.form_fields((form.host, form.port)) }}
|
{{ macros.form_fields((form.host, form.port)) }}
|
||||||
{{ macros.form_field(form.tls) }}
|
{{ 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.username) }}
|
||||||
{{ macros.form_field(form.password) }}
|
{{ macros.form_field(form.password) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{% call macros.card(title="Settings") %}
|
{%- call macros.card(title="Settings") %}
|
||||||
{{ macros.form_field(form.keep) }}
|
{{ macros.form_field(form.keep) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</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 %}
|
{% trans %}Update a fetched account{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ user }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Fetched accounts{% endtrans %}
|
{% trans %}Fetched accounts{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ 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>
|
<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 %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for fetch in user.fetches %}
|
{%- for fetch in user.fetches %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.fetch_edit', fetch_id=fetch.id) }}" title="{% trans %}Edit{% endtrans %}"><i class="fa fa-pencil"></i></a>
|
<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.created_at }}</td>
|
||||||
<td>{{ fetch.updated_at or '' }}</td>
|
<td>{{ fetch.updated_at or '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
{{ macros.form(form) }}
|
{{ macros.form(form) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "form.html" %}
|
{%- extends "form.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Sign in{% endtrans %}
|
{% trans %}Sign in{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{% trans %}to access the administration tools{% endtrans %}
|
{% trans %}to access the administration tools{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,104 +1,127 @@
|
|||||||
{% macro form_errors(form) %}
|
{%- macro form_errors(form) %}
|
||||||
{% if form.errors %}
|
{%- if form.errors %}
|
||||||
{% for fieldname, errors in form.errors.items() %}
|
{%- for fieldname, errors in form.errors.items() %}
|
||||||
{% if bootstrap_is_hidden_field(form[fieldname]) %}
|
{%- if bootstrap_is_hidden_field(form[fieldname]) %}
|
||||||
{% for error in errors %}
|
{%- for error in errors %}
|
||||||
<p class="error">{{error}}</p>
|
<p class="error">{{error}}</p>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro form_field_errors(field) %}
|
{%- macro form_field_errors(field) %}
|
||||||
{% if field.errors %}
|
{%- if field.errors %}
|
||||||
{% for error in field.errors %}
|
{%- for error in field.errors %}
|
||||||
<p class="help-block inline">{{ error }}</p>
|
<p class="help-block inline">{{ error }}</p>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro form_fields(fields, prepend='', append='', label=True) %}
|
{%- macro form_fields(fields, prepend='', append='', label=True) %}
|
||||||
{% set width = (12 / fields|length)|int %}
|
{%- set width = (12 / fields|length)|int %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="row">
|
<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 '' }}">
|
<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) }}
|
{{ form_individual_field(field, prepend=prepend, append=append, label=label, **kwargs) }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro form_individual_field(field, prepend='', append='', label=True, class_="") %}
|
{%- macro form_individual_field(field, prepend='', append='', label=True, class_="") %}
|
||||||
{% if field.type == "BooleanField" %}
|
{%- if field.type == "BooleanField" %}
|
||||||
{{ field(**kwargs) }}<span> </span>
|
{{ field(**kwargs) }}<span> </span>{{ field.label if label else '' }}
|
||||||
{{ field.label if label else '' }}
|
{%- else %}
|
||||||
{% else %}
|
|
||||||
{{ field.label if label else '' }}{{ form_field_errors(field) }}
|
{{ field.label if label else '' }}{{ form_field_errors(field) }}
|
||||||
{% if prepend %}<div class="input-group-prepend">{% endif %}
|
{%- if prepend %}<div class="input-group-prepend">{%- elif append %}<div class="input-group-append">{%- endif %}
|
||||||
{% if append %}<div class="input-group-append">{% endif %}
|
{{ prepend|safe }}{{ field(class_=("form-control " + class_) if class_ else "form-control", **kwargs) }}{{ append|safe }}
|
||||||
{{ prepend|safe }}{{ field(class_="form-control " + class_, **kwargs) }}{{ append|safe }}
|
{%- if prepend or append %}</div>{%- endif %}
|
||||||
{% if prepend or append %}</div>{% endif %}
|
{%- endif %}
|
||||||
{% endif %}
|
{%- endmacro %}
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro form_field(field) %}
|
{%- macro form_field(field) %}
|
||||||
{% if field.type == 'SubmitField' %}
|
{%- if field.type == 'SubmitField' %}
|
||||||
{{ form_fields((field,), label=False, class="btn btn-default", **kwargs) }}
|
{{- form_fields((field,), label=False, class="btn btn-default", **kwargs) }}
|
||||||
{% else %}
|
{%- else %}
|
||||||
{{ form_fields((field,), **kwargs) }}
|
{{- form_fields((field,), **kwargs) }}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro form(form) %}
|
{%- macro form(form) %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{% for field in form %}
|
{%- for field in form %}
|
||||||
{% if bootstrap_is_hidden_field(field) %}
|
{%- if bootstrap_is_hidden_field(field) %}
|
||||||
{{ field() }}
|
{{ field() }}
|
||||||
{% else %}
|
{%- else %}
|
||||||
{{ form_field(field) }}
|
{{ form_field(field) }}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</form>
|
</form>
|
||||||
{% endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro card(title=None, theme="primary", header=True) %}
|
{%- macro card(title=None, theme="primary", header=True) %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="card card-outline card-{{ theme }}">
|
<div class="card card-outline card-{{ theme }}">
|
||||||
{% if header %}
|
{%- if header %}
|
||||||
<div class="card-header border-0">
|
<div class="card-header border-0">
|
||||||
{% if title %}
|
{%- if title %}
|
||||||
<h3 class="card-title">{{ title }}</h3>
|
<h3 class="card-title">{{ title }}</h3>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{{ caller() }}
|
{{- caller() }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="card card-outline card-{{ theme }}">
|
<div class="card card-outline card-{{ theme }}">
|
||||||
|
{%- if title %}
|
||||||
<div class="card-header border-0">
|
<div class="card-header border-0">
|
||||||
{% if title %}
|
|
||||||
<h3 class="card-title">{{ title }}</h3>
|
<h3 class="card-title">{{ title }}</h3>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
{%- endif %}
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-bordered{% if datatable %} dataTable{% endif %}">
|
<table class="table table-bordered{% if datatable %} dataTable{% endif %}">
|
||||||
{{ caller() }}
|
{{- caller() }}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% trans %}Add a manager{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain }}
|
{{ domain }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.manager, class_='mailselect') }}
|
{{ macros.form_field(form.manager, class_='mailselect') }}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Manager list{% endtrans %}
|
{% trans %}Manager list{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain.name }}
|
{{ 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>
|
<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 %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -21,14 +21,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for manager in domain.managers %}
|
{%- for manager in domain.managers %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<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>
|
||||||
<td>{{ manager }}</td>
|
<td>{{ manager }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% extends "form.html" %}
|
{%- extends "form.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}New relay domain{% endtrans %}
|
{% 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 %}
|
{% trans %}Edit relayd domain{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ relay }}
|
{{ relay }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Relayed domain list{% endtrans %}
|
{% trans %}Relayed domain list{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block main_action %}
|
{%- block main_action %}
|
||||||
{% if current_user.global_admin %}
|
{%- if current_user.global_admin %}
|
||||||
<a class="btn btn-primary float-right" href="{{ url_for('.relay_create') }}">{% trans %}New relayed domain{% endtrans %}</a>
|
<a class="btn btn-primary float-right" href="{{ url_for('.relay_create') }}">{% trans %}New relayed domain{% endtrans %}</a>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for relay in relays %}
|
{%- for relay in relays %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.relay_edit', relay_name=relay.name) }}" title="{% trans %}Edit{% endtrans %}"><i class="fa fa-pencil"></i></a>
|
<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.created_at }}</td>
|
||||||
<td>{{ relay.updated_at or '' }}</td>
|
<td>{{ relay.updated_at or '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,144 +1,156 @@
|
|||||||
<div class="sidebar">
|
<div class="sidebar text-sm">
|
||||||
{% if current_user.is_authenticated %}
|
{%- if current_user.is_authenticated %}
|
||||||
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
|
<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">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<nav class="mt-2">
|
<nav class="mt-2">
|
||||||
<ul class="nav nav-pills nav-sidebar flex-column" role="menu">
|
<ul class="nav nav-pills nav-sidebar flex-column" role="menu">
|
||||||
{% if current_user.is_authenticated %}
|
{%- if current_user.is_authenticated %}
|
||||||
<li class="nav-header">{% trans %}My account{% endtrans %}</li>
|
<li class="nav-header text-uppercase text-primary" role="none">{% trans %}My account{% endtrans %}</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.user_settings') }}" class="nav-link">
|
<a href="{{ url_for('.user_settings') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fa fa-wrench"></i>
|
<i class="nav-icon fa fa-wrench"></i>
|
||||||
<p class="text">{% trans %}Settings{% endtrans %}</p>
|
<p>{% trans %}Settings{% endtrans %}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.user_password') }}" class="nav-link">
|
<a href="{{ url_for('.user_password') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fa fa-lock"></i>
|
<i class="nav-icon fa fa-lock"></i>
|
||||||
<p class="text">{% trans %}Update password{% endtrans %}</p>
|
<p>{% trans %}Update password{% endtrans %}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.user_reply') }}" class="nav-link">
|
<a href="{{ url_for('.user_reply') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fa fa-plane"></i>
|
<i class="nav-icon fa fa-plane"></i>
|
||||||
<p class="text">{% trans %}Auto-reply{% endtrans %}</p>
|
<p>{% trans %}Auto-reply{% endtrans %}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.fetch_list') }}" class="nav-link">
|
<a href="{{ url_for('.fetch_list') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fas fa-download"></i>
|
<i class="nav-icon fas fa-download"></i>
|
||||||
<p class="text">{% trans %}Fetched accounts{% endtrans %}</p>
|
<p>{% trans %}Fetched accounts{% endtrans %}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.token_list') }}" class="nav-link">
|
<a href="{{ url_for('.token_list') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fas fa-ticket-alt"></i>
|
<i class="nav-icon fas fa-ticket-alt"></i>
|
||||||
<p class="text">{% trans %}Authentication tokens{% endtrans %}</p>
|
<p>{% trans %}Authentication tokens{% endtrans %}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{%- if current_user.is_authenticated %}
|
||||||
{% if current_user.manager_of or current_user.global_admin %}
|
<li class="nav-item" role="none">
|
||||||
<li class="nav-header">{% trans %}Administration{% endtrans %}</li>
|
<a href="{{ url_for('.client') }}" class="nav-link" role="menuitem">
|
||||||
{% 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">
|
|
||||||
<i class="nav-icon fa fa-laptop"></i>
|
<i class="nav-icon fa fa-laptop"></i>
|
||||||
<p class="text">{% trans %}Client setup{% endtrans %}</p>
|
<p>{% trans %}Client setup{% endtrans %}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
{%- endif %}
|
||||||
<a href="{{ config["WEBSITE"] }}" target="_blank" class="nav-link">
|
|
||||||
|
{%- 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>
|
<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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="https://mailu.io" target="_blank" class="nav-link">
|
<a href="https://mailu.io" target="_blank" class="nav-link" role="menuitem" rel="noreferrer">
|
||||||
<i class="nav-icon fa fa-life-ring"></i>
|
<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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if config['DOMAIN_REGISTRATION'] %}
|
{%- if config['DOMAIN_REGISTRATION'] %}
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.domain_signup') }}" class="nav-link">
|
<a href="{{ url_for('.domain_signup') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fa fa-plus-square"></i>
|
<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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% if current_user.is_authenticated %}
|
{%- if current_user.is_authenticated %}
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.logout') }}" class="nav-link">
|
<a href="{{ url_for('.logout') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fas fa-sign-out-alt"></i>
|
<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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{%- else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.login') }}" class="nav-link">
|
<a href="{{ url_for('.login') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fas fa-sign-in-alt"></i>
|
<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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if signup_domains %}
|
{%- if signup_domains %}
|
||||||
<li class="nav-item">
|
<li class="nav-item" role="none">
|
||||||
<a href="{{ url_for('.user_signup') }}" class="nav-link">
|
<a href="{{ url_for('.user_signup') }}" class="nav-link" role="menuitem">
|
||||||
<i class="nav-icon fa fa-user-plus"></i>
|
<i class="nav-icon fa fa-user-plus"></i>
|
||||||
<p class="text">{% trans %}Sign up{% endtrans %}</p>
|
<p>{% trans %}Sign up{% endtrans %}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "form.html" %}
|
{%- extends "form.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Create an authentication token{% endtrans %}
|
{% trans %}Create an authentication token{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ user }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Authentication tokens{% endtrans %}
|
{% trans %}Authentication tokens{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ 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>
|
<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 %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for token in user.tokens %}
|
{%- for token in user.tokens %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.token_delete', token_id=token.id) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
<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.ip or "any" }}</td>
|
||||||
<td>{{ token.created_at }}</td>
|
<td>{{ token.created_at }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,33 +1,32 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}New user{% endtrans %}
|
{% trans %}New user{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain.name }}
|
{{ domain.name }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ 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_field(form.localpart, append='<span class="input-group-text">@'+domain.name+'</span>') }}
|
||||||
{{ macros.form_fields((form.pw, form.pw2)) }}
|
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||||
{{ macros.form_field(form.displayed_name) }}
|
{{ macros.form_field(form.displayed_name) }}
|
||||||
{{ macros.form_field(form.comment) }}
|
{{ macros.form_field(form.comment) }}
|
||||||
{{ macros.form_field(form.enabled) }}
|
{{ macros.form_field(form.enabled) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{% call macros.card(_("Features and quotas"), theme="success") %}
|
{%- 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),
|
{{ 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">'+((form.quota_bytes.data//1000000000).__str__() if form.quota_bytes.data else '∞')+'</span> GiB</span>',
|
prepend='<span class="input-group-text"><span id="quota_bytes_value"></span> GB</span>') }}
|
||||||
oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }}
|
|
||||||
{{ macros.form_field(form.enable_imap) }}
|
{{ macros.form_field(form.enable_imap) }}
|
||||||
{{ macros.form_field(form.enable_pop) }}
|
{{ macros.form_field(form.enable_pop) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "user/create.html" %}
|
{%- extends "user/create.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Edit user{% endtrans %}
|
{% trans %}Edit user{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ 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 %}
|
{% trans %}User list{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain.name }}
|
{{ 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>
|
<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 %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in domain.users %}
|
{%- for user in domain.users %}
|
||||||
<tr{% if not user.enabled %} class="warning"{% endif %}>
|
<tr{% if not user.enabled %} class="warning"{% endif %}>
|
||||||
<td>
|
<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_edit', user_email=user.email) }}" title="{% trans %}Edit{% endtrans %}"><i class="fas fa-pencil-alt"></i></a>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<td>{{ user.created_at }}</td>
|
<td>{{ user.created_at }}</td>
|
||||||
<td>{{ user.updated_at or '' }}</td>
|
<td>{{ user.updated_at or '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% extends "form.html" %}
|
{%- extends "form.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Password update{% endtrans %}
|
{% trans %}Password update{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ user }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,30 +1,23 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Automatic reply{% endtrans %}
|
{% trans %}Automatic reply{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ user }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.card() %}
|
{%- call macros.card() %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.reply_enabled,
|
{%- call macros.fieldset(
|
||||||
onchange="if(this.checked){$('#reply_subject,#reply_body,#reply_enddate,#reply_startdate').removeAttr('readonly')}
|
field=form.reply_enabled,
|
||||||
else{$('#reply_subject,#reply_body,#reply_enddate,#reply_startdate').attr('readonly', '')}") }}
|
enabled=user.reply_enabled,
|
||||||
{{ macros.form_field(form.reply_subject,
|
fields=[form.reply_subject, form.reply_body, form.reply_enddate, form.reply_startdate]) %}
|
||||||
**{("rw" if user.reply_enabled else "readonly"): ""}) }}
|
{%- endcall %}
|
||||||
{{ 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"): ""}) }}
|
|
||||||
|
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,38 +1,36 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}User settings{% endtrans %}
|
{% trans %}User settings{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ user }}
|
{{ user }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
{% call macros.card(title=_("Displayed name")) %}
|
{%- call macros.card(title=_("Displayed name")) %}
|
||||||
{{ macros.form_field(form.displayed_name) }}
|
{{ macros.form_field(form.displayed_name) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{% call macros.card(title=_("Antispam")) %}
|
{%- call macros.card(title=_("Antispam")) %}
|
||||||
{{ macros.form_field(form.spam_enabled) }}
|
{%- call macros.fieldset(field=form.spam_enabled, enabled=user.spam_enabled) %}
|
||||||
{{ macros.form_field(form.spam_threshold, step=1, max=100,
|
{{ 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>',
|
prepend='<span class="input-group-text"><span id="spam_threshold_value"></span> / 100</span>') }}
|
||||||
oninput='$("#threshold").text(this.value);') }}
|
{%- endcall %}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
|
|
||||||
{% call macros.card(title=_("Auto-forward")) %}
|
{%- call macros.card(title=_("Auto-forward")) %}
|
||||||
{{ macros.form_field(form.forward_enabled,
|
{%- call macros.fieldset(
|
||||||
onchange="if(this.checked){$('#forward_destination,#forward_keep').removeAttr('disabled')}
|
field=form.forward_enabled,
|
||||||
else{$('#forward_destination,#forward_keep').attr('disabled', '')}") }}
|
enabled=user.forward_enabled,
|
||||||
{{ macros.form_field(form.forward_keep,
|
fields=[form.forward_keep, form.forward_destination]) %}
|
||||||
**{("enabled" if user.forward_enabled else "disabled"): ""}) }}
|
{%- endcall %}
|
||||||
{{ macros.form_field(form.forward_destination,
|
{%- endcall %}
|
||||||
**{("enabled" if user.forward_enabled else "disabled"): ""}) }}
|
|
||||||
{% endcall %}
|
|
||||||
|
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Sign up{% endtrans %}
|
{% trans %}Sign up{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{{ domain }}
|
{{ domain }}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
<form class="form" method="post" role="form">
|
<form class="form" method="post" role="form">
|
||||||
{{ form.hidden_tag() }}
|
{{ 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_field(form.localpart, append='<span class="input-group-text">@'+domain.name+'</span>') }}
|
||||||
{{ macros.form_fields((form.pw, form.pw2)) }}
|
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||||
{% if form.captcha %}
|
{%- if form.captcha %}
|
||||||
{{ macros.form_field(form.captcha) }}
|
{{ macros.form_field(form.captcha) }}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
{% extends "base.html" %}
|
{%- extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{%- block title %}
|
||||||
{% trans %}Sign up{% endtrans %}
|
{% trans %}Sign up{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{%- block subtitle %}
|
||||||
{% trans %}pick a domain for the new account{% endtrans %}
|
{% trans %}pick a domain for the new account{% endtrans %}
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{%- block content %}
|
||||||
{% call macros.table() %}
|
{%- call macros.table() %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Domain{% endtrans %}</th>
|
<th>{% trans %}Domain{% endtrans %}</th>
|
||||||
<th>{% trans %}Available slots{% endtrans %}</th>
|
<th>{% trans %}Available slots{% endtrans %}</th>
|
||||||
<th>{% trans %}Quota{% endtrans %}</th>
|
<th>{% trans %}Quota{% endtrans %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for domain_name, domain in available_domains.items() %}
|
{%- for domain_name, domain in available_domains.items() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url_for('.user_signup', domain_name=domain_name) }}">{{ domain_name }}</a></td>
|
<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>{{ '∞' if domain.max_users == -1 else domain.max_users - (domain.users | count)}}</td>
|
||||||
<td>{{ domain.max_quota_bytes or config['DEFAULT_QUOTA'] | filesizeformat }}</td>
|
<td>{{ domain.max_quota_bytes or config['DEFAULT_QUOTA'] | filesizeformat }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
{% endcall %}
|
{%- endcall %}
|
||||||
{% endblock %}
|
{%- 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>
|
<div class="alert alert-warning" role="alert">{% trans %}We are still working on this feature!{% endtrans %}</div>
|
||||||
{% endblock %}
|
{%- endblock %}
|
||||||
|
@ -57,3 +57,8 @@ def webmail():
|
|||||||
@ui.route('/client', methods=['GET'])
|
@ui.route('/client', methods=['GET'])
|
||||||
def client():
|
def client():
|
||||||
return flask.render_template('client.html')
|
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
|
import flask
|
||||||
|
|
||||||
|
@ui.route('/language/<language>', methods=['POST'])
|
||||||
@ui.route('/language/<language>', methods=['GET'])
|
|
||||||
def set_language(language=None):
|
def set_language(language=None):
|
||||||
flask.session['language'] = language
|
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)
|
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', methods=['GET', 'POST'], defaults={'user_email': None})
|
||||||
@ui.route('/user/reply/<path:user_email>', methods=['GET', 'POST'])
|
@ui.route('/user/reply/<path:user_email>', methods=['GET', 'POST'])
|
||||||
@access.owner(models.User, 'user_email')
|
@access.owner(models.User, 'user_email')
|
||||||
|
@ -46,15 +46,10 @@ babel = flask_babel.Babel()
|
|||||||
@babel.localeselector
|
@babel.localeselector
|
||||||
def get_locale():
|
def get_locale():
|
||||||
""" selects locale for translation """
|
""" selects locale for translation """
|
||||||
translations = list(map(str, babel.list_translations()))
|
language = flask.session.get('language')
|
||||||
flask.session['available_languages'] = translations
|
if not language in flask.current_app.config.translations:
|
||||||
|
language = flask.request.accept_languages.best_match(flask.current_app.config.translations.keys())
|
||||||
try:
|
|
||||||
language = flask.session['language']
|
|
||||||
except KeyError:
|
|
||||||
language = flask.request.accept_languages.best_match(translations)
|
|
||||||
flask.session['language'] = language
|
flask.session['language'] = language
|
||||||
|
|
||||||
return language
|
return language
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +445,7 @@ class MailuSessionExtension:
|
|||||||
with cleaned.get_lock():
|
with cleaned.get_lock():
|
||||||
if not cleaned.value:
|
if not cleaned.value:
|
||||||
cleaned.value = True
|
cleaned.value = True
|
||||||
flask.current_app.logger.error('cleaning')
|
flask.current_app.logger.info('cleaning')
|
||||||
MailuSessionExtension.cleanup_sessions(app)
|
MailuSessionExtension.cleanup_sessions(app)
|
||||||
|
|
||||||
app.before_first_request(cleaner)
|
app.before_first_request(cleaner)
|
||||||
|
@ -12,20 +12,19 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.15.0",
|
|
||||||
"@babel/preset-env": "^7.15.0",
|
|
||||||
"admin-lte": "^3.1.0",
|
"admin-lte": "^3.1.0",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
|
"clipboard": "^2.0.8",
|
||||||
|
"compression-webpack-plugin": "^8.0.1",
|
||||||
"css-loader": "^6.2.0",
|
"css-loader": "^6.2.0",
|
||||||
"expose-loader": "^3.0.0",
|
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||||
"jquery": "^3.6.0",
|
"datatables.net-plugins": "^1.10.24",
|
||||||
"less": "^4.1.1",
|
"import-glob": "^1.5.0",
|
||||||
"less-loader": "^10.0.1",
|
"less-loader": "^10.0.1",
|
||||||
"mini-css-extract-plugin": "^2.2.0",
|
"mini-css-extract-plugin": "^2.2.0",
|
||||||
"node-sass": "^6.0.1",
|
"sass": "<1.33.0",
|
||||||
"sass-loader": "^12.1.0",
|
"sass-loader": "^12.1.0",
|
||||||
"select2": "^4.0.13",
|
"terser-webpack-plugin": "^5.2.0",
|
||||||
"webpack": "^5.50.0",
|
"webpack-cli": "^4.8.0"
|
||||||
"webpack-cli": "^4.7.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ Flask-Migrate==2.4.0
|
|||||||
Flask-Script==2.0.6
|
Flask-Script==2.0.6
|
||||||
Flask-SQLAlchemy==2.4.0
|
Flask-SQLAlchemy==2.4.0
|
||||||
Flask-WTF==0.14.2
|
Flask-WTF==0.14.2
|
||||||
gunicorn==19.9.0
|
gunicorn==20.1.0
|
||||||
idna==2.8
|
idna==2.8
|
||||||
infinity==1.4
|
infinity==1.4
|
||||||
intervals==0.8.1
|
intervals==0.8.1
|
||||||
|
@ -1,65 +1,76 @@
|
|||||||
var path = require("path");
|
const path = require('path');
|
||||||
var webpack = require("webpack");
|
const webpack = require('webpack');
|
||||||
var css = require("mini-css-extract-plugin");
|
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 = {
|
module.exports = {
|
||||||
mode: "development",
|
mode: 'production',
|
||||||
entry: {
|
entry: {
|
||||||
app: "./assets/app.js",
|
app: {
|
||||||
vendor: "./assets/vendor.js"
|
import: './assets/app.js',
|
||||||
|
dependOn: 'vendor',
|
||||||
|
},
|
||||||
|
vendor: './assets/vendor.js',
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, "static/"),
|
path: path.resolve(__dirname, 'static/'),
|
||||||
filename: "[name].js"
|
filename: '[name].js',
|
||||||
|
assetModuleFilename: '[name][ext]',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
use: ['babel-loader']
|
use: ['babel-loader', 'import-glob'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.s?css$/i,
|
||||||
use: [css.loader, 'css-loader', 'sass-loader']
|
use: [css.loader, 'css-loader', 'sass-loader'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.less$/,
|
test: /\.less$/i,
|
||||||
use: [css.loader, 'css-loader', 'less-loader']
|
use: [css.loader, 'css-loader', 'less-loader'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.(json|png|svg|jpg|jpeg|gif)$/i,
|
||||||
use: [css.loader, 'css-loader']
|
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: [
|
plugins: [
|
||||||
new css({
|
new css({
|
||||||
filename: "[name].css",
|
filename: '[name].css',
|
||||||
chunkFilename: "[id].css"
|
chunkFilename: '[id].css',
|
||||||
}),
|
}),
|
||||||
new webpack.ProvidePlugin({
|
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 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user