diff --git a/authentik/api/templates/api/browser.html b/authentik/api/templates/api/browser.html index 0e7a3d3cd..675dfc6fe 100644 --- a/authentik/api/templates/api/browser.html +++ b/authentik/api/templates/api/browser.html @@ -7,82 +7,13 @@ API Browser - {{ tenant.branding_title }} {% endblock %} {% block head %} - - - + + + + + {% endblock %} {% block body %} - -
- -
-
- + {% endblock %} diff --git a/authentik/core/templates/base/skeleton.html b/authentik/core/templates/base/skeleton.html index fb2b1195c..ae89350c2 100644 --- a/authentik/core/templates/base/skeleton.html +++ b/authentik/core/templates/base/skeleton.html @@ -9,16 +9,13 @@ {% block title %}{% trans title|default:tenant.branding_title %}{% endblock %} - - - - {% block head_before %} {% endblock %} - + + {% block head %} {% endblock %} diff --git a/authentik/core/templates/if/admin.html b/authentik/core/templates/if/admin.html index fe780f9b3..00b977972 100644 --- a/authentik/core/templates/if/admin.html +++ b/authentik/core/templates/if/admin.html @@ -1,7 +1,6 @@ {% extends "base/skeleton.html" %} {% load static %} -{% load i18n %} {% block head %} @@ -15,19 +14,6 @@ {% block body %} -
-
-
- - - - - -

- {% trans "Loading..." %} -

