mirror of
				https://github.com/optim-enterprises-bv/Mailu.git
				synced 2025-10-31 01:57:59 +00:00 
			
		
		
		
	Display the user quota in the admin interface
This commit is contained in:
		| @@ -11,6 +11,7 @@ import os | ||||
| import docker | ||||
| import socket | ||||
| import uuid | ||||
| import redis | ||||
|  | ||||
| from werkzeug.contrib import fixers | ||||
|  | ||||
| @@ -26,6 +27,7 @@ default_config = { | ||||
|     'BABEL_DEFAULT_TIMEZONE': 'UTC', | ||||
|     'BOOTSTRAP_SERVE_LOCAL': True, | ||||
|     'RATELIMIT_STORAGE_URL': 'redis://redis/2', | ||||
|     'QUOTA_STORAGE_URL': 'redis://redis/1', | ||||
|     'DEBUG': False, | ||||
|     'DOMAIN_REGISTRATION': False, | ||||
|     # Statistics management | ||||
| @@ -87,6 +89,9 @@ manager.add_command('db', flask_migrate.MigrateCommand) | ||||
| babel = flask_babel.Babel(app) | ||||
| translations = list(map(str, babel.list_translations())) | ||||
|  | ||||
| # Quota manager | ||||
| quota = redis.Redis.from_url(app.config.get("QUOTA_STORAGE_URL")) | ||||
|  | ||||
| @babel.localeselector | ||||
| def get_locale(): | ||||
|     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 passlib import context, hash | ||||
| @@ -20,9 +20,8 @@ class IdnaDomain(db.TypeDecorator): | ||||
|  | ||||
|     impl = db.String(80) | ||||
|  | ||||
|  | ||||
|     def process_bind_param(self, value, dialect): | ||||
|         return idna.encode(value) | ||||
|         return idna.encode(value).decode("ascii") | ||||
|  | ||||
|     def process_result_value(self, value, dialect): | ||||
|         return idna.decode(value) | ||||
| @@ -34,31 +33,19 @@ class IdnaEmail(db.TypeDecorator): | ||||
|  | ||||
|     impl = db.String(255, collation="NOCASE") | ||||
|  | ||||
|  | ||||
|     def process_bind_param(self, value, dialect): | ||||
|         localpart, domain_name = value.split('@') | ||||
|  | ||||
|         email = "{0}@{1}".format( | ||||
|         return "{0}@{1}".format( | ||||
|             localpart, | ||||
|             idna.encode(domain_name).decode('ascii'), | ||||
|         ) | ||||
|         return email | ||||
|  | ||||
|     def process_result_value(self, value, dialect): | ||||
|         localpart, domain_name = value.split('@') | ||||
|  | ||||
|         email = "{0}@{1}".format( | ||||
|         return "{0}@{1}".format( | ||||
|             localpart, | ||||
|             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): | ||||
| @@ -67,7 +54,6 @@ class CommaSeparatedList(db.TypeDecorator): | ||||
|  | ||||
|     impl = db.String | ||||
|  | ||||
|  | ||||
|     def process_bind_param(self, value, dialect): | ||||
|         if type(value) is not list: | ||||
|             raise TypeError("Shoud be a list") | ||||
| @@ -80,6 +66,13 @@ class CommaSeparatedList(db.TypeDecorator): | ||||
|         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): | ||||
|     """ Base class for all models | ||||
|     """ | ||||
| @@ -264,6 +257,10 @@ class User(Base, Email): | ||||
|     def get_id(self): | ||||
|         return self.email | ||||
|  | ||||
|     @property | ||||
|     def quota_bytes_used(self): | ||||
|         return quota.get(self.email) or 0 | ||||
|  | ||||
|     scheme_dict = {'SHA512-CRYPT': "sha512_crypt", | ||||
|                    'SHA256-CRYPT': "sha256_crypt", | ||||
|                    'MD5-CRYPT': "md5_crypt", | ||||
|   | ||||
| @@ -40,7 +40,7 @@ | ||||
|     {% if user.enable_imap %}<span class="label label-info">imap</span>{% endif %} | ||||
|     {% if user.enable_pop %}<span class="label label-info">pop3</span>{% endif %} | ||||
|   </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.created_at }}</td> | ||||
|   <td>{{ user.updated_at or '' }}</td> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ log_path = /dev/stderr | ||||
| protocols = imap pop3 lmtp sieve | ||||
| postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }} | ||||
| hostname = {{ HOSTNAMES.split(",")[0] }} | ||||
| mail_plugins = $mail_plugins quota | ||||
| mail_plugins = $mail_plugins quota quota_clone | ||||
| submission_host = front | ||||
|  | ||||
| service dict { | ||||
| @@ -119,6 +119,7 @@ service lmtp { | ||||
|  | ||||
| plugin { | ||||
|   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 | ||||
| os.environ["FRONT_ADDRESS"] = socket.gethostbyname("front") | ||||
| os.environ["REDIS_ADDRESS"] = socket.gethostbyname("redis") | ||||
| if os.environ["WEBMAIL"] != "none": | ||||
| 	os.environ["WEBMAIL_ADDRESS"] = socket.gethostbyname("webmail") | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 kaiyou
					kaiyou