From 3740e659061da9fcd9fffc3b165576ef81d2021e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 14 Dec 2021 22:04:16 +0100 Subject: [PATCH] web/admin: add dashboard with user creation/login statistics closes #1867 Signed-off-by: Jens Langhammer --- authentik/core/api/applications.py | 2 +- locale/en/LC_MESSAGES/django.po | 399 +++++++++--------- web/src/elements/charts/AdminModelPerDay.ts | 52 +++ web/src/elements/charts/Chart.ts | 20 +- web/src/interfaces/AdminInterface.ts | 3 + web/src/locales/en.po | 35 +- web/src/locales/fr_FR.po | 35 +- web/src/locales/pseudo-LOCALE.po | 35 +- .../pages/admin-overview/AdminOverviewPage.ts | 4 +- .../pages/admin-overview/DashboardUserPage.ts | 86 ++++ web/src/routesAdmin.ts | 5 + 11 files changed, 460 insertions(+), 216 deletions(-) create mode 100644 web/src/elements/charts/AdminModelPerDay.ts create mode 100644 web/src/pages/admin-overview/DashboardUserPage.ts diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index 1bbf042f8..eab41f471 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -232,7 +232,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): app.save() return Response({}) - @permission_required("authentik_core.view_application") + @permission_required("authentik_core.view_application", ["authentik_events.view_event"]) @extend_schema(responses={200: CoordinateSerializer(many=True)}) @action(detail=True, pagination_class=None, filter_backends=[]) # pylint: disable=unused-argument diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 0d69d5ab9..1111fcd77 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-11-28 20:14+0000\n" +"POT-Creation-Date: 2021-12-14 21:03+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -39,99 +39,99 @@ msgstr "" msgid "Create a SAML Provider by importing its Metadata." msgstr "" -#: authentik/core/models.py:69 +#: authentik/core/models.py:68 msgid "name" msgstr "" -#: authentik/core/models.py:71 +#: authentik/core/models.py:70 msgid "Users added to this group will be superusers." msgstr "" -#: authentik/core/models.py:129 +#: authentik/core/models.py:128 msgid "User's display name." msgstr "" -#: authentik/core/models.py:196 authentik/providers/oauth2/models.py:315 +#: authentik/core/models.py:195 authentik/providers/oauth2/models.py:314 msgid "User" msgstr "" -#: authentik/core/models.py:197 +#: authentik/core/models.py:196 msgid "Users" msgstr "" -#: authentik/core/models.py:208 +#: authentik/core/models.py:207 msgid "Flow used when authorizing this provider." msgstr "" -#: authentik/core/models.py:241 +#: authentik/core/models.py:240 msgid "Application's display Name." msgstr "" -#: authentik/core/models.py:242 +#: authentik/core/models.py:241 msgid "Internal application name, used in URLs." msgstr "" -#: authentik/core/models.py:289 +#: authentik/core/models.py:288 msgid "Application" msgstr "" -#: authentik/core/models.py:290 +#: authentik/core/models.py:289 msgid "Applications" msgstr "" -#: authentik/core/models.py:296 +#: authentik/core/models.py:295 msgid "Use the source-specific identifier" msgstr "" -#: authentik/core/models.py:304 +#: authentik/core/models.py:303 msgid "" "Use the user's email address, but deny enrollment when the email address " "already exists." msgstr "" -#: authentik/core/models.py:313 +#: authentik/core/models.py:312 msgid "" "Use the user's username, but deny enrollment when the username already " "exists." msgstr "" -#: authentik/core/models.py:320 +#: authentik/core/models.py:319 msgid "Source's display Name." msgstr "" -#: authentik/core/models.py:321 +#: authentik/core/models.py:320 msgid "Internal source name, used in URLs." msgstr "" -#: authentik/core/models.py:332 +#: authentik/core/models.py:331 msgid "Flow to use when authenticating existing users." msgstr "" -#: authentik/core/models.py:341 +#: authentik/core/models.py:340 msgid "Flow to use when enrolling new users." msgstr "" -#: authentik/core/models.py:473 +#: authentik/core/models.py:470 msgid "Token" msgstr "" -#: authentik/core/models.py:474 +#: authentik/core/models.py:471 msgid "Tokens" msgstr "" -#: authentik/core/models.py:517 +#: authentik/core/models.py:514 msgid "Property Mapping" msgstr "" -#: authentik/core/models.py:518 +#: authentik/core/models.py:515 msgid "Property Mappings" msgstr "" -#: authentik/core/models.py:554 +#: authentik/core/models.py:551 msgid "Authenticated Session" msgstr "" -#: authentik/core/models.py:555 +#: authentik/core/models.py:552 msgid "Authenticated Sessions" msgstr "" @@ -204,111 +204,116 @@ msgstr "" msgid "Powered by authentik" msgstr "" -#: authentik/crypto/api.py:120 +#: authentik/crypto/api.py:121 msgid "Subject-alt name" msgstr "" -#: authentik/crypto/models.py:26 +#: authentik/crypto/models.py:29 msgid "PEM-encoded Certificate data" msgstr "" -#: authentik/crypto/models.py:29 +#: authentik/crypto/models.py:32 msgid "" "Optional Private Key. If this is set, you can use this keypair for " "encryption." msgstr "" -#: authentik/crypto/models.py:89 +#: authentik/crypto/models.py:93 msgid "Certificate-Key Pair" msgstr "" -#: authentik/crypto/models.py:90 +#: authentik/crypto/models.py:94 msgid "Certificate-Key Pairs" msgstr "" -#: authentik/events/models.py:207 +#: authentik/crypto/tasks.py:90 +#, python-format +msgid "Successfully imported %(count)d files." +msgstr "" + +#: authentik/events/models.py:284 msgid "Event" msgstr "" -#: authentik/events/models.py:208 +#: authentik/events/models.py:285 msgid "Events" msgstr "" -#: authentik/events/models.py:214 +#: authentik/events/models.py:291 msgid "Generic Webhook" msgstr "" -#: authentik/events/models.py:215 +#: authentik/events/models.py:292 msgid "Slack Webhook (Slack/Discord)" msgstr "" -#: authentik/events/models.py:216 +#: authentik/events/models.py:293 msgid "Email" msgstr "" -#: authentik/events/models.py:234 +#: authentik/events/models.py:311 msgid "" "Only send notification once, for example when sending a webhook into a chat " "channel." msgstr "" -#: authentik/events/models.py:279 +#: authentik/events/models.py:356 msgid "Severity" msgstr "" -#: authentik/events/models.py:284 +#: authentik/events/models.py:361 msgid "Dispatched for user" msgstr "" -#: authentik/events/models.py:361 +#: authentik/events/models.py:438 msgid "Notification Transport" msgstr "" -#: authentik/events/models.py:362 +#: authentik/events/models.py:439 msgid "Notification Transports" msgstr "" -#: authentik/events/models.py:368 +#: authentik/events/models.py:445 msgid "Notice" msgstr "" -#: authentik/events/models.py:369 +#: authentik/events/models.py:446 msgid "Warning" msgstr "" -#: authentik/events/models.py:370 +#: authentik/events/models.py:447 msgid "Alert" msgstr "" -#: authentik/events/models.py:390 +#: authentik/events/models.py:467 msgid "Notification" msgstr "" -#: authentik/events/models.py:391 +#: authentik/events/models.py:468 msgid "Notifications" msgstr "" -#: authentik/events/models.py:410 +#: authentik/events/models.py:487 msgid "Controls which severity level the created notifications will have." msgstr "" -#: authentik/events/models.py:430 +#: authentik/events/models.py:507 msgid "Notification Rule" msgstr "" -#: authentik/events/models.py:431 +#: authentik/events/models.py:508 msgid "Notification Rules" msgstr "" -#: authentik/events/models.py:452 +#: authentik/events/models.py:529 msgid "Notification Webhook Mapping" msgstr "" -#: authentik/events/models.py:453 +#: authentik/events/models.py:530 msgid "Notification Webhook Mappings" msgstr "" -#: authentik/events/monitored_tasks.py:125 +#: authentik/events/monitored_tasks.py:197 msgid "Task has not been run yet." msgstr "" @@ -317,37 +322,37 @@ msgstr "" msgid "Flow not applicable to current user/request: %(messages)s" msgstr "" -#: authentik/flows/models.py:104 +#: authentik/flows/models.py:107 msgid "Visible in the URL." msgstr "" -#: authentik/flows/models.py:106 +#: authentik/flows/models.py:109 msgid "Shown as the Title in Flow pages." msgstr "" -#: authentik/flows/models.py:123 +#: authentik/flows/models.py:126 msgid "Background shown during execution" msgstr "" -#: authentik/flows/models.py:130 +#: authentik/flows/models.py:133 msgid "" "Enable compatibility mode, increases compatibility with password managers on " "mobile devices." msgstr "" -#: authentik/flows/models.py:175 +#: authentik/flows/models.py:178 msgid "Flow" msgstr "" -#: authentik/flows/models.py:176 +#: authentik/flows/models.py:179 msgid "Flows" msgstr "" -#: authentik/flows/models.py:206 +#: authentik/flows/models.py:209 msgid "Evaluate policies when the Stage is present to the user." msgstr "" -#: authentik/flows/models.py:213 +#: authentik/flows/models.py:216 msgid "" "Configure how the flow executor should handle an invalid response to a " "challenge. RETRY returns the error message and a similar challenge to the " @@ -355,14 +360,22 @@ msgid "" "RESTART_WITH_CONTEXT restarts the flow while keeping the current context." msgstr "" -#: authentik/flows/models.py:237 +#: authentik/flows/models.py:240 msgid "Flow Stage Binding" msgstr "" -#: authentik/flows/models.py:238 +#: authentik/flows/models.py:241 msgid "Flow Stage Bindings" msgstr "" +#: authentik/flows/models.py:291 +msgid "Flow Token" +msgstr "" + +#: authentik/flows/models.py:292 +msgid "Flow Tokens" +msgstr "" + #: authentik/flows/templates/flows/error.html:12 msgid "Whoops!" msgstr "" @@ -389,33 +402,33 @@ msgstr "" msgid "Invalid kubeconfig" msgstr "" -#: authentik/outposts/models.py:167 +#: authentik/outposts/models.py:165 msgid "Outpost Service-Connection" msgstr "" -#: authentik/outposts/models.py:168 +#: authentik/outposts/models.py:166 msgid "Outpost Service-Connections" msgstr "" -#: authentik/outposts/models.py:204 +#: authentik/outposts/models.py:202 msgid "" "Certificate/Key used for authentication. Can be left empty for no " "authentication." msgstr "" -#: authentik/outposts/models.py:246 +#: authentik/outposts/models.py:244 msgid "Docker Service-Connection" msgstr "" -#: authentik/outposts/models.py:247 +#: authentik/outposts/models.py:245 msgid "Docker Service-Connections" msgstr "" -#: authentik/outposts/models.py:293 +#: authentik/outposts/models.py:291 msgid "Kubernetes Service-Connection" msgstr "" -#: authentik/outposts/models.py:294 +#: authentik/outposts/models.py:292 msgid "Kubernetes Service-Connections" msgstr "" @@ -608,173 +621,173 @@ msgstr "" msgid "RS256 requires a Certificate-Key-Pair to be selected." msgstr "" -#: authentik/providers/oauth2/models.py:35 +#: authentik/providers/oauth2/models.py:34 msgid "Confidential" msgstr "" -#: authentik/providers/oauth2/models.py:36 +#: authentik/providers/oauth2/models.py:35 msgid "Public" msgstr "" -#: authentik/providers/oauth2/models.py:50 +#: authentik/providers/oauth2/models.py:49 msgid "Based on the Hashed User ID" msgstr "" -#: authentik/providers/oauth2/models.py:51 +#: authentik/providers/oauth2/models.py:50 msgid "Based on the username" msgstr "" -#: authentik/providers/oauth2/models.py:54 +#: authentik/providers/oauth2/models.py:53 msgid "Based on the User's Email. This is recommended over the UPN method." msgstr "" -#: authentik/providers/oauth2/models.py:70 +#: authentik/providers/oauth2/models.py:69 msgid "Same identifier is used for all providers" msgstr "" -#: authentik/providers/oauth2/models.py:72 +#: authentik/providers/oauth2/models.py:71 msgid "Each provider has a different issuer, based on the application slug." msgstr "" -#: authentik/providers/oauth2/models.py:79 +#: authentik/providers/oauth2/models.py:78 msgid "code (Authorization Code Flow)" msgstr "" -#: authentik/providers/oauth2/models.py:80 +#: authentik/providers/oauth2/models.py:79 msgid "id_token (Implicit Flow)" msgstr "" -#: authentik/providers/oauth2/models.py:81 +#: authentik/providers/oauth2/models.py:80 msgid "id_token token (Implicit Flow)" msgstr "" -#: authentik/providers/oauth2/models.py:82 +#: authentik/providers/oauth2/models.py:81 msgid "code token (Hybrid Flow)" msgstr "" -#: authentik/providers/oauth2/models.py:83 +#: authentik/providers/oauth2/models.py:82 msgid "code id_token (Hybrid Flow)" msgstr "" -#: authentik/providers/oauth2/models.py:84 +#: authentik/providers/oauth2/models.py:83 msgid "code id_token token (Hybrid Flow)" msgstr "" -#: authentik/providers/oauth2/models.py:90 +#: authentik/providers/oauth2/models.py:89 msgid "HS256 (Symmetric Encryption)" msgstr "" -#: authentik/providers/oauth2/models.py:91 +#: authentik/providers/oauth2/models.py:90 msgid "RS256 (Asymmetric Encryption)" msgstr "" -#: authentik/providers/oauth2/models.py:97 +#: authentik/providers/oauth2/models.py:96 msgid "Scope used by the client" msgstr "" -#: authentik/providers/oauth2/models.py:123 +#: authentik/providers/oauth2/models.py:122 msgid "Scope Mapping" msgstr "" -#: authentik/providers/oauth2/models.py:124 +#: authentik/providers/oauth2/models.py:123 msgid "Scope Mappings" msgstr "" -#: authentik/providers/oauth2/models.py:134 +#: authentik/providers/oauth2/models.py:133 msgid "Client Type" msgstr "" -#: authentik/providers/oauth2/models.py:140 +#: authentik/providers/oauth2/models.py:139 msgid "Client ID" msgstr "" -#: authentik/providers/oauth2/models.py:146 +#: authentik/providers/oauth2/models.py:145 msgid "Client Secret" msgstr "" -#: authentik/providers/oauth2/models.py:153 +#: authentik/providers/oauth2/models.py:152 msgid "JWT Algorithm" msgstr "" -#: authentik/providers/oauth2/models.py:159 +#: authentik/providers/oauth2/models.py:158 msgid "Redirect URIs" msgstr "" -#: authentik/providers/oauth2/models.py:160 +#: authentik/providers/oauth2/models.py:159 msgid "Enter each URI on a new line." msgstr "" -#: authentik/providers/oauth2/models.py:165 +#: authentik/providers/oauth2/models.py:164 msgid "Include claims in id_token" msgstr "" -#: authentik/providers/oauth2/models.py:213 +#: authentik/providers/oauth2/models.py:212 msgid "RSA Key" msgstr "" -#: authentik/providers/oauth2/models.py:217 +#: authentik/providers/oauth2/models.py:216 msgid "" "Key used to sign the tokens. Only required when JWT Algorithm is set to " "RS256." msgstr "" -#: authentik/providers/oauth2/models.py:307 +#: authentik/providers/oauth2/models.py:306 msgid "OAuth2/OpenID Provider" msgstr "" -#: authentik/providers/oauth2/models.py:308 +#: authentik/providers/oauth2/models.py:307 msgid "OAuth2/OpenID Providers" msgstr "" -#: authentik/providers/oauth2/models.py:316 +#: authentik/providers/oauth2/models.py:315 msgid "Scopes" msgstr "" -#: authentik/providers/oauth2/models.py:335 +#: authentik/providers/oauth2/models.py:334 msgid "Code" msgstr "" -#: authentik/providers/oauth2/models.py:336 +#: authentik/providers/oauth2/models.py:335 msgid "Nonce" msgstr "" -#: authentik/providers/oauth2/models.py:337 +#: authentik/providers/oauth2/models.py:336 msgid "Is Authentication?" msgstr "" -#: authentik/providers/oauth2/models.py:338 +#: authentik/providers/oauth2/models.py:337 msgid "Code Challenge" msgstr "" -#: authentik/providers/oauth2/models.py:340 +#: authentik/providers/oauth2/models.py:339 msgid "Code Challenge Method" msgstr "" -#: authentik/providers/oauth2/models.py:354 +#: authentik/providers/oauth2/models.py:353 msgid "Authorization Code" msgstr "" -#: authentik/providers/oauth2/models.py:355 +#: authentik/providers/oauth2/models.py:354 msgid "Authorization Codes" msgstr "" -#: authentik/providers/oauth2/models.py:398 +#: authentik/providers/oauth2/models.py:397 msgid "Access Token" msgstr "" -#: authentik/providers/oauth2/models.py:399 +#: authentik/providers/oauth2/models.py:398 msgid "Refresh Token" msgstr "" -#: authentik/providers/oauth2/models.py:400 +#: authentik/providers/oauth2/models.py:399 msgid "ID Token" msgstr "" -#: authentik/providers/oauth2/models.py:403 +#: authentik/providers/oauth2/models.py:402 msgid "OAuth2 Token" msgstr "" -#: authentik/providers/oauth2/models.py:404 +#: authentik/providers/oauth2/models.py:403 msgid "OAuth2 Tokens" msgstr "" @@ -823,11 +836,11 @@ msgstr "" msgid "Proxy Providers" msgstr "" -#: authentik/providers/saml/api.py:163 +#: authentik/providers/saml/api.py:176 msgid "Invalid XML Syntax" msgstr "" -#: authentik/providers/saml/api.py:173 +#: authentik/providers/saml/api.py:186 #, python-format msgid "Failed to import Metadata: %(message)s" msgstr "" @@ -924,71 +937,77 @@ msgstr "" msgid "Used recovery-link to authenticate." msgstr "" -#: authentik/sources/ldap/models.py:20 +#: authentik/sources/ldap/models.py:33 msgid "Server URI" msgstr "" -#: authentik/sources/ldap/models.py:22 +#: authentik/sources/ldap/models.py:41 +msgid "" +"Optionally verify the LDAP Server's Certificate against the CA Chain in this " +"keypair." +msgstr "" + +#: authentik/sources/ldap/models.py:46 msgid "Bind CN" msgstr "" -#: authentik/sources/ldap/models.py:24 +#: authentik/sources/ldap/models.py:48 msgid "Enable Start TLS" msgstr "" -#: authentik/sources/ldap/models.py:26 +#: authentik/sources/ldap/models.py:50 msgid "Base DN" msgstr "" -#: authentik/sources/ldap/models.py:28 +#: authentik/sources/ldap/models.py:52 msgid "Prepended to Base DN for User-queries." msgstr "" -#: authentik/sources/ldap/models.py:29 +#: authentik/sources/ldap/models.py:53 msgid "Addition User DN" msgstr "" -#: authentik/sources/ldap/models.py:33 +#: authentik/sources/ldap/models.py:57 msgid "Prepended to Base DN for Group-queries." msgstr "" -#: authentik/sources/ldap/models.py:34 +#: authentik/sources/ldap/models.py:58 msgid "Addition Group DN" msgstr "" -#: authentik/sources/ldap/models.py:40 +#: authentik/sources/ldap/models.py:64 msgid "Consider Objects matching this filter to be Users." msgstr "" -#: authentik/sources/ldap/models.py:43 +#: authentik/sources/ldap/models.py:67 msgid "Field which contains members of a group." msgstr "" -#: authentik/sources/ldap/models.py:47 +#: authentik/sources/ldap/models.py:71 msgid "Consider Objects matching this filter to be Groups." msgstr "" -#: authentik/sources/ldap/models.py:50 +#: authentik/sources/ldap/models.py:74 msgid "Field which contains a unique Identifier." msgstr "" -#: authentik/sources/ldap/models.py:57 +#: authentik/sources/ldap/models.py:81 msgid "Property mappings used for group creation/updating." msgstr "" -#: authentik/sources/ldap/models.py:107 +#: authentik/sources/ldap/models.py:146 msgid "LDAP Source" msgstr "" -#: authentik/sources/ldap/models.py:108 +#: authentik/sources/ldap/models.py:147 msgid "LDAP Sources" msgstr "" -#: authentik/sources/ldap/models.py:131 +#: authentik/sources/ldap/models.py:170 msgid "LDAP Property Mapping" msgstr "" -#: authentik/sources/ldap/models.py:132 +#: authentik/sources/ldap/models.py:171 msgid "LDAP Property Mappings" msgstr "" @@ -1029,83 +1048,91 @@ msgstr "" msgid "URL used by authentik to get user information." msgstr "" -#: authentik/sources/oauth/models.py:102 +#: authentik/sources/oauth/models.py:93 msgid "OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:103 +#: authentik/sources/oauth/models.py:94 msgid "OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:112 +#: authentik/sources/oauth/models.py:103 msgid "GitHub OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:113 +#: authentik/sources/oauth/models.py:104 msgid "GitHub OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:122 +#: authentik/sources/oauth/models.py:113 msgid "Twitter OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:123 +#: authentik/sources/oauth/models.py:114 msgid "Twitter OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:132 +#: authentik/sources/oauth/models.py:123 msgid "Facebook OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:133 +#: authentik/sources/oauth/models.py:124 msgid "Facebook OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:142 +#: authentik/sources/oauth/models.py:133 msgid "Discord OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:143 +#: authentik/sources/oauth/models.py:134 msgid "Discord OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:152 +#: authentik/sources/oauth/models.py:143 msgid "Google OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:153 +#: authentik/sources/oauth/models.py:144 msgid "Google OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:162 +#: authentik/sources/oauth/models.py:153 msgid "Azure AD OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:163 +#: authentik/sources/oauth/models.py:154 msgid "Azure AD OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:172 +#: authentik/sources/oauth/models.py:163 msgid "OpenID OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:173 +#: authentik/sources/oauth/models.py:164 msgid "OpenID OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:182 +#: authentik/sources/oauth/models.py:173 msgid "Apple OAuth Source" msgstr "" -#: authentik/sources/oauth/models.py:183 +#: authentik/sources/oauth/models.py:174 msgid "Apple OAuth Sources" msgstr "" -#: authentik/sources/oauth/models.py:198 -msgid "User OAuth Source Connection" +#: authentik/sources/oauth/models.py:183 +msgid "Okta OAuth Source" +msgstr "" + +#: authentik/sources/oauth/models.py:184 +msgid "Okta OAuth Sources" msgstr "" #: authentik/sources/oauth/models.py:199 +msgid "User OAuth Source Connection" +msgstr "" + +#: authentik/sources/oauth/models.py:200 msgid "User OAuth Source Connections" msgstr "" @@ -1113,31 +1140,31 @@ msgstr "" msgid "Authentication Failed." msgstr "" -#: authentik/sources/plex/models.py:36 +#: authentik/sources/plex/models.py:37 msgid "Client identifier used to talk to Plex." msgstr "" -#: authentik/sources/plex/models.py:51 +#: authentik/sources/plex/models.py:52 msgid "Allow friends to authenticate, even if you don't share a server." msgstr "" -#: authentik/sources/plex/models.py:53 +#: authentik/sources/plex/models.py:54 msgid "Plex token used to check friends" msgstr "" -#: authentik/sources/plex/models.py:92 +#: authentik/sources/plex/models.py:91 msgid "Plex Source" msgstr "" -#: authentik/sources/plex/models.py:93 +#: authentik/sources/plex/models.py:92 msgid "Plex Sources" msgstr "" -#: authentik/sources/plex/models.py:104 +#: authentik/sources/plex/models.py:103 msgid "User Plex Source Connection" msgstr "" -#: authentik/sources/plex/models.py:105 +#: authentik/sources/plex/models.py:104 msgid "User Plex Source Connections" msgstr "" @@ -1202,43 +1229,43 @@ msgid "" "signing." msgstr "" -#: authentik/sources/saml/models.py:190 +#: authentik/sources/saml/models.py:189 msgid "SAML Source" msgstr "" -#: authentik/sources/saml/models.py:191 +#: authentik/sources/saml/models.py:190 msgid "SAML Sources" msgstr "" -#: authentik/stages/authenticator_duo/models.py:65 +#: authentik/stages/authenticator_duo/models.py:64 msgid "Duo Authenticator Setup Stage" msgstr "" -#: authentik/stages/authenticator_duo/models.py:66 +#: authentik/stages/authenticator_duo/models.py:65 msgid "Duo Authenticator Setup Stages" msgstr "" -#: authentik/stages/authenticator_duo/models.py:83 +#: authentik/stages/authenticator_duo/models.py:82 msgid "Duo Device" msgstr "" -#: authentik/stages/authenticator_duo/models.py:84 +#: authentik/stages/authenticator_duo/models.py:83 msgid "Duo Devices" msgstr "" -#: authentik/stages/authenticator_sms/models.py:158 +#: authentik/stages/authenticator_sms/models.py:157 msgid "SMS Authenticator Setup Stage" msgstr "" -#: authentik/stages/authenticator_sms/models.py:159 +#: authentik/stages/authenticator_sms/models.py:158 msgid "SMS Authenticator Setup Stages" msgstr "" -#: authentik/stages/authenticator_sms/models.py:176 +#: authentik/stages/authenticator_sms/models.py:175 msgid "SMS Device" msgstr "" -#: authentik/stages/authenticator_sms/models.py:177 +#: authentik/stages/authenticator_sms/models.py:176 msgid "SMS Devices" msgstr "" @@ -1247,11 +1274,11 @@ msgstr "" msgid "Code does not match" msgstr "" -#: authentik/stages/authenticator_static/models.py:48 +#: authentik/stages/authenticator_static/models.py:47 msgid "Static Authenticator Stage" msgstr "" -#: authentik/stages/authenticator_static/models.py:49 +#: authentik/stages/authenticator_static/models.py:48 msgid "Static Authenticator Stages" msgstr "" @@ -1263,11 +1290,11 @@ msgstr "" msgid "8 digits, not compatible with apps like Google Authenticator" msgstr "" -#: authentik/stages/authenticator_totp/models.py:55 +#: authentik/stages/authenticator_totp/models.py:54 msgid "TOTP Authenticator Setup Stage" msgstr "" -#: authentik/stages/authenticator_totp/models.py:56 +#: authentik/stages/authenticator_totp/models.py:55 msgid "TOTP Authenticator Setup Stages" msgstr "" @@ -1303,19 +1330,19 @@ msgstr "" msgid "Authenticator Validation Stages" msgstr "" -#: authentik/stages/authenticator_webauthn/models.py:51 +#: authentik/stages/authenticator_webauthn/models.py:71 msgid "WebAuthn Authenticator Setup Stage" msgstr "" -#: authentik/stages/authenticator_webauthn/models.py:52 +#: authentik/stages/authenticator_webauthn/models.py:72 msgid "WebAuthn Authenticator Setup Stages" msgstr "" -#: authentik/stages/authenticator_webauthn/models.py:85 +#: authentik/stages/authenticator_webauthn/models.py:105 msgid "WebAuthn Device" msgstr "" -#: authentik/stages/authenticator_webauthn/models.py:86 +#: authentik/stages/authenticator_webauthn/models.py:106 msgid "WebAuthn Devices" msgstr "" @@ -1393,19 +1420,15 @@ msgstr "" msgid "Email Stages" msgstr "" -#: authentik/stages/email/stage.py:103 -msgid "Invalid token" -msgstr "" - -#: authentik/stages/email/stage.py:107 +#: authentik/stages/email/stage.py:106 msgid "Successfully verified Email." msgstr "" -#: authentik/stages/email/stage.py:114 authentik/stages/email/stage.py:136 +#: authentik/stages/email/stage.py:113 authentik/stages/email/stage.py:135 msgid "No pending user." msgstr "" -#: authentik/stages/email/stage.py:126 +#: authentik/stages/email/stage.py:125 msgid "Email sent." msgstr "" @@ -1513,7 +1536,7 @@ msgstr "" msgid "Identification Stages" msgstr "" -#: authentik/stages/identification/stage.py:163 +#: authentik/stages/identification/stage.py:174 msgid "Log in" msgstr "" @@ -1557,15 +1580,15 @@ msgstr "" msgid "Selection of backends to test the password against." msgstr "" -#: authentik/stages/password/models.py:79 +#: authentik/stages/password/models.py:78 msgid "Password Stage" msgstr "" -#: authentik/stages/password/models.py:80 +#: authentik/stages/password/models.py:79 msgid "Password Stages" msgstr "" -#: authentik/stages/password/stage.py:141 +#: authentik/stages/password/stage.py:152 msgid "Invalid password" msgstr "" @@ -1597,19 +1620,19 @@ msgstr "" msgid "Name of the form field, also used to store the value" msgstr "" -#: authentik/stages/prompt/models.py:128 +#: authentik/stages/prompt/models.py:131 msgid "Prompt" msgstr "" -#: authentik/stages/prompt/models.py:129 +#: authentik/stages/prompt/models.py:132 msgid "Prompts" msgstr "" -#: authentik/stages/prompt/models.py:157 +#: authentik/stages/prompt/models.py:160 msgid "Prompt Stage" msgstr "" -#: authentik/stages/prompt/models.py:158 +#: authentik/stages/prompt/models.py:161 msgid "Prompt Stages" msgstr "" diff --git a/web/src/elements/charts/AdminModelPerDay.ts b/web/src/elements/charts/AdminModelPerDay.ts new file mode 100644 index 000000000..547761e41 --- /dev/null +++ b/web/src/elements/charts/AdminModelPerDay.ts @@ -0,0 +1,52 @@ +import { ChartData, Tick } from "chart.js"; + +import { t } from "@lingui/macro"; + +import { customElement, property } from "lit/decorators.js"; + +import { Coordinate, EventActions, EventsApi } from "@goauthentik/api"; + +import { DEFAULT_CONFIG } from "../../api/Config"; +import { AKChart } from "./Chart"; + +@customElement("ak-charts-admin-model-per-day") +export class AdminModelPerDay extends AKChart { + @property() + action: EventActions = EventActions.ModelCreated; + + @property({ attribute: false }) + query?: { [key: string]: unknown } | undefined; + + apiRequest(): Promise { + return new EventsApi(DEFAULT_CONFIG).eventsEventsPerMonthList({ + action: this.action, + query: JSON.stringify(this.query || {}), + }); + } + + timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string { + const valueStamp = ticks[index]; + const delta = Date.now() - valueStamp.value; + const ago = Math.round(delta / 1000 / 3600 / 24); + return t`${ago} days ago`; + } + + getChartData(data: Coordinate[]): ChartData { + return { + datasets: [ + { + label: t`Objects created`, + backgroundColor: "rgba(189, 229, 184, .5)", + spanGaps: true, + data: + data.map((cord) => { + return { + x: cord.xCord || 0, + y: cord.yCord || 0, + }; + }) || [], + }, + ], + }; + } +} diff --git a/web/src/elements/charts/Chart.ts b/web/src/elements/charts/Chart.ts index 687063b29..348e4398d 100644 --- a/web/src/elements/charts/Chart.ts +++ b/web/src/elements/charts/Chart.ts @@ -5,6 +5,8 @@ import { ArcElement, BarElement } from "chart.js"; import { LinearScale, TimeScale } from "chart.js"; import "chartjs-adapter-moment"; +import { t } from "@lingui/macro"; + import { CSSResult, LitElement, TemplateResult, css, html } from "lit"; import { property } from "lit/decorators.js"; @@ -114,6 +116,13 @@ export abstract class AKChart extends LitElement { ]; } + timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string { + const valueStamp = ticks[index]; + const delta = Date.now() - valueStamp.value; + const ago = Math.round(delta / 1000 / 3600); + return t`${ago} hours ago`; + } + getOptions(): ChartOptions { return { maintainAspectRatio: false, @@ -122,15 +131,8 @@ export abstract class AKChart extends LitElement { type: "time", display: true, ticks: { - callback: function ( - tickValue: string | number, - index: number, - ticks: Tick[], - ): string { - const valueStamp = ticks[index]; - const delta = Date.now() - valueStamp.value; - const ago = Math.round(delta / 1000 / 3600); - return `${ago} Hours ago`; + callback: (tickValue: string | number, index: number, ticks: Tick[]) => { + return this.timeTickCallback(tickValue, index, ticks); }, autoSkip: true, maxTicksLimit: 8, diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index 27a9a37a3..4f977b44f 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -194,6 +194,9 @@ export class AdminInterface extends LitElement { ${t`Overview`} + + ${t`Users`} + ${t`System Tasks`} diff --git a/web/src/locales/en.po b/web/src/locales/en.po index 451d10764..111cd56bd 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -1902,6 +1902,10 @@ msgstr "External host" msgid "Failed Logins" msgstr "Failed Logins" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Failed Logins per day in the last month" +msgstr "Failed Logins per day in the last month" + #: src/pages/stages/password/PasswordStageForm.ts msgid "Failed attempts before cancel" msgstr "Failed attempts before cancel" @@ -2124,7 +2128,6 @@ msgid "General system exception" msgstr "General system exception" #: src/pages/admin-overview/AdminOverviewPage.ts -#: src/pages/admin-overview/UserDashboardPage.ts msgid "General system status" msgstr "General system status" @@ -2739,6 +2742,10 @@ msgstr "Logins" msgid "Logins over the last 24 hours" msgstr "Logins over the last 24 hours" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Logins per day in the last month" +msgstr "Logins per day in the last month" + #: src/pages/tenants/TenantForm.ts msgid "Logo" msgstr "Logo" @@ -3197,6 +3204,10 @@ msgstr "Object field" msgid "Object uniqueness field" msgstr "Object uniqueness field" +#: src/elements/charts/AdminModelPerDay.ts +msgid "Objects created" +msgstr "Objects created" + #: src/pages/stages/consent/ConsentStageForm.ts msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)." msgstr "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)." @@ -4774,7 +4785,6 @@ msgid "Superuser-groups" msgstr "Superuser-groups" #: src/pages/admin-overview/charts/UserCountStatusChart.ts -#: src/pages/admin-overview/charts/UserOverTimeStatusChart.ts msgid "Superusers" msgstr "Superusers" @@ -5138,7 +5148,6 @@ msgid "Total policies" msgstr "Total policies" #: src/pages/admin-overview/charts/UserCountStatusChart.ts -#: src/pages/admin-overview/charts/UserOverTimeStatusChart.ts msgid "Total users" msgstr "Total users" @@ -5549,8 +5558,8 @@ msgid "User matching mode" msgstr "User matching mode" #: src/pages/admin-overview/UserDashboardPage.ts -msgid "User metrics" -msgstr "User metrics" +#~ msgid "User metrics" +#~ msgstr "User metrics" #: src/pages/sources/ldap/LDAPSourceForm.ts msgid "User object filter" @@ -5560,6 +5569,10 @@ msgstr "User object filter" msgid "User password writeback" msgstr "User password writeback" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "User statistics" +msgstr "User statistics" + #: src/pages/users/UserListPage.ts msgid "User status" msgstr "User status" @@ -5641,6 +5654,10 @@ msgstr "Users" msgid "Users added to this group will be superusers." msgstr "Users added to this group will be superusers." +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Users created per day in the last month" +msgstr "Users created per day in the last month" + #: src/pages/providers/ldap/LDAPProviderForm.ts msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed." msgstr "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed." @@ -5972,3 +5989,11 @@ msgstr "{0}, should be {1}" #: src/elements/forms/ConfirmationForm.ts msgid "{0}: {1}" msgstr "{0}: {1}" + +#: src/elements/charts/AdminModelPerDay.ts +msgid "{ago} days ago" +msgstr "{ago} days ago" + +#: src/elements/charts/Chart.ts +msgid "{ago} hours ago" +msgstr "{ago} hours ago" diff --git a/web/src/locales/fr_FR.po b/web/src/locales/fr_FR.po index 1416a2de6..eabb84352 100644 --- a/web/src/locales/fr_FR.po +++ b/web/src/locales/fr_FR.po @@ -1888,6 +1888,10 @@ msgstr "Hôte externe" msgid "Failed Logins" msgstr "Connexions échouées" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Failed Logins per day in the last month" +msgstr "" + #: src/pages/stages/password/PasswordStageForm.ts msgid "Failed attempts before cancel" msgstr "Échecs avant annulation" @@ -2109,7 +2113,6 @@ msgid "General system exception" msgstr "Exception générale du systèm" #: src/pages/admin-overview/AdminOverviewPage.ts -#: src/pages/admin-overview/UserDashboardPage.ts msgid "General system status" msgstr "État général du système" @@ -2718,6 +2721,10 @@ msgstr "Connexions" msgid "Logins over the last 24 hours" msgstr "Connexions ces dernières 24 heures" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Logins per day in the last month" +msgstr "" + #: src/pages/tenants/TenantForm.ts msgid "Logo" msgstr "Logo" @@ -3173,6 +3180,10 @@ msgstr "Champ d'objet" msgid "Object uniqueness field" msgstr "Champ d'unicité de l'objet" +#: src/elements/charts/AdminModelPerDay.ts +msgid "Objects created" +msgstr "" + #: src/pages/stages/consent/ConsentStageForm.ts msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)." msgstr "Durée d'expiration du consentement (Format : hours=1;minutes=2;seconds=3)." @@ -4730,7 +4741,6 @@ msgid "Superuser-groups" msgstr "Groupes de super-utilisateur" #: src/pages/admin-overview/charts/UserCountStatusChart.ts -#: src/pages/admin-overview/charts/UserOverTimeStatusChart.ts msgid "Superusers" msgstr "Super-utilisateurs" @@ -5079,7 +5089,6 @@ msgid "Total policies" msgstr "Politiques totales" #: src/pages/admin-overview/charts/UserCountStatusChart.ts -#: src/pages/admin-overview/charts/UserOverTimeStatusChart.ts msgid "Total users" msgstr "Utilisateurs totaux" @@ -5487,8 +5496,8 @@ msgid "User matching mode" msgstr "Mode de correspondance utilisateur" #: src/pages/admin-overview/UserDashboardPage.ts -msgid "User metrics" -msgstr "" +#~ msgid "User metrics" +#~ msgstr "" #: src/pages/sources/ldap/LDAPSourceForm.ts msgid "User object filter" @@ -5498,6 +5507,10 @@ msgstr "Filtre des objets utilisateur" msgid "User password writeback" msgstr "Réécriture du mot de passe utilisateur" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "User statistics" +msgstr "" + #: src/pages/users/UserListPage.ts msgid "User status" msgstr "Statut utilisateur" @@ -5579,6 +5592,10 @@ msgstr "Utilisateurs" msgid "Users added to this group will be superusers." msgstr "Les utilisateurs ajoutés à ce groupe seront des super-utilisateurs." +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Users created per day in the last month" +msgstr "" + #: src/pages/providers/ldap/LDAPProviderForm.ts msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed." msgstr "Les utilisateurs de ce groupe peuvent effectuer des recherches. Si aucun groupe n'est sélectionné, aucune recherche LDAP n'est autorisée." @@ -5906,3 +5923,11 @@ msgstr "{0}, devrait être {1}" #: src/elements/forms/ConfirmationForm.ts msgid "{0}: {1}" msgstr "{0} : {1}" + +#: src/elements/charts/AdminModelPerDay.ts +msgid "{ago} days ago" +msgstr "" + +#: src/elements/charts/Chart.ts +msgid "{ago} hours ago" +msgstr "" diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po index b05348885..59433f231 100644 --- a/web/src/locales/pseudo-LOCALE.po +++ b/web/src/locales/pseudo-LOCALE.po @@ -1894,6 +1894,10 @@ msgstr "" msgid "Failed Logins" msgstr "" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Failed Logins per day in the last month" +msgstr "" + #: src/pages/stages/password/PasswordStageForm.ts msgid "Failed attempts before cancel" msgstr "" @@ -2116,7 +2120,6 @@ msgid "General system exception" msgstr "" #: src/pages/admin-overview/AdminOverviewPage.ts -#: src/pages/admin-overview/UserDashboardPage.ts msgid "General system status" msgstr "" @@ -2729,6 +2732,10 @@ msgstr "" msgid "Logins over the last 24 hours" msgstr "" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Logins per day in the last month" +msgstr "" + #: src/pages/tenants/TenantForm.ts msgid "Logo" msgstr "" @@ -3187,6 +3194,10 @@ msgstr "" msgid "Object uniqueness field" msgstr "" +#: src/elements/charts/AdminModelPerDay.ts +msgid "Objects created" +msgstr "" + #: src/pages/stages/consent/ConsentStageForm.ts msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)." msgstr "" @@ -4764,7 +4775,6 @@ msgid "Superuser-groups" msgstr "" #: src/pages/admin-overview/charts/UserCountStatusChart.ts -#: src/pages/admin-overview/charts/UserOverTimeStatusChart.ts msgid "Superusers" msgstr "" @@ -5118,7 +5128,6 @@ msgid "Total policies" msgstr "" #: src/pages/admin-overview/charts/UserCountStatusChart.ts -#: src/pages/admin-overview/charts/UserOverTimeStatusChart.ts msgid "Total users" msgstr "" @@ -5529,8 +5538,8 @@ msgid "User matching mode" msgstr "" #: src/pages/admin-overview/UserDashboardPage.ts -msgid "User metrics" -msgstr "" +#~ msgid "User metrics" +#~ msgstr "" #: src/pages/sources/ldap/LDAPSourceForm.ts msgid "User object filter" @@ -5540,6 +5549,10 @@ msgstr "" msgid "User password writeback" msgstr "" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "User statistics" +msgstr "" + #: src/pages/users/UserListPage.ts msgid "User status" msgstr "" @@ -5621,6 +5634,10 @@ msgstr "" msgid "Users added to this group will be superusers." msgstr "" +#: src/pages/admin-overview/DashboardUserPage.ts +msgid "Users created per day in the last month" +msgstr "" + #: src/pages/providers/ldap/LDAPProviderForm.ts msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed." msgstr "" @@ -5950,3 +5967,11 @@ msgstr "" #: src/elements/forms/ConfirmationForm.ts msgid "{0}: {1}" msgstr "" + +#: src/elements/charts/AdminModelPerDay.ts +msgid "{ago} days ago" +msgstr "" + +#: src/elements/charts/Chart.ts +msgid "{ago} hours ago" +msgstr "" diff --git a/web/src/pages/admin-overview/AdminOverviewPage.ts b/web/src/pages/admin-overview/AdminOverviewPage.ts index 926219684..7e6c76c03 100644 --- a/web/src/pages/admin-overview/AdminOverviewPage.ts +++ b/web/src/pages/admin-overview/AdminOverviewPage.ts @@ -91,9 +91,7 @@ export class AdminOverviewPage extends LitElement { >
  • - ${t`Check the logs`}
  • diff --git a/web/src/pages/admin-overview/DashboardUserPage.ts b/web/src/pages/admin-overview/DashboardUserPage.ts new file mode 100644 index 000000000..f7d91353d --- /dev/null +++ b/web/src/pages/admin-overview/DashboardUserPage.ts @@ -0,0 +1,86 @@ +import { t } from "@lingui/macro"; + +import { CSSResult, LitElement, TemplateResult, css, html } from "lit"; +import { customElement } from "lit/decorators.js"; + +import AKGlobal from "../../authentik.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFList from "@patternfly/patternfly/components/List/list.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; + +import { EventActions } from "@goauthentik/api"; + +import "../../elements/PageHeader"; +import "../../elements/cards/AggregatePromiseCard"; +import "../../elements/charts/AdminModelPerDay"; + +@customElement("ak-admin-dashboard-users") +export class DashboardUserPage extends LitElement { + static get styles(): CSSResult[] { + return [ + PFGrid, + PFPage, + PFContent, + PFList, + AKGlobal, + css` + .row-divider { + margin-top: -4px; + margin-bottom: -4px; + } + .graph-container { + height: 20em; + } + .big-graph-container { + height: 35em; + } + .card-container { + max-height: 10em; + } + `, + ]; + } + + render(): TemplateResult { + return html` + +
    +
    +
    + + + + +
    +
    +
    +
    + +
    + + + + +
    +
    + + + + +
    +
    +
    `; + } +} diff --git a/web/src/routesAdmin.ts b/web/src/routesAdmin.ts index e66ea154f..99e2250df 100644 --- a/web/src/routesAdmin.ts +++ b/web/src/routesAdmin.ts @@ -2,6 +2,7 @@ import { html } from "lit"; import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "./elements/router/Route"; import "./pages/admin-overview/AdminOverviewPage"; +import "./pages/admin-overview/DashboardUserPage"; import "./pages/applications/ApplicationListPage"; import "./pages/applications/ApplicationViewPage"; import "./pages/crypto/CertificateKeyPairListPage"; @@ -40,6 +41,10 @@ export const ROUTES: Route[] = [ new RegExp("^/administration/overview$"), html``, ), + new Route( + new RegExp("^/administration/dashboard/users$"), + html``, + ), new Route( new RegExp("^/administration/system-tasks$"), html``,