flows: add option for flow layout with frame background (#19527)

* flows: add option for flow layout with frame background

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

fix

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

* Tidy variables. Fix mobile and tablet layouts, shadows.

* Update web/src/flow/FlowExecutor.ts

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
This commit is contained in:
Jens L.
2026-02-04 17:39:01 +01:00
committed by GitHub
parent 30d41ded81
commit 68c7037eea
9 changed files with 203 additions and 73 deletions
+3
View File
@@ -31,6 +31,9 @@ class FlowLayout(models.TextChoices):
SIDEBAR_LEFT = "sidebar_left"
SIDEBAR_RIGHT = "sidebar_right"
SIDEBAR_LEFT_FRAME_BACKGROUND = "sidebar_left_frame_background"
SIDEBAR_RIGHT_FRAME_BACKGROUND = "sidebar_right_frame_background"
class ErrorDetailSerializer(PassiveSerializer):
"""Serializer for rest_framework's error messages"""
@@ -0,0 +1,29 @@
# Generated by Django 5.2.10 on 2026-01-16 17:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0030_alter_flow_background"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="layout",
field=models.TextField(
choices=[
("stacked", "Stacked"),
("content_left", "Content Left"),
("content_right", "Content Right"),
("sidebar_left", "Sidebar Left"),
("sidebar_right", "Sidebar Right"),
("sidebar_left_frame_background", "Sidebar Left Frame Background"),
("sidebar_right_frame_background", "Sidebar Right Frame Background"),
],
default="stacked",
),
),
]
+3 -1
View File
@@ -7966,7 +7966,9 @@
"content_left",
"content_right",
"sidebar_left",
"sidebar_right"
"sidebar_right",
"sidebar_left_frame_background",
"sidebar_right_frame_background"
],
"title": "Layout"
},
+4
View File
@@ -36155,6 +36155,8 @@ components:
- content_right
- sidebar_left
- sidebar_right
- sidebar_left_frame_background
- sidebar_right_frame_background
type: string
CountryCodeEnum:
enum:
@@ -38807,6 +38809,8 @@ components:
- content_right
- sidebar_left
- sidebar_right
- sidebar_left_frame_background
- sidebar_right_frame_background
type: string
FlowRequest:
type: object
+14
View File
@@ -293,6 +293,20 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
>
${LayoutToLabel(FlowLayoutEnum.SidebarRight)}
</option>
<option
value=${FlowLayoutEnum.SidebarLeftFrameBackground}
?selected=${this.instance?.layout ===
FlowLayoutEnum.SidebarLeftFrameBackground}
>
${LayoutToLabel(FlowLayoutEnum.SidebarLeftFrameBackground)}
</option>
<option
value=${FlowLayoutEnum.SidebarRightFrameBackground}
?selected=${this.instance?.layout ===
FlowLayoutEnum.SidebarRightFrameBackground}
>
${LayoutToLabel(FlowLayoutEnum.SidebarRightFrameBackground)}
</option>
</select>
</ak-form-element-horizontal>
<ak-file-search-input
+4
View File
@@ -39,6 +39,10 @@ export function LayoutToLabel(layout: FlowLayoutEnum): string {
return msg("Sidebar left");
case FlowLayoutEnum.SidebarRight:
return msg("Sidebar right");
case FlowLayoutEnum.SidebarLeftFrameBackground:
return msg("Sidebar left (frame background)");
case FlowLayoutEnum.SidebarRightFrameBackground:
return msg("Sidebar right (frame background)");
case FlowLayoutEnum.UnknownDefaultOpenApi:
return msg("Unknown layout");
}
+35 -11
View File
@@ -25,7 +25,7 @@ import { Interface } from "#elements/Interface";
import { showAPIErrorMessage } from "#elements/messages/MessageContainer";
import { WithBrandConfig } from "#elements/mixins/branding";
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
import { LitPropertyRecord } from "#elements/types";
import { LitPropertyRecord, SlottedTemplateResult } from "#elements/types";
import { exportParts } from "#elements/utils/attributes";
import { ThemedImage } from "#elements/utils/images";
@@ -185,9 +185,10 @@ export class FlowExecutor
* Synchronize flow info such as background image with the current state.
*/
#synchronizeFlowInfo() {
if (!this.flowInfo) {
return;
}
if (!this.flowInfo) return;
if (this.layout === FlowLayoutEnum.SidebarLeftFrameBackground) return;
if (this.layout === FlowLayoutEnum.SidebarRightFrameBackground) return;
const background =
this.flowInfo.backgroundThemedUrls?.[this.activeTheme] || this.flowInfo.background;
@@ -275,10 +276,7 @@ export class FlowExecutor
this.layout = this.challenge?.flowInfo?.layout || FlowExecutor.DefaultLayout;
}
if (
(changedProperties.has("flowInfo") || changedProperties.has("activeTheme")) &&
this.flowInfo
) {
if (changedProperties.has("flowInfo") || changedProperties.has("activeTheme")) {
this.#synchronizeFlowInfo();
}
@@ -539,11 +537,37 @@ export class FlowExecutor
//#region Render
protected renderLoading(): TemplateResult {
protected renderLoading(): SlottedTemplateResult {
return html`<slot class="slotted-content" name="placeholder"></slot>`;
}
protected override render(): TemplateResult {
protected renderFrameBackground(): SlottedTemplateResult {
return guard([this.layout, this.#challenge], () => {
if (
this.layout !== FlowLayoutEnum.SidebarLeftFrameBackground &&
this.layout !== FlowLayoutEnum.SidebarRightFrameBackground
) {
return nothing;
}
const src = this.#challenge?.flowInfo?.background;
if (!src) return nothing;
return html`
<div class="ak-c-login__content" part="content">
<iframe
class="ak-c-login__content-iframe"
part="content-iframe"
name="flow-content-frame"
src=${src}
></iframe>
</div>
`;
});
}
protected override render(): SlottedTemplateResult {
const { component } = this.challenge || {};
return html`<ak-locale-select
@@ -551,7 +575,7 @@ export class FlowExecutor
exportparts="label:locale-select-label,select:locale-select-select"
class="pf-m-dark"
></ak-locale-select>
${this.renderFrameBackground()}
<header class="pf-c-login__header">${this.renderInspectorButton()}</header>
<main
data-layout=${this.layout}
@@ -1,5 +1,5 @@
fieldset[name="login-sources"] {
--ak-login-sources-padding-inline: var(--pf-global--spacer--xl);
--ak-c-login-sources-padding-inline: var(--pf-global--spacer--xl);
/* compatibility-mode-fix */
@@ -10,7 +10,7 @@ fieldset[name="login-sources"] {
justify-content: center;
gap: var(--pf-global--spacer--sm);
padding-inline: var(--ak-login-sources-padding-inline) !important;
padding-inline: var(--ak-c-login-sources-padding-inline) !important;
padding-block-start: var(--pf-global--spacer--md) !important;
}
@@ -46,7 +46,7 @@ fieldset[name="login-sources"] {
}
@media (min-width: 768px) {
--ak-login-sources-padding-inline: var(--pf-global--spacer--2xl);
--ak-c-login-sources-padding-inline: var(--pf-global--spacer--2xl);
}
}
@@ -2,36 +2,36 @@
/* compatibility-mode-fix */
.pf-c-login.pf-c-login {
--ak-login--PaddingMax: 8dvw;
--ak-login--padding: clamp(
--ak-c-login--PaddingMax: 8dvw;
--ak-c-login--padding: clamp(
var(--pf-global--spacer--md),
var(--pf-global--spacer--2xl),
var(--ak-login--PaddingMax)
var(--ak-c-login--PaddingMax)
);
--ak-login__main--brand-PaddingMin: var(--pf-global--spacer--xs);
--ak-login__main--brand-PaddingIdeal: 5rem;
--ak-login__main--brand-PaddingMax: 15dvh;
--ak-c-login__main--brand-PaddingMin: var(--pf-global--spacer--xs);
--ak-c-login__main--brand-PaddingIdeal: 5rem;
--ak-c-login__main--brand-PaddingMax: 15dvh;
--ak-login__footer--PaddingBlock: var(--pf-global--spacer--md);
--ak-c-login__footer--PaddingBlock: var(--pf-global--spacer--md);
--ak-login--MaxWidth: 35rem;
--ak-c-login--MaxWidth: 35rem;
--ak-login__main-ColumnWidth: minmax(
min(100%, var(--ak-login--MaxWidth)),
var(--ak-login--MaxWidth)
--ak-c-login__main-ColumnWidth: minmax(
min(100%, var(--ak-c-login--MaxWidth)),
var(--ak-c-login--MaxWidth)
);
--pf-c-login__main-body--PaddingBottom: 0;
--ak-login__main--footer-PaddingMin: var(--pf-global--spacer--xs);
--ak-login__main--footer-PaddingIdeal: 3rem;
--ak-login__main--footer-PaddingMax: 9dvh;
--ak-c-login__main--footer-PaddingMin: var(--pf-global--spacer--xs);
--ak-c-login__main--footer-PaddingIdeal: 3rem;
--ak-c-login__main--footer-PaddingMax: 9dvh;
--pf-c-login__main-footer--PaddingBottom: clamp(
var(--ak-login__main--footer-PaddingMin),
var(--ak-login__main--footer-PaddingIdeal),
var(--ak-login__main--footer-PaddingMax)
var(--ak-c-login__main--footer-PaddingMin),
var(--ak-c-login__main--footer-PaddingIdeal),
var(--ak-c-login__main--footer-PaddingMax)
);
--pf-c-login__main-footer-band--BackgroundColor: transparent;
@@ -52,7 +52,7 @@
grid-template-columns:
1fr
[main] var(--ak-login__main-ColumnWidth)
[main] var(--ak-c-login__main-ColumnWidth)
1fr;
grid-template-areas:
@@ -63,14 +63,10 @@
&::before {
display: block;
content: "";
background-color: var(--ak-login--background-color-overlay, transparent);
background-color: var(--ak-c-login--BackgroundColorOverlay, transparent);
z-index: -1;
height: 100%;
pointer-events: none;
}
&::before,
.pf-c-login__overlay {
grid-row: header / footer;
grid-column: header;
}
@@ -85,7 +81,7 @@
}
@media (max-width: 35rem) or (max-height: 17.5rem) {
--ak-login--background-color-overlay: var(--pf-c-login__main--BackgroundColor);
--ak-c-login--BackgroundColorOverlay: var(--pf-c-login__main--BackgroundColor);
}
}
@@ -102,7 +98,7 @@
.pf-c-login__header {
grid-area: header;
padding-inline: calc(var(--ak-login--padding) / 2);
padding-inline: calc(var(--ak-c-login--padding) / 2);
align-self: start;
display: grid;
@@ -131,14 +127,16 @@
.pf-c-login__main {
--pf-c-login__container--PaddingLeft: 0 !important;
--pf-c-login__container--PaddingRight: 0 !important;
box-shadow: var(--pf-global--BoxShadow--md);
--ak-c-login__main--BoxShadow: var(--pf-global--BoxShadow--md);
box-shadow: var(--ak-c-login__main--BoxShadow) !important;
grid-area: main;
margin: 0;
position: relative;
max-width: var(--ak-login--MaxWidth);
min-height: calc(var(--ak-login--MaxWidth) * 0.8);
max-width: var(--ak-c-login--MaxWidth);
min-height: calc(var(--ak-c-login--MaxWidth) * 0.8);
display: flex;
flex-flow: column;
@@ -150,7 +148,7 @@
}
@media (max-width: 35rem) or (max-height: 17.5rem) {
box-shadow: none;
--ak-c-login__main--BoxShadow: none;
}
}
@@ -159,7 +157,7 @@
/* #region Main Header */
.pf-c-login__main-header {
padding-inline: var(--ak-login--padding);
padding-inline: var(--ak-c-login--padding);
padding-block: clamp(var(--pf-global--spacer--xs), 6dvw, var(--pf-global--spacer--lg));
.pf-c-title {
@@ -168,26 +166,26 @@
}
.pf-c-login__main-header.pf-c-brand {
--ak-login__main-padding-block-start: clamp(
var(--ak-login__main--brand-PaddingMin),
var(--ak-login__main--brand-PaddingIdeal),
var(--ak-login__main--brand-PaddingMax)
--ak-c-login__main-padding-block-start: clamp(
var(--ak-c-login__main--brand-PaddingMin),
var(--ak-c-login__main--brand-PaddingIdeal),
var(--ak-c-login__main--brand-PaddingMax)
);
padding-inline: calc(var(--ak-login--padding) / 4);
padding-inline: calc(var(--ak-c-login--padding) / 4);
padding-block-start: calc(
var(--ak-login__main-padding-block-start) - var(--ak-login__footer--PaddingBlock)
var(--ak-c-login__main-padding-block-start) - var(--ak-c-login__footer--PaddingBlock)
);
padding-bottom: var(--pf-global--spacer--xs);
padding-block-end: calc(var(--ak-login--padding) / 2);
padding-block-end: calc(var(--ak-c-login--padding) / 2);
display: flex;
justify-content: center;
.branding-logo {
display: block;
max-width: clamp(75%, calc(var(--ak-login--MaxWidth) / 2), 90%);
max-width: clamp(75%, calc(var(--ak-c-login--MaxWidth) / 2), 90%);
}
/* Ensure Font Awesome logos scale similarly to image logos */
@@ -205,20 +203,70 @@
.pf-c-login__main-body {
flex: 1 1 auto;
padding-inline: var(--ak-login--padding);
padding-inline: var(--ak-c-login--padding);
}
/* #endregion */
/* #region Frame */
.ak-c-login__content {
width: 100%;
height: 100%;
display: block;
grid-column: content;
grid-row: content;
display: none;
position: relative;
isolation: isolate;
&::after {
content: "";
display: block;
position: absolute;
inset: 0;
box-shadow: var(--ak-c-login__content-BoxShadow, none);
pointer-events: none;
}
}
.ak-c-login__content-iframe {
display: block;
border: none;
width: 100%;
height: 100%;
}
/* #endregion */
/* #region Layout variations */
.pf-c-login[data-layout$="frame_background"] {
--ak-c-login--BackgroundColorOverlay: var(--pf-c-login__main--BackgroundColor);
}
.pf-c-login[data-layout^="sidebar_left"] {
--ak-c-login__content-BoxShadow: inset var(--pf-global--BoxShadow--md-right);
}
.pf-c-login[data-layout^="sidebar_right"] {
--ak-c-login__content-BoxShadow: inset var(--pf-global--BoxShadow--md-left);
}
.pf-c-login__main[data-layout$="frame_background"] {
--ak-c-login__main--BoxShadow: none;
}
@media (min-width: 70rem) and (min-height: 17.5rem) {
.pf-c-login__main[data-layout^="sidebar"] {
box-shadow: none;
--ak-c-login__main--BoxShadow: none;
}
.pf-c-login[data-layout="content_left"],
.pf-c-login[data-layout="content_right"] {
.ak-c-login__content {
display: block;
}
.pf-c-login[data-layout^="content"] {
display: flex;
flex-flow: row wrap;
@@ -237,12 +285,14 @@
.pf-c-login[data-layout="sidebar_left"],
.pf-c-login[data-layout="sidebar_right"] {
--ak-login--MaxWidth: 36rem;
--ak-login--background-color-overlay: var(--pf-c-login__main--BackgroundColor);
&::before {
box-shadow: var(--pf-global--BoxShadow--md);
}
}
.pf-c-login[data-layout^="sidebar"] {
--ak-c-login--MaxWidth: 36rem;
--ak-c-login--BackgroundColorOverlay: var(--pf-c-login__main--BackgroundColor);
.pf-c-login__main {
height: 100%;
@@ -257,22 +307,22 @@
}
}
.pf-c-login[data-layout="sidebar_left"] {
grid-template-columns: [main footer] var(--ak-login__main-ColumnWidth) repeat(2, 1fr);
.pf-c-login[data-layout^="sidebar_left"] {
grid-template-columns: [main footer] var(--ak-c-login__main-ColumnWidth) repeat(2, 1fr);
grid-template-areas:
"header . ."
"main . ."
"footer . .";
"header content content"
"main content content"
"footer content content";
}
.pf-c-login[data-layout="sidebar_right"] {
grid-template-columns: repeat(2, 1fr) var(--ak-login__main-ColumnWidth) [main footer];
.pf-c-login[data-layout^="sidebar_right"] {
grid-template-columns: repeat(2, 1fr) var(--ak-c-login__main-ColumnWidth) [main footer];
grid-template-areas:
". . header"
". . main "
". . footer";
"content content header"
"content content main "
"content content footer";
}
}
@@ -287,11 +337,11 @@
align-self: end;
justify-content: center;
padding-inline: var(--pf-global--spacer--xl) !important;
padding-block: var(--ak-login__footer--PaddingBlock) !important;
min-height: calc((var(--ak-login__footer--PaddingBlock) * 2) + 1rem);
padding-block: var(--ak-c-login__footer--PaddingBlock) !important;
min-height: calc((var(--ak-c-login__footer--PaddingBlock) * 2) + 1rem);
line-height: var(--pf-global--LineHeight--md);
min-height: calc(
(var(--ak-login__footer--PaddingBlock) * 2) + (var(--pf-global--LineHeight--md) * 1rem)
(var(--ak-c-login__footer--PaddingBlock) * 2) + (var(--pf-global--LineHeight--md) * 1rem)
);
@media (max-width: 35rem) {