diff --git a/web/src/admin/users/UserListPage.ts b/web/src/admin/users/UserListPage.ts index 7b3e3075c..17eddae14 100644 --- a/web/src/admin/users/UserListPage.ts +++ b/web/src/admin/users/UserListPage.ts @@ -21,10 +21,11 @@ import { getURLParam, updateURLParams } from "@goauthentik/elements/router/Route import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; +import { writeToClipboard } from "@goauthentik/elements/utils/writeToClipboard"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; @@ -40,6 +41,56 @@ import { UserPath, } from "@goauthentik/api"; +export const requestRecoveryLink = (user: User) => + new CoreApi(DEFAULT_CONFIG) + .coreUsersRecoveryRetrieve({ + id: user.pk, + }) + .then((rec) => + writeToClipboard(rec.link).then((wroteToClipboard) => + showMessage({ + level: MessageLevel.success, + message: rec.link, + description: wroteToClipboard + ? msg("A copy of this recovery link has been placed in your clipboard") + : "", + }), + ), + ) + .catch((ex: ResponseError) => + ex.response.json().then(() => + showMessage({ + level: MessageLevel.error, + message: msg( + "The current tenant must have a recovery flow configured to use a recovery link", + ), + }), + ), + ); + +export const renderRecoveryEmailRequest = (user: User) => + html` + ${msg("Send link")} + ${msg("Send recovery link to user")} + + + `; + +const recoveryButtonStyles = css` + #recovery-request-buttons { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 0.375rem; + } + #recovery-request-buttons > *, + #update-password-request .pf-c-button { + margin: 0; + } +`; + @customElement("ak-user-list") export class UserListPage extends TablePage { expandable = true; @@ -74,7 +125,7 @@ export class UserListPage extends TablePage { me?: SessionUser; static get styles(): CSSResult[] { - return super.styles.concat(PFDescriptionList, PFCard, PFAlert); + return [...super.styles, PFDescriptionList, PFCard, PFAlert, recoveryButtonStyles]; } constructor() { @@ -287,8 +338,14 @@ export class UserListPage extends TablePage { ${msg("Recovery")}
-
- +
+ ${msg("Update password")} ${msg("Update password")} { ? html` { - return new CoreApi(DEFAULT_CONFIG) - .coreUsersRecoveryRetrieve({ - id: item.pk, - }) - .then((rec) => { - showMessage({ - level: MessageLevel.success, - message: msg( - "Successfully generated recovery link", - ), - description: rec.link, - }); - }) - .catch((ex: ResponseError) => { - ex.response.json().then(() => { - showMessage({ - level: MessageLevel.error, - message: msg( - "No recovery flow is configured.", - ), - }); - }); - }); - }} + .apiRequest=${() => requestRecoveryLink(item)} > - ${msg("Copy recovery link")} + ${msg("Create recovery link")} ${item.email - ? html` - - ${msg("Send link")} - - - ${msg("Send recovery link to user")} - - - - - ` + ? renderRecoveryEmailRequest(item) : html`${msg( "Recovery link cannot be emailed, user has no email address saved.", diff --git a/web/src/admin/users/UserViewPage.ts b/web/src/admin/users/UserViewPage.ts index c97a20298..64a917c9d 100644 --- a/web/src/admin/users/UserViewPage.ts +++ b/web/src/admin/users/UserViewPage.ts @@ -5,11 +5,14 @@ import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserPasswordForm"; import "@goauthentik/app/admin/users/UserAssignedGlobalPermissionsTable"; import "@goauthentik/app/admin/users/UserAssignedObjectPermissionsTable"; +import { + renderRecoveryEmailRequest, + requestRecoveryLink, +} from "@goauthentik/app/admin/users/UserListPage"; import { me } from "@goauthentik/app/common/users"; import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; -import { MessageLevel } from "@goauthentik/common/messages"; import "@goauthentik/components/events/ObjectChangelog"; import "@goauthentik/components/events/UserEvents"; import { AKElement, rootInterface } from "@goauthentik/elements/Base"; @@ -21,13 +24,12 @@ import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/ModalForm"; -import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import "@goauthentik/elements/oauth/UserRefreshList"; import "@goauthentik/elements/user/SessionList"; import "@goauthentik/elements/user/UserConsentList"; import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html } from "lit"; +import { css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -72,7 +74,7 @@ export class UserViewPage extends AKElement { @state() me?: SessionUser; - static get styles(): CSSResult[] { + static get styles() { return [ PFBase, PFPage, @@ -84,12 +86,24 @@ export class UserViewPage extends AKElement { PFDescriptionList, PFSizing, css` - .pf-c-description-list__description ak-action-button { - margin-right: 6px; - margin-bottom: 6px; - } .ak-button-collection { - max-width: 12em; + display: flex; + flex-direction: column; + gap: 0.375rem; + max-width: 12rem; + } + .ak-button-collection > * { + flex: 1 0 100%; + } + #reset-password-button { + margin-right: 0; + } + + #ak-email-recovery-request, + #update-password-request .pf-c-button, + #ak-email-recovery-request .pf-c-button { + margin: 0; + width: 100%; } `, ]; @@ -103,7 +117,7 @@ export class UserViewPage extends AKElement { }); } - render(): TemplateResult { + render() { return html`${msg("User Info")}
@@ -129,7 +147,7 @@ export class UserViewPage extends AKElement { ${msg("Username")}
-
${this.user.username}
+
${user.username}
@@ -137,7 +155,7 @@ export class UserViewPage extends AKElement { ${msg("Name")}
-
${this.user.name}
+
${user.name}
@@ -145,7 +163,7 @@ export class UserViewPage extends AKElement { ${msg("Email")}
-
${this.user.email || "-"}
+
${user.email || "-"}
@@ -154,7 +172,7 @@ export class UserViewPage extends AKElement {
- ${this.user.lastLogin?.toLocaleString()} + ${user.lastLogin?.toLocaleString()}
@@ -165,7 +183,7 @@ export class UserViewPage extends AKElement {
@@ -177,7 +195,7 @@ export class UserViewPage extends AKElement {
@@ -191,7 +209,7 @@ export class UserViewPage extends AKElement { ${msg("Update")} ${msg("Update User")} - + @@ -238,7 +254,7 @@ export class UserViewPage extends AKElement { .apiRequest=${() => { return new CoreApi(DEFAULT_CONFIG) .coreUsersImpersonateCreate({ - id: this.user?.pk || 0, + id: user.pk, }) .then(() => { window.location.href = "/"; @@ -255,7 +271,7 @@ export class UserViewPage extends AKElement { ` - : html``} + : nothing} @@ -265,12 +281,12 @@ export class UserViewPage extends AKElement {
- + ${msg("Update password")} ${msg("Update password")}
@@ -329,9 +323,9 @@ export class UserViewPage extends AKElement { `; } - renderBody(): TemplateResult { + renderBody() { if (!this.user) { - return html``; + return nothing; } return html`