Compare commits

...

103 Commits

Author SHA1 Message Date
Matthew Wild
6407eb90db Explicitly set cookie SameSite attribute to Lax
With 'Secure' set, it may default to 'None', which we don't need or want.

'Strict' is not suitable for session cookies - the user would see the login
screen when navigating from another site (e.g. hosting dashboard) and we
already have CSRF protection on forms.
2024-04-29 11:18:55 +01:00
Matthew Wild
a8c6b1a70c Merge pull request #186 from snikket-im/cookie-secure-attribute
Add 'secure' attribute to session cookies
2024-04-29 11:09:44 +01:00
Matthew Wild
67c94bb045 Add 'secure' attribute to session cookies 2024-04-29 11:08:30 +01:00
Weblate
f4c1173a34 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2024-04-28 08:40:16 +00:00
Jonas Schäfer
e39b0082b1 Merge pull request #185 from Zash/translate-welcome
Translate welcome message
2024-04-28 10:39:57 +02:00
Kim Alvefur
9eb187a951 Make welcome message translatable 2024-04-27 14:22:39 +02:00
Kim Alvefur
b928e74a74 make extract_translations 2024-04-27 14:21:32 +02:00
Andrey
75c0f504d0 Translated using Weblate (Russian)
Currently translated at 100.0% (368 of 368 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2024-04-23 17:14:06 +00:00
Matthew Wild
7c0310a141 Merge pull request #184 from Zash/really-fix-default-invite-role
Specify a default role in invite form
2024-04-19 14:50:01 +01:00
Kim Alvefur
5e2e645787 Specify a default role in invite form
Actually in the invite form this time
2024-04-19 15:48:40 +02:00
Weblate
9b31894e85 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2024-04-19 13:38:57 +00:00
Kim Alvefur
a4472e1a44 Translated using Weblate (Swedish)
Currently translated at 100.0% (362 of 362 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/sv/
2024-04-19 13:38:56 +00:00
Matthew Wild
b99cae84de Update translations 2024-04-19 14:38:45 +01:00
Matthew Wild
1cac19e4c9 Merge pull request #183 from Zash/default-invite-role
Specify a default role in invite form
2024-04-19 14:37:22 +01:00
Kim Alvefur
d4883765b2 Specify a default role in invite form
The role creation appears to fail without an error, only refreshing the
page unless a role is selected.
2024-04-19 15:30:20 +02:00
Weblate
041f26274b Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2024-04-17 08:41:42 +00:00
Matthew Wild
82db30ffd9 Merge pull request #182 from snikket-im/invitation-improvements
Allow selecting a role when creating an invitation
2024-04-17 09:41:31 +01:00
Matthew Wild
b8684329b4 Fix syntax error in template 2024-04-16 21:30:19 +01:00
Matthew Wild
7e26b5f994 Update translations 2024-04-16 21:22:07 +01:00
Matthew Wild
4bdcb46a8a Allow selecting a role when creating an invitation
Includes some reorganization and prettification of the creation form.
2024-04-16 21:16:06 +01:00
Matthew Wild
ed6f413c18 Don't fail if active user metrics are unavailable 2024-04-16 20:42:48 +01:00
Weblate
f63549ee87 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2024-04-16 14:51:58 +00:00
Matthew Wild
bd71ab1449 Update translations 2024-04-16 15:51:47 +01:00
Matthew Wild
220bf9994b Show invitation hint when user is alone on their instance 2024-04-16 15:09:16 +01:00
Matthew Wild
33d28e5890 Show active user counts in instance metrics 2024-04-16 15:09:16 +01:00
Matthew Wild
f0f0fa15c9 Small clarifications to the invitation type UI 2024-04-16 15:09:16 +01:00
Matthew Wild
30a9a6816f prosodyclient: Skip adding metadata of broken avatars 2024-04-16 15:09:16 +01:00
Sergio Moreno López
970b8fa7f1 Translated using Weblate (Spanish)
Currently translated at 39.6% (143 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/es/
2024-04-14 21:02:20 +00:00
Sergio Moreno López
629d725ff5 Translated using Weblate (Spanish)
Currently translated at 36.0% (130 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/es/
2024-04-05 21:27:23 +00:00
Sergio Moreno López
6998e66b22 Translated using Weblate (Spanish)
Currently translated at 29.9% (108 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/es/
2024-03-31 22:27:22 +00:00
Matthew Wild
c668c4c56a Added translation using Weblate (Spanish) 2024-03-29 10:13:30 +00:00
Matthew Wild
a13fbd87a6 Merge pull request #181 from snikket-im/feature/handle-invalid-avatars-gracefully
Handle broken/incorrect avatar metadata gracefully
2024-03-11 15:30:24 +00:00
Jonas Schäfer
7ffcd76cea Handle broken/incorrect avatar metadata gracefully
Fixes #180.
2024-03-10 10:49:29 +01:00
Federico
bda0f52320 Translated using Weblate (Italian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/it/
2024-01-20 23:01:47 +00:00
Matthew Wild
5efc2a671e Merge pull request #177 from snikket-im/feature/fix-requirements
Fix python requirements to actually work
2024-01-16 16:12:37 +00:00
Jonas Schäfer
1578654816 Fix python requirements to actually work
Didn't do deep research here, just fitted it against the current Docker
image release.
2024-01-16 16:52:47 +01:00
Matthew Wild
e8ab33e12f Merge pull request #175 from snikket-im/fix/html-tag-typo
templates: Fix typo in closing form tag
2024-01-11 15:29:26 +00:00
Matthew Wild
712b0dc502 templates: Fix typo in closing form tag 2024-01-11 15:20:17 +00:00
Matthew Wild
e56c0f9029 github: Strip Generated-By in both places 2024-01-09 15:07:21 +00:00
Weblate
794b48a50b Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2024-01-09 14:52:58 +00:00
Matthew Wild
393b30cf5c Remove collapsible flag from column to fix mobile display 2024-01-09 14:52:43 +00:00
Matthew Wild
97198a1da4 Ignore generator name/version when checking if translations are up to date 2024-01-09 14:51:34 +00:00
Weblate
3ba1195fbe Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2024-01-09 10:09:07 +00:00
Matthew Wild
121f3eddb5 Merge pull request #174 from snikket-im/fix/password-validation
Fix password validation
2024-01-09 10:08:58 +00:00
Matthew Wild
38ad81b0e2 Validate passwords as early as possible
Prosody now enforces some password policies, including a minimum length of 10
characters. If this fails, we currently show a rather unfriendly error to the
user. By adding this validation, the user should get nicer feedback and never
see that error.

There is a known issue that we don't currently validate all the policies that
Prosody does - for example, Prosody won't accept a password that contains the
username.

Ultimately we should fix the error handling anyway.
2024-01-08 22:58:46 +00:00
Matthew Wild
ec94c64dbc Handle errors on password change 2024-01-08 22:50:36 +00:00
Andrey
28a9a33aa1 Translated using Weblate (Russian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2024-01-07 16:01:03 +00:00
Kim Alvefur
97eeb85032 Merge pull request #173 from Zash/fix-scope-restricted
Fix login for restricted users
2024-01-07 12:40:26 +01:00
Kim Alvefur
ceef9f024c Fix login for restricted users
Previously this led to an OAuth grant with an empty scope, which could
not be used for anything.
2024-01-07 00:03:38 +01:00
Andrey
40c8b9cc36 Translated using Weblate (Russian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2024-01-06 05:01:03 +00:00
Matthew Wild
95a8ac1387 Enable Russian and Ukranian languages by default 2024-01-04 14:11:27 +00:00
Roberto Resoli
4c6e26e66b Translated using Weblate (Italian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/it/
2024-01-04 10:17:34 +00:00
Andrey
ad2b351a99 Translated using Weblate (Russian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2024-01-04 10:17:33 +00:00
pep
3bda1f9863 Translated using Weblate (French)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2024-01-04 10:17:33 +00:00
Matthew Wild
f46d95db66 Merge pull request #172 from Zash/form-translation-fix
Fix incomplete translation of Announcement form and Limited user role
2024-01-04 09:05:33 +00:00
Kim Alvefur
ddfdd2fd55 Remove stray HTML closing tag
Where is the <form> ???
2024-01-04 09:54:45 +01:00
Kim Alvefur
17d586e384 Fix translation of "Limited"
Strings in Forms must use lazy_gettext aka _l
2024-01-04 09:54:43 +01:00
Kim Alvefur
dbec07d149 Fix translation of AnnouncementForm
Form translation things must use lazy_gettext aka _l
2024-01-04 09:53:03 +01:00
Weblate
ebf142b505 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2024-01-03 16:12:45 +00:00
Matthew Wild
0539d0ab88 translation: Update copyright year to satisfy lint check 2024-01-03 16:12:28 +00:00
Jonas Schäfer
2736bff76b Translated using Weblate (German)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/de/
2024-01-03 16:08:48 +00:00
Dmytro Vozniuk
192601f387 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/uk/
2023-12-25 16:00:58 +00:00
Dmytro Vozniuk
bc9cfeabab Translated using Weblate (Russian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2023-12-25 16:00:53 +00:00
Dmytro Vozniuk
b770086071 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/uk/
2023-12-24 04:00:53 +00:00
Dmytro Vozniuk
b2c1fdd23b Translated using Weblate (Russian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2023-12-24 04:00:52 +00:00
Dmytro Vozniuk
906978556e Translated using Weblate (Ukrainian)
Currently translated at 79.2% (286 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/uk/
2023-12-19 13:00:48 +00:00
riccio
274c8e4658 Translated using Weblate (Italian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/it/
2023-12-19 13:00:48 +00:00
Dmytro Vozniuk
257a44dac2 Translated using Weblate (Ukrainian)
Currently translated at 29.6% (107 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/uk/
2023-12-17 22:49:22 +00:00
Roberto Resoli
f393a3980b Translated using Weblate (Italian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/it/
2023-12-17 22:49:22 +00:00
riccio
badff7eed8 Translated using Weblate (Italian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/it/
2023-12-17 22:49:22 +00:00
Andrey
384e07c2a9 Translated using Weblate (Russian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2023-12-17 22:49:21 +00:00
pep
89724a9712 Translated using Weblate (French)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2023-12-17 22:49:21 +00:00
misiek
94f4325f40 Translated using Weblate (Polish)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/pl/
2023-12-17 22:49:21 +00:00
Matthew Wild
af1285b650 Translated using Weblate (Russian)
Currently translated at 99.4% (359 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2023-12-16 14:41:14 +00:00
Andrey
52eba53d8e Translated using Weblate (Russian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/ru/
2023-12-16 08:38:56 +00:00
pep
94f240687a Translated using Weblate (French)
Currently translated at 99.7% (360 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2023-12-16 08:38:56 +00:00
misiek
1b2bdfa881 Translated using Weblate (Polish)
Currently translated at 93.0% (336 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/pl/
2023-12-16 08:38:56 +00:00
Kim Alvefur
271f450c86 Translated using Weblate (Swedish)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/sv/
2023-12-16 00:19:29 +00:00
Deleted User
6186e8b635 Translated using Weblate (French)
Currently translated at 99.7% (360 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2023-12-16 00:19:29 +00:00
pep
dfc6c392c3 Translated using Weblate (French)
Currently translated at 99.7% (360 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2023-12-16 00:19:28 +00:00
uira
0ec9a2ae02 Translated using Weblate (Indonesian)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/id/
2023-12-15 16:11:00 +00:00
Weblate
09fcf64818 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2023-12-15 15:05:45 +00:00
Kim Alvefur
c25db5c3ae Translated using Weblate (Swedish)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/sv/
2023-12-15 15:05:44 +00:00
Deleted User
c85fff7581 Translated using Weblate (French)
Currently translated at 99.7% (360 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2023-12-15 15:05:44 +00:00
Link Mauve
039f4b8210 Translated using Weblate (French)
Currently translated at 99.7% (360 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2023-12-15 15:05:44 +00:00
uira
7be7ee67c2 Translated using Weblate (Indonesian)
Currently translated at 93.0% (336 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/id/
2023-12-15 15:05:44 +00:00
Jonas Schäfer
6f5fc14dbc Translated using Weblate (German)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/de/
2023-12-15 15:05:44 +00:00
Matthew Wild
65edd3a52b Merge pull request #169 from snikket-im/feature/tweak-user-profile-display
Tweak user profile display
2023-12-15 15:05:36 +00:00
Matthew Wild
ab7149403a Update translations pot (no actual string changes) 2023-12-15 15:04:46 +00:00
Matthew Wild
5b2f3db867 Update user listing in 'edit circle' view to use new user profile layout 2023-12-15 15:00:13 +00:00
Matthew Wild
e12941eab0 Improve user name/JID display
Show the display name more prominently (if one is set), as it is more
"friendly" name. The JID is now displayed instead of just the username, as
this makes it more clear what is being displayed.

The inspiration for this change was the observation that many users on my own
server have the same display name and username, causing a repetitive list
like:

  Jane
  jane

  Robert
  robert

  Sally
  sally

  Steven
  steven

In addition, some accounts do not have a display name set, so it was not
obvious why some people had their name rendered once, and some twice, and why
the capitalization differences.
2023-12-15 14:41:17 +00:00
Andriy Utkin
eda3f4826c Translated using Weblate (Ukrainian)
Currently translated at 6.0% (22 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/uk/
2023-12-15 11:56:32 +00:00
pep
61161eb472 Translated using Weblate (French)
Currently translated at 96.9% (350 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2023-12-15 11:56:31 +00:00
Matthew Wild
325826c19b Merge pull request #168 from snikket-im/fix/rc2-runtime-errors
Fix a couple of runtime errors
2023-12-14 15:35:04 +00:00
Matthew Wild
587839f852 Added translation using Weblate (Ukrainian) 2023-12-14 13:11:43 +00:00
Matthew Wild
7411f4a9e1 prosodyclient: Use empty name if none provided by the server
In parallel, I have updated the server to use the group name for groups with
no name (the MUCs with no name are typically the default auto-created group
MUC).
2023-12-14 12:43:59 +00:00
Matthew Wild
d63ae4768a infra: Fix string to use correct translation function name 2023-12-14 12:43:52 +00:00
Weblate
92a8da724f Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2023-12-12 18:25:32 +00:00
Matthew Wild
ea3a081b6c Merge pull request #167 from snikket-im/fix/remove-useless-qr-js
Remove broken/needless JS from certain pages
2023-12-12 18:25:20 +00:00
Matthew Wild
0647ba2601 Remove broken/needless JS from certain pages 2023-12-12 18:24:01 +00:00
Kim Alvefur
2769036f94 Translated using Weblate (Swedish)
Currently translated at 100.0% (361 of 361 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/sv/
2023-12-09 16:59:59 +00:00
Weblate
c76befad1c Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/
2023-12-08 12:14:28 +00:00
Matthew Wild
74ecfb8653 Merge pull request #166 from snikket-im/feature/admin-users-ui-updates-dec-23
Admin user management UI updates (Dec 23)
2023-12-08 12:14:19 +00:00
46 changed files with 11415 additions and 3880 deletions

View File

@@ -66,10 +66,10 @@ jobs:
pip install flask-babel
- name: Linting
run: |
sed -ri '/^"POT-Creation-Date: /d' snikket_web/translations/messages.pot
sed -ri '/^"POT-Creation-Date: /d;/^"Generated-By: /d' snikket_web/translations/messages.pot
git add snikket_web/translations/messages.pot
make extract_translations
sed -ri '/^"POT-Creation-Date: /d' snikket_web/translations/messages.pot
sed -ri '/^"POT-Creation-Date: /d;/^"Generated-By: /d' snikket_web/translations/messages.pot
git diff --exit-code --color -- snikket_web/translations/messages.pot

View File

@@ -1,9 +1,10 @@
aiohttp~=3.6
quart~=0.17,<0.18
flask-wtf~=1.0
aiohttp~=3.8,<3.9
quart~=0.18,<0.19
flask-wtf~=1.1,<1.2
hsluv~=5.0
flask-babel~=1.0
email-validator~=1.1
flask-babel~=2.0,<3
email-validator~=1.3
environ-config~=20.0
wtforms~=3.0
wtforms~=3.0,<4
typing-extensions
werkzeug~=2.2,<3

View File

@@ -158,7 +158,9 @@ class AppConfig:
"id",
"it",
"pl",
"ru",
"sv",
"uk",
"zh_Hans_CN",
], converter=autosplit)
apple_store_url = environ.var(
@@ -210,6 +212,8 @@ def create_app() -> quart.Quart:
app.config["PRIVACY_URI"] = config.privacy_uri
app.config["ABUSE_EMAIL"] = config.abuse_email
app.config["SECURITY_EMAIL"] = config.security_email
app.config["SESSION_COOKIE_SECURE"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
app.context_processor(proc)
app.register_error_handler(

View File

@@ -76,10 +76,11 @@ class EditUserForm(BaseForm):
role = wtforms.RadioField(
_l("Access Level"),
choices=[
("prosody:restricted", _("Limited")),
("prosody:restricted", _l("Limited")),
("prosody:registered", _l("Normal user")),
("prosody:admin", _l("Administrator")),
],
default="prosody:registered",
)
action_save = wtforms.SubmitField(
@@ -290,6 +291,16 @@ class InvitePost(BaseForm):
default="account",
)
role = wtforms.RadioField(
_l("Access Level"),
choices=[
("prosody:restricted", _l("Limited")),
("prosody:registered", _l("Normal user")),
("prosody:admin", _l("Administrator")),
],
default="prosody:registered",
)
action_create_invite = wtforms.SubmitField(
_l("New invitation link")
)
@@ -369,11 +380,13 @@ async def create_invite() -> typing.Union[str, werkzeug.Response]:
if form.type_.data == "group":
invite = await client.create_group_invite(
group_ids=form.circles.data,
role_names=[form.role.data],
ttl=form.lifetime.data,
)
else:
invite = await client.create_account_invite(
group_ids=form.circles.data,
role_names=[form.role.data],
ttl=form.lifetime.data,
)
await flash(
@@ -733,21 +746,21 @@ def get_system_stats() -> typing.MutableMapping[
class AnnouncementForm(BaseForm):
text = wtforms.StringField(
_("Message contents"),
_l("Message contents"),
widget=wtforms.widgets.TextArea(),
validators=[wtforms.validators.DataRequired()],
)
online_only = wtforms.BooleanField(
_("Only send to online users"),
_l("Only send to online users"),
)
action_post_all = wtforms.SubmitField(
_("Post to all users"),
_l("Post to all users"),
)
action_send_preview = wtforms.SubmitField(
_("Send preview to yourself"),
_l("Send preview to yourself"),
)
@@ -812,6 +825,11 @@ async def system() -> typing.Union[str, werkzeug.Response]:
except KeyError:
pass
try:
metrics["users"] = prosody_metrics["users"]
except KeyError:
pass
for k in list(metrics.keys()):
if metrics[k] is None:
# so that defaulting in jinja works

View File

@@ -53,7 +53,7 @@ def flatten(a: typing.Iterable, levels: int = 1) -> typing.Iterable:
def circle_name(c: typing.Any) -> str:
if c.id_ == "default" and c.name == "default":
return _("Main")
return _l("Main")
return c.name

View File

@@ -116,6 +116,10 @@ class RegisterForm(BaseForm):
password = wtforms.PasswordField(
_l("Password"),
validators=[
wtforms.validators.InputRequired(),
wtforms.validators.Length(min=10),
],
)
password_confirm = wtforms.PasswordField(
@@ -184,6 +188,10 @@ async def register(id_: str) -> typing.Union[str, werkzeug.Response]:
class ResetForm(BaseForm):
password = wtforms.PasswordField(
_l("Password"),
validators=[
wtforms.validators.InputRequired(),
wtforms.validators.Length(min=10),
],
)
password_confirm = wtforms.PasswordField(

View File

@@ -29,6 +29,7 @@ from . import xmpputil
from .xmpputil import split_jid
SCOPE_RESTRICTED = "prosody:restricted"
SCOPE_DEFAULT = "prosody:registered"
SCOPE_ADMIN = "prosody:admin"
@@ -119,6 +120,14 @@ class AdminUserInfo:
roles.extend(data.get("secondary_roles", []))
except KeyError:
roles = data.get("roles")
avatar_info: typing.List[AvatarMetadata] = []
for avatar in data.get("avatar_info", []):
# Ignore somehow broken avatars.
try:
avatar_metadata = AvatarMetadata.from_api_response(avatar)
avatar_info.append(avatar_metadata)
except KeyError:
pass
return cls(
localpart=data["username"],
display_name=data.get("display_name") or None,
@@ -130,10 +139,7 @@ class AdminUserInfo:
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", [])
],
avatar_info=avatar_info,
)
@@ -154,6 +160,7 @@ class AdminInviteInfo:
expires: datetime
reusable: bool
group_ids: typing.Collection[str]
role_names: typing.Collection[str]
is_reset: bool
@classmethod
@@ -171,6 +178,7 @@ class AdminInviteInfo:
xmpp_uri=data.get("xmpp_uri"),
landing_page=data.get("landing_page"),
group_ids=data.get("groups", []),
role_names=data.get("roles", []),
reusable=data["reusable"],
is_reset=data.get("reset", False),
)
@@ -190,7 +198,7 @@ class AdminGroupChatInfo:
return cls(
id_=data["id"],
jid=data["jid"],
name=data["name"],
name=data.get("name", ""),
)
@@ -399,7 +407,7 @@ class ProsodyClient:
request.add_field("password", password)
request.add_field(
"scope",
" ".join([SCOPE_DEFAULT, SCOPE_ADMIN])
" ".join([SCOPE_RESTRICTED, SCOPE_DEFAULT, SCOPE_ADMIN])
)
self.logger.debug("sending OAuth2 request (payload omitted)")
@@ -908,7 +916,7 @@ class ProsodyClient:
self.session_address,
current_password,
)
await self._xml_iq_call(
password_changed = await self._xml_iq_call(
session,
xmpputil.make_password_change_request(
self.session_address,
@@ -919,7 +927,7 @@ class ProsodyClient:
},
sensitive=True,
)
# TODO: error handling
xmpputil.extract_iq_reply(password_changed)
# TODO: obtain a new token using the new password to allow the
# server to expire/revoke all tokens on password change.
self._store_token_in_session(token_info)
@@ -1080,12 +1088,14 @@ class ProsodyClient:
self,
*,
group_ids: typing.Collection[str] = [],
role_names: typing.Collection[str] = [],
restrict_username: typing.Optional[str] = None,
ttl: typing.Optional[int] = None,
session: aiohttp.ClientSession,
) -> AdminInviteInfo:
payload: typing.Dict[str, typing.Any] = {}
payload["groups"] = list(group_ids)
payload["roles"] = list(role_names)
if restrict_username is not None:
payload["username"] = restrict_username
if ttl is not None:
@@ -1102,11 +1112,13 @@ class ProsodyClient:
self,
*,
group_ids: typing.Collection[str] = [],
role_names: typing.Collection[str] = [],
ttl: typing.Optional[int] = None,
session: aiohttp.ClientSession,
) -> AdminInviteInfo:
payload: typing.Dict[str, typing.Any] = {
"groups": list(group_ids),
"roles": list(role_names),
}
if ttl is not None:
payload["ttl"] = ttl

View File

@@ -259,6 +259,13 @@ div.form.layout-expanded {
margin: 0;
}
fieldset.descriptive-radio-selection {
p {
margin-top: 0;
margin-bottom: $w-s2;
}
}
input[type="radio"] + label, input[type="checkbox"] + label {
font-weight: inherit;
color: inherit;
@@ -363,6 +370,10 @@ div.form.layout-expanded {
margin-left: 0.25em;
}
.radio-button-ext {
margin-left: 0.5rem;
}
div.select-wrap {
display: block;
border-bottom: $w-s4 solid $primary-500;
@@ -1304,4 +1315,11 @@ pre.guru-meditation {
margin-left: 0.5em;
}
.user-display-name {
font-size: 110%;
}
.user-jid {
font-size: 90%;
}
}

View File

@@ -148,6 +148,11 @@ licensed under the terms of the Apache 2.0 License -->
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V18c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-1.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05.02.01.03.03.04.04 1.14.83 1.93 1.94 1.93 3.41V18c0 .35-.07.69-.18 1H22c.55 0 1-.45 1-1v-1.5c0-2.33-4.67-3.5-7-3.5z" />
</symbol>
<!-- from: social/person/materialiconsround/24px.svg -->
<symbol id="icon-person" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v1c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-1c0-2.66-5.33-4-8-4z" />
</symbol>
<!-- from: social/group_add/materialiconsround/24px.svg -->
<symbol id="icon-create_group" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,19 +1,57 @@
{% from "library.j2" import form_button, render_errors %}
{% from "library.j2" import form_button,
render_errors,
access_level_description, access_level_icon,
invite_type_description, invite_type_icon
%}
<form method="POST" action="{{ url_for(".create_invite") }}">
{{- invite_form.csrf_token -}}
<div class="form layout-expanded">
<h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
<p class="form-descr weak">{% trans %}Create a new invitation link to invite more users to your Snikket service by clicking the button below.{% endtrans %}</p>
<!-- Invitation type -->
<div class="f-ebox">
<fieldset>{#- -#}
<fieldset class="descriptive-radio-selection">{#- -#}
<legend>{{ invite_form.type_.label.text }}</legend>
{{- invite_form.type_ -}}
<p>{% trans %}Choose whether this invitation link will allow more than one person to join.{% endtrans %}</p>
{%- for invite_type in invite_form.type_ -%}
<div class="radio-button-ext">
{{ invite_type }}<label for="{{ invite_type.id }}">
{%- trans title=invite_type.label.text, icon=invite_type_icon(invite_type.data), description=invite_type_description(invite_type.data) -%}
<span class="invite-type">{{ title }}{{ icon }}</span><p>{{ description }}</p>
{%- endtrans -%}
</label>
</div>
{%- endfor -%}
</fieldset>
</div>
<!-- Access level -->
<div class="f-ebox">
<fieldset class="descriptive-radio-selection">{#- -#}
<legend>{{ invite_form.role.label.text }}</legend>
<p>{% trans %}The access level of a user determines what interactions are allowed for them on your Snikket service.{% endtrans %}</p>
{%- for level in invite_form.role -%}
<div class="radio-button-ext">
{{ level }}<label for="{{ level.id }}">
{%- trans title=level.label.text, icon=access_level_icon(level.data), description=access_level_description(level.data) -%}
<span class="access-level">{{ title }}{{ icon }}</span><p>{{ description }}</p>
{%- endtrans -%}
</label>
</div>
{%- endfor -%}
</fieldset>
</div>
<!-- Valid for -->
<div class="f-ebox">
{{ invite_form.lifetime.label }}
<div class="select-wrap">{{ invite_form.lifetime }}</div>
</div>
<!-- Invite to circle -->
<div class="f-ebox">
{#
NOTE: This is for when/if we ever support multi-group invites.
@@ -27,6 +65,7 @@
<div class="select-wrap">{{ invite_form.circles }}</div>
{%- call render_errors(invite_form.circles) -%}{%- endcall -%}
</div>
<div class="f-bbox">
{%- call form_button("create_link", invite_form.action_create_invite, class="primary") %}{% endcall -%}
</div>

View File

@@ -1,5 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import form_button, standard_button, value_or_hint, custom_form_button, clipboard_button, icon %}
{% from "library.j2" import form_button, standard_button, value_or_hint, custom_form_button, clipboard_button, icon, render_user with context %}
{% block head_lead %}
{{ super() }}
{% include "copy-snippet.html" %}
@@ -47,7 +47,7 @@
<tbody>
{%- for chat in circle_chats -%}
<tr>
<td class="collapsible">{% call value_or_hint(chat.name) %}{% endcall %}</td>
<td>{% 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 %}
@@ -71,7 +71,6 @@
<div class="el-2 elevated"><table>
<thead>
<th>{% trans %}Login name{% endtrans %}</th>
<th class="collapsible">{% trans %}Display name{% endtrans %}</th>
<th>{% trans %}Actions{% endtrans %}</th>
</thead>
<tbody>
@@ -79,13 +78,12 @@
<tr>
<td>
{%- if member -%}
{{ localpart }}
{%- call render_user(member) -%}{%- endcall -%}
{%- else -%}
{{ localpart }}
<span class="with-tooltip above" data-tooltip="{% trans %}The user has been deleted from the server.{% endtrans %}"><em> ({% trans %}deleted{% endtrans %})</em></span>
{%- endif -%}
</td>
<td class="collapsible">{% call value_or_hint(member.display_name) %}{% endcall %}</td>
<td class="nowrap">
{%- call custom_form_button("remove_user", form.action_remove_user.name, member.localpart, class="primary danger", slim=True) -%}
{% trans username=member.localpart %}Remove user {{ username }} from circle{% endtrans %}

View File

@@ -1,21 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import box, form_button, standard_button, icon %}
{% macro access_level_description(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
{%- elif role == "prosody:registered" -%}
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
{%- elif role == "prosody:admin" -%}
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
{%- endif -%}
{% endmacro %}
{% macro access_level_icon(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% call icon("lock") %}{% endcall %}
{%- elif role == "prosody:admin" -%}
{% call icon("admin") %}{% endcall %}
{%- endif -%}
{% endmacro %}
{% from "library.j2" import box, form_button, standard_button, icon, access_level_description, access_level_icon %}
{% block content %}
<h1>{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}</h1>
<form method="POST">{{ form.csrf_token }}<div class="form layout-expanded">

View File

@@ -76,13 +76,20 @@
<em>{% trans %}unknown{% endtrans %}</em>
{%- endif -%}
</dd>
<dt>{% trans %}Connected devices{% endtrans %}</dt>
<dt>{% trans %}Active users{% endtrans %}</dt>
<dd>
<ul>
{%- if metrics.prosody_devices | default(None) is not none -%}
{{ metrics.prosody_devices }}
<li>{% trans %}Connected now:{% endtrans %} {{ metrics.prosody_devices }}</li>
{%- else -%}
<em>{% trans %}unknown{% endtrans %}</em>
<li><em>{% trans %}unknown{% endtrans %}</em></li>
{%- endif -%}
{%- if metrics.users | default(None) is not none -%}
<li>{% trans %}Past 24 hours:{% endtrans %} {{ metrics.users.active_1d }}</li>
<li>{% trans %}Past 7 days:{% endtrans %} {{ metrics.users.active_7d }}</li>
<li>{% trans %}Past 30 days:{% endtrans %} {{ metrics.users.active_30d }}</li>
{%- endif -%}
</ul>
</dd>
</dl>
</div>

View File

@@ -27,7 +27,6 @@
{%- call action_button("edit", url_for(".edit_user", localpart=user.localpart), class="primary") -%}
{% trans user_name=user.localpart %}Edit user {{ user_name }}{% endtrans %}
{%- endcall -%}
</form>
</td>
</tr>
{% endfor %}

View File

@@ -7,7 +7,6 @@
{% block head_lead %}
{{ super() }}
<title>{% trans %}Reset your password | Snikket{% endtrans %}</title>
<script async type="text/javascript" src="{{ url_for("static", filename="js/qrcode.min.js") }}"></script>
{% endblock %}
{% block content %}
<form method="POST"><div class="form layout-expanded">
@@ -27,9 +26,4 @@
{%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%}
</div>
</div></form>
<script type="text/javascript">
var onload = function() {
apply_qr_code(document.getElementById("qr-uri"));
};
</script>
{% endblock %}

View File

@@ -134,7 +134,6 @@
var onload = function() {
apply_qr_code(document.getElementById("qr-invite-page"));
apply_qr_code(document.getElementById("qr-uri"));
var popover_as = document.getElementsByClassName("popover");
for (var i = 0; i < popover_as.length; ++i) {
var a = popover_as[i];

View File

@@ -25,10 +25,10 @@
{%- 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 class="user-jid"><span class="user-jid-localpart">{{- user.localpart -}}</span><span class="user-jid-at">@</span><span class="user-jid-domain">{{- config["SNIKKET_DOMAIN"] -}}</span></div>
</div>
</div>
{%- endmacro -%}
@@ -147,3 +147,37 @@
{% trans %}Can be used once to create an account on this Snikket service.{% endtrans %}
{%- endif -%}
{%- endmacro -%}
{% macro access_level_description(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
{%- elif role == "prosody:registered" -%}
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
{%- elif role == "prosody:admin" -%}
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
{%- endif -%}
{% endmacro %}
{% macro access_level_icon(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% call icon("lock") %}{% endcall %}
{%- elif role == "prosody:admin" -%}
{% call icon("admin") %}{% endcall %}
{%- endif -%}
{% endmacro %}
{% macro invite_type_description(invite_type, caller=None) %}
{%- if invite_type == "account" -%}
{% trans %}Invite a single person (invitation link can only be used once).{% endtrans %}
{%- elif invite_type == "group" -%}
{% trans %}Invite a group of people (invitation link can be used multiple times).{% endtrans %}
{%- endif -%}
{% endmacro %}
{% macro invite_type_icon(invite_type, caller=None) %}
{%- if invite_type == "account" -%}
{% call icon("person") %}{% endcall %}
{%- elif invite_type == "group" -%}
{% call icon("people") %}{% endcall %}
{%- endif -%}
{% endmacro %}

View File

@@ -30,7 +30,7 @@
<div class="f-bbox">
{%- call form_button("login", form.action_signin, class="primary") -%}{% endcall -%}
</div>
</from>
</form>
<script type="text/javascript">
var domainCheck = function() {
var form = document.getElementById("login-form");

View File

@@ -6,8 +6,13 @@
{% include "copy-snippet.html" %}
{% endblock %}
{% block content %}
<h1>{% trans %}Welcome!{% endtrans %}</h1>
<p>{% trans user_name=user_info.display_name %}Welcome home, {{ user_name }}.{% endtrans %}</p>
{% if user_info.is_admin and metrics.users and metrics.users.active_1d <= 1 %}
<aside class="box hint">
<header>{% trans %}Welcome to Snikket!{% endtrans %}</header>
<p>{% trans %}Now your Snikket instance is up and running, the next step is to invite people to join it. Family, friends, colleagues... you choose!{% endtrans %}</p>
<a href="/admin/invitations">{% trans %}Create new invitation{% endtrans %}</a>
</aside>
{% endif %}
<nav class="welcome">
<ul>
<li class="wide">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,21 @@
# Translations template for PROJECT.
# Copyright (C) 2023 ORGANIZATION
# Copyright (C) 2024 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2024.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-12-08 12:08+0000\n"
"POT-Creation-Date: 2024-04-27 14:22+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.13.1\n"
"Generated-By: Babel 2.10.3\n"
#: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_edit_circle.html:73
@@ -23,229 +23,230 @@ msgid "Login name"
msgstr ""
#: snikket_web/admin.py:73 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_edit_circle.html:74 snikket_web/user.py:63
#: snikket_web/user.py:69
msgid "Display name"
msgstr ""
#: snikket_web/admin.py:77 snikket_web/templates/admin_edit_user.html:53
#: snikket_web/admin.py:77 snikket_web/admin.py:295
#: snikket_web/templates/admin_edit_user.html:37
msgid "Access Level"
msgstr ""
#: snikket_web/admin.py:79
#: snikket_web/admin.py:79 snikket_web/admin.py:297
msgid "Limited"
msgstr ""
#: snikket_web/admin.py:80
#: snikket_web/admin.py:80 snikket_web/admin.py:298
msgid "Normal user"
msgstr ""
#: snikket_web/admin.py:81
#: snikket_web/admin.py:81 snikket_web/admin.py:299
msgid "Administrator"
msgstr ""
#: snikket_web/admin.py:86
#: snikket_web/admin.py:87
msgid "Update user"
msgstr ""
#: snikket_web/admin.py:90
#: snikket_web/admin.py:91
msgid "Restore account"
msgstr ""
#: snikket_web/admin.py:94
#: snikket_web/admin.py:95
msgid "Unlock account"
msgstr ""
#: snikket_web/admin.py:98
#: snikket_web/admin.py:99
msgid "Create password reset link"
msgstr ""
#: snikket_web/admin.py:116
#: snikket_web/admin.py:117
msgid "Password reset link created"
msgstr ""
#: snikket_web/admin.py:128
#: snikket_web/admin.py:129
msgid "User account restored"
msgstr ""
#: snikket_web/admin.py:133
#: snikket_web/admin.py:134
msgid "User account unlocked"
msgstr ""
#: snikket_web/admin.py:140
#: snikket_web/admin.py:141
msgid "Could not restore user account"
msgstr ""
#: snikket_web/admin.py:145
#: snikket_web/admin.py:146
msgid "Could not unlock user account"
msgstr ""
#: snikket_web/admin.py:157
#: snikket_web/admin.py:158
msgid "User information updated."
msgstr ""
#: snikket_web/admin.py:179
#: snikket_web/admin.py:180
msgid "Delete user permanently"
msgstr ""
#: snikket_web/admin.py:192
#: snikket_web/admin.py:193
msgid "User deleted"
msgstr ""
#: snikket_web/admin.py:230
#: snikket_web/admin.py:231
msgid "Password reset link not found"
msgstr ""
#: snikket_web/admin.py:242
#: snikket_web/admin.py:243
msgid "Password reset link deleted"
msgstr ""
#: snikket_web/admin.py:262
#: snikket_web/admin.py:263
msgid "Invite to circle"
msgstr ""
#: snikket_web/admin.py:268
#: snikket_web/admin.py:269
msgid "At least one circle must be selected"
msgstr ""
#: snikket_web/admin.py:273
#: snikket_web/admin.py:274
msgid "Valid for"
msgstr ""
#: snikket_web/admin.py:275
#: snikket_web/admin.py:276
msgid "One hour"
msgstr ""
#: snikket_web/admin.py:276
#: snikket_web/admin.py:277
msgid "Twelve hours"
msgstr ""
#: snikket_web/admin.py:277
#: snikket_web/admin.py:278
msgid "One day"
msgstr ""
#: snikket_web/admin.py:278
#: snikket_web/admin.py:279
msgid "One week"
msgstr ""
#: snikket_web/admin.py:279
#: snikket_web/admin.py:280
msgid "Four weeks"
msgstr ""
#: snikket_web/admin.py:285 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:286 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr ""
#: snikket_web/admin.py:287 snikket_web/templates/library.j2:139
#: snikket_web/admin.py:288 snikket_web/templates/library.j2:139
msgid "Individual"
msgstr ""
#: snikket_web/admin.py:288 snikket_web/templates/library.j2:137
#: snikket_web/admin.py:289 snikket_web/templates/library.j2:137
msgid "Group"
msgstr ""
#: snikket_web/admin.py:294
#: snikket_web/admin.py:305
msgid "New invitation link"
msgstr ""
#: snikket_web/admin.py:356
#: snikket_web/admin.py:367
msgid "Revoke"
msgstr ""
#: snikket_web/admin.py:380
#: snikket_web/admin.py:393
msgid "Invitation created"
msgstr ""
#: snikket_web/admin.py:396
#: snikket_web/admin.py:409
msgid "No such invitation exists"
msgstr ""
#: snikket_web/admin.py:411
#: snikket_web/admin.py:424
msgid "Invitation revoked"
msgstr ""
#: snikket_web/admin.py:428 snikket_web/admin.py:476
#: snikket_web/admin.py:441 snikket_web/admin.py:489
#: snikket_web/templates/admin_delete_circle.html:10
#: snikket_web/templates/admin_edit_circle.html:44
msgid "Name"
msgstr ""
#: snikket_web/admin.py:433 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:446 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr ""
#: snikket_web/admin.py:463
#: snikket_web/admin.py:476
msgid "Circle created"
msgstr ""
#: snikket_web/admin.py:481
#: snikket_web/admin.py:494
msgid "Select user"
msgstr ""
#: snikket_web/admin.py:486
#: snikket_web/admin.py:499
msgid "Update circle"
msgstr ""
#: snikket_web/admin.py:492
#: snikket_web/admin.py:505
msgid "Add user"
msgstr ""
#: snikket_web/admin.py:510 snikket_web/admin.py:609 snikket_web/admin.py:657
#: snikket_web/admin.py:523 snikket_web/admin.py:622 snikket_web/admin.py:670
msgid "No such circle exists"
msgstr ""
#: snikket_web/admin.py:547
#: snikket_web/admin.py:560
msgid "Circle data updated"
msgstr ""
#: snikket_web/admin.py:557
#: snikket_web/admin.py:570
msgid "User added to circle"
msgstr ""
#: snikket_web/admin.py:566
#: snikket_web/admin.py:579
msgid "User removed from circle"
msgstr ""
#: snikket_web/admin.py:575
#: snikket_web/admin.py:588
msgid "Chat removed from circle"
msgstr ""
#: snikket_web/admin.py:593
#: snikket_web/admin.py:606
msgid "Delete circle permanently"
msgstr ""
#: snikket_web/admin.py:620
#: snikket_web/admin.py:633
msgid "Circle deleted"
msgstr ""
#: snikket_web/admin.py:634
#: snikket_web/admin.py:647
msgid "Group chat name"
msgstr ""
#: snikket_web/admin.py:639
#: snikket_web/admin.py:652
msgid "Create group chat"
msgstr ""
#: snikket_web/admin.py:669
#: snikket_web/admin.py:682
msgid "New group chat added to circle"
msgstr ""
#: snikket_web/admin.py:736
#: snikket_web/admin.py:749
msgid "Message contents"
msgstr ""
#: snikket_web/admin.py:742
#: snikket_web/admin.py:755
msgid "Only send to online users"
msgstr ""
#: snikket_web/admin.py:746
#: snikket_web/admin.py:759
msgid "Post to all users"
msgstr ""
#: snikket_web/admin.py:750
#: snikket_web/admin.py:763
msgid "Send preview to yourself"
msgstr ""
#: snikket_web/admin.py:772
#: snikket_web/admin.py:785
msgid "Announcement sent!"
msgstr ""
@@ -280,56 +281,56 @@ msgstr ""
msgid "Username"
msgstr ""
#: snikket_web/invite.py:118 snikket_web/invite.py:186 snikket_web/main.py:43
#: snikket_web/invite.py:118 snikket_web/invite.py:190 snikket_web/main.py:43
msgid "Password"
msgstr ""
#: snikket_web/invite.py:122 snikket_web/invite.py:190
#: snikket_web/invite.py:126 snikket_web/invite.py:198
msgid "Confirm password"
msgstr ""
#: snikket_web/invite.py:126 snikket_web/invite.py:194
#: snikket_web/invite.py:130 snikket_web/invite.py:202
msgid "The passwords must match."
msgstr ""
#: snikket_web/invite.py:131
#: snikket_web/invite.py:135
msgid "Create account"
msgstr ""
#: snikket_web/invite.py:158
#: snikket_web/invite.py:162
msgid "That username is already taken."
msgstr ""
#: snikket_web/invite.py:162 snikket_web/invite.py:227
#: snikket_web/invite.py:166 snikket_web/invite.py:235
msgid "Registration was declined for unknown reasons."
msgstr ""
#: snikket_web/invite.py:166
#: snikket_web/invite.py:170
msgid "The username is not valid."
msgstr ""
#: snikket_web/invite.py:199 snikket_web/templates/user_home.html:32
#: snikket_web/invite.py:207 snikket_web/templates/user_home.html:37
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr ""
#: snikket_web/invite.py:246
#: snikket_web/invite.py:254
msgid "Account data file"
msgstr ""
#: snikket_web/invite.py:250
#: snikket_web/invite.py:258
msgid "Import data"
msgstr ""
#: snikket_web/invite.py:271
#: snikket_web/invite.py:279
#, python-format
msgid ""
"The account data you tried to import is in an unknown format. Please "
"upload an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
#: snikket_web/invite.py:291 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:189
msgid "Error"
msgstr ""
@@ -357,73 +358,73 @@ msgstr ""
msgid "New password"
msgstr ""
#: snikket_web/user.py:39
#: snikket_web/user.py:42
msgid "Confirm new password"
msgstr ""
#: snikket_web/user.py:43
#: snikket_web/user.py:47
msgid "The new passwords must match."
msgstr ""
#: snikket_web/user.py:50
#: snikket_web/user.py:56
msgid "Sign out"
msgstr ""
#: snikket_web/user.py:55
#: snikket_web/user.py:61
msgid "Nobody"
msgstr ""
#: snikket_web/user.py:56
#: snikket_web/user.py:62
msgid "Friends only"
msgstr ""
#: snikket_web/user.py:57
#: snikket_web/user.py:63
msgid "Everyone"
msgstr ""
#: snikket_web/user.py:67
#: snikket_web/user.py:73
msgid "Avatar"
msgstr ""
#: snikket_web/user.py:71
#: snikket_web/user.py:77
msgid "Profile visibility"
msgstr ""
#: snikket_web/user.py:76
#: snikket_web/user.py:82
msgid "Update profile"
msgstr ""
#: snikket_web/user.py:82
#: snikket_web/user.py:88
msgid "Account data"
msgstr ""
#: snikket_web/user.py:86
#: snikket_web/user.py:92
msgid "Upload"
msgstr ""
#: snikket_web/user.py:111
#: snikket_web/user.py:122
msgid "Incorrect password."
msgstr ""
#: snikket_web/user.py:115
#: snikket_web/user.py:126
msgid "Password changed"
msgstr ""
#: snikket_web/user.py:123
#: snikket_web/user.py:134
msgid ""
"The chosen avatar is too big. To be able to upload larger avatars, please"
" use the app."
msgstr ""
#: snikket_web/user.py:170
#: snikket_web/user.py:181
msgid "Profile updated"
msgstr ""
#: snikket_web/user.py:184
#: snikket_web/user.py:195
msgid "Export"
msgstr ""
#: snikket_web/user.py:202
#: snikket_web/user.py:213
msgid "You currently have no account data to export."
msgstr ""
@@ -544,7 +545,7 @@ msgstr ""
#: snikket_web/templates/admin_circles.html:15
#: snikket_web/templates/admin_edit_circle.html:45
#: snikket_web/templates/admin_edit_circle.html:75
#: snikket_web/templates/admin_edit_circle.html:74
#: snikket_web/templates/admin_invites.html:24
#: snikket_web/templates/admin_users.html:10
msgid "Actions"
@@ -603,16 +604,42 @@ msgstr ""
msgid "Create invitation"
msgstr ""
#: snikket_web/templates/admin_create_invite_form.html:5
#: snikket_web/templates/admin_create_invite_form.html:9
#: snikket_web/templates/user_home.html:13
msgid "Create new invitation"
msgstr ""
#: snikket_web/templates/admin_create_invite_form.html:6
#: snikket_web/templates/admin_create_invite_form.html:10
msgid ""
"Create a new invitation link to invite more users to your Snikket service"
" by clicking the button below."
msgstr ""
#: snikket_web/templates/admin_create_invite_form.html:16
msgid ""
"Choose whether this invitation link will allow more than one person to "
"join."
msgstr ""
#: snikket_web/templates/admin_create_invite_form.html:21
#, python-format
msgid "<span class=\"invite-type\">%(title)s%(icon)s</span><p>%(description)s</p>"
msgstr ""
#: snikket_web/templates/admin_create_invite_form.html:34
#: snikket_web/templates/admin_edit_user.html:38
msgid ""
"The access level of a user determines what interactions are allowed for "
"them on your Snikket service."
msgstr ""
#: snikket_web/templates/admin_create_invite_form.html:38
#, python-format
msgid ""
"<span class=\"access-"
"level\">%(title)s%(icon)s</span><p>%(description)s</p>"
msgstr ""
#: snikket_web/templates/admin_debug_user.html:8
#, python-format
msgid "Debug information for %(user_name)s"
@@ -678,7 +705,7 @@ msgid "Delete user %(user_name)s"
msgstr ""
#: snikket_web/templates/admin_delete_user.html:6
#: snikket_web/templates/admin_edit_user.html:74
#: snikket_web/templates/admin_edit_user.html:58
msgid "Delete user"
msgstr ""
@@ -742,37 +769,37 @@ msgstr ""
msgid "All members of the circle will see each other in their contact list."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:85
#: snikket_web/templates/admin_edit_circle.html:84
msgid "The user has been deleted from the server."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:85
#: snikket_web/templates/admin_edit_circle.html:84
#: snikket_web/templates/library.j2:131
msgid "deleted"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:91
#: snikket_web/templates/admin_edit_circle.html:89
#, python-format
msgid "Remove user %(username)s from circle"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:99
#: snikket_web/templates/admin_edit_circle.html:97
msgid "This circle currently has no members."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:101
#: snikket_web/templates/admin_edit_circle.html:99
msgid "Invite more members"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:104
#: snikket_web/templates/admin_edit_circle.html:102
msgid "Add existing user"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:115
#: snikket_web/templates/admin_edit_circle.html:113
msgid "All users added"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:116
#: snikket_web/templates/admin_edit_circle.html:114
msgid "All users on this service are already in this circle."
msgstr ""
@@ -825,112 +852,90 @@ msgstr ""
msgid "Return to invitation list"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:5
msgid ""
"Limited users can interact with users on the same Snikket service and be "
"members of circles."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:7
msgid ""
"Like limited users and can also interact with users on other Snikket "
"services."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:9
msgid "Like normal users and can access the admin panel in the web portal."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:20
#: snikket_web/templates/admin_edit_user.html:4
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Edit user %(user_name)s"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:24
#: snikket_web/templates/admin_edit_user.html:8
msgid "This user account is pending deletion"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:25
#: snikket_web/templates/admin_edit_user.html:9
#, python-format
msgid ""
"The owner of the account sent a deletion request on %(date)s using their "
"app."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:26
#: snikket_web/templates/admin_edit_user.html:10
#, 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
#: snikket_web/templates/admin_edit_user.html:12
msgid ""
"If this was a mistake, you can cancel the deletion and restore the "
"account."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:34
#: snikket_web/templates/admin_edit_user.html:18
msgid "This user account is locked"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:35
#: snikket_web/templates/admin_edit_user.html:19
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
#: snikket_web/templates/admin_edit_user.html:25
msgid "Edit user"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:46
#: snikket_web/templates/admin_edit_user.html:30
msgid "The login name cannot be changed."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:54
msgid ""
"The access level of a user determines what interactions are allowed for "
"them on your Snikket service."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:61
#: snikket_web/templates/admin_edit_user.html:45
#, python-format
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:71
#: snikket_web/templates/admin_edit_user.html:55
msgid "Return to user list"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:79
#: snikket_web/templates/admin_edit_user.html:63
msgid "Further actions"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:81
#: snikket_web/templates/admin_edit_user.html:65
msgid "Reset password"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:84
#: snikket_web/templates/admin_edit_user.html:68
msgid ""
"If the user has lost their password, you can use the button below to "
"create a special link which allows to change the password of the account,"
" once."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:89
#: snikket_web/templates/admin_edit_user.html:73
msgid "Debug information"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:91
#: snikket_web/templates/admin_edit_user.html:75
msgid ""
"In some cases, extended information about the user account and the "
"connected devices is necessary to troubleshoot issues. The button below "
"reveals this (sensitive) information."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:95
#: snikket_web/templates/admin_edit_user.html:79
msgid "Show debug information"
msgstr ""
@@ -1060,7 +1065,7 @@ msgstr ""
#: snikket_web/templates/admin_system.html:60
#: snikket_web/templates/admin_system.html:68
#: snikket_web/templates/admin_system.html:76
#: snikket_web/templates/admin_system.html:84
#: snikket_web/templates/admin_system.html:85
msgid "unknown"
msgstr ""
@@ -1108,14 +1113,30 @@ msgid "Storage used by shared files"
msgstr ""
#: snikket_web/templates/admin_system.html:79
msgid "Connected devices"
msgid "Active users"
msgstr ""
#: snikket_web/templates/admin_system.html:83
msgid "Connected now:"
msgstr ""
#: snikket_web/templates/admin_system.html:88
msgid "Past 24 hours:"
msgstr ""
#: snikket_web/templates/admin_system.html:89
msgid "Past 7 days:"
msgstr ""
#: snikket_web/templates/admin_system.html:90
msgid "Past 30 days:"
msgstr ""
#: snikket_web/templates/admin_system.html:97
msgid "Broadcast message"
msgstr ""
#: snikket_web/templates/admin_system.html:92
#: snikket_web/templates/admin_system.html:99
msgid ""
"This form allows you to send a message to all users currently online on "
"your Snikket server. Use it wisely."
@@ -1260,11 +1281,11 @@ msgstr ""
msgid "Reset your password | Snikket"
msgstr ""
#: snikket_web/templates/invite_reset.html:15
#: snikket_web/templates/invite_reset.html:14
msgid "Reset your password online"
msgstr ""
#: snikket_web/templates/invite_reset.html:16
#: snikket_web/templates/invite_reset.html:15
msgid ""
"To reset your password online, fill out the fields below and confirm "
"using the button."
@@ -1358,7 +1379,7 @@ msgid "Your address"
msgstr ""
#: snikket_web/templates/invite_success.html:15
#: snikket_web/templates/user_home.html:21
#: snikket_web/templates/user_home.html:26
msgid "Copy address"
msgstr ""
@@ -1560,6 +1581,30 @@ msgstr ""
msgid "Can be used once to create an account on this Snikket service."
msgstr ""
#: snikket_web/templates/library.j2:153
msgid ""
"Limited users can interact with users on the same Snikket service and be "
"members of circles."
msgstr ""
#: snikket_web/templates/library.j2:155
msgid ""
"Like limited users and can also interact with users on other Snikket "
"services."
msgstr ""
#: snikket_web/templates/library.j2:157
msgid "Like normal users and can access the admin panel in the web portal."
msgstr ""
#: snikket_web/templates/library.j2:171
msgid "Invite a single person (invitation link can only be used once)."
msgstr ""
#: snikket_web/templates/library.j2:173
msgid "Invite a group of people (invitation link can be used multiple times)."
msgstr ""
#: snikket_web/templates/login.html:5
msgid "Snikket Login"
msgstr ""
@@ -1620,41 +1665,42 @@ msgstr ""
msgid "Operation successful"
msgstr ""
#: snikket_web/templates/user_home.html:9
msgid "Welcome!"
#: snikket_web/templates/user_home.html:11
msgid "Welcome to Snikket!"
msgstr ""
#: snikket_web/templates/user_home.html:10
#, python-format
msgid "Welcome home, %(user_name)s."
#: snikket_web/templates/user_home.html:12
msgid ""
"Now your Snikket instance is up and running, the next step is to invite "
"people to join it. Family, friends, colleagues... you choose!"
msgstr ""
#: snikket_web/templates/user_home.html:14
#: snikket_web/templates/user_home.html:19
msgid "Your account"
msgstr ""
#: snikket_web/templates/user_home.html:20
#: snikket_web/templates/user_home.html:25
msgid "Your XMPP address"
msgstr ""
#: snikket_web/templates/user_home.html:31
#: snikket_web/templates/user_home.html:36
msgid "Edit profile"
msgstr ""
#: snikket_web/templates/user_home.html:33
#: snikket_web/templates/user_home.html:38
#: snikket_web/templates/user_manage_data.html:4
msgid "Manage your data"
msgstr ""
#: snikket_web/templates/user_home.html:39
#: snikket_web/templates/user_home.html:44
msgid "Your Snikket"
msgstr ""
#: snikket_web/templates/user_home.html:41
#: snikket_web/templates/user_home.html:46
msgid "Manage users, invitations and circles of your Snikket service."
msgstr ""
#: snikket_web/templates/user_home.html:43
#: snikket_web/templates/user_home.html:48
msgid "Admin panel"
msgstr ""

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -32,16 +32,22 @@ class ChangePasswordForm(BaseForm):
new_password = wtforms.PasswordField(
_l("New password"),
validators=[wtforms.validators.InputRequired()]
validators=[
wtforms.validators.InputRequired(),
wtforms.validators.Length(min=10),
]
)
new_password_confirm = wtforms.PasswordField(
_l("Confirm new password"),
validators=[wtforms.validators.InputRequired(),
wtforms.validators.EqualTo(
"new_password",
_l("The new passwords must match.")
)]
validators=[
wtforms.validators.InputRequired(),
wtforms.validators.EqualTo(
"new_password",
_l("The new passwords must match.")
),
wtforms.validators.Length(min=10),
]
)
@@ -91,7 +97,12 @@ class ImportAccountDataForm(BaseForm):
@client.require_session()
async def index() -> str:
user_info = await client.get_user_info()
return await render_template("user_home.html", user_info=user_info)
metrics = await client.get_system_metrics()
return await render_template(
"user_home.html",
user_info=user_info,
metrics=metrics,
)
@bp.route('/passwd', methods=["GET", "POST"])

View File

@@ -27,6 +27,7 @@ navigation/cancel:cancel
navigation/more_vert:more
social/groups:groups
social/people:people
social/person:person
social/group_add:create_group
social/person_add:add_user
social/person_remove:remove_user