diff --git a/admin/freeposte/admin/__init__.py b/admin/freeposte/admin/__init__.py
index 92aa50a2..179d5155 100644
--- a/admin/freeposte/admin/__init__.py
+++ b/admin/freeposte/admin/__init__.py
@@ -21,7 +21,8 @@ def inject_user():
# Import views
from freeposte.admin.views import \
- administrators, \
+ admins, \
+ managers, \
base, \
aliases, \
users, \
diff --git a/admin/freeposte/admin/forms.py b/admin/freeposte/admin/forms.py
index b6ad7414..dbd1eab9 100644
--- a/admin/freeposte/admin/forms.py
+++ b/admin/freeposte/admin/forms.py
@@ -61,3 +61,13 @@ class AliasForm(Form):
destination = fields.StringField('Destination')
comment = fields.StringField('Comment')
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')
diff --git a/admin/freeposte/admin/models.py b/admin/freeposte/admin/models.py
index 28f05c60..3a4eefd9 100644
--- a/admin/freeposte/admin/models.py
+++ b/admin/freeposte/admin/models.py
@@ -7,15 +7,10 @@ from datetime import datetime
import re
-# Many-to-many association table for domain administrators
-admins = db.Table('admin',
+# Many-to-many association table for domain managers
+managers = db.Table('manager',
db.Column('domain_name', db.String(80), db.ForeignKey('domain.name')),
- db.Column('user_domain_name', db.String(80)),
- db.Column('user_localpart', db.String(80)),
- db.ForeignKeyConstraint(
- ('user_domain_name', 'user_localpart'),
- ('user.domain_name', 'user.localpart')
- )
+ db.Column('user_address', db.String(80), db.ForeignKey('user.address'))
)
@@ -34,8 +29,8 @@ class Domain(Base):
""" A DNS domain that has mail addresses associated to it.
"""
name = db.Column(db.String(80), primary_key=True, nullable=False)
- admins = db.relationship('User', secondary=admins,
- backref=db.backref('admin_of'), lazy='dynamic')
+ managers = db.relationship('User', secondary=managers,
+ backref=db.backref('manager_of'), lazy='dynamic')
max_users = db.Column(db.Integer, nullable=False, default=0)
max_aliases = db.Column(db.Integer, nullable=False, default=0)
diff --git a/admin/freeposte/admin/templates/admin/admins.html b/admin/freeposte/admin/templates/admin/admins.html
deleted file mode 100644
index a500fb36..00000000
--- a/admin/freeposte/admin/templates/admin/admins.html
+++ /dev/null
@@ -1 +0,0 @@
-{% extends "working.html" %}
diff --git a/admin/freeposte/admin/templates/admin/create.html b/admin/freeposte/admin/templates/admin/create.html
new file mode 100644
index 00000000..c974e28b
--- /dev/null
+++ b/admin/freeposte/admin/templates/admin/create.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}
+Add a global administrator
+{% endblock %}
+
+{% block box_content %}
+
+{% endblock %}
diff --git a/admin/freeposte/admin/templates/admin/list.html b/admin/freeposte/admin/templates/admin/list.html
new file mode 100644
index 00000000..eeaa1d12
--- /dev/null
+++ b/admin/freeposte/admin/templates/admin/list.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+
+{% block title %}
+Global administrators
+{% endblock %}
+
+{% block main_action %}
+Add administrator
+{% endblock %}
+
+{% block box %}
+
+
+
+ Actions |
+ Address |
+ Created |
+ Last edit |
+
+ {% for admin in admins %}
+
+
+
+ |
+ {{ admin }} |
+ {{ admin.created_at }} |
+ {{ admin.updated_at or '' }} |
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/admin/freeposte/admin/templates/domain/admins.html b/admin/freeposte/admin/templates/domain/admins.html
deleted file mode 100644
index e69de29b..00000000
diff --git a/admin/freeposte/admin/templates/domain/list.html b/admin/freeposte/admin/templates/domain/list.html
index 4547f421..ac380a54 100644
--- a/admin/freeposte/admin/templates/domain/list.html
+++ b/admin/freeposte/admin/templates/domain/list.html
@@ -32,7 +32,7 @@ Domain list
-
+
|
{{ domain.name }} |
{{ domain.users | count }} / {{ domain.max_users or '∞' }} |
diff --git a/admin/freeposte/admin/templates/manager/create.html b/admin/freeposte/admin/templates/manager/create.html
new file mode 100644
index 00000000..aa70bd7d
--- /dev/null
+++ b/admin/freeposte/admin/templates/manager/create.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+
+{% block title %}
+Add a manager
+{% endblock %}
+
+{% block subtitle %}
+{{ domain }}
+{% endblock %}
+
+{% block box_content %}
+
+{% endblock %}
diff --git a/admin/freeposte/admin/templates/manager/list.html b/admin/freeposte/admin/templates/manager/list.html
new file mode 100644
index 00000000..2e978906
--- /dev/null
+++ b/admin/freeposte/admin/templates/manager/list.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+
+{% block title %}
+Manager list
+{% endblock %}
+
+{% block subtitle %}
+{{ domain.name }}
+{% endblock %}
+
+{% block main_action %}
+Add manager
+{% endblock %}
+
+{% block box %}
+
+
+
+ Actions |
+ Address |
+ Created |
+ Last edit |
+
+ {% for manager in domain.managers %}
+
+
+
+ |
+ {{ manager }} |
+ {{ manager.created_at }} |
+ {{ manager.updated_at or '' }} |
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/admin/freeposte/admin/templates/admin/services.html b/admin/freeposte/admin/templates/services.html
similarity index 100%
rename from admin/freeposte/admin/templates/admin/services.html
rename to admin/freeposte/admin/templates/services.html
diff --git a/admin/freeposte/admin/templates/sidebar.html b/admin/freeposte/admin/templates/sidebar.html
index 7621b03a..577443af 100644
--- a/admin/freeposte/admin/templates/sidebar.html
+++ b/admin/freeposte/admin/templates/sidebar.html
@@ -41,7 +41,7 @@
-
+
Adminitrators
diff --git a/admin/freeposte/admin/views/administrators.py b/admin/freeposte/admin/views/administrators.py
deleted file mode 100644
index 828e06f1..00000000
--- a/admin/freeposte/admin/views/administrators.py
+++ /dev/null
@@ -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')
diff --git a/admin/freeposte/admin/views/admins.py b/admin/freeposte/admin/views/admins.py
new file mode 100644
index 00000000..d7b93fa0
--- /dev/null
+++ b/admin/freeposte/admin/views/admins.py
@@ -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/', 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))
diff --git a/admin/freeposte/admin/views/base.py b/admin/freeposte/admin/views/base.py
index 142fddcf..4bf7d68a 100644
--- a/admin/freeposte/admin/views/base.py
+++ b/admin/freeposte/admin/views/base.py
@@ -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
import os
@@ -29,3 +30,17 @@ def login():
def logout():
flask_login.logout_user()
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)
diff --git a/admin/freeposte/admin/views/managers.py b/admin/freeposte/admin/views/managers.py
new file mode 100644
index 00000000..4e5938bd
--- /dev/null
+++ b/admin/freeposte/admin/views/managers.py
@@ -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/', 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/', 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/', 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))