You've already forked Mailu
mirror of
https://github.com/Mailu/Mailu.git
synced 2025-07-07 00:56:11 +02:00
Display the user quota in the admin interface
This commit is contained in:
@ -11,6 +11,7 @@ import os
|
|||||||
import docker
|
import docker
|
||||||
import socket
|
import socket
|
||||||
import uuid
|
import uuid
|
||||||
|
import redis
|
||||||
|
|
||||||
from werkzeug.contrib import fixers
|
from werkzeug.contrib import fixers
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ default_config = {
|
|||||||
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
||||||
'BOOTSTRAP_SERVE_LOCAL': True,
|
'BOOTSTRAP_SERVE_LOCAL': True,
|
||||||
'RATELIMIT_STORAGE_URL': 'redis://redis/2',
|
'RATELIMIT_STORAGE_URL': 'redis://redis/2',
|
||||||
|
'QUOTA_STORAGE_URL': 'redis://redis/1',
|
||||||
'DEBUG': False,
|
'DEBUG': False,
|
||||||
'DOMAIN_REGISTRATION': False,
|
'DOMAIN_REGISTRATION': False,
|
||||||
# Statistics management
|
# Statistics management
|
||||||
@ -87,6 +89,9 @@ manager.add_command('db', flask_migrate.MigrateCommand)
|
|||||||
babel = flask_babel.Babel(app)
|
babel = flask_babel.Babel(app)
|
||||||
translations = list(map(str, babel.list_translations()))
|
translations = list(map(str, babel.list_translations()))
|
||||||
|
|
||||||
|
# Quota manager
|
||||||
|
quota = redis.Redis.from_url(app.config.get("QUOTA_STORAGE_URL"))
|
||||||
|
|
||||||
@babel.localeselector
|
@babel.localeselector
|
||||||
def get_locale():
|
def get_locale():
|
||||||
return flask.request.accept_languages.best_match(translations)
|
return flask.request.accept_languages.best_match(translations)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from mailu import app, db, dkim, login_manager
|
from mailu import app, db, dkim, login_manager, quota
|
||||||
|
|
||||||
from sqlalchemy.ext import declarative
|
from sqlalchemy.ext import declarative
|
||||||
from passlib import context, hash
|
from passlib import context, hash
|
||||||
@ -20,9 +20,8 @@ class IdnaDomain(db.TypeDecorator):
|
|||||||
|
|
||||||
impl = db.String(80)
|
impl = db.String(80)
|
||||||
|
|
||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
def process_bind_param(self, value, dialect):
|
||||||
return idna.encode(value)
|
return idna.encode(value).decode("ascii")
|
||||||
|
|
||||||
def process_result_value(self, value, dialect):
|
def process_result_value(self, value, dialect):
|
||||||
return idna.decode(value)
|
return idna.decode(value)
|
||||||
@ -34,31 +33,19 @@ class IdnaEmail(db.TypeDecorator):
|
|||||||
|
|
||||||
impl = db.String(255, collation="NOCASE")
|
impl = db.String(255, collation="NOCASE")
|
||||||
|
|
||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
def process_bind_param(self, value, dialect):
|
||||||
localpart, domain_name = value.split('@')
|
localpart, domain_name = value.split('@')
|
||||||
|
return "{0}@{1}".format(
|
||||||
email = "{0}@{1}".format(
|
|
||||||
localpart,
|
localpart,
|
||||||
idna.encode(domain_name).decode('ascii'),
|
idna.encode(domain_name).decode('ascii'),
|
||||||
)
|
)
|
||||||
return email
|
|
||||||
|
|
||||||
def process_result_value(self, value, dialect):
|
def process_result_value(self, value, dialect):
|
||||||
localpart, domain_name = value.split('@')
|
localpart, domain_name = value.split('@')
|
||||||
|
return "{0}@{1}".format(
|
||||||
email = "{0}@{1}".format(
|
|
||||||
localpart,
|
localpart,
|
||||||
idna.decode(domain_name),
|
idna.decode(domain_name),
|
||||||
)
|
)
|
||||||
return email
|
|
||||||
|
|
||||||
|
|
||||||
# Many-to-many association table for domain managers
|
|
||||||
managers = db.Table('manager',
|
|
||||||
db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')),
|
|
||||||
db.Column('user_email', IdnaEmail, db.ForeignKey('user.email'))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CommaSeparatedList(db.TypeDecorator):
|
class CommaSeparatedList(db.TypeDecorator):
|
||||||
@ -67,7 +54,6 @@ class CommaSeparatedList(db.TypeDecorator):
|
|||||||
|
|
||||||
impl = db.String
|
impl = db.String
|
||||||
|
|
||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
def process_bind_param(self, value, dialect):
|
||||||
if type(value) is not list:
|
if type(value) is not list:
|
||||||
raise TypeError("Shoud be a list")
|
raise TypeError("Shoud be a list")
|
||||||
@ -80,6 +66,13 @@ class CommaSeparatedList(db.TypeDecorator):
|
|||||||
return filter(bool, value.split(","))
|
return filter(bool, value.split(","))
|
||||||
|
|
||||||
|
|
||||||
|
# Many-to-many association table for domain managers
|
||||||
|
managers = db.Table('manager',
|
||||||
|
db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')),
|
||||||
|
db.Column('user_email', IdnaEmail, db.ForeignKey('user.email'))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Base(db.Model):
|
class Base(db.Model):
|
||||||
""" Base class for all models
|
""" Base class for all models
|
||||||
"""
|
"""
|
||||||
@ -264,6 +257,10 @@ class User(Base, Email):
|
|||||||
def get_id(self):
|
def get_id(self):
|
||||||
return self.email
|
return self.email
|
||||||
|
|
||||||
|
@property
|
||||||
|
def quota_bytes_used(self):
|
||||||
|
return quota.get(self.email) or 0
|
||||||
|
|
||||||
scheme_dict = {'SHA512-CRYPT': "sha512_crypt",
|
scheme_dict = {'SHA512-CRYPT': "sha512_crypt",
|
||||||
'SHA256-CRYPT': "sha256_crypt",
|
'SHA256-CRYPT': "sha256_crypt",
|
||||||
'MD5-CRYPT': "md5_crypt",
|
'MD5-CRYPT': "md5_crypt",
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
{% if user.enable_imap %}<span class="label label-info">imap</span>{% endif %}
|
{% if user.enable_imap %}<span class="label label-info">imap</span>{% endif %}
|
||||||
{% if user.enable_pop %}<span class="label label-info">pop3</span>{% endif %}
|
{% if user.enable_pop %}<span class="label label-info">pop3</span>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td>
|
<td>{{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td>
|
||||||
<td>{{ user.comment or '-' }}</td>
|
<td>{{ user.comment or '-' }}</td>
|
||||||
<td>{{ user.created_at }}</td>
|
<td>{{ user.created_at }}</td>
|
||||||
<td>{{ user.updated_at or '' }}</td>
|
<td>{{ user.updated_at or '' }}</td>
|
||||||
|
@ -5,7 +5,7 @@ log_path = /dev/stderr
|
|||||||
protocols = imap pop3 lmtp sieve
|
protocols = imap pop3 lmtp sieve
|
||||||
postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }}
|
postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }}
|
||||||
hostname = {{ HOSTNAMES.split(",")[0] }}
|
hostname = {{ HOSTNAMES.split(",")[0] }}
|
||||||
mail_plugins = $mail_plugins quota
|
mail_plugins = $mail_plugins quota quota_clone
|
||||||
submission_host = front
|
submission_host = front
|
||||||
|
|
||||||
service dict {
|
service dict {
|
||||||
@ -119,6 +119,7 @@ service lmtp {
|
|||||||
|
|
||||||
plugin {
|
plugin {
|
||||||
quota = maildir:User quota
|
quota = maildir:User quota
|
||||||
|
quota_clone_dict = redis:host={{ REDIS_ADDRESS }}:db=1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()
|
|||||||
|
|
||||||
# Actual startup script
|
# Actual startup script
|
||||||
os.environ["FRONT_ADDRESS"] = socket.gethostbyname("front")
|
os.environ["FRONT_ADDRESS"] = socket.gethostbyname("front")
|
||||||
|
os.environ["REDIS_ADDRESS"] = socket.gethostbyname("redis")
|
||||||
if os.environ["WEBMAIL"] != "none":
|
if os.environ["WEBMAIL"] != "none":
|
||||||
os.environ["WEBMAIL_ADDRESS"] = socket.gethostbyname("webmail")
|
os.environ["WEBMAIL_ADDRESS"] = socket.gethostbyname("webmail")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user