From 19e8b675ae77f84b20e127683d3bc5eff625b0da Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Tue, 7 Nov 2023 14:13:20 -0800 Subject: [PATCH] web: refactor sidebar capabilities for categorical subsections The project "Change Admin UI lists to have sublists per type" requires some initial changes to the UI to facilitate this request. The AdminSidebar is the principle target of this project, and it is embedded in the AdminInterface. To facilitate editing the AdminSidebar as an independent entity, AdminInterface has been moved into its own folder and the AdminSidebar extracted as a standalone Web Component. This removes, oh, about half the code from AdminInterface. A little cleanup with `classMap` was also committed. The rollup config was adjusted to find the new AdminInterface location. The Sidebar uses the global `config: Config` object to check for Enterprise capabilities. Rather than plumb all the way down through the Interface => AdminInterface -> AdminSidebar, I chose to make provide an alternative way of reaching the `config` object, as a *context*. Other configuration objects (Me, UiConfig, Tenant) interfaces will be contextualized as demand warrants. Demand will warrant. Just not yet. 1 The Sidebar has been refactored only slightly; the renderers are entirely the same as they were prior to extraction. What has been changed is the source of information: when we retrieve the current version we story *only* the information, and use type information to ensure that the version we store is the version we care about. The same is true of `impersonation`; we care only about the name of the person being impersonated being present, so we don't store anything else. Fetches have been moved from `firstUpdated` to the constructor. No reason to have the sidebar render twice if the network returns before the render is scheduled. Because the path used to identify the user being impersonated has changed, the `str()` references in the XLIFF files had to be adjusted. **This change is to a variable only and does not require translation.** --- 1 The code is littered with checks to `me()?`, `uiConfig?`, `config?`, etc. In the *context* of being logged in as an administrator those should never be in doubt. I intend to make our interfaces not have any doubt. --- web/rollup.config.mjs | 2 +- web/src/admin/AdminInterface.ts | 296 ------------------ .../admin/AdminInterface/AdminInterface.ts | 174 ++++++++++ web/src/admin/AdminInterface/AdminSidebar.ts | 176 +++++++++++ web/src/admin/AdminInterface/index.ts | 5 + web/src/elements/AuthentikContexts.ts | 7 + web/src/elements/Base.ts | 19 +- web/src/elements/sidebar/SidebarItem.ts | 111 ++++--- web/xliff/de.xlf | 4 +- web/xliff/en.xlf | 4 +- web/xliff/es.xlf | 4 +- web/xliff/fr.xlf | 4 +- web/xliff/nl.xlf | 4 +- web/xliff/pl.xlf | 4 +- web/xliff/pseudo-LOCALE.xlf | 4 +- web/xliff/tr.xlf | 4 +- web/xliff/zh-Hans.xlf | 4 +- web/xliff/zh-Hant.xlf | 4 +- web/xliff/zh_CN.xlf | 4 +- web/xliff/zh_TW.xlf | 4 +- 20 files changed, 479 insertions(+), 359 deletions(-) delete mode 100644 web/src/admin/AdminInterface.ts create mode 100644 web/src/admin/AdminInterface/AdminInterface.ts create mode 100644 web/src/admin/AdminInterface/AdminSidebar.ts create mode 100644 web/src/admin/AdminInterface/index.ts create mode 100644 web/src/elements/AuthentikContexts.ts diff --git a/web/rollup.config.mjs b/web/rollup.config.mjs index db9caf6ee..49825a29d 100644 --- a/web/rollup.config.mjs +++ b/web/rollup.config.mjs @@ -148,7 +148,7 @@ export default [ }, // Admin interface { - input: "./src/admin/AdminInterface.ts", + input: "./src/admin/AdminInterface/AdminInterface.ts", output: [ { format: "es", diff --git a/web/src/admin/AdminInterface.ts b/web/src/admin/AdminInterface.ts deleted file mode 100644 index e59793706..000000000 --- a/web/src/admin/AdminInterface.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { ROUTES } from "@goauthentik/admin/Routes"; -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { - EVENT_API_DRAWER_TOGGLE, - EVENT_NOTIFICATION_DRAWER_TOGGLE, - EVENT_SIDEBAR_TOGGLE, - VERSION, -} from "@goauthentik/common/constants"; -import { configureSentry } from "@goauthentik/common/sentry"; -import { me } from "@goauthentik/common/users"; -import { WebsocketClient } from "@goauthentik/common/ws"; -import { Interface } from "@goauthentik/elements/Base"; -import "@goauthentik/elements/ak-locale-context"; -import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; -import "@goauthentik/elements/messages/MessageContainer"; -import "@goauthentik/elements/messages/MessageContainer"; -import "@goauthentik/elements/notifications/APIDrawer"; -import "@goauthentik/elements/notifications/NotificationDrawer"; -import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; -import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch"; -import "@goauthentik/elements/router/RouterOutlet"; -import "@goauthentik/elements/sidebar/Sidebar"; -import "@goauthentik/elements/sidebar/SidebarItem"; -import { spread } from "@open-wc/lit-helpers"; - -import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; -import { map } from "lit/directives/map.js"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; -import PFPage from "@patternfly/patternfly/components/Page/page.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -import { - AdminApi, - CapabilitiesEnum, - CoreApi, - SessionUser, - UiThemeEnum, - Version, -} from "@goauthentik/api"; - -@customElement("ak-interface-admin") -export class AdminInterface extends Interface { - @property({ type: Boolean }) - sidebarOpen = true; - - @property({ type: Boolean }) - notificationDrawerOpen = getURLParam("notificationDrawerOpen", false); - - @property({ type: Boolean }) - apiDrawerOpen = getURLParam("apiDrawerOpen", false); - - ws: WebsocketClient; - - @state() - version?: Version; - - @state() - user?: SessionUser; - - static get styles(): CSSResult[] { - return [ - PFBase, - PFPage, - PFButton, - PFDrawer, - css` - .pf-c-page__main, - .pf-c-drawer__content, - .pf-c-page__drawer { - z-index: auto !important; - background-color: transparent; - } - .display-none { - display: none; - } - .pf-c-page { - background-color: var(--pf-c-page--BackgroundColor) !important; - } - /* Global page background colour */ - :host([theme="dark"]) .pf-c-page { - --pf-c-page--BackgroundColor: var(--ak-dark-background); - } - `, - ]; - } - - constructor() { - super(); - this.ws = new WebsocketClient(); - this.sidebarOpen = window.innerWidth >= 1280; - window.addEventListener("resize", () => { - this.sidebarOpen = window.innerWidth >= 1280; - }); - window.addEventListener(EVENT_SIDEBAR_TOGGLE, () => { - this.sidebarOpen = !this.sidebarOpen; - }); - window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => { - this.notificationDrawerOpen = !this.notificationDrawerOpen; - updateURLParams({ - notificationDrawerOpen: this.notificationDrawerOpen, - }); - }); - window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => { - this.apiDrawerOpen = !this.apiDrawerOpen; - updateURLParams({ - apiDrawerOpen: this.apiDrawerOpen, - }); - }); - } - - async firstUpdated(): Promise { - configureSentry(true); - this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve(); - this.user = await me(); - const canAccessAdmin = - this.user.user.isSuperuser || - // TODO: somehow add `access_admin_interface` to the API schema - this.user.user.systemPermissions.includes("access_admin_interface"); - if (!canAccessAdmin && this.user.user.pk > 0) { - window.location.assign("/if/user/"); - } - } - - render(): TemplateResult { - return html` -
- - ${this.renderSidebarItems()} - -
-
-
-
-
-
- - -
-
-
- - -
-
-
`; - } - - renderSidebarItems(): TemplateResult { - // The second attribute type is of string[] to help with the 'activeWhen' control, which was - // commonplace and singular enough to merit its own handler. - type SidebarEntry = [ - path: string | null, - label: string, - attributes?: Record | string[] | null, // eslint-disable-line - children?: SidebarEntry[], - ]; - - // prettier-ignore - const sidebarContent: SidebarEntry[] = [ - ["/if/user/", msg("User interface"), { "?isAbsoluteLink": true, "?highlight": true }], - [null, msg("Dashboards"), { "?expanded": true }, [ - ["/administration/overview", msg("Overview")], - ["/administration/dashboard/users", msg("User Statistics")], - ["/administration/system-tasks", msg("System Tasks")]]], - [null, msg("Applications"), null, [ - ["/core/providers", msg("Providers"), [`^/core/providers/(?${ID_REGEX})$`]], - ["/core/applications", msg("Applications"), [`^/core/applications/(?${SLUG_REGEX})$`]], - ["/outpost/outposts", msg("Outposts")]]], - [null, msg("Events"), null, [ - ["/events/log", msg("Logs"), [`^/events/log/(?${UUID_REGEX})$`]], - ["/events/rules", msg("Notification Rules")], - ["/events/transports", msg("Notification Transports")]]], - [null, msg("Customisation"), null, [ - ["/policy/policies", msg("Policies")], - ["/core/property-mappings", msg("Property Mappings")], - ["/blueprints/instances", msg("Blueprints")], - ["/policy/reputation", msg("Reputation scores")]]], - [null, msg("Flows and Stages"), null, [ - ["/flow/flows", msg("Flows"), [`^/flow/flows/(?${SLUG_REGEX})$`]], - ["/flow/stages", msg("Stages")], - ["/flow/stages/prompts", msg("Prompts")]]], - [null, msg("Directory"), null, [ - ["/identity/users", msg("Users"), [`^/identity/users/(?${ID_REGEX})$`]], - ["/identity/groups", msg("Groups"), [`^/identity/groups/(?${UUID_REGEX})$`]], - ["/identity/roles", msg("Roles"), [`^/identity/roles/(?${UUID_REGEX})$`]], - ["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?${SLUG_REGEX})$`]], - ["/core/tokens", msg("Tokens and App passwords")], - ["/flow/stages/invitations", msg("Invitations")]]], - [null, msg("System"), null, [ - ["/core/tenants", msg("Tenants")], - ["/crypto/certificates", msg("Certificates")], - ["/outpost/integrations", msg("Outpost Integrations")]]] - ]; - - // Typescript requires the type here to correctly type the recursive path - type SidebarRenderer = (_: SidebarEntry) => TemplateResult; - - const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => { - const properties = Array.isArray(attributes) - ? { ".activeWhen": attributes } - : attributes ?? {}; - if (path) { - properties["path"] = path; - } - return html` - ${label ? html`${label}` : nothing} - ${map(children, renderOneSidebarItem)} - `; - }; - - // prettier-ignore - return html` - ${this.renderNewVersionMessage()} - ${this.renderImpersonationMessage()} - ${map(sidebarContent, renderOneSidebarItem)} - ${this.renderEnterpriseMessage()} - `; - } - - renderNewVersionMessage() { - return this.version && this.version.versionCurrent !== VERSION - ? html` - - ${msg("A newer version of the frontend is available.")} - - ` - : nothing; - } - - renderImpersonationMessage() { - return this.user?.original - ? html` { - new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => { - window.location.reload(); - }); - }} - > - ${msg( - str`You're currently impersonating ${this.user.user.username}. Click to stop.`, - )} - ` - : nothing; - } - - renderEnterpriseMessage() { - return this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise) - ? html` - - ${msg("Enterprise")} - - ${msg("Licenses")} - - - ` - : nothing; - } -} diff --git a/web/src/admin/AdminInterface/AdminInterface.ts b/web/src/admin/AdminInterface/AdminInterface.ts new file mode 100644 index 000000000..d964ad59a --- /dev/null +++ b/web/src/admin/AdminInterface/AdminInterface.ts @@ -0,0 +1,174 @@ +import { ROUTES } from "@goauthentik/admin/Routes"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { + EVENT_API_DRAWER_TOGGLE, + EVENT_NOTIFICATION_DRAWER_TOGGLE, + EVENT_SIDEBAR_TOGGLE, +} from "@goauthentik/common/constants"; +import { configureSentry } from "@goauthentik/common/sentry"; +import { me } from "@goauthentik/common/users"; +import { WebsocketClient } from "@goauthentik/common/ws"; +import { Interface } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/ak-locale-context"; +import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; +import "@goauthentik/elements/messages/MessageContainer"; +import "@goauthentik/elements/messages/MessageContainer"; +import "@goauthentik/elements/notifications/APIDrawer"; +import "@goauthentik/elements/notifications/NotificationDrawer"; +import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch"; +import "@goauthentik/elements/router/RouterOutlet"; +import "@goauthentik/elements/sidebar/Sidebar"; +import "@goauthentik/elements/sidebar/SidebarItem"; + +import { CSSResult, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { AdminApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api"; + +import "./AdminSidebar"; + +@customElement("ak-interface-admin") +export class AdminInterface extends Interface { + @property({ type: Boolean }) + sidebarOpen = true; + + @property({ type: Boolean }) + notificationDrawerOpen = getURLParam("notificationDrawerOpen", false); + + @property({ type: Boolean }) + apiDrawerOpen = getURLParam("apiDrawerOpen", false); + + ws: WebsocketClient; + + @state() + version?: Version; + + @state() + user?: SessionUser; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFPage, + PFButton, + PFDrawer, + css` + .pf-c-page__main, + .pf-c-drawer__content, + .pf-c-page__drawer { + z-index: auto !important; + background-color: transparent; + } + .display-none { + display: none; + } + .pf-c-page { + background-color: var(--pf-c-page--BackgroundColor) !important; + } + /* Global page background colour */ + :host([theme="dark"]) .pf-c-page { + --pf-c-page--BackgroundColor: var(--ak-dark-background); + } + `, + ]; + } + + constructor() { + super(); + this.ws = new WebsocketClient(); + this.sidebarOpen = window.innerWidth >= 1280; + window.addEventListener("resize", () => { + this.sidebarOpen = window.innerWidth >= 1280; + }); + window.addEventListener(EVENT_SIDEBAR_TOGGLE, () => { + this.sidebarOpen = !this.sidebarOpen; + }); + window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => { + this.notificationDrawerOpen = !this.notificationDrawerOpen; + updateURLParams({ + notificationDrawerOpen: this.notificationDrawerOpen, + }); + }); + window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => { + this.apiDrawerOpen = !this.apiDrawerOpen; + updateURLParams({ + apiDrawerOpen: this.apiDrawerOpen, + }); + }); + } + + async firstUpdated(): Promise { + configureSentry(true); + this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve(); + this.user = await me(); + const canAccessAdmin = + this.user.user.isSuperuser || + // TODO: somehow add `access_admin_interface` to the API schema + this.user.user.systemPermissions.includes("access_admin_interface"); + if (!canAccessAdmin && this.user.user.pk > 0) { + window.location.assign("/if/user/"); + } + } + + render(): TemplateResult { + const sidebarClasses = { + "pf-m-expanded": this.sidebarOpen, + "pf-m-collapsed": !this.sidebarOpen, + "pf-m-light": this.activeTheme === UiThemeEnum.Light, + }; + + const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen; + const drawerClasses = { + "pf-m-expanded": drawerOpen, + "pf-m-collapsed": !drawerOpen, + }; + + return html` +
+ +
+
+
+
+
+
+ + +
+
+
+ + +
+
+
`; + } +} diff --git a/web/src/admin/AdminInterface/AdminSidebar.ts b/web/src/admin/AdminInterface/AdminSidebar.ts new file mode 100644 index 000000000..d80c38bd9 --- /dev/null +++ b/web/src/admin/AdminInterface/AdminSidebar.ts @@ -0,0 +1,176 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { VERSION } from "@goauthentik/common/constants"; +import { me } from "@goauthentik/common/users"; +import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; +import { AKElement } from "@goauthentik/elements/Base"; +import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; +import { spread } from "@open-wc/lit-helpers"; + +import { consume } from "@lit-labs/context"; +import { msg, str } from "@lit/localize"; +import { TemplateResult, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { map } from "lit/directives/map.js"; + +import { + AdminApi, + CapabilitiesEnum, + type Config, + CoreApi, + type SessionUser, + UiThemeEnum, + type UserSelf, + Version, +} from "@goauthentik/api"; + +@customElement("ak-admin-sidebar") +export class AkAdminSidebar extends AKElement { + @property({ type: Boolean }) + open = true; + + @state() + version: Version["versionCurrent"] | null = null; + + @state() + impersonation: UserSelf["username"] | null = null; + + @consume({ context: authentikConfigContext }) + public config!: Config; + + constructor() { + super(); + new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => { + this.version = version.versionCurrent; + }); + me().then((user: SessionUser) => { + this.impersonation = user.original ? user.user.username : null; + }); + } + + render() { + return html` + + ${this.renderSidebarItems()} + + `; + } + + renderSidebarItems(): TemplateResult { + // The second attribute type is of string[] to help with the 'activeWhen' control, which was + // commonplace and singular enough to merit its own handler. + type SidebarEntry = [ + path: string | null, + label: string, + attributes?: Record | string[] | null, // eslint-disable-line + children?: SidebarEntry[], + ]; + + // prettier-ignore + const sidebarContent: SidebarEntry[] = [ + ["/if/user/", msg("User interface"), { "?isAbsoluteLink": true, "?highlight": true }], + [null, msg("Dashboards"), { "?expanded": true }, [ + ["/administration/overview", msg("Overview")], + ["/administration/dashboard/users", msg("User Statistics")], + ["/administration/system-tasks", msg("System Tasks")]]], + [null, msg("Applications"), null, [ + ["/core/applications", msg("Applications"), [`^/core/applications/(?${SLUG_REGEX})$`]], + ["/core/providers", msg("Providers"), [`^/core/providers/(?${ID_REGEX})$`]], + ["/outpost/outposts", msg("Outposts")]]], + [null, msg("Events"), null, [ + ["/events/log", msg("Logs"), [`^/events/log/(?${UUID_REGEX})$`]], + ["/events/rules", msg("Notification Rules")], + ["/events/transports", msg("Notification Transports")]]], + [null, msg("Customisation"), null, [ + ["/policy/policies", msg("Policies")], + ["/core/property-mappings", msg("Property Mappings")], + ["/blueprints/instances", msg("Blueprints")], + ["/policy/reputation", msg("Reputation scores")]]], + [null, msg("Flows and Stages"), null, [ + ["/flow/flows", msg("Flows"), [`^/flow/flows/(?${SLUG_REGEX})$`]], + ["/flow/stages", msg("Stages")], + ["/flow/stages/prompts", msg("Prompts")]]], + [null, msg("Directory"), null, [ + ["/identity/users", msg("Users"), [`^/identity/users/(?${ID_REGEX})$`]], + ["/identity/groups", msg("Groups"), [`^/identity/groups/(?${UUID_REGEX})$`]], + ["/identity/roles", msg("Roles"), [`^/identity/roles/(?${UUID_REGEX})$`]], + ["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?${SLUG_REGEX})$`]], + ["/core/tokens", msg("Tokens and App passwords")], + ["/flow/stages/invitations", msg("Invitations")]]], + [null, msg("System"), null, [ + ["/core/tenants", msg("Tenants")], + ["/crypto/certificates", msg("Certificates")], + ["/outpost/integrations", msg("Outpost Integrations")]]] + ]; + + // Typescript requires the type here to correctly type the recursive path + type SidebarRenderer = (_: SidebarEntry) => TemplateResult; + + const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => { + const properties = Array.isArray(attributes) + ? { ".activeWhen": attributes } + : attributes ?? {}; + if (path) { + properties["path"] = path; + } + return html` + ${label ? html`${label}` : nothing} + ${map(children, renderOneSidebarItem)} + `; + }; + + // prettier-ignore + return html` + ${this.renderNewVersionMessage()} + ${this.renderImpersonationMessage()} + ${map(sidebarContent, renderOneSidebarItem)} + ${this.renderEnterpriseMessage()} + `; + } + + renderNewVersionMessage() { + return this.version && this.version !== VERSION + ? html` + + ${msg("A newer version of the frontend is available.")} + + ` + : nothing; + } + + renderImpersonationMessage() { + const reload = () => + new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => { + window.location.reload(); + }); + + return this.impersonation + ? html` + ${msg( + str`You're currently impersonating ${this.impersonation}. Click to stop.`, + )} + ` + : nothing; + } + + renderEnterpriseMessage() { + return this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise) + ? html` + + ${msg("Enterprise")} + + ${msg("Licenses")} + + + ` + : nothing; + } +} diff --git a/web/src/admin/AdminInterface/index.ts b/web/src/admin/AdminInterface/index.ts new file mode 100644 index 000000000..570b87e3b --- /dev/null +++ b/web/src/admin/AdminInterface/index.ts @@ -0,0 +1,5 @@ +import { AdminInterface } from "./AdminInterface"; +import "./AdminInterface"; + +export { AdminInterface }; +export default AdminInterface; diff --git a/web/src/elements/AuthentikContexts.ts b/web/src/elements/AuthentikContexts.ts new file mode 100644 index 000000000..97a89a881 --- /dev/null +++ b/web/src/elements/AuthentikContexts.ts @@ -0,0 +1,7 @@ +import { createContext } from "@lit-labs/context"; + +import { type Config } from "@goauthentik/api"; + +export const authentikConfigContext = createContext(Symbol("authentik-config-context")); + +export default authentikConfigContext; diff --git a/web/src/elements/Base.ts b/web/src/elements/Base.ts index 7b2420454..46c983aad 100644 --- a/web/src/elements/Base.ts +++ b/web/src/elements/Base.ts @@ -2,7 +2,9 @@ import { config, tenant } from "@goauthentik/common/api/config"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; import { adaptCSS } from "@goauthentik/common/utils"; +import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; +import { ContextProvider } from "@lit-labs/context"; import { localized } from "@lit/localize"; import { CSSResult, LitElement } from "lit"; import { state } from "lit/decorators.js"; @@ -181,8 +183,23 @@ export class Interface extends AKElement implements AkInterface { @state() uiConfig?: UIConfig; + _configContext = new ContextProvider(this, { + context: authentikConfigContext, + initialValue: undefined, + }); + + _config?: Config; + @state() - config?: Config; + set config(c: Config) { + this._config = c; + this._configContext.setValue(c); + this.requestUpdate(); + } + + get config(): Config | undefined { + return this._config; + } constructor() { super(); diff --git a/web/src/elements/sidebar/SidebarItem.ts b/web/src/elements/sidebar/SidebarItem.ts index 9d5374736..26cdb975e 100644 --- a/web/src/elements/sidebar/SidebarItem.ts +++ b/web/src/elements/sidebar/SidebarItem.ts @@ -144,47 +144,84 @@ export class SidebarItem extends AKElement { return this.renderInner(); } - renderInner(): TemplateResult { - if (this.childItems.length > 0) { - return html`
  • + -
    -
      - -
    -
    -
  • `; + + +
    +
      + +
    +
    + `; + } + + renderWithPathAndChildren() { + return html`
  • + + +
    +
      + +
    +
    +
  • `; + } + + renderWithPath() { + return html` + + + + `; + } + + renderWithLabel() { + html` + + + + `; + } + + renderInner() { + if (this.childItems.length > 0) { + return this.path ? this.renderWithPathAndChildren() : this.renderWithChildren(); } + return html`
  • - ${this.path - ? html` - - - - ` - : html` - - - - `} + ${this.path ? this.renderWithPath() : this.renderWithLabel()}
  • `; } } diff --git a/web/xliff/de.xlf b/web/xliff/de.xlf index a1737f42f..57a62031a 100644 --- a/web/xliff/de.xlf +++ b/web/xliff/de.xlf @@ -5005,9 +5005,9 @@ Bindings to groups/users are checked against the user of the event. Eine neuere Version des Frontends ist verfügbar. - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. Sie geben sich gerade als - aus. Klicken Sie zum Stoppen. + aus. Klicken Sie zum Stoppen. User interface diff --git a/web/xliff/en.xlf b/web/xliff/en.xlf index d4849da70..6ddf32abc 100644 --- a/web/xliff/en.xlf +++ b/web/xliff/en.xlf @@ -5268,9 +5268,9 @@ Bindings to groups/users are checked against the user of the event. A newer version of the frontend is available. - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. You're currently impersonating - . Click to stop. + . Click to stop. User interface diff --git a/web/xliff/es.xlf b/web/xliff/es.xlf index 71af7be01..8e91e36ac 100644 --- a/web/xliff/es.xlf +++ b/web/xliff/es.xlf @@ -4929,9 +4929,9 @@ Bindings to groups/users are checked against the user of the event. Está disponible una versión más reciente de la interfaz. - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. Estás suplantando a - . Haga clic para parar. + . Haga clic para parar. User interface diff --git a/web/xliff/fr.xlf b/web/xliff/fr.xlf index b14628a21..ddcfd5b63 100644 --- a/web/xliff/fr.xlf +++ b/web/xliff/fr.xlf @@ -6583,9 +6583,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. Vous vous faites actuellement passer pour - . Cliquer pour arrêter. + . Cliquer pour arrêter. diff --git a/web/xliff/nl.xlf b/web/xliff/nl.xlf index c6ac85543..f2ceeb89c 100644 --- a/web/xliff/nl.xlf +++ b/web/xliff/nl.xlf @@ -6797,8 +6797,8 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de - You're currently impersonating . Click to stop. - Je doet momenteel alsof je bent. Klik om te stoppen. + You're currently impersonating . Click to stop. + Je doet momenteel alsof je bent. Klik om te stoppen. diff --git a/web/xliff/pl.xlf b/web/xliff/pl.xlf index 3914d725a..b84774dca 100644 --- a/web/xliff/pl.xlf +++ b/web/xliff/pl.xlf @@ -5119,9 +5119,9 @@ Bindings to groups/users are checked against the user of the event. Dostępna jest nowsza wersja frontendu. - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. Obecnie podszywasz się pod - . Kliknij, aby zatrzymać. + . Kliknij, aby zatrzymać. User interface diff --git a/web/xliff/pseudo-LOCALE.xlf b/web/xliff/pseudo-LOCALE.xlf index f354a57aa..da75d8a35 100644 --- a/web/xliff/pseudo-LOCALE.xlf +++ b/web/xliff/pseudo-LOCALE.xlf @@ -6540,8 +6540,8 @@ Bindings to groups/users are checked against the user of the event. - You're currently impersonating . Click to stop. - Ŷōũ'ŕē ćũŕŕēńţĺŷ ĩmƥēŕśōńàţĩńĝ . Ćĺĩćķ ţō śţōƥ. + You're currently impersonating . Click to stop. + Ŷōũ'ŕē ćũŕŕēńţĺŷ ĩmƥēŕśōńàţĩńĝ . Ćĺĩćķ ţō śţōƥ. diff --git a/web/xliff/tr.xlf b/web/xliff/tr.xlf index ee64b82dd..d5405037f 100644 --- a/web/xliff/tr.xlf +++ b/web/xliff/tr.xlf @@ -4922,9 +4922,9 @@ Bindings to groups/users are checked against the user of the event. Ön yüzün daha yeni bir sürümü mevcuttur. - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. Şu anda - kimliğine bürünüyorsunuz. Durdurmak için tıklayın. + kimliğine bürünüyorsunuz. Durdurmak için tıklayın. User interface diff --git a/web/xliff/zh-Hans.xlf b/web/xliff/zh-Hans.xlf index 154e9a11b..b77ff09a1 100644 --- a/web/xliff/zh-Hans.xlf +++ b/web/xliff/zh-Hans.xlf @@ -6585,9 +6585,9 @@ Bindings to groups/users are checked against the user of the event. - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. 您目前正在模拟 - 的身份。点击以停止。 + 的身份。点击以停止。 diff --git a/web/xliff/zh-Hant.xlf b/web/xliff/zh-Hant.xlf index 08b410b7b..5ab195baf 100644 --- a/web/xliff/zh-Hant.xlf +++ b/web/xliff/zh-Hant.xlf @@ -4966,9 +4966,9 @@ Bindings to groups/users are checked against the user of the event. 有较新版本的前端可用。 - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. 你目前正在模拟 - 。单击停止。 + 。单击停止。 User interface diff --git a/web/xliff/zh_CN.xlf b/web/xliff/zh_CN.xlf index 7fc656ced..8e510a4eb 100644 --- a/web/xliff/zh_CN.xlf +++ b/web/xliff/zh_CN.xlf @@ -6585,9 +6585,9 @@ Bindings to groups/users are checked against the user of the event. - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. 您目前正在模拟 - 的身份。点击以停止。 + 的身份。点击以停止。 diff --git a/web/xliff/zh_TW.xlf b/web/xliff/zh_TW.xlf index 6a0e1905f..105c5c832 100644 --- a/web/xliff/zh_TW.xlf +++ b/web/xliff/zh_TW.xlf @@ -4965,9 +4965,9 @@ Bindings to groups/users are checked against the user of the event. 有较新版本的前端可用。 - You're currently impersonating . Click to stop. + You're currently impersonating . Click to stop. 你目前正在模拟 - 。单击停止。 + 。单击停止。 User interface