web: initial implementation of new forms
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -1,15 +0,0 @@ | ||||
| """authentik core user forms""" | ||||
|  | ||||
| from django import forms | ||||
|  | ||||
| from authentik.core.models import User | ||||
|  | ||||
|  | ||||
| class UserDetailForm(forms.ModelForm): | ||||
|     """Update User Details""" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = User | ||||
|         fields = ["username", "name", "email"] | ||||
|         widgets = {"name": forms.TextInput} | ||||
| @ -1,26 +0,0 @@ | ||||
| {% load i18n %} | ||||
|  | ||||
| <div class="pf-c-card"> | ||||
|     <div class="pf-c-card__title"> | ||||
|         {% trans 'Update details' %} | ||||
|     </div> | ||||
|     <div class="pf-c-card__body"> | ||||
|         <form action="" method="post" class="pf-c-form pf-m-horizontal"> | ||||
|             {% include 'partials/form_horizontal.html' with form=form %} | ||||
|             {% block beneath_form %} | ||||
|             {% endblock %} | ||||
|             <div class="pf-c-form__group pf-m-action"> | ||||
|                 <div class="pf-c-form__horizontal-group"> | ||||
|                     <div class="pf-c-form__actions"> | ||||
|                         <input class="pf-c-button pf-m-primary" type="submit" value="{% trans 'Update' %}" /> | ||||
|                         {% if unenrollment_enabled %} | ||||
|                         <a class="pf-c-button pf-m-danger" | ||||
|                             href="{% url 'authentik_flows:default-unenrollment' %}?back={{ request.get_full_path }}">{% | ||||
|                             trans "Delete account" %}</a> | ||||
|                         {% endif %} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
| </div> | ||||
| @ -1,30 +0,0 @@ | ||||
| """authentik user view tests""" | ||||
| import string | ||||
| from random import SystemRandom | ||||
|  | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.models import User | ||||
|  | ||||
|  | ||||
| class TestUserViews(TestCase): | ||||
|     """Test User Views""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.user = User.objects.create_user( | ||||
|             username="unittest user", | ||||
|             email="unittest@example.com", | ||||
|             password="".join( | ||||
|                 SystemRandom().choice(string.ascii_uppercase + string.digits) | ||||
|                 for _ in range(8) | ||||
|             ), | ||||
|         ) | ||||
|         self.client.force_login(self.user) | ||||
|  | ||||
|     def test_user_details(self): | ||||
|         """Test UserDetailsView""" | ||||
|         self.assertEqual( | ||||
|             self.client.get(reverse("authentik_core:user-details")).status_code, 200 | ||||
|         ) | ||||
| @ -14,7 +14,6 @@ urlpatterns = [ | ||||
|         name="root-redirect", | ||||
|     ), | ||||
|     # User views | ||||
|     path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"), | ||||
|     path( | ||||
|         "-/user/tokens/create/", | ||||
|         user.TokenCreateView.as_view(), | ||||
|  | ||||
| @ -15,39 +15,11 @@ from guardian.mixins import PermissionRequiredMixin | ||||
| from guardian.shortcuts import get_objects_for_user | ||||
|  | ||||
| from authentik.core.forms.token import UserTokenForm | ||||
| from authentik.core.forms.users import UserDetailForm | ||||
| from authentik.core.models import Token, TokenIntents | ||||
| from authentik.flows.models import Flow, FlowDesignation | ||||
| from authentik.lib.views import CreateAssignPermView | ||||
|  | ||||
|  | ||||
| class UserSettingsView(TemplateView): | ||||
|     """Multiple SiteShells for user details and all stages""" | ||||
|  | ||||
|     template_name = "user/settings.html" | ||||
|  | ||||
|  | ||||
| class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): | ||||
|     """Update User details""" | ||||
|  | ||||
|     template_name = "user/details.html" | ||||
|     form_class = UserDetailForm | ||||
|  | ||||
|     success_message = _("Successfully updated user.") | ||||
|     success_url = reverse_lazy("authentik_core:user-details") | ||||
|  | ||||
|     def get_object(self): | ||||
|         return self.request.user | ||||
|  | ||||
|     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||
|         kwargs = super().get_context_data(**kwargs) | ||||
|         unenrollment_flow = Flow.with_policy( | ||||
|             self.request, designation=FlowDesignation.UNRENOLLMENT | ||||
|         ) | ||||
|         kwargs["unenrollment_enabled"] = bool(unenrollment_flow) | ||||
|         return kwargs | ||||
|  | ||||
|  | ||||
| class TokenCreateView( | ||||
|     SuccessMessageMixin, | ||||
|     LoginRequiredMixin, | ||||
|  | ||||
| @ -98,7 +98,7 @@ class TestFlowsEnroll(SeleniumTestCase): | ||||
|         wait = WebDriverWait(interface_admin, self.wait_timeout) | ||||
|  | ||||
|         wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar"))) | ||||
|         self.driver.get(self.if_admin_url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         user = User.objects.get(username="foo") | ||||
|         self.assertEqual(user.username, "foo") | ||||
| @ -198,7 +198,7 @@ class TestFlowsEnroll(SeleniumTestCase): | ||||
|         ) | ||||
|  | ||||
|         wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar"))) | ||||
|         self.driver.get(self.if_admin_url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         self.assert_user(User.objects.get(username="foo")) | ||||
|  | ||||
|  | ||||
| @ -160,7 +160,7 @@ class TestSourceOAuth2(SeleniumTestCase): | ||||
|  | ||||
|         # Wait until we've logged in | ||||
|         self.wait_for_url(self.if_admin_url("/library")) | ||||
|         self.driver.get(self.url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" | ||||
| @ -255,7 +255,7 @@ class TestSourceOAuth2(SeleniumTestCase): | ||||
|  | ||||
|         # Wait until we've logged in | ||||
|         self.wait_for_url(self.if_admin_url("/library")) | ||||
|         self.driver.get(self.url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" | ||||
| @ -359,7 +359,7 @@ class TestSourceOAuth1(SeleniumTestCase): | ||||
|         sleep(2) | ||||
|         # Wait until we've logged in | ||||
|         self.wait_for_url(self.if_admin_url("/library")) | ||||
|         self.driver.get(self.url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             self.driver.find_element(By.ID, "id_username").get_attribute("value"), | ||||
|  | ||||
| @ -153,7 +153,7 @@ class TestSourceSAML(SeleniumTestCase): | ||||
|  | ||||
|         # Wait until we're logged in | ||||
|         self.wait_for_url(self.if_admin_url("/library")) | ||||
|         self.driver.get(self.url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         # Wait until we've loaded the user info page | ||||
|         self.assertNotEqual( | ||||
| @ -233,7 +233,7 @@ class TestSourceSAML(SeleniumTestCase): | ||||
|  | ||||
|         # Wait until we're logged in | ||||
|         self.wait_for_url(self.if_admin_url("/library")) | ||||
|         self.driver.get(self.url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         # Wait until we've loaded the user info page | ||||
|         self.assertNotEqual( | ||||
| @ -300,7 +300,7 @@ class TestSourceSAML(SeleniumTestCase): | ||||
|  | ||||
|         # Wait until we're logged in | ||||
|         self.wait_for_url(self.if_admin_url("/library")) | ||||
|         self.driver.get(self.url("authentik_core:user-details")) | ||||
|         self.driver.get(self.if_admin_url("/user")) | ||||
|  | ||||
|         # Wait until we've loaded the user info page | ||||
|         self.assertNotEqual( | ||||
|  | ||||
							
								
								
									
										138
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										138
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -133,6 +133,139 @@ | ||||
|             "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.90.5.tgz", | ||||
|             "integrity": "sha512-Fe0C8UkzSjtacQ+fHXlFB/LHzrv/c2K4z479C6dboOgkGQE1FyB0wt1NBfxij0D++rhOy04OOYdE+Tr0JSlZKw==" | ||||
|         }, | ||||
|         "@polymer/font-roboto": { | ||||
|             "version": "3.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/font-roboto/-/font-roboto-3.0.2.tgz", | ||||
|             "integrity": "sha512-tx5TauYSmzsIvmSqepUPDYbs4/Ejz2XbZ1IkD7JEGqkdNUJlh+9KU85G56Tfdk/xjEZ8zorFfN09OSwiMrIQWA==" | ||||
|         }, | ||||
|         "@polymer/iron-a11y-announcer": { | ||||
|             "version": "3.1.0", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.1.0.tgz", | ||||
|             "integrity": "sha512-lc5i4NKB8kSQHH0Hwu8WS3ym93m+J69OHJWSSBxwd17FI+h2wmgxDzeG9LI4ojMMck17/uc2pLe7g/UHt5/K/A==", | ||||
|             "requires": { | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-a11y-keys-behavior": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-a11y-keys-behavior/-/iron-a11y-keys-behavior-3.0.1.tgz", | ||||
|             "integrity": "sha512-lnrjKq3ysbBPT/74l0Fj0U9H9C35Tpw2C/tpJ8a+5g8Y3YJs1WSZYnEl1yOkw6sEyaxOq/1DkzH0+60gGu5/PQ==", | ||||
|             "requires": { | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-ajax": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-ajax/-/iron-ajax-3.0.1.tgz", | ||||
|             "integrity": "sha512-7+TPEAfWsRdhj1Y8UeF1759ktpVu+c3sG16rJiUC3wF9+woQ9xI1zUm2d59i7Yc3aDEJrR/Q8Y262KlOvyGVNg==", | ||||
|             "requires": { | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-autogrow-textarea": { | ||||
|             "version": "3.0.3", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-autogrow-textarea/-/iron-autogrow-textarea-3.0.3.tgz", | ||||
|             "integrity": "sha512-5r0VkWrIlm0JIp5E5wlnvkw7slK72lFRZXncmrsLZF+6n1dg2rI8jt7xpFzSmUWrqpcyXwyKaGaDvUjl3j4JLA==", | ||||
|             "requires": { | ||||
|                 "@polymer/iron-behaviors": "^3.0.0-pre.26", | ||||
|                 "@polymer/iron-flex-layout": "^3.0.0-pre.26", | ||||
|                 "@polymer/iron-validatable-behavior": "^3.0.0-pre.26", | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-behaviors": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-behaviors/-/iron-behaviors-3.0.1.tgz", | ||||
|             "integrity": "sha512-IMEwcv1lhf1HSQxuyWOUIL0lOBwmeaoSTpgCJeP9IBYnuB1SPQngmfRuHKgK6/m9LQ9F9miC7p3HeQQUdKAE0w==", | ||||
|             "requires": { | ||||
|                 "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26", | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-flex-layout": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz", | ||||
|             "integrity": "sha512-7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw==", | ||||
|             "requires": { | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-form": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-form/-/iron-form-3.0.1.tgz", | ||||
|             "integrity": "sha512-JwSQXHjYALsytCeBkXlY8aRwqgZuYIqzOk3iHuugb1RXOdZ7MZHyJhMDVBbscHjxqPKu/KaVzAjrcfwNNafzEA==", | ||||
|             "requires": { | ||||
|                 "@polymer/iron-ajax": "^3.0.0-pre.26", | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-form-element-behavior": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-form-element-behavior/-/iron-form-element-behavior-3.0.1.tgz", | ||||
|             "integrity": "sha512-G/e2KXyL5AY7mMjmomHkGpgS0uAf4ovNpKhkuUTRnMuMJuf589bKqE85KN4ovE1Tzhv2hJoh/igyD6ekHiYU1A==", | ||||
|             "requires": { | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-input": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-input/-/iron-input-3.0.1.tgz", | ||||
|             "integrity": "sha512-WLx13kEcbH9GKbj9+pWR6pbJkA5kxn3796ynx6eQd2rueMyUfVTR3GzOvadBKsciUuIuzrxpBWZ2+3UcueVUQQ==", | ||||
|             "requires": { | ||||
|                 "@polymer/iron-a11y-announcer": "^3.0.0-pre.26", | ||||
|                 "@polymer/iron-validatable-behavior": "^3.0.0-pre.26", | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-meta": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-meta/-/iron-meta-3.0.1.tgz", | ||||
|             "integrity": "sha512-pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA==", | ||||
|             "requires": { | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/iron-validatable-behavior": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/iron-validatable-behavior/-/iron-validatable-behavior-3.0.1.tgz", | ||||
|             "integrity": "sha512-wwpYh6wOa4fNI+jH5EYKC7TVPYQ2OfgQqocWat7GsNWcsblKYhLYbwsvEY5nO0n2xKqNfZzDLrUom5INJN7msQ==", | ||||
|             "requires": { | ||||
|                 "@polymer/iron-meta": "^3.0.0-pre.26", | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/paper-input": { | ||||
|             "version": "3.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/paper-input/-/paper-input-3.2.1.tgz", | ||||
|             "integrity": "sha512-6ghgwQKM6mS0hAQxQqj+tkeEY1VUBqAsrasAm8V5RpNcfSWQC/hhRFxU0beGuKTAhndzezDzWYP6Zz4b8fExGg==", | ||||
|             "requires": { | ||||
|                 "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26", | ||||
|                 "@polymer/iron-autogrow-textarea": "^3.0.0-pre.26", | ||||
|                 "@polymer/iron-behaviors": "^3.0.0-pre.26", | ||||
|                 "@polymer/iron-form-element-behavior": "^3.0.0-pre.26", | ||||
|                 "@polymer/iron-input": "^3.0.0-pre.26", | ||||
|                 "@polymer/paper-styles": "^3.0.0-pre.26", | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/paper-styles": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/paper-styles/-/paper-styles-3.0.1.tgz", | ||||
|             "integrity": "sha512-y6hmObLqlCx602TQiSBKHqjwkE7xmDiFkoxdYGaNjtv4xcysOTdVJsDR/R9UHwIaxJ7gHlthMSykir1nv78++g==", | ||||
|             "requires": { | ||||
|                 "@polymer/font-roboto": "^3.0.1", | ||||
|                 "@polymer/iron-flex-layout": "^3.0.0-pre.26", | ||||
|                 "@polymer/polymer": "^3.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@polymer/polymer": { | ||||
|             "version": "3.4.1", | ||||
|             "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.4.1.tgz", | ||||
|             "integrity": "sha512-KPWnhDZibtqKrUz7enIPOiO4ZQoJNOuLwqrhV2MXzIt3VVnUVJVG5ORz4Z2sgO+UZ+/UZnPD0jqY+jmw/+a9mQ==", | ||||
|             "requires": { | ||||
|                 "@webcomponents/shadycss": "^1.9.1" | ||||
|             } | ||||
|         }, | ||||
|         "@rollup/plugin-typescript": { | ||||
|             "version": "8.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.0.tgz", | ||||
| @ -490,6 +623,11 @@ | ||||
|                 "eslint-visitor-keys": "^2.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "@webcomponents/shadycss": { | ||||
|             "version": "1.10.2", | ||||
|             "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.10.2.tgz", | ||||
|             "integrity": "sha512-9Iseu8bRtecb0klvv+WXZOVZatsRkbaH7M97Z+f+Pt909R4lDfgUODAnra23DOZTpeMTAkVpf4m/FZztN7Ox1A==" | ||||
|         }, | ||||
|         "acorn": { | ||||
|             "version": "7.4.1", | ||||
|             "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", | ||||
|  | ||||
| @ -12,6 +12,8 @@ | ||||
|     "dependencies": { | ||||
|         "@fortawesome/fontawesome-free": "^5.15.3", | ||||
|         "@patternfly/patternfly": "^4.90.5", | ||||
|         "@polymer/iron-form": "^3.0.1", | ||||
|         "@polymer/paper-input": "^3.2.1", | ||||
|         "@sentry/browser": "^6.2.3", | ||||
|         "@sentry/tracing": "^6.2.3", | ||||
|         "@types/chart.js": "^2.9.31", | ||||
|  | ||||
| @ -105,6 +105,10 @@ export class AppURLManager { | ||||
|  | ||||
| export class FlowURLManager { | ||||
|  | ||||
|     static defaultUnenrollment(): string { | ||||
|         return "-/default/unenrollment/"; | ||||
|     } | ||||
|  | ||||
|     static configure(stageUuid: string, rest: string): string { | ||||
|         return `-/configure/${stageUuid}/${rest}`; | ||||
|     } | ||||
|  | ||||
| @ -88,6 +88,7 @@ body { | ||||
|  | ||||
| @media (prefers-color-scheme: dark) { | ||||
|     :root { | ||||
|         --ak-accent: #fd4b2d; | ||||
|         --ak-dark-foreground: #fafafa; | ||||
|         --ak-dark-foreground-darker: #bebebe; | ||||
|         --ak-dark-foreground-link: #5a5cb9; | ||||
| @ -100,6 +101,12 @@ body { | ||||
|         --pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker); | ||||
|         --pf-global--link--Color: var(--ak-dark-foreground-link); | ||||
|     } | ||||
|  | ||||
|     paper-input { | ||||
|         /* --paper-input-container-color: var(--ak-accent); */ | ||||
|         --paper-input-container-input-color: var(--ak-dark-foreground); | ||||
|     } | ||||
|  | ||||
|     /* Global page background colour */ | ||||
|     .pf-c-page { | ||||
|         --pf-c-page--BackgroundColor: var(--ak-dark-background); | ||||
|  | ||||
| @ -8,7 +8,6 @@ import "./elements/buttons/ModalButton"; | ||||
| import "./elements/buttons/SpinnerButton"; | ||||
| import "./elements/CodeMirror"; | ||||
|  | ||||
| import "./pages/tokens/UserTokenList"; | ||||
| import "./pages/generic/SiteShell"; | ||||
| import "./interfaces/AdminInterface"; | ||||
| import "./elements/messages/MessageContainer"; | ||||
|  | ||||
							
								
								
									
										151
									
								
								web/src/pages/users/UserDetailsPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								web/src/pages/users/UserDetailsPage.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | ||||
| import { gettext } from "django"; | ||||
| import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import PFCard from "@patternfly/patternfly/components/Card/card.css"; | ||||
| import AKGlobal from "../../authentik.css"; | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import PFForm from "@patternfly/patternfly/components/Form/form.css"; | ||||
| import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; | ||||
| import { CoreApi, User } from "authentik-api"; | ||||
| import { me } from "../../api/Users"; | ||||
| import "../../elements/forms/FormElement"; | ||||
| import "../../elements/EmptyState"; | ||||
| import { FlowURLManager } from "../../api/legacy"; | ||||
| import "@polymer/paper-input/paper-input"; | ||||
| import "@polymer/iron-form/iron-form"; | ||||
| import { DEFAULT_CONFIG } from "../../api/Config"; | ||||
| import { PaperInputElement } from "@polymer/paper-input/paper-input"; | ||||
| import { showMessage } from "../../elements/messages/MessageContainer"; | ||||
|  | ||||
| export interface ErrorResponse { | ||||
|     [key: string]: string[]; | ||||
| } | ||||
|  | ||||
| @customElement("ak-form") | ||||
| export class Form extends LitElement { | ||||
|  | ||||
|     @property() | ||||
|     successMessage = ""; | ||||
|  | ||||
|     @property() | ||||
|     send!: (data: Record<string, unknown>) => Promise<unknown>; | ||||
|  | ||||
|     submit(ev: Event): void { | ||||
|         ev.preventDefault(); | ||||
|         const ironForm = this.shadowRoot?.querySelector("iron-form"); | ||||
|         if (!ironForm) { | ||||
|             return; | ||||
|         } | ||||
|         const data = ironForm.serializeForm(); | ||||
|         this.send(data).then(() => { | ||||
|             showMessage({ | ||||
|                 level_tag: "success", | ||||
|                 message: this.successMessage | ||||
|             }); | ||||
|         }).catch((ex: Response) => { | ||||
|             if (ex.status > 399 && ex.status < 500) { | ||||
|                 return ex.json(); | ||||
|             } | ||||
|             return ex; | ||||
|         }).then((errorMessage?: ErrorResponse) => { | ||||
|             if (!errorMessage) return; | ||||
|             const elements: PaperInputElement[] = ironForm._getSubmittableElements(); | ||||
|             elements.forEach((element) => { | ||||
|                 const elementName = element.name; | ||||
|                 if (!elementName) return; | ||||
|                 if (elementName in errorMessage) { | ||||
|                     element.errorMessage = errorMessage[elementName].join(", "); | ||||
|                     element.invalid = true; | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<iron-form | ||||
|             @iron-form-presubmit=${(ev: Event) => { this.submit(ev); }}> | ||||
|             <slot></slot> | ||||
|         </iron-form>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @customElement("ak-user-details") | ||||
| export class UserDetailsPage extends LitElement { | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFCard, PFForm, PFFormControl, PFButton, AKGlobal]; | ||||
|     } | ||||
|  | ||||
|     @property({attribute: false}) | ||||
|     user?: User; | ||||
|  | ||||
|     firstUpdated(): void { | ||||
|         me().then((user) => { | ||||
|             this.user = user.user; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         if (!this.user) { | ||||
|             return html`<ak-empty-state | ||||
|                 ?loading="${true}" | ||||
|                 header=${gettext("Loading")}> | ||||
|             </ak-empty-state>`; | ||||
|         } | ||||
|         return html`<div class="pf-c-card"> | ||||
|             <div class="pf-c-card__title"> | ||||
|                 ${gettext("Update details")} | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 <ak-form | ||||
|                     successMessage=${gettext("Successfully updated details.")} | ||||
|                     .send=${(data: unknown) => { | ||||
|                         return new CoreApi(DEFAULT_CONFIG).coreUsersUpdate({ | ||||
|                             id: this.user?.pk || 0, | ||||
|                             data: data as User | ||||
|                         }); | ||||
|                     }}> | ||||
|                     <form class="pf-c-form pf-m-horizontal"> | ||||
|                         <paper-input | ||||
|                             name="username" | ||||
|                             ?alwaysFloatLabel=${true} | ||||
|                             label="${gettext("Username")}" | ||||
|                             value=${this.user.username}> | ||||
|                         </paper-input> | ||||
|                         <p class="pf-c-form__helper-text">${gettext("Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.")}</p> | ||||
|                         <paper-input | ||||
|                             name="name" | ||||
|                             ?alwaysFloatLabel=${true} | ||||
|                             label="${gettext("Name")}" | ||||
|                             value=${this.user.name}> | ||||
|                         </paper-input> | ||||
|                         <p class="pf-c-form__helper-text">${gettext("User's display name.")}</p> | ||||
|                         <paper-input | ||||
|                             name="email" | ||||
|                             ?alwaysFloatLabel=${true} | ||||
|                             type="email" | ||||
|                             label="${gettext("Email address")}" | ||||
|                             value=${this.user.email || ""}> | ||||
|                         </paper-input> | ||||
|  | ||||
|                         <div class="pf-c-form__group pf-m-action"> | ||||
|                             <div class="pf-c-form__horizontal-group"> | ||||
|                                 <div class="pf-c-form__actions"> | ||||
|                                     <button class="pf-c-button pf-m-primary"> | ||||
|                                         ${gettext("Update")} | ||||
|                                     </button> | ||||
|                                     <a class="pf-c-button pf-m-danger" | ||||
|                                         href="${FlowURLManager.defaultUnenrollment()}"> | ||||
|                                         ${gettext("Delete account")} | ||||
|                                     </a> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </form> | ||||
|                 </ak-form> | ||||
|             </div> | ||||
|         </div>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -18,8 +18,8 @@ import { DEFAULT_CONFIG } from "../../api/Config"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
| import { ifDefined } from "lit-html/directives/if-defined"; | ||||
| import "../../elements/Tabs"; | ||||
| import "../tokens/UserTokenList"; | ||||
| import "../generic/SiteShell"; | ||||
| import "./UserDetailsPage"; | ||||
| import "./UserTokenList"; | ||||
| import "./settings/UserSettingsAuthenticatorTOTP"; | ||||
| import "./settings/UserSettingsAuthenticatorStatic"; | ||||
| import "./settings/UserSettingsAuthenticatorWebAuthnDevices"; | ||||
| @ -48,13 +48,7 @@ export class UserSettingsPage extends LitElement { | ||||
|                 return html`<ak-user-settings-authenticator-static objectId=${stage.objectUid}> | ||||
|                 </ak-user-settings-authenticator-static>`; | ||||
|             default: | ||||
|                 return html`<div class="pf-u-display-flex pf-u-justify-content-center"> | ||||
|                     <div class="pf-u-w-75"> | ||||
|                         <ak-site-shell url="${ifDefined(stage.component)}"> | ||||
|                             <div slot="body"></div> | ||||
|                         </ak-site-shell> | ||||
|                     </div> | ||||
|                 </div>`; | ||||
|                 return html`<p>${gettext(`Error: unsupported stage settings: ${stage.component}`)}</p>`; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -64,13 +58,7 @@ export class UserSettingsPage extends LitElement { | ||||
|                 return html`<ak-user-settings-source-oauth objectId=${source.objectUid}> | ||||
|                 </ak-user-settings-source-oauth>`; | ||||
|             default: | ||||
|                 return html`<div class="pf-u-display-flex pf-u-justify-content-center"> | ||||
|                     <div class="pf-u-w-75"> | ||||
|                         <ak-site-shell url="${ifDefined(source.component)}"> | ||||
|                             <div slot="body"></div> | ||||
|                         </ak-site-shell> | ||||
|                     </div> | ||||
|                 </div>`; | ||||
|                 return html`<p>${gettext(`Error: unsupported source settings: ${source.component}`)}</p>`; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -88,16 +76,10 @@ export class UserSettingsPage extends LitElement { | ||||
|                 </section> | ||||
|                 <ak-tabs ?vertical="${true}" style="height: 100%;"> | ||||
|                     <section slot="page-1" data-tab-title="${gettext("User details")}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|                         <div class="pf-u-display-flex pf-u-justify-content-center"> | ||||
|                             <div class="pf-u-w-75"> | ||||
|                                 <ak-site-shell url="/-/user/details/"> | ||||
|                                     <div slot="body"></div> | ||||
|                                 </ak-site-shell> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <ak-user-details></ak-user-details> | ||||
|                     </section> | ||||
|                     <section slot="page-2" data-tab-title="${gettext("Tokens")}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|                         <ak-token-user-list></ak-token-user-list> | ||||
|                         <ak-user-token-list></ak-user-token-list> | ||||
|                     </section> | ||||
|                     ${until(new StagesApi(DEFAULT_CONFIG).stagesAllUserSettings({}).then((stages) => { | ||||
|                         return stages.map((stage) => { | ||||
|  | ||||
| @ -12,7 +12,7 @@ import { CoreApi, Token } from "authentik-api"; | ||||
| import { DEFAULT_CONFIG } from "../../api/Config"; | ||||
| import { AdminURLManager } from "../../api/legacy"; | ||||
| 
 | ||||
| @customElement("ak-token-user-list") | ||||
| @customElement("ak-user-token-list") | ||||
| export class UserTokenList extends Table<Token> { | ||||
|     searchEnabled(): boolean { | ||||
|         return true; | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer