Compare commits
	
		
			12 Commits
		
	
	
		
			safari-fol
			...
			dependabot
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0f88f5214 | |||
| 2bef7695db | |||
| df472dd842 | |||
| 98d201d34c | |||
| 47e89602ab | |||
| ceb0851452 | |||
| cac2593658 | |||
| 1c9705bfaa | |||
| 9e2566cec4 | |||
| 5bdef1c4f6 | |||
| ae41ccd862 | |||
| 337956672f | 
							
								
								
									
										8
									
								
								.github/workflows/packages-npm-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/packages-npm-publish.yml
									
									
									
									
										vendored
									
									
								
							@ -3,10 +3,10 @@ on:
 | 
				
			|||||||
  push:
 | 
					  push:
 | 
				
			||||||
    branches: [main]
 | 
					    branches: [main]
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - packages/docusaurus-config
 | 
					      - packages/docusaurus-config/**
 | 
				
			||||||
      - packages/eslint-config
 | 
					      - packages/eslint-config/**
 | 
				
			||||||
      - packages/prettier-config
 | 
					      - packages/prettier-config/**
 | 
				
			||||||
      - packages/tsconfig
 | 
					      - packages/tsconfig/**
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  publish:
 | 
					  publish:
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -18,7 +18,7 @@ msgid ""
 | 
				
			|||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
					"Project-Id-Version: PACKAGE VERSION\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
 | 
					"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
				
			||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
					"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
				
			||||||
"Last-Translator: Gil Poiares-Oliveira, 2025\n"
 | 
					"Last-Translator: Gil Poiares-Oliveira, 2025\n"
 | 
				
			||||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
 | 
					"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
 | 
				
			||||||
@ -192,6 +192,7 @@ msgid "User's display name."
 | 
				
			|||||||
msgstr "Nome de exibição do usuário."
 | 
					msgstr "Nome de exibição do usuário."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
 | 
					#: authentik/core/models.py authentik/providers/oauth2/models.py
 | 
				
			||||||
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
msgid "User"
 | 
					msgid "User"
 | 
				
			||||||
msgstr "Usuário"
 | 
					msgstr "Usuário"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -376,6 +377,18 @@ msgstr "Mapeamento de propriedades"
 | 
				
			|||||||
msgid "Property Mappings"
 | 
					msgid "Property Mappings"
 | 
				
			||||||
msgstr "Mapeamentos de propriedades"
 | 
					msgstr "Mapeamentos de propriedades"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "session data"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "Session"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "Sessions"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/core/models.py
 | 
					#: authentik/core/models.py
 | 
				
			||||||
msgid "Authenticated Session"
 | 
					msgid "Authenticated Session"
 | 
				
			||||||
msgstr "Sessão Autenticada"
 | 
					msgstr "Sessão Autenticada"
 | 
				
			||||||
@ -483,6 +496,38 @@ msgstr "Uso de licença"
 | 
				
			|||||||
msgid "License Usage Records"
 | 
					msgid "License Usage Records"
 | 
				
			||||||
msgstr "Registros de uso de licença"
 | 
					msgstr "Registros de uso de licença"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
 | 
					msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Chave de campo para verificar, as chaves de campo definidas nos estágios de "
 | 
				
			||||||
 | 
					"prompt estão disponíveis."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Number of passwords to check against."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
 | 
					msgid "Password not set in context"
 | 
				
			||||||
 | 
					msgstr "Senha não definida no contexto"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "This password has been used previously. Please choose a different one."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Password Uniqueness Policy"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Password Uniqueness Policies"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "User Password History"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/enterprise/policy.py
 | 
					#: authentik/enterprise/policy.py
 | 
				
			||||||
msgid "Enterprise required to access this feature."
 | 
					msgid "Enterprise required to access this feature."
 | 
				
			||||||
msgstr "Entrerprise é necessário para acessar essa funcionalidade"
 | 
					msgstr "Entrerprise é necessário para acessar essa funcionalidade"
 | 
				
			||||||
@ -1252,12 +1297,6 @@ msgstr ""
 | 
				
			|||||||
msgid "Clear Policy's cache metrics"
 | 
					msgid "Clear Policy's cache metrics"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					 | 
				
			||||||
msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
"Chave de campo para verificar, as chaves de campo definidas nos estágios de "
 | 
					 | 
				
			||||||
"prompt estão disponíveis."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
 | 
					msgid "How many times the password hash is allowed to be on haveibeenpwned"
 | 
				
			||||||
msgstr "Quantas vezes o hash da senha pode estar em haveibeenpwned"
 | 
					msgstr "Quantas vezes o hash da senha pode estar em haveibeenpwned"
 | 
				
			||||||
@ -1268,10 +1307,6 @@ msgid ""
 | 
				
			|||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Se a pontuação zxcvbn for igual ou menor que esse valor, a política falhará."
 | 
					"Se a pontuação zxcvbn for igual ou menor que esse valor, a política falhará."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					 | 
				
			||||||
msgid "Password not set in context"
 | 
					 | 
				
			||||||
msgstr "Senha não definida no contexto"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
msgid "Invalid password."
 | 
					msgid "Invalid password."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
@ -1313,20 +1348,6 @@ msgstr "Pontuação de reputação"
 | 
				
			|||||||
msgid "Reputation Scores"
 | 
					msgid "Reputation Scores"
 | 
				
			||||||
msgstr "Pontuações de reputação"
 | 
					msgstr "Pontuações de reputação"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid "Waiting for authentication..."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid ""
 | 
					 | 
				
			||||||
"You're already authenticating in another tab. This page will refresh once "
 | 
					 | 
				
			||||||
"authentication is completed."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid "Authenticate in this tab"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/denied.html
 | 
					#: authentik/policies/templates/policies/denied.html
 | 
				
			||||||
msgid "Permission denied"
 | 
					msgid "Permission denied"
 | 
				
			||||||
msgstr "Permissão negada"
 | 
					msgstr "Permissão negada"
 | 
				
			||||||
@ -2141,6 +2162,10 @@ msgstr ""
 | 
				
			|||||||
msgid "Roles"
 | 
					msgid "Roles"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
 | 
					msgid "Initial Permissions"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/rbac/models.py
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
msgid "System permission"
 | 
					msgid "System permission"
 | 
				
			||||||
msgstr "Permissão do sistema"
 | 
					msgstr "Permissão do sistema"
 | 
				
			||||||
@ -2387,6 +2412,22 @@ msgstr ""
 | 
				
			|||||||
msgid "LDAP Source Property Mappings"
 | 
					msgid "LDAP Source Property Mappings"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "User LDAP Source Connection"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "User LDAP Source Connections"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "Group LDAP Source Connection"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "Group LDAP Source Connections"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/ldap/signals.py
 | 
					#: authentik/sources/ldap/signals.py
 | 
				
			||||||
msgid "Password does not match Active Directory Complexity."
 | 
					msgid "Password does not match Active Directory Complexity."
 | 
				
			||||||
msgstr "A senha não corresponde à complexidade do Active Directory."
 | 
					msgstr "A senha não corresponde à complexidade do Active Directory."
 | 
				
			||||||
@ -2395,6 +2436,14 @@ msgstr "A senha não corresponde à complexidade do Active Directory."
 | 
				
			|||||||
msgid "No token received."
 | 
					msgid "No token received."
 | 
				
			||||||
msgstr "Nenhum token recebido."
 | 
					msgstr "Nenhum token recebido."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid "HTTP Basic Authentication"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid "Include the client ID and secret as request parameters"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/oauth/models.py
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
msgid "Request Token URL"
 | 
					msgid "Request Token URL"
 | 
				
			||||||
msgstr "URL do token de solicitação"
 | 
					msgstr "URL do token de solicitação"
 | 
				
			||||||
@ -2435,6 +2484,12 @@ msgstr "URL usado pelo authentik para obter informações do usuário."
 | 
				
			|||||||
msgid "Additional Scopes"
 | 
					msgid "Additional Scopes"
 | 
				
			||||||
msgstr "Escopos Adicionais"
 | 
					msgstr "Escopos Adicionais"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"How to perform authentication during an authorization_code token request "
 | 
				
			||||||
 | 
					"flow"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/oauth/models.py
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
msgid "OAuth Source"
 | 
					msgid "OAuth Source"
 | 
				
			||||||
msgstr "Fonte OAuth"
 | 
					msgstr "Fonte OAuth"
 | 
				
			||||||
@ -3318,6 +3373,12 @@ msgid ""
 | 
				
			|||||||
"info is entered."
 | 
					"info is entered."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/stages/identification/models.py
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"Show the user the 'Remember me on this device' toggle, allowing repeat users"
 | 
				
			||||||
 | 
					" to skip straight to entering their password."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/stages/identification/models.py
 | 
					#: authentik/stages/identification/models.py
 | 
				
			||||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
 | 
					msgid "Optional enrollment flow, which is linked at the bottom of the page."
 | 
				
			||||||
msgstr "Optional enrollment flow, which is linked at the bottom of the page."
 | 
					msgstr "Optional enrollment flow, which is linked at the bottom of the page."
 | 
				
			||||||
@ -3678,6 +3739,14 @@ msgstr ""
 | 
				
			|||||||
"Os eventos serão excluídos após esta duração.(Formato: "
 | 
					"Os eventos serão excluídos após esta duração.(Formato: "
 | 
				
			||||||
"semanas=3;dias=2;horas=3,segundos=2)."
 | 
					"semanas=3;dias=2;horas=3,segundos=2)."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
 | 
					msgid "Reputation cannot decrease lower than this value. Zero or negative."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
 | 
					msgid "Reputation cannot increase higher than this value. Zero or positive."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/tenants/models.py
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
msgid "The option configures the footer links on the flow executor pages."
 | 
					msgid "The option configures the footer links on the flow executor pages."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ msgid ""
 | 
				
			|||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
					"Project-Id-Version: PACKAGE VERSION\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
 | 
					"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
				
			||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
					"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
				
			||||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
 | 
					"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
 | 
				
			||||||
"Language-Team: Turkish (https://app.transifex.com/authentik/teams/119923/tr/)\n"
 | 
					"Language-Team: Turkish (https://app.transifex.com/authentik/teams/119923/tr/)\n"
 | 
				
			||||||
@ -187,6 +187,7 @@ msgid "User's display name."
 | 
				
			|||||||
msgstr "Kullanıcının görünen adı."
 | 
					msgstr "Kullanıcının görünen adı."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
 | 
					#: authentik/core/models.py authentik/providers/oauth2/models.py
 | 
				
			||||||
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
msgid "User"
 | 
					msgid "User"
 | 
				
			||||||
msgstr "Kullanıcı"
 | 
					msgstr "Kullanıcı"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -372,6 +373,18 @@ msgstr "Özellik Eşleme"
 | 
				
			|||||||
msgid "Property Mappings"
 | 
					msgid "Property Mappings"
 | 
				
			||||||
msgstr "Özellik Eşlemeleri"
 | 
					msgstr "Özellik Eşlemeleri"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "session data"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "Session"
 | 
				
			||||||
 | 
					msgstr "Oturum"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "Sessions"
 | 
				
			||||||
 | 
					msgstr "Oturumlar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/core/models.py
 | 
					#: authentik/core/models.py
 | 
				
			||||||
msgid "Authenticated Session"
 | 
					msgid "Authenticated Session"
 | 
				
			||||||
msgstr "Kimliği Doğrulanmış Oturum"
 | 
					msgstr "Kimliği Doğrulanmış Oturum"
 | 
				
			||||||
@ -479,6 +492,38 @@ msgstr "Lisans Kullanımı"
 | 
				
			|||||||
msgid "License Usage Records"
 | 
					msgid "License Usage Records"
 | 
				
			||||||
msgstr "Lisans Kullanım Kayıtları"
 | 
					msgstr "Lisans Kullanım Kayıtları"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
 | 
					msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Alan tuşu kontrol etmek için, İstem aşamalarında tanımlanan alan tuşları "
 | 
				
			||||||
 | 
					"mevcuttur."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Number of passwords to check against."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
 | 
					msgid "Password not set in context"
 | 
				
			||||||
 | 
					msgstr "Parola bağlam içinde ayarlanmamış"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "This password has been used previously. Please choose a different one."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Password Uniqueness Policy"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Password Uniqueness Policies"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "User Password History"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/enterprise/policy.py
 | 
					#: authentik/enterprise/policy.py
 | 
				
			||||||
msgid "Enterprise required to access this feature."
 | 
					msgid "Enterprise required to access this feature."
 | 
				
			||||||
msgstr "Bu özelliğe erişmek için Kurumsal Paket gereklidir."
 | 
					msgstr "Bu özelliğe erişmek için Kurumsal Paket gereklidir."
 | 
				
			||||||
@ -1253,12 +1298,6 @@ msgstr "İlke'nin önbellek ölçümlerini görüntüleme"
 | 
				
			|||||||
msgid "Clear Policy's cache metrics"
 | 
					msgid "Clear Policy's cache metrics"
 | 
				
			||||||
msgstr "İlke'nin önbellek ölçümlerini temizleyin"
 | 
					msgstr "İlke'nin önbellek ölçümlerini temizleyin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					 | 
				
			||||||
msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
"Alan tuşu kontrol etmek için, İstem aşamalarında tanımlanan alan tuşları "
 | 
					 | 
				
			||||||
"mevcuttur."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
 | 
					msgid "How many times the password hash is allowed to be on haveibeenpwned"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
@ -1271,10 +1310,6 @@ msgstr ""
 | 
				
			|||||||
"Eğer zxcvbn puanı bu değere eşit veya daha az ise, politika başarısız "
 | 
					"Eğer zxcvbn puanı bu değere eşit veya daha az ise, politika başarısız "
 | 
				
			||||||
"olacaktır."
 | 
					"olacaktır."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					 | 
				
			||||||
msgid "Password not set in context"
 | 
					 | 
				
			||||||
msgstr "Parola bağlam içinde ayarlanmamış"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
msgid "Invalid password."
 | 
					msgid "Invalid password."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
@ -1316,20 +1351,6 @@ msgstr "İtibar Puanı"
 | 
				
			|||||||
msgid "Reputation Scores"
 | 
					msgid "Reputation Scores"
 | 
				
			||||||
msgstr "İtibar Puanları"
 | 
					msgstr "İtibar Puanları"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid "Waiting for authentication..."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid ""
 | 
					 | 
				
			||||||
"You're already authenticating in another tab. This page will refresh once "
 | 
					 | 
				
			||||||
"authentication is completed."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid "Authenticate in this tab"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/denied.html
 | 
					#: authentik/policies/templates/policies/denied.html
 | 
				
			||||||
msgid "Permission denied"
 | 
					msgid "Permission denied"
 | 
				
			||||||
msgstr "İzin reddedildi"
 | 
					msgstr "İzin reddedildi"
 | 
				
			||||||
@ -2155,6 +2176,10 @@ msgstr "Rol"
 | 
				
			|||||||
msgid "Roles"
 | 
					msgid "Roles"
 | 
				
			||||||
msgstr "Roller"
 | 
					msgstr "Roller"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
 | 
					msgid "Initial Permissions"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/rbac/models.py
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
msgid "System permission"
 | 
					msgid "System permission"
 | 
				
			||||||
msgstr "Sistem yetkisi"
 | 
					msgstr "Sistem yetkisi"
 | 
				
			||||||
@ -2398,6 +2423,13 @@ msgstr ""
 | 
				
			|||||||
"Bir kullanıcı parolasını değiştirdiğinde, parolayı LDAP ile geri eşitleyin. "
 | 
					"Bir kullanıcı parolasını değiştirdiğinde, parolayı LDAP ile geri eşitleyin. "
 | 
				
			||||||
"Bu yalnızca tek bir LDAP kaynağında etkinleştirilebilir."
 | 
					"Bu yalnızca tek bir LDAP kaynağında etkinleştirilebilir."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"Lookup group membership based on a user attribute instead of a group "
 | 
				
			||||||
 | 
					"attribute. This allows nested group resolution on systems like FreeIPA and "
 | 
				
			||||||
 | 
					"Active Directory"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/ldap/models.py
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
msgid "LDAP Source"
 | 
					msgid "LDAP Source"
 | 
				
			||||||
msgstr "LDAP Kaynağı"
 | 
					msgstr "LDAP Kaynağı"
 | 
				
			||||||
@ -2414,6 +2446,22 @@ msgstr "LDAP Kaynak Özellik Eşlemesi"
 | 
				
			|||||||
msgid "LDAP Source Property Mappings"
 | 
					msgid "LDAP Source Property Mappings"
 | 
				
			||||||
msgstr "LDAP Kaynak Özellik Eşlemeleri"
 | 
					msgstr "LDAP Kaynak Özellik Eşlemeleri"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "User LDAP Source Connection"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "User LDAP Source Connections"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "Group LDAP Source Connection"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "Group LDAP Source Connections"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/ldap/signals.py
 | 
					#: authentik/sources/ldap/signals.py
 | 
				
			||||||
msgid "Password does not match Active Directory Complexity."
 | 
					msgid "Password does not match Active Directory Complexity."
 | 
				
			||||||
msgstr "Parola Active Directory Karmaşıklığıyla eşleşmiyor."
 | 
					msgstr "Parola Active Directory Karmaşıklığıyla eşleşmiyor."
 | 
				
			||||||
@ -2422,6 +2470,14 @@ msgstr "Parola Active Directory Karmaşıklığıyla eşleşmiyor."
 | 
				
			|||||||
msgid "No token received."
 | 
					msgid "No token received."
 | 
				
			||||||
msgstr "Jeton alınmadı."
 | 
					msgstr "Jeton alınmadı."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid "HTTP Basic Authentication"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid "Include the client ID and secret as request parameters"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/oauth/models.py
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
msgid "Request Token URL"
 | 
					msgid "Request Token URL"
 | 
				
			||||||
msgstr "Jeton URL'si İste"
 | 
					msgstr "Jeton URL'si İste"
 | 
				
			||||||
@ -2462,6 +2518,12 @@ msgstr "Kullanıcı bilgilerini almak için authentik tarafından kullanılan UR
 | 
				
			|||||||
msgid "Additional Scopes"
 | 
					msgid "Additional Scopes"
 | 
				
			||||||
msgstr "Ek Kapsamlar"
 | 
					msgstr "Ek Kapsamlar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"How to perform authentication during an authorization_code token request "
 | 
				
			||||||
 | 
					"flow"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/oauth/models.py
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
msgid "OAuth Source"
 | 
					msgid "OAuth Source"
 | 
				
			||||||
msgstr "OAuth Kaynağı"
 | 
					msgstr "OAuth Kaynağı"
 | 
				
			||||||
@ -3360,6 +3422,12 @@ msgstr ""
 | 
				
			|||||||
"Etkinleştirildiğinde, yanlış kullanıcı bilgisi girilse bile aşama başarılı "
 | 
					"Etkinleştirildiğinde, yanlış kullanıcı bilgisi girilse bile aşama başarılı "
 | 
				
			||||||
"olur ve devam eder."
 | 
					"olur ve devam eder."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/stages/identification/models.py
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"Show the user the 'Remember me on this device' toggle, allowing repeat users"
 | 
				
			||||||
 | 
					" to skip straight to entering their password."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/stages/identification/models.py
 | 
					#: authentik/stages/identification/models.py
 | 
				
			||||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
 | 
					msgid "Optional enrollment flow, which is linked at the bottom of the page."
 | 
				
			||||||
msgstr "Sayfanın alt kısmında bağlanan isteğe bağlı kayıt akışı."
 | 
					msgstr "Sayfanın alt kısmında bağlanan isteğe bağlı kayıt akışı."
 | 
				
			||||||
@ -3734,6 +3802,14 @@ msgstr ""
 | 
				
			|||||||
"Olaylar bu süreden sonra silinecektir (Format: "
 | 
					"Olaylar bu süreden sonra silinecektir (Format: "
 | 
				
			||||||
"weeks=3;days=2;hours=3,seconds=2)."
 | 
					"weeks=3;days=2;hours=3,seconds=2)."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
 | 
					msgid "Reputation cannot decrease lower than this value. Zero or negative."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
 | 
					msgid "Reputation cannot increase higher than this value. Zero or positive."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/tenants/models.py
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
msgid "The option configures the footer links on the flow executor pages."
 | 
					msgid "The option configures the footer links on the flow executor pages."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ msgid ""
 | 
				
			|||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
					"Project-Id-Version: PACKAGE VERSION\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
 | 
					"POT-Creation-Date: 2025-04-23 09:00+0000\n"
 | 
				
			||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
					"PO-Revision-Date: 2022-09-26 16:47+0000\n"
 | 
				
			||||||
"Last-Translator: 刘松, 2025\n"
 | 
					"Last-Translator: 刘松, 2025\n"
 | 
				
			||||||
"Language-Team: Chinese (Taiwan) (https://app.transifex.com/authentik/teams/119923/zh_TW/)\n"
 | 
					"Language-Team: Chinese (Taiwan) (https://app.transifex.com/authentik/teams/119923/zh_TW/)\n"
 | 
				
			||||||
@ -178,6 +178,7 @@ msgid "User's display name."
 | 
				
			|||||||
msgstr "使用者的顯示名稱。"
 | 
					msgstr "使用者的顯示名稱。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
 | 
					#: authentik/core/models.py authentik/providers/oauth2/models.py
 | 
				
			||||||
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
msgid "User"
 | 
					msgid "User"
 | 
				
			||||||
msgstr "使用者"
 | 
					msgstr "使用者"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -344,6 +345,18 @@ msgstr "屬性對應"
 | 
				
			|||||||
msgid "Property Mappings"
 | 
					msgid "Property Mappings"
 | 
				
			||||||
msgstr "屬性對應"
 | 
					msgstr "屬性對應"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "session data"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "Session"
 | 
				
			||||||
 | 
					msgstr "会话"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/core/models.py
 | 
				
			||||||
 | 
					msgid "Sessions"
 | 
				
			||||||
 | 
					msgstr "会话"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/core/models.py
 | 
					#: authentik/core/models.py
 | 
				
			||||||
msgid "Authenticated Session"
 | 
					msgid "Authenticated Session"
 | 
				
			||||||
msgstr "已認證會談"
 | 
					msgstr "已認證會談"
 | 
				
			||||||
@ -447,6 +460,36 @@ msgstr "授權使用情況"
 | 
				
			|||||||
msgid "License Usage Records"
 | 
					msgid "License Usage Records"
 | 
				
			||||||
msgstr "授權使用紀錄"
 | 
					msgstr "授權使用紀錄"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
 | 
					msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
				
			||||||
 | 
					msgstr "要檢查的欄位鍵,在提示階段中有可用的已定義欄位鍵。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Number of passwords to check against."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
 | 
					msgid "Password not set in context"
 | 
				
			||||||
 | 
					msgstr "未在上下文中設定密碼"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "This password has been used previously. Please choose a different one."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Password Uniqueness Policy"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "Password Uniqueness Policies"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/enterprise/policies/unique_password/models.py
 | 
				
			||||||
 | 
					msgid "User Password History"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/enterprise/policy.py
 | 
					#: authentik/enterprise/policy.py
 | 
				
			||||||
msgid "Enterprise required to access this feature."
 | 
					msgid "Enterprise required to access this feature."
 | 
				
			||||||
msgstr "企業版才能存取此功能。"
 | 
					msgstr "企業版才能存取此功能。"
 | 
				
			||||||
@ -1176,10 +1219,6 @@ msgstr "檢視原則的快取指標"
 | 
				
			|||||||
msgid "Clear Policy's cache metrics"
 | 
					msgid "Clear Policy's cache metrics"
 | 
				
			||||||
msgstr "清除原則的快取指標"
 | 
					msgstr "清除原則的快取指標"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					 | 
				
			||||||
msgid "Field key to check, field keys defined in Prompt stages are available."
 | 
					 | 
				
			||||||
msgstr "要檢查的欄位鍵,在提示階段中有可用的已定義欄位鍵。"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
 | 
					msgid "How many times the password hash is allowed to be on haveibeenpwned"
 | 
				
			||||||
msgstr "密碼雜湊在 haveibeenpwned 上允許出現的次數"
 | 
					msgstr "密碼雜湊在 haveibeenpwned 上允許出現的次數"
 | 
				
			||||||
@ -1189,10 +1228,6 @@ msgid ""
 | 
				
			|||||||
"If the zxcvbn score is equal or less than this value, the policy will fail."
 | 
					"If the zxcvbn score is equal or less than this value, the policy will fail."
 | 
				
			||||||
msgstr "如果 zxcvbn 分數等於或小於此值,則該政策將失敗。"
 | 
					msgstr "如果 zxcvbn 分數等於或小於此值,則該政策將失敗。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					 | 
				
			||||||
msgid "Password not set in context"
 | 
					 | 
				
			||||||
msgstr "未在上下文中設定密碼"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/password/models.py
 | 
					#: authentik/policies/password/models.py
 | 
				
			||||||
msgid "Invalid password."
 | 
					msgid "Invalid password."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
@ -1234,20 +1269,6 @@ msgstr "信譽分數"
 | 
				
			|||||||
msgid "Reputation Scores"
 | 
					msgid "Reputation Scores"
 | 
				
			||||||
msgstr "信譽分數"
 | 
					msgstr "信譽分數"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid "Waiting for authentication..."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid ""
 | 
					 | 
				
			||||||
"You're already authenticating in another tab. This page will refresh once "
 | 
					 | 
				
			||||||
"authentication is completed."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/buffer.html
 | 
					 | 
				
			||||||
msgid "Authenticate in this tab"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: authentik/policies/templates/policies/denied.html
 | 
					#: authentik/policies/templates/policies/denied.html
 | 
				
			||||||
msgid "Permission denied"
 | 
					msgid "Permission denied"
 | 
				
			||||||
msgstr "權限不足。"
 | 
					msgstr "權限不足。"
 | 
				
			||||||
@ -1999,6 +2020,10 @@ msgstr "角色"
 | 
				
			|||||||
msgid "Roles"
 | 
					msgid "Roles"
 | 
				
			||||||
msgstr "角色"
 | 
					msgstr "角色"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
 | 
					msgid "Initial Permissions"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/rbac/models.py
 | 
					#: authentik/rbac/models.py
 | 
				
			||||||
msgid "System permission"
 | 
					msgid "System permission"
 | 
				
			||||||
msgstr "系統權限"
 | 
					msgstr "系統權限"
 | 
				
			||||||
@ -2240,6 +2265,22 @@ msgstr ""
 | 
				
			|||||||
msgid "LDAP Source Property Mappings"
 | 
					msgid "LDAP Source Property Mappings"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "User LDAP Source Connection"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "User LDAP Source Connections"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "Group LDAP Source Connection"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/ldap/models.py
 | 
				
			||||||
 | 
					msgid "Group LDAP Source Connections"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/ldap/signals.py
 | 
					#: authentik/sources/ldap/signals.py
 | 
				
			||||||
msgid "Password does not match Active Directory Complexity."
 | 
					msgid "Password does not match Active Directory Complexity."
 | 
				
			||||||
msgstr "密碼不符合 Active Directory 的複雜性要求。"
 | 
					msgstr "密碼不符合 Active Directory 的複雜性要求。"
 | 
				
			||||||
@ -2248,6 +2289,14 @@ msgstr "密碼不符合 Active Directory 的複雜性要求。"
 | 
				
			|||||||
msgid "No token received."
 | 
					msgid "No token received."
 | 
				
			||||||
msgstr "未收到權杖。"
 | 
					msgstr "未收到權杖。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid "HTTP Basic Authentication"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid "Include the client ID and secret as request parameters"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/oauth/models.py
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
msgid "Request Token URL"
 | 
					msgid "Request Token URL"
 | 
				
			||||||
msgstr "請求權杖的網址"
 | 
					msgstr "請求權杖的網址"
 | 
				
			||||||
@ -2286,6 +2335,12 @@ msgstr "authentik 用來擷取使用者資訊的網址。"
 | 
				
			|||||||
msgid "Additional Scopes"
 | 
					msgid "Additional Scopes"
 | 
				
			||||||
msgstr "附加範圍"
 | 
					msgstr "附加範圍"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"How to perform authentication during an authorization_code token request "
 | 
				
			||||||
 | 
					"flow"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/sources/oauth/models.py
 | 
					#: authentik/sources/oauth/models.py
 | 
				
			||||||
msgid "OAuth Source"
 | 
					msgid "OAuth Source"
 | 
				
			||||||
msgstr "OAuth 來源"
 | 
					msgstr "OAuth 來源"
 | 
				
			||||||
@ -3137,6 +3192,12 @@ msgid ""
 | 
				
			|||||||
"info is entered."
 | 
					"info is entered."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/stages/identification/models.py
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"Show the user the 'Remember me on this device' toggle, allowing repeat users"
 | 
				
			||||||
 | 
					" to skip straight to entering their password."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/stages/identification/models.py
 | 
					#: authentik/stages/identification/models.py
 | 
				
			||||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
 | 
					msgid "Optional enrollment flow, which is linked at the bottom of the page."
 | 
				
			||||||
msgstr "可選的註冊流程,連結在頁面的底部。"
 | 
					msgstr "可選的註冊流程,連結在頁面的底部。"
 | 
				
			||||||
@ -3481,6 +3542,14 @@ msgid ""
 | 
				
			|||||||
"weeks=3;days=2;hours=3,seconds=2)."
 | 
					"weeks=3;days=2;hours=3,seconds=2)."
 | 
				
			||||||
msgstr "事件將在此期間後刪除。(格式:weeks=3;days=2;hours=3,seconds=2)"
 | 
					msgstr "事件將在此期間後刪除。(格式:weeks=3;days=2;hours=3,seconds=2)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
 | 
					msgid "Reputation cannot decrease lower than this value. Zero or negative."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
 | 
					msgid "Reputation cannot increase higher than this value. Zero or positive."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: authentik/tenants/models.py
 | 
					#: authentik/tenants/models.py
 | 
				
			||||||
msgid "The option configures the footer links on the flow executor pages."
 | 
					msgid "The option configures the footer links on the flow executor pages."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12184
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12184
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -12,7 +12,8 @@
 | 
				
			|||||||
        "@floating-ui/dom": "^1.6.11",
 | 
					        "@floating-ui/dom": "^1.6.11",
 | 
				
			||||||
        "@formatjs/intl-listformat": "^7.5.7",
 | 
					        "@formatjs/intl-listformat": "^7.5.7",
 | 
				
			||||||
        "@fortawesome/fontawesome-free": "^6.6.0",
 | 
					        "@fortawesome/fontawesome-free": "^6.6.0",
 | 
				
			||||||
        "@goauthentik/api": "^2025.2.4-1745325566",
 | 
					        "@goauthentik/api": "^2025.2.4-1745519715",
 | 
				
			||||||
 | 
					        "@lit-labs/ssr": "3.2.2",
 | 
				
			||||||
        "@lit/context": "^1.1.2",
 | 
					        "@lit/context": "^1.1.2",
 | 
				
			||||||
        "@lit/localize": "^0.12.2",
 | 
					        "@lit/localize": "^0.12.2",
 | 
				
			||||||
        "@lit/reactive-element": "^2.0.4",
 | 
					        "@lit/reactive-element": "^2.0.4",
 | 
				
			||||||
@ -53,7 +54,6 @@
 | 
				
			|||||||
        "remark-gfm": "^4.0.1",
 | 
					        "remark-gfm": "^4.0.1",
 | 
				
			||||||
        "remark-mdx-frontmatter": "^5.0.0",
 | 
					        "remark-mdx-frontmatter": "^5.0.0",
 | 
				
			||||||
        "style-mod": "^4.1.2",
 | 
					        "style-mod": "^4.1.2",
 | 
				
			||||||
        "trusted-types": "^2.0.0",
 | 
					 | 
				
			||||||
        "ts-pattern": "^5.4.0",
 | 
					        "ts-pattern": "^5.4.0",
 | 
				
			||||||
        "unist-util-visit": "^5.0.0",
 | 
					        "unist-util-visit": "^5.0.0",
 | 
				
			||||||
        "webcomponent-qr-code": "^1.2.0",
 | 
					        "webcomponent-qr-code": "^1.2.0",
 | 
				
			||||||
 | 
				
			|||||||
@ -4,17 +4,13 @@ import { ROUTES } from "@goauthentik/admin/Routes";
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    EVENT_API_DRAWER_TOGGLE,
 | 
					    EVENT_API_DRAWER_TOGGLE,
 | 
				
			||||||
    EVENT_NOTIFICATION_DRAWER_TOGGLE,
 | 
					    EVENT_NOTIFICATION_DRAWER_TOGGLE,
 | 
				
			||||||
    EVENT_SIDEBAR_TOGGLE,
 | 
					 | 
				
			||||||
} from "@goauthentik/common/constants";
 | 
					} from "@goauthentik/common/constants";
 | 
				
			||||||
import { configureSentry } from "@goauthentik/common/sentry";
 | 
					import { configureSentry } from "@goauthentik/common/sentry";
 | 
				
			||||||
import { me } from "@goauthentik/common/users";
 | 
					import { me } from "@goauthentik/common/users";
 | 
				
			||||||
import { WebsocketClient } from "@goauthentik/common/ws";
 | 
					import { WebsocketClient } from "@goauthentik/common/ws";
 | 
				
			||||||
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
 | 
					import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
 | 
				
			||||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
 | 
					 | 
				
			||||||
import "@goauthentik/elements/ak-locale-context";
 | 
					import "@goauthentik/elements/ak-locale-context";
 | 
				
			||||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
 | 
					import "@goauthentik/elements/banner/EnterpriseStatusBanner";
 | 
				
			||||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
 | 
					 | 
				
			||||||
import "@goauthentik/elements/banner/VersionBanner";
 | 
					 | 
				
			||||||
import "@goauthentik/elements/banner/VersionBanner";
 | 
					import "@goauthentik/elements/banner/VersionBanner";
 | 
				
			||||||
import "@goauthentik/elements/messages/MessageContainer";
 | 
					import "@goauthentik/elements/messages/MessageContainer";
 | 
				
			||||||
import "@goauthentik/elements/messages/MessageContainer";
 | 
					import "@goauthentik/elements/messages/MessageContainer";
 | 
				
			||||||
@ -25,32 +21,25 @@ import "@goauthentik/elements/router/RouterOutlet";
 | 
				
			|||||||
import "@goauthentik/elements/sidebar/Sidebar";
 | 
					import "@goauthentik/elements/sidebar/Sidebar";
 | 
				
			||||||
import "@goauthentik/elements/sidebar/SidebarItem";
 | 
					import "@goauthentik/elements/sidebar/SidebarItem";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
 | 
					import { CSSResult, TemplateResult, css, html } from "lit";
 | 
				
			||||||
import { customElement, property, query, state } from "lit/decorators.js";
 | 
					import { customElement, property, query, state } from "lit/decorators.js";
 | 
				
			||||||
import { classMap } from "lit/directives/class-map.js";
 | 
					import { classMap } from "lit/directives/class-map.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
					import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
				
			||||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
 | 
					import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
 | 
				
			||||||
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
 | 
					 | 
				
			||||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
 | 
					import PFPage from "@patternfly/patternfly/components/Page/page.css";
 | 
				
			||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
					import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { LicenseSummaryStatusEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
 | 
					import { SessionUser, UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import "./AdminSidebar";
 | 
				
			||||||
    AdminSidebarEnterpriseEntries,
 | 
					 | 
				
			||||||
    AdminSidebarEntries,
 | 
					 | 
				
			||||||
    renderSidebarItems,
 | 
					 | 
				
			||||||
} from "./AdminSidebar.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (process.env.NODE_ENV === "development") {
 | 
					if (process.env.NODE_ENV === "development") {
 | 
				
			||||||
    await import("@goauthentik/esbuild-plugin-live-reload/client");
 | 
					    await import("@goauthentik/esbuild-plugin-live-reload/client");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-interface-admin")
 | 
					@customElement("ak-interface-admin")
 | 
				
			||||||
export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
					export class AdminInterface extends AuthenticatedInterface {
 | 
				
			||||||
    //#region Properties
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: Boolean })
 | 
					    @property({ type: Boolean })
 | 
				
			||||||
    notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
 | 
					    notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,29 +54,12 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
				
			|||||||
    @query("ak-about-modal")
 | 
					    @query("ak-about-modal")
 | 
				
			||||||
    aboutModal?: AboutModal;
 | 
					    aboutModal?: AboutModal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property({ type: Boolean, reflect: true })
 | 
					 | 
				
			||||||
    public sidebarOpen: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #toggleSidebar = () => {
 | 
					 | 
				
			||||||
        this.sidebarOpen = !this.sidebarOpen;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #sidebarMatcher: MediaQueryList;
 | 
					 | 
				
			||||||
    #sidebarListener = (event: MediaQueryListEvent) => {
 | 
					 | 
				
			||||||
        this.sidebarOpen = event.matches;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#region Styles
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            PFBase,
 | 
					            PFBase,
 | 
				
			||||||
            PFPage,
 | 
					            PFPage,
 | 
				
			||||||
            PFButton,
 | 
					            PFButton,
 | 
				
			||||||
            PFDrawer,
 | 
					            PFDrawer,
 | 
				
			||||||
            PFNav,
 | 
					 | 
				
			||||||
            css`
 | 
					            css`
 | 
				
			||||||
                .pf-c-page__main,
 | 
					                .pf-c-page__main,
 | 
				
			||||||
                .pf-c-drawer__content,
 | 
					                .pf-c-drawer__content,
 | 
				
			||||||
@ -95,30 +67,23 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
				
			|||||||
                    z-index: auto !important;
 | 
					                    z-index: auto !important;
 | 
				
			||||||
                    background-color: transparent;
 | 
					                    background-color: transparent;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                .display-none {
 | 
					                .display-none {
 | 
				
			||||||
                    display: none;
 | 
					                    display: none;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                .pf-c-page {
 | 
					                .pf-c-page {
 | 
				
			||||||
                    background-color: var(--pf-c-page--BackgroundColor) !important;
 | 
					                    background-color: var(--pf-c-page--BackgroundColor) !important;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                /* Global page background colour */
 | 
				
			||||||
                :host([theme="dark"]) {
 | 
					                :host([theme="dark"]) .pf-c-page {
 | 
				
			||||||
                    /* Global page background colour */
 | 
					                    --pf-c-page--BackgroundColor: var(--ak-dark-background);
 | 
				
			||||||
                    .pf-c-page {
 | 
					 | 
				
			||||||
                        --pf-c-page--BackgroundColor: var(--ak-dark-background);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                ak-enterprise-status,
 | 
				
			||||||
                ak-page-navbar {
 | 
					                ak-version-banner {
 | 
				
			||||||
                    grid-area: header;
 | 
					                    grid-area: header;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                ak-admin-sidebar {
 | 
				
			||||||
                .ak-sidebar {
 | 
					 | 
				
			||||||
                    grid-area: nav;
 | 
					                    grid-area: nav;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                .pf-c-drawer__panel {
 | 
					                .pf-c-drawer__panel {
 | 
				
			||||||
                    z-index: var(--pf-global--ZIndex--xl);
 | 
					                    z-index: var(--pf-global--ZIndex--xl);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -126,23 +91,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
				
			|||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#region Lifecycle
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
        this.ws = new WebsocketClient();
 | 
					        this.ws = new WebsocketClient();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)");
 | 
					 | 
				
			||||||
        this.sidebarOpen = this.#sidebarMatcher.matches;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public connectedCallback() {
 | 
					 | 
				
			||||||
        super.connectedCallback();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
 | 
					        window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
 | 
				
			||||||
            this.notificationDrawerOpen = !this.notificationDrawerOpen;
 | 
					            this.notificationDrawerOpen = !this.notificationDrawerOpen;
 | 
				
			||||||
            updateURLParams({
 | 
					            updateURLParams({
 | 
				
			||||||
@ -156,14 +108,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
				
			|||||||
                apiDrawerOpen: this.apiDrawerOpen,
 | 
					                apiDrawerOpen: this.apiDrawerOpen,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.#sidebarMatcher.addEventListener("change", this.#sidebarListener);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public disconnectedCallback(): void {
 | 
					 | 
				
			||||||
        super.disconnectedCallback();
 | 
					 | 
				
			||||||
        window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
 | 
					 | 
				
			||||||
        this.#sidebarMatcher.removeEventListener("change", this.#sidebarListener);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async firstUpdated(): Promise<void> {
 | 
					    async firstUpdated(): Promise<void> {
 | 
				
			||||||
@ -174,7 +118,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
				
			|||||||
            this.user.user.isSuperuser ||
 | 
					            this.user.user.isSuperuser ||
 | 
				
			||||||
            // TODO: somehow add `access_admin_interface` to the API schema
 | 
					            // TODO: somehow add `access_admin_interface` to the API schema
 | 
				
			||||||
            this.user.user.systemPermissions.includes("access_admin_interface");
 | 
					            this.user.user.systemPermissions.includes("access_admin_interface");
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!canAccessAdmin && this.user.user.pk > 0) {
 | 
					        if (!canAccessAdmin && this.user.user.pk > 0) {
 | 
				
			||||||
            window.location.assign("/if/user/");
 | 
					            window.location.assign("/if/user/");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -182,14 +125,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render(): TemplateResult {
 | 
				
			||||||
        const sidebarClasses = {
 | 
					        const sidebarClasses = {
 | 
				
			||||||
            "pf-c-page__sidebar": true,
 | 
					 | 
				
			||||||
            "pf-m-light": this.activeTheme === UiThemeEnum.Light,
 | 
					            "pf-m-light": this.activeTheme === UiThemeEnum.Light,
 | 
				
			||||||
            "pf-m-expanded": this.sidebarOpen,
 | 
					 | 
				
			||||||
            "pf-m-collapsed": !this.sidebarOpen,
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;
 | 
					        const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        const drawerClasses = {
 | 
					        const drawerClasses = {
 | 
				
			||||||
            "pf-m-expanded": drawerOpen,
 | 
					            "pf-m-expanded": drawerOpen,
 | 
				
			||||||
            "pf-m-collapsed": !drawerOpen,
 | 
					            "pf-m-collapsed": !drawerOpen,
 | 
				
			||||||
@ -197,18 +136,11 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return html` <ak-locale-context>
 | 
					        return html` <ak-locale-context>
 | 
				
			||||||
            <div class="pf-c-page">
 | 
					            <div class="pf-c-page">
 | 
				
			||||||
                <ak-page-navbar>
 | 
					                <ak-enterprise-status interface="admin"></ak-enterprise-status>
 | 
				
			||||||
                    <ak-version-banner></ak-version-banner>
 | 
					                <ak-version-banner></ak-version-banner>
 | 
				
			||||||
                    <ak-enterprise-status interface="admin"></ak-enterprise-status>
 | 
					                <ak-admin-sidebar
 | 
				
			||||||
                </ak-page-navbar>
 | 
					                    class="pf-c-page__sidebar ${classMap(sidebarClasses)}"
 | 
				
			||||||
 | 
					                ></ak-admin-sidebar>
 | 
				
			||||||
                <ak-sidebar class="${classMap(sidebarClasses)}">
 | 
					 | 
				
			||||||
                    ${renderSidebarItems(AdminSidebarEntries)}
 | 
					 | 
				
			||||||
                    ${this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed
 | 
					 | 
				
			||||||
                        ? renderSidebarItems(AdminSidebarEnterpriseEntries)
 | 
					 | 
				
			||||||
                        : nothing}
 | 
					 | 
				
			||||||
                </ak-sidebar>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <div class="pf-c-page__drawer">
 | 
					                <div class="pf-c-page__drawer">
 | 
				
			||||||
                    <div class="pf-c-drawer ${classMap(drawerClasses)}">
 | 
					                    <div class="pf-c-drawer ${classMap(drawerClasses)}">
 | 
				
			||||||
                        <div class="pf-c-drawer__main">
 | 
					                        <div class="pf-c-drawer__main">
 | 
				
			||||||
 | 
				
			|||||||
@ -1,97 +1,186 @@
 | 
				
			|||||||
 | 
					import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
 | 
				
			||||||
 | 
					import { me } from "@goauthentik/common/users";
 | 
				
			||||||
 | 
					import { AKElement } from "@goauthentik/elements/Base";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    CapabilitiesEnum,
 | 
				
			||||||
 | 
					    WithCapabilitiesConfig,
 | 
				
			||||||
 | 
					} from "@goauthentik/elements/Interface/capabilitiesProvider";
 | 
				
			||||||
 | 
					import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
 | 
				
			||||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
 | 
					import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
 | 
				
			||||||
 | 
					import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
 | 
				
			||||||
import { spread } from "@open-wc/lit-helpers";
 | 
					import { spread } from "@open-wc/lit-helpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { TemplateResult, html, nothing } from "lit";
 | 
					import { TemplateResult, html, nothing } from "lit";
 | 
				
			||||||
import { repeat } from "lit/directives/repeat.js";
 | 
					import { customElement, property, state } from "lit/decorators.js";
 | 
				
			||||||
 | 
					import { map } from "lit/directives/map.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
 | 
					import { UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
// commonplace and singular enough to merit its own handler.
 | 
					import type { SessionUser, UserSelf } from "@goauthentik/api";
 | 
				
			||||||
type SidebarEntry = [
 | 
					 | 
				
			||||||
    path: string | null,
 | 
					 | 
				
			||||||
    label: string,
 | 
					 | 
				
			||||||
    attributes?: Record<string, any> | string[] | null, // eslint-disable-line
 | 
					 | 
				
			||||||
    children?: SidebarEntry[],
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					@customElement("ak-admin-sidebar")
 | 
				
			||||||
 * Recursively renders a sidebar entry.
 | 
					export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement)) {
 | 
				
			||||||
 */
 | 
					    @property({ type: Boolean, reflect: true })
 | 
				
			||||||
export function renderSidebarItem([
 | 
					    open = true;
 | 
				
			||||||
    path,
 | 
					 | 
				
			||||||
    label,
 | 
					 | 
				
			||||||
    attributes,
 | 
					 | 
				
			||||||
    children,
 | 
					 | 
				
			||||||
]: SidebarEntry): TemplateResult {
 | 
					 | 
				
			||||||
    const properties = Array.isArray(attributes)
 | 
					 | 
				
			||||||
        ? { ".activeWhen": attributes }
 | 
					 | 
				
			||||||
        : (attributes ?? {});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (path) {
 | 
					    @state()
 | 
				
			||||||
        properties.path = path;
 | 
					    impersonation: UserSelf["username"] | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					        me().then((user: SessionUser) => {
 | 
				
			||||||
 | 
					            this.impersonation = user.original ? user.user.username : null;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        this.toggleOpen = this.toggleOpen.bind(this);
 | 
				
			||||||
 | 
					        this.checkWidth = this.checkWidth.bind(this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return html`<ak-sidebar-item ${spread(properties)}>
 | 
					    // This has to be a bound method so the event listener can be removed on disconnection as
 | 
				
			||||||
        ${label ? html`<span slot="label">${label}</span>` : nothing}
 | 
					    // needed.
 | 
				
			||||||
        ${children ? renderSidebarItems(children) : nothing}
 | 
					    toggleOpen() {
 | 
				
			||||||
    </ak-sidebar-item>`;
 | 
					        this.open = !this.open;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    checkWidth() {
 | 
				
			||||||
 | 
					        // This works just fine, but it assumes that the `--ak-sidebar--minimum-auto-width` is in
 | 
				
			||||||
 | 
					        // REMs. If that changes, this code will have to be adjusted as well.
 | 
				
			||||||
 | 
					        const minWidth =
 | 
				
			||||||
 | 
					            parseFloat(getRootStyle("--ak-sidebar--minimum-auto-width")) *
 | 
				
			||||||
 | 
					            parseFloat(getRootStyle("font-size"));
 | 
				
			||||||
 | 
					        this.open = window.innerWidth >= minWidth;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    connectedCallback() {
 | 
				
			||||||
 | 
					        super.connectedCallback();
 | 
				
			||||||
 | 
					        window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
 | 
				
			||||||
 | 
					        window.addEventListener("resize", this.checkWidth);
 | 
				
			||||||
 | 
					        // After connecting to the DOM, we can now perform this check to see if the sidebar should
 | 
				
			||||||
 | 
					        // be open by default.
 | 
				
			||||||
 | 
					        this.checkWidth();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The symmetry (☟, ☝) here is critical in that you want to start adding these handlers after
 | 
				
			||||||
 | 
					    // connection, and removing them before disconnection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disconnectedCallback() {
 | 
				
			||||||
 | 
					        window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
 | 
				
			||||||
 | 
					        window.removeEventListener("resize", this.checkWidth);
 | 
				
			||||||
 | 
					        super.disconnectedCallback();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render() {
 | 
				
			||||||
 | 
					        return html`
 | 
				
			||||||
 | 
					            <ak-sidebar
 | 
				
			||||||
 | 
					                class="pf-c-page__sidebar ${this.open ? "pf-m-expanded" : "pf-m-collapsed"} ${this
 | 
				
			||||||
 | 
					                    .activeTheme === UiThemeEnum.Light
 | 
				
			||||||
 | 
					                    ? "pf-m-light"
 | 
				
			||||||
 | 
					                    : ""}"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                ${this.renderSidebarItems()}
 | 
				
			||||||
 | 
					            </ak-sidebar>
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updated() {
 | 
				
			||||||
 | 
					        // This is permissible as`:host.classList` is not one of the properties Lit uses as a
 | 
				
			||||||
 | 
					        // scheduling trigger. This sort of shenanigans can trigger an loop, in that it will trigger
 | 
				
			||||||
 | 
					        // a browser reflow, which may trigger some other styling the application is monitoring,
 | 
				
			||||||
 | 
					        // triggering a re-render which triggers a browser reflow, ad infinitum. But we've been
 | 
				
			||||||
 | 
					        // living with that since jQuery, and it's both well-known and fortunately rare.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // eslint-disable-next-line wc/no-self-class
 | 
				
			||||||
 | 
					        this.classList.remove("pf-m-expanded", "pf-m-collapsed");
 | 
				
			||||||
 | 
					        // eslint-disable-next-line wc/no-self-class
 | 
				
			||||||
 | 
					        this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderSidebarItems(): TemplateResult {
 | 
				
			||||||
 | 
					        // The second attribute type is of string[] to help with the 'activeWhen' control, which was
 | 
				
			||||||
 | 
					        // commonplace and singular enough to merit its own handler.
 | 
				
			||||||
 | 
					        type SidebarEntry = [
 | 
				
			||||||
 | 
					            path: string | null,
 | 
				
			||||||
 | 
					            label: string,
 | 
				
			||||||
 | 
					            attributes?: Record<string, any> | string[] | null, // eslint-disable-line
 | 
				
			||||||
 | 
					            children?: SidebarEntry[],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // prettier-ignore
 | 
				
			||||||
 | 
					        const sidebarContent: SidebarEntry[] = [
 | 
				
			||||||
 | 
					            [null, msg("Dashboards"), { "?expanded": true }, [
 | 
				
			||||||
 | 
					                ["/administration/overview", msg("Overview")],
 | 
				
			||||||
 | 
					                ["/administration/dashboard/users", msg("User Statistics")],
 | 
				
			||||||
 | 
					                ["/administration/system-tasks", msg("System Tasks")]]],
 | 
				
			||||||
 | 
					            [null, msg("Applications"), null, [
 | 
				
			||||||
 | 
					                ["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/outpost/outposts", msg("Outposts")]]],
 | 
				
			||||||
 | 
					            [null, msg("Events"), null, [
 | 
				
			||||||
 | 
					                ["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/events/rules", msg("Notification Rules")],
 | 
				
			||||||
 | 
					                ["/events/transports", msg("Notification Transports")]]],
 | 
				
			||||||
 | 
					            [null, msg("Customization"), null, [
 | 
				
			||||||
 | 
					                ["/policy/policies", msg("Policies")],
 | 
				
			||||||
 | 
					                ["/core/property-mappings", msg("Property Mappings")],
 | 
				
			||||||
 | 
					                ["/blueprints/instances", msg("Blueprints")],
 | 
				
			||||||
 | 
					                ["/policy/reputation", msg("Reputation scores")]]],
 | 
				
			||||||
 | 
					            [null, msg("Flows and Stages"), null, [
 | 
				
			||||||
 | 
					                ["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/flow/stages", msg("Stages")],
 | 
				
			||||||
 | 
					                ["/flow/stages/prompts", msg("Prompts")]]],
 | 
				
			||||||
 | 
					            [null, msg("Directory"), null, [
 | 
				
			||||||
 | 
					                ["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
 | 
				
			||||||
 | 
					                ["/core/tokens", msg("Tokens and App passwords")],
 | 
				
			||||||
 | 
					                ["/flow/stages/invitations", msg("Invitations")]]],
 | 
				
			||||||
 | 
					            [null, msg("System"), null, [
 | 
				
			||||||
 | 
					                ["/core/brands", msg("Brands")],
 | 
				
			||||||
 | 
					                ["/crypto/certificates", msg("Certificates")],
 | 
				
			||||||
 | 
					                ["/outpost/integrations", msg("Outpost Integrations")],
 | 
				
			||||||
 | 
					                ["/admin/settings", msg("Settings")]]],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Typescript requires the type here to correctly type the recursive path
 | 
				
			||||||
 | 
					        type SidebarRenderer = (_: SidebarEntry) => TemplateResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => {
 | 
				
			||||||
 | 
					            const properties = Array.isArray(attributes)
 | 
				
			||||||
 | 
					                ? { ".activeWhen": attributes }
 | 
				
			||||||
 | 
					                : (attributes ?? {});
 | 
				
			||||||
 | 
					            if (path) {
 | 
				
			||||||
 | 
					                properties.path = path;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return html`<ak-sidebar-item ${spread(properties)}>
 | 
				
			||||||
 | 
					                ${label ? html`<span slot="label">${label}</span>` : nothing}
 | 
				
			||||||
 | 
					                ${map(children, renderOneSidebarItem)}
 | 
				
			||||||
 | 
					            </ak-sidebar-item>`;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // prettier-ignore
 | 
				
			||||||
 | 
					        return html`
 | 
				
			||||||
 | 
					            ${map(sidebarContent, renderOneSidebarItem)}
 | 
				
			||||||
 | 
					            ${this.renderEnterpriseMenu()}
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderEnterpriseMenu() {
 | 
				
			||||||
 | 
					        return this.can(CapabilitiesEnum.IsEnterprise)
 | 
				
			||||||
 | 
					            ? html`
 | 
				
			||||||
 | 
					                  <ak-sidebar-item>
 | 
				
			||||||
 | 
					                      <span slot="label">${msg("Enterprise")}</span>
 | 
				
			||||||
 | 
					                      <ak-sidebar-item path="/enterprise/licenses">
 | 
				
			||||||
 | 
					                          <span slot="label">${msg("Licenses")}</span>
 | 
				
			||||||
 | 
					                      </ak-sidebar-item>
 | 
				
			||||||
 | 
					                  </ak-sidebar-item>
 | 
				
			||||||
 | 
					              `
 | 
				
			||||||
 | 
					            : nothing;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					declare global {
 | 
				
			||||||
 * Recursively renders a collection of sidebar entries.
 | 
					    interface HTMLElementTagNameMap {
 | 
				
			||||||
 */
 | 
					        "ak-admin-sidebar": AkAdminSidebar;
 | 
				
			||||||
export function renderSidebarItems(entries: readonly SidebarEntry[]) {
 | 
					    }
 | 
				
			||||||
    return repeat(entries, ([path, label]) => path || label, renderSidebarItem);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// prettier-ignore
 | 
					 | 
				
			||||||
export const AdminSidebarEntries: readonly SidebarEntry[] = [
 | 
					 | 
				
			||||||
    [null, msg("Dashboards"), { "?expanded": true }, [
 | 
					 | 
				
			||||||
        ["/administration/overview", msg("Overview")],
 | 
					 | 
				
			||||||
        ["/administration/dashboard/users", msg("User Statistics")],
 | 
					 | 
				
			||||||
        ["/administration/system-tasks", msg("System Tasks")]]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    [null, msg("Applications"), null, [
 | 
					 | 
				
			||||||
        ["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/outpost/outposts", msg("Outposts")]]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    [null, msg("Events"), null, [
 | 
					 | 
				
			||||||
        ["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/events/rules", msg("Notification Rules")],
 | 
					 | 
				
			||||||
        ["/events/transports", msg("Notification Transports")]]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    [null, msg("Customization"), null, [
 | 
					 | 
				
			||||||
        ["/policy/policies", msg("Policies")],
 | 
					 | 
				
			||||||
        ["/core/property-mappings", msg("Property Mappings")],
 | 
					 | 
				
			||||||
        ["/blueprints/instances", msg("Blueprints")],
 | 
					 | 
				
			||||||
        ["/policy/reputation", msg("Reputation scores")]]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    [null, msg("Flows and Stages"), null, [
 | 
					 | 
				
			||||||
        ["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/flow/stages", msg("Stages")],
 | 
					 | 
				
			||||||
        ["/flow/stages/prompts", msg("Prompts")]]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    [null, msg("Directory"), null, [
 | 
					 | 
				
			||||||
        ["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
 | 
					 | 
				
			||||||
        ["/core/tokens", msg("Tokens and App passwords")],
 | 
					 | 
				
			||||||
        ["/flow/stages/invitations", msg("Invitations")]]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    [null, msg("System"), null, [
 | 
					 | 
				
			||||||
        ["/core/brands", msg("Brands")],
 | 
					 | 
				
			||||||
        ["/crypto/certificates", msg("Certificates")],
 | 
					 | 
				
			||||||
        ["/outpost/integrations", msg("Outpost Integrations")],
 | 
					 | 
				
			||||||
        ["/admin/settings", msg("Settings")]]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// prettier-ignore
 | 
					 | 
				
			||||||
export const AdminSidebarEnterpriseEntries: readonly SidebarEntry[] = [
 | 
					 | 
				
			||||||
    [null, msg("Enterprise"), null, [
 | 
					 | 
				
			||||||
        ["/enterprise/licenses", msg("Licenses"), null]
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
]]
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -94,13 +94,10 @@ export class AdminOverviewPage extends AdminOverviewBase {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render(): TemplateResult {
 | 
				
			||||||
        const username = this.user?.user.name || this.user?.user.username;
 | 
					        const name = this.user?.user.name ?? this.user?.user.username;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return html` <ak-page-header
 | 
					        return html`<ak-page-header description=${msg("General system status")} ?hasIcon=${false}>
 | 
				
			||||||
                header=${msg(str`Welcome, ${username || ""}.`)}
 | 
					                <span slot="header"> ${msg(str`Welcome, ${name || ""}.`)} </span>
 | 
				
			||||||
                description=${msg("General system status")}
 | 
					 | 
				
			||||||
                ?hasIcon=${false}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
            </ak-page-header>
 | 
					            </ak-page-header>
 | 
				
			||||||
            <section class="pf-c-page__main-section">
 | 
					            <section class="pf-c-page__main-section">
 | 
				
			||||||
                <div class="pf-l-grid pf-m-gutter">
 | 
					                <div class="pf-l-grid pf-m-gutter">
 | 
				
			||||||
 | 
				
			|||||||
@ -83,10 +83,13 @@ export class AdminSettingsPage extends AKElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render() {
 | 
					    render() {
 | 
				
			||||||
        if (!this.settings) return nothing;
 | 
					        if (!this.settings) {
 | 
				
			||||||
 | 
					            return nothing;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return html`
 | 
					        return html`
 | 
				
			||||||
            <ak-page-header icon="fa fa-cog" header="${msg("System settings")}"> </ak-page-header>
 | 
					            <ak-page-header icon="fa fa-cog" header="" description="">
 | 
				
			||||||
 | 
					                <span slot="header"> ${msg("System settings")} </span>
 | 
				
			||||||
 | 
					            </ak-page-header>
 | 
				
			||||||
            <section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
 | 
					            <section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
 | 
				
			||||||
                <div class="pf-c-card">
 | 
					                <div class="pf-c-card">
 | 
				
			||||||
                    <div class="pf-c-card__body">
 | 
					                    <div class="pf-c-card__body">
 | 
				
			||||||
 | 
				
			|||||||
@ -1,110 +1,26 @@
 | 
				
			|||||||
import type { Config as DOMPurifyConfig } from "dompurify";
 | 
					import type { Config as DOMPurifyConfig } from "dompurify";
 | 
				
			||||||
import DOMPurify from "dompurify";
 | 
					import DOMPurify from "dompurify";
 | 
				
			||||||
import { trustedTypes } from "trusted-types";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { render } from "lit";
 | 
					import { render } from "@lit-labs/ssr";
 | 
				
			||||||
 | 
					import { collectResult } from "@lit-labs/ssr/lib/render-result.js";
 | 
				
			||||||
 | 
					import { TemplateResult, html } from "lit";
 | 
				
			||||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
 | 
					import { unsafeHTML } from "lit/directives/unsafe-html.js";
 | 
				
			||||||
 | 
					import { until } from "lit/directives/until.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Trusted types policy that escapes HTML content in place.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @see {@linkcode SanitizedTrustPolicy} to strip HTML content.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @returns {TrustedHTML} All HTML content, escaped.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const EscapeTrustPolicy = trustedTypes.createPolicy("authentik-escape", {
 | 
					 | 
				
			||||||
    createHTML: (untrustedHTML: string) => {
 | 
					 | 
				
			||||||
        return DOMPurify.sanitize(untrustedHTML, {
 | 
					 | 
				
			||||||
            RETURN_TRUSTED_TYPE: false,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Trusted types policy, stripping all HTML content.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @returns {TrustedHTML} Text content only, all HTML tags stripped.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const SanitizedTrustPolicy = trustedTypes.createPolicy("authentik-sanitize", {
 | 
					 | 
				
			||||||
    createHTML: (untrustedHTML: string) => {
 | 
					 | 
				
			||||||
        return DOMPurify.sanitize(untrustedHTML, {
 | 
					 | 
				
			||||||
            RETURN_TRUSTED_TYPE: false,
 | 
					 | 
				
			||||||
            ALLOWED_TAGS: ["#text"],
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Trusted types policy, allowing a minimal set of _safe_ HTML tags supplied by
 | 
					 | 
				
			||||||
 * a trusted source, such as the brand API.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const BrandedHTMLPolicy = trustedTypes.createPolicy("authentik-restrict", {
 | 
					 | 
				
			||||||
    createHTML: (untrustedHTML: string) => {
 | 
					 | 
				
			||||||
        return DOMPurify.sanitize(untrustedHTML, {
 | 
					 | 
				
			||||||
            RETURN_TRUSTED_TYPE: false,
 | 
					 | 
				
			||||||
            FORBID_TAGS: [
 | 
					 | 
				
			||||||
                "script",
 | 
					 | 
				
			||||||
                "style",
 | 
					 | 
				
			||||||
                "iframe",
 | 
					 | 
				
			||||||
                "link",
 | 
					 | 
				
			||||||
                "object",
 | 
					 | 
				
			||||||
                "embed",
 | 
					 | 
				
			||||||
                "applet",
 | 
					 | 
				
			||||||
                "meta",
 | 
					 | 
				
			||||||
                "base",
 | 
					 | 
				
			||||||
                "form",
 | 
					 | 
				
			||||||
                "input",
 | 
					 | 
				
			||||||
                "textarea",
 | 
					 | 
				
			||||||
                "select",
 | 
					 | 
				
			||||||
                "button",
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            FORBID_ATTR: [
 | 
					 | 
				
			||||||
                "onerror",
 | 
					 | 
				
			||||||
                "onclick",
 | 
					 | 
				
			||||||
                "onload",
 | 
					 | 
				
			||||||
                "onmouseover",
 | 
					 | 
				
			||||||
                "onmouseout",
 | 
					 | 
				
			||||||
                "onmouseup",
 | 
					 | 
				
			||||||
                "onmousedown",
 | 
					 | 
				
			||||||
                "onfocus",
 | 
					 | 
				
			||||||
                "onblur",
 | 
					 | 
				
			||||||
                "onsubmit",
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type AuthentikTrustPolicy =
 | 
					 | 
				
			||||||
    | typeof EscapeTrustPolicy
 | 
					 | 
				
			||||||
    | typeof SanitizedTrustPolicy
 | 
					 | 
				
			||||||
    | typeof BrandedHTMLPolicy;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Sanitize an untrusted HTML string using a trusted types policy.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function sanitizeHTML(trustPolicy: AuthentikTrustPolicy, untrustedHTML: string) {
 | 
					 | 
				
			||||||
    return unsafeHTML(trustPolicy.createHTML(untrustedHTML).toString());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * DOMPurify configuration for strict sanitization.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This configuration only allows text nodes and disallows all HTML tags.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const DOM_PURIFY_STRICT = {
 | 
					export const DOM_PURIFY_STRICT = {
 | 
				
			||||||
    ALLOWED_TAGS: ["#text"],
 | 
					    ALLOWED_TAGS: ["#text"],
 | 
				
			||||||
} as const satisfies DOMPurifyConfig;
 | 
					} as const satisfies DOMPurifyConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					export async function renderStatic(input: TemplateResult): Promise<string> {
 | 
				
			||||||
 * Render untrusted HTML to a string without escaping it.
 | 
					    return await collectResult(render(input));
 | 
				
			||||||
 *
 | 
					}
 | 
				
			||||||
 * @returns {string} The rendered HTML string.
 | 
					
 | 
				
			||||||
 */
 | 
					export function purify(input: TemplateResult): TemplateResult {
 | 
				
			||||||
export function renderStaticHTMLUnsafe(untrustedHTML: unknown): string {
 | 
					    return html`${until(
 | 
				
			||||||
    const container = document.createElement("html");
 | 
					        (async () => {
 | 
				
			||||||
    render(untrustedHTML, container);
 | 
					            const rendered = await renderStatic(input);
 | 
				
			||||||
 | 
					            const purified = DOMPurify.sanitize(rendered);
 | 
				
			||||||
    const result = container.innerHTML;
 | 
					            return html`${unsafeHTML(purified)}`;
 | 
				
			||||||
 | 
					        })(),
 | 
				
			||||||
    return result;
 | 
					    )}`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,13 +17,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* Minimum width after which the sidebar becomes automatic */
 | 
					    /* Minimum width after which the sidebar becomes automatic */
 | 
				
			||||||
    --ak-sidebar--minimum-auto-width: 80rem;
 | 
					    --ak-sidebar--minimum-auto-width: 80rem;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The height of the navbar and branded sidebar.
 | 
					 | 
				
			||||||
     * @todo This shouldn't be necessary. The sidebar can instead use a grid layout
 | 
					 | 
				
			||||||
     * ensuring they share the same height.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    --ak-navbar--height: 7rem;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@supports selector(::-webkit-scrollbar) {
 | 
					@supports selector(::-webkit-scrollbar) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,264 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 * @file Stylesheet utilities.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
import { CSSResult, CSSResultOrNative, ReactiveElement, css } from "lit";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Elements containing adoptable stylesheets.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type StyleSheetParent = Pick<DocumentOrShadowRoot, "adoptedStyleSheets">;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type-predicate to determine if a given object has adoptable stylesheets.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function isAdoptableStyleSheetParent(input: unknown): input is StyleSheetParent {
 | 
					 | 
				
			||||||
    // Sanity check - Does the input have the right shape?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!input || typeof input !== "object") return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!("adoptedStyleSheets" in input) || !input.adoptedStyleSheets) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (typeof input.adoptedStyleSheets !== "object") return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // We avoid `Array.isArray` because the adopted stylesheets property
 | 
					 | 
				
			||||||
    // is defined as a proxied array.
 | 
					 | 
				
			||||||
    // All we care about is that it's shaped like an array.
 | 
					 | 
				
			||||||
    if (!("length" in input.adoptedStyleSheets)) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (typeof input.adoptedStyleSheets.length !== "number") return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Finally is the array mutable?
 | 
					 | 
				
			||||||
    return "push" in input.adoptedStyleSheets;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Assert that the given input can adopt stylesheets.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function assertAdoptableStyleSheetParent<T>(
 | 
					 | 
				
			||||||
    input: T,
 | 
					 | 
				
			||||||
): asserts input is T & StyleSheetParent {
 | 
					 | 
				
			||||||
    if (isAdoptableStyleSheetParent(input)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.debug("Given input missing `adoptedStyleSheets`", input);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    throw new TypeError("Assertion failed: `adoptedStyleSheets` missing in given input");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function resolveStyleSheetParent<T extends HTMLElement | DocumentFragment | Document>(
 | 
					 | 
				
			||||||
    renderRoot: T,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    const styleRoot = "ShadyDOM" in window ? document : renderRoot;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assertAdoptableStyleSheetParent(styleRoot);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return styleRoot;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type StyleSheetInit = string | CSSResult | CSSStyleSheet;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Given a source of CSS, create a `CSSStyleSheet`.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @throw {@linkcode TypeError} if the input cannot be converted to a `CSSStyleSheet`
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @remarks
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Storybook's `build` does not currently have a coherent way of importing
 | 
					 | 
				
			||||||
 * CSS-as-text into CSSStyleSheet.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * It works well when Storybook is running in `dev`, but in `build` it fails.
 | 
					 | 
				
			||||||
 * Storied components will have to map their textual CSS imports.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function createStyleSheet(input: string): CSSResult {
 | 
					 | 
				
			||||||
    const inputTemplate = [input] as unknown as TemplateStringsArray;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const result = css(inputTemplate, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Given a source of CSS, create a `CSSStyleSheet`.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @see {@linkcode createStyleSheet}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function normalizeCSSSource(css: string): CSSStyleSheet;
 | 
					 | 
				
			||||||
export function normalizeCSSSource(styleSheet: CSSStyleSheet): CSSStyleSheet;
 | 
					 | 
				
			||||||
export function normalizeCSSSource(cssResult: CSSResult): CSSResult;
 | 
					 | 
				
			||||||
export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative;
 | 
					 | 
				
			||||||
export function normalizeCSSSource(input: StyleSheetInit): CSSResultOrNative {
 | 
					 | 
				
			||||||
    if (typeof input === "string") return createStyleSheet(input);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return input;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create a `CSSStyleSheet` from the given input.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function createStyleSheetUnsafe(input: StyleSheetInit): CSSStyleSheet {
 | 
					 | 
				
			||||||
    const result = normalizeCSSSource(input);
 | 
					 | 
				
			||||||
    if (result instanceof CSSStyleSheet) return result;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!result.styleSheet) {
 | 
					 | 
				
			||||||
        console.debug(
 | 
					 | 
				
			||||||
            "authentik/common/stylesheets: CSSResult missing styleSheet, returning empty",
 | 
					 | 
				
			||||||
            { result, input },
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        throw new TypeError("Expected a CSSStyleSheet");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return result.styleSheet;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A symbol to indicate that a stylesheet has been adopted by a style parent.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @remarks
 | 
					 | 
				
			||||||
 * Safari considers stylesheet removed from the `adoptedStyleSheets` array
 | 
					 | 
				
			||||||
 * ready for garbage collection. Reuse of the stylesheet will result in tab-crash.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Always discard the stylesheet after use.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const StyleSheetAdoptedParent = Symbol("stylesheet-adopted");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A CSS style sheet that has been adopted by a style parent.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface AdoptedStyleSheet extends CSSStyleSheet {
 | 
					 | 
				
			||||||
    [StyleSheetAdoptedParent]: WeakRef<StyleSheetParent>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Type-predicate to determine if a given stylesheet has been adopted.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function isAdoptedStyleSheet(styleSheet: CSSStyleSheet): styleSheet is AdoptedStyleSheet {
 | 
					 | 
				
			||||||
    if (!(StyleSheetAdoptedParent in styleSheet)) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return styleSheet[StyleSheetAdoptedParent] instanceof WeakRef;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Append stylesheet(s) to the given roots.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @see {@linkcode removeStyleSheet} to remove a stylesheet from a given roots.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function appendStyleSheet(
 | 
					 | 
				
			||||||
    styleParent: StyleSheetParent,
 | 
					 | 
				
			||||||
    ...insertions: CSSStyleSheet[]
 | 
					 | 
				
			||||||
): void {
 | 
					 | 
				
			||||||
    insertions = Array.isArray(insertions) ? insertions : [insertions];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const styleSheetInsertion of insertions) {
 | 
					 | 
				
			||||||
        if (isAdoptedStyleSheet(styleSheetInsertion)) {
 | 
					 | 
				
			||||||
            console.warn("Attempted to append adopted stylesheet", {
 | 
					 | 
				
			||||||
                styleSheetInsertion,
 | 
					 | 
				
			||||||
                currentParent: styleSheetInsertion[StyleSheetAdoptedParent]?.deref(),
 | 
					 | 
				
			||||||
                rules: serializeStyleSheet(styleSheetInsertion),
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            throw new TypeError("Attempted to append a previously adopted stylesheet");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (styleParent.adoptedStyleSheets.includes(styleSheetInsertion)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        styleParent.adoptedStyleSheets = [...styleParent.adoptedStyleSheets, styleSheetInsertion];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Object.assign(styleSheetInsertion, {
 | 
					 | 
				
			||||||
            [StyleSheetAdoptedParent]: new WeakRef(styleParent),
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Remove a stylesheet from the given roots, matching by referential equality.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @see {@linkcode appendStyleSheet} to append a stylesheet to a given roots.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function removeStyleSheet(
 | 
					 | 
				
			||||||
    styleParent: StyleSheetParent,
 | 
					 | 
				
			||||||
    ...removals: CSSStyleSheet[]
 | 
					 | 
				
			||||||
): void {
 | 
					 | 
				
			||||||
    const nextAdoptedStyleSheets = styleParent.adoptedStyleSheets.filter(
 | 
					 | 
				
			||||||
        (styleSheet) => !removals.includes(styleSheet),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (nextAdoptedStyleSheets.length === styleParent.adoptedStyleSheets.length) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    styleParent.adoptedStyleSheets = nextAdoptedStyleSheets;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Serialize a stylesheet to a string.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This is useful for debugging or inspecting the contents of a stylesheet.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function serializeStyleSheet(stylesheet: CSSStyleSheet): string {
 | 
					 | 
				
			||||||
    return Array.from(stylesheet.cssRules || [], (rule) => rule.cssText || "").join("\n");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Inspect the adopted stylesheets of a given style parent, serializing them to strings.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function inspectStyleSheets(styleParent: StyleSheetParent): string[] {
 | 
					 | 
				
			||||||
    return styleParent.adoptedStyleSheets.map((styleSheet) => serializeStyleSheet(styleSheet));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface InspectedStyleSheetEntry {
 | 
					 | 
				
			||||||
    tagName: string;
 | 
					 | 
				
			||||||
    element: ReactiveElement;
 | 
					 | 
				
			||||||
    styles: string[];
 | 
					 | 
				
			||||||
    children?: InspectedStyleSheetEntry[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Recursively inspect the adopted stylesheets of a given style parent, serializing them to strings.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleSheetEntry {
 | 
					 | 
				
			||||||
    const styleParent = resolveStyleSheetParent(element.renderRoot);
 | 
					 | 
				
			||||||
    const styles = inspectStyleSheets(styleParent);
 | 
					 | 
				
			||||||
    const tagName = element.tagName.toLowerCase();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const treewalker = document.createTreeWalker(element.renderRoot, NodeFilter.SHOW_ELEMENT, {
 | 
					 | 
				
			||||||
        acceptNode(node) {
 | 
					 | 
				
			||||||
            if (node instanceof ReactiveElement) {
 | 
					 | 
				
			||||||
                return NodeFilter.FILTER_ACCEPT;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return NodeFilter.FILTER_SKIP;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const children: InspectedStyleSheetEntry[] = [];
 | 
					 | 
				
			||||||
    let currentNode: Node | null = treewalker.nextNode();
 | 
					 | 
				
			||||||
    while (currentNode) {
 | 
					 | 
				
			||||||
        const childElement = currentNode as ReactiveElement;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!isAdoptableStyleSheetParent(childElement.renderRoot)) {
 | 
					 | 
				
			||||||
            currentNode = treewalker.nextNode();
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const childStyles = inspectStyleSheets(childElement.renderRoot);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        children.push({
 | 
					 | 
				
			||||||
            tagName: childElement.tagName.toLowerCase(),
 | 
					 | 
				
			||||||
            element: childElement,
 | 
					 | 
				
			||||||
            styles: childStyles,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        currentNode = treewalker.nextNode();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        tagName,
 | 
					 | 
				
			||||||
        element,
 | 
					 | 
				
			||||||
        styles,
 | 
					 | 
				
			||||||
        children,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (process.env.NODE_ENV === "development") {
 | 
					 | 
				
			||||||
    Object.assign(window, {
 | 
					 | 
				
			||||||
        inspectStyleSheetTree,
 | 
					 | 
				
			||||||
        serializeStyleSheet,
 | 
					 | 
				
			||||||
        inspectStyleSheets,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,200 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 * @file Theme utilities.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
import { UIConfig } from "@goauthentik/common/ui/config";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#region Scheme Types
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Valid CSS color scheme values.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @link {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme | MDN}
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @category CSS
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type CSSColorSchemeValue = "dark" | "light" | "auto";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A CSS color scheme value that can be preferred by the user, i.e. not `"auto"`.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @category CSS
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type ResolvedCSSColorSchemeValue = Exclude<CSSColorSchemeValue, "auto">;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#region UI Theme Types
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A UI color scheme value that can be preferred by the user.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * i.e. not an lack of preference or unknown value.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @category CSS
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type ResolvedUITheme = typeof UiThemeEnum.Light | typeof UiThemeEnum.Dark;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A mapping of theme values to their respective inversion.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @category CSS
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const UIThemeInversion = {
 | 
					 | 
				
			||||||
    dark: "light",
 | 
					 | 
				
			||||||
    light: "dark",
 | 
					 | 
				
			||||||
} as const satisfies Record<ResolvedUITheme, ResolvedUITheme>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Either a valid CSS color scheme value, or a theme preference.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type UIThemeHint = CSSColorSchemeValue | UiThemeEnum;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#region Scheme Functions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Creates an event target for the given color scheme.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param colorScheme The color scheme to target.
 | 
					 | 
				
			||||||
 * @returns A {@linkcode MediaQueryList} that can be used to listen for changes to the color scheme.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList | MDN}
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @category CSS
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function createColorSchemeTarget(colorScheme: ResolvedCSSColorSchemeValue): MediaQueryList {
 | 
					 | 
				
			||||||
    return window.matchMedia(`(prefers-color-scheme: ${colorScheme})`);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Formats the given input into a valid CSS color scheme value.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * If the input is not provided, it defaults to "auto".
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @category CSS
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function formatColorScheme(theme: ResolvedUITheme): ResolvedCSSColorSchemeValue;
 | 
					 | 
				
			||||||
export function formatColorScheme(
 | 
					 | 
				
			||||||
    colorScheme: ResolvedCSSColorSchemeValue,
 | 
					 | 
				
			||||||
): ResolvedCSSColorSchemeValue;
 | 
					 | 
				
			||||||
export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue;
 | 
					 | 
				
			||||||
export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue {
 | 
					 | 
				
			||||||
    if (!hint) return "auto";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    switch (hint) {
 | 
					 | 
				
			||||||
        case "dark":
 | 
					 | 
				
			||||||
        case UiThemeEnum.Dark:
 | 
					 | 
				
			||||||
            return "dark";
 | 
					 | 
				
			||||||
        case "light":
 | 
					 | 
				
			||||||
        case UiThemeEnum.Light:
 | 
					 | 
				
			||||||
            return "light";
 | 
					 | 
				
			||||||
        case "auto":
 | 
					 | 
				
			||||||
        case UiThemeEnum.Automatic:
 | 
					 | 
				
			||||||
            return "auto";
 | 
					 | 
				
			||||||
        default:
 | 
					 | 
				
			||||||
            console.warn(`Unknown color scheme hint: ${hint}. Defaulting to "auto".`);
 | 
					 | 
				
			||||||
            return "auto";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#region Theme Functions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Resolve the current UI theme based on the user's preference or the provided color scheme.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param hint The color scheme hint to use.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @category CSS
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function resolveUITheme(
 | 
					 | 
				
			||||||
    hint?: UIThemeHint,
 | 
					 | 
				
			||||||
    defaultUITheme: ResolvedUITheme = UiThemeEnum.Light,
 | 
					 | 
				
			||||||
): ResolvedUITheme {
 | 
					 | 
				
			||||||
    const colorScheme = formatColorScheme(hint);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (colorScheme !== "auto") return colorScheme;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Given that we don't know the user's preference,
 | 
					 | 
				
			||||||
    // we can determine the theme based on whether the default theme is
 | 
					 | 
				
			||||||
    // currently being overridden.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const colorSchemeInversion = formatColorScheme(UIThemeInversion[defaultUITheme]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const mediaQueryList = createColorSchemeTarget(colorSchemeInversion);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return mediaQueryList.matches ? colorSchemeInversion : defaultUITheme;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Effect listener invoked when the color scheme changes.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type UIThemeListener = (currentUITheme: ResolvedUITheme) => void;
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create an effect that runs
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @returns A cleanup function that removes the effect.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function createUIThemeEffect(
 | 
					 | 
				
			||||||
    effect: UIThemeListener,
 | 
					 | 
				
			||||||
    listenerOptions?: AddEventListenerOptions,
 | 
					 | 
				
			||||||
): () => void {
 | 
					 | 
				
			||||||
    const colorSchemeTarget = resolveUITheme();
 | 
					 | 
				
			||||||
    const invertedColorSchemeTarget = UIThemeInversion[colorSchemeTarget];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let previousUITheme: ResolvedUITheme | undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // First, wrap the effect to ensure we can abort it.
 | 
					 | 
				
			||||||
    const changeListener = (event: MediaQueryListEvent) => {
 | 
					 | 
				
			||||||
        if (listenerOptions?.signal?.aborted) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const currentUITheme = event.matches ? colorSchemeTarget : invertedColorSchemeTarget;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (previousUITheme === currentUITheme) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        previousUITheme = currentUITheme;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        effect(currentUITheme);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const mediaQueryList = createColorSchemeTarget(colorSchemeTarget);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Trigger the effect immediately.
 | 
					 | 
				
			||||||
    effect(colorSchemeTarget);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Listen for changes to the color scheme...
 | 
					 | 
				
			||||||
    mediaQueryList.addEventListener("change", changeListener, listenerOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Finally, allow the caller to remove the effect.
 | 
					 | 
				
			||||||
    const cleanup = () => {
 | 
					 | 
				
			||||||
        mediaQueryList.removeEventListener("change", changeListener);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return cleanup;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#region Theme Element
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * An element that can be themed.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export interface ThemedElement extends HTMLElement {
 | 
					 | 
				
			||||||
    brand?: CurrentBrand;
 | 
					 | 
				
			||||||
    uiConfig?: UIConfig;
 | 
					 | 
				
			||||||
    config?: Config;
 | 
					 | 
				
			||||||
    activeTheme: ResolvedUITheme;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function rootInterface<T extends ThemedElement = ThemedElement>(): T | null {
 | 
					 | 
				
			||||||
    const element = document.body.querySelector<T>("[data-ak-interface-root]");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return element;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#endregion
 | 
					 | 
				
			||||||
@ -95,7 +95,7 @@ export class NavigationButtons extends AKElement {
 | 
				
			|||||||
            );
 | 
					            );
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-xl">
 | 
					        return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg">
 | 
				
			||||||
            <button class="pf-c-button pf-m-plain" type="button" @click=${onClick}>
 | 
					            <button class="pf-c-button pf-m-plain" type="button" @click=${onClick}>
 | 
				
			||||||
                <pf-tooltip position="top" content=${msg("Open API drawer")}>
 | 
					                <pf-tooltip position="top" content=${msg("Open API drawer")}>
 | 
				
			||||||
                    <i class="fas fa-code" aria-hidden="true"></i>
 | 
					                    <i class="fas fa-code" aria-hidden="true"></i>
 | 
				
			||||||
@ -116,7 +116,7 @@ export class NavigationButtons extends AKElement {
 | 
				
			|||||||
            );
 | 
					            );
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-xl">
 | 
					        return html`<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg">
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
                class="pf-c-button pf-m-plain"
 | 
					                class="pf-c-button pf-m-plain"
 | 
				
			||||||
                type="button"
 | 
					                type="button"
 | 
				
			||||||
@ -156,7 +156,9 @@ export class NavigationButtons extends AKElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderImpersonation() {
 | 
					    renderImpersonation() {
 | 
				
			||||||
        if (!this.me?.original) return nothing;
 | 
					        if (!this.me?.original) {
 | 
				
			||||||
 | 
					            return nothing;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const onClick = async () => {
 | 
					        const onClick = async () => {
 | 
				
			||||||
            await new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve();
 | 
					            await new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve();
 | 
				
			||||||
@ -173,14 +175,6 @@ export class NavigationButtons extends AKElement {
 | 
				
			|||||||
            </div>`;
 | 
					            </div>`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderAvatar() {
 | 
					 | 
				
			||||||
        return html`<img
 | 
					 | 
				
			||||||
            class="pf-c-page__header-tools-item pf-c-avatar pf-m-hidden pf-m-visible-on-xl"
 | 
					 | 
				
			||||||
            src=${ifDefined(this.me?.user.avatar)}
 | 
					 | 
				
			||||||
            alt="${msg("Avatar image")}"
 | 
					 | 
				
			||||||
        />`;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    get userDisplayName() {
 | 
					    get userDisplayName() {
 | 
				
			||||||
        return match<UserDisplay | undefined, string | undefined>(this.uiConfig?.navbar.userDisplay)
 | 
					        return match<UserDisplay | undefined, string | undefined>(this.uiConfig?.navbar.userDisplay)
 | 
				
			||||||
            .with(UserDisplay.username, () => this.me?.user.username)
 | 
					            .with(UserDisplay.username, () => this.me?.user.username)
 | 
				
			||||||
@ -212,13 +206,17 @@ export class NavigationButtons extends AKElement {
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            ${this.renderImpersonation()}
 | 
					            ${this.renderImpersonation()}
 | 
				
			||||||
            ${this.userDisplayName != ""
 | 
					            ${this.userDisplayName != ""
 | 
				
			||||||
                ? html`<div class="pf-c-page__header-tools-group pf-m-hidden">
 | 
					                ? html`<div class="pf-c-page__header-tools-group">
 | 
				
			||||||
                      <div class="pf-c-page__header-tools-item pf-m-visible-on-2xl">
 | 
					                      <div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md">
 | 
				
			||||||
                          ${this.userDisplayName}
 | 
					                          ${this.userDisplayName}
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                  </div>`
 | 
					                  </div>`
 | 
				
			||||||
                : nothing}
 | 
					                : nothing}
 | 
				
			||||||
            ${this.renderAvatar()}
 | 
					            <img
 | 
				
			||||||
 | 
					                class="pf-c-avatar"
 | 
				
			||||||
 | 
					                src=${ifDefined(this.me?.user.avatar)}
 | 
				
			||||||
 | 
					                alt="${msg("Avatar image")}"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
        </div>`;
 | 
					        </div>`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,140 +1,165 @@
 | 
				
			|||||||
 | 
					import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
 | 
				
			||||||
import { globalAK } from "@goauthentik/common/global";
 | 
					import { globalAK } from "@goauthentik/common/global";
 | 
				
			||||||
import {
 | 
					import { UIConfig } from "@goauthentik/common/ui/config";
 | 
				
			||||||
    StyleSheetInit,
 | 
					import { adaptCSS } from "@goauthentik/common/utils";
 | 
				
			||||||
    StyleSheetParent,
 | 
					import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
 | 
				
			||||||
    appendStyleSheet,
 | 
					 | 
				
			||||||
    createStyleSheetUnsafe,
 | 
					 | 
				
			||||||
    removeStyleSheet,
 | 
					 | 
				
			||||||
    resolveStyleSheetParent,
 | 
					 | 
				
			||||||
} from "@goauthentik/common/stylesheets";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
    CSSColorSchemeValue,
 | 
					 | 
				
			||||||
    ResolvedUITheme,
 | 
					 | 
				
			||||||
    UIThemeListener,
 | 
					 | 
				
			||||||
    createUIThemeEffect,
 | 
					 | 
				
			||||||
    formatColorScheme,
 | 
					 | 
				
			||||||
    resolveUITheme,
 | 
					 | 
				
			||||||
} from "@goauthentik/common/theme";
 | 
					 | 
				
			||||||
import { type ThemedElement } from "@goauthentik/common/theme";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { localized } from "@lit/localize";
 | 
					import { localized } from "@lit/localize";
 | 
				
			||||||
import { CSSResultGroup, CSSResultOrNative, LitElement } from "lit";
 | 
					import { LitElement, ReactiveElement } from "lit";
 | 
				
			||||||
import { property } from "lit/decorators.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
 | 
					import AKGlobal from "@goauthentik/common/styles/authentik.css";
 | 
				
			||||||
import OneDark from "@goauthentik/common/styles/one-dark.css";
 | 
					import OneDark from "@goauthentik/common/styles/one-dark.css";
 | 
				
			||||||
import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
 | 
					import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { UiThemeEnum } from "@goauthentik/api";
 | 
					import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Re-export the theme helpers
 | 
					type AkInterface = HTMLElement & {
 | 
				
			||||||
export { rootInterface } from "@goauthentik/common/theme";
 | 
					    getTheme: () => Promise<UiThemeEnum>;
 | 
				
			||||||
 | 
					    brand?: CurrentBrand;
 | 
				
			||||||
 | 
					    uiConfig?: UIConfig;
 | 
				
			||||||
 | 
					    config?: Config;
 | 
				
			||||||
 | 
					    get activeTheme(): UiThemeEnum | undefined;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const rootInterface = <T extends AkInterface>(): T | undefined =>
 | 
				
			||||||
 | 
					    (document.body.querySelector("[data-ak-interface-root]") as T) ?? undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Ensure themes are converted to a static instance of CSS Stylesheet, otherwise the
 | 
				
			||||||
 | 
					// when changing themes we might not remove the correct css stylesheet instance.
 | 
				
			||||||
 | 
					const _darkTheme = ensureCSSStyleSheet(ThemeDark);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@localized()
 | 
					@localized()
 | 
				
			||||||
export class AKElement extends LitElement implements ThemedElement {
 | 
					export class AKElement extends LitElement {
 | 
				
			||||||
    //#region Properties
 | 
					    _mediaMatcher?: MediaQueryList;
 | 
				
			||||||
 | 
					    _mediaMatcherHandler?: (ev?: MediaQueryListEvent) => void;
 | 
				
			||||||
 | 
					    _activeTheme?: UiThemeEnum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    get activeTheme(): UiThemeEnum | undefined {
 | 
				
			||||||
     * The resolved theme of the current element.
 | 
					        return this._activeTheme;
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @remarks
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * Unlike the browser's current color scheme, this is a value that can be
 | 
					 | 
				
			||||||
     * resolved to a specific theme, i.e. dark or light.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @property({
 | 
					 | 
				
			||||||
        attribute: "theme",
 | 
					 | 
				
			||||||
        type: String,
 | 
					 | 
				
			||||||
        reflect: true,
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    public activeTheme: ResolvedUITheme;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#region Private Properties
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    readonly #preferredColorScheme: CSSColorSchemeValue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #customCSSStyleSheet: CSSStyleSheet | null;
 | 
					 | 
				
			||||||
    #darkThemeStyleSheet: CSSStyleSheet | null = null;
 | 
					 | 
				
			||||||
    #themeAbortController: AbortController | null = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#region Lifecycle
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected static finalizeStyles(styles?: CSSResultGroup): CSSResultOrNative[] {
 | 
					 | 
				
			||||||
        // Ensure all style sheets being passed are really style sheets.
 | 
					 | 
				
			||||||
        const baseStyles: StyleSheetInit[] = [AKGlobal, OneDark];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!styles) return baseStyles.map(createStyleSheetUnsafe);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (Array.isArray(styles)) {
 | 
					 | 
				
			||||||
            return [
 | 
					 | 
				
			||||||
                //---
 | 
					 | 
				
			||||||
                ...(styles as unknown as CSSResultOrNative[]),
 | 
					 | 
				
			||||||
                ...baseStyles,
 | 
					 | 
				
			||||||
            ].flatMap(createStyleSheetUnsafe);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return [styles, ...baseStyles].map(createStyleSheetUnsafe);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        const { brand } = globalAK();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.#preferredColorScheme = formatColorScheme(brand.uiTheme);
 | 
					 | 
				
			||||||
        this.activeTheme = resolveUITheme(brand?.uiTheme);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.#customCSSStyleSheet = brand?.brandingCustomCss
 | 
					 | 
				
			||||||
            ? createStyleSheetUnsafe(brand.brandingCustomCss)
 | 
					 | 
				
			||||||
            : null;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public disconnectedCallback(): void {
 | 
					    setInitialStyles(root: DocumentOrShadowRoot) {
 | 
				
			||||||
        super.disconnectedCallback();
 | 
					        const styleRoot: DocumentOrShadowRoot = (
 | 
				
			||||||
        this.#themeAbortController?.abort();
 | 
					            "ShadyDOM" in window ? document : root
 | 
				
			||||||
 | 
					        ) as DocumentOrShadowRoot;
 | 
				
			||||||
 | 
					        styleRoot.adoptedStyleSheets = adaptCSS([
 | 
				
			||||||
 | 
					            ...styleRoot.adoptedStyleSheets,
 | 
				
			||||||
 | 
					            ensureCSSStyleSheet(AKGlobal),
 | 
				
			||||||
 | 
					            ensureCSSStyleSheet(OneDark),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        this._initTheme(styleRoot);
 | 
				
			||||||
 | 
					        this._initCustomCSS(styleRoot);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #styleRoot?: StyleSheetParent;
 | 
					    protected createRenderRoot() {
 | 
				
			||||||
 | 
					        this.fixElementStyles();
 | 
				
			||||||
    #dispatchTheme: UIThemeListener = (nextUITheme) => {
 | 
					        const root = super.createRenderRoot();
 | 
				
			||||||
        if (!this.#styleRoot) return;
 | 
					        this.setInitialStyles(root as unknown as DocumentOrShadowRoot);
 | 
				
			||||||
 | 
					        return root;
 | 
				
			||||||
        if (nextUITheme === UiThemeEnum.Dark) {
 | 
					 | 
				
			||||||
            this.#darkThemeStyleSheet ||= createStyleSheetUnsafe(ThemeDark);
 | 
					 | 
				
			||||||
            appendStyleSheet(this.#styleRoot, this.#darkThemeStyleSheet);
 | 
					 | 
				
			||||||
            this.activeTheme = UiThemeEnum.Dark;
 | 
					 | 
				
			||||||
        } else if (this.#darkThemeStyleSheet) {
 | 
					 | 
				
			||||||
            removeStyleSheet(this.#styleRoot, this.#darkThemeStyleSheet);
 | 
					 | 
				
			||||||
            this.#darkThemeStyleSheet = null;
 | 
					 | 
				
			||||||
            this.activeTheme = UiThemeEnum.Light;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected createRenderRoot(): HTMLElement | DocumentFragment {
 | 
					 | 
				
			||||||
        const renderRoot = super.createRenderRoot();
 | 
					 | 
				
			||||||
        this.#styleRoot = resolveStyleSheetParent(renderRoot);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.#customCSSStyleSheet) {
 | 
					 | 
				
			||||||
            console.debug(`authentik/element[${this.tagName.toLowerCase()}]: Adding custom CSS`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            appendStyleSheet(this.#styleRoot, this.#customCSSStyleSheet);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.#themeAbortController = new AbortController();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.#preferredColorScheme === "dark") {
 | 
					 | 
				
			||||||
            this.#dispatchTheme(UiThemeEnum.Dark);
 | 
					 | 
				
			||||||
        } else if (this.#preferredColorScheme === "auto") {
 | 
					 | 
				
			||||||
            createUIThemeEffect(this.#dispatchTheme, {
 | 
					 | 
				
			||||||
                signal: this.#themeAbortController.signal,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return renderRoot;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //#endregion
 | 
					    async getTheme(): Promise<UiThemeEnum> {
 | 
				
			||||||
 | 
					        return rootInterface()?.getTheme() || UiThemeEnum.Automatic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fixElementStyles() {
 | 
				
			||||||
 | 
					        // Ensure all style sheets being passed are really style sheets.
 | 
				
			||||||
 | 
					        (this.constructor as typeof ReactiveElement).elementStyles = (
 | 
				
			||||||
 | 
					            this.constructor as typeof ReactiveElement
 | 
				
			||||||
 | 
					        ).elementStyles.map(ensureCSSStyleSheet);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async _initTheme(root: DocumentOrShadowRoot): Promise<void> {
 | 
				
			||||||
 | 
					        // Early activate theme based on media query to prevent light flash
 | 
				
			||||||
 | 
					        // when dark is preferred
 | 
				
			||||||
 | 
					        this._applyTheme(root, globalAK().brand.uiTheme);
 | 
				
			||||||
 | 
					        this._applyTheme(root, await this.getTheme());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async _initCustomCSS(root: DocumentOrShadowRoot): Promise<void> {
 | 
				
			||||||
 | 
					        const brand = globalAK().brand;
 | 
				
			||||||
 | 
					        if (!brand) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const sheet = await new CSSStyleSheet().replace(brand.brandingCustomCss);
 | 
				
			||||||
 | 
					        root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _applyTheme(root: DocumentOrShadowRoot, theme?: UiThemeEnum): void {
 | 
				
			||||||
 | 
					        if (!theme) {
 | 
				
			||||||
 | 
					            theme = UiThemeEnum.Automatic;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (theme === UiThemeEnum.Automatic) {
 | 
				
			||||||
 | 
					            // Create a media matcher to automatically switch the theme depending on
 | 
				
			||||||
 | 
					            // prefers-color-scheme
 | 
				
			||||||
 | 
					            if (!this._mediaMatcher) {
 | 
				
			||||||
 | 
					                this._mediaMatcher = window.matchMedia(QUERY_MEDIA_COLOR_LIGHT);
 | 
				
			||||||
 | 
					                this._mediaMatcherHandler = (ev?: MediaQueryListEvent) => {
 | 
				
			||||||
 | 
					                    const theme =
 | 
				
			||||||
 | 
					                        ev?.matches || this._mediaMatcher?.matches
 | 
				
			||||||
 | 
					                            ? UiThemeEnum.Light
 | 
				
			||||||
 | 
					                            : UiThemeEnum.Dark;
 | 
				
			||||||
 | 
					                    this._activateTheme(theme, root);
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                this._mediaMatcherHandler(undefined);
 | 
				
			||||||
 | 
					                this._mediaMatcher.addEventListener("change", this._mediaMatcherHandler);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (this._mediaMatcher && this._mediaMatcherHandler) {
 | 
				
			||||||
 | 
					            // Theme isn't automatic and we have a matcher configured, remove the matcher
 | 
				
			||||||
 | 
					            // to prevent changes
 | 
				
			||||||
 | 
					            this._mediaMatcher.removeEventListener("change", this._mediaMatcherHandler);
 | 
				
			||||||
 | 
					            this._mediaMatcher = undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this._activateTheme(theme, root);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static themeToStylesheet(theme?: UiThemeEnum): CSSStyleSheet | undefined {
 | 
				
			||||||
 | 
					        if (theme === UiThemeEnum.Dark) {
 | 
				
			||||||
 | 
					            return _darkTheme;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Directly activate a given theme, accepts multiple document/ShadowDOMs to apply the stylesheet
 | 
				
			||||||
 | 
					     * to. The stylesheets are applied to each DOM in order. Does nothing if the given theme is already active.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    _activateTheme(theme: UiThemeEnum, ...roots: DocumentOrShadowRoot[]) {
 | 
				
			||||||
 | 
					        if (theme === this._activeTheme) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Make sure we only get to this callback once we've picked a concise theme choice
 | 
				
			||||||
 | 
					        this.dispatchEvent(
 | 
				
			||||||
 | 
					            new CustomEvent(EVENT_THEME_CHANGE, {
 | 
				
			||||||
 | 
					                bubbles: true,
 | 
				
			||||||
 | 
					                composed: true,
 | 
				
			||||||
 | 
					                detail: theme,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        this.setAttribute("theme", theme);
 | 
				
			||||||
 | 
					        const stylesheet = AKElement.themeToStylesheet(theme);
 | 
				
			||||||
 | 
					        const oldStylesheet = AKElement.themeToStylesheet(this._activeTheme);
 | 
				
			||||||
 | 
					        roots.forEach((root) => {
 | 
				
			||||||
 | 
					            if (stylesheet) {
 | 
				
			||||||
 | 
					                root.adoptedStyleSheets = [
 | 
				
			||||||
 | 
					                    ...root.adoptedStyleSheets,
 | 
				
			||||||
 | 
					                    ensureCSSStyleSheet(stylesheet),
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (oldStylesheet) {
 | 
				
			||||||
 | 
					                root.adoptedStyleSheets = root.adoptedStyleSheets.filter(
 | 
				
			||||||
 | 
					                    (v) => v !== oldStylesheet,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        this._activeTheme = theme;
 | 
				
			||||||
 | 
					        this.requestUpdate();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
					import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
				
			||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
 | 
					import { EVENT_REFRESH } from "@goauthentik/common/constants";
 | 
				
			||||||
import { ThemedElement } from "@goauthentik/common/theme";
 | 
					 | 
				
			||||||
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
 | 
					import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
 | 
				
			||||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
 | 
					import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -10,12 +9,14 @@ import type { ReactiveController } from "lit";
 | 
				
			|||||||
import type { CurrentBrand } from "@goauthentik/api";
 | 
					import type { CurrentBrand } from "@goauthentik/api";
 | 
				
			||||||
import { CoreApi } from "@goauthentik/api";
 | 
					import { CoreApi } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { AkInterface } from "./Interface";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class BrandContextController implements ReactiveController {
 | 
					export class BrandContextController implements ReactiveController {
 | 
				
			||||||
    host!: ReactiveElementHost<ThemedElement>;
 | 
					    host!: ReactiveElementHost<AkInterface>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;
 | 
					    context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(host: ReactiveElementHost<ThemedElement>) {
 | 
					    constructor(host: ReactiveElementHost<AkInterface>) {
 | 
				
			||||||
        this.host = host;
 | 
					        this.host = host;
 | 
				
			||||||
        this.context = new ContextProvider(this.host, {
 | 
					        this.context = new ContextProvider(this.host, {
 | 
				
			||||||
            context: authentikBrandContext,
 | 
					            context: authentikBrandContext,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
					import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
				
			||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
 | 
					import { EVENT_REFRESH } from "@goauthentik/common/constants";
 | 
				
			||||||
import { globalAK } from "@goauthentik/common/global";
 | 
					import { globalAK } from "@goauthentik/common/global";
 | 
				
			||||||
import { ThemedElement } from "@goauthentik/common/theme";
 | 
					 | 
				
			||||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
 | 
					import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
 | 
				
			||||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
 | 
					import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -11,12 +10,14 @@ import type { ReactiveController } from "lit";
 | 
				
			|||||||
import type { Config } from "@goauthentik/api";
 | 
					import type { Config } from "@goauthentik/api";
 | 
				
			||||||
import { RootApi } from "@goauthentik/api";
 | 
					import { RootApi } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { AkInterface } from "./Interface";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ConfigContextController implements ReactiveController {
 | 
					export class ConfigContextController implements ReactiveController {
 | 
				
			||||||
    host!: ReactiveElementHost<ThemedElement>;
 | 
					    host!: ReactiveElementHost<AkInterface>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context!: ContextProvider<{ __context__: Config | undefined }>;
 | 
					    context!: ContextProvider<{ __context__: Config | undefined }>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(host: ReactiveElementHost<ThemedElement>) {
 | 
					    constructor(host: ReactiveElementHost<AkInterface>) {
 | 
				
			||||||
        this.host = host;
 | 
					        this.host = host;
 | 
				
			||||||
        this.context = new ContextProvider(this.host, {
 | 
					        this.context = new ContextProvider(this.host, {
 | 
				
			||||||
            context: authentikConfigContext,
 | 
					            context: authentikConfigContext,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,78 +1,107 @@
 | 
				
			|||||||
import {
 | 
					import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
 | 
				
			||||||
    appendStyleSheet,
 | 
					 | 
				
			||||||
    createStyleSheetUnsafe,
 | 
					 | 
				
			||||||
    resolveStyleSheetParent,
 | 
					 | 
				
			||||||
} from "@goauthentik/common/stylesheets";
 | 
					 | 
				
			||||||
import { ThemedElement } from "@goauthentik/common/theme";
 | 
					 | 
				
			||||||
import { UIConfig } from "@goauthentik/common/ui/config";
 | 
					 | 
				
			||||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
					 | 
				
			||||||
import { VersionContextController } from "@goauthentik/elements/Interface/VersionContextController";
 | 
					import { VersionContextController } from "@goauthentik/elements/Interface/VersionContextController";
 | 
				
			||||||
import { ModalOrchestrationController } from "@goauthentik/elements/controllers/ModalOrchestrationController.js";
 | 
					import { ModalOrchestrationController } from "@goauthentik/elements/controllers/ModalOrchestrationController.js";
 | 
				
			||||||
 | 
					import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { state } from "lit/decorators.js";
 | 
					import { state } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
					import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { Config, CurrentBrand, LicenseSummary, Version } from "@goauthentik/api";
 | 
					import type { Config, CurrentBrand, LicenseSummary, Version } from "@goauthentik/api";
 | 
				
			||||||
 | 
					import { UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AKElement, rootInterface } from "../Base";
 | 
				
			||||||
import { BrandContextController } from "./BrandContextController";
 | 
					import { BrandContextController } from "./BrandContextController";
 | 
				
			||||||
import { ConfigContextController } from "./ConfigContextController";
 | 
					import { ConfigContextController } from "./ConfigContextController";
 | 
				
			||||||
import { EnterpriseContextController } from "./EnterpriseContextController";
 | 
					import { EnterpriseContextController } from "./EnterpriseContextController";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AkInterface = HTMLElement & {
 | 
				
			||||||
 | 
					    getTheme: () => Promise<UiThemeEnum>;
 | 
				
			||||||
 | 
					    brand?: CurrentBrand;
 | 
				
			||||||
 | 
					    uiConfig?: UIConfig;
 | 
				
			||||||
 | 
					    config?: Config;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const brandContext = Symbol("brandContext");
 | 
				
			||||||
const configContext = Symbol("configContext");
 | 
					const configContext = Symbol("configContext");
 | 
				
			||||||
const modalController = Symbol("modalController");
 | 
					const modalController = Symbol("modalController");
 | 
				
			||||||
const versionContext = Symbol("versionContext");
 | 
					const versionContext = Symbol("versionContext");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class Interface extends AKElement implements ThemedElement {
 | 
					export class Interface extends AKElement implements AkInterface {
 | 
				
			||||||
    protected static readonly PFBaseStyleSheet = createStyleSheetUnsafe(PFBase);
 | 
					    [brandContext]!: BrandContextController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [configContext]: ConfigContextController;
 | 
					    [configContext]!: ConfigContextController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [modalController]: ModalOrchestrationController;
 | 
					    [modalController]!: ModalOrchestrationController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @state()
 | 
					    @state()
 | 
				
			||||||
    public config?: Config;
 | 
					    uiConfig?: UIConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @state()
 | 
					    @state()
 | 
				
			||||||
    public brand?: CurrentBrand;
 | 
					    config?: Config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @state()
 | 
				
			||||||
 | 
					    brand?: CurrentBrand;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
        const styleParent = resolveStyleSheetParent(document);
 | 
					        document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
 | 
				
			||||||
 | 
					        this._initContexts();
 | 
				
			||||||
 | 
					        this.dataset.akInterfaceRoot = "true";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
 | 
					    _initContexts() {
 | 
				
			||||||
 | 
					        this[brandContext] = new BrandContextController(this);
 | 
				
			||||||
        appendStyleSheet(styleParent, Interface.PFBaseStyleSheet);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.addController(new BrandContextController(this));
 | 
					 | 
				
			||||||
        this[configContext] = new ConfigContextController(this);
 | 
					        this[configContext] = new ConfigContextController(this);
 | 
				
			||||||
        this[modalController] = new ModalOrchestrationController(this);
 | 
					        this[modalController] = new ModalOrchestrationController(this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _activateTheme(theme: UiThemeEnum, ...roots: DocumentOrShadowRoot[]): void {
 | 
				
			||||||
 | 
					        if (theme === this._activeTheme) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        console.debug(
 | 
				
			||||||
 | 
					            `authentik/interface[${rootInterface()?.tagName.toLowerCase()}]: Enabling theme ${theme}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        // Special case for root interfaces, as they need to modify the global document CSS too
 | 
				
			||||||
 | 
					        // Instead of calling ._activateTheme() twice, we insert the root document in the call
 | 
				
			||||||
 | 
					        // since multiple calls to ._activateTheme() would not do anything after the first call
 | 
				
			||||||
 | 
					        // as the theme is already enabled.
 | 
				
			||||||
 | 
					        roots.unshift(document as unknown as DocumentOrShadowRoot);
 | 
				
			||||||
 | 
					        super._activateTheme(theme, ...roots);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getTheme(): Promise<UiThemeEnum> {
 | 
				
			||||||
 | 
					        if (!this.uiConfig) {
 | 
				
			||||||
 | 
					            this.uiConfig = await uiConfig();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return this.uiConfig.theme?.base || UiThemeEnum.Automatic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AkAuthenticatedInterface extends ThemedElement {
 | 
					export type AkAuthenticatedInterface = AkInterface & {
 | 
				
			||||||
    licenseSummary?: LicenseSummary;
 | 
					    licenseSummary?: LicenseSummary;
 | 
				
			||||||
    version?: Version;
 | 
					    version?: Version;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const enterpriseContext = Symbol("enterpriseContext");
 | 
					const enterpriseContext = Symbol("enterpriseContext");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AuthenticatedInterface extends Interface implements AkAuthenticatedInterface {
 | 
					export class AuthenticatedInterface extends Interface {
 | 
				
			||||||
    [enterpriseContext]!: EnterpriseContextController;
 | 
					    [enterpriseContext]!: EnterpriseContextController;
 | 
				
			||||||
    [versionContext]!: VersionContextController;
 | 
					    [versionContext]!: VersionContextController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @state()
 | 
					    @state()
 | 
				
			||||||
    public uiConfig?: UIConfig;
 | 
					    licenseSummary?: LicenseSummary;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @state()
 | 
					    @state()
 | 
				
			||||||
    public licenseSummary?: LicenseSummary;
 | 
					    version?: Version;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @state()
 | 
					 | 
				
			||||||
    public version?: Version;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _initContexts(): void {
 | 
				
			||||||
 | 
					        super._initContexts();
 | 
				
			||||||
        this[enterpriseContext] = new EnterpriseContextController(this);
 | 
					        this[enterpriseContext] = new EnterpriseContextController(this);
 | 
				
			||||||
        this[versionContext] = new VersionContextController(this);
 | 
					        this[versionContext] = new VersionContextController(this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,23 +5,20 @@ import {
 | 
				
			|||||||
} from "@goauthentik/common/constants";
 | 
					} from "@goauthentik/common/constants";
 | 
				
			||||||
import { globalAK } from "@goauthentik/common/global";
 | 
					import { globalAK } from "@goauthentik/common/global";
 | 
				
			||||||
import { currentInterface } from "@goauthentik/common/sentry";
 | 
					import { currentInterface } from "@goauthentik/common/sentry";
 | 
				
			||||||
import { UIConfig, UserDisplay, getConfigForUser } from "@goauthentik/common/ui/config";
 | 
					import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
 | 
				
			||||||
import { me } from "@goauthentik/common/users";
 | 
					import { me } from "@goauthentik/common/users";
 | 
				
			||||||
import "@goauthentik/components/ak-nav-buttons";
 | 
					import "@goauthentik/components/ak-nav-buttons";
 | 
				
			||||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
					import { AKElement } from "@goauthentik/elements/Base";
 | 
				
			||||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
 | 
					import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
 | 
				
			||||||
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
 | 
					 | 
				
			||||||
import { themeImage } from "@goauthentik/elements/utils/images";
 | 
					 | 
				
			||||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
 | 
					import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, LitElement, TemplateResult, css, html, nothing } from "lit";
 | 
					import { CSSResult, TemplateResult, css, html, nothing } from "lit";
 | 
				
			||||||
import { customElement, property, state } from "lit/decorators.js";
 | 
					import { customElement, property, state } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
 | 
					import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
 | 
				
			||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
					import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
				
			||||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
 | 
					import PFContent from "@patternfly/patternfly/components/Content/content.css";
 | 
				
			||||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
 | 
					 | 
				
			||||||
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
 | 
					import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
 | 
				
			||||||
import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
 | 
					import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
 | 
				
			||||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
 | 
					import PFPage from "@patternfly/patternfly/components/Page/page.css";
 | 
				
			||||||
@ -29,52 +26,34 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { SessionUser } from "@goauthentik/api";
 | 
					import { SessionUser } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//#region Page Navbar
 | 
					@customElement("ak-page-header")
 | 
				
			||||||
 | 
					export class PageHeader extends WithBrandConfig(AKElement) {
 | 
				
			||||||
export interface PageNavbarDetails {
 | 
					    @property()
 | 
				
			||||||
    header?: string;
 | 
					 | 
				
			||||||
    description?: string;
 | 
					 | 
				
			||||||
    icon?: string;
 | 
					    icon?: string;
 | 
				
			||||||
    iconImage?: boolean;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					    @property({ type: Boolean })
 | 
				
			||||||
 * A global navbar component at the top of the page.
 | 
					    iconImage = false;
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Internally, this component listens for the `ak-page-header` event, which is
 | 
					 | 
				
			||||||
 * dispatched by the `ak-page-header` component.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
@customElement("ak-page-navbar")
 | 
					 | 
				
			||||||
export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavbarDetails {
 | 
					 | 
				
			||||||
    //#region Static Properties
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static elementRef: AKPageNavbar | null = null;
 | 
					    @property()
 | 
				
			||||||
 | 
					    header = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static readonly setNavbarDetails = (detail: Partial<PageNavbarDetails>): void => {
 | 
					    @property()
 | 
				
			||||||
        const { elementRef } = AKPageNavbar;
 | 
					    description?: string;
 | 
				
			||||||
        if (!elementRef) {
 | 
					 | 
				
			||||||
            console.debug(
 | 
					 | 
				
			||||||
                `ak-page-header: Could not find ak-page-navbar, skipping event dispatch.`,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { header, description, icon, iconImage } = detail;
 | 
					    @property({ type: Boolean })
 | 
				
			||||||
 | 
					    hasIcon = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        elementRef.header = header;
 | 
					    @state()
 | 
				
			||||||
        elementRef.description = description;
 | 
					    me?: SessionUser;
 | 
				
			||||||
        elementRef.icon = icon;
 | 
					
 | 
				
			||||||
        elementRef.iconImage = iconImage || false;
 | 
					    @state()
 | 
				
			||||||
        elementRef.hasIcon = !!icon;
 | 
					    uiConfig!: UIConfig;
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            PFBase,
 | 
					            PFBase,
 | 
				
			||||||
            PFButton,
 | 
					            PFButton,
 | 
				
			||||||
            PFPage,
 | 
					            PFPage,
 | 
				
			||||||
            PFDrawer,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            PFNotificationBadge,
 | 
					            PFNotificationBadge,
 | 
				
			||||||
            PFContent,
 | 
					            PFContent,
 | 
				
			||||||
            PFAvatar,
 | 
					            PFAvatar,
 | 
				
			||||||
@ -84,404 +63,143 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
 | 
				
			|||||||
                    position: sticky;
 | 
					                    position: sticky;
 | 
				
			||||||
                    top: 0;
 | 
					                    top: 0;
 | 
				
			||||||
                    z-index: var(--pf-global--ZIndex--lg);
 | 
					                    z-index: var(--pf-global--ZIndex--lg);
 | 
				
			||||||
                    --pf-c-page__header-tools--MarginRight: 0;
 | 
					 | 
				
			||||||
                    --ak-brand-logo-height: var(--pf-global--FontSize--4xl, 2.25rem);
 | 
					 | 
				
			||||||
                    --ak-brand-background-color: var(
 | 
					 | 
				
			||||||
                        --pf-c-page__sidebar--m-light--BackgroundColor
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                    --host-navbar-height: var(--ak-c-page-header--height, 7.5rem);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                .bar {
 | 
				
			||||||
                :host([theme="dark"]) {
 | 
					 | 
				
			||||||
                    --ak-brand-background-color: var(--pf-c-page__sidebar--BackgroundColor);
 | 
					 | 
				
			||||||
                    --pf-c-page__sidebar--BackgroundColor: var(--ak-dark-background-light);
 | 
					 | 
				
			||||||
                    color: var(--ak-dark-foreground);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                navbar {
 | 
					 | 
				
			||||||
                    border-bottom: var(--pf-global--BorderWidth--sm);
 | 
					                    border-bottom: var(--pf-global--BorderWidth--sm);
 | 
				
			||||||
                    border-bottom-style: solid;
 | 
					                    border-bottom-style: solid;
 | 
				
			||||||
                    border-bottom-color: var(--pf-global--BorderColor--100);
 | 
					                    border-bottom-color: var(--pf-global--BorderColor--100);
 | 
				
			||||||
                    background-color: var(--pf-c-page--BackgroundColor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    display: flex;
 | 
					                    display: flex;
 | 
				
			||||||
                    flex-direction: row;
 | 
					                    flex-direction: row;
 | 
				
			||||||
 | 
					                    min-height: 114px;
 | 
				
			||||||
                    display: grid;
 | 
					                    max-height: 114px;
 | 
				
			||||||
                    row-gap: var(--pf-global--spacer--sm);
 | 
					                    background-color: var(--pf-c-page--BackgroundColor);
 | 
				
			||||||
                    column-gap: var(--pf-global--spacer--sm);
 | 
					 | 
				
			||||||
                    grid-template-columns: [brand] auto [toggle] auto [primary] 1fr [secondary] auto;
 | 
					 | 
				
			||||||
                    grid-template-rows: auto auto;
 | 
					 | 
				
			||||||
                    grid-template-areas:
 | 
					 | 
				
			||||||
                        "brand toggle primary secondary"
 | 
					 | 
				
			||||||
                        "brand toggle description secondary";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    @media (min-width: 426px) {
 | 
					 | 
				
			||||||
                        height: var(--host-navbar-height);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    @media (max-width: 768px) {
 | 
					 | 
				
			||||||
                        row-gap: var(--pf-global--spacer--xs);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        align-items: center;
 | 
					 | 
				
			||||||
                        grid-template-areas:
 | 
					 | 
				
			||||||
                            "toggle primary secondary"
 | 
					 | 
				
			||||||
                            "toggle description description";
 | 
					 | 
				
			||||||
                        justify-content: space-between;
 | 
					 | 
				
			||||||
                        width: 100%;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                .pf-c-page__main-section.pf-m-light {
 | 
				
			||||||
                .items {
 | 
					                    background-color: transparent;
 | 
				
			||||||
                    display: block;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    &.primary {
 | 
					 | 
				
			||||||
                        grid-column: primary;
 | 
					 | 
				
			||||||
                        grid-row: primary / description;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        align-content: center;
 | 
					 | 
				
			||||||
                        padding-block: var(--pf-global--spacer--md);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        @media (min-width: 426px) {
 | 
					 | 
				
			||||||
                            &.block-sibling {
 | 
					 | 
				
			||||||
                                padding-block-end: 0;
 | 
					 | 
				
			||||||
                                grid-row: primary;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        @media (max-width: 768px) {
 | 
					 | 
				
			||||||
                            padding-block: var(--pf-global--spacer--sm);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        .accent-icon {
 | 
					 | 
				
			||||||
                            height: 1em;
 | 
					 | 
				
			||||||
                            width: 1em;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            @media (max-width: 768px) {
 | 
					 | 
				
			||||||
                                display: none;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    &.page-description {
 | 
					 | 
				
			||||||
                        grid-area: description;
 | 
					 | 
				
			||||||
                        margin-block-end: var(--pf-global--spacer--md);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        display: box;
 | 
					 | 
				
			||||||
                        display: -webkit-box;
 | 
					 | 
				
			||||||
                        line-clamp: 2;
 | 
					 | 
				
			||||||
                        -webkit-line-clamp: 2;
 | 
					 | 
				
			||||||
                        box-orient: vertical;
 | 
					 | 
				
			||||||
                        -webkit-box-orient: vertical;
 | 
					 | 
				
			||||||
                        overflow: hidden;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        @media (max-width: 425px) {
 | 
					 | 
				
			||||||
                            display: none;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        @media (min-width: 769px) {
 | 
					 | 
				
			||||||
                            text-wrap: balance;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    &.secondary {
 | 
					 | 
				
			||||||
                        grid-area: secondary;
 | 
					 | 
				
			||||||
                        flex: 0 0 auto;
 | 
					 | 
				
			||||||
                        justify-self: end;
 | 
					 | 
				
			||||||
                        padding-block: var(--pf-global--spacer--sm);
 | 
					 | 
				
			||||||
                        padding-inline-end: var(--pf-global--spacer--sm);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        @media (min-width: 769px) {
 | 
					 | 
				
			||||||
                            align-content: center;
 | 
					 | 
				
			||||||
                            padding-block: var(--pf-global--spacer--md);
 | 
					 | 
				
			||||||
                            padding-inline-end: var(--pf-global--spacer--xl);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                .pf-c-page__main-section {
 | 
				
			||||||
                .brand {
 | 
					                    flex-grow: 1;
 | 
				
			||||||
                    grid-area: brand;
 | 
					                    flex-shrink: 1;
 | 
				
			||||||
                    background-color: var(--ak-brand-background-color);
 | 
					 | 
				
			||||||
                    height: 100%;
 | 
					 | 
				
			||||||
                    width: var(--pf-c-page__sidebar--Width);
 | 
					 | 
				
			||||||
                    align-items: center;
 | 
					 | 
				
			||||||
                    padding-inline: var(--pf-global--spacer--sm);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    display: flex;
 | 
					                    display: flex;
 | 
				
			||||||
 | 
					                    flex-direction: column;
 | 
				
			||||||
                    justify-content: center;
 | 
					                    justify-content: center;
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    &.pf-m-collapsed {
 | 
					 | 
				
			||||||
                        display: none;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    @media (max-width: 1199px) {
 | 
					 | 
				
			||||||
                        display: none;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                img.pf-icon {
 | 
				
			||||||
                .sidebar-trigger {
 | 
					                    max-height: 24px;
 | 
				
			||||||
                    grid-area: toggle;
 | 
					 | 
				
			||||||
                    height: 100%;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                .logo {
 | 
					 | 
				
			||||||
                    flex: 0 0 auto;
 | 
					 | 
				
			||||||
                    height: var(--ak-brand-logo-height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    & img {
 | 
					 | 
				
			||||||
                        height: 100%;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                .sidebar-trigger,
 | 
					                .sidebar-trigger,
 | 
				
			||||||
                .notification-trigger {
 | 
					                .notification-trigger {
 | 
				
			||||||
                    font-size: 1.5rem;
 | 
					                    font-size: 24px;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                .notification-trigger.has-notifications {
 | 
					                .notification-trigger.has-notifications {
 | 
				
			||||||
                    color: var(--pf-global--active-color--100);
 | 
					                    color: var(--pf-global--active-color--100);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                .page-title {
 | 
					 | 
				
			||||||
                    display: flex;
 | 
					 | 
				
			||||||
                    gap: var(--pf-global--spacer--xs);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                h1 {
 | 
					                h1 {
 | 
				
			||||||
                    display: flex;
 | 
					                    display: flex;
 | 
				
			||||||
                    flex-direction: row;
 | 
					                    flex-direction: row;
 | 
				
			||||||
                    align-items: center !important;
 | 
					                    align-items: center !important;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            `,
 | 
					                .pf-c-page__header-tools {
 | 
				
			||||||
        ];
 | 
					                    flex-shrink: 0;
 | 
				
			||||||
    }
 | 
					                }
 | 
				
			||||||
 | 
					                .pf-c-page__header-tools-group {
 | 
				
			||||||
    //#endregion
 | 
					                    height: 100%;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
    //#region Properties
 | 
					                :host([theme="dark"]) .pf-c-page__header-tools {
 | 
				
			||||||
 | 
					                    color: var(--ak-dark-foreground) !important;
 | 
				
			||||||
    @property({ type: String })
 | 
					 | 
				
			||||||
    icon?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: Boolean })
 | 
					 | 
				
			||||||
    iconImage = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: String })
 | 
					 | 
				
			||||||
    header?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: String })
 | 
					 | 
				
			||||||
    description?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: Boolean })
 | 
					 | 
				
			||||||
    hasIcon = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: Boolean })
 | 
					 | 
				
			||||||
    open = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @state()
 | 
					 | 
				
			||||||
    session?: SessionUser;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @state()
 | 
					 | 
				
			||||||
    uiConfig!: UIConfig;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#region Private Methods
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #setTitle(header?: string) {
 | 
					 | 
				
			||||||
        const currentIf = currentInterface();
 | 
					 | 
				
			||||||
        let title = this.brand?.brandingTitle || TITLE_DEFAULT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (currentIf === "admin") {
 | 
					 | 
				
			||||||
            title = `${msg("Admin")} - ${title}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // Prepend the header to the title
 | 
					 | 
				
			||||||
        if (header) {
 | 
					 | 
				
			||||||
            title = `${header} - ${title}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        document.title = title;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #toggleSidebar() {
 | 
					 | 
				
			||||||
        this.open = !this.open;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.dispatchEvent(
 | 
					 | 
				
			||||||
            new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
 | 
					 | 
				
			||||||
                bubbles: true,
 | 
					 | 
				
			||||||
                composed: true,
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#region Lifecycle
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public connectedCallback(): void {
 | 
					 | 
				
			||||||
        super.connectedCallback();
 | 
					 | 
				
			||||||
        AKPageNavbar.elementRef = this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.addEventListener(EVENT_WS_MESSAGE, () => {
 | 
					 | 
				
			||||||
            this.firstUpdated();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public disconnectedCallback(): void {
 | 
					 | 
				
			||||||
        super.disconnectedCallback();
 | 
					 | 
				
			||||||
        AKPageNavbar.elementRef = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async firstUpdated() {
 | 
					 | 
				
			||||||
        this.session = await me();
 | 
					 | 
				
			||||||
        this.uiConfig = getConfigForUser(this.session.user);
 | 
					 | 
				
			||||||
        this.uiConfig.navbar.userDisplay = UserDisplay.none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    willUpdate() {
 | 
					 | 
				
			||||||
        // Always update title, even if there's no header value set,
 | 
					 | 
				
			||||||
        // as in that case we still need to return to the generic title
 | 
					 | 
				
			||||||
        this.#setTitle(this.header);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#region Render
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    renderIcon() {
 | 
					 | 
				
			||||||
        if (this.icon) {
 | 
					 | 
				
			||||||
            if (this.iconImage && !this.icon.startsWith("fa://")) {
 | 
					 | 
				
			||||||
                return html`<img class="accent-icon pf-icon" src="${this.icon}" alt="page icon" />`;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const icon = this.icon.replaceAll("fa://", "fa ");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return html`<i class="accent-icon ${icon}"></i>`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return nothing;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    render(): TemplateResult {
 | 
					 | 
				
			||||||
        return html`<navbar aria-label="Main" class="navbar">
 | 
					 | 
				
			||||||
                <aside class="brand ${this.open ? "" : "pf-m-collapsed"}">
 | 
					 | 
				
			||||||
                    <a href="#/">
 | 
					 | 
				
			||||||
                        <div class="logo">
 | 
					 | 
				
			||||||
                            <img
 | 
					 | 
				
			||||||
                                src=${themeImage(
 | 
					 | 
				
			||||||
                                    this.brand?.brandingLogo ?? DefaultBrand.brandingLogo,
 | 
					 | 
				
			||||||
                                )}
 | 
					 | 
				
			||||||
                                alt="${msg("authentik Logo")}"
 | 
					 | 
				
			||||||
                                loading="lazy"
 | 
					 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </a>
 | 
					 | 
				
			||||||
                </aside>
 | 
					 | 
				
			||||||
                <button
 | 
					 | 
				
			||||||
                    class="sidebar-trigger pf-c-button pf-m-plain"
 | 
					 | 
				
			||||||
                    @click=${this.#toggleSidebar}
 | 
					 | 
				
			||||||
                    aria-label=${msg("Toggle sidebar")}
 | 
					 | 
				
			||||||
                    aria-expanded=${this.open ? "true" : "false"}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                    <i class="fas fa-bars"></i>
 | 
					 | 
				
			||||||
                </button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <section
 | 
					 | 
				
			||||||
                    class="items primary pf-c-content ${this.description ? "block-sibling" : ""}"
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                    <h1 class="page-title">
 | 
					 | 
				
			||||||
                        ${this.hasIcon
 | 
					 | 
				
			||||||
                            ? html`<slot name="icon">${this.renderIcon()}</slot>`
 | 
					 | 
				
			||||||
                            : nothing}
 | 
					 | 
				
			||||||
                        ${this.header}
 | 
					 | 
				
			||||||
                    </h1>
 | 
					 | 
				
			||||||
                </section>
 | 
					 | 
				
			||||||
                ${this.description
 | 
					 | 
				
			||||||
                    ? html`<section class="items page-description pf-c-content">
 | 
					 | 
				
			||||||
                          <p>${this.description}</p>
 | 
					 | 
				
			||||||
                      </section>`
 | 
					 | 
				
			||||||
                    : nothing}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <section class="items secondary">
 | 
					 | 
				
			||||||
                    <div class="pf-c-page__header-tools-group">
 | 
					 | 
				
			||||||
                        <ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.session}>
 | 
					 | 
				
			||||||
                            <a
 | 
					 | 
				
			||||||
                                class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
 | 
					 | 
				
			||||||
                                href="${globalAK().api.base}if/user/"
 | 
					 | 
				
			||||||
                                slot="extra"
 | 
					 | 
				
			||||||
                            >
 | 
					 | 
				
			||||||
                                ${msg("User interface")}
 | 
					 | 
				
			||||||
                            </a>
 | 
					 | 
				
			||||||
                        </ak-nav-buttons>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </section>
 | 
					 | 
				
			||||||
            </navbar>
 | 
					 | 
				
			||||||
            <slot></slot>`;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //#endregion
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//#region Page Header
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * A page header component, used to display the page title and description.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Internally, this component dispatches the `ak-page-header` event, which is
 | 
					 | 
				
			||||||
 * listened to by the `ak-page-navbar` component.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @singleton
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
@customElement("ak-page-header")
 | 
					 | 
				
			||||||
export class AKPageHeader extends LitElement implements PageNavbarDetails {
 | 
					 | 
				
			||||||
    @property({ type: String })
 | 
					 | 
				
			||||||
    header?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: String })
 | 
					 | 
				
			||||||
    description?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: String })
 | 
					 | 
				
			||||||
    icon?: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: Boolean })
 | 
					 | 
				
			||||||
    iconImage = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					 | 
				
			||||||
        return [
 | 
					 | 
				
			||||||
            css`
 | 
					 | 
				
			||||||
                :host {
 | 
					 | 
				
			||||||
                    display: none;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            `,
 | 
					            `,
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connectedCallback(): void {
 | 
					    constructor() {
 | 
				
			||||||
        super.connectedCallback();
 | 
					        super();
 | 
				
			||||||
 | 
					        window.addEventListener(EVENT_WS_MESSAGE, () => {
 | 
				
			||||||
        AKPageNavbar.setNavbarDetails({
 | 
					            this.firstUpdated();
 | 
				
			||||||
            header: this.header,
 | 
					 | 
				
			||||||
            description: this.description,
 | 
					 | 
				
			||||||
            icon: this.icon,
 | 
					 | 
				
			||||||
            iconImage: this.iconImage,
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    updated(): void {
 | 
					    async firstUpdated() {
 | 
				
			||||||
        AKPageNavbar.setNavbarDetails({
 | 
					        this.me = await me();
 | 
				
			||||||
            header: this.header,
 | 
					        this.uiConfig = await uiConfig();
 | 
				
			||||||
            description: this.description,
 | 
					        this.uiConfig.navbar.userDisplay = UserDisplay.none;
 | 
				
			||||||
            icon: this.icon,
 | 
					    }
 | 
				
			||||||
            iconImage: this.iconImage,
 | 
					
 | 
				
			||||||
        });
 | 
					    setTitle(header?: string) {
 | 
				
			||||||
 | 
					        const currentIf = currentInterface();
 | 
				
			||||||
 | 
					        let title = this.brand?.brandingTitle || TITLE_DEFAULT;
 | 
				
			||||||
 | 
					        if (currentIf === "admin") {
 | 
				
			||||||
 | 
					            title = `${msg("Admin")} - ${title}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Prepend the header to the title
 | 
				
			||||||
 | 
					        if (header !== undefined && header !== "") {
 | 
				
			||||||
 | 
					            title = `${header} - ${title}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        document.title = title;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    willUpdate() {
 | 
				
			||||||
 | 
					        // Always update title, even if there's no header value set,
 | 
				
			||||||
 | 
					        // as in that case we still need to return to the generic title
 | 
				
			||||||
 | 
					        this.setTitle(this.header);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderIcon() {
 | 
				
			||||||
 | 
					        if (this.icon) {
 | 
				
			||||||
 | 
					            if (this.iconImage && !this.icon.startsWith("fa://")) {
 | 
				
			||||||
 | 
					                return html`<img class="pf-icon" src="${this.icon}" alt="page icon" />`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const icon = this.icon.replaceAll("fa://", "fa ");
 | 
				
			||||||
 | 
					            return html`<i class=${icon}></i>`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return nothing;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render(): TemplateResult {
 | 
				
			||||||
 | 
					        return html`<div class="bar">
 | 
				
			||||||
 | 
					            <button
 | 
				
			||||||
 | 
					                class="sidebar-trigger pf-c-button pf-m-plain"
 | 
				
			||||||
 | 
					                @click=${() => {
 | 
				
			||||||
 | 
					                    this.dispatchEvent(
 | 
				
			||||||
 | 
					                        new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
 | 
				
			||||||
 | 
					                            bubbles: true,
 | 
				
			||||||
 | 
					                            composed: true,
 | 
				
			||||||
 | 
					                        }),
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <i class="fas fa-bars"></i>
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					            <section class="pf-c-page__main-section pf-m-light">
 | 
				
			||||||
 | 
					                <div class="pf-c-content">
 | 
				
			||||||
 | 
					                    <h1>
 | 
				
			||||||
 | 
					                        ${this.hasIcon
 | 
				
			||||||
 | 
					                            ? html`<slot name="icon">${this.renderIcon()}</slot> `
 | 
				
			||||||
 | 
					                            : nothing}
 | 
				
			||||||
 | 
					                        <slot name="header">${this.header}</slot>
 | 
				
			||||||
 | 
					                    </h1>
 | 
				
			||||||
 | 
					                    ${this.description ? html`<p>${this.description}</p>` : html``}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </section>
 | 
				
			||||||
 | 
					            <div class="pf-c-page__header-tools">
 | 
				
			||||||
 | 
					                <div class="pf-c-page__header-tools-group">
 | 
				
			||||||
 | 
					                    <ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.me}>
 | 
				
			||||||
 | 
					                        <a
 | 
				
			||||||
 | 
					                            class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
 | 
				
			||||||
 | 
					                            href="${globalAK().api.base}if/user/"
 | 
				
			||||||
 | 
					                            slot="extra"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            ${msg("User interface")}
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                    </ak-nav-buttons>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
    interface HTMLElementTagNameMap {
 | 
					    interface HTMLElementTagNameMap {
 | 
				
			||||||
        "ak-page-header": AKPageHeader;
 | 
					        "ak-page-header": PageHeader;
 | 
				
			||||||
        "ak-page-navbar": AKPageNavbar;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,6 @@ export class Sidebar extends AKElement {
 | 
				
			|||||||
            css`
 | 
					            css`
 | 
				
			||||||
                :host {
 | 
					                :host {
 | 
				
			||||||
                    z-index: 100;
 | 
					                    z-index: 100;
 | 
				
			||||||
                    --pf-c-page__sidebar--Transition: 0 !important;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                .pf-c-nav__link.pf-m-current::after,
 | 
					                .pf-c-nav__link.pf-m-current::after,
 | 
				
			||||||
                .pf-c-nav__link.pf-m-current:hover::after,
 | 
					                .pf-c-nav__link.pf-m-current:hover::after,
 | 
				
			||||||
@ -36,7 +35,10 @@ export class Sidebar extends AKElement {
 | 
				
			|||||||
                .pf-c-nav__section + .pf-c-nav__section {
 | 
					                .pf-c-nav__section + .pf-c-nav__section {
 | 
				
			||||||
                    --pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
 | 
					                    --pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                .pf-c-nav__list .sidebar-brand {
 | 
				
			||||||
 | 
					                    max-height: 82px;
 | 
				
			||||||
 | 
					                    margin-bottom: -0.5rem;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                nav {
 | 
					                nav {
 | 
				
			||||||
                    display: flex;
 | 
					                    display: flex;
 | 
				
			||||||
                    flex-direction: column;
 | 
					                    flex-direction: column;
 | 
				
			||||||
@ -68,6 +70,7 @@ export class Sidebar extends AKElement {
 | 
				
			|||||||
            class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}"
 | 
					            class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}"
 | 
				
			||||||
            aria-label=${msg("Global")}
 | 
					            aria-label=${msg("Global")}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					            <ak-sidebar-brand></ak-sidebar-brand>
 | 
				
			||||||
            <ul class="pf-c-nav__list">
 | 
					            <ul class="pf-c-nav__list">
 | 
				
			||||||
                <slot></slot>
 | 
					                <slot></slot>
 | 
				
			||||||
            </ul>
 | 
					            </ul>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,17 @@
 | 
				
			|||||||
 | 
					import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
 | 
				
			||||||
 | 
					import { AKElement } from "@goauthentik/elements/Base";
 | 
				
			||||||
 | 
					import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
 | 
				
			||||||
 | 
					import { themeImage } from "@goauthentik/elements/utils/images";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
 | 
					import { CSSResult, TemplateResult, css, html } from "lit";
 | 
				
			||||||
 | 
					import { customElement } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
				
			||||||
 | 
					import PFPage from "@patternfly/patternfly/components/Page/page.css";
 | 
				
			||||||
 | 
					import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			||||||
 | 
					import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
 | 
					import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// If the viewport is wider than MIN_WIDTH, the sidebar
 | 
					// If the viewport is wider than MIN_WIDTH, the sidebar
 | 
				
			||||||
@ -14,3 +28,79 @@ export const DefaultBrand: CurrentBrand = {
 | 
				
			|||||||
    matchedDomain: "",
 | 
					    matchedDomain: "",
 | 
				
			||||||
    defaultLocale: "",
 | 
					    defaultLocale: "",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@customElement("ak-sidebar-brand")
 | 
				
			||||||
 | 
					export class SidebarBrand extends WithBrandConfig(AKElement) {
 | 
				
			||||||
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            PFBase,
 | 
				
			||||||
 | 
					            PFGlobal,
 | 
				
			||||||
 | 
					            PFPage,
 | 
				
			||||||
 | 
					            PFButton,
 | 
				
			||||||
 | 
					            css`
 | 
				
			||||||
 | 
					                :host {
 | 
				
			||||||
 | 
					                    display: flex;
 | 
				
			||||||
 | 
					                    flex-direction: row;
 | 
				
			||||||
 | 
					                    align-items: center;
 | 
				
			||||||
 | 
					                    height: 114px;
 | 
				
			||||||
 | 
					                    min-height: 114px;
 | 
				
			||||||
 | 
					                    border-bottom: var(--pf-global--BorderWidth--sm);
 | 
				
			||||||
 | 
					                    border-bottom-style: solid;
 | 
				
			||||||
 | 
					                    border-bottom-color: var(--pf-global--BorderColor--100);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .pf-c-brand img {
 | 
				
			||||||
 | 
					                    padding: 0 0.5rem;
 | 
				
			||||||
 | 
					                    height: 42px;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                button.pf-c-button.sidebar-trigger {
 | 
				
			||||||
 | 
					                    background-color: transparent;
 | 
				
			||||||
 | 
					                    border-radius: 0px;
 | 
				
			||||||
 | 
					                    height: 100%;
 | 
				
			||||||
 | 
					                    color: var(--ak-dark-foreground);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            `,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					        window.addEventListener("resize", () => {
 | 
				
			||||||
 | 
					            this.requestUpdate();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render(): TemplateResult {
 | 
				
			||||||
 | 
					        return html` ${window.innerWidth <= MIN_WIDTH
 | 
				
			||||||
 | 
					                ? html`
 | 
				
			||||||
 | 
					                      <button
 | 
				
			||||||
 | 
					                          class="sidebar-trigger pf-c-button"
 | 
				
			||||||
 | 
					                          @click=${() => {
 | 
				
			||||||
 | 
					                              this.dispatchEvent(
 | 
				
			||||||
 | 
					                                  new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
 | 
				
			||||||
 | 
					                                      bubbles: true,
 | 
				
			||||||
 | 
					                                      composed: true,
 | 
				
			||||||
 | 
					                                  }),
 | 
				
			||||||
 | 
					                              );
 | 
				
			||||||
 | 
					                          }}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                          <i class="fas fa-bars"></i>
 | 
				
			||||||
 | 
					                      </button>
 | 
				
			||||||
 | 
					                  `
 | 
				
			||||||
 | 
					                : html``}
 | 
				
			||||||
 | 
					            <a href="#/" class="pf-c-page__header-brand-link">
 | 
				
			||||||
 | 
					                <div class="pf-c-brand ak-brand">
 | 
				
			||||||
 | 
					                    <img
 | 
				
			||||||
 | 
					                        src=${themeImage(this.brand?.brandingLogo ?? DefaultBrand.brandingLogo)}
 | 
				
			||||||
 | 
					                        alt="${msg("authentik Logo")}"
 | 
				
			||||||
 | 
					                        loading="lazy"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </a>`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					    interface HTMLElementTagNameMap {
 | 
				
			||||||
 | 
					        "ak-sidebar-brand": SidebarBrand;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,21 +1,19 @@
 | 
				
			|||||||
import {
 | 
					 | 
				
			||||||
    appendStyleSheet,
 | 
					 | 
				
			||||||
    assertAdoptableStyleSheetParent,
 | 
					 | 
				
			||||||
    createStyleSheetUnsafe,
 | 
					 | 
				
			||||||
} from "@goauthentik/common/stylesheets.js";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { TemplateResult, render as litRender } from "lit";
 | 
					import { TemplateResult, render as litRender } from "lit";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
 | 
					import AKGlobal from "@goauthentik/common/styles/authentik.css";
 | 
				
			||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
					import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ensureCSSStyleSheet } from "../utils/ensureCSSStyleSheet.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// A special version of render that ensures our style sheets will always be available
 | 
					// A special version of render that ensures our style sheets will always be available
 | 
				
			||||||
// to all elements under test.  Ensures they look right during testing, and that any
 | 
					// to all elements under test.  Ensures they look right during testing, and that any
 | 
				
			||||||
// CSS-based checks for visibility will return correct values.
 | 
					// CSS-based checks for visibility will return correct values.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const render = (body: TemplateResult) => {
 | 
					export const render = (body: TemplateResult) => {
 | 
				
			||||||
    assertAdoptableStyleSheetParent(document);
 | 
					    document.adoptedStyleSheets = [
 | 
				
			||||||
 | 
					        ...document.adoptedStyleSheets,
 | 
				
			||||||
    appendStyleSheet(document, ...[PFBase, AKGlobal].map(createStyleSheetUnsafe));
 | 
					        ensureCSSStyleSheet(PFBase),
 | 
				
			||||||
 | 
					        ensureCSSStyleSheet(AKGlobal),
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
    return litRender(body, document.body);
 | 
					    return litRender(body, document.body);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,9 @@
 | 
				
			|||||||
 | 
					import { AKElement } from "@goauthentik/elements/Base";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { type LitElement, type ReactiveControllerHost, type TemplateResult, nothing } from "lit";
 | 
					import { type LitElement, type ReactiveControllerHost, type TemplateResult, nothing } from "lit";
 | 
				
			||||||
import "lit";
 | 
					import "lit";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					export type ReactiveElementHost<T = AKElement> = Partial<ReactiveControllerHost> & T;
 | 
				
			||||||
 * A custom element which may be used as a host for a ReactiveController.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @remarks
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This type is derived from an internal type in Lit.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type ReactiveElementHost<T> = Partial<ReactiveControllerHost & T> & HTMLElement;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AbstractLitElementConstructor = abstract new (...args: never[]) => LitElement;
 | 
					export type AbstractLitElementConstructor = abstract new (...args: never[]) => LitElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								web/src/elements/utils/ensureCSSStyleSheet.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/src/elements/utils/ensureCSSStyleSheet.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import { CSSResult, unsafeCSS } from "lit";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const supportsAdoptingStyleSheets: boolean =
 | 
				
			||||||
 | 
					    window.ShadowRoot &&
 | 
				
			||||||
 | 
					    (window.ShadyCSS === undefined || window.ShadyCSS.nativeShadow) &&
 | 
				
			||||||
 | 
					    "adoptedStyleSheets" in Document.prototype &&
 | 
				
			||||||
 | 
					    "replace" in CSSStyleSheet.prototype;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function stringToStylesheet(css: string) {
 | 
				
			||||||
 | 
					    if (supportsAdoptingStyleSheets) {
 | 
				
			||||||
 | 
					        const sheet = unsafeCSS(css).styleSheet;
 | 
				
			||||||
 | 
					        if (sheet === undefined) {
 | 
				
			||||||
 | 
					            throw new Error(
 | 
				
			||||||
 | 
					                `CSS processing error: undefined stylesheet from string.  Source: ${css}`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return sheet;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sheet = new CSSStyleSheet();
 | 
				
			||||||
 | 
					    sheet.replaceSync(css);
 | 
				
			||||||
 | 
					    return sheet;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function cssResultToStylesheet(css: CSSResult) {
 | 
				
			||||||
 | 
					    const sheet = css.styleSheet;
 | 
				
			||||||
 | 
					    return sheet ? sheet : stringToStylesheet(css.toString());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ensureCSSStyleSheet = (css: string | CSSStyleSheet | CSSResult): CSSStyleSheet =>
 | 
				
			||||||
 | 
					    css instanceof CSSResult
 | 
				
			||||||
 | 
					        ? cssResultToStylesheet(css)
 | 
				
			||||||
 | 
					        : typeof css === "string"
 | 
				
			||||||
 | 
					          ? stringToStylesheet(css)
 | 
				
			||||||
 | 
					          : css;
 | 
				
			||||||
@ -1,55 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 * @file IFrame Utilities
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IFrameLoadResult {
 | 
					 | 
				
			||||||
    contentWindow: Window;
 | 
					 | 
				
			||||||
    contentDocument: Document;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function pluckIFrameContent(iframe: HTMLIFrameElement) {
 | 
					 | 
				
			||||||
    const contentWindow = iframe.contentWindow;
 | 
					 | 
				
			||||||
    const contentDocument = iframe.contentDocument;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!contentWindow) {
 | 
					 | 
				
			||||||
        throw new Error("Iframe contentWindow is not accessible");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!contentDocument) {
 | 
					 | 
				
			||||||
        throw new Error("Iframe contentDocument is not accessible");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        contentWindow,
 | 
					 | 
				
			||||||
        contentDocument,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function resolveIFrameContent(iframe: HTMLIFrameElement): Promise<IFrameLoadResult> {
 | 
					 | 
				
			||||||
    if (iframe.contentDocument?.readyState === "complete") {
 | 
					 | 
				
			||||||
        return Promise.resolve(pluckIFrameContent(iframe));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return new Promise((resolve) => {
 | 
					 | 
				
			||||||
        iframe.addEventListener("load", () => resolve(pluckIFrameContent(iframe)), { once: true });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Creates a minimal HTML wrapper for an iframe.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @deprecated Use the `contentDocument.body` directly instead.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function createIFrameHTMLWrapper(bodyContent: string): string {
 | 
					 | 
				
			||||||
    const html = String.raw;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return html`<!doctype html>
 | 
					 | 
				
			||||||
        <html>
 | 
					 | 
				
			||||||
            <head>
 | 
					 | 
				
			||||||
                <meta charset="utf-8" />
 | 
					 | 
				
			||||||
            </head>
 | 
					 | 
				
			||||||
            <body style="display:flex;flex-direction:row;justify-content:center;">
 | 
					 | 
				
			||||||
                ${bodyContent}
 | 
					 | 
				
			||||||
            </body>
 | 
					 | 
				
			||||||
        </html>`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,8 +1,13 @@
 | 
				
			|||||||
import { resolveUITheme } from "@goauthentik/common/theme";
 | 
					import { QUERY_MEDIA_COLOR_LIGHT, rootInterface } from "@goauthentik/elements/Base";
 | 
				
			||||||
import { rootInterface } from "@goauthentik/elements/Base";
 | 
					
 | 
				
			||||||
 | 
					import { UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function themeImage(rawPath: string) {
 | 
					export function themeImage(rawPath: string) {
 | 
				
			||||||
    const enabledTheme = rootInterface()?.activeTheme || resolveUITheme();
 | 
					    let enabledTheme = rootInterface()?.activeTheme;
 | 
				
			||||||
 | 
					    if (!enabledTheme || enabledTheme === UiThemeEnum.Automatic) {
 | 
				
			||||||
 | 
					        enabledTheme = window.matchMedia(QUERY_MEDIA_COLOR_LIGHT).matches
 | 
				
			||||||
 | 
					            ? UiThemeEnum.Light
 | 
				
			||||||
 | 
					            : UiThemeEnum.Dark;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return rawPath.replaceAll("%(theme)s", enabledTheme);
 | 
					    return rawPath.replaceAll("%(theme)s", enabledTheme);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -46,6 +46,7 @@ import {
 | 
				
			|||||||
    FlowsApi,
 | 
					    FlowsApi,
 | 
				
			||||||
    ResponseError,
 | 
					    ResponseError,
 | 
				
			||||||
    ShellChallenge,
 | 
					    ShellChallenge,
 | 
				
			||||||
 | 
					    UiThemeEnum,
 | 
				
			||||||
} from "@goauthentik/api";
 | 
					} from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-flow-executor")
 | 
					@customElement("ak-flow-executor")
 | 
				
			||||||
@ -199,6 +200,10 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getTheme(): Promise<UiThemeEnum> {
 | 
				
			||||||
 | 
					        return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async submit(
 | 
					    async submit(
 | 
				
			||||||
        payload?: FlowChallengeResponseRequest,
 | 
					        payload?: FlowChallengeResponseRequest,
 | 
				
			||||||
        options?: SubmitOptions,
 | 
					        options?: SubmitOptions,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { BrandedHTMLPolicy, sanitizeHTML } from "@goauthentik/common/purify";
 | 
					import { purify } from "@goauthentik/common/purify";
 | 
				
			||||||
import { AKElement } from "@goauthentik/elements/Base.js";
 | 
					import { AKElement } from "@goauthentik/elements/Base.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
@ -21,6 +21,8 @@ const styles = css`
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const poweredBy: FooterLink = { name: msg("Powered by authentik"), href: null };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-brand-links")
 | 
					@customElement("ak-brand-links")
 | 
				
			||||||
export class BrandLinks extends AKElement {
 | 
					export class BrandLinks extends AKElement {
 | 
				
			||||||
    static get styles() {
 | 
					    static get styles() {
 | 
				
			||||||
@ -31,21 +33,13 @@ export class BrandLinks extends AKElement {
 | 
				
			|||||||
    links: FooterLink[] = [];
 | 
					    links: FooterLink[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render() {
 | 
					    render() {
 | 
				
			||||||
        const links = [...(this.links ?? [])];
 | 
					        const links = [...(this.links ?? []), poweredBy];
 | 
				
			||||||
 | 
					 | 
				
			||||||
        return html` <ul class="pf-c-list pf-m-inline">
 | 
					        return html` <ul class="pf-c-list pf-m-inline">
 | 
				
			||||||
            ${map(links, (link) => {
 | 
					            ${map(links, (link) =>
 | 
				
			||||||
                const children = sanitizeHTML(BrandedHTMLPolicy, link.name);
 | 
					                link.href
 | 
				
			||||||
 | 
					                    ? purify(html`<li><a href="${link.href}">${link.name}</a></li>`)
 | 
				
			||||||
                if (link.href) {
 | 
					                    : html`<li><span>${link.name}</span></li>`,
 | 
				
			||||||
                    return html`<li><a href="${link.href}">${children}</a></li>`;
 | 
					            )}
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return html`<li>
 | 
					 | 
				
			||||||
                    <span> ${children} </span>
 | 
					 | 
				
			||||||
                </li>`;
 | 
					 | 
				
			||||||
            })}
 | 
					 | 
				
			||||||
            <li><span>${msg("Powered by authentik")}</span></li>
 | 
					 | 
				
			||||||
        </ul>`;
 | 
					        </ul>`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,15 @@
 | 
				
			|||||||
/// <reference types="@hcaptcha/types"/>
 | 
					///<reference types="@hcaptcha/types"/>
 | 
				
			||||||
/// <reference types="turnstile-types"/>
 | 
					import { renderStatic } from "@goauthentik/common/purify";
 | 
				
			||||||
import { renderStaticHTMLUnsafe } from "@goauthentik/common/purify";
 | 
					 | 
				
			||||||
import "@goauthentik/elements/EmptyState";
 | 
					import "@goauthentik/elements/EmptyState";
 | 
				
			||||||
import { akEmptyState } from "@goauthentik/elements/EmptyState";
 | 
					import { akEmptyState } from "@goauthentik/elements/EmptyState";
 | 
				
			||||||
import { bound } from "@goauthentik/elements/decorators/bound";
 | 
					import { bound } from "@goauthentik/elements/decorators/bound";
 | 
				
			||||||
import "@goauthentik/elements/forms/FormElement";
 | 
					import "@goauthentik/elements/forms/FormElement";
 | 
				
			||||||
import { createIFrameHTMLWrapper } from "@goauthentik/elements/utils/iframe";
 | 
					 | 
				
			||||||
import { ListenerController } from "@goauthentik/elements/utils/listenerController.js";
 | 
					import { ListenerController } from "@goauthentik/elements/utils/listenerController.js";
 | 
				
			||||||
import { randomId } from "@goauthentik/elements/utils/randomId";
 | 
					import { randomId } from "@goauthentik/elements/utils/randomId";
 | 
				
			||||||
import "@goauthentik/flow/FormStatic";
 | 
					import "@goauthentik/flow/FormStatic";
 | 
				
			||||||
import { BaseStage } from "@goauthentik/flow/stages/base";
 | 
					import { BaseStage } from "@goauthentik/flow/stages/base";
 | 
				
			||||||
import { P, match } from "ts-pattern";
 | 
					import { P, match } from "ts-pattern";
 | 
				
			||||||
 | 
					import type * as _ from "turnstile-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
 | 
					import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
 | 
				
			||||||
@ -57,36 +56,40 @@ type CaptchaHandler = {
 | 
				
			|||||||
// a resize. Because the Captcha is itself in an iframe, the reported height is often off by some
 | 
					// a resize. Because the Captcha is itself in an iframe, the reported height is often off by some
 | 
				
			||||||
// margin, so adding 2rem of height to our container adds padding and prevents scroll bars or hidden
 | 
					// margin, so adding 2rem of height to our container adds padding and prevents scroll bars or hidden
 | 
				
			||||||
// rendering.
 | 
					// rendering.
 | 
				
			||||||
function iframeTemplate(children: TemplateResult, challengeURL: string): TemplateResult {
 | 
					 | 
				
			||||||
    return html` ${children}
 | 
					 | 
				
			||||||
        <script>
 | 
					 | 
				
			||||||
            new ResizeObserver((entries) => {
 | 
					 | 
				
			||||||
                const height =
 | 
					 | 
				
			||||||
                    document.body.offsetHeight +
 | 
					 | 
				
			||||||
                    parseFloat(getComputedStyle(document.body).fontSize) * 2;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                window.parent.postMessage({
 | 
					const iframeTemplate = (captchaElement: TemplateResult, challengeUrl: string) =>
 | 
				
			||||||
                    message: "resize",
 | 
					    html`<!doctype html>
 | 
				
			||||||
                    source: "goauthentik.io",
 | 
					        <head>
 | 
				
			||||||
                    context: "flow-executor",
 | 
					            <html>
 | 
				
			||||||
                    size: { height },
 | 
					                <body style="display:flex;flex-direction:row;justify-content:center;">
 | 
				
			||||||
                });
 | 
					                    ${captchaElement}
 | 
				
			||||||
            }).observe(document.querySelector(".ak-captcha-container"));
 | 
					                    <script>
 | 
				
			||||||
        </script>
 | 
					                        new ResizeObserver((entries) => {
 | 
				
			||||||
 | 
					                            const height =
 | 
				
			||||||
        <script src=${challengeURL}></script>
 | 
					                                document.body.offsetHeight +
 | 
				
			||||||
 | 
					                                parseFloat(getComputedStyle(document.body).fontSize) * 2;
 | 
				
			||||||
        <script>
 | 
					                            window.parent.postMessage({
 | 
				
			||||||
            function callback(token) {
 | 
					                                message: "resize",
 | 
				
			||||||
                window.parent.postMessage({
 | 
					                                source: "goauthentik.io",
 | 
				
			||||||
                    message: "captcha",
 | 
					                                context: "flow-executor",
 | 
				
			||||||
                    source: "goauthentik.io",
 | 
					                                size: { height },
 | 
				
			||||||
                    context: "flow-executor",
 | 
					                            });
 | 
				
			||||||
                    token,
 | 
					                        }).observe(document.querySelector(".ak-captcha-container"));
 | 
				
			||||||
                });
 | 
					                    </script>
 | 
				
			||||||
            }
 | 
					                    <script src=${challengeUrl}></script>
 | 
				
			||||||
        </script>`;
 | 
					                    <script>
 | 
				
			||||||
}
 | 
					                        function callback(token) {
 | 
				
			||||||
 | 
					                            window.parent.postMessage({
 | 
				
			||||||
 | 
					                                message: "captcha",
 | 
				
			||||||
 | 
					                                source: "goauthentik.io",
 | 
				
			||||||
 | 
					                                context: "flow-executor",
 | 
				
			||||||
 | 
					                                token: token,
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    </script>
 | 
				
			||||||
 | 
					                </body>
 | 
				
			||||||
 | 
					            </html>
 | 
				
			||||||
 | 
					        </head>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-stage-captcha")
 | 
					@customElement("ak-stage-captcha")
 | 
				
			||||||
export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeResponseRequest> {
 | 
					export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeResponseRequest> {
 | 
				
			||||||
@ -302,25 +305,11 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async renderFrame(captchaElement: TemplateResult) {
 | 
					    async renderFrame(captchaElement: TemplateResult) {
 | 
				
			||||||
        const { contentDocument } = this.captchaFrame || {};
 | 
					        this.captchaFrame.contentWindow?.document.open();
 | 
				
			||||||
 | 
					        this.captchaFrame.contentWindow?.document.write(
 | 
				
			||||||
        if (!contentDocument) {
 | 
					            await renderStatic(iframeTemplate(captchaElement, this.challenge.jsUrl)),
 | 
				
			||||||
            console.debug(
 | 
					 | 
				
			||||||
                "authentik/stages/captcha: unable to render captcha frame, no contentDocument",
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        contentDocument.open();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        contentDocument.write(
 | 
					 | 
				
			||||||
            createIFrameHTMLWrapper(
 | 
					 | 
				
			||||||
                renderStaticHTMLUnsafe(iframeTemplate(captchaElement, this.challenge.jsUrl)),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        this.captchaFrame.contentWindow?.document.close();
 | 
				
			||||||
        contentDocument.close();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderBody() {
 | 
					    renderBody() {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import "rapidoc";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { CSRFHeaderName } from "@goauthentik/common/api/config";
 | 
					import { CSRFHeaderName } from "@goauthentik/common/api/config";
 | 
				
			||||||
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
 | 
					import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
 | 
				
			||||||
 | 
					import { globalAK } from "@goauthentik/common/global";
 | 
				
			||||||
import { first, getCookie } from "@goauthentik/common/utils";
 | 
					import { first, getCookie } from "@goauthentik/common/utils";
 | 
				
			||||||
import { Interface } from "@goauthentik/elements/Interface";
 | 
					import { Interface } from "@goauthentik/elements/Interface";
 | 
				
			||||||
import "@goauthentik/elements/ak-locale-context";
 | 
					import "@goauthentik/elements/ak-locale-context";
 | 
				
			||||||
@ -61,6 +62,10 @@ export class APIBrowser extends Interface {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getTheme(): Promise<UiThemeEnum> {
 | 
				
			||||||
 | 
					        return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render(): TemplateResult {
 | 
				
			||||||
        return html`
 | 
					        return html`
 | 
				
			||||||
            <ak-locale-context>
 | 
					            <ak-locale-context>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { globalAK } from "@goauthentik/common/global";
 | 
				
			||||||
import { Interface } from "@goauthentik/elements/Interface";
 | 
					import { Interface } from "@goauthentik/elements/Interface";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
@ -9,6 +10,8 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
 | 
				
			|||||||
import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
 | 
					import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
 | 
				
			||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
					import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-loading")
 | 
					@customElement("ak-loading")
 | 
				
			||||||
export class Loading extends Interface {
 | 
					export class Loading extends Interface {
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
@ -25,7 +28,7 @@ export class Loading extends Interface {
 | 
				
			|||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    registerContexts(): void {
 | 
					    _initContexts(): void {
 | 
				
			||||||
        // Stub function to avoid making API requests for things we don't need. The `Interface` base class loads
 | 
					        // Stub function to avoid making API requests for things we don't need. The `Interface` base class loads
 | 
				
			||||||
        // a bunch of data that is used globally by various things, however this is an interface that is shown
 | 
					        // a bunch of data that is used globally by various things, however this is an interface that is shown
 | 
				
			||||||
        // very briefly and we don't need any of that data.
 | 
					        // very briefly and we don't need any of that data.
 | 
				
			||||||
@ -35,6 +38,10 @@ export class Loading extends Interface {
 | 
				
			|||||||
        // Stub function to avoid fetching custom CSS.
 | 
					        // Stub function to avoid fetching custom CSS.
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getTheme(): Promise<UiThemeEnum> {
 | 
				
			||||||
 | 
					        return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render(): TemplateResult {
 | 
				
			||||||
        return html` <section
 | 
					        return html` <section
 | 
				
			||||||
            class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
 | 
					            class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,18 @@
 | 
				
			|||||||
import { FlowExecutor } from "@goauthentik/flow/FlowExecutor";
 | 
					import { FlowExecutor } from "@goauthentik/flow/FlowExecutor";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { customElement } from "lit/decorators.js";
 | 
					import { customElement, property } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-storybook-interface-flow")
 | 
					@customElement("ak-storybook-interface-flow")
 | 
				
			||||||
export class StoryFlowInterface extends FlowExecutor {}
 | 
					export class StoryFlowInterface extends FlowExecutor {
 | 
				
			||||||
 | 
					    @property()
 | 
				
			||||||
 | 
					    storyTheme: UiThemeEnum = UiThemeEnum.Dark;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getTheme(): Promise<UiThemeEnum> {
 | 
				
			||||||
 | 
					        return this.storyTheme;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
    interface HTMLElementTagNameMap {
 | 
					    interface HTMLElementTagNameMap {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,18 @@
 | 
				
			|||||||
import { Interface } from "@goauthentik/elements/Interface";
 | 
					import { Interface } from "@goauthentik/elements/Interface";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { customElement } from "lit/decorators.js";
 | 
					import { customElement, property } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { UiThemeEnum } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-storybook-interface")
 | 
					@customElement("ak-storybook-interface")
 | 
				
			||||||
export class StoryInterface extends Interface {}
 | 
					export class StoryInterface extends Interface {
 | 
				
			||||||
 | 
					    @property()
 | 
				
			||||||
 | 
					    storyTheme: UiThemeEnum = UiThemeEnum.Dark;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getTheme(): Promise<UiThemeEnum> {
 | 
				
			||||||
 | 
					        return this.storyTheme;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
    interface HTMLElementTagNameMap {
 | 
					    interface HTMLElementTagNameMap {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import {
 | 
				
			|||||||
} from "@goauthentik/common/constants";
 | 
					} from "@goauthentik/common/constants";
 | 
				
			||||||
import { globalAK } from "@goauthentik/common/global";
 | 
					import { globalAK } from "@goauthentik/common/global";
 | 
				
			||||||
import { configureSentry } from "@goauthentik/common/sentry";
 | 
					import { configureSentry } from "@goauthentik/common/sentry";
 | 
				
			||||||
import { UIConfig, getConfigForUser } from "@goauthentik/common/ui/config";
 | 
					import { UIConfig } from "@goauthentik/common/ui/config";
 | 
				
			||||||
import { me } from "@goauthentik/common/users";
 | 
					import { me } from "@goauthentik/common/users";
 | 
				
			||||||
import { WebsocketClient } from "@goauthentik/common/ws";
 | 
					import { WebsocketClient } from "@goauthentik/common/ws";
 | 
				
			||||||
import "@goauthentik/components/ak-nav-buttons";
 | 
					import "@goauthentik/components/ak-nav-buttons";
 | 
				
			||||||
@ -292,7 +292,6 @@ export class UserInterface extends AuthenticatedInterface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async connectedCallback() {
 | 
					    async connectedCallback() {
 | 
				
			||||||
        super.connectedCallback();
 | 
					        super.connectedCallback();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
 | 
					        window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
 | 
				
			||||||
        window.addEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
 | 
					        window.addEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
 | 
				
			||||||
        window.addEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
 | 
					        window.addEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
 | 
				
			||||||
@ -302,7 +301,6 @@ export class UserInterface extends AuthenticatedInterface {
 | 
				
			|||||||
        window.removeEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
 | 
					        window.removeEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
 | 
				
			||||||
        window.removeEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
 | 
					        window.removeEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
 | 
				
			||||||
        window.removeEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
 | 
					        window.removeEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        super.disconnectedCallback();
 | 
					        super.disconnectedCallback();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -321,10 +319,8 @@ export class UserInterface extends AuthenticatedInterface {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fetchConfigurationDetails() {
 | 
					    fetchConfigurationDetails() {
 | 
				
			||||||
        me().then((session: SessionUser) => {
 | 
					        me().then((me: SessionUser) => {
 | 
				
			||||||
            this.me = session;
 | 
					            this.me = me;
 | 
				
			||||||
            this.uiConfig = getConfigForUser(session.user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            new EventsApi(DEFAULT_CONFIG)
 | 
					            new EventsApi(DEFAULT_CONFIG)
 | 
				
			||||||
                .eventsNotificationsList({
 | 
					                .eventsNotificationsList({
 | 
				
			||||||
                    seen: false,
 | 
					                    seen: false,
 | 
				
			||||||
@ -338,16 +334,12 @@ export class UserInterface extends AuthenticatedInterface {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get isFullyConfigured() {
 | 
				
			||||||
 | 
					        return Boolean(this.uiConfig && this.me);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render() {
 | 
					    render() {
 | 
				
			||||||
        if (!this.me) {
 | 
					        if (!this.isFullyConfigured) {
 | 
				
			||||||
            console.debug(`authentik/user/UserInterface: waiting for user session to be available`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return nothing;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!this.uiConfig) {
 | 
					 | 
				
			||||||
            console.debug(`authentik/user/UserInterface: waiting for UI config to be available`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return nothing;
 | 
					            return nothing;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2106
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2106
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -33,7 +33,7 @@
 | 
				
			|||||||
        "docusaurus-theme-openapi-docs": "4.3.4",
 | 
					        "docusaurus-theme-openapi-docs": "4.3.4",
 | 
				
			||||||
        "postcss": "^8.5.3",
 | 
					        "postcss": "^8.5.3",
 | 
				
			||||||
        "prism-react-renderer": "^2.4.1",
 | 
					        "prism-react-renderer": "^2.4.1",
 | 
				
			||||||
        "react": "^18.3.1",
 | 
					        "react": "^19.1.0",
 | 
				
			||||||
        "react-before-after-slider-component": "^1.1.8",
 | 
					        "react-before-after-slider-component": "^1.1.8",
 | 
				
			||||||
        "react-dom": "^18.3.1",
 | 
					        "react-dom": "^18.3.1",
 | 
				
			||||||
        "react-feather": "^2.0.10",
 | 
					        "react-feather": "^2.0.10",
 | 
				
			||||||
@ -58,7 +58,7 @@
 | 
				
			|||||||
        "@docusaurus/module-type-aliases": "^3.3.2",
 | 
					        "@docusaurus/module-type-aliases": "^3.3.2",
 | 
				
			||||||
        "@docusaurus/tsconfig": "^3.7.0",
 | 
					        "@docusaurus/tsconfig": "^3.7.0",
 | 
				
			||||||
        "@docusaurus/types": "^3.3.2",
 | 
					        "@docusaurus/types": "^3.3.2",
 | 
				
			||||||
        "@types/react": "^18.3.13",
 | 
					        "@types/react": "^19.1.2",
 | 
				
			||||||
        "cross-env": "^7.0.3",
 | 
					        "cross-env": "^7.0.3",
 | 
				
			||||||
        "prettier": "3.5.3",
 | 
					        "prettier": "3.5.3",
 | 
				
			||||||
        "typescript": "~5.8.3",
 | 
					        "typescript": "~5.8.3",
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user