Display the user quota in the admin interface

master
kaiyou 7 years ago
parent e13593f29a
commit 091369915b

@ -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")

Loading…
Cancel
Save