From 02bd699917002d1f12c940c4bdafbe4945d07062 Mon Sep 17 00:00:00 2001 From: "Jens L." Date: Sat, 21 Dec 2024 22:12:47 +0100 Subject: [PATCH] web/admin: Refine navigation (#12441) * fix spacing if there's no icon in page header Signed-off-by: Jens Langhammer * add a very slight bar Signed-off-by: Jens Langhammer * rework navigation to be similar between interfaces Signed-off-by: Jens Langhammer * fix subpath and rendering Signed-off-by: Jens Langhammer * fix display Signed-off-by: Jens Langhammer * add version to sidebar Signed-off-by: Jens Langhammer * make page header sticky? Signed-off-by: Jens Langhammer * unrelated: hide session in system api Signed-off-by: Jens Langhammer * unrelated: add unidecode for policies Signed-off-by: Jens Langhammer #5859 --------- Signed-off-by: Jens Langhammer --- authentik/admin/api/system.py | 10 +- poetry.lock | 13 +- pyproject.toml | 1 + .../admin/admin-overview/AdminOverviewPage.ts | 2 +- web/src/common/styles/theme-dark.css | 4 +- web/src/common/ui/config.ts | 1 + web/src/components/ak-nav-buttons.ts | 210 ++++++++++++++++++ web/src/elements/PageHeader.ts | 119 +++++----- web/src/elements/sidebar/Sidebar.ts | 4 +- web/src/elements/sidebar/SidebarBrand.ts | 3 + web/src/elements/sidebar/SidebarUser.ts | 70 ------ web/src/elements/sidebar/SidebarVersion.ts | 61 +++++ web/src/user/UserInterface.ts | 168 ++------------ 13 files changed, 382 insertions(+), 284 deletions(-) create mode 100644 web/src/components/ak-nav-buttons.ts delete mode 100644 web/src/elements/sidebar/SidebarUser.ts create mode 100644 web/src/elements/sidebar/SidebarVersion.ts diff --git a/authentik/admin/api/system.py b/authentik/admin/api/system.py index 1e119e5fbc..ba949f835f 100644 --- a/authentik/admin/api/system.py +++ b/authentik/admin/api/system.py @@ -7,7 +7,9 @@ from sys import version as python_version from typing import TypedDict from cryptography.hazmat.backends.openssl.backend import backend +from django.conf import settings from django.utils.timezone import now +from django.views.debug import SafeExceptionReporterFilter from drf_spectacular.utils import extend_schema from rest_framework.fields import SerializerMethodField from rest_framework.request import Request @@ -52,10 +54,16 @@ class SystemInfoSerializer(PassiveSerializer): def get_http_headers(self, request: Request) -> dict[str, str]: """Get HTTP Request headers""" headers = {} + raw_session = request._request.COOKIES.get(settings.SESSION_COOKIE_NAME) for key, value in request.META.items(): if not isinstance(value, str): continue - headers[key] = value + actual_value = value + if raw_session in actual_value: + actual_value = actual_value.replace( + raw_session, SafeExceptionReporterFilter.cleansed_substitute + ) + headers[key] = actual_value return headers def get_http_host(self, request: Request) -> str: diff --git a/poetry.lock b/poetry.lock index b88e4fbfcf..d417e8ac7c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5225,6 +5225,17 @@ files = [ [package.dependencies] ua-parser = "*" +[[package]] +name = "unidecode" +version = "1.3.8" +description = "ASCII transliterations of Unicode text" +optional = false +python-versions = ">=3.5" +files = [ + {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, + {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, +] + [[package]] name = "uritemplate" version = "4.1.1" @@ -5934,4 +5945,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "38089ad25be7638c118f4b503ad2f8495c941667f5485efe60b2bbdb14d6f44c" +content-hash = "527289cd983966592bc04f308917604dc5d4a4116d8071d5e3a0bf63d216a086" diff --git a/pyproject.toml b/pyproject.toml index c1d3507fae..30010759b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,6 +143,7 @@ swagger-spec-validator = "*" tenant-schemas-celery = "*" twilio = "*" ua-parser = "*" +unidecode = "*" # Pinned because of botocore https://github.com/orgs/python-poetry/discussions/7937 urllib3 = { extras = ["secure"], version = "<3" } uvicorn = { extras = ["standard"], version = "*" } diff --git a/web/src/admin/admin-overview/AdminOverviewPage.ts b/web/src/admin/admin-overview/AdminOverviewPage.ts index 005382bd1a..d7a9e1737c 100644 --- a/web/src/admin/admin-overview/AdminOverviewPage.ts +++ b/web/src/admin/admin-overview/AdminOverviewPage.ts @@ -96,7 +96,7 @@ export class AdminOverviewPage extends AdminOverviewBase { render(): TemplateResult { const name = this.user?.user.name ?? this.user?.user.username; - return html` + return html` ${msg(str`Welcome, ${name || ""}.`)}
diff --git a/web/src/common/styles/theme-dark.css b/web/src/common/styles/theme-dark.css index ba3d26a882..8023c7d1e4 100644 --- a/web/src/common/styles/theme-dark.css +++ b/web/src/common/styles/theme-dark.css @@ -3,6 +3,7 @@ --ak-global--Color--100: var(--ak-dark-foreground) !important; --pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker); --pf-global--link--Color: var(--ak-dark-foreground-link) !important; + --pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important; } body { background-color: var(--ak-dark-background) !important; @@ -31,9 +32,6 @@ body { .notification-trigger { background-color: transparent !important; } -.pf-c-page__main-section.pf-m-light { - background-color: transparent; -} .pf-c-content { color: var(--ak-dark-foreground); } diff --git a/web/src/common/ui/config.ts b/web/src/common/ui/config.ts index 1007af1bb7..ccf5045302 100644 --- a/web/src/common/ui/config.ts +++ b/web/src/common/ui/config.ts @@ -7,6 +7,7 @@ export enum UserDisplay { username = "username", name = "name", email = "email", + none = "none", } export enum LayoutType { diff --git a/web/src/components/ak-nav-buttons.ts b/web/src/components/ak-nav-buttons.ts new file mode 100644 index 0000000000..a74bca3d73 --- /dev/null +++ b/web/src/components/ak-nav-buttons.ts @@ -0,0 +1,210 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { + EVENT_API_DRAWER_TOGGLE, + EVENT_NOTIFICATION_DRAWER_TOGGLE, +} from "@goauthentik/common/constants"; +import { globalAK } from "@goauthentik/common/global"; +import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config"; +import { me } from "@goauthentik/common/users"; +import { AKElement } from "@goauthentik/elements/Base"; +import { match } from "ts-pattern"; + +import { msg } from "@lit/localize"; +import { css, html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; +import PFBrand from "@patternfly/patternfly/components/Brand/brand.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; +import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css"; +import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; + +import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api"; + +@customElement("ak-nav-buttons") +export class NavigationButtons extends AKElement { + @property({ type: Object }) + uiConfig?: UIConfig; + + @property({ type: Object }) + me?: SessionUser; + + @property({ type: Boolean, reflect: true }) + notificationDrawerOpen = false; + + @property({ type: Boolean, reflect: true }) + apiDrawerOpen = false; + + @property({ type: Number }) + notificationsCount = 0; + + static get styles() { + return [ + PFBase, + PFDisplay, + PFBrand, + PFPage, + PFAvatar, + PFButton, + PFDrawer, + PFDropdown, + PFNotificationBadge, + css` + .pf-c-page__header-tools { + display: flex; + } + `, + ]; + } + + async firstUpdated() { + this.me = await me(); + const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({ + seen: false, + ordering: "-created", + pageSize: 1, + user: this.me.user.pk, + }); + this.notificationsCount = notifications.pagination.count; + this.uiConfig = await uiConfig(); + } + + renderApiDrawerTrigger() { + if (!this.uiConfig?.enabledFeatures.apiDrawer) { + return nothing; + } + + const onClick = (ev: Event) => { + ev.stopPropagation(); + this.dispatchEvent( + new Event(EVENT_API_DRAWER_TOGGLE, { bubbles: true, composed: true }), + ); + }; + + return html`
+ +
`; + } + + renderNotificationDrawerTrigger() { + if (!this.uiConfig?.enabledFeatures.notificationDrawer) { + return nothing; + } + + const onClick = (ev: Event) => { + ev.stopPropagation(); + this.dispatchEvent( + new Event(EVENT_NOTIFICATION_DRAWER_TOGGLE, { bubbles: true, composed: true }), + ); + }; + + return html`
+ +
`; + } + + renderSettings() { + if (!this.uiConfig?.enabledFeatures.settings) { + return nothing; + } + + return html``; + } + + renderImpersonation() { + if (!this.me?.original) { + return nothing; + } + + const onClick = () => { + return new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => { + window.location.reload(); + }); + }; + + return html`  +
+
+ + ${msg("Stop impersonation")} + +
+
`; + } + + get userDisplayName() { + return match(this.uiConfig?.navbar.userDisplay) + .with(UserDisplay.username, () => this.me?.user.username) + .with(UserDisplay.name, () => this.me?.user.name) + .with(UserDisplay.email, () => this.me?.user.email || "") + .with(UserDisplay.none, () => "") + .otherwise(() => this.me?.user.username); + } + + render() { + return html`
+
+ ${this.renderApiDrawerTrigger()} + + ${this.renderNotificationDrawerTrigger()} + + ${this.renderSettings()} + + +
+ ${this.renderImpersonation()} + ${this.userDisplayName != "" + ? html`
+
+ ${this.userDisplayName} +
+
` + : nothing} + ${msg( +
`; + } +} diff --git a/web/src/elements/PageHeader.ts b/web/src/elements/PageHeader.ts index 5d1f96249c..1e6bef8228 100644 --- a/web/src/elements/PageHeader.ts +++ b/web/src/elements/PageHeader.ts @@ -1,27 +1,30 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { - EVENT_API_DRAWER_TOGGLE, - EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_SIDEBAR_TOGGLE, EVENT_WS_MESSAGE, TITLE_DEFAULT, } from "@goauthentik/common/constants"; import { currentInterface } from "@goauthentik/common/sentry"; +import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config"; import { me } from "@goauthentik/common/users"; +import "@goauthentik/components/ak-nav-buttons"; import { AKElement } from "@goauthentik/elements/Base"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { CSSResult, TemplateResult, css, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css"; +import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { EventsApi } from "@goauthentik/api"; +import { EventsApi, SessionUser } from "@goauthentik/api"; @customElement("ak-page-header") export class PageHeader extends WithBrandConfig(AKElement) { @@ -31,8 +34,8 @@ export class PageHeader extends WithBrandConfig(AKElement) { @property({ type: Boolean }) iconImage = false; - @property({ type: Boolean }) - hasNotifications = false; + @state() + notificationsCount = 0; @property() header = ""; @@ -40,21 +43,39 @@ export class PageHeader extends WithBrandConfig(AKElement) { @property() description?: string; + @property({ type: Boolean }) + hasIcon = true; + + @state() + me?: SessionUser; + + @state() + uiConfig!: UIConfig; + static get styles(): CSSResult[] { return [ PFBase, PFButton, PFPage, + PFNotificationBadge, PFContent, + PFAvatar, + PFDropdown, css` + :host { + position: sticky; + top: 0; + z-index: 100; + } .bar { + border-bottom: var(--pf-global--BorderWidth--sm); + border-bottom-style: solid; + border-bottom-color: var(--pf-global--BorderColor--100); display: flex; flex-direction: row; min-height: 114px; - } - .pf-c-button.pf-m-plain { - background-color: transparent; - border-radius: 0px; + max-height: 114px; + background-color: var(--pf-c-page--BackgroundColor); } .pf-c-page__main-section.pf-m-light { background-color: transparent; @@ -81,6 +102,15 @@ export class PageHeader extends WithBrandConfig(AKElement) { flex-direction: row; align-items: center !important; } + .pf-c-page__header-tools { + flex-shrink: 0; + } + .pf-c-page__header-tools-group { + height: 100%; + } + :host([theme="dark"]) .pf-c-page__header-tools { + color: var(--ak-dark-foreground) !important; + } `, ]; } @@ -92,19 +122,17 @@ export class PageHeader extends WithBrandConfig(AKElement) { }); } - firstUpdated(): void { - me().then((user) => { - new EventsApi(DEFAULT_CONFIG) - .eventsNotificationsList({ - seen: false, - ordering: "-created", - pageSize: 1, - user: user.user.pk, - }) - .then((r) => { - this.hasNotifications = r.pagination.count > 0; - }); + async firstUpdated() { + this.me = await me(); + this.uiConfig = await uiConfig(); + this.uiConfig.navbar.userDisplay = UserDisplay.none; + const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({ + seen: false, + ordering: "-created", + pageSize: 1, + user: this.me.user.pk, }); + this.notificationsCount = notifications.pagination.count; } setTitle(header?: string) { @@ -126,7 +154,7 @@ export class PageHeader extends WithBrandConfig(AKElement) { this.setTitle(this.header); } - renderIcon(): TemplateResult { + renderIcon() { if (this.icon) { if (this.iconImage && !this.icon.startsWith("fa://")) { return html`page icon`; @@ -134,7 +162,7 @@ export class PageHeader extends WithBrandConfig(AKElement) { const icon = this.icon.replaceAll("fa://", "fa "); return html``; } - return html``; + return nothing; } render(): TemplateResult { @@ -155,44 +183,19 @@ export class PageHeader extends WithBrandConfig(AKElement) {

- ${this.renderIcon()}  + ${this.hasIcon + ? html`${this.renderIcon()} ` + : nothing} ${this.header}

${this.description ? html`

${this.description}

` : html``}
- - +
+
+ +
+
`; } } diff --git a/web/src/elements/sidebar/Sidebar.ts b/web/src/elements/sidebar/Sidebar.ts index 76575acefb..2fcfe3b6b5 100644 --- a/web/src/elements/sidebar/Sidebar.ts +++ b/web/src/elements/sidebar/Sidebar.ts @@ -1,6 +1,6 @@ import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/sidebar/SidebarBrand"; -import "@goauthentik/elements/sidebar/SidebarUser"; +import "@goauthentik/elements/sidebar/SidebarVersion"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -74,7 +74,7 @@ export class Sidebar extends AKElement {
- + `; } } diff --git a/web/src/elements/sidebar/SidebarBrand.ts b/web/src/elements/sidebar/SidebarBrand.ts index eb36024199..d5df0eefdb 100644 --- a/web/src/elements/sidebar/SidebarBrand.ts +++ b/web/src/elements/sidebar/SidebarBrand.ts @@ -42,6 +42,9 @@ export class SidebarBrand extends WithBrandConfig(AKElement) { 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; diff --git a/web/src/elements/sidebar/SidebarUser.ts b/web/src/elements/sidebar/SidebarUser.ts deleted file mode 100644 index 3f39263129..0000000000 --- a/web/src/elements/sidebar/SidebarUser.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { globalAK } from "@goauthentik/common/global"; -import { me } from "@goauthentik/common/users"; -import { AKElement } from "@goauthentik/elements/Base"; - -import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement } from "lit/decorators.js"; -import { ifDefined } from "lit/directives/if-defined.js"; -import { until } from "lit/directives/until.js"; - -import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; -import PFNav from "@patternfly/patternfly/components/Nav/nav.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -@customElement("ak-sidebar-user") -export class SidebarUser extends AKElement { - static get styles(): CSSResult[] { - return [ - PFBase, - PFNav, - PFAvatar, - css` - :host { - display: flex; - width: 100%; - flex-direction: row; - justify-content: space-between; - } - .pf-c-nav__link { - align-items: center; - display: flex; - justify-content: center; - } - `, - ]; - } - - render(): TemplateResult { - return html` - - ${until( - me().then((u) => { - return html``; - }), - html``, - )} - - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ak-sidebar-user": SidebarUser; - } -} diff --git a/web/src/elements/sidebar/SidebarVersion.ts b/web/src/elements/sidebar/SidebarVersion.ts new file mode 100644 index 0000000000..2fa402757c --- /dev/null +++ b/web/src/elements/sidebar/SidebarVersion.ts @@ -0,0 +1,61 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { globalAK } from "@goauthentik/common/global"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; + +import { msg, str } from "@lit/localize"; +import { CSSResult, TemplateResult, css, html } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; +import PFNav from "@patternfly/patternfly/components/Nav/nav.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { AdminApi, LicenseSummaryStatusEnum, Version } from "@goauthentik/api"; + +@customElement("ak-sidebar-version") +export class SidebarVersion extends WithLicenseSummary(AKElement) { + static get styles(): CSSResult[] { + return [ + PFBase, + PFNav, + PFAvatar, + css` + :host { + display: flex; + width: 100%; + flex-direction: column; + justify-content: space-between; + padding: 1rem !important; + } + p { + text-align: center; + width: 100%; + font-size: var(--pf-global--FontSize--xs); + } + `, + ]; + } + + @state() + version?: Version; + + async firstUpdated() { + this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve(); + } + + render(): TemplateResult { + let product = globalAK().brand.brandingTitle; + if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) { + product += ` ${msg("Enterprise")}`; + } + return html`

${product}

+

${msg(str`Version ${this.version?.versionCurrent}`)}

`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ak-sidebar-version": SidebarVersion; + } +} diff --git a/web/src/user/UserInterface.ts b/web/src/user/UserInterface.ts index dca7fcb296..ed3f2ab56a 100644 --- a/web/src/user/UserInterface.ts +++ b/web/src/user/UserInterface.ts @@ -4,11 +4,11 @@ import { EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_WS_MESSAGE, } from "@goauthentik/common/constants"; -import { globalAK } from "@goauthentik/common/global"; import { configureSentry } from "@goauthentik/common/sentry"; -import { UIConfig, UserDisplay } from "@goauthentik/common/ui/config"; +import { UIConfig } from "@goauthentik/common/ui/config"; import { me } from "@goauthentik/common/users"; import { WebsocketClient } from "@goauthentik/common/ws"; +import "@goauthentik/components/ak-nav-buttons"; import { AKElement } from "@goauthentik/elements/Base"; import { EnterpriseAwareInterface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/ak-locale-context"; @@ -25,7 +25,6 @@ import "@goauthentik/elements/sidebar/SidebarItem"; import { themeImage } from "@goauthentik/elements/utils/images"; import { ROUTES } from "@goauthentik/user/Routes"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; -import { match } from "ts-pattern"; import { msg } from "@lit/localize"; import { css, html, nothing } from "lit"; @@ -41,7 +40,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; -import { CoreApi, CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api"; +import { CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api"; const customStyles = css` .pf-c-page__main, @@ -152,14 +151,6 @@ class UserInterfacePresentation extends AKElement { @property({ type: Object }) brand!: CurrentBrand; - get userDisplayName() { - return match(this.uiConfig.navbar.userDisplay) - .with(UserDisplay.username, () => this.me.user.username) - .with(UserDisplay.name, () => this.me.user.name) - .with(UserDisplay.email, () => this.me.user.email || "") - .otherwise(() => this.me.user.username); - } - get canAccessAdmin() { return ( this.me.user.isSuperuser || @@ -172,6 +163,19 @@ class UserInterfacePresentation extends AKElement { return Boolean(this.uiConfig && this.me && this.brand); } + renderAdminInterfaceLink() { + if (!this.canAccessAdmin) { + return nothing; + } + + return html` + ${msg("Admin interface")} + `; + } render() { // The `!` in the field definitions above only re-assure typescript and eslint that the // values *should* be available, not that they *are*. Thus this contract check; it asserts @@ -181,7 +185,7 @@ class UserInterfacePresentation extends AKElement { throw new Error("ak-interface-user-presentation misused; no valid values passed"); } - return html` + return html`
@@ -199,39 +203,9 @@ class UserInterfacePresentation extends AKElement { />
-
-
- ${this.renderApiDrawerTrigger()} - - ${this.renderNotificationDrawerTrigger()} - - ${this.renderSettings()} - - ${this.renderAdminInterfaceLink()} -
- ${this.renderImpersonation()} -
-
- ${this.userDisplayName} -
-
- ${msg( -
+ ${this.renderAdminInterfaceLink()}
`; } - - renderApiDrawerTrigger() { - if (!this.uiConfig.enabledFeatures.apiDrawer) { - return nothing; - } - - const onClick = (ev: Event) => { - ev.stopPropagation(); - this.dispatchEvent( - new Event(EVENT_API_DRAWER_TOGGLE, { bubbles: true, composed: true }), - ); - }; - - return html`
- -
`; - } - - renderNotificationDrawerTrigger() { - if (!this.uiConfig.enabledFeatures.notificationDrawer) { - return nothing; - } - - const onClick = (ev: Event) => { - ev.stopPropagation(); - this.dispatchEvent( - new Event(EVENT_NOTIFICATION_DRAWER_TOGGLE, { bubbles: true, composed: true }), - ); - }; - - return html`
- -
`; - } - - renderSettings() { - if (!this.uiConfig.enabledFeatures.settings) { - return nothing; - } - - return html` `; - } - - renderAdminInterfaceLink() { - if (!this.canAccessAdmin) { - return nothing; - } - - return html` - ${msg("Admin interface")} - `; - } - - renderImpersonation() { - if (!this.me.original) { - return nothing; - } - - const onClick = () => { - return new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => { - window.location.reload(); - }); - }; - - return html`  -
-
- - ${msg("Stop impersonation")} - -
-
`; - } } // ___ _