You've already forked snikket-web-portal
Implement group support (we call ’em circles)
This commit is contained in:
@@ -1,18 +1,28 @@
|
||||
import asyncio
|
||||
import typing
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import aiohttp
|
||||
|
||||
import quart.flask_patch
|
||||
|
||||
import wtforms
|
||||
import wtforms.fields.html5
|
||||
|
||||
from quart import (Blueprint, render_template, redirect, url_for)
|
||||
from quart import (
|
||||
Blueprint,
|
||||
render_template,
|
||||
redirect,
|
||||
url_for,
|
||||
request,
|
||||
)
|
||||
import flask_wtf
|
||||
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from .infra import client
|
||||
from . import prosodyclient
|
||||
from .infra import client, circle_name
|
||||
|
||||
bp = Blueprint("admin", __name__, url_prefix="/admin")
|
||||
|
||||
@@ -67,10 +77,55 @@ async def delete_user(localpart: str) -> typing.Union[str, quart.Response]:
|
||||
class InvitesListForm(flask_wtf.FlaskForm): # type:ignore
|
||||
action_revoke = wtforms.StringField()
|
||||
|
||||
|
||||
class InvitePost(flask_wtf.FlaskForm): # type:ignore
|
||||
circles = wtforms.SelectMultipleField(
|
||||
_l("Invite to circle"),
|
||||
# NOTE: This is for when/if we ever support multi-group invites.
|
||||
# also see the note in admin_create_invite_form.html
|
||||
# option_widget=wtforms.widgets.CheckboxInput(),
|
||||
widget=wtforms.widgets.Select(multiple=False),
|
||||
validators=[wtforms.validators.InputRequired(
|
||||
_l("At least one circle must be selected")
|
||||
)],
|
||||
)
|
||||
|
||||
lifetime = wtforms.SelectField(
|
||||
_l("Valid for"),
|
||||
choices=[
|
||||
(3600, _l("One hour")),
|
||||
(12*3600, _l("Twelve hours")),
|
||||
(86400, _l("One day")),
|
||||
(7*86400, _l("One week")),
|
||||
(28*86400, _l("Four weeks")),
|
||||
],
|
||||
default=7*86400,
|
||||
)
|
||||
|
||||
reusable = wtforms.BooleanField(
|
||||
_l("Allow multiple uses"),
|
||||
)
|
||||
|
||||
action_create_invite = wtforms.SubmitField(
|
||||
_l("New invitation link")
|
||||
)
|
||||
|
||||
async def init_choices(
|
||||
self,
|
||||
*,
|
||||
circles: typing.Optional[typing.Collection[
|
||||
prosodyclient.AdminGroupInfo
|
||||
]] = None) -> None:
|
||||
if circles is not None:
|
||||
self.circles.choices = [
|
||||
(circle.id_, circle_name(circle))
|
||||
for circle in sorted(circles, key=lambda x: x.name)
|
||||
]
|
||||
return
|
||||
return await self.init_choices(
|
||||
circles=await client.list_groups()
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/invitations", methods=["GET", "POST"])
|
||||
@client.require_admin_session()
|
||||
@@ -78,23 +133,34 @@ async def invitations() -> typing.Union[str, quart.Response]:
|
||||
user_info = await client.get_user_info()
|
||||
invites = sorted(
|
||||
await client.list_invites(),
|
||||
key=lambda x: x.created_at
|
||||
key=lambda x: x.created_at,
|
||||
reverse=True,
|
||||
)
|
||||
circles = sorted(
|
||||
await client.list_groups(),
|
||||
key=lambda x: x.name
|
||||
)
|
||||
circle_map = {
|
||||
circle.id_: circle
|
||||
for circle in circles
|
||||
}
|
||||
|
||||
invite_form = InvitePost()
|
||||
await invite_form.init_choices(circles=circles)
|
||||
|
||||
form = InvitesListForm()
|
||||
if form.validate_on_submit():
|
||||
if form.action_revoke.data:
|
||||
await client.delete_invite(form.action_revoke.data)
|
||||
if form.action_create_invite.data:
|
||||
info = await client.create_invite()
|
||||
return redirect(url_for(".edit_invite", id_=info.id_))
|
||||
return redirect(url_for(".invitations"))
|
||||
|
||||
return await render_template(
|
||||
"admin_invites.html",
|
||||
user_info=user_info,
|
||||
invites=invites,
|
||||
invite_form=invite_form,
|
||||
now=datetime.utcnow(),
|
||||
circle_map=circle_map,
|
||||
form=form,
|
||||
)
|
||||
|
||||
@@ -105,11 +171,37 @@ class InviteForm(flask_wtf.FlaskForm): # type:ignore
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/invitation/-/new", methods=["POST"])
|
||||
@client.require_admin_session()
|
||||
async def create_invite() -> typing.Union[str, quart.Response]:
|
||||
user_info = await client.get_user_info()
|
||||
form = InvitePost()
|
||||
circles = await client.list_groups()
|
||||
form.circles.choices = [
|
||||
(c.id_, c.name) for c in circles
|
||||
]
|
||||
if form.validate_on_submit():
|
||||
invite = await client.create_invite(
|
||||
group_ids=form.circles.data,
|
||||
reusable=form.reusable.data,
|
||||
ttl=form.lifetime.data,
|
||||
)
|
||||
return redirect(url_for(".edit_invite", id_=invite.id_))
|
||||
return await render_template("admin_create_invite.html",
|
||||
user_info=user_info,
|
||||
invite_form=form)
|
||||
|
||||
|
||||
@bp.route("/invitation/<id_>", methods=["GET", "POST"])
|
||||
@client.require_admin_session()
|
||||
async def edit_invite(id_: str) -> typing.Union[str, quart.Response]:
|
||||
user_info = await client.get_user_info()
|
||||
invite_info = await client.get_invite_by_id(id_)
|
||||
circles = await client.list_groups()
|
||||
circle_map = {
|
||||
circle.id_: circle
|
||||
for circle in circles
|
||||
}
|
||||
|
||||
form = InviteForm()
|
||||
if form.validate_on_submit():
|
||||
@@ -124,4 +216,120 @@ async def edit_invite(id_: str) -> typing.Union[str, quart.Response]:
|
||||
invite=invite_info,
|
||||
now=datetime.utcnow(),
|
||||
form=form,
|
||||
circle_map=circle_map,
|
||||
)
|
||||
|
||||
|
||||
class CirclePost(flask_wtf.FlaskForm): # type:ignore
|
||||
name = wtforms.StringField(
|
||||
_l("Name"),
|
||||
)
|
||||
|
||||
action_create = wtforms.SubmitField(
|
||||
_l("Create circle")
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/circles")
|
||||
@client.require_admin_session()
|
||||
async def circles() -> str:
|
||||
user_info = await client.get_user_info()
|
||||
circles = sorted(
|
||||
await client.list_groups(),
|
||||
key=lambda x: x.name
|
||||
)
|
||||
invite_form = InvitePost()
|
||||
create_form = CirclePost()
|
||||
return await render_template(
|
||||
"admin_circles.html",
|
||||
circles=circles,
|
||||
user_info=user_info,
|
||||
invite_form=invite_form,
|
||||
create_form=create_form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/circle/-/new", methods=["POST"])
|
||||
@client.require_admin_session()
|
||||
async def create_circle() -> typing.Union[str, quart.Response]:
|
||||
user_info = await client.get_user_info()
|
||||
create_form = CirclePost()
|
||||
if create_form.validate_on_submit():
|
||||
circle = await client.create_group(
|
||||
name=create_form.name.data,
|
||||
)
|
||||
return redirect(url_for(".edit_circle", id_=circle.id_))
|
||||
|
||||
return await render_template(
|
||||
"admin_create_circle.html",
|
||||
user_info=user_info,
|
||||
create_form=create_form,
|
||||
)
|
||||
|
||||
|
||||
class EditCircleForm(flask_wtf.FlaskForm): # type:ignore
|
||||
name = wtforms.StringField(
|
||||
_l("Name"),
|
||||
)
|
||||
|
||||
action_save = wtforms.SubmitField(
|
||||
_l("Apply")
|
||||
)
|
||||
|
||||
action_delete = wtforms.SubmitField(
|
||||
_l("Delete circle permanently")
|
||||
)
|
||||
|
||||
action_remove_user = wtforms.StringField()
|
||||
|
||||
|
||||
@bp.route("/circle/<id_>", methods=["GET", "POST"])
|
||||
@client.require_admin_session()
|
||||
async def edit_circle(id_: str) -> typing.Union[str, quart.Response]:
|
||||
async with client.authenticated_session() as session:
|
||||
user_info = await client.get_user_info(
|
||||
session=session,
|
||||
)
|
||||
try:
|
||||
circle = await client.get_group_by_id(
|
||||
id_,
|
||||
session=session,
|
||||
)
|
||||
except aiohttp.ClientResponseError as exc:
|
||||
if exc.status == 404:
|
||||
return redirect(url_for(".circles"))
|
||||
raise
|
||||
|
||||
circle_members = await asyncio.gather(*(
|
||||
client.get_user_by_localpart(
|
||||
localpart,
|
||||
session=session,
|
||||
)
|
||||
for localpart in sorted(circle.members)
|
||||
))
|
||||
|
||||
form = EditCircleForm()
|
||||
invite_form = InvitePost()
|
||||
await invite_form.init_choices()
|
||||
invite_form.circles.data = [id_]
|
||||
|
||||
if request.method != "POST":
|
||||
form.name.data = circle.name
|
||||
if form.validate_on_submit():
|
||||
if form.action_save.data:
|
||||
# TODO: post update
|
||||
pass
|
||||
elif form.action_delete.data:
|
||||
await client.delete_group(id_)
|
||||
return redirect(url_for(".circles"))
|
||||
|
||||
return redirect(url_for(".edit_circle", id_=id_))
|
||||
|
||||
return await render_template(
|
||||
"admin_edit_circle.html",
|
||||
target_circle=circle,
|
||||
user_info=user_info,
|
||||
form=form,
|
||||
circle_members=circle_members,
|
||||
invite_form=invite_form,
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from quart import (
|
||||
)
|
||||
|
||||
import flask_babel
|
||||
from flask_babel import _
|
||||
|
||||
from . import prosodyclient
|
||||
|
||||
@@ -20,9 +21,11 @@ babel = flask_babel.Babel()
|
||||
|
||||
@babel.localeselector # type:ignore
|
||||
def selected_locale() -> str:
|
||||
return request.accept_languages.best_match(
|
||||
selected = request.accept_languages.best_match(
|
||||
current_app.config['LANGUAGES']
|
||||
)
|
||||
print(request.accept_languages, current_app.config["LANGUAGES"], selected)
|
||||
return selected
|
||||
|
||||
|
||||
def flatten(a: typing.Iterable, levels: int = 1) -> typing.Iterable:
|
||||
@@ -31,6 +34,12 @@ def flatten(a: typing.Iterable, levels: int = 1) -> typing.Iterable:
|
||||
return a
|
||||
|
||||
|
||||
def circle_name(c: typing.Any) -> str:
|
||||
if c.id_ == "default" and c.name == "default":
|
||||
return _("Main")
|
||||
return c.name
|
||||
|
||||
|
||||
def init_templating(app: quart.Quart) -> None:
|
||||
app.template_filter("repr")(repr)
|
||||
app.template_filter("format_datetime")(flask_babel.format_datetime)
|
||||
@@ -38,3 +47,4 @@ def init_templating(app: quart.Quart) -> None:
|
||||
app.template_filter("format_time")(flask_babel.format_time)
|
||||
app.template_filter("format_timedelta")(flask_babel.format_timedelta)
|
||||
app.template_filter("flatten")(flatten)
|
||||
app.template_filter("circle_name")(circle_name)
|
||||
|
||||
@@ -39,6 +39,10 @@ class LoginForm(flask_wtf.FlaskForm): # type:ignore
|
||||
validators=[wtforms.validators.InputRequired()],
|
||||
)
|
||||
|
||||
action_signin = wtforms.SubmitField(
|
||||
_l("Sign in"),
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/login", methods=["GET", "POST"])
|
||||
async def login() -> typing.Union[str, quart.Response]:
|
||||
|
||||
@@ -72,6 +72,8 @@ class AdminInviteInfo:
|
||||
landing_page: typing.Optional[str]
|
||||
created_at: datetime
|
||||
expires: datetime
|
||||
reusable: bool
|
||||
group_ids: typing.Collection[str]
|
||||
|
||||
@classmethod
|
||||
def from_api_response(
|
||||
@@ -87,6 +89,26 @@ class AdminInviteInfo:
|
||||
token=data["id"],
|
||||
xmpp_uri=data.get("xmpp_uri"),
|
||||
landing_page=data.get("landing_page"),
|
||||
group_ids=data.get("groups", []),
|
||||
reusable=data["reusable"],
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class AdminGroupInfo:
|
||||
id_: str
|
||||
name: str
|
||||
members: typing.Collection[str]
|
||||
|
||||
@classmethod
|
||||
def from_api_response(
|
||||
cls,
|
||||
data: typing.Mapping[str, typing.Any],
|
||||
) -> "AdminGroupInfo":
|
||||
return cls(
|
||||
id_=data["id"],
|
||||
name=data["name"],
|
||||
members=data["members"],
|
||||
)
|
||||
|
||||
|
||||
@@ -305,6 +327,9 @@ class ProsodyClient:
|
||||
def has_session(self) -> bool:
|
||||
return self.SESSION_TOKEN in http_session
|
||||
|
||||
def authenticated_session(self) -> HTTPAuthSessionManager:
|
||||
return self._auth_session
|
||||
|
||||
def require_session(
|
||||
self,
|
||||
redirect_to: typing.Optional[str] = None,
|
||||
@@ -394,29 +419,33 @@ class ProsodyClient:
|
||||
)
|
||||
return ET.fromstring(reply_payload)
|
||||
|
||||
async def get_user_info(self) -> typing.Mapping:
|
||||
@autosession
|
||||
async def get_user_info(
|
||||
self,
|
||||
*,
|
||||
session: aiohttp.ClientSession,
|
||||
) -> typing.Mapping:
|
||||
localpart, domain, _ = split_jid(self.session_address)
|
||||
|
||||
async with self._auth_session as session:
|
||||
nickname = await self.get_user_nickname(session=session)
|
||||
try:
|
||||
avatar_info = await self.get_avatar(
|
||||
self.session_address,
|
||||
metadata_only=True,
|
||||
session=session,
|
||||
)
|
||||
avatar_hash = avatar_info["sha1"]
|
||||
except quart.exceptions.HTTPException:
|
||||
avatar_hash = None
|
||||
nickname = await self.get_user_nickname(session=session)
|
||||
try:
|
||||
avatar_info = await self.get_avatar(
|
||||
self.session_address,
|
||||
metadata_only=True,
|
||||
session=session,
|
||||
)
|
||||
avatar_hash = avatar_info["sha1"]
|
||||
except quart.exceptions.HTTPException:
|
||||
avatar_hash = None
|
||||
|
||||
return {
|
||||
"address": self.session_address,
|
||||
"username": localpart,
|
||||
"nickname": nickname,
|
||||
"display_name": nickname or localpart,
|
||||
"avatar_hash": avatar_hash,
|
||||
"is_admin": self.is_admin_session,
|
||||
}
|
||||
return {
|
||||
"address": self.session_address,
|
||||
"username": localpart,
|
||||
"nickname": nickname,
|
||||
"display_name": nickname or localpart,
|
||||
"avatar_hash": avatar_hash,
|
||||
"is_admin": self.is_admin_session,
|
||||
}
|
||||
|
||||
@autosession
|
||||
async def test_session(self, session: aiohttp.ClientSession) -> bool:
|
||||
@@ -698,7 +727,7 @@ class ProsodyClient:
|
||||
if resp.status == 400:
|
||||
abort(500, "request rejected by backend")
|
||||
if not 200 <= resp.status < 300:
|
||||
abort(resp.status)
|
||||
resp.raise_for_status()
|
||||
|
||||
@autosession
|
||||
async def list_users(
|
||||
@@ -777,13 +806,89 @@ class ProsodyClient:
|
||||
@autosession
|
||||
async def create_invite(
|
||||
self,
|
||||
group_ids: typing.Collection[str],
|
||||
reusable: bool,
|
||||
ttl: int,
|
||||
*,
|
||||
session: aiohttp.ClientSession,
|
||||
) -> AdminInviteInfo:
|
||||
async with session.put(self._admin_v1_endpoint("/invites")) as resp:
|
||||
payload = {
|
||||
"reusable": reusable,
|
||||
"groups": list(group_ids),
|
||||
"ttl": ttl,
|
||||
}
|
||||
|
||||
async with session.post(
|
||||
self._admin_v1_endpoint("/invites"),
|
||||
json=payload) as resp:
|
||||
self._raise_error_from_response(resp)
|
||||
return AdminInviteInfo.from_api_response(await resp.json())
|
||||
|
||||
@autosession
|
||||
async def create_group(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
session: aiohttp.ClientSession,
|
||||
) -> AdminGroupInfo:
|
||||
payload = {
|
||||
"name": name,
|
||||
}
|
||||
|
||||
async with session.post(
|
||||
self._admin_v1_endpoint("/groups"),
|
||||
json=payload) as resp:
|
||||
self._raise_error_from_response(resp)
|
||||
return AdminGroupInfo.from_api_response(await resp.json())
|
||||
|
||||
@autosession
|
||||
async def list_groups(
|
||||
self,
|
||||
*,
|
||||
session: aiohttp.ClientSession,
|
||||
) -> typing.Collection[AdminGroupInfo]:
|
||||
async with session.get(self._admin_v1_endpoint("/groups")) as resp:
|
||||
self._raise_error_from_response(resp)
|
||||
return list(map(
|
||||
AdminGroupInfo.from_api_response,
|
||||
await resp.json(),
|
||||
))
|
||||
|
||||
@autosession
|
||||
async def get_group_by_id(
|
||||
self,
|
||||
id_: str,
|
||||
*,
|
||||
session: aiohttp.ClientSession,
|
||||
) -> AdminGroupInfo:
|
||||
async with session.get(
|
||||
self._admin_v1_endpoint("/groups/{}".format(id_)),
|
||||
) as resp:
|
||||
self._raise_error_from_response(resp)
|
||||
return AdminGroupInfo.from_api_response(await resp.json())
|
||||
|
||||
@autosession
|
||||
async def update_group(
|
||||
self,
|
||||
id_: str,
|
||||
*,
|
||||
new_name: typing.Optional[str] = None,
|
||||
session: aiohttp.ClientSession,
|
||||
) -> AdminGroupInfo:
|
||||
pass
|
||||
|
||||
@autosession
|
||||
async def delete_group(
|
||||
self,
|
||||
id_: str,
|
||||
*,
|
||||
session: aiohttp.ClientSession,
|
||||
) -> None:
|
||||
async with session.delete(
|
||||
self._admin_v1_endpoint("/groups/{}".format(id_)),
|
||||
) as resp:
|
||||
self._raise_error_from_response(resp)
|
||||
|
||||
async def logout(self) -> None:
|
||||
# this currently only kills the cookie stuff, we may want to invalidate
|
||||
# the token on the server side, toos
|
||||
|
||||
@@ -250,3 +250,4 @@ $h-sizes: [200.0%, 174.11011266%, 151.57165665%, 131.95079108%, 114.8698355%, 10
|
||||
*/
|
||||
$h-small-sizes: [150.0%, 138.31618672%, 127.54245006%, 117.60790225%, 108.44717712%, 100.0%];
|
||||
$small-screen-threshold: 40rem;
|
||||
$medium-screen-threshold: 60rem;
|
||||
|
||||
@@ -805,6 +805,22 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
div.elevated {
|
||||
margin: $w-l1;
|
||||
padding: $w-l1;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
div.elevated > *:first-child {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.elevated > *:last-child {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.long-url-link {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
@@ -820,15 +836,47 @@ table {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.inline {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> li:before {
|
||||
content: ', ';
|
||||
}
|
||||
|
||||
> li:first-child:before {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
/* linearisation / responsive stuff */
|
||||
|
||||
@media screen and (max-width: $small-screen-threshold) {
|
||||
main > .form.layout-expanded {
|
||||
@media screen and (max-width: $medium-screen-threshold) {
|
||||
.form.layout-expanded {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
div.elevated {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-screen-threshold) {
|
||||
.form.layout-expanded .box {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
||||
51
snikket_web/templates/admin_circles.html
Normal file
51
snikket_web/templates/admin_circles.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% from "library.j2" import action_button, custom_form_button, form_button, circle_name %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Manage circles{% endtrans %}</h1>
|
||||
{%- if circles -%}
|
||||
<form method="POST" action="{{ url_for(".create_invite") }}">
|
||||
{{- invite_form.csrf_token -}}
|
||||
<div class="elevated el-2"><table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Circle name{% endtrans %}</th>
|
||||
<th class="collapsible">{% trans %}Members{% endtrans %}</th>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for circle in circles %}
|
||||
<tr>
|
||||
<td>{{ circle | circle_name }}</td>
|
||||
<td class="collapsible">{{ circle.members | length }}</td>
|
||||
<td class="nowrap">
|
||||
{%- call custom_form_button("create_link", invite_form.circles.name, circle.id_, slim=True, class="secondary accent") -%}
|
||||
{% trans circle_name=circle.name %}Create invitation to circle {{ circle_name }}{% endtrans %}
|
||||
{%- endcall -%}
|
||||
{%- call action_button("more", url_for(".edit_circle", id_=circle.id_), class="primary") -%}
|
||||
{% trans circle_name=circle.name %}Show details of circle {{ circle_name }}{% endtrans %}
|
||||
{%- endcall -%}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table></div></form>
|
||||
{%- else -%}
|
||||
<div class="box primary">
|
||||
<header>{% trans %}No circles{% endtrans %}</header>
|
||||
<p>{% trans %}Currently, there are no circles on this instance. Use the form below to create one.{% endtrans %}</p>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
<h2>{% trans %}New circle{% endtrans %}</h2>
|
||||
<form method="POST" action="{{ url_for(".create_circle") }}"><div class="form layout-expanded">
|
||||
{{- create_form.csrf_token -}}
|
||||
<h2 class="form-title">{% trans %}Create circle{% endtrans %}</h2>
|
||||
<div class="f-ebox">
|
||||
{{- create_form.name.label -}}
|
||||
{{- create_form.name -}}
|
||||
</div>
|
||||
<div class="f-bbox">
|
||||
{%- call form_button("create_group", create_form.action_create, class="primary") -%}{%- endcall -%}
|
||||
</div>
|
||||
</div></form>
|
||||
{% endblock %}
|
||||
5
snikket_web/templates/admin_create_invite.html
Normal file
5
snikket_web/templates/admin_create_invite.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Create invitation{% endtrans %}</h1>
|
||||
{%- include "admin_create_invite_form.html" -%}
|
||||
{% endblock %}
|
||||
31
snikket_web/templates/admin_create_invite_form.html
Normal file
31
snikket_web/templates/admin_create_invite_form.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% from "library.j2" import form_button, render_errors %}
|
||||
<form method="POST" action="{{ url_for(".create_invite") }}">
|
||||
{{- invite_form.csrf_token -}}
|
||||
<div class="form layout-expanded">
|
||||
<h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
|
||||
<p class="form-descr weak">{% trans %}Create a new invitation link to invite more users to your Snikket instance by clicking the button below.{% endtrans %}</p>
|
||||
<div class="f-ebox">
|
||||
{{ invite_form.reusable }}
|
||||
{{ invite_form.reusable.label }}
|
||||
</div>
|
||||
<div class="f-ebox">
|
||||
{{ invite_form.lifetime.label }}
|
||||
<div class="select-wrap">{{ invite_form.lifetime }}</div>
|
||||
</div>
|
||||
<div class="f-ebox">
|
||||
{#
|
||||
NOTE: This is for when/if we ever support multi-group invites.
|
||||
Also see the NOTE in admin.py
|
||||
{{ invite_form.circles.label(class="required") }}
|
||||
{%- for choice in invite_form.circles -%}
|
||||
{{ choice }}{{ choice.label }}
|
||||
{%- endfor -%}
|
||||
#}
|
||||
{{- invite_form.circles.label -}}
|
||||
<div class="select-wrap">{{ invite_form.circles }}</div>
|
||||
{%- call render_errors(invite_form.circles) -%}{%- endcall -%}
|
||||
</div>
|
||||
<div class="f-bbox">
|
||||
{%- call form_button("create_link", invite_form.action_create_invite, class="primary") %}{% endcall -%}
|
||||
</div>
|
||||
</div></form>
|
||||
48
snikket_web/templates/admin_edit_circle.html
Normal file
48
snikket_web/templates/admin_edit_circle.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% from "library.j2" import form_button, standard_button, value_or_hint, custom_form_button %}
|
||||
{% block content %}
|
||||
<h1>{% trans circle_name=(target_circle | circle_name) %}Edit circle {{ circle_name }}{% endtrans %}</h1>
|
||||
<div class="form layout-expanded"><form method="POST">
|
||||
<h2 class="form-title">{% trans %}Circle information{% endtrans %}</h2>
|
||||
{{ form.csrf_token }}
|
||||
<div class="f-ebox">
|
||||
{{ form.name.label }}
|
||||
{{ form.name }}
|
||||
</div>
|
||||
<div class="f-bbox">
|
||||
{%- call standard_button("back", url_for(".circles"), class="secondary") -%}
|
||||
{% trans %}Back{% endtrans %}
|
||||
{%- endcall -%}
|
||||
{%- call form_button("done", form.action_save, class="primary") %}{% endcall -%}
|
||||
</div>
|
||||
<h3 class="form-title">{% trans %}Delete circle{% endtrans %}</h3>
|
||||
<p class="form-desc">{% trans %}Deleting a circle does not delete any users in the circle.{% endtrans %}</p>
|
||||
<div class="f-bbox">
|
||||
{%- call form_button("done", form.action_delete, class="secondary danger") %}{% endcall -%}
|
||||
</div>
|
||||
</div>
|
||||
<h2>{% trans %}Circle members{% endtrans %}</h2>
|
||||
<div class="el-2 elevated"><table>
|
||||
<thead>
|
||||
<th>Login name</th>
|
||||
<th class="collapsible">Display name</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- for member in circle_members -%}
|
||||
<tr>
|
||||
<td>{{ member.localpart }}</td>
|
||||
<td class="collapsible">{% call value_or_hint(member.display_name) %}{% endcall %}</td>
|
||||
<td class="nowrap">
|
||||
{%- call custom_form_button("remove", form.action_remove_user.name, member.localpart, class="primary danger", slim=True) -%}
|
||||
{% trans username=member.localpart %}Remove user {{ username }} from circle{% endtrans %}
|
||||
{%- endcall -%}
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
</tbody>
|
||||
</table></div>
|
||||
</form>
|
||||
<h3>{% trans %}Invite more members{% endtrans %}</h3>
|
||||
{%- include "admin_create_invite_form.html" -%}
|
||||
{% endblock %}
|
||||
@@ -10,12 +10,34 @@
|
||||
{{ form.csrf_token }}
|
||||
<div class="form layout-expanded">
|
||||
<dl>
|
||||
<dt>{% trans %}Created{% endtrans %}</dt>
|
||||
<dd>{{ invite.created_at | format_date }}</dd>
|
||||
<dt>{% trans %}Valid until{% endtrans %}</dt>
|
||||
<dd>{{ invite.expires | format_date }}</dd>
|
||||
<dt>{% trans %}Link{% endtrans %}</dt>
|
||||
<dd>{% call showuri(invite.landing_page) %}{% endcall %}</dd>
|
||||
<dt>{% trans %}Reusability{% endtrans %}</dt>
|
||||
<dd>{% if invite.reusable %}{% trans %}This invitation link can be used arbitrarily often, until it expires, is revoked or a server-wide user limit is reached.{% endtrans %}{% else %}{% trans %}This invitation link can only be used once and is then depleted.{% endtrans %}{% endif %}</dd>
|
||||
{%- set ngroups = invite.group_ids | length -%}
|
||||
{%- if ngroups > 1 -%}
|
||||
{#- not supported via the web UI, but we should still display it properly -#}
|
||||
<dt>{% trans %}Circles{% endtrans %}</dt>
|
||||
<dd><p>{% trans %}Users joining via this invitation will be added to the following circles:{% endtrans %}</p><ul>
|
||||
{%- for group_id in invite.group_ids -%}
|
||||
<li>{{ circle_map[group_id] | circle_name }}</li>
|
||||
{%- endfor -%}
|
||||
</ul></dd>
|
||||
{%- else -%}
|
||||
<dt>{% trans %}Circle{% endtrans %}</dt>
|
||||
<dd>
|
||||
{%- if ngroups == 1 -%}
|
||||
{%- set group_id = invite.group_ids[0] -%}
|
||||
<a href="{{ url_for(".edit_circle", id_=group_id) }}">{{ circle_map[invite.group_ids[0]] | circle_name }}</a>
|
||||
{%- else -%}
|
||||
<em>{% trans %}The user will not be added to any circle and will have no contacts.{% endtrans %}</em>
|
||||
{%- endif -%}
|
||||
</dd>
|
||||
{%- endif -%}
|
||||
<dt>{% trans %}Created{% endtrans %}</dt>
|
||||
<dd>{{ invite.created_at | format_date }}</dd>
|
||||
</dl>
|
||||
<div class="f-bbox">
|
||||
{%- call form_button("remove_link", form.action_revoke, class="secondary danger") %}{% endcall -%}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<h2>{% trans %}Manage users{% endtrans %}</h2>
|
||||
<p>{% trans %}Modify administrative user information or delete users.{% endtrans %}</p>
|
||||
</a>
|
||||
<a class="card" href="{{ url_for('.circles') }}">
|
||||
<h2>{% trans %}Manage circles{% endtrans %}</h2>
|
||||
</a>
|
||||
<a class="card" href="{{ url_for('.invitations') }}">
|
||||
<h2>{% trans %}Manage invitations{% endtrans %}</h2>
|
||||
<p>{% trans %}Create, revoke or view invitations.{% endtrans %}</p>
|
||||
|
||||
@@ -6,33 +6,40 @@
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Manage invitations{% endtrans %}</h1>
|
||||
<form method="POST">{{ form.csrf_token }}
|
||||
<div class="form layout-expanded">
|
||||
<h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
|
||||
<p class="form-descr weak">{% trans %}Create a new invitation link to invite more users to your Snikket instance by clicking the button below.{% endtrans %}</p>
|
||||
<div class="f-bbox">
|
||||
{%- call form_button("create_link", form.action_create_invite, class="primary") %}{% endcall -%}
|
||||
</div>
|
||||
</div>
|
||||
{%- include "admin_create_invite_form.html" -%}
|
||||
<h2>{% trans %}Pending invitations{% endtrans %}</h2>
|
||||
{% if invites %}
|
||||
<table>
|
||||
<col/>
|
||||
<form method="POST">
|
||||
{{- form.csrf_token -}}
|
||||
<div class="elevated el-2"><table>
|
||||
<col/>
|
||||
<col class="collapsible"/>
|
||||
<col class="collapsible"/>
|
||||
<col/>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Created{% endtrans %}</th>
|
||||
<th>{% trans %}Valid until{% endtrans %}</th>
|
||||
<th class="collapsible">{% trans %}Reusable{% endtrans %}</th>
|
||||
<th class="collapsible">{% trans %}Circle{% endtrans %}</th>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for invite in invites %}
|
||||
<tr>
|
||||
<td>{{ invite.created_at | format_date }}</td>
|
||||
<td>{{ (invite.expires - now) | format_timedelta(add_direction=True) }}</td>
|
||||
<td style="white-space: nowrap;">
|
||||
<td class="collapsible">{% if invite.reusable %}{% trans %}Yes{% endtrans %}{% else %}{% trans
|
||||
%}No{% endtrans %}{% endif %}</td>
|
||||
<td class="collapsible">
|
||||
{#- -#}
|
||||
<ul class="inline">
|
||||
{%- for group_id in invite.group_ids -%}
|
||||
<li>{{ circle_map[group_id] | circle_name }}</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
{#- -#}
|
||||
</td>
|
||||
<td class="nowrap">
|
||||
{%- call action_button("more", url_for(".edit_invite", id_=invite.id_), class="secondary") -%}
|
||||
{% trans %}Show invite details{% endtrans %}
|
||||
{%- endcall -%}
|
||||
@@ -46,9 +53,8 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</table></form></div>
|
||||
{% else %}
|
||||
<p>{% trans %}Currently, there are no pending invitations.{% endtrans %}</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% from "library.j2" import icon %}
|
||||
{% macro value_or_hint(v, caller=None) %}
|
||||
{%- if v is not none -%}
|
||||
{{- v -}}
|
||||
{%- else -%}
|
||||
—
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
{% from "library.j2" import action_button, value_or_hint %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Manage users{% endtrans %}</h1>
|
||||
<table>
|
||||
<div class="elevated el-2"><table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Login name{% endtrans %}</th>
|
||||
@@ -27,11 +20,12 @@
|
||||
<td class="collapsible">{% call value_or_hint(user.email) %}{% endcall %}</td>
|
||||
<td class="collapsible">{% call value_or_hint(user.phone) %}{% endcall %}</td>
|
||||
<td>
|
||||
{#- -#}<a class="button secondary btn-delete" href="{{ url_for(".delete_user", localpart=user.localpart) }}">{% call icon("remove") %}{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}{% endcall %}</a>
|
||||
{#- -#}
|
||||
{%- call action_button("remove", url_for(".delete_user", localpart=user.localpart), class="secondary") -%}
|
||||
{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}
|
||||
{%- endcall -%}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</table></div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -72,3 +72,28 @@
|
||||
{% endif -%}
|
||||
</a>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro render_errors(field, caller=None) -%}
|
||||
{%- if field.errors -%}
|
||||
<div class="box warning">{#- -#}
|
||||
<header>{% trans %}Invalid input{% endtrans %}</header>
|
||||
{%- if field.errors | length == 1 -%}
|
||||
<p>{{ field.errors[0] }}.</p>
|
||||
{%- else -%}
|
||||
<ul>
|
||||
{%- for error in field.errors -%}
|
||||
<li>{{ error }}</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro value_or_hint(v, caller=None) %}
|
||||
{%- if v is not none -%}
|
||||
{{- v -}}
|
||||
{%- else -%}
|
||||
—
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "library.j2" import box, icon %}
|
||||
{% from "library.j2" import box, form_button %}
|
||||
{% set body_id = "login" %}
|
||||
{% block head_lead %}
|
||||
<title>{{ _("Snikket Login") }}</title>
|
||||
@@ -28,7 +28,7 @@
|
||||
{{ form.password(placeholder=form.password.label.text) }}
|
||||
</div>
|
||||
<div class="f-bbox">
|
||||
<button type="submit" class="primary">{% call icon("login") %}{% endcall %}{{ _("Log in") }}</button>
|
||||
{%- call form_button("login", form.action_signin, class="primary") -%}{% endcall -%}
|
||||
</div>
|
||||
</from>
|
||||
</div></main></div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "app.html" %}
|
||||
{% from "library.j2" import icon %}
|
||||
{% from "library.j2" import standard_button, form_button %}
|
||||
{% block head_lead %}
|
||||
<title>Snikket Web Portal</title>
|
||||
{% endblock %}
|
||||
@@ -9,11 +9,10 @@
|
||||
<p class="form-desc">{% trans %}Click below to log yourself out of the web portal. This does not affect any other connected devices.{% endtrans %}</p>
|
||||
{{ form.csrf_token }}
|
||||
<div class="f-bbox">
|
||||
{#- -#}
|
||||
<a href="{{ url_for('user.index') }}" class="button secondary">{% call icon("back") %}{% endcall %}{% trans %}Back{% endtrans %}</a>
|
||||
{#- -#}
|
||||
<button type="submit" class="primary">{% call icon("logout") %}{% endcall %}{% trans %}Sign out{% endtrans %}</button>
|
||||
{#- -#}
|
||||
{%- call standard_button("back", url_for("user.index"), class="secondary") -%}
|
||||
{% trans %}Back{% endtrans %}
|
||||
{%- endcall -%}
|
||||
{%- call form_button("logout", form.action_signout, class="primary") %}{% endcall -%}
|
||||
</div>
|
||||
</form></div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SnikketWeb 0.1.0\n"
|
||||
"Report-Msgid-Bugs-To: jonas@zombofant.net\n"
|
||||
"POT-Creation-Date: 2021-01-17 20:13+0100\n"
|
||||
"POT-Creation-Date: 2021-01-21 16:54+0100\n"
|
||||
"PO-Revision-Date: 2020-03-07 16:32+0100\n"
|
||||
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
|
||||
"Language: de\n"
|
||||
@@ -18,18 +18,74 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.0\n"
|
||||
|
||||
#: snikket_web/admin.py:44
|
||||
#: snikket_web/admin.py:54
|
||||
msgid "Delete user permanently"
|
||||
msgstr "Benutzer endgültig löschen"
|
||||
|
||||
#: snikket_web/admin.py:71
|
||||
#: snikket_web/admin.py:83
|
||||
msgid "Invite to circle"
|
||||
msgstr "In Gemeinschaft einladen"
|
||||
|
||||
#: snikket_web/admin.py:89
|
||||
msgid "At least one circle must be selected"
|
||||
msgstr "Mindestens eine Gemeinschaft muss ausgewählt sein"
|
||||
|
||||
#: snikket_web/admin.py:94
|
||||
msgid "Valid for"
|
||||
msgstr "Gültig für"
|
||||
|
||||
#: snikket_web/admin.py:96
|
||||
msgid "One hour"
|
||||
msgstr "Eine Stunde"
|
||||
|
||||
#: snikket_web/admin.py:97
|
||||
msgid "Twelve hours"
|
||||
msgstr "Zwölf Stunden"
|
||||
|
||||
#: snikket_web/admin.py:98
|
||||
msgid "One day"
|
||||
msgstr "Ein Tag"
|
||||
|
||||
#: snikket_web/admin.py:99
|
||||
msgid "One week"
|
||||
msgstr "Eine Woche"
|
||||
|
||||
#: snikket_web/admin.py:100
|
||||
msgid "Four weeks"
|
||||
msgstr "Vier Wochen"
|
||||
|
||||
#: snikket_web/admin.py:106
|
||||
msgid "Allow multiple uses"
|
||||
msgstr "Mehrfach verwendbar"
|
||||
|
||||
#: snikket_web/admin.py:110
|
||||
msgid "New invitation link"
|
||||
msgstr "Neuer Einladungslink"
|
||||
|
||||
#: snikket_web/admin.py:104
|
||||
#: snikket_web/admin.py:170
|
||||
msgid "Revoke"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: snikket_web/admin.py:225 snikket_web/admin.py:272
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: snikket_web/admin.py:229 snikket_web/templates/admin_circles.html:42
|
||||
msgid "Create circle"
|
||||
msgstr "Gemeinschaft gründen"
|
||||
|
||||
#: snikket_web/admin.py:276 snikket_web/user.py:73
|
||||
msgid "Apply"
|
||||
msgstr "Übernehmen"
|
||||
|
||||
#: snikket_web/admin.py:280
|
||||
msgid "Delete circle permanently"
|
||||
msgstr "Gemeinschaft endgültig löschen"
|
||||
|
||||
#: snikket_web/infra.py:39
|
||||
msgid "Main"
|
||||
msgstr "Kern"
|
||||
|
||||
#: snikket_web/main.py:33
|
||||
msgid "Address"
|
||||
msgstr "Adresse"
|
||||
@@ -38,7 +94,11 @@ msgstr "Adresse"
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: snikket_web/main.py:60
|
||||
#: snikket_web/main.py:43
|
||||
msgid "Sign in"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: snikket_web/main.py:64
|
||||
msgid "Invalid user name or password."
|
||||
msgstr "Benutzername oder Passwort falsch."
|
||||
|
||||
@@ -58,38 +118,103 @@ msgstr "Neues Passwort (Bestätigung)"
|
||||
msgid "The new passwords must match."
|
||||
msgstr "Die neuen Passwörter müssen übereinstimmen."
|
||||
|
||||
#: snikket_web/user.py:50
|
||||
#: snikket_web/user.py:47
|
||||
msgid "Sign out"
|
||||
msgstr "Abmelden"
|
||||
|
||||
#: snikket_web/user.py:52
|
||||
msgid "Nobody"
|
||||
msgstr "Niemand"
|
||||
|
||||
#: snikket_web/user.py:51
|
||||
#: snikket_web/user.py:53
|
||||
msgid "Friends only"
|
||||
msgstr "Nur Freunde"
|
||||
|
||||
#: snikket_web/user.py:52
|
||||
#: snikket_web/user.py:54
|
||||
msgid "Everyone"
|
||||
msgstr "Jeder"
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:12
|
||||
#: snikket_web/templates/admin_delete_user.html:16
|
||||
#: snikket_web/templates/admin_users.html:15 snikket_web/user.py:58
|
||||
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:60
|
||||
msgid "Display name"
|
||||
msgstr "Anzeigename"
|
||||
|
||||
#: snikket_web/user.py:62
|
||||
#: snikket_web/user.py:64
|
||||
msgid "Avatar"
|
||||
msgstr "Bild"
|
||||
|
||||
#: snikket_web/user.py:66
|
||||
#: snikket_web/user.py:68
|
||||
msgid "Profile visibility"
|
||||
msgstr "Profilsichtbarkeit"
|
||||
|
||||
#: snikket_web/user.py:91
|
||||
#: snikket_web/user.py:97
|
||||
msgid "Incorrect password"
|
||||
msgstr "Ungültiges Passwort"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:4
|
||||
#: snikket_web/templates/admin_home.html:11
|
||||
msgid "Manage circles"
|
||||
msgstr "Gemeinschaften verwalten"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:11
|
||||
msgid "Circle name"
|
||||
msgstr "Name"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:12
|
||||
msgid "Members"
|
||||
msgstr "Mitglieder"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:13
|
||||
#: snikket_web/templates/admin_invites.html:24
|
||||
#: snikket_web/templates/admin_users.html:12
|
||||
msgid "Actions"
|
||||
msgstr "Aktionen"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:23
|
||||
#, python-format
|
||||
msgid "Create invitation to circle %(circle_name)s"
|
||||
msgstr "Einladung in die %(circle_name)s Gemeinschaft erzeugen"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:26
|
||||
#, python-format
|
||||
msgid "Show details of circle %(circle_name)s"
|
||||
msgstr "Details der %(circle_name)s Gemeinschaft anzeigen"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:35
|
||||
msgid "No circles"
|
||||
msgstr "Keine Gemeinschaften"
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:36
|
||||
msgid ""
|
||||
"Currently, there are no circles on this instance. Use the form below to "
|
||||
"create one."
|
||||
msgstr ""
|
||||
"Es gibt derzeit keine Gemeinschaften auf dieser Instanz. Unten kannst du "
|
||||
"eine anlegen."
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:39
|
||||
msgid "New circle"
|
||||
msgstr "Neue Gemeinschaft"
|
||||
|
||||
#: snikket_web/templates/admin_create_invite.html:3
|
||||
msgid "Create invitation"
|
||||
msgstr "Gemeinschaft gründen"
|
||||
|
||||
#: snikket_web/templates/admin_create_invite_form.html:5
|
||||
msgid "Create new invitation"
|
||||
msgstr "Neue Einladung erzeugen"
|
||||
|
||||
#: snikket_web/templates/admin_create_invite_form.html:6
|
||||
msgid ""
|
||||
"Create a new invitation link to invite more users to your Snikket "
|
||||
"instance by clicking the button below."
|
||||
msgstr ""
|
||||
"Erzeuge eine neue Einladung um mehr Benutzer auf deine Snikket-Instanz "
|
||||
"einzuladen indem du den folgenden Button klickst."
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:4
|
||||
#: snikket_web/templates/admin_users.html:29
|
||||
#: snikket_web/templates/admin_users.html:24
|
||||
#, python-format
|
||||
msgid "Delete user %(user_name)s"
|
||||
msgstr "Benutzer %(user_name)s löschen"
|
||||
@@ -104,12 +229,12 @@ msgid "Are you sure you want to delete the following user?"
|
||||
msgstr "Bist du sicher dass du den folgenden Benutzer löschen willst?"
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:10
|
||||
#: snikket_web/templates/admin_users.html:14
|
||||
#: snikket_web/templates/admin_users.html:8
|
||||
msgid "Login name"
|
||||
msgstr "Anmeldename"
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:14
|
||||
#: snikket_web/templates/admin_users.html:16
|
||||
#: snikket_web/templates/admin_users.html:10
|
||||
msgid "Email address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
|
||||
@@ -117,30 +242,100 @@ msgstr "E-Mail-Adresse"
|
||||
msgid "Danger"
|
||||
msgstr "Gefahr"
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:23
|
||||
#: snikket_web/templates/admin_edit_circle.html:14
|
||||
#: snikket_web/templates/admin_edit_invite.html:45
|
||||
#: snikket_web/templates/user_logout.html:13
|
||||
#: snikket_web/templates/user_passwd.html:40
|
||||
#: snikket_web/templates/user_profile.html:25
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:4
|
||||
#, python-format
|
||||
msgid "Edit circle %(circle_name)s"
|
||||
msgstr "Gemeinschaft %(circle_name)s bearbeiten"
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:6
|
||||
msgid "Circle information"
|
||||
msgstr "Gemeinschaftsinformationen"
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:18
|
||||
msgid "Delete circle"
|
||||
msgstr "Gemeinschaft löschen"
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:19
|
||||
msgid "Deleting a circle does not delete any users in the circle."
|
||||
msgstr ""
|
||||
"Wenn eine Gemeinschaft gelöscht wird, werden die Benutzer die zu dieser "
|
||||
"Gemeinschaft gehören nicht gelöscht."
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:24
|
||||
msgid "Circle members"
|
||||
msgstr "Mitglieder der Gemeinschaft"
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:38
|
||||
#, python-format
|
||||
msgid "Remove user %(username)s from circle"
|
||||
msgstr "Benutzer %(username)s aus der Gemeinschaft entfernen"
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:46
|
||||
msgid "Invite more members"
|
||||
msgstr "Mehr Mitglieder einladen"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:8
|
||||
msgid "View invitation"
|
||||
msgstr "Einladung anzeigen"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:13
|
||||
#: snikket_web/templates/admin_invites.html:24
|
||||
msgid "Created"
|
||||
msgstr "Erzeugt"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:15
|
||||
#: snikket_web/templates/admin_invites.html:25
|
||||
#: snikket_web/templates/admin_invites.html:21
|
||||
msgid "Valid until"
|
||||
msgstr "Gültig bis"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:17
|
||||
#: snikket_web/templates/admin_edit_invite.html:15
|
||||
msgid "Link"
|
||||
msgstr "Link"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:24
|
||||
#: snikket_web/templates/user_logout.html:12
|
||||
#: snikket_web/templates/user_passwd.html:39
|
||||
#: snikket_web/templates/user_profile.html:24
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
#: snikket_web/templates/admin_edit_invite.html:17
|
||||
msgid "Reusability"
|
||||
msgstr "Wiederverwendbarkeit"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:18
|
||||
msgid ""
|
||||
"This invitation link can be used arbitrarily often, until it expires, is "
|
||||
"revoked or a server-wide user limit is reached."
|
||||
msgstr ""
|
||||
"Diese Einladung kann beliebig oft verwendet werden, bis sie abläuft, "
|
||||
"gelöscht wird oder ein instanzweites Limit erreicht ist."
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:18
|
||||
msgid "This invitation link can only be used once and is then depleted."
|
||||
msgstr "Diese Einladung kann nur einmal verwendet werden und ist dann ungültig."
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:22
|
||||
msgid "Circles"
|
||||
msgstr "Gemeinschaften"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:23
|
||||
msgid "Users joining via this invitation will be added to the following circles:"
|
||||
msgstr ""
|
||||
"Benutzer die über diese Einladung zur Instanz stoßen werden zu den "
|
||||
"folgenden Gemeinschaften hinzugefügt:"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:29
|
||||
#: snikket_web/templates/admin_invites.html:23
|
||||
msgid "Circle"
|
||||
msgstr "Gemeinschaft"
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:35
|
||||
msgid "The user will not be added to any circle and will have no contacts."
|
||||
msgstr ""
|
||||
"Benutzer werden zu keiner Gemeinschaft hinzugefügt und werden zu Beginn "
|
||||
"keine Kontakte haben."
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:39
|
||||
msgid "Created"
|
||||
msgstr "Erzeugt"
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:3
|
||||
#, python-format
|
||||
@@ -178,7 +373,7 @@ msgid "At your service, %(user_name)s."
|
||||
msgstr "Zu deinen Diensten, %(user_name)s."
|
||||
|
||||
#: snikket_web/templates/admin_home.html:7
|
||||
#: snikket_web/templates/admin_users.html:10
|
||||
#: snikket_web/templates/admin_users.html:4
|
||||
msgid "Manage users"
|
||||
msgstr "Benutzer verwalten"
|
||||
|
||||
@@ -186,61 +381,56 @@ msgstr "Benutzer verwalten"
|
||||
msgid "Modify administrative user information or delete users."
|
||||
msgstr "Benutzerinformationen verändern oder Benutzer löschen."
|
||||
|
||||
#: snikket_web/templates/admin_home.html:11
|
||||
#: snikket_web/templates/admin_invites.html:7
|
||||
#: snikket_web/templates/admin_home.html:14
|
||||
#: snikket_web/templates/admin_invites.html:8
|
||||
msgid "Manage invitations"
|
||||
msgstr "Einladungen verwalten"
|
||||
|
||||
#: snikket_web/templates/admin_home.html:12
|
||||
#: snikket_web/templates/admin_home.html:15
|
||||
msgid "Create, revoke or view invitations."
|
||||
msgstr "Einladungen erzeugen, löschen oder anzeigen."
|
||||
|
||||
#: snikket_web/templates/admin_home.html:15
|
||||
#: snikket_web/templates/admin_home.html:18
|
||||
msgid "Back to the main view"
|
||||
msgstr "Zurück zur Hauptseite"
|
||||
|
||||
#: snikket_web/templates/admin_home.html:16
|
||||
#: snikket_web/templates/admin_home.html:19
|
||||
msgid "Go back to your user’s web portal page."
|
||||
msgstr "Zurück zur Startseite deines Benutzers."
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:10
|
||||
msgid "Create new invitation"
|
||||
msgstr "Neue Einladung erzeugen"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:11
|
||||
msgid ""
|
||||
"Create a new invitation link to invite more users to your Snikket "
|
||||
"instance by clicking the button below."
|
||||
msgstr ""
|
||||
"Erzeuge eine neue Einladung um mehr Benutzer auf deine Snikket-Instanz "
|
||||
"einzuladen indem du den folgenden Button klickst."
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:16
|
||||
msgid "Pending invitations"
|
||||
msgstr "Ausstehende Einladungen"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:26
|
||||
#: snikket_web/templates/admin_users.html:18
|
||||
msgid "Actions"
|
||||
msgstr "Aktionen"
|
||||
#: snikket_web/templates/admin_invites.html:22
|
||||
msgid "Reusable"
|
||||
msgstr "Mehrfach"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:36
|
||||
#: snikket_web/templates/admin_invites.html:31
|
||||
msgid "Yes"
|
||||
msgstr "Ja"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:31
|
||||
msgid "No"
|
||||
msgstr "Nein"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:44
|
||||
msgid "Show invite details"
|
||||
msgstr "Einladungsdetails anzeigen"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:38
|
||||
#: snikket_web/templates/admin_invites.html:47
|
||||
msgid "Copy invite link to clipboard"
|
||||
msgstr "Einladungslink kopieren"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:40
|
||||
#: snikket_web/templates/admin_invites.html:50
|
||||
msgid "Delete invitation"
|
||||
msgstr "Einladung löschen"
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:48
|
||||
#: snikket_web/templates/admin_invites.html:58
|
||||
msgid "Currently, there are no pending invitations."
|
||||
msgstr "Derzeit gibt es keine ausstehenden Einladungen."
|
||||
|
||||
#: snikket_web/templates/admin_users.html:17
|
||||
#: snikket_web/templates/admin_users.html:11
|
||||
msgid "Phone number"
|
||||
msgstr "Telefonnummer"
|
||||
|
||||
@@ -253,14 +443,21 @@ msgstr "Snikket Webportal"
|
||||
msgid "A <a href=\"%(about_url)s\">Snikket</a> server"
|
||||
msgstr "Ein <a href=\"%(about_url)s\">Snikket</a>-Server"
|
||||
|
||||
#: snikket_web/templates/library.j2:13
|
||||
#: snikket_web/templates/copy-snippet.html:106
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "Kopiert"
|
||||
|
||||
#: snikket_web/templates/copy-snippet.html:109
|
||||
msgid "Copy operation failed"
|
||||
msgstr "Kopieren fehlgeschlagen"
|
||||
|
||||
#: snikket_web/templates/library.j2:18
|
||||
msgid "Copy link"
|
||||
msgstr "Link kopieren"
|
||||
|
||||
#: snikket_web/templates/library.j2:15
|
||||
#, python-format
|
||||
msgid "Copy "%(content)s" to clipboard"
|
||||
msgstr ""%(content)s" kopieren"
|
||||
#: snikket_web/templates/library.j2:79
|
||||
msgid "Invalid input"
|
||||
msgstr "Ungültige Eingabe"
|
||||
|
||||
#: snikket_web/templates/login.html:5
|
||||
msgid "Snikket Login"
|
||||
@@ -274,10 +471,6 @@ msgstr "Gib deine Snikket-Adresse und -Passwort ein um dein Konto zu verwalten."
|
||||
msgid "Login failed"
|
||||
msgstr "Anmeldung fehlgeschlagen"
|
||||
|
||||
#: snikket_web/templates/login.html:31
|
||||
msgid "Log in"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: snikket_web/templates/user_home.html:3
|
||||
msgid "Welcome!"
|
||||
msgstr "Willkommen!"
|
||||
@@ -300,7 +493,7 @@ msgstr ""
|
||||
"einsehen kann."
|
||||
|
||||
#: snikket_web/templates/user_home.html:11
|
||||
#: snikket_web/templates/user_passwd.html:40
|
||||
#: snikket_web/templates/user_passwd.html:42
|
||||
msgid "Change password"
|
||||
msgstr "Passwort ändern"
|
||||
|
||||
@@ -322,11 +515,11 @@ msgstr ""
|
||||
"Verlasse das Snikket Web-Portal, ohne dass deine anderen Geräte "
|
||||
"beeinträchtigt werden."
|
||||
|
||||
#: snikket_web/templates/user_logout.html:7
|
||||
#: snikket_web/templates/user_logout.html:8
|
||||
msgid "Sign out of the Snikket Web Portal"
|
||||
msgstr "Aus dem Webportal abmelden"
|
||||
|
||||
#: snikket_web/templates/user_logout.html:8
|
||||
#: snikket_web/templates/user_logout.html:9
|
||||
msgid ""
|
||||
"Click below to log yourself out of the web portal. This does not affect "
|
||||
"any other connected devices."
|
||||
@@ -334,15 +527,11 @@ msgstr ""
|
||||
"Klicke unten um dich aus dem Webportal abzumelden. Dies betrifft keine "
|
||||
"anderen Geräte von dir."
|
||||
|
||||
#: snikket_web/templates/user_logout.html:14
|
||||
msgid "Sign out"
|
||||
msgstr "Abmelden"
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:7
|
||||
#: snikket_web/templates/user_passwd.html:8
|
||||
msgid "Change your password"
|
||||
msgstr "Ändere dein Passwort"
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:8
|
||||
#: snikket_web/templates/user_passwd.html:9
|
||||
msgid ""
|
||||
"To change your password, you need to provide the current password as well"
|
||||
" as the new one. To reduce the chance of typos, we ask for your new "
|
||||
@@ -352,15 +541,15 @@ msgstr ""
|
||||
"alsauch dein neues Passwort angeben. Damit Tippfehler dich nicht "
|
||||
"aussperren bitten wir dich, dein neues Passwort zweimal einzutippen."
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:12
|
||||
#: snikket_web/templates/user_passwd.html:13
|
||||
msgid "Password change failed"
|
||||
msgstr "Passwortänderung fehlgeschlagen"
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:35
|
||||
#: snikket_web/templates/user_passwd.html:36
|
||||
msgid "Warning"
|
||||
msgstr "Warnung"
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:36
|
||||
#: snikket_web/templates/user_passwd.html:37
|
||||
msgid ""
|
||||
"After changing your password, you will have to enter the new password on "
|
||||
"all of your devices."
|
||||
@@ -368,15 +557,15 @@ msgstr ""
|
||||
"Nachdem du das Passwort geändert hast, musst du das neue Passwort auf "
|
||||
"allen Geräten manuell eintragen."
|
||||
|
||||
#: snikket_web/templates/user_profile.html:7
|
||||
#: snikket_web/templates/user_profile.html:8
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: snikket_web/templates/user_profile.html:17
|
||||
#: snikket_web/templates/user_profile.html:18
|
||||
msgid "Visibility"
|
||||
msgstr "Sichtbarkeit"
|
||||
|
||||
#: snikket_web/templates/user_profile.html:18
|
||||
#: snikket_web/templates/user_profile.html:19
|
||||
msgid ""
|
||||
"This section allows you to control who can see your profile information, "
|
||||
"like avatar and nickname."
|
||||
@@ -384,7 +573,3 @@ msgstr ""
|
||||
"Hier kannst du einstellen, wer deine Profilinformationen, wie Bild oder "
|
||||
"Anzeigename einsehen kann."
|
||||
|
||||
#: snikket_web/templates/user_profile.html:24
|
||||
msgid "Apply"
|
||||
msgstr "Übernehmen"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-01-17 20:13+0100\n"
|
||||
"POT-Creation-Date: 2021-01-21 16:54+0100\n"
|
||||
"PO-Revision-Date: 2020-03-07 16:50+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
@@ -18,18 +18,74 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.0\n"
|
||||
|
||||
#: snikket_web/admin.py:44
|
||||
#: snikket_web/admin.py:54
|
||||
msgid "Delete user permanently"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:71
|
||||
#: snikket_web/admin.py:83
|
||||
msgid "Invite to circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:89
|
||||
msgid "At least one circle must be selected"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:94
|
||||
msgid "Valid for"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:96
|
||||
msgid "One hour"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:97
|
||||
msgid "Twelve hours"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:98
|
||||
msgid "One day"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:99
|
||||
msgid "One week"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:100
|
||||
msgid "Four weeks"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:106
|
||||
msgid "Allow multiple uses"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:110
|
||||
msgid "New invitation link"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:104
|
||||
#: snikket_web/admin.py:170
|
||||
msgid "Revoke"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:225 snikket_web/admin.py:272
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:229 snikket_web/templates/admin_circles.html:42
|
||||
msgid "Create circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:276 snikket_web/user.py:73
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/admin.py:280
|
||||
msgid "Delete circle permanently"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/infra.py:39
|
||||
msgid "Main"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:33
|
||||
msgid "Address"
|
||||
msgstr ""
|
||||
@@ -38,7 +94,11 @@ msgstr ""
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:60
|
||||
#: snikket_web/main.py:43
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/main.py:64
|
||||
#, fuzzy
|
||||
msgid "Invalid user name or password."
|
||||
msgstr ""
|
||||
@@ -59,38 +119,100 @@ msgstr ""
|
||||
msgid "The new passwords must match."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:50
|
||||
msgid "Nobody"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:51
|
||||
msgid "Friends only"
|
||||
#: snikket_web/user.py:47
|
||||
#, fuzzy
|
||||
msgid "Sign out"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:52
|
||||
msgid "Nobody"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:53
|
||||
msgid "Friends only"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:54
|
||||
msgid "Everyone"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:12
|
||||
#: snikket_web/templates/admin_delete_user.html:16
|
||||
#: snikket_web/templates/admin_users.html:15 snikket_web/user.py:58
|
||||
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:60
|
||||
msgid "Display name"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:62
|
||||
#: snikket_web/user.py:64
|
||||
msgid "Avatar"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:66
|
||||
#: snikket_web/user.py:68
|
||||
msgid "Profile visibility"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/user.py:91
|
||||
#: snikket_web/user.py:97
|
||||
msgid "Incorrect password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:4
|
||||
#: snikket_web/templates/admin_home.html:11
|
||||
msgid "Manage circles"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:11
|
||||
msgid "Circle name"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:12
|
||||
msgid "Members"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:13
|
||||
#: snikket_web/templates/admin_invites.html:24
|
||||
#: snikket_web/templates/admin_users.html:12
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:23
|
||||
#, python-format
|
||||
msgid "Create invitation to circle %(circle_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:26
|
||||
#, python-format
|
||||
msgid "Show details of circle %(circle_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:35
|
||||
msgid "No circles"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:36
|
||||
msgid ""
|
||||
"Currently, there are no circles on this instance. Use the form below to "
|
||||
"create one."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_circles.html:39
|
||||
msgid "New circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_create_invite.html:3
|
||||
msgid "Create invitation"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_create_invite_form.html:5
|
||||
msgid "Create new invitation"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_create_invite_form.html:6
|
||||
msgid ""
|
||||
"Create a new invitation link to invite more users to your Snikket "
|
||||
"instance by clicking the button below."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:4
|
||||
#: snikket_web/templates/admin_users.html:29
|
||||
#: snikket_web/templates/admin_users.html:24
|
||||
#, python-format
|
||||
msgid "Delete user %(user_name)s"
|
||||
msgstr ""
|
||||
@@ -105,12 +227,12 @@ msgid "Are you sure you want to delete the following user?"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:10
|
||||
#: snikket_web/templates/admin_users.html:14
|
||||
#: snikket_web/templates/admin_users.html:8
|
||||
msgid "Login name"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:14
|
||||
#: snikket_web/templates/admin_users.html:16
|
||||
#: snikket_web/templates/admin_users.html:10
|
||||
#, fuzzy
|
||||
msgid "Email address"
|
||||
msgstr ""
|
||||
@@ -119,29 +241,91 @@ msgstr ""
|
||||
msgid "Danger"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_delete_user.html:23
|
||||
#: snikket_web/templates/admin_edit_circle.html:14
|
||||
#: snikket_web/templates/admin_edit_invite.html:45
|
||||
#: snikket_web/templates/user_logout.html:13
|
||||
#: snikket_web/templates/user_passwd.html:40
|
||||
#: snikket_web/templates/user_profile.html:25
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:4
|
||||
#, python-format
|
||||
msgid "Edit circle %(circle_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:6
|
||||
msgid "Circle information"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:18
|
||||
msgid "Delete circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:19
|
||||
msgid "Deleting a circle does not delete any users in the circle."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:24
|
||||
msgid "Circle members"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:38
|
||||
#, python-format
|
||||
msgid "Remove user %(username)s from circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_circle.html:46
|
||||
msgid "Invite more members"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:8
|
||||
msgid "View invitation"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:13
|
||||
#: snikket_web/templates/admin_invites.html:24
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:15
|
||||
#: snikket_web/templates/admin_invites.html:25
|
||||
#: snikket_web/templates/admin_invites.html:21
|
||||
msgid "Valid until"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:17
|
||||
#: snikket_web/templates/admin_edit_invite.html:15
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:24
|
||||
#: snikket_web/templates/user_logout.html:12
|
||||
#: snikket_web/templates/user_passwd.html:39
|
||||
#: snikket_web/templates/user_profile.html:24
|
||||
msgid "Back"
|
||||
#: snikket_web/templates/admin_edit_invite.html:17
|
||||
msgid "Reusability"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:18
|
||||
msgid ""
|
||||
"This invitation link can be used arbitrarily often, until it expires, is "
|
||||
"revoked or a server-wide user limit is reached."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:18
|
||||
msgid "This invitation link can only be used once and is then depleted."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:22
|
||||
msgid "Circles"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:23
|
||||
msgid "Users joining via this invitation will be added to the following circles:"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:29
|
||||
#: snikket_web/templates/admin_invites.html:23
|
||||
msgid "Circle"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:35
|
||||
msgid "The user will not be added to any circle and will have no contacts."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_invite.html:39
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_edit_user.html:3
|
||||
@@ -176,7 +360,7 @@ msgid "At your service, %(user_name)s."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_home.html:7
|
||||
#: snikket_web/templates/admin_users.html:10
|
||||
#: snikket_web/templates/admin_users.html:4
|
||||
msgid "Manage users"
|
||||
msgstr ""
|
||||
|
||||
@@ -184,59 +368,56 @@ msgstr ""
|
||||
msgid "Modify administrative user information or delete users."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_home.html:11
|
||||
#: snikket_web/templates/admin_invites.html:7
|
||||
#: snikket_web/templates/admin_home.html:14
|
||||
#: snikket_web/templates/admin_invites.html:8
|
||||
msgid "Manage invitations"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_home.html:12
|
||||
#: snikket_web/templates/admin_home.html:15
|
||||
msgid "Create, revoke or view invitations."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_home.html:15
|
||||
#: snikket_web/templates/admin_home.html:18
|
||||
msgid "Back to the main view"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_home.html:16
|
||||
#: snikket_web/templates/admin_home.html:19
|
||||
msgid "Go back to your user’s web portal page."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:10
|
||||
msgid "Create new invitation"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:11
|
||||
msgid ""
|
||||
"Create a new invitation link to invite more users to your Snikket "
|
||||
"instance by clicking the button below."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:16
|
||||
msgid "Pending invitations"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:26
|
||||
#: snikket_web/templates/admin_users.html:18
|
||||
msgid "Actions"
|
||||
#: snikket_web/templates/admin_invites.html:22
|
||||
msgid "Reusable"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:36
|
||||
#: snikket_web/templates/admin_invites.html:31
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:31
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:44
|
||||
msgid "Show invite details"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:38
|
||||
#: snikket_web/templates/admin_invites.html:47
|
||||
msgid "Copy invite link to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:40
|
||||
#: snikket_web/templates/admin_invites.html:50
|
||||
msgid "Delete invitation"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_invites.html:48
|
||||
#: snikket_web/templates/admin_invites.html:58
|
||||
msgid "Currently, there are no pending invitations."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/admin_users.html:17
|
||||
#: snikket_web/templates/admin_users.html:11
|
||||
msgid "Phone number"
|
||||
msgstr ""
|
||||
|
||||
@@ -249,13 +430,20 @@ msgstr ""
|
||||
msgid "A <a href=\"%(about_url)s\">Snikket</a> server"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/library.j2:13
|
||||
#: snikket_web/templates/copy-snippet.html:106
|
||||
msgid "Copied to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/copy-snippet.html:109
|
||||
msgid "Copy operation failed"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/library.j2:18
|
||||
msgid "Copy link"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/library.j2:15
|
||||
#, python-format
|
||||
msgid "Copy "%(content)s" to clipboard"
|
||||
#: snikket_web/templates/library.j2:79
|
||||
msgid "Invalid input"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/login.html:5
|
||||
@@ -270,10 +458,6 @@ msgstr ""
|
||||
msgid "Login failed"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/login.html:31
|
||||
msgid "Log in"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_home.html:3
|
||||
msgid "Welcome!"
|
||||
msgstr ""
|
||||
@@ -294,7 +478,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_home.html:11
|
||||
#: snikket_web/templates/user_passwd.html:40
|
||||
#: snikket_web/templates/user_passwd.html:42
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
|
||||
@@ -314,60 +498,58 @@ msgstr ""
|
||||
msgid "Exit the Snikket Web Portal, without logging out your other devices."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_logout.html:7
|
||||
#: snikket_web/templates/user_logout.html:8
|
||||
msgid "Sign out of the Snikket Web Portal"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_logout.html:8
|
||||
#: snikket_web/templates/user_logout.html:9
|
||||
msgid ""
|
||||
"Click below to log yourself out of the web portal. This does not affect "
|
||||
"any other connected devices."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_logout.html:14
|
||||
#, fuzzy
|
||||
msgid "Sign out"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:7
|
||||
#: snikket_web/templates/user_passwd.html:8
|
||||
msgid "Change your password"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:8
|
||||
#: snikket_web/templates/user_passwd.html:9
|
||||
msgid ""
|
||||
"To change your password, you need to provide the current password as well"
|
||||
" as the new one. To reduce the chance of typos, we ask for your new "
|
||||
"password twice."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:12
|
||||
#: snikket_web/templates/user_passwd.html:13
|
||||
msgid "Password change failed"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:35
|
||||
#: snikket_web/templates/user_passwd.html:36
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_passwd.html:36
|
||||
#: snikket_web/templates/user_passwd.html:37
|
||||
msgid ""
|
||||
"After changing your password, you will have to enter the new password on "
|
||||
"all of your devices."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_profile.html:7
|
||||
#: snikket_web/templates/user_profile.html:8
|
||||
msgid "Profile"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_profile.html:17
|
||||
#: snikket_web/templates/user_profile.html:18
|
||||
msgid "Visibility"
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_profile.html:18
|
||||
#: snikket_web/templates/user_profile.html:19
|
||||
msgid ""
|
||||
"This section allows you to control who can see your profile information, "
|
||||
"like avatar and nickname."
|
||||
msgstr ""
|
||||
|
||||
#: snikket_web/templates/user_profile.html:24
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
#~ msgid "Copy "%(content)s" to clipboard"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Log in"
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ class ChangePasswordForm(flask_wtf.FlaskForm): # type:ignore
|
||||
|
||||
|
||||
class LogoutForm(flask_wtf.FlaskForm): # type:ignore
|
||||
pass
|
||||
action_signout = wtforms.SubmitField(
|
||||
_l("Sign out"),
|
||||
)
|
||||
|
||||
|
||||
_ACCESS_MODEL_CHOICES = [
|
||||
@@ -106,6 +108,9 @@ async def profile() -> typing.Union[str, quart.Response]:
|
||||
form = ProfileForm()
|
||||
if request.method != "POST":
|
||||
user_info = await client.get_user_info()
|
||||
# TODO: find a better way to determine the access model, e.g. by
|
||||
# taking the first access model which is defined in [nickname, avatar,
|
||||
# vcard] or by taking the most open one.-
|
||||
try:
|
||||
profile_access_model = await client.get_nickname_access_model()
|
||||
except quart.exceptions.NotFound:
|
||||
|
||||
Reference in New Issue
Block a user