diff --git a/snikket_web/admin.py b/snikket_web/admin.py index 04c1cb5..f68cda8 100644 --- a/snikket_web/admin.py +++ b/snikket_web/admin.py @@ -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", diff --git a/snikket_web/prosodyclient.py b/snikket_web/prosodyclient.py index a98b767..68a41a4 100644 --- a/snikket_web/prosodyclient.py +++ b/snikket_web/prosodyclient.py @@ -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)), diff --git a/snikket_web/scss/app.scss b/snikket_web/scss/app.scss index 8612d62..a22386e 100644 --- a/snikket_web/scss/app.scss +++ b/snikket_web/scss/app.scss @@ -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; diff --git a/snikket_web/static/img/icons.svg b/snikket_web/static/img/icons.svg index 58bdd80..1d241ec 100644 --- a/snikket_web/static/img/icons.svg +++ b/snikket_web/static/img/icons.svg @@ -37,6 +37,11 @@ licensed under the terms of the Apache 2.0 License --> + + + + + diff --git a/snikket_web/templates/admin_edit_user.html b/snikket_web/templates/admin_edit_user.html index df070fb..b54d6c1 100644 --- a/snikket_web/templates/admin_edit_user.html +++ b/snikket_web/templates/admin_edit_user.html @@ -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 %}

{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}

@@ -14,6 +30,22 @@ {{ form.display_name.label }} {{ form.display_name }}
+

{% trans %}Access Level{% endtrans %}

+

{% trans %}The access level of a user determines what interactions are allowed for them on your Snikket service.{% endtrans %}

+
+
{#- -#} + {{ form.role.label.text }} + {%- for level in form.role -%} +
+ {{ level }} +
+ {%- endfor -%} +
+
{%- call standard_button("back", url_for(".users"), class="tertiary") -%} {%- trans -%}Return to user list{%- endtrans -%} diff --git a/snikket_web/templates/admin_users.html b/snikket_web/templates/admin_users.html index e25290e..7c1831b 100644 --- a/snikket_web/templates/admin_users.html +++ b/snikket_web/templates/admin_users.html @@ -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 %}

{% trans %}Manage users{% endtrans %}

@@ -13,7 +13,15 @@ {% for user in users %} - +
{{ user.localpart }} + {{- user.localpart -}} + {%- if user.has_admin_role -%} + {% call icon("admin") %}{% trans %} (Administrator){% endtrans %}{% endcall %} + {%- endif -%} + {%- if user.has_restricted_role -%} + {% call icon("lock") %}{% trans %} (Restricted){% endtrans %}{% endcall %} + {%- endif -%} + {% call value_or_hint(user.display_name) %}{% endcall %} {%- call action_button("edit", url_for(".edit_user", localpart=user.localpart), class="primary") -%} diff --git a/snikket_web/translations/messages.pot b/snikket_web/translations/messages.pot index c16a963..c977ccd 100644 --- a/snikket_web/translations/messages.pot +++ b/snikket_web/translations/messages.pot @@ -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 "%(title)s%(icon)s

%(description)s

" +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 "" diff --git a/tools/icons.list b/tools/icons.list index 4a9d836..47bc0a4 100644 --- a/tools/icons.list +++ b/tools/icons.list @@ -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