brands: migrate custom CSS to brands (#13172)

* brands: migrate custom CSS to brands

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

* fix missing default

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

* fix tests

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

* simpler migration

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

* add css to brand form

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

* fix

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2025-03-19 22:52:38 +00:00
committed by GitHub
parent 70b1f05a84
commit f37e1ca642
16 changed files with 94 additions and 54 deletions

View File

@ -51,11 +51,7 @@ export class BrandForm extends ModelForm<Brand, string> {
}
renderForm(): TemplateResult {
return html` <ak-form-element-horizontal
label=${msg("Domain")}
?required=${true}
name="domain"
>
return html` <ak-form-element-horizontal label=${msg("Domain")} required name="domain">
<input
type="text"
value="${first(this.instance?.domain, window.location.host)}"
@ -90,14 +86,10 @@ export class BrandForm extends ModelForm<Brand, string> {
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<ak-form-group>
<span slot="header"> ${msg("Branding settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Title")}
?required=${true}
name="brandingTitle"
>
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
<input
type="text"
value="${first(
@ -111,11 +103,7 @@ export class BrandForm extends ModelForm<Brand, string> {
${msg("Branding shown in page title and several other places.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Logo")}
?required=${true}
name="brandingLogo"
>
<ak-form-element-horizontal label=${msg("Logo")} required name="brandingLogo">
<input
type="text"
value="${first(this.instance?.brandingLogo, DefaultBrand.brandingLogo)}"
@ -130,7 +118,7 @@ export class BrandForm extends ModelForm<Brand, string> {
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Favicon")}
?required=${true}
required
name="brandingFavicon"
>
<input
@ -148,6 +136,23 @@ export class BrandForm extends ModelForm<Brand, string> {
${msg("Icon shown in the browser tab.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Custom CSS")}
required
name="brandingCustomCss"
>
<ak-codemirror
mode=${CodeMirrorMode.CSS}
value="${first(
this.instance?.brandingCustomCss,
DefaultBrand.brandingCustomCss,
)}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${msg("Custom CSS to apply to pages when this brand is active.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>

View File

@ -1 +0,0 @@
/* User customisable */

View File

@ -24,26 +24,6 @@ type AkInterface = HTMLElement & {
export const rootInterface = <T extends AkInterface>(): T | undefined =>
(document.body.querySelector("[data-ak-interface-root]") as T) ?? undefined;
let css: Promise<string[]> | undefined;
function fetchCustomCSS(): Promise<string[]> {
if (!css) {
css = Promise.all(
Array.of(...document.head.querySelectorAll<HTMLLinkElement>("link[data-inject]")).map(
(link) => {
return fetch(link.href)
.then((res) => {
return res.text();
})
.finally(() => {
return "";
});
},
),
);
}
return css;
}
export const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)";
// Ensure themes are converted to a static instance of CSS Stylesheet, otherwise the
@ -103,15 +83,12 @@ export class AKElement extends LitElement {
}
async _initCustomCSS(root: DocumentOrShadowRoot): Promise<void> {
const sheets = await fetchCustomCSS();
sheets.map((css) => {
if (css === "") {
return;
}
new CSSStyleSheet().replace(css).then((sheet) => {
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
});
});
const brand = globalAK().brand;
if (!brand) {
return;
}
const sheet = await new CSSStyleSheet().replace(brand.brandingCustomCss);
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
}
_applyTheme(root: DocumentOrShadowRoot, theme?: UiThemeEnum): void {

View File

@ -1,4 +1,5 @@
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
import { css as cssLang } from "@codemirror/lang-css";
import { html as htmlLang } from "@codemirror/lang-html";
import { javascript } from "@codemirror/lang-javascript";
import { python } from "@codemirror/lang-python";
@ -27,6 +28,7 @@ export enum CodeMirrorMode {
XML = "xml",
JavaScript = "javascript",
HTML = "html",
CSS = "css",
Python = "python",
YAML = "yaml",
}
@ -147,6 +149,8 @@ export class CodeMirrorTextarea<T> extends AKElement {
return htmlLang();
case CodeMirrorMode.Python:
return python();
case CodeMirrorMode.CSS:
return cssLang();
case CodeMirrorMode.YAML:
return new LanguageSupport(StreamLanguage.define(yamlMode.yaml));
}

View File

@ -22,6 +22,7 @@ export const DefaultBrand: CurrentBrand = {
brandingLogo: "/static/dist/assets/icons/icon_left_brand.svg",
brandingFavicon: "/static/dist/assets/icons/icon.png",
brandingTitle: "authentik",
brandingCustomCss: "",
uiFooterLinks: [],
uiTheme: UiThemeEnum.Automatic,
matchedDomain: "",