web: clean up UserInterface in prep for OAuth and Silo Projects (#8278)
* This was pretty quick. While looking at the Oauth stuff, changes made to the UserInterface triggered the "harder" eslint pass, which said "UserInterface exceeds permitted complexity (9)." I couldn't disagree; it had lots of conditionals. This commit: - Changes no functionality; it's just cleanup. - Breaks UserInterface into business and presentation layers - The presentation layer: - Further breaks the presentation layer into a frame and conditional components. Each conditional is now a simple guard condition. - Taps into the event listener set-up for toggles, eliminating their local scope/window duplication - Extracts in-line complex expressions into isolated and scope functions to name them and make them easier to find and read. - Extracts the custom CSS into its own named variable, again, making it easier to find and read. - The business layer: - Builds the window-level event listener at connection, and disconnects them correctly, allowing this whole interface to be used in a SPA. - Asserts a reliable contract at the presentation layer; there should be no question "Session" and "UIConfig" are available before rendering. - Renames `firstUpdated` to `fetchConfigurationDetails`, and calls it in the constructor. There ought to be no circumstances where this object is constructed outside a working environment; no sense in waiting until you've done a `render() { nothing }` pass to fetch details. Oddities: There are a pair of `<!-- -->` HTML comments in the framing `render()`; those are there just to stop prettier from slamming a string of conditional renders all into one line, making them harder to read. * Adding a small experiment: Typescript pattern matching. * A few renames as requested by @BeryJu
This commit is contained in:
3668
web/package-lock.json
generated
3668
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -64,6 +64,7 @@
|
|||||||
"mermaid": "^10.8.0",
|
"mermaid": "^10.8.0",
|
||||||
"rapidoc": "^9.3.4",
|
"rapidoc": "^9.3.4",
|
||||||
"style-mod": "^4.1.2",
|
"style-mod": "^4.1.2",
|
||||||
|
"ts-pattern": "^5.0.6",
|
||||||
"webcomponent-qr-code": "^1.2.0",
|
"webcomponent-qr-code": "^1.2.0",
|
||||||
"yaml": "^2.4.0"
|
"yaml": "^2.4.0"
|
||||||
},
|
},
|
||||||
|
@ -5,10 +5,10 @@ import {
|
|||||||
EVENT_WS_MESSAGE,
|
EVENT_WS_MESSAGE,
|
||||||
} from "@goauthentik/common/constants";
|
} from "@goauthentik/common/constants";
|
||||||
import { configureSentry } from "@goauthentik/common/sentry";
|
import { configureSentry } from "@goauthentik/common/sentry";
|
||||||
import { UserDisplay } from "@goauthentik/common/ui/config";
|
import { UIConfig, UserDisplay } from "@goauthentik/common/ui/config";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { first } from "@goauthentik/common/utils";
|
|
||||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||||
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { EnterpriseAwareInterface } from "@goauthentik/elements/Interface";
|
import { EnterpriseAwareInterface } from "@goauthentik/elements/Interface";
|
||||||
import "@goauthentik/elements/ak-locale-context";
|
import "@goauthentik/elements/ak-locale-context";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
@ -23,9 +23,10 @@ import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
|||||||
import "@goauthentik/elements/sidebar/SidebarItem";
|
import "@goauthentik/elements/sidebar/SidebarItem";
|
||||||
import { ROUTES } from "@goauthentik/user/Routes";
|
import { ROUTES } from "@goauthentik/user/Routes";
|
||||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||||
|
import { match } from "ts-pattern";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } 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 { 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";
|
||||||
@ -38,25 +39,86 @@ 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 PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
|
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
|
||||||
|
|
||||||
import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api";
|
import { CoreApi, CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-interface-user")
|
const customStyles = css`
|
||||||
export class UserInterface extends EnterpriseAwareInterface {
|
.pf-c-page__main,
|
||||||
@property({ type: Boolean })
|
.pf-c-drawer__content,
|
||||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
.pf-c-page__drawer {
|
||||||
|
z-index: auto !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
.pf-c-page__header {
|
||||||
|
background-color: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
:host([theme="dark"]) .pf-c-page__header {
|
||||||
|
color: var(--ak-dark-foreground) !important;
|
||||||
|
}
|
||||||
|
:host([theme="light"]) .pf-c-page__header-tools-item .fas,
|
||||||
|
:host([theme="light"]) .pf-c-notification-badge__count,
|
||||||
|
:host([theme="light"]) .pf-c-page__header-tools-group .pf-c-button {
|
||||||
|
color: var(--ak-global--Color--100) !important;
|
||||||
|
}
|
||||||
|
.pf-c-page {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.display-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.pf-c-brand {
|
||||||
|
min-height: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
.has-notifications {
|
||||||
|
color: #2b9af3;
|
||||||
|
}
|
||||||
|
.background-wrapper {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: -1;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--pf-c-page--BackgroundColor) !important;
|
||||||
|
}
|
||||||
|
.background-default-slant {
|
||||||
|
background-color: white; /*var(--ak-accent);*/
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 5vw));
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
:host([theme="dark"]) .background-default-slant {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
ak-locale-context {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.pf-c-drawer__main {
|
||||||
|
min-height: calc(100vh - 76px);
|
||||||
|
max-height: calc(100vh - 76px);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
// ___ _ _ _
|
||||||
apiDrawerOpen = getURLParam("apiDrawerOpen", false);
|
// | _ \_ _ ___ ___ ___ _ _| |_ __ _| |_(_)___ _ _
|
||||||
|
// | _/ '_/ -_|_-</ -_) ' \ _/ _` | _| / _ \ ' \
|
||||||
|
// |_| |_| \___/__/\___|_||_\__\__,_|\__|_\___/_||_|
|
||||||
|
//
|
||||||
|
|
||||||
ws: WebsocketClient;
|
// Despite the length of the render() method and its accessories, this top-level Interface does
|
||||||
|
// surprisingly little. It has been broken into two parts: the business logic at the bottom, and the
|
||||||
|
// rendering code at the top, which is wholly independent of APIs and Interfaces.
|
||||||
|
|
||||||
@property({ type: Number })
|
// Because this is not exported, and because it's invoked as a web component, neither TSC or ESLint
|
||||||
notificationsCount = 0;
|
// trusts that we actually used it. Hence the double ignore below:
|
||||||
|
|
||||||
@state()
|
@customElement("ak-interface-user-presentation")
|
||||||
me?: SessionUser;
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
static get styles(): CSSResult[] {
|
class UserInterfacePresentation extends AKElement {
|
||||||
|
static get styles() {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
PFDisplay,
|
PFDisplay,
|
||||||
@ -67,122 +129,57 @@ export class UserInterface extends EnterpriseAwareInterface {
|
|||||||
PFDrawer,
|
PFDrawer,
|
||||||
PFDropdown,
|
PFDropdown,
|
||||||
PFNotificationBadge,
|
PFNotificationBadge,
|
||||||
css`
|
customStyles,
|
||||||
.pf-c-page__main,
|
|
||||||
.pf-c-drawer__content,
|
|
||||||
.pf-c-page__drawer {
|
|
||||||
z-index: auto !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
.pf-c-page__header {
|
|
||||||
background-color: transparent !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
:host([theme="dark"]) .pf-c-page__header {
|
|
||||||
color: var(--ak-dark-foreground) !important;
|
|
||||||
}
|
|
||||||
:host([theme="light"]) .pf-c-page__header-tools-item .fas,
|
|
||||||
:host([theme="light"]) .pf-c-notification-badge__count,
|
|
||||||
:host([theme="light"]) .pf-c-page__header-tools-group .pf-c-button {
|
|
||||||
color: var(--ak-global--Color--100) !important;
|
|
||||||
}
|
|
||||||
.pf-c-page {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.display-none {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.pf-c-brand {
|
|
||||||
min-height: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
.has-notifications {
|
|
||||||
color: #2b9af3;
|
|
||||||
}
|
|
||||||
.background-wrapper {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
position: fixed;
|
|
||||||
z-index: -1;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: var(--pf-c-page--BackgroundColor) !important;
|
|
||||||
}
|
|
||||||
.background-default-slant {
|
|
||||||
background-color: white; /*var(--ak-accent);*/
|
|
||||||
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 5vw));
|
|
||||||
height: 50vh;
|
|
||||||
}
|
|
||||||
:host([theme="dark"]) .background-default-slant {
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
ak-locale-context {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.pf-c-drawer__main {
|
|
||||||
min-height: calc(100vh - 76px);
|
|
||||||
max-height: calc(100vh - 76px);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
@property({ type: Object })
|
||||||
super();
|
uiConfig!: UIConfig;
|
||||||
this.ws = new WebsocketClient();
|
|
||||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
@property({ type: Object })
|
||||||
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
me!: SessionUser;
|
||||||
updateURLParams({
|
|
||||||
notificationDrawerOpen: this.notificationDrawerOpen,
|
@property({ type: Boolean, reflect: true })
|
||||||
});
|
notificationDrawerOpen = false;
|
||||||
});
|
|
||||||
window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => {
|
@property({ type: Boolean, reflect: true })
|
||||||
this.apiDrawerOpen = !this.apiDrawerOpen;
|
apiDrawerOpen = false;
|
||||||
updateURLParams({
|
|
||||||
apiDrawerOpen: this.apiDrawerOpen,
|
@property({ type: Number })
|
||||||
});
|
notificationsCount = 0;
|
||||||
});
|
|
||||||
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
@property({ type: Object })
|
||||||
this.firstUpdated();
|
brand!: CurrentBrand;
|
||||||
});
|
|
||||||
configureSentry(true);
|
get userDisplayName() {
|
||||||
|
return match<UserDisplay, string>(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
get canAccessAdmin() {
|
||||||
this.me = await me();
|
return (
|
||||||
const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
|
|
||||||
seen: false,
|
|
||||||
ordering: "-created",
|
|
||||||
pageSize: 1,
|
|
||||||
user: this.me.user.pk,
|
|
||||||
});
|
|
||||||
this.notificationsCount = notifications.pagination.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): TemplateResult {
|
|
||||||
if (!this.uiConfig || !this.me) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
let userDisplay = "";
|
|
||||||
switch (this.uiConfig.navbar.userDisplay) {
|
|
||||||
case UserDisplay.username:
|
|
||||||
userDisplay = this.me.user.username;
|
|
||||||
break;
|
|
||||||
case UserDisplay.name:
|
|
||||||
userDisplay = this.me.user.name;
|
|
||||||
break;
|
|
||||||
case UserDisplay.email:
|
|
||||||
userDisplay = this.me.user.email || "";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
userDisplay = this.me.user.username;
|
|
||||||
}
|
|
||||||
const canAccessAdmin =
|
|
||||||
this.me.user.isSuperuser ||
|
this.me.user.isSuperuser ||
|
||||||
// TODO: somehow add `access_admin_interface` to the API schema
|
// TODO: somehow add `access_admin_interface` to the API schema
|
||||||
this.me.user.systemPermissions.includes("access_admin_interface");
|
this.me.user.systemPermissions.includes("access_admin_interface")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFullyConfigured() {
|
||||||
|
return !!(this.uiConfig && this.me && this.brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// that the contract we promised is being honored, and the rest of the code that depends on
|
||||||
|
// `!` being truthful is not being lied to.
|
||||||
|
if (!this.isFullyConfigured) {
|
||||||
|
throw new Error("ak-interface-user-presentation misused; no valid values passed");
|
||||||
|
}
|
||||||
|
|
||||||
return html` <ak-locale-context>
|
return html` <ak-locale-context>
|
||||||
<ak-enterprise-status interface="user"></ak-enterprise-status>
|
<ak-enterprise-status interface="user"></ak-enterprise-status>
|
||||||
<div class="pf-c-page">
|
<div class="pf-c-page">
|
||||||
@ -196,85 +193,18 @@ export class UserInterface extends EnterpriseAwareInterface {
|
|||||||
<a href="#/" class="pf-c-page__header-brand-link">
|
<a href="#/" class="pf-c-page__header-brand-link">
|
||||||
<img
|
<img
|
||||||
class="pf-c-brand"
|
class="pf-c-brand"
|
||||||
src="${first(this.brand?.brandingLogo, DefaultBrand.brandingLogo)}"
|
src="${this.brand.brandingLogo}"
|
||||||
alt="${(this.brand?.brandingTitle, DefaultBrand.brandingTitle)}"
|
alt="${this.brand.brandingTitle}"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-page__header-tools">
|
<div class="pf-c-page__header-tools">
|
||||||
<div class="pf-c-page__header-tools-group">
|
<div class="pf-c-page__header-tools-group">
|
||||||
${this.uiConfig.enabledFeatures.apiDrawer
|
${this.renderApiDrawerTrigger()}
|
||||||
? html`<div
|
<!-- -->
|
||||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
${this.renderNotificationDrawerTrigger()}
|
||||||
>
|
<!-- -->
|
||||||
<button
|
${this.renderSettings()}
|
||||||
class="pf-c-button pf-m-plain"
|
|
||||||
type="button"
|
|
||||||
@click=${() => {
|
|
||||||
this.apiDrawerOpen = !this.apiDrawerOpen;
|
|
||||||
updateURLParams({
|
|
||||||
apiDrawerOpen: this.apiDrawerOpen,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<pf-tooltip
|
|
||||||
position="top"
|
|
||||||
content=${msg("Open API drawer")}
|
|
||||||
>
|
|
||||||
<i class="fas fa-code" aria-hidden="true"></i>
|
|
||||||
</pf-tooltip>
|
|
||||||
</button>
|
|
||||||
</div>`
|
|
||||||
: html``}
|
|
||||||
${this.uiConfig.enabledFeatures.notificationDrawer
|
|
||||||
? 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"
|
|
||||||
aria-label="${msg("Unread notifications")}"
|
|
||||||
@click=${() => {
|
|
||||||
this.notificationDrawerOpen =
|
|
||||||
!this.notificationDrawerOpen;
|
|
||||||
updateURLParams({
|
|
||||||
notificationDrawerOpen:
|
|
||||||
this.notificationDrawerOpen,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="pf-c-notification-badge ${this
|
|
||||||
.notificationsCount > 0
|
|
||||||
? "pf-m-unread"
|
|
||||||
: ""}"
|
|
||||||
>
|
|
||||||
<pf-tooltip
|
|
||||||
position="top"
|
|
||||||
content=${msg("Open Notification drawer")}
|
|
||||||
>
|
|
||||||
<i class="fas fa-bell" aria-hidden="true"></i>
|
|
||||||
</pf-tooltip>
|
|
||||||
<span class="pf-c-notification-badge__count"
|
|
||||||
>${this.notificationsCount}</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div> `
|
|
||||||
: html``}
|
|
||||||
${this.uiConfig.enabledFeatures.settings
|
|
||||||
? html` <div class="pf-c-page__header-tools-item">
|
|
||||||
<a
|
|
||||||
class="pf-c-button pf-m-plain"
|
|
||||||
type="button"
|
|
||||||
href="#/settings"
|
|
||||||
>
|
|
||||||
<pf-tooltip position="top" content=${msg("Settings")}>
|
|
||||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
|
||||||
</pf-tooltip>
|
|
||||||
</a>
|
|
||||||
</div>`
|
|
||||||
: html``}
|
|
||||||
<div class="pf-c-page__header-tools-item">
|
<div class="pf-c-page__header-tools-item">
|
||||||
<a
|
<a
|
||||||
href="/flows/-/default/invalidation/"
|
href="/flows/-/default/invalidation/"
|
||||||
@ -285,39 +215,14 @@ export class UserInterface extends EnterpriseAwareInterface {
|
|||||||
</pf-tooltip>
|
</pf-tooltip>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
${canAccessAdmin
|
${this.renderAdminInterfaceLink()}
|
||||||
? html`<a
|
|
||||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
|
||||||
href="/if/admin/"
|
|
||||||
>
|
|
||||||
${msg("Admin interface")}
|
|
||||||
</a>`
|
|
||||||
: html``}
|
|
||||||
</div>
|
</div>
|
||||||
${this.me.original
|
${this.renderImpersonation()}
|
||||||
? html`
|
|
||||||
<div class="pf-c-page__header-tools">
|
|
||||||
<div class="pf-c-page__header-tools-group">
|
|
||||||
<ak-action-button
|
|
||||||
class="pf-m-warning pf-m-small"
|
|
||||||
.apiRequest=${() => {
|
|
||||||
return new CoreApi(DEFAULT_CONFIG)
|
|
||||||
.coreUsersImpersonateEndRetrieve()
|
|
||||||
.then(() => {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
${msg("Stop impersonation")}
|
|
||||||
</ak-action-button>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
: html``}
|
|
||||||
<div class="pf-c-page__header-tools-group">
|
<div class="pf-c-page__header-tools-group">
|
||||||
<div
|
<div
|
||||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md"
|
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md"
|
||||||
>
|
>
|
||||||
${userDisplay}
|
${this.userDisplayName}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
@ -368,4 +273,202 @@ export class UserInterface extends EnterpriseAwareInterface {
|
|||||||
</div>
|
</div>
|
||||||
</ak-locale-context>`;
|
</ak-locale-context>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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`<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}>
|
||||||
|
<pf-tooltip position="top" content=${msg("Open API drawer")}>
|
||||||
|
<i class="fas fa-code" aria-hidden="true"></i>
|
||||||
|
</pf-tooltip>
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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`<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"
|
||||||
|
aria-label="${msg("Unread notifications")}"
|
||||||
|
@click=${onClick}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="pf-c-notification-badge ${this.notificationsCount > 0
|
||||||
|
? "pf-m-unread"
|
||||||
|
: ""}"
|
||||||
|
>
|
||||||
|
<pf-tooltip position="top" content=${msg("Open Notification drawer")}>
|
||||||
|
<i class="fas fa-bell" aria-hidden="true"></i>
|
||||||
|
</pf-tooltip>
|
||||||
|
<span class="pf-c-notification-badge__count">${this.notificationsCount}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div> `;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSettings() {
|
||||||
|
if (!this.uiConfig.enabledFeatures.settings) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html` <div class="pf-c-page__header-tools-item">
|
||||||
|
<a class="pf-c-button pf-m-plain" type="button" href="#/settings">
|
||||||
|
<pf-tooltip position="top" content=${msg("Settings")}>
|
||||||
|
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||||
|
</pf-tooltip>
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAdminInterfaceLink() {
|
||||||
|
if (!this.canAccessAdmin) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<a
|
||||||
|
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||||
|
href="/if/admin/"
|
||||||
|
>
|
||||||
|
${msg("Admin interface")}
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderImpersonation() {
|
||||||
|
if (!this.me.original) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="pf-c-page__header-tools">
|
||||||
|
<div class="pf-c-page__header-tools-group">
|
||||||
|
<ak-action-button class="pf-m-warning pf-m-small" .apiRequest=${onClick}>
|
||||||
|
${msg("Stop impersonation")}
|
||||||
|
</ak-action-button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ___ _
|
||||||
|
// | _ )_ _ __(_)_ _ ___ ______
|
||||||
|
// | _ \ || (_-< | ' \/ -_|_-<_-<
|
||||||
|
// |___/\_,_/__/_|_||_\___/__/__/
|
||||||
|
//
|
||||||
|
//
|
||||||
|
@customElement("ak-interface-user")
|
||||||
|
export class UserInterface extends EnterpriseAwareInterface {
|
||||||
|
@property({ type: Boolean })
|
||||||
|
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||||
|
|
||||||
|
@state()
|
||||||
|
apiDrawerOpen = getURLParam("apiDrawerOpen", false);
|
||||||
|
|
||||||
|
ws: WebsocketClient;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
notificationsCount = 0;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
me?: SessionUser;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.ws = new WebsocketClient();
|
||||||
|
this.fetchConfigurationDetails();
|
||||||
|
configureSentry(true);
|
||||||
|
this.toggleNotificationDrawer = this.toggleNotificationDrawer.bind(this);
|
||||||
|
this.toggleApiDrawer = this.toggleApiDrawer.bind(this);
|
||||||
|
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
|
||||||
|
window.addEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
|
||||||
|
window.addEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer);
|
||||||
|
window.removeEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer);
|
||||||
|
window.removeEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails);
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleNotificationDrawer() {
|
||||||
|
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
||||||
|
updateURLParams({
|
||||||
|
notificationDrawerOpen: this.notificationDrawerOpen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleApiDrawer() {
|
||||||
|
this.apiDrawerOpen = !this.apiDrawerOpen;
|
||||||
|
updateURLParams({
|
||||||
|
apiDrawerOpen: this.apiDrawerOpen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchConfigurationDetails() {
|
||||||
|
me().then((me: SessionUser) => {
|
||||||
|
this.me = me;
|
||||||
|
new EventsApi(DEFAULT_CONFIG)
|
||||||
|
.eventsNotificationsList({
|
||||||
|
seen: false,
|
||||||
|
ordering: "-created",
|
||||||
|
pageSize: 1,
|
||||||
|
user: this.me.user.pk,
|
||||||
|
})
|
||||||
|
.then((notifications) => {
|
||||||
|
this.notificationsCount = notifications.pagination.count;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFullyConfigured() {
|
||||||
|
return !!(this.uiConfig && this.me);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.isFullyConfigured) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<ak-interface-user-presentation
|
||||||
|
.uiConfig=${this.uiConfig}
|
||||||
|
.me=${this.me}
|
||||||
|
.brand=${this.brand ?? DefaultBrand}
|
||||||
|
?notificationDrawerOpen=${this.notificationDrawerOpen}
|
||||||
|
?apiDrawerOpen=${this.apiDrawerOpen}
|
||||||
|
notificationsCount=${this.notificationsCount}
|
||||||
|
></ak-interface-user-presentation>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user