web: full web components part 1 (#4964)

* migrate loading

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

* migrate api browser

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

* migrate base css

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

* move tenant fetching to base interface

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

* import pre-loaded stages in flow interface and not executor to strip down executor size

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

* fix redirect and such

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-03-17 23:10:19 +01:00 committed by GitHub
parent bebf18f257
commit e2d3a95c80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 251 additions and 211 deletions

View File

@ -7,82 +7,13 @@ API Browser - {{ tenant.branding_title }}
{% endblock %} {% endblock %}
{% block head %} {% block head %}
<script type="module" src="{% static 'dist/rapidoc-min.js' %}"></script> <script src="{% static 'dist/standalone/api-browser/index.js' %}?version={{ version }}" type="module"></script>
<script> <meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
function getCookie(name) { <meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
let cookieValue = ""; <link rel="icon" href="{{ tenant.branding_favicon }}">
if (document.cookie && document.cookie !== "") { <link rel="shortcut icon" href="{{ tenant.branding_favicon }}">
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
window.addEventListener('DOMContentLoaded', (event) => {
const rapidocEl = document.querySelector('rapi-doc');
rapidocEl.addEventListener('before-try', (e) => {
e.detail.request.headers.append('X-authentik-CSRF', getCookie("authentik_csrf"));
});
});
</script>
<style>
img.logo {
width: 100%;
padding: 1rem 0.5rem 1.5rem 0.5rem;
min-height: 48px;
}
</style>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<rapi-doc <ak-api-browser schemaPath="{{ path }}"></ak-api-browser>
spec-url="{{ path }}"
heading-text=""
theme="light"
render-style="read"
default-schema-tab="schema"
primary-color="#fd4b2d"
nav-bg-color="#212427"
bg-color="#000000"
text-color="#000000"
nav-text-color="#ffffff"
nav-hover-bg-color="#3c3f42"
nav-accent-color="#4f5255"
nav-hover-text-color="#ffffff"
use-path-in-nav-bar="true"
nav-item-spacing="relaxed"
allow-server-selection="false"
show-header="false"
allow-spec-url-load="false"
allow-spec-file-load="false">
<div slot="nav-logo">
<img alt="authentik Logo" class="logo" src="{% static 'dist/assets/icons/icon_left_brand.png' %}" />
</div>
</rapi-doc>
<script>
const rapidoc = document.querySelector("rapi-doc");
const matcher = window.matchMedia("(prefers-color-scheme: light)");
const changer = (ev) => {
const style = getComputedStyle(document.documentElement);
let bg, text = "";
if (matcher.matches) {
bg = style.getPropertyValue('--pf-global--BackgroundColor--light-300');
text = style.getPropertyValue('--pf-global--Color--300');
} else {
bg = style.getPropertyValue('--ak-dark-background');
text = style.getPropertyValue('--ak-dark-foreground');
}
rapidoc.attributes.getNamedItem("bg-color").value = bg.trim();
rapidoc.attributes.getNamedItem("text-color").value = text.trim();
rapidoc.requestUpdate();
};
matcher.addEventListener("change", changer);
window.addEventListener("load", changer);
</script>
{% endblock %} {% endblock %}

View File

@ -9,16 +9,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:tenant.branding_title %}{% endblock %}</title> <title>{% block title %}{% trans title|default:tenant.branding_title %}{% endblock %}</title>
<link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}"> <link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-base.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/page.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/empty-state.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/spinner.css' %}">
{% block head_before %} {% block head_before %}
{% endblock %} {% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)"> <link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject> <link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject>
<script src="{% static 'dist/poly.js' %}" type="module"></script> <script src="{% static 'dist/poly.js' %}?version={{ version }}" type="module"></script>
<script src="{% static 'dist/standalone/loading/index.js' %}?version={{ version }}" type="module"></script>
{% block head %} {% block head %}
{% endblock %} {% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}" /> <meta name="sentry-trace" content="{{ sentry_trace }}" />

View File

@ -1,7 +1,6 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% block head %} {% block head %}
<script src="{% static 'dist/admin/AdminInterface.js' %}?version={{ version }}" type="module"></script> <script src="{% static 'dist/admin/AdminInterface.js' %}?version={{ version }}" type="module"></script>
@ -15,19 +14,6 @@
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-interface-admin> <ak-interface-admin>
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"> <ak-loading></ak-loading>
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">
{% trans "Loading..." %}
</h1>
</div>
</div>
</section>
</ak-interface-admin> </ak-interface-admin>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,6 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% block head_before %} {% block head_before %}
{{ block.super }} {{ block.super }}
@ -31,19 +30,6 @@ window.authentik.flow = {
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-flow-executor> <ak-flow-executor>
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"> <ak-loading></ak-loading>
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">
{% trans "Loading..." %}
</h1>
</div>
</div>
</section>
</ak-flow-executor> </ak-flow-executor>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,6 @@
{% extends "base/skeleton.html" %} {% extends "base/skeleton.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% block head %} {% block head %}
<script src="{% static 'dist/user/UserInterface.js' %}?version={{ version }}" type="module"></script> <script src="{% static 'dist/user/UserInterface.js' %}?version={{ version }}" type="module"></script>
@ -15,19 +14,6 @@
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-interface-user> <ak-interface-user>
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"> <ak-loading></ak-loading>
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">
{% trans "Loading..." %}
</h1>
</div>
</div>
</section>
</ak-interface-user> </ak-interface-user>
{% endblock %} {% endblock %}

View File

@ -14,28 +14,10 @@ const D3_WARNING = /Circular dependency.*d3-[interpolate|selection]/;
const extensions = [".js", ".jsx", ".ts", ".tsx"]; const extensions = [".js", ".jsx", ".ts", ".tsx"];
export const resources = [ export const resources = [
{ src: "node_modules/rapidoc/dist/rapidoc-min.js", dest: "dist/" },
{ {
src: "node_modules/@patternfly/patternfly/patternfly.min.css", src: "node_modules/@patternfly/patternfly/patternfly.min.css",
dest: "dist/", 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/common/styles/*", dest: "dist/" },
{ src: "src/custom.css", dest: "dist/" }, { src: "src/custom.css", dest: "dist/" },
@ -132,8 +114,25 @@ export const POLY = {
].filter((p) => p), ].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 [ export default [
POLY, POLY,
// Standalone
...standalone,
// Flow interface // Flow interface
{ {
input: "./src/flow/FlowInterface.ts", input: "./src/flow/FlowInterface.ts",

View File

@ -1,3 +1,3 @@
import { POLY } from "./rollup.config"; import { POLY, standalone } from "./rollup.config";
export default [POLY]; export default [POLY, ...standalone];

View File

@ -6,6 +6,7 @@ import {
EVENT_SIDEBAR_TOGGLE, EVENT_SIDEBAR_TOGGLE,
VERSION, VERSION,
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { configureSentry } from "@goauthentik/common/sentry";
import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; import { autoDetectLanguage } from "@goauthentik/common/ui/locale";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { WebsocketClient } from "@goauthentik/common/ws"; import { WebsocketClient } from "@goauthentik/common/ws";
@ -105,6 +106,7 @@ export class AdminInterface extends Interface {
} }
async firstUpdated(): Promise<void> { async firstUpdated(): Promise<void> {
configureSentry(true);
this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve(); this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
this.user = await me(); this.user = await me();
if (!this.user.user.isSuperuser && this.user.user.pk > 0) { if (!this.user.user.isSuperuser && this.user.user.pk > 0) {

View File

@ -1,6 +1,7 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; 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 { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -10,7 +11,7 @@ import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit"; 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 { ifDefined } from "lit/directives/if-defined.js";
import { import {
@ -19,7 +20,6 @@ import {
CoreGroupsListRequest, CoreGroupsListRequest,
CryptoApi, CryptoApi,
CryptoCertificatekeypairsListRequest, CryptoCertificatekeypairsListRequest,
CurrentTenant,
Flow, Flow,
FlowsApi, FlowsApi,
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
@ -32,14 +32,10 @@ import {
@customElement("ak-provider-ldap-form") @customElement("ak-provider-ldap-form")
export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
@state()
tenant?: CurrentTenant;
async loadInstance(pk: number): Promise<LDAPProvider> { async loadInstance(pk: number): Promise<LDAPProvider> {
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
id: pk, id: pk,
}); });
this.tenant = await tenant();
return provider;
} }
getSuccessMessage(): string { getSuccessMessage(): string {
@ -101,7 +97,7 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
return flow?.pk; return flow?.pk;
}} }}
.selected=${(flow: Flow): boolean => { .selected=${(flow: Flow): boolean => {
let selected = flow.pk === this.tenant?.flowAuthentication; let selected = flow.pk === rootInterface()?.tenant?.flowAuthentication;
if (this.instance?.authorizationFlow === flow.pk) { if (this.instance?.authorizationFlow === flow.pk) {
selected = true; selected = true;
} }

View File

@ -1,13 +1,10 @@
.ak-static-page h1 {
color: var(--ak-dark-foreground);
}
body { body {
background-color: var(--ak-dark-background) !important; background-color: var(--ak-dark-background) !important;
} }
:root { :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-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 {
--pf-c-radio__label--Color: var(--ak-dark-foreground); --pf-c-radio__label--Color: var(--ak-dark-foreground);

View File

@ -41,7 +41,7 @@ export function me(): Promise<SessionUser> {
settings: {}, settings: {},
}, },
}; };
if (ex.response.status === 401 || ex.response.status === 403) { if (ex.response?.status === 401 || ex.response?.status === 403) {
const relativeUrl = window.location const relativeUrl = window.location
.toString() .toString()
.substring(window.location.origin.length); .substring(window.location.origin.length);

View File

@ -1,12 +1,15 @@
import { tenant } from "@goauthentik/common/api/config";
import { EVENT_LOCALE_CHANGE, EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { EVENT_LOCALE_CHANGE, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import { LitElement } from "lit"; import { LitElement } from "lit";
import { state } from "lit/decorators.js";
import AKGlobal from "@goauthentik/common/styles/authentik.css"; import AKGlobal from "@goauthentik/common/styles/authentik.css";
import ThemeDark from "@goauthentik/common/styles/theme-dark.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 { export function rootInterface(): Interface | undefined {
const el = Array.from(document.body.querySelectorAll("*")).filter( const el = Array.from(document.body.querySelectorAll("*")).filter(
@ -165,6 +168,15 @@ export class AKElement extends LitElement {
} }
export class Interface extends AKElement { 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 { _activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum): void {
super._activateTheme(root, theme); super._activateTheme(root, theme);
super._activateTheme(document, theme); super._activateTheme(document, theme);

View File

@ -34,7 +34,6 @@ export class AggregateCard extends AKElement {
.center-value { .center-value {
font-size: var(--pf-global--icon--FontSize--lg); font-size: var(--pf-global--icon--FontSize--lg);
text-align: center; text-align: center;
color: var(--pf-global--Color--100);
} }
.subtext { .subtext {
font-size: var(--pf-global--FontSize--sm); font-size: var(--pf-global--FontSize--sm);

View File

@ -1,11 +1,9 @@
import { tenant } from "@goauthentik/common/api/config";
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants"; import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
import { configureSentry } from "@goauthentik/common/sentry";
import { first } from "@goauthentik/common/utils"; 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 { 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 PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css";
@ -30,9 +28,6 @@ export const DefaultTenant: CurrentTenant = {
@customElement("ak-sidebar-brand") @customElement("ak-sidebar-brand")
export class SidebarBrand extends AKElement { export class SidebarBrand extends AKElement {
@property({ attribute: false })
tenant: CurrentTenant = DefaultTenant;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
PFBase, PFBase,
@ -69,11 +64,6 @@ export class SidebarBrand extends AKElement {
}); });
} }
firstUpdated(): void {
configureSentry(true);
tenant().then((tenant) => (this.tenant = tenant));
}
render(): TemplateResult { render(): TemplateResult {
return html` ${window.innerWidth <= MIN_WIDTH return html` ${window.innerWidth <= MIN_WIDTH
? html` ? html`
@ -95,7 +85,10 @@ export class SidebarBrand extends AKElement {
<a href="#/" class="pf-c-page__header-brand-link"> <a href="#/" class="pf-c-page__header-brand-link">
<div class="pf-c-brand ak-brand"> <div class="pf-c-brand ak-brand">
<img <img
src="${first(this.tenant.brandingLogo, DefaultTenant.brandingLogo)}" src="${first(
rootInterface()?.tenant?.brandingLogo,
DefaultTenant.brandingLogo,
)}"
alt="authentik Logo" alt="authentik Logo"
loading="lazy" loading="lazy"
/> />

View File

@ -12,17 +12,7 @@ import { Interface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/LoadingOverlay"; import "@goauthentik/elements/LoadingOverlay";
import "@goauthentik/flow/stages/FlowErrorStage"; import "@goauthentik/flow/stages/FlowErrorStage";
import "@goauthentik/flow/stages/RedirectStage"; 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 { 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"; import { t } from "@lingui/macro";
@ -43,7 +33,6 @@ import {
ChallengeChoices, ChallengeChoices,
ChallengeTypes, ChallengeTypes,
ContextualFlowInfo, ContextualFlowInfo,
CurrentTenant,
FlowChallengeResponseRequest, FlowChallengeResponseRequest,
FlowErrorChallenge, FlowErrorChallenge,
FlowsApi, FlowsApi,
@ -92,9 +81,6 @@ export class FlowExecutor extends Interface implements StageHost {
@property({ type: Boolean }) @property({ type: Boolean })
loading = false; loading = false;
@property({ attribute: false })
tenant!: CurrentTenant;
@state() @state()
inspectorOpen = false; inspectorOpen = false;
@ -186,7 +172,6 @@ export class FlowExecutor extends Interface implements StageHost {
this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => { this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => {
this.inspectorOpen = !this.inspectorOpen; this.inspectorOpen = !this.inspectorOpen;
}); });
tenant().then((tenant) => (this.tenant = tenant));
} }
async getTheme(): Promise<UiThemeEnum> { async getTheme(): Promise<UiThemeEnum> {
@ -283,25 +268,25 @@ export class FlowExecutor extends Interface implements StageHost {
async renderChallengeNativeElement(): Promise<TemplateResult> { async renderChallengeNativeElement(): Promise<TemplateResult> {
switch (this.challenge?.component) { switch (this.challenge?.component) {
case "ak-stage-access-denied": case "ak-stage-access-denied":
// Statically imported for performance reasons await import("@goauthentik/flow/stages/access_denied/AccessDeniedStage");
return html`<ak-stage-access-denied return html`<ak-stage-access-denied
.host=${this as StageHost} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
></ak-stage-access-denied>`; ></ak-stage-access-denied>`;
case "ak-stage-identification": case "ak-stage-identification":
// Statically imported for performance reasons await import("@goauthentik/flow/stages/identification/IdentificationStage");
return html`<ak-stage-identification return html`<ak-stage-identification
.host=${this as StageHost} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
></ak-stage-identification>`; ></ak-stage-identification>`;
case "ak-stage-password": case "ak-stage-password":
// Statically imported for performance reasons await import("@goauthentik/flow/stages/password/PasswordStage");
return html`<ak-stage-password return html`<ak-stage-password
.host=${this as StageHost} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
></ak-stage-password>`; ></ak-stage-password>`;
case "ak-stage-captcha": case "ak-stage-captcha":
// Statically imported to prevent browsers blocking urls await import("@goauthentik/flow/stages/captcha/CaptchaStage");
return html`<ak-stage-captcha return html`<ak-stage-captcha
.host=${this as StageHost} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
@ -325,7 +310,7 @@ export class FlowExecutor extends Interface implements StageHost {
.challenge=${this.challenge} .challenge=${this.challenge}
></ak-stage-email>`; ></ak-stage-email>`;
case "ak-stage-autosubmit": case "ak-stage-autosubmit":
// Statically imported for performance reasons await import("@goauthentik/flow/stages/autosubmit/AutosubmitStage");
return html`<ak-stage-autosubmit return html`<ak-stage-autosubmit
.host=${this as StageHost} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
@ -368,6 +353,9 @@ export class FlowExecutor extends Interface implements StageHost {
.challenge=${this.challenge} .challenge=${this.challenge}
></ak-stage-authenticator-sms>`; ></ak-stage-authenticator-sms>`;
case "ak-stage-authenticator-validate": case "ak-stage-authenticator-validate":
await import(
"@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage"
);
return html`<ak-stage-authenticator-validate return html`<ak-stage-authenticator-validate
.host=${this as StageHost} .host=${this as StageHost}
.challenge=${this.challenge} .challenge=${this.challenge}
@ -411,9 +399,8 @@ export class FlowExecutor extends Interface implements StageHost {
.challenge=${this.challenge} .challenge=${this.challenge}
></ak-stage-flow-error>`; ></ak-stage-flow-error>`;
default: default:
break; return html`Invalid native challenge element`;
} }
return html`Invalid native challenge element`;
} }
async renderChallenge(): Promise<TemplateResult> { async renderChallenge(): Promise<TemplateResult> {

View File

@ -1,5 +1,18 @@
import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; import { autoDetectLanguage } from "@goauthentik/common/ui/locale";
import "@goauthentik/elements/messages/MessageContainer"; import "@goauthentik/elements/messages/MessageContainer";
import "@goauthentik/flow/FlowExecutor"; 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(); autoDetectLanguage();

View File

@ -44,7 +44,7 @@ export class AuthenticatorValidateStage
return this.host.loading; return this.host.loading;
} }
get tenant(): CurrentTenant { get tenant(): CurrentTenant | undefined {
return this.host.tenant; return this.host.tenant;
} }

View File

@ -12,7 +12,7 @@ export interface StageHost {
loading: boolean; loading: boolean;
submit(payload: unknown): Promise<boolean>; submit(payload: unknown): Promise<boolean>;
readonly tenant: CurrentTenant; readonly tenant?: CurrentTenant;
} }
export function readFileAsync(file: Blob) { export function readFileAsync(file: Blob) {

View File

@ -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<UiThemeEnum>) => {
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`
<rapi-doc
spec-url=${ifDefined(this.schemaPath)}
heading-text=""
theme="light"
render-style="read"
default-schema-tab="schema"
primary-color="#fd4b2d"
nav-bg-color="#212427"
bg-color=${this.bgColor}
text-color=${this.textColor}
nav-text-color="#ffffff"
nav-hover-bg-color="#3c3f42"
nav-accent-color="#4f5255"
nav-hover-text-color="#ffffff"
use-path-in-nav-bar="true"
nav-item-spacing="relaxed"
allow-server-selection="false"
show-header="false"
allow-spec-url-load="false"
allow-spec-file-load="false"
@before-try=${(
e: CustomEvent<{
request: {
headers: Headers;
};
}>,
) => {
e.detail.request.headers.append(
"X-authentik-CSRF",
getCookie("authentik_csrf"),
);
}}
>
<div slot="nav-logo">
<img
alt="authentik Logo"
class="logo"
src="${first(this.tenant?.brandingLogo, DefaultTenant.brandingLogo)}"
/>
</div>
</rapi-doc>
`;
}
}

View File

@ -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<UiThemeEnum> {
return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic;
}
render(): TemplateResult {
return html` <section
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
>
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">
<span
class="pf-c-spinner pf-m-xl"
role="progressbar"
aria-valuetext="${t`Loading...`}"
>
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">${t`Loading...`}</h1>
</div>
</div>
</section>`;
}
}

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { import {
EVENT_API_DRAWER_TOGGLE, EVENT_API_DRAWER_TOGGLE,
EVENT_NOTIFICATION_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 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 { CurrentTenant, EventsApi, SessionUser } from "@goauthentik/api"; import { EventsApi, SessionUser } from "@goauthentik/api";
autoDetectLanguage(); autoDetectLanguage();
@ -50,9 +50,6 @@ export class UserInterface extends Interface {
ws: WebsocketClient; ws: WebsocketClient;
@property({ attribute: false })
tenant: CurrentTenant = DefaultTenant;
@property({ type: Number }) @property({ type: Number })
notificationsCount = 0; notificationsCount = 0;
@ -128,7 +125,6 @@ export class UserInterface extends Interface {
} }
async firstUpdated(): Promise<void> { async firstUpdated(): Promise<void> {
this.tenant = await tenant();
this.me = await me(); this.me = await me();
this.config = await uiConfig(); this.config = await uiConfig();
const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({ const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
@ -165,8 +161,8 @@ export class UserInterface extends Interface {
<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.tenant.brandingLogo, DefaultTenant.brandingLogo)}" src="${first(this.tenant?.brandingLogo, DefaultTenant.brandingLogo)}"
alt="${(this.tenant.brandingTitle, DefaultTenant.brandingTitle)}" alt="${(this.tenant?.brandingTitle, DefaultTenant.brandingTitle)}"
/> />
</a> </a>
</div> </div>

View File

@ -62,7 +62,7 @@ export class UserSettingsPromptStage extends PromptStage {
<div class="pf-c-form__horizontal-group"> <div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions"> <div class="pf-c-form__actions">
<button type="submit" class="pf-c-button pf-m-primary">${t`Save`}</button> <button type="submit" class="pf-c-button pf-m-primary">${t`Save`}</button>
${this.host.tenant.flowUnenrollment ${this.host.tenant?.flowUnenrollment
? html` <a ? html` <a
class="pf-c-button pf-m-danger" class="pf-c-button pf-m-danger"
href="/if/flow/${this.host.tenant.flowUnenrollment}/" href="/if/flow/${this.host.tenant.flowUnenrollment}/"

View File

@ -4,12 +4,13 @@
"paths": { "paths": {
"@goauthentik/admin/*": ["src/admin/*"], "@goauthentik/admin/*": ["src/admin/*"],
"@goauthentik/common/*": ["src/common/*"], "@goauthentik/common/*": ["src/common/*"],
"@goauthentik/docs/*": ["../website/docs/*"],
"@goauthentik/elements/*": ["src/elements/*"], "@goauthentik/elements/*": ["src/elements/*"],
"@goauthentik/flow/*": ["src/flow/*"], "@goauthentik/flow/*": ["src/flow/*"],
"@goauthentik/polyfill/*": ["src/polyfill/*"],
"@goauthentik/user/*": ["src/user/*"],
"@goauthentik/locales/*": ["src/locales/*"], "@goauthentik/locales/*": ["src/locales/*"],
"@goauthentik/docs/*": ["../website/docs/*"] "@goauthentik/polyfill/*": ["src/polyfill/*"],
"@goauthentik/standalone/*": ["src/standalone/*"],
"@goauthentik/user/*": ["src/user/*"]
}, },
"baseUrl": ".", "baseUrl": ".",
"esModuleInterop": true, "esModuleInterop": true,