-
-
-
+
{% endblock %} diff --git a/authentik/core/templates/if/flow.html b/authentik/core/templates/if/flow.html index e6997f29a..bdf81be15 100644 --- a/authentik/core/templates/if/flow.html +++ b/authentik/core/templates/if/flow.html @@ -1,7 +1,6 @@ {% extends "base/skeleton.html" %} {% load static %} -{% load i18n %} {% block head_before %} {{ block.super }} @@ -31,19 +30,6 @@ window.authentik.flow = { {% block body %} -
-
-
- - - - - -

- {% trans "Loading..." %} -

-
-
-
+
{% endblock %} diff --git a/authentik/core/templates/if/user.html b/authentik/core/templates/if/user.html index 5cbff0d8a..39f882f41 100644 --- a/authentik/core/templates/if/user.html +++ b/authentik/core/templates/if/user.html @@ -1,7 +1,6 @@ {% extends "base/skeleton.html" %} {% load static %} -{% load i18n %} {% block head %} @@ -15,19 +14,6 @@ {% block body %} -
-
-
- - - - - -

- {% trans "Loading..." %} -

-
-
-
+
{% endblock %} diff --git a/web/rollup.config.js b/web/rollup.config.js index 67c3ff104..07f919514 100644 --- a/web/rollup.config.js +++ b/web/rollup.config.js @@ -14,28 +14,10 @@ const D3_WARNING = /Circular dependency.*d3-[interpolate|selection]/; const extensions = [".js", ".jsx", ".ts", ".tsx"]; export const resources = [ - { src: "node_modules/rapidoc/dist/rapidoc-min.js", dest: "dist/" }, - { src: "node_modules/@patternfly/patternfly/patternfly.min.css", dest: "dist/", }, - { - src: "node_modules/@patternfly/patternfly/patternfly-base.css", - dest: "dist/", - }, - { - src: "node_modules/@patternfly/patternfly/components/Page/page.css", - dest: "dist/", - }, - { - src: "node_modules/@patternfly/patternfly/components/EmptyState/empty-state.css", - dest: "dist/", - }, - { - src: "node_modules/@patternfly/patternfly/components/Spinner/spinner.css", - dest: "dist/", - }, { src: "src/common/styles/*", dest: "dist/" }, { src: "src/custom.css", dest: "dist/" }, @@ -132,8 +114,25 @@ export const POLY = { ].filter((p) => p), }; +export const standalone = ["api-browser", "loading"].map((input) => { + return { + input: `./src/standalone/${input}`, + output: [ + { + format: "es", + dir: `dist/standalone/${input}`, + sourcemap: true, + manualChunks: manualChunks, + }, + ], + ...defaultOptions, + }; +}); + export default [ POLY, + // Standalone + ...standalone, // Flow interface { input: "./src/flow/FlowInterface.ts", diff --git a/web/rollup.proxy.js b/web/rollup.proxy.js index d0e3f4aee..3599f5916 100644 --- a/web/rollup.proxy.js +++ b/web/rollup.proxy.js @@ -1,3 +1,3 @@ -import { POLY } from "./rollup.config"; +import { POLY, standalone } from "./rollup.config"; -export default [POLY]; +export default [POLY, ...standalone]; diff --git a/web/src/admin/AdminInterface.ts b/web/src/admin/AdminInterface.ts index ba19cb4fa..edfe19077 100644 --- a/web/src/admin/AdminInterface.ts +++ b/web/src/admin/AdminInterface.ts @@ -6,6 +6,7 @@ import { EVENT_SIDEBAR_TOGGLE, VERSION, } from "@goauthentik/common/constants"; +import { configureSentry } from "@goauthentik/common/sentry"; import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; import { me } from "@goauthentik/common/users"; import { WebsocketClient } from "@goauthentik/common/ws"; @@ -105,6 +106,7 @@ export class AdminInterface extends Interface { } async firstUpdated(): Promise { + configureSentry(true); this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve(); this.user = await me(); if (!this.user.user.isSuperuser && this.user.user.pk > 0) { diff --git a/web/src/admin/providers/ldap/LDAPProviderForm.ts b/web/src/admin/providers/ldap/LDAPProviderForm.ts index 9187581d2..93fe0afcc 100644 --- a/web/src/admin/providers/ldap/LDAPProviderForm.ts +++ b/web/src/admin/providers/ldap/LDAPProviderForm.ts @@ -1,6 +1,7 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; -import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; +import { rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; @@ -10,7 +11,7 @@ import "@goauthentik/elements/forms/SearchSelect"; import { t } from "@lingui/macro"; import { TemplateResult, html } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { @@ -19,7 +20,6 @@ import { CoreGroupsListRequest, CryptoApi, CryptoCertificatekeypairsListRequest, - CurrentTenant, Flow, FlowsApi, FlowsInstancesListDesignationEnum, @@ -32,14 +32,10 @@ import { @customElement("ak-provider-ldap-form") export class LDAPProviderFormPage extends ModelForm { - @state() - tenant?: CurrentTenant; async loadInstance(pk: number): Promise { - const provider = await new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ + return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ id: pk, }); - this.tenant = await tenant(); - return provider; } getSuccessMessage(): string { @@ -101,7 +97,7 @@ export class LDAPProviderFormPage extends ModelForm { return flow?.pk; }} .selected=${(flow: Flow): boolean => { - let selected = flow.pk === this.tenant?.flowAuthentication; + let selected = flow.pk === rootInterface()?.tenant?.flowAuthentication; if (this.instance?.authorizationFlow === flow.pk) { selected = true; } diff --git a/web/src/common/styles/theme-dark.css b/web/src/common/styles/theme-dark.css index 3b9c3796c..9e52b715c 100644 --- a/web/src/common/styles/theme-dark.css +++ b/web/src/common/styles/theme-dark.css @@ -1,13 +1,10 @@ -.ak-static-page h1 { - color: var(--ak-dark-foreground); -} body { background-color: var(--ak-dark-background) !important; } :root { - --pf-global--Color--100: var(--ak-dark-foreground); + --pf-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); + --pf-global--link--Color: var(--ak-dark-foreground-link) !important; } .pf-c-radio { --pf-c-radio__label--Color: var(--ak-dark-foreground); diff --git a/web/src/common/users.ts b/web/src/common/users.ts index a94950a44..994a09409 100644 --- a/web/src/common/users.ts +++ b/web/src/common/users.ts @@ -41,7 +41,7 @@ export function me(): Promise { settings: {}, }, }; - if (ex.response.status === 401 || ex.response.status === 403) { + if (ex.response?.status === 401 || ex.response?.status === 403) { const relativeUrl = window.location .toString() .substring(window.location.origin.length); diff --git a/web/src/elements/Base.ts b/web/src/elements/Base.ts index c5090fc9d..5af3ff052 100644 --- a/web/src/elements/Base.ts +++ b/web/src/elements/Base.ts @@ -1,12 +1,15 @@ +import { tenant } from "@goauthentik/common/api/config"; import { EVENT_LOCALE_CHANGE, EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { uiConfig } from "@goauthentik/common/ui/config"; import { LitElement } from "lit"; +import { state } from "lit/decorators.js"; import AKGlobal from "@goauthentik/common/styles/authentik.css"; import ThemeDark from "@goauthentik/common/styles/theme-dark.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { UiThemeEnum } from "@goauthentik/api"; +import { CurrentTenant, UiThemeEnum } from "@goauthentik/api"; export function rootInterface(): Interface | undefined { const el = Array.from(document.body.querySelectorAll("*")).filter( @@ -165,6 +168,15 @@ export class AKElement extends LitElement { } export class Interface extends AKElement { + @state() + tenant?: CurrentTenant; + + constructor() { + super(); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, PFBase]; + tenant().then((tenant) => (this.tenant = tenant)); + } + _activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum): void { super._activateTheme(root, theme); super._activateTheme(document, theme); diff --git a/web/src/elements/cards/AggregateCard.ts b/web/src/elements/cards/AggregateCard.ts index 58534ce9c..b37d3ff24 100644 --- a/web/src/elements/cards/AggregateCard.ts +++ b/web/src/elements/cards/AggregateCard.ts @@ -34,7 +34,6 @@ export class AggregateCard extends AKElement { .center-value { font-size: var(--pf-global--icon--FontSize--lg); text-align: center; - color: var(--pf-global--Color--100); } .subtext { font-size: var(--pf-global--FontSize--sm); diff --git a/web/src/elements/sidebar/SidebarBrand.ts b/web/src/elements/sidebar/SidebarBrand.ts index efa83cee2..fa442b36c 100644 --- a/web/src/elements/sidebar/SidebarBrand.ts +++ b/web/src/elements/sidebar/SidebarBrand.ts @@ -1,11 +1,9 @@ -import { tenant } from "@goauthentik/common/api/config"; import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants"; -import { configureSentry } from "@goauthentik/common/sentry"; import { first } from "@goauthentik/common/utils"; -import { AKElement } from "@goauthentik/elements/Base"; +import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; @@ -30,9 +28,6 @@ export const DefaultTenant: CurrentTenant = { @customElement("ak-sidebar-brand") export class SidebarBrand extends AKElement { - @property({ attribute: false }) - tenant: CurrentTenant = DefaultTenant; - static get styles(): CSSResult[] { return [ PFBase, @@ -69,11 +64,6 @@ export class SidebarBrand extends AKElement { }); } - firstUpdated(): void { - configureSentry(true); - tenant().then((tenant) => (this.tenant = tenant)); - } - render(): TemplateResult { return html` ${window.innerWidth <= MIN_WIDTH ? html` @@ -95,7 +85,10 @@ export class SidebarBrand extends AKElement {
authentik Logo diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index ad7ecb5ab..08df46748 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -12,17 +12,7 @@ import { Interface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/LoadingOverlay"; import "@goauthentik/flow/stages/FlowErrorStage"; import "@goauthentik/flow/stages/RedirectStage"; -import "@goauthentik/flow/stages/access_denied/AccessDeniedStage"; -// Import webauthn-related stages to prevent issues on safari -// Which is overly sensitive to allowing things only in the context of a -// user interaction -import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage"; -import "@goauthentik/flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage"; -import "@goauthentik/flow/stages/autosubmit/AutosubmitStage"; import { StageHost } from "@goauthentik/flow/stages/base"; -import "@goauthentik/flow/stages/captcha/CaptchaStage"; -import "@goauthentik/flow/stages/identification/IdentificationStage"; -import "@goauthentik/flow/stages/password/PasswordStage"; import { t } from "@lingui/macro"; @@ -43,7 +33,6 @@ import { ChallengeChoices, ChallengeTypes, ContextualFlowInfo, - CurrentTenant, FlowChallengeResponseRequest, FlowErrorChallenge, FlowsApi, @@ -92,9 +81,6 @@ export class FlowExecutor extends Interface implements StageHost { @property({ type: Boolean }) loading = false; - @property({ attribute: false }) - tenant!: CurrentTenant; - @state() inspectorOpen = false; @@ -186,7 +172,6 @@ export class FlowExecutor extends Interface implements StageHost { this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => { this.inspectorOpen = !this.inspectorOpen; }); - tenant().then((tenant) => (this.tenant = tenant)); } async getTheme(): Promise { @@ -283,25 +268,25 @@ export class FlowExecutor extends Interface implements StageHost { async renderChallengeNativeElement(): Promise { switch (this.challenge?.component) { case "ak-stage-access-denied": - // Statically imported for performance reasons + await import("@goauthentik/flow/stages/access_denied/AccessDeniedStage"); return html``; case "ak-stage-identification": - // Statically imported for performance reasons + await import("@goauthentik/flow/stages/identification/IdentificationStage"); return html``; case "ak-stage-password": - // Statically imported for performance reasons + await import("@goauthentik/flow/stages/password/PasswordStage"); return html``; case "ak-stage-captcha": - // Statically imported to prevent browsers blocking urls + await import("@goauthentik/flow/stages/captcha/CaptchaStage"); return html``; case "ak-stage-autosubmit": - // Statically imported for performance reasons + await import("@goauthentik/flow/stages/autosubmit/AutosubmitStage"); return html``; case "ak-stage-authenticator-validate": + await import( + "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage" + ); return html``; default: - break; + return html`Invalid native challenge element`; } - return html`Invalid native challenge element`; } async renderChallenge(): Promise { diff --git a/web/src/flow/FlowInterface.ts b/web/src/flow/FlowInterface.ts index 9cd9254c5..c46396e26 100644 --- a/web/src/flow/FlowInterface.ts +++ b/web/src/flow/FlowInterface.ts @@ -1,5 +1,18 @@ import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; import "@goauthentik/elements/messages/MessageContainer"; import "@goauthentik/flow/FlowExecutor"; +// Statically import some stages to speed up load speed +import "@goauthentik/flow/stages/access_denied/AccessDeniedStage"; +// Import webauthn-related stages to prevent issues on safari +// Which is overly sensitive to allowing things only in the context of a +// user interaction +import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage"; +import "@goauthentik/flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage"; +import "@goauthentik/flow/stages/autosubmit/AutosubmitStage"; +import "@goauthentik/flow/stages/captcha/CaptchaStage"; +import "@goauthentik/flow/stages/identification/IdentificationStage"; +import "@goauthentik/flow/stages/password/PasswordStage"; + +// end of stage import autoDetectLanguage(); diff --git a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts index 1525a063b..893c108d0 100644 --- a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts +++ b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts @@ -44,7 +44,7 @@ export class AuthenticatorValidateStage return this.host.loading; } - get tenant(): CurrentTenant { + get tenant(): CurrentTenant | undefined { return this.host.tenant; } diff --git a/web/src/flow/stages/base.ts b/web/src/flow/stages/base.ts index 69a8ad28c..90a568592 100644 --- a/web/src/flow/stages/base.ts +++ b/web/src/flow/stages/base.ts @@ -12,7 +12,7 @@ export interface StageHost { loading: boolean; submit(payload: unknown): Promise; - readonly tenant: CurrentTenant; + readonly tenant?: CurrentTenant; } export function readFileAsync(file: Blob) { diff --git a/web/src/standalone/api-browser/index.ts b/web/src/standalone/api-browser/index.ts new file mode 100644 index 000000000..2e4545c0c --- /dev/null +++ b/web/src/standalone/api-browser/index.ts @@ -0,0 +1,103 @@ +import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; +import { first, getCookie } from "@goauthentik/common/utils"; +import { Interface } from "@goauthentik/elements/Base"; +import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand"; +import "rapidoc"; + +import { CSSResult, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { UiThemeEnum } from "@goauthentik/api"; + +@customElement("ak-api-browser") +export class APIBrowser extends Interface { + @property() + schemaPath?: string; + + static get styles(): CSSResult[] { + return [ + css` + img.logo { + width: 100%; + padding: 1rem 0.5rem 1.5rem 0.5rem; + min-height: 48px; + } + `, + ]; + } + + @state() + bgColor = "#000000"; + + @state() + textColor = "#000000"; + + firstUpdated(): void { + this.addEventListener(EVENT_THEME_CHANGE, ((ev: CustomEvent) => { + const style = getComputedStyle(document.documentElement); + if (ev.detail === UiThemeEnum.Light) { + this.bgColor = style + .getPropertyValue("--pf-global--BackgroundColor--light-300") + .trim(); + this.textColor = style.getPropertyValue("--pf-global--Color--300").trim(); + } else { + this.bgColor = style.getPropertyValue("--ak-dark-background").trim(); + this.textColor = style.getPropertyValue("--ak-dark-foreground").trim(); + } + }) as EventListener); + this.dispatchEvent( + new CustomEvent(EVENT_THEME_CHANGE, { + bubbles: true, + composed: true, + detail: UiThemeEnum.Automatic, + }), + ); + } + + render(): TemplateResult { + return html` + , + ) => { + e.detail.request.headers.append( + "X-authentik-CSRF", + getCookie("authentik_csrf"), + ); + }} + > +
+ +
+
+ `; + } +} diff --git a/web/src/standalone/loading/index.ts b/web/src/standalone/loading/index.ts new file mode 100644 index 000000000..728be4e52 --- /dev/null +++ b/web/src/standalone/loading/index.ts @@ -0,0 +1,56 @@ +import { globalAK } from "@goauthentik/common/global"; +import { Interface } from "@goauthentik/elements/Base"; + +import { t } from "@lingui/macro"; + +import { CSSResult, TemplateResult, css, html } from "lit"; +import { customElement } from "lit/decorators.js"; + +import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { UiThemeEnum } from "@goauthentik/api"; + +@customElement("ak-loading") +export class Loading extends Interface { + static get styles(): CSSResult[] { + return [ + PFBase, + PFPage, + PFSpinner, + PFEmptyState, + css` + :host([theme="dark"]) h1 { + color: var(--ak-dark-foreground); + } + `, + ]; + } + + async getTheme(): Promise { + return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic; + } + + render(): TemplateResult { + return html`
+
+
+ + + + + +

${t`Loading...`}

+
+
+
`; + } +} diff --git a/web/src/user/UserInterface.ts b/web/src/user/UserInterface.ts index 5daae57a2..7368e7af0 100644 --- a/web/src/user/UserInterface.ts +++ b/web/src/user/UserInterface.ts @@ -1,4 +1,4 @@ -import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE, @@ -36,7 +36,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 { CurrentTenant, EventsApi, SessionUser } from "@goauthentik/api"; +import { EventsApi, SessionUser } from "@goauthentik/api"; autoDetectLanguage(); @@ -50,9 +50,6 @@ export class UserInterface extends Interface { ws: WebsocketClient; - @property({ attribute: false }) - tenant: CurrentTenant = DefaultTenant; - @property({ type: Number }) notificationsCount = 0; @@ -128,7 +125,6 @@ export class UserInterface extends Interface { } async firstUpdated(): Promise { - this.tenant = await tenant(); this.me = await me(); this.config = await uiConfig(); const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({ @@ -165,8 +161,8 @@ export class UserInterface extends Interface {
${(this.tenant.brandingTitle, DefaultTenant.brandingTitle)}
diff --git a/web/src/user/user-settings/details/stages/prompt/PromptStage.ts b/web/src/user/user-settings/details/stages/prompt/PromptStage.ts index 3eaa7325c..c77366d49 100644 --- a/web/src/user/user-settings/details/stages/prompt/PromptStage.ts +++ b/web/src/user/user-settings/details/stages/prompt/PromptStage.ts @@ -62,7 +62,7 @@ export class UserSettingsPromptStage extends PromptStage {
- ${this.host.tenant.flowUnenrollment + ${this.host.tenant?.flowUnenrollment ? html`