You've already forked snikket-web-portal
Add support for roles
Requires patches to prosody trunk which have been submitted already (2021-03-22) which introduce the set_roles function on usermanager. Fixes #42.
This commit is contained in:
@@ -65,6 +65,15 @@ class EditUserForm(BaseForm):
|
||||
_l("Display name"),
|
||||
)
|
||||
|
||||
role = wtforms.RadioField(
|
||||
_l("Access Level"),
|
||||
choices=[
|
||||
("prosody:restricted", _l("Limited")),
|
||||
("prosody:normal", _l("Normal user")),
|
||||
("prosody:admin", _l("Administrator")),
|
||||
],
|
||||
)
|
||||
|
||||
action_save = wtforms.SubmitField(
|
||||
_l("Update user"),
|
||||
)
|
||||
@@ -99,6 +108,7 @@ async def edit_user(localpart: str) -> typing.Union[quart.Response, str]:
|
||||
await client.update_user(
|
||||
localpart,
|
||||
display_name=form.display_name.data,
|
||||
roles=[form.role.data],
|
||||
)
|
||||
|
||||
await flash(
|
||||
@@ -110,6 +120,10 @@ async def edit_user(localpart: str) -> typing.Union[quart.Response, str]:
|
||||
elif request.method == "GET":
|
||||
form.localpart.data = target_user_info.localpart
|
||||
form.display_name.data = target_user_info.display_name
|
||||
if target_user_info.roles:
|
||||
form.role.data = target_user_info.roles[0]
|
||||
else:
|
||||
form.role.data = "prosody:normal"
|
||||
|
||||
return await render_template(
|
||||
"admin_edit_user.html",
|
||||
|
||||
@@ -44,6 +44,15 @@ class AdminUserInfo:
|
||||
display_name: typing.Optional[str]
|
||||
email: typing.Optional[str]
|
||||
phone: typing.Optional[str]
|
||||
roles: typing.Optional[typing.List[str]]
|
||||
|
||||
@property
|
||||
def has_admin_role(self) -> bool:
|
||||
return bool(self.roles and "prosody:admin" in self.roles)
|
||||
|
||||
@property
|
||||
def has_restricted_role(self) -> bool:
|
||||
return bool(self.roles and "prosody:restricted" in self.roles)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(
|
||||
@@ -55,6 +64,7 @@ class AdminUserInfo:
|
||||
display_name=data.get("display_name") or None,
|
||||
email=data.get("email") or None,
|
||||
phone=data.get("phone") or None,
|
||||
roles=data.get("roles"),
|
||||
)
|
||||
|
||||
|
||||
@@ -864,13 +874,16 @@ class ProsodyClient:
|
||||
localpart: str,
|
||||
*,
|
||||
display_name: typing.Optional[str],
|
||||
roles: typing.Optional[typing.Collection[str]],
|
||||
session: aiohttp.ClientSession,
|
||||
) -> None:
|
||||
payload = {
|
||||
payload: typing.Dict[str, typing.Any] = {
|
||||
"username": localpart,
|
||||
}
|
||||
if display_name is not None:
|
||||
payload["display_name"] = display_name
|
||||
if roles is not None:
|
||||
payload["roles"] = list(roles)
|
||||
|
||||
async with session.put(
|
||||
self._admin_v1_endpoint("/users/{}".format(localpart)),
|
||||
|
||||
@@ -354,6 +354,15 @@ div.form.layout-expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.radio-button-ext > label > p {
|
||||
margin-left: 1.75rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.radio-button-ext > label .icon {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
|
||||
div.select-wrap {
|
||||
display: block;
|
||||
border-bottom: $w-s4 solid $primary-500;
|
||||
|
||||
@@ -37,6 +37,11 @@ licensed under the terms of the Apache 2.0 License -->
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M10.79 16.29c.39.39 1.02.39 1.41 0l3.59-3.59c.39-.39.39-1.02 0-1.41L12.2 7.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L12.67 11H4c-.55 0-1 .45-1 1s.45 1 1 1h8.67l-1.88 1.88c-.39.39-.38 1.03 0 1.41zM19 3H5c-1.11 0-2 .9-2 2v3c0 .55.45 1 1 1s1-.45 1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1v3c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
|
||||
</symbol>
|
||||
<!-- from: action/lock/materialiconsround/24px.svg -->
|
||||
<symbol id="icon-lock" viewBox="0 0 24 24">
|
||||
<g fill="none"><path d="M0 0h24v24H0V0z" /><path d="M0 0h24v24H0V0z" opacity=".87" /></g>
|
||||
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z" />
|
||||
</symbol>
|
||||
<!-- from: communication/qr_code/materialiconsround/24px.svg -->
|
||||
<symbol id="icon-qrcode" viewBox="0 0 24 24">
|
||||
<g><rect fill="none" height="24" width="24" /><rect fill="none" height="24" width="24" /></g>
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,5 +1,21 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% from "library.j2" import box, form_button, standard_button %}
|
||||
{% from "library.j2" import box, form_button, standard_button, icon %}
|
||||
{% macro access_level_description(role, caller=None) %}
|
||||
{%- if role == "prosody:restricted" -%}
|
||||
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
|
||||
{%- elif role == "prosody:normal" -%}
|
||||
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
|
||||
{%- elif role == "prosody:admin" -%}
|
||||
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
{% macro access_level_icon(role, caller=None) %}
|
||||
{%- if role == "prosody:restricted" -%}
|
||||
{% call icon("lock") %}{% endcall %}
|
||||
{%- elif role == "prosody:admin" -%}
|
||||
{% call icon("admin") %}{% endcall %}
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
{% block content %}
|
||||
<h1>{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}</h1>
|
||||
<div class="form layout-expanded"><form method="POST">
|
||||
@@ -14,6 +30,22 @@
|
||||
{{ form.display_name.label }}
|
||||
{{ form.display_name }}
|
||||
</div>
|
||||
<h3 class="form-title">{% trans %}Access Level{% endtrans %}</h3>
|
||||
<p class="form-descr weak">{% trans %}The access level of a user determines what interactions are allowed for them on your Snikket service.{% endtrans %}</p>
|
||||
<div class="f-ebox">
|
||||
<fieldset>{#- -#}
|
||||
<legend class="a11y-only">{{ form.role.label.text }}</legend>
|
||||
{%- for level in form.role -%}
|
||||
<div class="radio-button-ext">
|
||||
{{ level }}<label for="{{ level.id }}">
|
||||
{%- trans title=level.label.text, icon=access_level_icon(level.data), description=access_level_description(level.data) -%}
|
||||
<strong>{{ title }}{{ icon }}</strong><p>{{ description }}</p>
|
||||
{%- endtrans -%}
|
||||
</label>
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="f-bbox">
|
||||
{%- call standard_button("back", url_for(".users"), class="tertiary") -%}
|
||||
{%- trans -%}Return to user list{%- endtrans -%}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% from "library.j2" import action_button, value_or_hint, custom_form_button %}
|
||||
{% from "library.j2" import action_button, icon, value_or_hint, custom_form_button %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Manage users{% endtrans %}</h1>
|
||||
<div class="elevated el-2"><table>
|
||||
@@ -13,7 +13,15 @@
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.localpart }}</td>
|
||||
<td>
|
||||
{{- user.localpart -}}
|
||||
{%- if user.has_admin_role -%}
|
||||
<span class="with-tooltip above" data-tooltip="{% trans %}The user is an administrator.{% endtrans %}">{% call icon("admin") %}{% trans %} (Administrator){% endtrans %}{% endcall %}</span>
|
||||
{%- endif -%}
|
||||
{%- if user.has_restricted_role -%}
|
||||
<span class="with-tooltip above" data-tooltip="{% trans %}The user is restricted.{% endtrans %}">{% call icon("lock") %}{% trans %} (Restricted){% endtrans %}{% endcall %}</span>
|
||||
{%- endif -%}
|
||||
</td>
|
||||
<td>{% call value_or_hint(user.display_name) %}{% endcall %}</td>
|
||||
<td class="nowrap">
|
||||
{%- call action_button("edit", url_for(".edit_user", localpart=user.localpart), class="primary") -%}
|
||||
|
||||
@@ -27,147 +27,163 @@ msgstr ""
|
||||
msgid "Display name"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:69
|
||||
msgid "Update user"
|
||||
#: snikket_web/admin.py:69 snikket_web/templates/admin_edit_user.html:33
|
||||
msgid "Access Level"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:71
|
||||
msgid "Limited"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:72
|
||||
msgid "Normal user"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:73
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:78
|
||||
msgid "Update user"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:82
|
||||
msgid "Create password reset link"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:91
|
||||
#: snikket_web/admin.py:100
|
||||
msgid "Password reset link created"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:105
|
||||
#: snikket_web/admin.py:115
|
||||
msgid "User information updated."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:123
|
||||
#: snikket_web/admin.py:137
|
||||
msgid "Delete user permanently"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:136
|
||||
#: snikket_web/admin.py:150
|
||||
msgid "User deleted"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:174
|
||||
#: snikket_web/admin.py:188
|
||||
msgid "Password reset link not found"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:186
|
||||
#: snikket_web/admin.py:200
|
||||
msgid "Password reset link deleted"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:206
|
||||
#: snikket_web/admin.py:220
|
||||
msgid "Invite to circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:212
|
||||
#: snikket_web/admin.py:226
|
||||
msgid "At least one circle must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:217
|
||||
#: snikket_web/admin.py:231
|
||||
msgid "Valid for"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:219
|
||||
#: snikket_web/admin.py:233
|
||||
msgid "One hour"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:220
|
||||
#: snikket_web/admin.py:234
|
||||
msgid "Twelve hours"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:221
|
||||
#: snikket_web/admin.py:235
|
||||
msgid "One day"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:222
|
||||
#: snikket_web/admin.py:236
|
||||
msgid "One week"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:223
|
||||
#: snikket_web/admin.py:237
|
||||
msgid "Four weeks"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:229 snikket_web/templates/admin_edit_invite.html:17
|
||||
#: snikket_web/admin.py:243 snikket_web/templates/admin_edit_invite.html:17
|
||||
msgid "Invitation type"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:231 snikket_web/templates/library.j2:116
|
||||
#: snikket_web/admin.py:245 snikket_web/templates/library.j2:116
|
||||
msgid "Individual"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:232 snikket_web/templates/library.j2:114
|
||||
#: snikket_web/admin.py:246 snikket_web/templates/library.j2:114
|
||||
msgid "Group"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:238
|
||||
#: snikket_web/admin.py:252
|
||||
msgid "New invitation link"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:300
|
||||
#: snikket_web/admin.py:314
|
||||
msgid "Revoke"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:324
|
||||
#: snikket_web/admin.py:338
|
||||
msgid "Invitation created"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:340
|
||||
#: snikket_web/admin.py:354
|
||||
msgid "No such invitation exists"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:355
|
||||
#: snikket_web/admin.py:369
|
||||
msgid "Invitation revoked"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:372 snikket_web/admin.py:420
|
||||
#: snikket_web/admin.py:386 snikket_web/admin.py:434
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:377 snikket_web/templates/admin_circles.html:47
|
||||
#: snikket_web/admin.py:391 snikket_web/templates/admin_circles.html:47
|
||||
msgid "Create circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:407
|
||||
#: snikket_web/admin.py:421
|
||||
msgid "Circle created"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:425
|
||||
#: snikket_web/admin.py:439
|
||||
msgid "Select user"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:430
|
||||
#: snikket_web/admin.py:444
|
||||
msgid "Update circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:434
|
||||
#: snikket_web/admin.py:448
|
||||
msgid "Delete circle permanently"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:440
|
||||
#: snikket_web/admin.py:454
|
||||
msgid "Add user"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:456
|
||||
#: snikket_web/admin.py:470
|
||||
msgid "No such circle exists"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:493
|
||||
#: snikket_web/admin.py:507
|
||||
msgid "Circle data updated"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:499
|
||||
#: snikket_web/admin.py:513
|
||||
msgid "Circle deleted"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:510
|
||||
#: snikket_web/admin.py:524
|
||||
msgid "User added to circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:519
|
||||
#: snikket_web/admin.py:533
|
||||
msgid "User removed from circle"
|
||||
msgstr ""
|
||||
|
||||
@@ -481,7 +497,7 @@ msgid "Delete user %(user_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:6
|
||||
#: snikket_web/templates/admin_edit_user.html:22
|
||||
#: snikket_web/templates/admin_edit_user.html:54
|
||||
msgid "Delete user"
|
||||
msgstr ""
|
||||
|
||||
@@ -626,51 +642,78 @@ msgstr ""
|
||||
msgid "Return to invitation list"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:4
|
||||
#: snikket_web/templates/admin_users.html:20
|
||||
#: snikket_web/templates/admin_edit_user.html:5
|
||||
msgid ""
|
||||
"Limited users can interact with users on the same Snikket service and be "
|
||||
"members of circles."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:7
|
||||
msgid ""
|
||||
"Like limited users and can also interact with users on other Snikket "
|
||||
"services."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:9
|
||||
msgid "Like normal users and can access the admin panel in the web portal."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:20
|
||||
#: snikket_web/templates/admin_users.html:28
|
||||
#, python-format
|
||||
msgid "Edit user %(user_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:7
|
||||
#: snikket_web/templates/admin_edit_user.html:23
|
||||
msgid "Edit user"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:11
|
||||
#: snikket_web/templates/admin_edit_user.html:27
|
||||
msgid "The login name cannot be changed."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:19
|
||||
#: snikket_web/templates/admin_edit_user.html:34
|
||||
msgid ""
|
||||
"The access level of a user determines what interactions are allowed for "
|
||||
"them on your Snikket service."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:41
|
||||
#, python-format
|
||||
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:51
|
||||
msgid "Return to user list"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:27
|
||||
#: snikket_web/templates/admin_edit_user.html:59
|
||||
msgid "Further actions"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:29
|
||||
#: snikket_web/templates/admin_edit_user.html:61
|
||||
msgid "Reset password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:32
|
||||
#: snikket_web/templates/admin_edit_user.html:64
|
||||
msgid ""
|
||||
"If the user has lost their password, you can use the button below to "
|
||||
"create a special link which allows to change the password of the account,"
|
||||
" once."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:37
|
||||
#: snikket_web/templates/admin_edit_user.html:69
|
||||
msgid "Debug information"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:39
|
||||
#: snikket_web/templates/admin_edit_user.html:71
|
||||
msgid ""
|
||||
"In some cases, extended information about the user account and the "
|
||||
"connected devices is necessary to troubleshoot issues. The button below "
|
||||
"reveals this (sensitive) information."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:43
|
||||
#: snikket_web/templates/admin_edit_user.html:75
|
||||
msgid "Show debug information"
|
||||
msgstr ""
|
||||
|
||||
@@ -768,6 +811,22 @@ msgstr ""
|
||||
msgid "Destroy link"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_users.html:19
|
||||
msgid "The user is an administrator."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_users.html:19
|
||||
msgid " (Administrator)"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_users.html:22
|
||||
msgid "The user is restricted."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_users.html:22
|
||||
msgid " (Restricted)"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/app.html:4
|
||||
msgid "Snikket Web Portal"
|
||||
msgstr ""
|
||||
|
||||
@@ -5,6 +5,7 @@ action/delete:delete
|
||||
action/logout:logout
|
||||
action/login:login
|
||||
action/exit_to_app:exit_to_app
|
||||
action/lock:lock
|
||||
communication/qr_code:qrcode
|
||||
communication/vpn_key:passwd
|
||||
content/add_circle_outline:add
|
||||
|
||||
Reference in New Issue
Block a user