You've already forked snikket-web-portal
Compare commits
37 Commits
feature/pr
...
fix/remove
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0647ba2601 | ||
|
|
2769036f94 | ||
|
|
c76befad1c | ||
|
|
74ecfb8653 | ||
|
|
55b195cd7f | ||
|
|
46a7d0c37d | ||
|
|
c63b95c6e0 | ||
|
|
6848691141 | ||
|
|
1e83881a24 | ||
|
|
35e6bec328 | ||
|
|
d345f0d98d | ||
|
|
f5ccb7d858 | ||
|
|
f7c8bccfa2 | ||
|
|
e5d06877a4 | ||
|
|
e7ed9dd176 | ||
|
|
6778557db8 | ||
|
|
73f3f25515 | ||
|
|
bd66600d05 | ||
|
|
db363367da | ||
|
|
7ce13b55ac | ||
|
|
d6d4bb5afb | ||
|
|
da52771ebe | ||
|
|
e39b6ca8bb | ||
|
|
14368c5e9a | ||
|
|
2cdcf7f282 | ||
|
|
0f1e76e38c | ||
|
|
ad9af20f12 | ||
|
|
9672cd6870 | ||
|
|
d3a6be7bec | ||
|
|
7a4b56914c | ||
|
|
0f74b1b8f2 | ||
|
|
df78e8a8b0 | ||
|
|
77ccdd5eed | ||
|
|
54b6cad7cd | ||
|
|
fbb618c178 | ||
|
|
bd3d56851b | ||
|
|
c475b83c02 |
28
Dockerfile
28
Dockerfile
@@ -1,28 +1,22 @@
|
|||||||
FROM debian:bullseye-slim AS build
|
FROM debian:bookworm-slim AS build
|
||||||
|
|
||||||
RUN set -eu; \
|
RUN set -eu; \
|
||||||
export DEBIAN_FRONTEND=noninteractive ; \
|
export DEBIAN_FRONTEND=noninteractive ; \
|
||||||
apt-get update ; \
|
apt-get update ; \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
python3 python3-pip python3-setuptools python3-wheel \
|
python3 python3-mypy python3-dotenv python3-toml python3-babel python3-distutils \
|
||||||
libpython3-dev \
|
sassc make;
|
||||||
make build-essential;
|
|
||||||
|
|
||||||
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
|
|
||||||
COPY build-requirements.txt /opt/snikket-web-portal/build-requirements.txt
|
|
||||||
COPY Makefile /opt/snikket-web-portal/Makefile
|
COPY Makefile /opt/snikket-web-portal/Makefile
|
||||||
COPY snikket_web/ /opt/snikket-web-portal/snikket_web
|
COPY snikket_web/ /opt/snikket-web-portal/snikket_web
|
||||||
COPY babel.cfg /opt/snikket-web-portal/babel.cfg
|
COPY babel.cfg /opt/snikket-web-portal/babel.cfg
|
||||||
|
|
||||||
WORKDIR /opt/snikket-web-portal
|
WORKDIR /opt/snikket-web-portal
|
||||||
|
|
||||||
RUN set -eu; \
|
RUN make
|
||||||
pip3 install -r requirements.txt; \
|
|
||||||
pip3 install -r build-requirements.txt; \
|
|
||||||
make;
|
|
||||||
|
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
ARG BUILD_SERIES=dev
|
ARG BUILD_SERIES=dev
|
||||||
ARG BUILD_ID=0
|
ARG BUILD_ID=0
|
||||||
@@ -33,19 +27,19 @@ ENV SNIKKET_WEB_PYENV=/etc/snikket-web-portal/env.py
|
|||||||
|
|
||||||
ENV SNIKKET_WEB_PROSODY_ENDPOINT=http://127.0.0.1:5280/
|
ENV SNIKKET_WEB_PROSODY_ENDPOINT=http://127.0.0.1:5280/
|
||||||
|
|
||||||
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
|
|
||||||
|
|
||||||
WORKDIR /opt/snikket-web-portal
|
WORKDIR /opt/snikket-web-portal
|
||||||
|
|
||||||
RUN set -eu; \
|
RUN set -eu; \
|
||||||
export DEBIAN_FRONTEND=noninteractive ; \
|
export DEBIAN_FRONTEND=noninteractive ; \
|
||||||
apt-get update ; \
|
apt-get update ; \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
python3 python3-pip python3-setuptools python3-wheel build-essential libpython3-dev netcat; \
|
netcat-traditional python3 python3-setuptools python3-pip \
|
||||||
pip3 install -r requirements.txt; \
|
python3-aiohttp python3-email-validator python3-flask-babel \
|
||||||
apt-get remove -y --autoremove build-essential libpython3-dev; \
|
python3-flaskext.wtf python3-hsluv python3-hypercorn \
|
||||||
|
python3-quart python3-typing-extensions python3-wtforms ; \
|
||||||
|
pip3 install --break-system-packages environ-config ; \
|
||||||
|
apt-get remove -y --purge python3-pip python3-setuptools; \
|
||||||
apt-get clean ; rm -rf /var/lib/apt/lists; \
|
apt-get clean ; rm -rf /var/lib/apt/lists; \
|
||||||
pip3 install hypercorn; \
|
|
||||||
rm -rf /root/.cache;
|
rm -rf /root/.cache;
|
||||||
|
|
||||||
HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765}
|
HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765}
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -6,7 +6,7 @@ translation_basepath = snikket_web/translations
|
|||||||
pot_file = $(translation_basepath)/messages.pot
|
pot_file = $(translation_basepath)/messages.pot
|
||||||
|
|
||||||
PYTHON3 ?= python3
|
PYTHON3 ?= python3
|
||||||
SCSSC ?= $(PYTHON3) -m scss --load-path snikket_web/scss/
|
SCSSC ?= sassc --load-path snikket_web/scss/
|
||||||
|
|
||||||
all: build_css compile_translations
|
all: build_css compile_translations
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ build_css: $(generated_css_files)
|
|||||||
|
|
||||||
$(generated_css_files): snikket_web/static/css/%.css: snikket_web/scss/%.scss $(scss_files) $(scss_includes)
|
$(generated_css_files): snikket_web/static/css/%.css: snikket_web/scss/%.scss $(scss_files) $(scss_includes)
|
||||||
mkdir -p snikket_web/static/css/
|
mkdir -p snikket_web/static/css/
|
||||||
$(SCSSC) -o "$@" "$<"
|
$(SCSSC) "$<" "$@"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(generated_css_files)
|
rm -f $(generated_css_files)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
pyscss~=1.3
|
|
||||||
mypy
|
mypy
|
||||||
python-dotenv~=0.15
|
python-dotenv~=0.15
|
||||||
types-toml
|
types-toml
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ if [ -n "${SNIKKET_SITE_NAME:-}" ]; then
|
|||||||
export SNIKKET_WEB_SITE_NAME="$SNIKKET_SITE_NAME"
|
export SNIKKET_WEB_SITE_NAME="$SNIKKET_SITE_NAME"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export SNIKKET_WEB_TOS_URI="${SNIKKET_TOS_URI}"
|
||||||
|
export SNIKKET_WEB_PRIVACY_URI="${SNIKKET_PRIVACY_URI}"
|
||||||
|
export SNIKKET_WEB_ABUSE_EMAIL="${SNIKKET_ABUSE_EMAIL}"
|
||||||
|
export SNIKKET_WEB_SECURITY_EMAIL="${SNIKKET_SECURITY_EMAIL}"
|
||||||
|
|
||||||
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE-127.0.0.1}"
|
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE-127.0.0.1}"
|
||||||
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT-5765}"
|
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT-5765}"
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ class AppConfig:
|
|||||||
# tools may also very well override it.
|
# tools may also very well override it.
|
||||||
max_avatar_size = environ.var(1024*1024, converter=int)
|
max_avatar_size = environ.var(1024*1024, converter=int)
|
||||||
show_metrics = environ.bool_var(True)
|
show_metrics = environ.bool_var(True)
|
||||||
|
tos_uri = environ.var("")
|
||||||
|
privacy_uri = environ.var("")
|
||||||
|
abuse_email = environ.var("")
|
||||||
|
security_email = environ.var("")
|
||||||
|
|
||||||
|
|
||||||
_UPPER_CASE = "".join(map(chr, range(ord("A"), ord("Z")+1)))
|
_UPPER_CASE = "".join(map(chr, range(ord("A"), ord("Z")+1)))
|
||||||
@@ -202,6 +206,10 @@ def create_app() -> quart.Quart:
|
|||||||
app.config["APPLE_STORE_URL"] = config.apple_store_url
|
app.config["APPLE_STORE_URL"] = config.apple_store_url
|
||||||
app.config["MAX_AVATAR_SIZE"] = config.max_avatar_size
|
app.config["MAX_AVATAR_SIZE"] = config.max_avatar_size
|
||||||
app.config["SHOW_METRICS"] = config.show_metrics
|
app.config["SHOW_METRICS"] = config.show_metrics
|
||||||
|
app.config["TOS_URI"] = config.tos_uri
|
||||||
|
app.config["PRIVACY_URI"] = config.privacy_uri
|
||||||
|
app.config["ABUSE_EMAIL"] = config.abuse_email
|
||||||
|
app.config["SECURITY_EMAIL"] = config.security_email
|
||||||
|
|
||||||
app.context_processor(proc)
|
app.context_processor(proc)
|
||||||
app.register_error_handler(
|
app.register_error_handler(
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class EditUserForm(BaseForm):
|
|||||||
_l("Access Level"),
|
_l("Access Level"),
|
||||||
choices=[
|
choices=[
|
||||||
("prosody:restricted", _("Limited")),
|
("prosody:restricted", _("Limited")),
|
||||||
("prosody:user", _l("Normal user")),
|
("prosody:registered", _l("Normal user")),
|
||||||
("prosody:admin", _l("Administrator")),
|
("prosody:admin", _l("Administrator")),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -86,6 +86,14 @@ class EditUserForm(BaseForm):
|
|||||||
_l("Update user"),
|
_l("Update user"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
action_restore = wtforms.SubmitField(
|
||||||
|
_l("Restore account"),
|
||||||
|
)
|
||||||
|
|
||||||
|
action_enable = wtforms.SubmitField(
|
||||||
|
_l("Unlock account"),
|
||||||
|
)
|
||||||
|
|
||||||
action_create_reset = wtforms.SubmitField(
|
action_create_reset = wtforms.SubmitField(
|
||||||
_l("Create password reset link"),
|
_l("Create password reset link"),
|
||||||
)
|
)
|
||||||
@@ -112,6 +120,32 @@ async def edit_user(localpart: str) -> typing.Union[werkzeug.Response, str]:
|
|||||||
".user_password_reset_link",
|
".user_password_reset_link",
|
||||||
id_=reset_link.id_,
|
id_=reset_link.id_,
|
||||||
))
|
))
|
||||||
|
elif form.action_restore.data or form.action_enable.data:
|
||||||
|
await client.enable_user_account(localpart)
|
||||||
|
try:
|
||||||
|
if form.action_restore.data:
|
||||||
|
await flash(
|
||||||
|
_("User account restored"),
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await flash(
|
||||||
|
_("User account unlocked"),
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
return redirect(url_for(".users"))
|
||||||
|
except aiohttp.ClientResponseError:
|
||||||
|
if form.action_restore.data:
|
||||||
|
await flash(
|
||||||
|
_("Could not restore user account"),
|
||||||
|
"alert",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await flash(
|
||||||
|
_("Could not unlock user account"),
|
||||||
|
"alert",
|
||||||
|
)
|
||||||
|
return redirect(url_for(".edit_user", localpart=localpart))
|
||||||
|
|
||||||
await client.update_user(
|
await client.update_user(
|
||||||
localpart,
|
localpart,
|
||||||
@@ -123,7 +157,7 @@ async def edit_user(localpart: str) -> typing.Union[werkzeug.Response, str]:
|
|||||||
_("User information updated."),
|
_("User information updated."),
|
||||||
"success",
|
"success",
|
||||||
)
|
)
|
||||||
return redirect(url_for(".edit_user", localpart=localpart))
|
return redirect(url_for(".users"))
|
||||||
|
|
||||||
elif request.method == "GET":
|
elif request.method == "GET":
|
||||||
form.localpart.data = target_user_info.localpart
|
form.localpart.data = target_user_info.localpart
|
||||||
@@ -131,7 +165,7 @@ async def edit_user(localpart: str) -> typing.Union[werkzeug.Response, str]:
|
|||||||
if target_user_info.roles:
|
if target_user_info.roles:
|
||||||
form.role.data = target_user_info.roles[0]
|
form.role.data = target_user_info.roles[0]
|
||||||
else:
|
else:
|
||||||
form.role.data = "prosody:user"
|
form.role.data = "prosody:registered"
|
||||||
|
|
||||||
return await render_template(
|
return await render_template(
|
||||||
"admin_edit_user.html",
|
"admin_edit_user.html",
|
||||||
@@ -458,6 +492,8 @@ class EditCircleForm(BaseForm):
|
|||||||
_l("Add user")
|
_l("Add user")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
action_remove_group_chat = wtforms.StringField()
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/circle/<id_>", methods=["GET", "POST"])
|
@bp.route("/circle/<id_>", methods=["GET", "POST"])
|
||||||
@client.require_admin_session()
|
@client.require_admin_session()
|
||||||
@@ -530,6 +566,15 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
|
|||||||
_("User removed from circle"),
|
_("User removed from circle"),
|
||||||
"success",
|
"success",
|
||||||
)
|
)
|
||||||
|
elif form.action_remove_group_chat.data:
|
||||||
|
await client.remove_group_chat(
|
||||||
|
id_,
|
||||||
|
form.action_remove_group_chat.data,
|
||||||
|
)
|
||||||
|
await flash(
|
||||||
|
_("Chat removed from circle"),
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(url_for(".edit_circle", id_=id_))
|
return redirect(url_for(".edit_circle", id_=id_))
|
||||||
|
|
||||||
@@ -537,6 +582,7 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
|
|||||||
"admin_edit_circle.html",
|
"admin_edit_circle.html",
|
||||||
target_circle=circle,
|
target_circle=circle,
|
||||||
form=form,
|
form=form,
|
||||||
|
circle_chats=circle.chats,
|
||||||
circle_members=circle_members,
|
circle_members=circle_members,
|
||||||
invite_form=invite_form,
|
invite_form=invite_form,
|
||||||
)
|
)
|
||||||
@@ -583,6 +629,56 @@ async def delete_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AddCircleChatForm(BaseForm):
|
||||||
|
name = wtforms.StringField(
|
||||||
|
_l("Group chat name"),
|
||||||
|
validators=[wtforms.validators.InputRequired()],
|
||||||
|
)
|
||||||
|
|
||||||
|
action_save = wtforms.SubmitField(
|
||||||
|
_l("Create group chat")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/circle/<id_>/add_chat", methods=["GET", "POST"])
|
||||||
|
@client.require_admin_session()
|
||||||
|
async def edit_circle_add_chat(
|
||||||
|
id_: str
|
||||||
|
) -> typing.Union[str, werkzeug.Response]:
|
||||||
|
async with client.authenticated_session() as session:
|
||||||
|
try:
|
||||||
|
circle = await client.get_group_by_id(
|
||||||
|
id_,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
except aiohttp.ClientResponseError as exc:
|
||||||
|
if exc.status == 404:
|
||||||
|
await flash(
|
||||||
|
_("No such circle exists"),
|
||||||
|
"alert",
|
||||||
|
)
|
||||||
|
return redirect(url_for(".circles"))
|
||||||
|
raise
|
||||||
|
|
||||||
|
form = AddCircleChatForm()
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if form.action_save.data:
|
||||||
|
await client.add_group_chat(id_, form.name.data)
|
||||||
|
await flash(
|
||||||
|
_("New group chat added to circle"),
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect(url_for(".edit_circle", id_=id_))
|
||||||
|
|
||||||
|
return await render_template(
|
||||||
|
"admin_create_circle_chat.html",
|
||||||
|
target_circle=circle,
|
||||||
|
group_chat_form=form,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_CPU_EPOCH = time.process_time()
|
_CPU_EPOCH = time.process_time()
|
||||||
_MONOTONIC_EPOCH = time.monotonic()
|
_MONOTONIC_EPOCH = time.monotonic()
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import math
|
|||||||
import secrets
|
import secrets
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
import quart.flask_patch # noqa:F401
|
import quart.flask_patch # noqa:F401
|
||||||
from quart import (
|
from quart import (
|
||||||
current_app,
|
current_app,
|
||||||
@@ -13,7 +15,8 @@ from quart import (
|
|||||||
|
|
||||||
import flask_babel
|
import flask_babel
|
||||||
import flask_wtf
|
import flask_wtf
|
||||||
from flask_babel import _
|
from flask_babel import lazy_gettext as _l
|
||||||
|
import flask_babel as _
|
||||||
|
|
||||||
from . import prosodyclient
|
from . import prosodyclient
|
||||||
|
|
||||||
@@ -70,6 +73,43 @@ def format_bytes(n: float) -> str:
|
|||||||
return "{} {}".format(n, unit)
|
return "{} {}".format(n, unit)
|
||||||
|
|
||||||
|
|
||||||
|
def format_last_activity(timestamp: typing.Optional[int]) -> str:
|
||||||
|
if timestamp is None:
|
||||||
|
return _l("Never")
|
||||||
|
|
||||||
|
last_active = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
||||||
|
# TODO: This 'now' should use the user's local time zone, but we
|
||||||
|
# don't have that information. Thus 'today'/'yesterday' may be
|
||||||
|
# slightly inaccurate, but compared to alternative solutions it
|
||||||
|
# should hopefully be "good enough".
|
||||||
|
now = datetime.now(tz=timezone.utc)
|
||||||
|
time_ago = now - last_active
|
||||||
|
|
||||||
|
yesterday = now - timedelta(days=1)
|
||||||
|
|
||||||
|
if (
|
||||||
|
last_active.year == now.year
|
||||||
|
and last_active.month == now.month
|
||||||
|
and last_active.day == now.day
|
||||||
|
):
|
||||||
|
return _l("Today")
|
||||||
|
elif (
|
||||||
|
last_active.year == yesterday.year
|
||||||
|
and last_active.month == yesterday.month
|
||||||
|
and last_active.day == yesterday.day
|
||||||
|
):
|
||||||
|
return _l("Yesterday")
|
||||||
|
|
||||||
|
return _.gettext(
|
||||||
|
"%(time)s ago",
|
||||||
|
time=flask_babel.format_timedelta(time_ago, granularity="day"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def template_now() -> typing.Dict[str, typing.Any]:
|
||||||
|
return dict(now=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
|
||||||
def add_vary_language_header(resp: quart.Response) -> quart.Response:
|
def add_vary_language_header(resp: quart.Response) -> quart.Response:
|
||||||
if getattr(g, "language_header_accessed", False):
|
if getattr(g, "language_header_accessed", False):
|
||||||
resp.vary.add("Accept-Language")
|
resp.vary.add("Accept-Language")
|
||||||
@@ -86,6 +126,8 @@ def init_templating(app: quart.Quart) -> None:
|
|||||||
app.template_filter("format_bytes")(format_bytes)
|
app.template_filter("format_bytes")(format_bytes)
|
||||||
app.template_filter("flatten")(flatten)
|
app.template_filter("flatten")(flatten)
|
||||||
app.template_filter("circle_name")(circle_name)
|
app.template_filter("circle_name")(circle_name)
|
||||||
|
app.template_filter("format_last_activity")(format_last_activity)
|
||||||
|
app.context_processor(template_now)
|
||||||
app.after_request(add_vary_language_header)
|
app.after_request(add_vary_language_header)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,42 @@ async def avatar(from_: str, code: str) -> quart.Response:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/terms")
|
||||||
|
async def terms() -> Response:
|
||||||
|
if not current_app.config["TOS_URI"]:
|
||||||
|
return Response("", 404)
|
||||||
|
|
||||||
|
return Response("", status=303, headers={
|
||||||
|
"Location": current_app.config["TOS_URI"],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/privacy")
|
||||||
|
async def privacy() -> Response:
|
||||||
|
if not current_app.config["PRIVACY_URI"]:
|
||||||
|
return Response("", 404)
|
||||||
|
|
||||||
|
return Response("", status=303, headers={
|
||||||
|
"Location": current_app.config["PRIVACY_URI"],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# This is linked from the iOS app and about page
|
||||||
|
@bp.route("/policies/")
|
||||||
|
async def policies() -> str:
|
||||||
|
return await render_template(
|
||||||
|
"policies.html",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/.well-known/security.txt")
|
||||||
|
async def securitytxt() -> Response:
|
||||||
|
return Response(
|
||||||
|
await render_template("security.txt"),
|
||||||
|
mimetype="text/plain;charset=UTF-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/_health")
|
@bp.route("/_health")
|
||||||
async def health() -> Response:
|
async def health() -> Response:
|
||||||
return Response("STATUS OK", content_type="text/plain")
|
return Response("STATUS OK", content_type="text/plain")
|
||||||
|
|||||||
@@ -9,25 +9,27 @@ import types
|
|||||||
import typing
|
import typing
|
||||||
import typing_extensions
|
import typing_extensions
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from quart import (
|
from quart import (
|
||||||
current_app, _app_ctx_stack, session as http_session, abort, redirect,
|
current_app, session as http_session, abort, redirect,
|
||||||
url_for,
|
url_for,
|
||||||
)
|
)
|
||||||
import quart
|
import quart
|
||||||
|
|
||||||
|
from flask import g as _app_ctx_stack
|
||||||
|
|
||||||
import werkzeug.exceptions
|
import werkzeug.exceptions
|
||||||
|
|
||||||
from . import xmpputil
|
from . import xmpputil
|
||||||
from .xmpputil import split_jid
|
from .xmpputil import split_jid
|
||||||
|
|
||||||
|
|
||||||
SCOPE_DEFAULT = "prosody:user"
|
SCOPE_DEFAULT = "prosody:registered"
|
||||||
SCOPE_ADMIN = "prosody:admin"
|
SCOPE_ADMIN = "prosody:admin"
|
||||||
|
|
||||||
|
|
||||||
@@ -40,6 +42,52 @@ class TokenInfo:
|
|||||||
scopes: typing.Collection[str]
|
scopes: typing.Collection[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class UserDeletionRequestInfo:
|
||||||
|
deleted_at: datetime
|
||||||
|
pending_until: datetime
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_api_response(
|
||||||
|
cls,
|
||||||
|
data: typing.Optional[typing.Mapping[str, typing.Any]],
|
||||||
|
) -> typing.Optional["UserDeletionRequestInfo"]:
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
return cls(
|
||||||
|
deleted_at=datetime.fromtimestamp(
|
||||||
|
data["deleted_at"],
|
||||||
|
tz=timezone.utc
|
||||||
|
),
|
||||||
|
pending_until=datetime.fromtimestamp(
|
||||||
|
data["pending_until"],
|
||||||
|
tz=timezone.utc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class AvatarMetadata:
|
||||||
|
bytes: int
|
||||||
|
hash: str
|
||||||
|
type: str
|
||||||
|
width: typing.Optional[int]
|
||||||
|
height: typing.Optional[int]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_api_response(
|
||||||
|
cls,
|
||||||
|
data: typing.Mapping[str, typing.Any],
|
||||||
|
) -> "AvatarMetadata":
|
||||||
|
return cls(
|
||||||
|
hash=data["hash"],
|
||||||
|
bytes=data["bytes"],
|
||||||
|
type=data["type"],
|
||||||
|
width=data.get("width") or None,
|
||||||
|
height=data.get("height") or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class AdminUserInfo:
|
class AdminUserInfo:
|
||||||
localpart: str
|
localpart: str
|
||||||
@@ -47,6 +95,10 @@ class AdminUserInfo:
|
|||||||
email: typing.Optional[str]
|
email: typing.Optional[str]
|
||||||
phone: typing.Optional[str]
|
phone: typing.Optional[str]
|
||||||
roles: typing.Optional[typing.List[str]]
|
roles: typing.Optional[typing.List[str]]
|
||||||
|
enabled: bool
|
||||||
|
last_active: typing.Optional[int]
|
||||||
|
deletion_request: typing.Optional[UserDeletionRequestInfo]
|
||||||
|
avatar_info: typing.List[AvatarMetadata]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_admin_role(self) -> bool:
|
def has_admin_role(self) -> bool:
|
||||||
@@ -73,6 +125,15 @@ class AdminUserInfo:
|
|||||||
email=data.get("email") or None,
|
email=data.get("email") or None,
|
||||||
phone=data.get("phone") or None,
|
phone=data.get("phone") or None,
|
||||||
roles=roles,
|
roles=roles,
|
||||||
|
enabled=data.get("enabled", True),
|
||||||
|
last_active=data.get("last_active") or None,
|
||||||
|
deletion_request=UserDeletionRequestInfo.from_api_response(
|
||||||
|
data.get("deletion_request")
|
||||||
|
),
|
||||||
|
avatar_info=[
|
||||||
|
AvatarMetadata.from_api_response(avatar_info)
|
||||||
|
for avatar_info in data.get("avatar_info", [])
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -115,12 +176,30 @@ class AdminInviteInfo:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class AdminGroupChatInfo:
|
||||||
|
id_: str
|
||||||
|
jid: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_api_response(
|
||||||
|
cls,
|
||||||
|
data: typing.Mapping[str, typing.Any],
|
||||||
|
) -> "AdminGroupChatInfo":
|
||||||
|
return cls(
|
||||||
|
id_=data["id"],
|
||||||
|
jid=data["jid"],
|
||||||
|
name=data["name"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class AdminGroupInfo:
|
class AdminGroupInfo:
|
||||||
id_: str
|
id_: str
|
||||||
name: str
|
name: str
|
||||||
muc_jid: typing.Optional[str]
|
|
||||||
members: typing.Collection[str]
|
members: typing.Collection[str]
|
||||||
|
chats: typing.Collection[AdminGroupChatInfo]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api_response(
|
def from_api_response(
|
||||||
@@ -130,8 +209,11 @@ class AdminGroupInfo:
|
|||||||
return cls(
|
return cls(
|
||||||
id_=data["id"],
|
id_=data["id"],
|
||||||
name=data["name"],
|
name=data["name"],
|
||||||
muc_jid=data.get("muc_jid") or None,
|
|
||||||
members=data.get("members", []),
|
members=data.get("members", []),
|
||||||
|
chats=[
|
||||||
|
AdminGroupChatInfo.from_api_response(x)
|
||||||
|
for x in data.get("chats", [])
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -166,7 +248,7 @@ class HTTPSessionManager:
|
|||||||
})
|
})
|
||||||
|
|
||||||
async def teardown(self, exc: typing.Optional[BaseException]) -> None:
|
async def teardown(self, exc: typing.Optional[BaseException]) -> None:
|
||||||
app_ctx = _app_ctx_stack.top
|
app_ctx = _app_ctx_stack
|
||||||
try:
|
try:
|
||||||
session = getattr(app_ctx, self._app_context_attribute)
|
session = getattr(app_ctx, self._app_context_attribute)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -183,7 +265,7 @@ class HTTPSessionManager:
|
|||||||
await session.__aexit__(exc_type, exc, traceback)
|
await session.__aexit__(exc_type, exc, traceback)
|
||||||
|
|
||||||
async def __aenter__(self) -> aiohttp.ClientSession:
|
async def __aenter__(self) -> aiohttp.ClientSession:
|
||||||
app_ctx = _app_ctx_stack.top
|
app_ctx = _app_ctx_stack
|
||||||
try:
|
try:
|
||||||
return getattr(app_ctx, self._app_context_attribute)
|
return getattr(app_ctx, self._app_context_attribute)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -902,6 +984,36 @@ class ProsodyClient:
|
|||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
|
@autosession
|
||||||
|
async def enable_user_account(
|
||||||
|
self,
|
||||||
|
localpart: str,
|
||||||
|
*,
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
) -> None:
|
||||||
|
async with session.patch(
|
||||||
|
self._admin_v1_endpoint("/users/{}".format(localpart)),
|
||||||
|
json={
|
||||||
|
"enabled": True,
|
||||||
|
},
|
||||||
|
) as resp:
|
||||||
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
|
@autosession
|
||||||
|
async def disable_user_account(
|
||||||
|
self,
|
||||||
|
localpart: str,
|
||||||
|
*,
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
) -> None:
|
||||||
|
async with session.patch(
|
||||||
|
self._admin_v1_endpoint("/users/{}".format(localpart)),
|
||||||
|
json={
|
||||||
|
"enabled": False,
|
||||||
|
},
|
||||||
|
) as resp:
|
||||||
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_user_debug_info(
|
async def get_user_debug_info(
|
||||||
self,
|
self,
|
||||||
@@ -1030,7 +1142,7 @@ class ProsodyClient:
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
create_muc: bool = True,
|
create_muc: bool = False,
|
||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> AdminGroupInfo:
|
) -> AdminGroupInfo:
|
||||||
payload = {
|
payload = {
|
||||||
@@ -1105,6 +1217,27 @@ class ProsodyClient:
|
|||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
|
@autosession
|
||||||
|
async def add_group_chat(
|
||||||
|
self,
|
||||||
|
id_: str,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
payload: typing.Dict[str, typing.Any] = {
|
||||||
|
"name": name,
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.post(
|
||||||
|
self._admin_v1_endpoint(
|
||||||
|
"/groups/{}/chats".format(id_)
|
||||||
|
),
|
||||||
|
json=payload,
|
||||||
|
) as resp:
|
||||||
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def remove_group_member(
|
async def remove_group_member(
|
||||||
self,
|
self,
|
||||||
@@ -1120,6 +1253,21 @@ class ProsodyClient:
|
|||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
|
@autosession
|
||||||
|
async def remove_group_chat(
|
||||||
|
self,
|
||||||
|
group_id: str,
|
||||||
|
chat_id: str,
|
||||||
|
*,
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
) -> None:
|
||||||
|
async with session.delete(
|
||||||
|
self._admin_v1_endpoint(
|
||||||
|
"/groups/{}/chats/{}".format(group_id, chat_id)
|
||||||
|
),
|
||||||
|
) as resp:
|
||||||
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def delete_group(
|
async def delete_group(
|
||||||
self,
|
self,
|
||||||
@@ -1160,7 +1308,6 @@ class ProsodyClient:
|
|||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@autosession
|
|
||||||
async def revoke_token(
|
async def revoke_token(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -1174,7 +1321,8 @@ class ProsodyClient:
|
|||||||
|
|
||||||
async def logout(self) -> None:
|
async def logout(self) -> None:
|
||||||
try:
|
try:
|
||||||
await self.revoke_token()
|
async with self._plain_session as session:
|
||||||
|
await self.revoke_token(session=session)
|
||||||
except aiohttp.ClientError:
|
except aiohttp.ClientError:
|
||||||
self.logger.warn("failed to revoke token!",
|
self.logger.warn("failed to revoke token!",
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
|
|||||||
@@ -275,22 +275,22 @@ div.form.layout-expanded {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@each $type in $text-entry-inputs {
|
@each $type in $text-entry-inputs {
|
||||||
input[type=$type] {
|
input[type=#{$type}] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: $w-s4 solid $primary-500;
|
border-bottom: $w-s4 solid $primary-500;
|
||||||
margin-bottom: -$w-s4;
|
margin-bottom: -$w-s4;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=$type].has-error {
|
input[type=#{$type}].has-error {
|
||||||
border-right: $w-s4 solid $alert-500;
|
border-right: $w-s4 solid $alert-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=$type]:hover {
|
input[type=#{$type}]:hover {
|
||||||
border-bottom-color: $primary-700;
|
border-bottom-color: $primary-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=$type]:focus {
|
input[type=#{$type}]:focus {
|
||||||
border-bottom-color: $primary-800;
|
border-bottom-color: $primary-800;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -646,69 +646,6 @@ input[type="submit"], button, .button {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* button, .button {
|
|
||||||
margin: 0 $w-s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.lv-primary, .button.lv-primary {
|
|
||||||
background-color: $gray-500;
|
|
||||||
color: $gray-900;
|
|
||||||
border-radius: $w-s4;
|
|
||||||
border: $w-s4 solid $gray-400;
|
|
||||||
|
|
||||||
@each $type, $values in $colours {
|
|
||||||
&.c-#{$type} {
|
|
||||||
border-color: nth($values, 4);
|
|
||||||
background-color: nth($values, 5);
|
|
||||||
color: nth($values, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.c-#{$type}:hover {
|
|
||||||
background-color: nth($values, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button.lv-secondary, .button.lv-secondary {
|
|
||||||
background-color: $gray-700;
|
|
||||||
color: $gray-100;
|
|
||||||
border-radius: $w-s4;
|
|
||||||
|
|
||||||
@each $type, $values in $colours {
|
|
||||||
&.c-#{$type} {
|
|
||||||
background-color: nth($values, 7);
|
|
||||||
color: nth($values, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button.lv-tertiary, .button.lv-tertiary {
|
|
||||||
background-color: inherit;
|
|
||||||
color: $gray-300;
|
|
||||||
border-radius: $w-s4;
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
@each $type, $values in $colours {
|
|
||||||
&.c-#{$type} {
|
|
||||||
color: nth($values, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
button.lv-secondary.c-#{$type}, .button.lv-secondary.c-#{$type} {
|
|
||||||
background-color: nth($values, 7);
|
|
||||||
color: nth($values, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
button.lv-tertiary.c-#{$type}, .button.lv-tertiary.c-#{$type} {
|
|
||||||
color: nth($values, 3);
|
|
||||||
text-decoration: underline;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/* boxes */
|
/* boxes */
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
@@ -771,8 +708,7 @@ button.lv-tertiary, .button.lv-tertiary {
|
|||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
box-shadow: inset 0px 0px 0px 2px rgba(0, 0, 0, 0.2);
|
border-radius: 10%;
|
||||||
border-radius: $w-s4;
|
|
||||||
|
|
||||||
margin: 0 0.25em;
|
margin: 0 0.25em;
|
||||||
|
|
||||||
@@ -1121,7 +1057,7 @@ pre.guru-meditation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@each $type in $text-entry-inputs {
|
@each $type in $text-entry-inputs {
|
||||||
input[type=$type] {
|
input[type=#{$type}] {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1131,6 +1067,10 @@ pre.guru-meditation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label, legend {
|
||||||
|
color: $gray-800 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
border-color: $gray-800;
|
border-color: $gray-800;
|
||||||
@@ -1265,6 +1205,13 @@ pre.guru-meditation {
|
|||||||
p.form-desc.weak, p.field-desc.weak {
|
p.form-desc.weak, p.field-desc.weak {
|
||||||
color: $gray-700;
|
color: $gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-badge-icon {
|
||||||
|
color: $gray-900 !important;
|
||||||
|
background-color: $gray-100 !important;
|
||||||
|
border-color: $gray-300 !important;
|
||||||
|
box-shadow: black 0 0 2px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tooltip magic */
|
/* tooltip magic */
|
||||||
@@ -1315,3 +1262,46 @@ pre.guru-meditation {
|
|||||||
.with-tooltip:hover:before, .with-tooltip:hover:after {
|
.with-tooltip:hover:before, .with-tooltip:hover:after {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.username-with-avatar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badge-icon {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
right: 0px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
border-color: $gray-500;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: $gray-500 0px 0px 2px;
|
||||||
|
|
||||||
|
line-height: 1;
|
||||||
|
.icon {
|
||||||
|
/* vertical-align: text-bottom; */
|
||||||
|
padding: 0.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-container {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,6 +42,16 @@ licensed under the terms of the Apache 2.0 License -->
|
|||||||
<g fill="none"><path d="M0 0h24v24H0V0z" /><path d="M0 0h24v24H0V0z" opacity=".87" /></g>
|
<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" />
|
<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>
|
</symbol>
|
||||||
|
<!-- from: action/lock_open/materialiconsround/24px.svg -->
|
||||||
|
<symbol id="icon-lock_open" viewBox="0 0 24 24">
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M12 13c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6-5h-1V6c0-2.76-2.24-5-5-5-2.28 0-4.27 1.54-4.84 3.75-.14.54.18 1.08.72 1.22.53.14 1.08-.18 1.22-.72C9.44 3.93 10.63 3 12 3c1.65 0 3 1.35 3 3v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 11c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-8c0-.55.45-1 1-1h10c.55 0 1 .45 1 1v8z" />
|
||||||
|
</symbol>
|
||||||
|
<!-- from: action/restore_from_trash/materialiconsround/24px.svg -->
|
||||||
|
<symbol id="icon-restore_from_trash" viewBox="0 0 24 24">
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v10zm5.65-8.65c.2-.2.51-.2.71 0L16 14h-2v4h-4v-4H8l3.65-3.65zM15.5 4l-.71-.71c-.18-.18-.44-.29-.7-.29H9.91c-.26 0-.52.11-.7.29L8.5 4H6c-.55 0-1 .45-1 1s.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1h-2.5z" />
|
||||||
|
</symbol>
|
||||||
<!-- from: communication/import_export/materialiconsround/24px.svg -->
|
<!-- from: communication/import_export/materialiconsround/24px.svg -->
|
||||||
<symbol id="icon-import_export" viewBox="0 0 24 24">
|
<symbol id="icon-import_export" viewBox="0 0 24 24">
|
||||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
@@ -6,16 +6,20 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<main>
|
<main>
|
||||||
<div class="box el-2">
|
<div class="box el-2">
|
||||||
<h1>{% trans %}About Snikket{% endtrans %}</h1>
|
|
||||||
<p>{% trans snikket_url="https://snikket.org" %}To learn more about Snikket, visit the <a href="{{ snikket_url}}">Snikket website</a>.{% endtrans %}</p>
|
|
||||||
<h2>{% trans %}About this Service{% endtrans %}</h2>
|
<h2>{% trans %}About this Service{% endtrans %}</h2>
|
||||||
<p>{% trans site_name=config["SITE_NAME"] %}This is the Snikket service <em>{{ site_name }}</em>.{% endtrans %}</p>
|
<p>{% trans site_name=config["SITE_NAME"] %}This is the Snikket service <em>{{ site_name }}</em>, running open-source software from the Snikket project.{% endtrans %}</p>
|
||||||
|
<p>{% trans snikket_url="https://snikket.org" %}To learn more about Snikket, visit the <a href="{{ snikket_url}}">Snikket website</a>.{% endtrans %}</p>
|
||||||
|
|
||||||
|
<p><a href="/policies/">{% trans %}View service policies{% endtrans %}</a>
|
||||||
|
|
||||||
<h3>{% trans %}Licenses{% endtrans %}</h3>
|
<h3>{% trans %}Licenses{% endtrans %}</h3>
|
||||||
<p>{% trans agpl_url="https://www.gnu.org/licenses/agpl.html" %}The web portal software is licensed under the terms of the <a href="{{ agpl_url }}">Affero GNU General Public License, version 3.0 or later</a>. The full terms of the license can be reviewed using the aforementioned link.{% endtrans %}</p>
|
<p>{% trans agpl_url="https://www.gnu.org/licenses/agpl.html" %}The web portal software is licensed under the terms of the <a href="{{ agpl_url }}">Affero GNU General Public License, version 3.0 or later</a>. The full terms of the license can be reviewed using the aforementioned link.{% endtrans %}</p>
|
||||||
<p>{% trans source_url="https://github.com/snikket-im/snikket-web-portal/" %}The source code of the web portal can be downloaded and viewed in <a href="{{ source_url }}">its GitHub repository</a>.{% endtrans %}</p>
|
<p>{% trans source_url="https://github.com/snikket-im/snikket-web-portal/" %}The source code of the web portal can be downloaded and viewed in <a href="{{ source_url }}">its GitHub repository</a>.{% endtrans %}</p>
|
||||||
<p>{% trans source_url="https://material.io/resources/icons/", apache20_url="https://www.apache.org/licenses/LICENSE-2.0.txt" %}The icons used in the web portal are <a href="{{ source_url }}">Google’s Material Icons</a>, made available by Google under the terms of the <a href="{{ apache20_url }}">Apache 2.0 License</a>.{% endtrans %}</p>
|
<p>{% trans source_url="https://material.io/resources/icons/", apache20_url="https://www.apache.org/licenses/LICENSE-2.0.txt" %}The icons used in the web portal are <a href="{{ source_url }}">Google’s Material Icons</a>, made available by Google under the terms of the <a href="{{ apache20_url }}">Apache 2.0 License</a>.{% endtrans %}</p>
|
||||||
|
|
||||||
<h3>{% trans %}Trademarks{% endtrans %}</h3>
|
<h3>{% trans %}Trademarks{% endtrans %}</h3>
|
||||||
<p>{% trans trademarks_url="https://snikket.org/about/trademarks/" %}“Snikket” and the parrot logo are trademarks of Snikket Community Interest Company. For more information about the trademarks, visit the <a href="{{ trademarks_url }}">Snikket Trademarks information page</a>.{% endtrans %}
|
<p>{% trans trademarks_url="https://snikket.org/about/trademarks/" %}“Snikket” and the parrot logo are trademarks of Snikket Community Interest Company. For more information about the trademarks, visit the <a href="{{ trademarks_url }}">Snikket Trademarks information page</a>.{% endtrans %}
|
||||||
|
|
||||||
<h3>{% trans %}Software Versions{% endtrans %}</h3>
|
<h3>{% trans %}Software Versions{% endtrans %}</h3>
|
||||||
<pre>Domain: {{ config["SNIKKET_DOMAIN"] }}
|
<pre>Domain: {{ config["SNIKKET_DOMAIN"] }}
|
||||||
Web Portal{% if version %} ({{ version }}){% endif %}
|
Web Portal{% if version %} ({{ version }}){% endif %}
|
||||||
@@ -27,6 +31,7 @@ Web Portal{% if version %} ({{ version }}){% endif %}
|
|||||||
{% for name, version in extra_versions.items() %}
|
{% for name, version in extra_versions.items() %}
|
||||||
{{ name }} ({{ version }}){% endfor %}
|
{{ name }} ({{ version }}){% endfor %}
|
||||||
{%- endif -%}</pre>
|
{%- endif -%}</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{%- call standard_button("back", url_for("index"), class="primary") -%}
|
{%- call standard_button("back", url_for("index"), class="primary") -%}
|
||||||
{% trans %}Back to the main page{% endtrans %}
|
{% trans %}Back to the main page{% endtrans %}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans %}Manage circles{% endtrans %}</h1>
|
<h1>{% trans %}Manage circles{% endtrans %}</h1>
|
||||||
<p>{% trans %}<em>Circles</em> aim to help people who are in the same social circle find each other on your service.{% endtrans %}</p>
|
<p>{% trans %}<em>Circles</em> aim to help people who are in the same social circle find each other on your service.{% endtrans %}</p>
|
||||||
<p>{% trans %}Users who are in the same circle will see each other in their contact list. In addition, each circle has a group chat where the circle members are included.{% endtrans %}</p>
|
<p>{% trans %}Users who are in the same circle will see each other in their contact list. In addition, each circle may have group chats where the circle members are included.{% endtrans %}</p>
|
||||||
{%- if circles -%}
|
{%- if circles -%}
|
||||||
<form method="POST" action="{{ url_for(".create_invite") }}">
|
<form method="POST" action="{{ url_for(".create_invite") }}">
|
||||||
{{- invite_form.csrf_token -}}
|
{{- invite_form.csrf_token -}}
|
||||||
|
|||||||
5
snikket_web/templates/admin_create_circle_chat.html
Normal file
5
snikket_web/templates/admin_create_circle_chat.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends "admin_app.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ target_circle.name }}</h1>
|
||||||
|
{%- include "admin_create_circle_group_chat_form.html" -%}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% from "library.j2" import form_button, render_errors %}
|
||||||
|
<form method="POST" action="{{ url_for(".edit_circle_add_chat", id_=target_circle.id_) }}">
|
||||||
|
{{- group_chat_form.csrf_token -}}
|
||||||
|
<div class="form layout-expanded">
|
||||||
|
<h2 class="form-title">{% trans %}Create new circle group chat{% endtrans %}</h2>
|
||||||
|
<p class="form-descr weak">{% trans %}Add a chat to your circle so its members can hold group discussions.{% endtrans %}</p>
|
||||||
|
<p class="form-descr weak"><strong>{% trans %}Tip:{% endtrans %}</strong> {% trans %}This is only for creating group chats that automatically include <em>all</em> members of the circle. If you want a normal group chat, create it in the Snikket app instead.{% endtrans %}</p>
|
||||||
|
<div class="f-ebox">
|
||||||
|
{{ group_chat_form.name.label }}
|
||||||
|
{{ group_chat_form.name }}
|
||||||
|
</div>
|
||||||
|
<div class="f-bbox">
|
||||||
|
{%- call form_button("add", group_chat_form.action_save, class="primary") %}{% endcall -%}
|
||||||
|
</div>
|
||||||
|
</div></form>
|
||||||
@@ -13,13 +13,6 @@
|
|||||||
<div class="box hint form layout-expanded">
|
<div class="box hint form layout-expanded">
|
||||||
<header>{% trans %}This is your main circle{% endtrans %}</header>
|
<header>{% trans %}This is your main circle{% endtrans %}</header>
|
||||||
<p>{% trans %}This circle is managed automatically and cannot be removed or renamed.{% endtrans %}</p>
|
<p>{% trans %}This circle is managed automatically and cannot be removed or renamed.{% endtrans %}</p>
|
||||||
{%- if target_circle.muc_jid -%}
|
|
||||||
<div><label for="circle-muc-jid">{% trans %}Group chat address{% endtrans %}</label></div>
|
|
||||||
<div><input type="text" readonly="readonly" id="circle-muc-jid" value="{{ target_circle.muc_jid }}"></div>
|
|
||||||
{%- call clipboard_button(target_circle.muc_jid, show_label=True) -%}
|
|
||||||
{%- trans -%}Copy address{%- endtrans -%}
|
|
||||||
{%- endcall -%}
|
|
||||||
{%- endif -%}
|
|
||||||
</div>
|
</div>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
<div class="form layout-expanded">
|
<div class="form layout-expanded">
|
||||||
@@ -28,17 +21,6 @@
|
|||||||
{{ form.name.label }}
|
{{ form.name.label }}
|
||||||
{{ form.name }}
|
{{ form.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="f-ebox">
|
|
||||||
{%- if target_circle.muc_jid -%}
|
|
||||||
<label for="circle-muc-jid">{% trans %}Group chat address{% endtrans %}</label>
|
|
||||||
<input type="text" readonly="readonly" id="circle-muc-jid" value="{{ target_circle.muc_jid }}">
|
|
||||||
{%- call clipboard_button(target_circle.muc_jid, show_label=True) -%}
|
|
||||||
{%- trans -%}Copy address{%- endtrans -%}
|
|
||||||
{%- endcall -%}
|
|
||||||
{%- else -%}
|
|
||||||
<p>{% trans %}This circle has no group chat associated.{% endtrans %}<p>
|
|
||||||
{%- endif -%}
|
|
||||||
</div>
|
|
||||||
<div class="f-bbox">
|
<div class="f-bbox">
|
||||||
{%- call standard_button("back", url_for(".circles"), class="tertiary") -%}
|
{%- call standard_button("back", url_for(".circles"), class="tertiary") -%}
|
||||||
{% trans %}Return to circle list{% endtrans %}
|
{% trans %}Return to circle list{% endtrans %}
|
||||||
@@ -52,7 +34,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
|
<h2 id="chats">{% trans %}Group chats{% endtrans %}</h2>
|
||||||
|
<p>{% trans %}These group chats will be available to all members of the circle.{% endtrans %}</p>
|
||||||
|
|
||||||
|
{%- if circle_chats -%}
|
||||||
|
<div class="el-2 elevated"><table>
|
||||||
|
<thead>
|
||||||
|
<th>{% trans %}Name{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for chat in circle_chats -%}
|
||||||
|
<tr>
|
||||||
|
<td class="collapsible">{% call value_or_hint(chat.name) %}{% endcall %}</td>
|
||||||
|
<td class="nowrap">
|
||||||
|
{%- call custom_form_button("delete", form.action_remove_group_chat.name, chat.id_, class="primary danger", slim=True) -%}
|
||||||
|
{% trans name=chat.name %}Delete group chat '{{ name }}'{% endtrans %}
|
||||||
|
{%- endcall -%}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor -%}
|
||||||
|
</tbody>
|
||||||
|
</table></div>
|
||||||
|
{%- else -%}
|
||||||
|
<p>{% trans %}This circle currently has no group chats.{% endtrans %}</p>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- call standard_button("add", url_for(".edit_circle_add_chat", id_=target_circle.id_), class="secondary") -%}
|
||||||
|
{% trans %}Add group chat{% endtrans %}
|
||||||
|
{%- endcall -%}
|
||||||
|
|
||||||
<h2 id="members">{% trans %}Circle members{% endtrans %}</h2>
|
<h2 id="members">{% trans %}Circle members{% endtrans %}</h2>
|
||||||
|
<p>{% trans %}All members of the circle will see each other in their contact list.{% endtrans %}</p>
|
||||||
|
|
||||||
{%- if circle_members -%}
|
{%- if circle_members -%}
|
||||||
<div class="el-2 elevated"><table>
|
<div class="el-2 elevated"><table>
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% macro access_level_description(role, caller=None) %}
|
{% macro access_level_description(role, caller=None) %}
|
||||||
{%- if role == "prosody:restricted" -%}
|
{%- if role == "prosody:restricted" -%}
|
||||||
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
|
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
|
||||||
{%- elif role == "prosody:user" -%}
|
{%- elif role == "prosody:registered" -%}
|
||||||
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
|
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
|
||||||
{%- elif role == "prosody:admin" -%}
|
{%- elif role == "prosody:admin" -%}
|
||||||
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
|
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
|
||||||
@@ -19,12 +19,33 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}</h1>
|
<h1>{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}</h1>
|
||||||
<form method="POST">{{ form.csrf_token }}<div class="form layout-expanded">
|
<form method="POST">{{ form.csrf_token }}<div class="form layout-expanded">
|
||||||
|
{% if target_user.deletion_request %}
|
||||||
|
<div class="box alert">
|
||||||
|
<header>{% trans %}This user account is pending deletion{% endtrans %}</header>
|
||||||
|
<p>{% trans date=target_user.deletion_request.deleted_at | format_datetime %}The owner of the account sent a deletion request on {{ date }} using their app.{% endtrans %}
|
||||||
|
<p>{% trans time=(target_user.deletion_request.pending_until - now())|format_timedelta %}The account has been locked, and will be automatically deleted permanently in {{ time }}.{% endtrans %}</p>
|
||||||
|
|
||||||
|
<p>{% trans %}If this was a mistake, you can cancel the deletion and restore the account.{% endtrans %}</p>
|
||||||
|
|
||||||
|
{%- call form_button("restore_from_trash", form.action_restore, class="secondary") %}{% endcall %}
|
||||||
|
</div>
|
||||||
|
{% elif not target_user.enabled %}
|
||||||
|
<div class="box alert">
|
||||||
|
<header>{% trans %}This user account is locked{% endtrans %}</header>
|
||||||
|
<p>{% trans %}The user will not be able to log in to their account until it is unlocked again.{% endtrans %}</p>
|
||||||
|
|
||||||
|
{%- call form_button("lock_open", form.action_enable, class="secondary") %}{% endcall %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h2 class="form-title">{% trans %}Edit user{% endtrans %}</h2>
|
<h2 class="form-title">{% trans %}Edit user{% endtrans %}</h2>
|
||||||
|
|
||||||
<div class="f-ebox">
|
<div class="f-ebox">
|
||||||
{{ form.localpart.label }}
|
{{ form.localpart.label }}
|
||||||
{{ form.localpart(readonly="readonly") }}
|
{{ form.localpart(readonly="readonly") }}
|
||||||
<p class="form-desc weak">{% trans %}The login name cannot be changed.{% endtrans %}</p>
|
<p class="form-desc weak">{% trans %}The login name cannot be changed.{% endtrans %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="f-ebox">
|
<div class="f-ebox">
|
||||||
{{ form.display_name.label }}
|
{{ form.display_name.label }}
|
||||||
{{ form.display_name }}
|
{{ form.display_name }}
|
||||||
@@ -63,14 +84,14 @@
|
|||||||
{% trans %}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.{% endtrans %}
|
{% trans %}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.{% endtrans %}
|
||||||
</p>
|
</p>
|
||||||
<div class="f-bbox">
|
<div class="f-bbox">
|
||||||
{%- call form_button("passwd", form.action_create_reset, class="primary") -%}{%- endcall -%}
|
{%- call form_button("passwd", form.action_create_reset, class="secondary") -%}{%- endcall -%}
|
||||||
</div>
|
</div>
|
||||||
<h2 class="form-title">{% trans %}Debug information{% endtrans %}</h2>
|
<h2 class="form-title">{% trans %}Debug information{% endtrans %}</h2>
|
||||||
<p class="form-desc">
|
<p class="form-desc">
|
||||||
{% trans %}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.{% endtrans %}
|
{% trans %}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.{% endtrans %}
|
||||||
</p>
|
</p>
|
||||||
<div class="f-bbox">
|
<div class="f-bbox">
|
||||||
{%- call standard_button("bug_report", url_for(".debug_user", localpart=target_user.localpart), class="primary") -%}
|
{%- call standard_button("bug_report", url_for(".debug_user", localpart=target_user.localpart), class="secondary") -%}
|
||||||
{%- trans -%}Show debug information{%- endtrans -%}
|
{%- trans -%}Show debug information{%- endtrans -%}
|
||||||
{%- endcall -%}
|
{%- endcall -%}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{% extends "admin_app.html" %}
|
{% extends "admin_app.html" %}
|
||||||
{% from "library.j2" import action_button, icon, value_or_hint, custom_form_button %}
|
{% from "library.j2" import action_button, avatar, icon, render_user, value_or_hint, custom_form_button with context %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans %}Manage users{% endtrans %}</h1>
|
<h1>{% trans %}Manage users{% endtrans %}</h1>
|
||||||
<div class="elevated el-2"><table>
|
<div class="elevated el-2"><table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}Login name{% endtrans %}</th>
|
<th>{% trans %}User{% endtrans %}</th>
|
||||||
<th>{% trans %}Display name{% endtrans %}</th>
|
<th>{% trans %}Last active{% endtrans %}</th>
|
||||||
<th>{% trans %}Actions{% endtrans %}</th>
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -14,15 +14,15 @@
|
|||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{- user.localpart -}}
|
{%- call render_user(user) -%}{%- endcall -%}
|
||||||
{%- 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>
|
||||||
<td>{% call value_or_hint(user.display_name) %}{% endcall %}</td>
|
{% if user.enabled %}
|
||||||
|
<td>{{ user.last_active | format_last_activity }}</td>
|
||||||
|
{% elif user.deletion_request %}
|
||||||
|
<td>{% trans %}Deleted{% endtrans %}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{% trans %}Locked{% endtrans %}</td>
|
||||||
|
{% endif %}
|
||||||
<td class="nowrap">
|
<td class="nowrap">
|
||||||
{%- call action_button("edit", url_for(".edit_user", localpart=user.localpart), class="primary") -%}
|
{%- call action_button("edit", url_for(".edit_user", localpart=user.localpart), class="primary") -%}
|
||||||
{% trans user_name=user.localpart %}Edit user {{ user_name }}{% endtrans %}
|
{% trans user_name=user.localpart %}Edit user {{ user_name }}{% endtrans %}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
{% block head_lead %}
|
{% block head_lead %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<title>{% trans %}Reset your password | Snikket{% endtrans %}</title>
|
<title>{% trans %}Reset your password | Snikket{% endtrans %}</title>
|
||||||
<script async type="text/javascript" src="{{ url_for("static", filename="js/qrcode.min.js") }}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="POST"><div class="form layout-expanded">
|
<form method="POST"><div class="form layout-expanded">
|
||||||
@@ -27,9 +26,4 @@
|
|||||||
{%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%}
|
{%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%}
|
||||||
</div>
|
</div>
|
||||||
</div></form>
|
</div></form>
|
||||||
<script type="text/javascript">
|
|
||||||
var onload = function() {
|
|
||||||
apply_qr_code(document.getElementById("qr-uri"));
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -17,6 +17,13 @@
|
|||||||
{%- else -%}
|
{%- else -%}
|
||||||
<p>{% trans site_name=config["SITE_NAME"] %}You have been invited to chat on {{ site_name }} using Snikket, a secure, privacy-friendly chat app.{% endtrans %}</p>
|
<p>{% trans site_name=config["SITE_NAME"] %}You have been invited to chat on {{ site_name }} using Snikket, a secure, privacy-friendly chat app.{% endtrans %}</p>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if config["TOS_URI"] and config["PRIVACY_URI"] -%}
|
||||||
|
<p>
|
||||||
|
{% trans site_name=config["SITE_NAME"], tos_uri=config["TOS_URI"], privacy_uri=config["PRIVACY_URI"] %}By continuing, you agree to the <a href="{{tos_uri}}">Terms of Service</a> and <a href="{{privacy_uri}}">Privacy Policy</a>.{% endtrans %}
|
||||||
|
</p>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
<h2>{% trans %}Get started{% endtrans %}</h2>
|
<h2>{% trans %}Get started{% endtrans %}</h2>
|
||||||
{%- if apple_store_url -%}
|
{%- if apple_store_url -%}
|
||||||
<p>{% trans %}Install the Snikket App on your Android or iOS device.{% endtrans %}</p>
|
<p>{% trans %}Install the Snikket App on your Android or iOS device.{% endtrans %}</p>
|
||||||
@@ -127,7 +134,6 @@
|
|||||||
|
|
||||||
var onload = function() {
|
var onload = function() {
|
||||||
apply_qr_code(document.getElementById("qr-invite-page"));
|
apply_qr_code(document.getElementById("qr-invite-page"));
|
||||||
apply_qr_code(document.getElementById("qr-uri"));
|
|
||||||
var popover_as = document.getElementsByClassName("popover");
|
var popover_as = document.getElementsByClassName("popover");
|
||||||
for (var i = 0; i < popover_as.length; ++i) {
|
for (var i = 0; i < popover_as.length; ++i) {
|
||||||
var a = popover_as[i];
|
var a = popover_as[i];
|
||||||
|
|||||||
@@ -10,6 +10,29 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro render_user(user, caller=None) -%}
|
||||||
|
<div class="username-with-avatar">
|
||||||
|
<div class="avatar-container">
|
||||||
|
{%- call avatar(user.localpart+"@"+config["SNIKKET_DOMAIN"], user.avatar_info[0].hash if user.avatar_info | length > 0 else None ) %}{% endcall -%}
|
||||||
|
{%- if user.has_admin_role -%}
|
||||||
|
<div class="user-badge-icon">
|
||||||
|
<span class="with-tooltip above" data-tooltip="{% trans %}The user is an administrator.{% endtrans %}">{% call icon("admin") %}{% trans %} (Administrator){% endtrans %}{% endcall %}</span>
|
||||||
|
</div>
|
||||||
|
{%- elif user.has_restricted_role -%}
|
||||||
|
<div class="user-badge-icon">
|
||||||
|
<span class="with-tooltip above" data-tooltip="{% trans %}The user is restricted.{% endtrans %}">{% call icon("lock") %}{% trans %} (Restricted){% endtrans %}{% endcall %}</span>
|
||||||
|
</div>
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
<div class="user-info-container">
|
||||||
|
<div class="user-localpart">{{- user.localpart -}}</div>
|
||||||
|
{%- if user.display_name %}
|
||||||
|
<div class="user-display-name">{{- user.display_name -}}</div>
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endmacro -%}
|
||||||
|
|
||||||
{% macro showuri(uri, caller=None, id_=None) %}
|
{% macro showuri(uri, caller=None, id_=None) %}
|
||||||
{%- if uri is none -%}
|
{%- if uri is none -%}
|
||||||
<em>—</em>
|
<em>—</em>
|
||||||
|
|||||||
39
snikket_web/templates/policies.html
Normal file
39
snikket_web/templates/policies.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% from "library.j2" import standard_button %}
|
||||||
|
{% block head_lead %}
|
||||||
|
<title>{% trans %}Policies{% endtrans %} - {{ config["SITE_NAME"] }}</title>
|
||||||
|
{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<main>
|
||||||
|
<div class="box el-2">
|
||||||
|
<h1>{{ config["SITE_NAME"] }}</h1>
|
||||||
|
<h2>{% trans %}Policies{% endtrans %}</h2>
|
||||||
|
|
||||||
|
{% if config["TOS_URI"] or config["PRIVACY_URI"] -%}
|
||||||
|
<p>{% trans %}Use of this service is subject to the following policies:{% endtrans %}</p>
|
||||||
|
<ul>
|
||||||
|
{%- if config["TOS_URI"] %}
|
||||||
|
<li><a href="{{ config["TOS_URI"] }}">{% trans %}Terms of Service{% endtrans %}</a></li>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if config["PRIVACY_URI"] %}
|
||||||
|
<li><a href="{{ config["PRIVACY_URI"] }}">{% trans %}Privacy Policy{% endtrans %}</a></li>
|
||||||
|
{%- endif %}
|
||||||
|
</ul>
|
||||||
|
{%- else -%}
|
||||||
|
<p>{% trans %}Please contact the administrator of this instance if you have questions about policies.{% endtrans %}</p>
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
<p>{% trans url="https://snikket.org/app/privacy/" %}Use of the Snikket apps is subject to the <a href="{{url}}">Snikket Apps Privacy Policy</a>.{% endtrans %}</p>
|
||||||
|
|
||||||
|
{%- if config["ABUSE_EMAIL"] %}
|
||||||
|
<p>{% trans email=config["ABUSE_EMAIL"], domain=config["SNIKKET_DOMAIN"] %}To report policy violations or other abuse from this service, please send an email to {{email}}. Specify the domain name of this instance ({{domain}}) and include details of the incident(s).{% endtrans %}</p>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{%- call standard_button("back", url_for("index"), class="primary") -%}
|
||||||
|
{% trans %}Back to the main page{% endtrans %}
|
||||||
|
{%- endcall -%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
16
snikket_web/templates/security.txt
Normal file
16
snikket_web/templates/security.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# {{ config["SNIKKET_DOMAIN"] }} is running open-source software
|
||||||
|
# from the Snikket project: https://snikket.org/
|
||||||
|
|
||||||
|
{% if config["SECURITY_EMAIL"] -%}
|
||||||
|
# Security issues related to this service should be addressed to the
|
||||||
|
# following security contact:
|
||||||
|
Contact: mailto:{{ config["SECURITY_EMAIL"] }}
|
||||||
|
{% else -%}
|
||||||
|
# This service does not have a public security contact. You might find
|
||||||
|
# more information about the service at the following link:
|
||||||
|
Contact: https://{{ config["SNIKKET_DOMAIN"] }}/policies/
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
# Please report software defects to the project developers, per the
|
||||||
|
# instructions at the following link:
|
||||||
|
Contact: https://snikket.org/security/
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,28 +8,26 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2023-04-01 10:07+0200\n"
|
"POT-Creation-Date: 2023-12-12 18:22+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.12.1\n"
|
"Generated-By: Babel 2.14.0\n"
|
||||||
|
|
||||||
#: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10
|
#: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10
|
||||||
#: snikket_web/templates/admin_edit_circle.html:59
|
#: snikket_web/templates/admin_edit_circle.html:73
|
||||||
#: snikket_web/templates/admin_users.html:8
|
|
||||||
msgid "Login name"
|
msgid "Login name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:73 snikket_web/templates/admin_delete_user.html:12
|
#: snikket_web/admin.py:73 snikket_web/templates/admin_delete_user.html:12
|
||||||
#: snikket_web/templates/admin_edit_circle.html:60
|
#: snikket_web/templates/admin_edit_circle.html:74 snikket_web/user.py:63
|
||||||
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:63
|
|
||||||
msgid "Display name"
|
msgid "Display name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:77 snikket_web/templates/admin_edit_user.html:32
|
#: snikket_web/admin.py:77 snikket_web/templates/admin_edit_user.html:53
|
||||||
msgid "Access Level"
|
msgid "Access Level"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -50,170 +48,228 @@ msgid "Update user"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:90
|
#: snikket_web/admin.py:90
|
||||||
|
msgid "Restore account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:94
|
||||||
|
msgid "Unlock account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:98
|
||||||
msgid "Create password reset link"
|
msgid "Create password reset link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:108
|
#: snikket_web/admin.py:116
|
||||||
msgid "Password reset link created"
|
msgid "Password reset link created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:123
|
#: snikket_web/admin.py:128
|
||||||
msgid "User information updated."
|
msgid "User account restored"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:133
|
||||||
|
msgid "User account unlocked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:140
|
||||||
|
msgid "Could not restore user account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:145
|
#: snikket_web/admin.py:145
|
||||||
|
msgid "Could not unlock user account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:157
|
||||||
|
msgid "User information updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:179
|
||||||
msgid "Delete user permanently"
|
msgid "Delete user permanently"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:158
|
#: snikket_web/admin.py:192
|
||||||
msgid "User deleted"
|
msgid "User deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:196
|
#: snikket_web/admin.py:230
|
||||||
msgid "Password reset link not found"
|
msgid "Password reset link not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:208
|
#: snikket_web/admin.py:242
|
||||||
msgid "Password reset link deleted"
|
msgid "Password reset link deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:228
|
#: snikket_web/admin.py:262
|
||||||
msgid "Invite to circle"
|
msgid "Invite to circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:234
|
#: snikket_web/admin.py:268
|
||||||
msgid "At least one circle must be selected"
|
msgid "At least one circle must be selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:239
|
#: snikket_web/admin.py:273
|
||||||
msgid "Valid for"
|
msgid "Valid for"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:241
|
#: snikket_web/admin.py:275
|
||||||
msgid "One hour"
|
msgid "One hour"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:242
|
#: snikket_web/admin.py:276
|
||||||
msgid "Twelve hours"
|
msgid "Twelve hours"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:243
|
#: snikket_web/admin.py:277
|
||||||
msgid "One day"
|
msgid "One day"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:244
|
#: snikket_web/admin.py:278
|
||||||
msgid "One week"
|
msgid "One week"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:245
|
#: snikket_web/admin.py:279
|
||||||
msgid "Four weeks"
|
msgid "Four weeks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:251 snikket_web/templates/admin_edit_invite.html:17
|
#: snikket_web/admin.py:285 snikket_web/templates/admin_edit_invite.html:17
|
||||||
msgid "Invitation type"
|
msgid "Invitation type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:253 snikket_web/templates/library.j2:116
|
#: snikket_web/admin.py:287 snikket_web/templates/library.j2:139
|
||||||
msgid "Individual"
|
msgid "Individual"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:254 snikket_web/templates/library.j2:114
|
#: snikket_web/admin.py:288 snikket_web/templates/library.j2:137
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:260
|
#: snikket_web/admin.py:294
|
||||||
msgid "New invitation link"
|
msgid "New invitation link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:322
|
#: snikket_web/admin.py:356
|
||||||
msgid "Revoke"
|
msgid "Revoke"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:346
|
#: snikket_web/admin.py:380
|
||||||
msgid "Invitation created"
|
msgid "Invitation created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:362
|
#: snikket_web/admin.py:396
|
||||||
msgid "No such invitation exists"
|
msgid "No such invitation exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:377
|
#: snikket_web/admin.py:411
|
||||||
msgid "Invitation revoked"
|
msgid "Invitation revoked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:394 snikket_web/admin.py:442
|
#: snikket_web/admin.py:428 snikket_web/admin.py:476
|
||||||
#: snikket_web/templates/admin_delete_circle.html:10
|
#: snikket_web/templates/admin_delete_circle.html:10
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:44
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:399 snikket_web/templates/admin_circles.html:47
|
#: snikket_web/admin.py:433 snikket_web/templates/admin_circles.html:47
|
||||||
msgid "Create circle"
|
msgid "Create circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:429
|
#: snikket_web/admin.py:463
|
||||||
msgid "Circle created"
|
msgid "Circle created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:447
|
#: snikket_web/admin.py:481
|
||||||
msgid "Select user"
|
msgid "Select user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:452
|
#: snikket_web/admin.py:486
|
||||||
msgid "Update circle"
|
msgid "Update circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:458
|
#: snikket_web/admin.py:492
|
||||||
msgid "Add user"
|
msgid "Add user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:474 snikket_web/admin.py:563
|
#: snikket_web/admin.py:510 snikket_web/admin.py:609 snikket_web/admin.py:657
|
||||||
msgid "No such circle exists"
|
msgid "No such circle exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:511
|
#: snikket_web/admin.py:547
|
||||||
msgid "Circle data updated"
|
msgid "Circle data updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:521
|
#: snikket_web/admin.py:557
|
||||||
msgid "User added to circle"
|
msgid "User added to circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:530
|
#: snikket_web/admin.py:566
|
||||||
msgid "User removed from circle"
|
msgid "User removed from circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:547
|
#: snikket_web/admin.py:575
|
||||||
|
msgid "Chat removed from circle"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:593
|
||||||
msgid "Delete circle permanently"
|
msgid "Delete circle permanently"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:574
|
#: snikket_web/admin.py:620
|
||||||
msgid "Circle deleted"
|
msgid "Circle deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:640
|
#: snikket_web/admin.py:634
|
||||||
|
msgid "Group chat name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:639
|
||||||
|
msgid "Create group chat"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:669
|
||||||
|
msgid "New group chat added to circle"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/admin.py:736
|
||||||
msgid "Message contents"
|
msgid "Message contents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:646
|
#: snikket_web/admin.py:742
|
||||||
msgid "Only send to online users"
|
msgid "Only send to online users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:650
|
#: snikket_web/admin.py:746
|
||||||
msgid "Post to all users"
|
msgid "Post to all users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:654
|
#: snikket_web/admin.py:750
|
||||||
msgid "Send preview to yourself"
|
msgid "Send preview to yourself"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/admin.py:676
|
#: snikket_web/admin.py:772
|
||||||
msgid "Announcement sent!"
|
msgid "Announcement sent!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/infra.py:53
|
#: snikket_web/infra.py:56
|
||||||
msgid "Main"
|
msgid "Main"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/infra.py:78
|
||||||
|
msgid "Never"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/infra.py:95
|
||||||
|
msgid "Today"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/infra.py:101
|
||||||
|
msgid "Yesterday"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/infra.py:105
|
||||||
|
#, python-format
|
||||||
|
msgid "%(time)s ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:35
|
#: snikket_web/invite.py:35
|
||||||
msgid ""
|
msgid ""
|
||||||
"The account data you tried to import is too large to upload. Please "
|
"The account data you tried to import is too large to upload. Please "
|
||||||
@@ -382,31 +438,37 @@ msgid ""
|
|||||||
"Interest Company."
|
"Interest Company."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:4 snikket_web/templates/about.html:9
|
#: snikket_web/templates/about.html:4
|
||||||
msgid "About Snikket"
|
msgid "About Snikket"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/about.html:9
|
||||||
|
msgid "About this Service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:10
|
#: snikket_web/templates/about.html:10
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
|
"This is the Snikket service <em>%(site_name)s</em>, running open-source "
|
||||||
|
"software from the Snikket project."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/about.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
"To learn more about Snikket, visit the <a "
|
"To learn more about Snikket, visit the <a "
|
||||||
"href=\"%(snikket_url)s\">Snikket website</a>."
|
"href=\"%(snikket_url)s\">Snikket website</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:11
|
|
||||||
msgid "About this Service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:12
|
|
||||||
#, python-format
|
|
||||||
msgid "This is the Snikket service <em>%(site_name)s</em>."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:13
|
#: snikket_web/templates/about.html:13
|
||||||
|
msgid "View service policies"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/about.html:15
|
||||||
msgid "Licenses"
|
msgid "Licenses"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:14
|
#: snikket_web/templates/about.html:16
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The web portal software is licensed under the terms of the <a "
|
"The web portal software is licensed under the terms of the <a "
|
||||||
@@ -415,14 +477,14 @@ msgid ""
|
|||||||
"aforementioned link."
|
"aforementioned link."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:15
|
#: snikket_web/templates/about.html:17
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The source code of the web portal can be downloaded and viewed in <a "
|
"The source code of the web portal can be downloaded and viewed in <a "
|
||||||
"href=\"%(source_url)s\">its GitHub repository</a>."
|
"href=\"%(source_url)s\">its GitHub repository</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:16
|
#: snikket_web/templates/about.html:18
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The icons used in the web portal are <a href=\"%(source_url)s\">Google’s "
|
"The icons used in the web portal are <a href=\"%(source_url)s\">Google’s "
|
||||||
@@ -430,11 +492,11 @@ msgid ""
|
|||||||
"href=\"%(apache20_url)s\">Apache 2.0 License</a>."
|
"href=\"%(apache20_url)s\">Apache 2.0 License</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:17
|
#: snikket_web/templates/about.html:20
|
||||||
msgid "Trademarks"
|
msgid "Trademarks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:18
|
#: snikket_web/templates/about.html:21
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"“Snikket” and the parrot logo are trademarks of Snikket Community "
|
"“Snikket” and the parrot logo are trademarks of Snikket Community "
|
||||||
@@ -442,11 +504,11 @@ msgid ""
|
|||||||
" href=\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
|
" href=\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:19
|
#: snikket_web/templates/about.html:23
|
||||||
msgid "Software Versions"
|
msgid "Software Versions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:32
|
#: snikket_web/templates/about.html:37 snikket_web/templates/policies.html:34
|
||||||
msgid "Back to the main page"
|
msgid "Back to the main page"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -468,8 +530,8 @@ msgstr ""
|
|||||||
#: snikket_web/templates/admin_circles.html:6
|
#: snikket_web/templates/admin_circles.html:6
|
||||||
msgid ""
|
msgid ""
|
||||||
"Users who are in the same circle will see each other in their contact "
|
"Users who are in the same circle will see each other in their contact "
|
||||||
"list. In addition, each circle has a group chat where the circle members "
|
"list. In addition, each circle may have group chats where the circle "
|
||||||
"are included."
|
"members are included."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_circles.html:13
|
#: snikket_web/templates/admin_circles.html:13
|
||||||
@@ -481,7 +543,8 @@ msgid "Members"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_circles.html:15
|
#: snikket_web/templates/admin_circles.html:15
|
||||||
#: snikket_web/templates/admin_edit_circle.html:61
|
#: snikket_web/templates/admin_edit_circle.html:45
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:75
|
||||||
#: snikket_web/templates/admin_invites.html:24
|
#: snikket_web/templates/admin_invites.html:24
|
||||||
#: snikket_web/templates/admin_users.html:10
|
#: snikket_web/templates/admin_users.html:10
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
@@ -517,6 +580,25 @@ msgstr ""
|
|||||||
msgid "New circle"
|
msgid "New circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_create_circle_group_chat_form.html:5
|
||||||
|
msgid "Create new circle group chat"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_create_circle_group_chat_form.html:6
|
||||||
|
msgid "Add a chat to your circle so its members can hold group discussions."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_create_circle_group_chat_form.html:7
|
||||||
|
msgid "Tip:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_create_circle_group_chat_form.html:7
|
||||||
|
msgid ""
|
||||||
|
"This is only for creating group chats that automatically include "
|
||||||
|
"<em>all</em> members of the circle. If you want a normal group chat, "
|
||||||
|
"create it in the Snikket app instead."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_create_invite.html:3
|
#: snikket_web/templates/admin_create_invite.html:3
|
||||||
msgid "Create invitation"
|
msgid "Create invitation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -559,8 +641,8 @@ msgid "Delete circle %(circle_name)s"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_delete_circle.html:6
|
#: snikket_web/templates/admin_delete_circle.html:6
|
||||||
#: snikket_web/templates/admin_edit_circle.html:48
|
#: snikket_web/templates/admin_edit_circle.html:30
|
||||||
#: snikket_web/templates/admin_edit_circle.html:51
|
#: snikket_web/templates/admin_edit_circle.html:33
|
||||||
msgid "Delete circle"
|
msgid "Delete circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -596,7 +678,7 @@ msgid "Delete user %(user_name)s"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_delete_user.html:6
|
#: snikket_web/templates/admin_delete_user.html:6
|
||||||
#: snikket_web/templates/admin_edit_user.html:53
|
#: snikket_web/templates/admin_edit_user.html:74
|
||||||
msgid "Delete user"
|
msgid "Delete user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -619,69 +701,78 @@ msgstr ""
|
|||||||
msgid "This circle is managed automatically and cannot be removed or renamed."
|
msgid "This circle is managed automatically and cannot be removed or renamed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:17
|
#: snikket_web/templates/admin_edit_circle.html:19
|
||||||
#: snikket_web/templates/admin_edit_circle.html:33
|
|
||||||
msgid "Group chat address"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:20
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:36
|
|
||||||
#: snikket_web/templates/invite_success.html:15
|
|
||||||
#: snikket_web/templates/user_home.html:21
|
|
||||||
msgid "Copy address"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:26
|
|
||||||
msgid "Circle information"
|
msgid "Circle information"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:39
|
#: snikket_web/templates/admin_edit_circle.html:26
|
||||||
msgid "This circle has no group chat associated."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:44
|
|
||||||
msgid "Return to circle list"
|
msgid "Return to circle list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:49
|
#: snikket_web/templates/admin_edit_circle.html:31
|
||||||
msgid "Deleting a circle does not delete any users in the circle."
|
msgid "Deleting a circle does not delete any users in the circle."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:55
|
#: snikket_web/templates/admin_edit_circle.html:38
|
||||||
|
msgid "Group chats"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:39
|
||||||
|
msgid "These group chats will be available to all members of the circle."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:53
|
||||||
|
#, python-format
|
||||||
|
msgid "Delete group chat '%(name)s'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:61
|
||||||
|
msgid "This circle currently has no group chats."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:64
|
||||||
|
msgid "Add group chat"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:67
|
||||||
msgid "Circle members"
|
msgid "Circle members"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:71
|
#: snikket_web/templates/admin_edit_circle.html:68
|
||||||
|
msgid "All members of the circle will see each other in their contact list."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_circle.html:85
|
||||||
msgid "The user has been deleted from the server."
|
msgid "The user has been deleted from the server."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:71
|
#: snikket_web/templates/admin_edit_circle.html:85
|
||||||
#: snikket_web/templates/library.j2:108
|
#: snikket_web/templates/library.j2:131
|
||||||
msgid "deleted"
|
msgid "deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:77
|
#: snikket_web/templates/admin_edit_circle.html:91
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Remove user %(username)s from circle"
|
msgid "Remove user %(username)s from circle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:85
|
#: snikket_web/templates/admin_edit_circle.html:99
|
||||||
msgid "This circle currently has no members."
|
msgid "This circle currently has no members."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:87
|
#: snikket_web/templates/admin_edit_circle.html:101
|
||||||
msgid "Invite more members"
|
msgid "Invite more members"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:90
|
#: snikket_web/templates/admin_edit_circle.html:104
|
||||||
msgid "Add existing user"
|
msgid "Add existing user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:101
|
#: snikket_web/templates/admin_edit_circle.html:115
|
||||||
msgid "All users added"
|
msgid "All users added"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:102
|
#: snikket_web/templates/admin_edit_circle.html:116
|
||||||
msgid "All users on this service are already in this circle."
|
msgid "All users on this service are already in this circle."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -756,56 +847,90 @@ msgstr ""
|
|||||||
msgid "Edit user %(user_name)s"
|
msgid "Edit user %(user_name)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:22
|
#: snikket_web/templates/admin_edit_user.html:24
|
||||||
msgid "Edit user"
|
msgid "This user account is pending deletion"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_user.html:25
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"The owner of the account sent a deletion request on %(date)s using their "
|
||||||
|
"app."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:26
|
#: snikket_web/templates/admin_edit_user.html:26
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"The account has been locked, and will be automatically deleted "
|
||||||
|
"permanently in %(time)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_user.html:28
|
||||||
|
msgid ""
|
||||||
|
"If this was a mistake, you can cancel the deletion and restore the "
|
||||||
|
"account."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_user.html:34
|
||||||
|
msgid "This user account is locked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_user.html:35
|
||||||
|
msgid ""
|
||||||
|
"The user will not be able to log in to their account until it is unlocked"
|
||||||
|
" again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_user.html:41
|
||||||
|
msgid "Edit user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/admin_edit_user.html:46
|
||||||
msgid "The login name cannot be changed."
|
msgid "The login name cannot be changed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:33
|
#: snikket_web/templates/admin_edit_user.html:54
|
||||||
msgid ""
|
msgid ""
|
||||||
"The access level of a user determines what interactions are allowed for "
|
"The access level of a user determines what interactions are allowed for "
|
||||||
"them on your Snikket service."
|
"them on your Snikket service."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:40
|
#: snikket_web/templates/admin_edit_user.html:61
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
|
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:50
|
#: snikket_web/templates/admin_edit_user.html:71
|
||||||
msgid "Return to user list"
|
msgid "Return to user list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:58
|
#: snikket_web/templates/admin_edit_user.html:79
|
||||||
msgid "Further actions"
|
msgid "Further actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:60
|
#: snikket_web/templates/admin_edit_user.html:81
|
||||||
msgid "Reset password"
|
msgid "Reset password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:63
|
#: snikket_web/templates/admin_edit_user.html:84
|
||||||
msgid ""
|
msgid ""
|
||||||
"If the user has lost their password, you can use the button below to "
|
"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,"
|
"create a special link which allows to change the password of the account,"
|
||||||
" once."
|
" once."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:68
|
#: snikket_web/templates/admin_edit_user.html:89
|
||||||
msgid "Debug information"
|
msgid "Debug information"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:70
|
#: snikket_web/templates/admin_edit_user.html:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"In some cases, extended information about the user account and the "
|
"In some cases, extended information about the user account and the "
|
||||||
"connected devices is necessary to troubleshoot issues. The button below "
|
"connected devices is necessary to troubleshoot issues. The button below "
|
||||||
"reveals this (sensitive) information."
|
"reveals this (sensitive) information."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:74
|
#: snikket_web/templates/admin_edit_user.html:95
|
||||||
msgid "Show debug information"
|
msgid "Show debug information"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -996,20 +1121,20 @@ msgid ""
|
|||||||
"your Snikket server. Use it wisely."
|
"your Snikket server. Use it wisely."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_users.html:19
|
#: snikket_web/templates/admin_users.html:8
|
||||||
msgid "The user is an administrator."
|
msgid "User"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_users.html:19
|
#: snikket_web/templates/admin_users.html:9
|
||||||
msgid " (Administrator)"
|
msgid "Last active"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_users.html:22
|
#: snikket_web/templates/admin_users.html:22
|
||||||
msgid "The user is restricted."
|
msgid "Deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/admin_users.html:22
|
#: snikket_web/templates/admin_users.html:24
|
||||||
msgid " (Restricted)"
|
msgid "Locked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/app.html:4
|
#: snikket_web/templates/app.html:4
|
||||||
@@ -1093,20 +1218,20 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_register.html:14
|
#: snikket_web/templates/invite_register.html:14
|
||||||
#: snikket_web/templates/invite_view.html:39
|
#: snikket_web/templates/invite_view.html:46
|
||||||
msgid "App already installed?"
|
msgid "App already installed?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_register.html:16
|
#: snikket_web/templates/invite_register.html:16
|
||||||
#: snikket_web/templates/invite_reset_view.html:21
|
#: snikket_web/templates/invite_reset_view.html:21
|
||||||
#: snikket_web/templates/invite_view.html:41
|
#: snikket_web/templates/invite_view.html:48
|
||||||
#: snikket_web/templates/invite_view.html:84
|
#: snikket_web/templates/invite_view.html:91
|
||||||
#: snikket_web/templates/invite_view.html:112
|
#: snikket_web/templates/invite_view.html:119
|
||||||
msgid "Open the app"
|
msgid "Open the app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_register.html:18
|
#: snikket_web/templates/invite_register.html:18
|
||||||
#: snikket_web/templates/invite_view.html:43
|
#: snikket_web/templates/invite_view.html:50
|
||||||
msgid "This button works only if you have the app installed already!"
|
msgid "This button works only if you have the app installed already!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1135,11 +1260,11 @@ msgstr ""
|
|||||||
msgid "Reset your password | Snikket"
|
msgid "Reset your password | Snikket"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_reset.html:15
|
#: snikket_web/templates/invite_reset.html:14
|
||||||
msgid "Reset your password online"
|
msgid "Reset your password online"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_reset.html:16
|
#: snikket_web/templates/invite_reset.html:15
|
||||||
msgid ""
|
msgid ""
|
||||||
"To reset your password online, fill out the fields below and confirm "
|
"To reset your password online, fill out the fields below and confirm "
|
||||||
"using the button."
|
"using the button."
|
||||||
@@ -1202,7 +1327,7 @@ msgid "You will then be prompted to enter a new password for your account."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_reset_view.html:29
|
#: snikket_web/templates/invite_reset_view.html:29
|
||||||
#: snikket_web/templates/invite_view.html:45
|
#: snikket_web/templates/invite_view.html:52
|
||||||
msgid "Alternatives"
|
msgid "Alternatives"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1232,6 +1357,11 @@ msgstr ""
|
|||||||
msgid "Your address"
|
msgid "Your address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/invite_success.html:15
|
||||||
|
#: snikket_web/templates/user_home.html:21
|
||||||
|
msgid "Copy address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_success.html:17
|
#: snikket_web/templates/invite_success.html:17
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can now set up your legacy XMPP client with the above address and the"
|
"You can now set up your legacy XMPP client with the above address and the"
|
||||||
@@ -1288,15 +1418,22 @@ msgid ""
|
|||||||
"privacy-friendly chat app."
|
"privacy-friendly chat app."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:20
|
#: snikket_web/templates/invite_view.html:23
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"By continuing, you agree to the <a href=\"%(tos_uri)s\">Terms of "
|
||||||
|
"Service</a> and <a href=\"%(privacy_uri)s\">Privacy Policy</a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/invite_view.html:27
|
||||||
msgid "Get started"
|
msgid "Get started"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:22
|
#: snikket_web/templates/invite_view.html:29
|
||||||
msgid "Install the Snikket App on your Android or iOS device."
|
msgid "Install the Snikket App on your Android or iOS device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:24
|
#: snikket_web/templates/invite_view.html:31
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Install the Snikket App on your Android device (<a "
|
"Install the Snikket App on your Android device (<a "
|
||||||
@@ -1304,30 +1441,30 @@ msgid ""
|
|||||||
"target=\"_blank\">iOS coming soon!</a>)."
|
"target=\"_blank\">iOS coming soon!</a>)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:28
|
#: snikket_web/templates/invite_view.html:35
|
||||||
msgid "Get it on Google Play"
|
msgid "Get it on Google Play"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:30
|
#: snikket_web/templates/invite_view.html:37
|
||||||
#: snikket_web/templates/invite_view.html:80
|
#: snikket_web/templates/invite_view.html:87
|
||||||
msgid "Download on the App Store"
|
msgid "Download on the App Store"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:32
|
#: snikket_web/templates/invite_view.html:39
|
||||||
msgid "Get it on F-Droid"
|
msgid "Get it on F-Droid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:35
|
#: snikket_web/templates/invite_view.html:42
|
||||||
msgid "Send to mobile device"
|
msgid "Send to mobile device"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:38
|
#: snikket_web/templates/invite_view.html:45
|
||||||
msgid ""
|
msgid ""
|
||||||
"After installation the app should automatically open and prompt you to "
|
"After installation the app should automatically open and prompt you to "
|
||||||
"create an account. If not, simply click the button below."
|
"create an account. If not, simply click the button below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:46
|
#: snikket_web/templates/invite_view.html:53
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can connect to Snikket using any XMPP-compatible software. If the "
|
"You can connect to Snikket using any XMPP-compatible software. If the "
|
||||||
@@ -1335,75 +1472,91 @@ msgid ""
|
|||||||
"href=\"%(register_url)s\">register an account manually</a>."
|
"href=\"%(register_url)s\">register an account manually</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:52
|
#: snikket_web/templates/invite_view.html:59
|
||||||
msgid "Scan invite code"
|
msgid "Scan invite code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:55
|
|
||||||
#: snikket_web/templates/invite_view.html:62
|
#: snikket_web/templates/invite_view.html:62
|
||||||
#: snikket_web/templates/invite_view.html:74
|
#: snikket_web/templates/invite_view.html:69
|
||||||
#: snikket_web/templates/invite_view.html:90
|
#: snikket_web/templates/invite_view.html:81
|
||||||
#: snikket_web/templates/invite_view.html:102
|
#: snikket_web/templates/invite_view.html:97
|
||||||
#: snikket_web/templates/invite_view.html:118
|
#: snikket_web/templates/invite_view.html:109
|
||||||
|
#: snikket_web/templates/invite_view.html:125
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:58
|
#: snikket_web/templates/invite_view.html:65
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can transfer this invite to your mobile device by scanning a code "
|
"You can transfer this invite to your mobile device by scanning a code "
|
||||||
"with your camera. You can use either a QR scanner app or the Snikket app "
|
"with your camera. You can use either a QR scanner app or the Snikket app "
|
||||||
"itself."
|
"itself."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:71
|
#: snikket_web/templates/invite_view.html:78
|
||||||
msgid "Install on iOS"
|
msgid "Install on iOS"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:77
|
#: snikket_web/templates/invite_view.html:84
|
||||||
msgid ""
|
msgid ""
|
||||||
"After downloading Snikket from the App Store, you have to return to this "
|
"After downloading Snikket from the App Store, you have to return to this "
|
||||||
"invite link and tap on \"Open the app\" to proceed."
|
"invite link and tap on \"Open the app\" to proceed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:79
|
#: snikket_web/templates/invite_view.html:86
|
||||||
msgid "First download Snikket from the App Store using the button below:"
|
msgid "First download Snikket from the App Store using the button below:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:81
|
#: snikket_web/templates/invite_view.html:88
|
||||||
#: snikket_web/templates/invite_view.html:109
|
#: snikket_web/templates/invite_view.html:116
|
||||||
msgid ""
|
msgid ""
|
||||||
"After the installation is complete, you can return to this page and tap "
|
"After the installation is complete, you can return to this page and tap "
|
||||||
"the \"Open the app\" button to continue with the setup:"
|
"the \"Open the app\" button to continue with the setup:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:99
|
#: snikket_web/templates/invite_view.html:106
|
||||||
#: snikket_web/templates/invite_view.html:108
|
#: snikket_web/templates/invite_view.html:115
|
||||||
msgid "Install via F-Droid"
|
msgid "Install via F-Droid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:105
|
#: snikket_web/templates/invite_view.html:112
|
||||||
msgid ""
|
msgid ""
|
||||||
"After installing Snikket via F-Droid, you have to return to this invite "
|
"After installing Snikket via F-Droid, you have to return to this invite "
|
||||||
"link and tap on \"Open the app\" to proceed."
|
"link and tap on \"Open the app\" to proceed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:107
|
#: snikket_web/templates/invite_view.html:114
|
||||||
msgid "First install Snikket from F-Droid using the button below:"
|
msgid "First install Snikket from F-Droid using the button below:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/library.j2:18
|
#: snikket_web/templates/library.j2:19
|
||||||
|
msgid "The user is an administrator."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/library.j2:19
|
||||||
|
msgid " (Administrator)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/library.j2:23
|
||||||
|
msgid "The user is restricted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/library.j2:23
|
||||||
|
msgid " (Restricted)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/library.j2:41
|
||||||
msgid "Copy link"
|
msgid "Copy link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/library.j2:81
|
#: snikket_web/templates/library.j2:104
|
||||||
msgid "Invalid input"
|
msgid "Invalid input"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/library.j2:122
|
#: snikket_web/templates/library.j2:145
|
||||||
msgid "Can be used multiple times to create accounts on this Snikket service."
|
msgid "Can be used multiple times to create accounts on this Snikket service."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/library.j2:124
|
#: snikket_web/templates/library.j2:147
|
||||||
msgid "Can be used once to create an account on this Snikket service."
|
msgid "Can be used once to create an account on this Snikket service."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1426,6 +1579,43 @@ msgid ""
|
|||||||
"<em>@%(snikket_domain)s</em>. Your password was not sent."
|
"<em>@%(snikket_domain)s</em>. Your password was not sent."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/policies.html:4 snikket_web/templates/policies.html:10
|
||||||
|
msgid "Policies"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/policies.html:13
|
||||||
|
msgid "Use of this service is subject to the following policies:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/policies.html:16
|
||||||
|
msgid "Terms of Service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/policies.html:19
|
||||||
|
msgid "Privacy Policy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/policies.html:23
|
||||||
|
msgid ""
|
||||||
|
"Please contact the administrator of this instance if you have questions "
|
||||||
|
"about policies."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/policies.html:26
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Use of the Snikket apps is subject to the <a href=\"%(url)s\">Snikket "
|
||||||
|
"Apps Privacy Policy</a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: snikket_web/templates/policies.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"To report policy violations or other abuse from this service, please send"
|
||||||
|
" an email to %(email)s. Specify the domain name of this instance "
|
||||||
|
"(%(domain)s) and include details of the incident(s)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/templates/unauth.html:16
|
#: snikket_web/templates/unauth.html:16
|
||||||
msgid "Operation successful"
|
msgid "Operation successful"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,8 @@ action/logout:logout
|
|||||||
action/login:login
|
action/login:login
|
||||||
action/exit_to_app:exit_to_app
|
action/exit_to_app:exit_to_app
|
||||||
action/lock:lock
|
action/lock:lock
|
||||||
|
action/lock_open:lock_open
|
||||||
|
action/restore_from_trash:restore_from_trash
|
||||||
communication/import_export:import_export
|
communication/import_export:import_export
|
||||||
communication/qr_code:qrcode
|
communication/qr_code:qrcode
|
||||||
communication/vpn_key:passwd
|
communication/vpn_key:passwd
|
||||||
|
|||||||
6
tools/import-icons.sh
Normal file → Executable file
6
tools/import-icons.sh
Normal file → Executable file
@@ -9,9 +9,9 @@ set -euo pipefail
|
|||||||
# FLAVOR one of '', 'round', 'sharp', 'outlined', 'twoshade'
|
# FLAVOR one of '', 'round', 'sharp', 'outlined', 'twoshade'
|
||||||
# SVGOUT path to the newly created SVG file
|
# SVGOUT path to the newly created SVG file
|
||||||
root="$1/src"
|
root="$1/src"
|
||||||
iconlist_file="$2"
|
iconlist_file="${2-tools/icons.list}"
|
||||||
flavor="$3"
|
flavor="${3-round}"
|
||||||
output_file="$4"
|
output_file="${4-snikket_web/static/img/icons.svg}"
|
||||||
|
|
||||||
printf '<svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n<defs>\n' > "$output_file"
|
printf '<svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n<defs>\n' > "$output_file"
|
||||||
printf '<!-- These icons are sourced from Google’s Material Icons set,\nlicensed under the terms of the Apache 2.0 License -->\n' >> "$output_file"
|
printf '<!-- These icons are sourced from Google’s Material Icons set,\nlicensed under the terms of the Apache 2.0 License -->\n' >> "$output_file"
|
||||||
|
|||||||
Reference in New Issue
Block a user