web: initial implementation of new forms

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-25 00:02:35 +01:00
parent 6e46124c94
commit fe4791c216
16 changed files with 317 additions and 134 deletions

View File

@ -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}

View File

@ -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>

View File

@ -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
)

View File

@ -14,7 +14,6 @@ urlpatterns = [
name="root-redirect", name="root-redirect",
), ),
# User views # User views
path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"),
path( path(
"-/user/tokens/create/", "-/user/tokens/create/",
user.TokenCreateView.as_view(), user.TokenCreateView.as_view(),

View File

@ -15,39 +15,11 @@ from guardian.mixins import PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from authentik.core.forms.token import UserTokenForm from authentik.core.forms.token import UserTokenForm
from authentik.core.forms.users import UserDetailForm
from authentik.core.models import Token, TokenIntents from authentik.core.models import Token, TokenIntents
from authentik.flows.models import Flow, FlowDesignation from authentik.flows.models import Flow, FlowDesignation
from authentik.lib.views import CreateAssignPermView 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( class TokenCreateView(
SuccessMessageMixin, SuccessMessageMixin,
LoginRequiredMixin, LoginRequiredMixin,

View File

@ -98,7 +98,7 @@ class TestFlowsEnroll(SeleniumTestCase):
wait = WebDriverWait(interface_admin, self.wait_timeout) wait = WebDriverWait(interface_admin, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar"))) 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") user = User.objects.get(username="foo")
self.assertEqual(user.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"))) 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")) self.assert_user(User.objects.get(username="foo"))

View File

@ -160,7 +160,7 @@ class TestSourceOAuth2(SeleniumTestCase):
# Wait until we've logged in # Wait until we've logged in
self.wait_for_url(self.if_admin_url("/library")) 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.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" 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 # Wait until we've logged in
self.wait_for_url(self.if_admin_url("/library")) 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.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo"
@ -359,7 +359,7 @@ class TestSourceOAuth1(SeleniumTestCase):
sleep(2) sleep(2)
# Wait until we've logged in # Wait until we've logged in
self.wait_for_url(self.if_admin_url("/library")) 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.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), self.driver.find_element(By.ID, "id_username").get_attribute("value"),

View File

@ -153,7 +153,7 @@ class TestSourceSAML(SeleniumTestCase):
# Wait until we're logged in # Wait until we're logged in
self.wait_for_url(self.if_admin_url("/library")) 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 # Wait until we've loaded the user info page
self.assertNotEqual( self.assertNotEqual(
@ -233,7 +233,7 @@ class TestSourceSAML(SeleniumTestCase):
# Wait until we're logged in # Wait until we're logged in
self.wait_for_url(self.if_admin_url("/library")) 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 # Wait until we've loaded the user info page
self.assertNotEqual( self.assertNotEqual(
@ -300,7 +300,7 @@ class TestSourceSAML(SeleniumTestCase):
# Wait until we're logged in # Wait until we're logged in
self.wait_for_url(self.if_admin_url("/library")) 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 # Wait until we've loaded the user info page
self.assertNotEqual( self.assertNotEqual(

138
web/package-lock.json generated
View File

@ -133,6 +133,139 @@
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.90.5.tgz", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.90.5.tgz",
"integrity": "sha512-Fe0C8UkzSjtacQ+fHXlFB/LHzrv/c2K4z479C6dboOgkGQE1FyB0wt1NBfxij0D++rhOy04OOYdE+Tr0JSlZKw==" "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": { "@rollup/plugin-typescript": {
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.0.tgz",
@ -490,6 +623,11 @@
"eslint-visitor-keys": "^2.0.0" "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": { "acorn": {
"version": "7.4.1", "version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",

View File

@ -12,6 +12,8 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3", "@fortawesome/fontawesome-free": "^5.15.3",
"@patternfly/patternfly": "^4.90.5", "@patternfly/patternfly": "^4.90.5",
"@polymer/iron-form": "^3.0.1",
"@polymer/paper-input": "^3.2.1",
"@sentry/browser": "^6.2.3", "@sentry/browser": "^6.2.3",
"@sentry/tracing": "^6.2.3", "@sentry/tracing": "^6.2.3",
"@types/chart.js": "^2.9.31", "@types/chart.js": "^2.9.31",

View File

@ -105,6 +105,10 @@ export class AppURLManager {
export class FlowURLManager { export class FlowURLManager {
static defaultUnenrollment(): string {
return "-/default/unenrollment/";
}
static configure(stageUuid: string, rest: string): string { static configure(stageUuid: string, rest: string): string {
return `-/configure/${stageUuid}/${rest}`; return `-/configure/${stageUuid}/${rest}`;
} }

View File

@ -88,6 +88,7 @@ body {
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--ak-accent: #fd4b2d;
--ak-dark-foreground: #fafafa; --ak-dark-foreground: #fafafa;
--ak-dark-foreground-darker: #bebebe; --ak-dark-foreground-darker: #bebebe;
--ak-dark-foreground-link: #5a5cb9; --ak-dark-foreground-link: #5a5cb9;
@ -100,6 +101,12 @@ body {
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker); --pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);
--pf-global--link--Color: var(--ak-dark-foreground-link); --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 */ /* Global page background colour */
.pf-c-page { .pf-c-page {
--pf-c-page--BackgroundColor: var(--ak-dark-background); --pf-c-page--BackgroundColor: var(--ak-dark-background);

View File

@ -8,7 +8,6 @@ import "./elements/buttons/ModalButton";
import "./elements/buttons/SpinnerButton"; import "./elements/buttons/SpinnerButton";
import "./elements/CodeMirror"; import "./elements/CodeMirror";
import "./pages/tokens/UserTokenList";
import "./pages/generic/SiteShell"; import "./pages/generic/SiteShell";
import "./interfaces/AdminInterface"; import "./interfaces/AdminInterface";
import "./elements/messages/MessageContainer"; import "./elements/messages/MessageContainer";

View 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>`;
}
}

View File

@ -18,8 +18,8 @@ import { DEFAULT_CONFIG } from "../../api/Config";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/Tabs"; import "../../elements/Tabs";
import "../tokens/UserTokenList"; import "./UserDetailsPage";
import "../generic/SiteShell"; import "./UserTokenList";
import "./settings/UserSettingsAuthenticatorTOTP"; import "./settings/UserSettingsAuthenticatorTOTP";
import "./settings/UserSettingsAuthenticatorStatic"; import "./settings/UserSettingsAuthenticatorStatic";
import "./settings/UserSettingsAuthenticatorWebAuthnDevices"; import "./settings/UserSettingsAuthenticatorWebAuthnDevices";
@ -48,13 +48,7 @@ export class UserSettingsPage extends LitElement {
return html`<ak-user-settings-authenticator-static objectId=${stage.objectUid}> return html`<ak-user-settings-authenticator-static objectId=${stage.objectUid}>
</ak-user-settings-authenticator-static>`; </ak-user-settings-authenticator-static>`;
default: default:
return html`<div class="pf-u-display-flex pf-u-justify-content-center"> return html`<p>${gettext(`Error: unsupported stage settings: ${stage.component}`)}</p>`;
<div class="pf-u-w-75">
<ak-site-shell url="${ifDefined(stage.component)}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>`;
} }
} }
@ -64,13 +58,7 @@ export class UserSettingsPage extends LitElement {
return html`<ak-user-settings-source-oauth objectId=${source.objectUid}> return html`<ak-user-settings-source-oauth objectId=${source.objectUid}>
</ak-user-settings-source-oauth>`; </ak-user-settings-source-oauth>`;
default: default:
return html`<div class="pf-u-display-flex pf-u-justify-content-center"> return html`<p>${gettext(`Error: unsupported source settings: ${source.component}`)}</p>`;
<div class="pf-u-w-75">
<ak-site-shell url="${ifDefined(source.component)}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>`;
} }
} }
@ -88,16 +76,10 @@ export class UserSettingsPage extends LitElement {
</section> </section>
<ak-tabs ?vertical="${true}" style="height: 100%;"> <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"> <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"> <ak-user-details></ak-user-details>
<div class="pf-u-w-75">
<ak-site-shell url="/-/user/details/">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>
</section> </section>
<section slot="page-2" data-tab-title="${gettext("Tokens")}" class="pf-c-page__main-section pf-m-no-padding-mobile"> <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> </section>
${until(new StagesApi(DEFAULT_CONFIG).stagesAllUserSettings({}).then((stages) => { ${until(new StagesApi(DEFAULT_CONFIG).stagesAllUserSettings({}).then((stages) => {
return stages.map((stage) => { return stages.map((stage) => {

View File

@ -12,7 +12,7 @@ import { CoreApi, Token } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy"; import { AdminURLManager } from "../../api/legacy";
@customElement("ak-token-user-list") @customElement("ak-user-token-list")
export class UserTokenList extends Table<Token> { export class UserTokenList extends Table<Token> {
searchEnabled(): boolean { searchEnabled(): boolean {
return true; return true;