web: housekeeping, optimizations and small fixes (#12450)

* web/user: fix incorrect font in RAC endpoint popup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix navbar button colour in light mode

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add about modal

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix sidebar overlapping page header

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix wizard hint alignment

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add loading state to about modal

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add version context

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* stub out init functions on loading interface

saves 4 HTTP requests on each full page load 🎉

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix z-index for panels

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove redundant api request

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-12-22 17:01:46 +01:00
committed by GitHub
parent 02bd699917
commit dc3559c7e9
16 changed files with 288 additions and 67 deletions

View File

@ -1,6 +1,6 @@
import { createContext } from "@lit/context";
import type { Config, CurrentBrand, LicenseSummary, SessionUser } from "@goauthentik/api";
import type { Config, CurrentBrand, LicenseSummary, SessionUser, Version } from "@goauthentik/api";
export const authentikConfigContext = createContext<Config>(Symbol("authentik-config-context"));
@ -12,4 +12,6 @@ export const authentikEnterpriseContext = createContext<LicenseSummary>(
export const authentikBrandContext = createContext<CurrentBrand>(Symbol("authentik-brand-context"));
export const authentikVersionContext = createContext<Version>(Symbol("authentik-version-context"));
export default authentikConfigContext;

View File

@ -100,7 +100,7 @@ export class AKElement extends LitElement {
this._applyTheme(root, await this.getTheme());
}
private async _initCustomCSS(root: DocumentOrShadowRoot): Promise<void> {
async _initCustomCSS(root: DocumentOrShadowRoot): Promise<void> {
const sheets = await fetchCustomCSS();
sheets.map((css) => {
if (css === "") {

View File

@ -1,4 +1,5 @@
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
import { VersionContextController } from "@goauthentik/elements/Interface/VersionContextController";
import { ModalOrchestrationController } from "@goauthentik/elements/controllers/ModalOrchestrationController.js";
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
@ -6,7 +7,7 @@ import { state } from "lit/decorators.js";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
import type { Config, CurrentBrand, LicenseSummary, Version } from "@goauthentik/api";
import { UiThemeEnum } from "@goauthentik/api";
import { AKElement, rootInterface } from "../Base";
@ -19,11 +20,13 @@ export type AkInterface = HTMLElement & {
brand?: CurrentBrand;
uiConfig?: UIConfig;
config?: Config;
version?: Version;
};
const brandContext = Symbol("brandContext");
const configContext = Symbol("configContext");
const modalController = Symbol("modalController");
const versionContext = Symbol("versionContext");
export class Interface extends AKElement implements AkInterface {
@state()
@ -35,6 +38,8 @@ export class Interface extends AKElement implements AkInterface {
[modalController]!: ModalOrchestrationController;
[versionContext]!: VersionContextController;
@state()
config?: Config;
@ -44,10 +49,15 @@ export class Interface extends AKElement implements AkInterface {
constructor() {
super();
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
this._initContexts();
this.dataset.akInterfaceRoot = "true";
}
_initContexts() {
this[brandContext] = new BrandContextController(this);
this[configContext] = new ConfigContextController(this);
this[modalController] = new ModalOrchestrationController(this);
this.dataset.akInterfaceRoot = "true";
this[versionContext] = new VersionContextController(this);
}
_activateTheme(theme: UiThemeEnum, ...roots: DocumentOrShadowRoot[]): void {

View File

@ -0,0 +1,51 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { authentikVersionContext } from "@goauthentik/elements/AuthentikContexts";
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
import { ContextProvider } from "@lit/context";
import type { ReactiveController } from "lit";
import type { Version } from "@goauthentik/api";
import { AdminApi } from "@goauthentik/api";
import type { AkInterface } from "./Interface";
export class VersionContextController implements ReactiveController {
host!: ReactiveElementHost<AkInterface>;
context!: ContextProvider<{ __context__: Version | undefined }>;
constructor(host: ReactiveElementHost<AkInterface>) {
this.host = host;
this.context = new ContextProvider(this.host, {
context: authentikVersionContext,
initialValue: undefined,
});
this.fetch = this.fetch.bind(this);
this.fetch();
}
fetch() {
new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => {
this.context.setValue(version);
this.host.version = version;
});
}
hostConnected() {
window.addEventListener(EVENT_REFRESH, this.fetch);
}
hostDisconnected() {
window.removeEventListener(EVENT_REFRESH, this.fetch);
}
hostUpdate() {
// If the Interface changes its version information for some reason,
// we should notify all users of the context of that change. doesn't
if (this.host.version !== this.context.value) {
this.context.setValue(this.host.version);
}
}
}

View File

@ -0,0 +1,18 @@
import { authentikVersionContext } from "@goauthentik/elements/AuthentikContexts";
import type { AbstractConstructor } from "@goauthentik/elements/types.js";
import { consume } from "@lit/context";
import type { LitElement } from "lit";
import type { Version } from "@goauthentik/api";
export function WithVersion<T extends AbstractConstructor<LitElement>>(
superclass: T,
subscribe = true,
) {
abstract class WithBrandProvider extends superclass {
@consume({ context: authentikVersionContext, subscribe })
public version!: Version;
}
return WithBrandProvider;
}

View File

@ -1,4 +1,3 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import {
EVENT_SIDEBAR_TOGGLE,
EVENT_WS_MESSAGE,
@ -24,7 +23,7 @@ import PFNotificationBadge from "@patternfly/patternfly/components/NotificationB
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { EventsApi, SessionUser } from "@goauthentik/api";
import { SessionUser } from "@goauthentik/api";
@customElement("ak-page-header")
export class PageHeader extends WithBrandConfig(AKElement) {
@ -34,9 +33,6 @@ export class PageHeader extends WithBrandConfig(AKElement) {
@property({ type: Boolean })
iconImage = false;
@state()
notificationsCount = 0;
@property()
header = "";
@ -65,7 +61,7 @@ export class PageHeader extends WithBrandConfig(AKElement) {
:host {
position: sticky;
top: 0;
z-index: 100;
z-index: var(--pf-global--ZIndex--lg);
}
.bar {
border-bottom: var(--pf-global--BorderWidth--sm);
@ -126,13 +122,6 @@ export class PageHeader extends WithBrandConfig(AKElement) {
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) {

View File

@ -1,25 +1,29 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import type { AdminInterface } from "@goauthentik/admin/AdminInterface/AdminInterface";
import { globalAK } from "@goauthentik/common/global";
import { AKElement } from "@goauthentik/elements/Base";
import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { CSSResult, css, html, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
import PFButton from "@patternfly/patternfly/components/Button/button.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";
import { LicenseSummaryStatusEnum } from "@goauthentik/api";
@customElement("ak-sidebar-version")
export class SidebarVersion extends WithLicenseSummary(AKElement) {
export class SidebarVersion extends WithLicenseSummary(WithVersion(AKElement)) {
static get styles(): CSSResult[] {
return [
PFBase,
PFNav,
PFAvatar,
PFButton,
css`
:host {
display: flex;
@ -37,20 +41,24 @@ export class SidebarVersion extends WithLicenseSummary(AKElement) {
];
}
@state()
version?: Version;
async firstUpdated() {
this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
}
render(): TemplateResult {
let product = globalAK().brand.brandingTitle;
render() {
if (!this.version || !this.licenseSummary) {
return nothing;
}
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
product += ` ${msg("Enterprise")}`;
}
return html`<p class="pf-c-title">${product}</p>
<p class="pf-c-title">${msg(str`Version ${this.version?.versionCurrent}`)}</p> `;
return html`<button
class="pf-c-button pf-m-plain"
@click=${() => {
const int = rootInterface<AdminInterface>();
int?.aboutModal?.onClick();
}}
>
<p class="pf-c-title">${product}</p>
<p class="pf-c-title">${msg(str`Version ${this.version?.versionCurrent || ""}`)}</p>
</button>`;
}
}