mirror of
https://github.com/Mailu/Mailu.git
synced 2024-12-14 10:53:30 +02:00
Implement admin and manager management
This commit is contained in:
parent
5fee525e74
commit
340edc629e
@ -21,7 +21,8 @@ def inject_user():
|
|||||||
|
|
||||||
# Import views
|
# Import views
|
||||||
from freeposte.admin.views import \
|
from freeposte.admin.views import \
|
||||||
administrators, \
|
admins, \
|
||||||
|
managers, \
|
||||||
base, \
|
base, \
|
||||||
aliases, \
|
aliases, \
|
||||||
users, \
|
users, \
|
||||||
|
@ -61,3 +61,13 @@ class AliasForm(Form):
|
|||||||
destination = fields.StringField('Destination')
|
destination = fields.StringField('Destination')
|
||||||
comment = fields.StringField('Comment')
|
comment = fields.StringField('Comment')
|
||||||
submit = fields.SubmitField('Create')
|
submit = fields.SubmitField('Create')
|
||||||
|
|
||||||
|
|
||||||
|
class AdminForm(Form):
|
||||||
|
admin = fields.StringField('Admin address', [validators.Email()])
|
||||||
|
submit = fields.SubmitField('Submit')
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerForm(Form):
|
||||||
|
manager = fields.StringField('Manager address', [validators.Email()])
|
||||||
|
submit = fields.SubmitField('Submit')
|
||||||
|
@ -7,15 +7,10 @@ from datetime import datetime
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
# Many-to-many association table for domain administrators
|
# Many-to-many association table for domain managers
|
||||||
admins = db.Table('admin',
|
managers = db.Table('manager',
|
||||||
db.Column('domain_name', db.String(80), db.ForeignKey('domain.name')),
|
db.Column('domain_name', db.String(80), db.ForeignKey('domain.name')),
|
||||||
db.Column('user_domain_name', db.String(80)),
|
db.Column('user_address', db.String(80), db.ForeignKey('user.address'))
|
||||||
db.Column('user_localpart', db.String(80)),
|
|
||||||
db.ForeignKeyConstraint(
|
|
||||||
('user_domain_name', 'user_localpart'),
|
|
||||||
('user.domain_name', 'user.localpart')
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -34,8 +29,8 @@ class Domain(Base):
|
|||||||
""" A DNS domain that has mail addresses associated to it.
|
""" A DNS domain that has mail addresses associated to it.
|
||||||
"""
|
"""
|
||||||
name = db.Column(db.String(80), primary_key=True, nullable=False)
|
name = db.Column(db.String(80), primary_key=True, nullable=False)
|
||||||
admins = db.relationship('User', secondary=admins,
|
managers = db.relationship('User', secondary=managers,
|
||||||
backref=db.backref('admin_of'), lazy='dynamic')
|
backref=db.backref('manager_of'), lazy='dynamic')
|
||||||
max_users = db.Column(db.Integer, nullable=False, default=0)
|
max_users = db.Column(db.Integer, nullable=False, default=0)
|
||||||
max_aliases = db.Column(db.Integer, nullable=False, default=0)
|
max_aliases = db.Column(db.Integer, nullable=False, default=0)
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
{% extends "working.html" %}
|
|
23
admin/freeposte/admin/templates/admin/create.html
Normal file
23
admin/freeposte/admin/templates/admin/create.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Add a global administrator
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block box_content %}
|
||||||
|
<form class="form" method="post" role="form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ macros.form_field(form.admin, id='admin') }}
|
||||||
|
{{ macros.form_field(form.submit) }}
|
||||||
|
<script>
|
||||||
|
$("#admin").tagsinput({
|
||||||
|
confirmKeys: [9, 13, 32],
|
||||||
|
tagClass: 'label label-primary large',
|
||||||
|
typeahead: {
|
||||||
|
afterSelect: function(val) { this.$element.val(""); },
|
||||||
|
source: {{ current_user.get_managed_addresses()|map('string')|list|tojson }}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
32
admin/freeposte/admin/templates/admin/list.html
Normal file
32
admin/freeposte/admin/templates/admin/list.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Global administrators
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_action %}
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('.admin_create') }}">Add administrator</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block box %}
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Actions</th>
|
||||||
|
<th>Address</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Last edit</th>
|
||||||
|
</tr>
|
||||||
|
{% for admin in admins %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('.admin_delete', admin=admin.address) }}" onclick="return confirm('Are you sure?')" title="Delete"><i class="fa fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>{{ admin }}</td>
|
||||||
|
<td>{{ admin.created_at }}</td>
|
||||||
|
<td>{{ admin.updated_at or '' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
@ -32,7 +32,7 @@ Domain list
|
|||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="Users"><i class="fa fa-envelope-o"></i></a>
|
<a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="Users"><i class="fa fa-envelope-o"></i></a>
|
||||||
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="Aliases"><i class="fa fa-at"></i></a>
|
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="Aliases"><i class="fa fa-at"></i></a>
|
||||||
<a href="{{ url_for('.domain_admins', domain_name=domain.name) }}" title="Administrators"><i class="fa fa-user"></i></a>
|
<a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="Managers"><i class="fa fa-user"></i></a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ domain.name }}</td>
|
<td>{{ domain.name }}</td>
|
||||||
<td>{{ domain.users | count }} / {{ domain.max_users or '∞' }}</td>
|
<td>{{ domain.users | count }} / {{ domain.max_users or '∞' }}</td>
|
||||||
|
27
admin/freeposte/admin/templates/manager/create.html
Normal file
27
admin/freeposte/admin/templates/manager/create.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Add a manager
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ domain }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block box_content %}
|
||||||
|
<form class="form" method="post" role="form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ macros.form_field(form.manager, id='manager') }}
|
||||||
|
{{ macros.form_field(form.submit) }}
|
||||||
|
<script>
|
||||||
|
$("#manager").tagsinput({
|
||||||
|
confirmKeys: [9, 13, 32],
|
||||||
|
tagClass: 'label label-primary large',
|
||||||
|
typeahead: {
|
||||||
|
afterSelect: function(val) { this.$element.val(""); },
|
||||||
|
source: {{ current_user.get_managed_addresses()|map('string')|list|tojson }}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
36
admin/freeposte/admin/templates/manager/list.html
Normal file
36
admin/freeposte/admin/templates/manager/list.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Manager list
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ domain.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_action %}
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('.manager_create', domain_name=domain.name) }}">Add manager</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block box %}
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Actions</th>
|
||||||
|
<th>Address</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Last edit</th>
|
||||||
|
</tr>
|
||||||
|
{% for manager in domain.managers %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('.manager_delete', manager=manager.address) }}" onclick="return confirm('Are you sure?')" title="Delete"><i class="fa fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>{{ manager }}</td>
|
||||||
|
<td>{{ manager.created_at }}</td>
|
||||||
|
<td>{{ manager.updated_at or '' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
@ -41,7 +41,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('.admins') }}">
|
<a href="{{ url_for('.admin_list') }}">
|
||||||
<i class="fa fa-user"></i> <span>Adminitrators</span>
|
<i class="fa fa-user"></i> <span>Adminitrators</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
from freeposte import dockercli
|
|
||||||
from freeposte.admin import app, db, models, forms, utils
|
|
||||||
from flask.ext import login as flask_login
|
|
||||||
|
|
||||||
import os
|
|
||||||
import pprint
|
|
||||||
import flask
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/services', methods=['GET'])
|
|
||||||
@flask_login.login_required
|
|
||||||
def services():
|
|
||||||
utils.require_global_admin()
|
|
||||||
containers = {}
|
|
||||||
for brief in dockercli.containers(all=True):
|
|
||||||
if brief['Image'].startswith('freeposte/'):
|
|
||||||
container = dockercli.inspect_container(brief['Id'])
|
|
||||||
container['Image'] = dockercli.inspect_image(container['Image'])
|
|
||||||
name = container['Config']['Labels']['com.docker.compose.service']
|
|
||||||
containers[name] = container
|
|
||||||
pprint.pprint(container)
|
|
||||||
return flask.render_template('admin/services.html', containers=containers)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admins', methods=['GET'])
|
|
||||||
@flask_login.login_required
|
|
||||||
def admins():
|
|
||||||
return flask.render_template('admin/admins.html')
|
|
48
admin/freeposte/admin/views/admins.py
Normal file
48
admin/freeposte/admin/views/admins.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from freeposte.admin import app, db, models, forms
|
||||||
|
from flask.ext import login as flask_login
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
|
import flask
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/list', methods=['GET'])
|
||||||
|
@flask_login.login_required
|
||||||
|
def admin_list():
|
||||||
|
admins = models.User.query.filter_by(global_admin=True)
|
||||||
|
return flask.render_template('admin/list.html', admins=admins)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/create', methods=['GET', 'POST'])
|
||||||
|
@flask_login.login_required
|
||||||
|
def admin_create():
|
||||||
|
form = forms.AdminForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = models.User.query.filter_by(address=form.admin.data).first()
|
||||||
|
if user:
|
||||||
|
user.global_admin = True
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('User %s is now admin' % user)
|
||||||
|
return flask.redirect(flask.url_for('.admin_list'))
|
||||||
|
else:
|
||||||
|
flask.flash('No such user', 'error')
|
||||||
|
return flask.render_template('admin/create.html', form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/delete/<admin>', methods=['GET'])
|
||||||
|
@flask_login.login_required
|
||||||
|
def admin_delete(admin):
|
||||||
|
user = models.User.query.filter_by(address=admin).first()
|
||||||
|
if user:
|
||||||
|
user.global_admin = False
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('User %s is no longer admin' % user)
|
||||||
|
return flask.redirect(flask.url_for('.admin_list'))
|
||||||
|
else:
|
||||||
|
flask.flash('No such user', 'error')
|
||||||
|
flask.flash('Alias %s deleted' % alias)
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('.alias_list', domain_name=alias.domain.name))
|
@ -1,4 +1,5 @@
|
|||||||
from freeposte.admin import app, db, models, forms
|
from freeposte import dockercli
|
||||||
|
from freeposte.admin import app, db, models, forms, utils
|
||||||
from flask.ext import login as flask_login
|
from flask.ext import login as flask_login
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -29,3 +30,17 @@ def login():
|
|||||||
def logout():
|
def logout():
|
||||||
flask_login.logout_user()
|
flask_login.logout_user()
|
||||||
return flask.redirect(flask.url_for('.index'))
|
return flask.redirect(flask.url_for('.index'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/services', methods=['GET'])
|
||||||
|
@flask_login.login_required
|
||||||
|
def services():
|
||||||
|
utils.require_global_admin()
|
||||||
|
containers = {}
|
||||||
|
for brief in dockercli.containers(all=True):
|
||||||
|
if brief['Image'].startswith('freeposte/'):
|
||||||
|
container = dockercli.inspect_container(brief['Id'])
|
||||||
|
container['Image'] = dockercli.inspect_image(container['Image'])
|
||||||
|
name = container['Config']['Labels']['com.docker.compose.service']
|
||||||
|
containers[name] = container
|
||||||
|
return flask.render_template('services.html', containers=containers)
|
||||||
|
49
admin/freeposte/admin/views/managers.py
Normal file
49
admin/freeposte/admin/views/managers.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from freeposte.admin import app, db, models, forms, utils
|
||||||
|
from flask.ext import login as flask_login
|
||||||
|
|
||||||
|
import os
|
||||||
|
import flask
|
||||||
|
import wtforms_components
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/manager/list/<domain_name>', methods=['GET'])
|
||||||
|
@flask_login.login_required
|
||||||
|
def manager_list(domain_name):
|
||||||
|
domain = utils.get_domain_admin(domain_name)
|
||||||
|
return flask.render_template('manager/list.html', domain=domain)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/manager/create/<domain_name>', methods=['GET', 'POST'])
|
||||||
|
@flask_login.login_required
|
||||||
|
def manager_create(domain_name):
|
||||||
|
domain = utils.get_domain_admin(domain_name)
|
||||||
|
form = forms.ManagerForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = utils.get_user(form.manager.data, admin=True)
|
||||||
|
if user in domain.managers:
|
||||||
|
flask.flash('User %s is already manager' % user, 'error')
|
||||||
|
else:
|
||||||
|
domain.managers.append(user)
|
||||||
|
db.session.add(domain)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('User %s can now manage %s' % (user, domain.name))
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('.manager_list', domain_name=domain.name))
|
||||||
|
return flask.render_template('manager/create.html',
|
||||||
|
domain=domain, form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/manager/delete/<manager>', methods=['GET'])
|
||||||
|
@flask_login.login_required
|
||||||
|
def manager_delete(manager):
|
||||||
|
user = utils.get_user(manager, admin=True)
|
||||||
|
domain = utils.get_domain_admin(user.domain_name)
|
||||||
|
if user in domain.managers:
|
||||||
|
domain.managers.remove(user)
|
||||||
|
db.session.add(domain)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('User %s can no longer manager %s' % (user, domain))
|
||||||
|
else:
|
||||||
|
flask.flash('User %s is not manager' % user, 'error')
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('.manager_list', domain_name=domain.name))
|
Loading…
Reference in New Issue
Block a user