diff --git a/tests/e2e/test_provider_proxy.py b/tests/e2e/test_provider_proxy.py index 0ac9300e6f..66d25ce8d7 100644 --- a/tests/e2e/test_provider_proxy.py +++ b/tests/e2e/test_provider_proxy.py @@ -118,7 +118,8 @@ class TestProviderProxy(SeleniumTestCase): sleep(2) flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) - title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text + flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) + title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text self.assertIn("You've logged out of", title) @retry() @@ -195,7 +196,8 @@ class TestProviderProxy(SeleniumTestCase): sleep(2) flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) - title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text + flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) + title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text self.assertIn("You've logged out of", title) diff --git a/tests/e2e/test_provider_proxy_forward.py b/tests/e2e/test_provider_proxy_forward.py index 8060d8c520..ab48779662 100644 --- a/tests/e2e/test_provider_proxy_forward.py +++ b/tests/e2e/test_provider_proxy_forward.py @@ -121,7 +121,8 @@ class TestProviderProxyForward(SeleniumTestCase): sleep(2) flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) - title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text + flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) + title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text self.assertIn("You've logged out of", title) @skip("Flaky test") @@ -156,7 +157,8 @@ class TestProviderProxyForward(SeleniumTestCase): sleep(2) flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) - title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text + flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) + title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text self.assertIn("You've logged out of", title) @retry() @@ -189,7 +191,8 @@ class TestProviderProxyForward(SeleniumTestCase): sleep(2) flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) - title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text + flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) + title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text self.assertIn("You've logged out of", title) @retry() @@ -225,5 +228,6 @@ class TestProviderProxyForward(SeleniumTestCase): sleep(2) flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) - title = session_end_stage.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text + flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) + title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text self.assertIn("You've logged out of", title) diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index 4254ae49c4..8a335856f9 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -10,6 +10,7 @@ import { WithBrandConfig } from "#elements/mixins/branding"; import { WithCapabilitiesConfig } from "#elements/mixins/capabilities"; import { themeImage } from "#elements/utils/images"; import "#flow/components/ak-brand-footer"; +import "#flow/components/ak-flow-card"; import "#flow/sources/apple/AppleLoginInit"; import "#flow/sources/plex/PlexLoginInit"; import "#flow/stages/FlowErrorStage"; @@ -304,7 +305,7 @@ export class FlowExecutor async renderChallenge(): Promise { if (!this.challenge) { - return html` `; + return html``; } switch (this.challenge?.component) { case "ak-stage-access-denied": diff --git a/web/src/flow/FlowInspector.ts b/web/src/flow/FlowInspector.ts index 38810fe1eb..081659a326 100644 --- a/web/src/flow/FlowInspector.ts +++ b/web/src/flow/FlowInspector.ts @@ -6,6 +6,7 @@ import { pluckErrorDetail, } from "@goauthentik/common/errors/network"; import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/Expand"; import { msg } from "@lit/localize"; diff --git a/web/src/flow/components/ak-flow-card.ts b/web/src/flow/components/ak-flow-card.ts new file mode 100644 index 0000000000..b81e1ce3ee --- /dev/null +++ b/web/src/flow/components/ak-flow-card.ts @@ -0,0 +1,87 @@ +import { AKElement } from "#elements/Base"; +import "@goauthentik/elements/EmptyState"; + +import { CSSResult, css, html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFLogin from "@patternfly/patternfly/components/Login/login.css"; +import PFTitle from "@patternfly/patternfly/components/Title/title.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { ChallengeTypes } from "@goauthentik/api"; + +/** + * @element ak-flow-card + * @class FlowCard + * @slot title - Title of the card, optional, when not set uses the flow title + * @slot - Main body of the card + * @slot footer - Footer links, optional + * @slot footer-band - Band in the footer, option + * + */ +@customElement("ak-flow-card") +export class FlowCard extends AKElement { + @property({ type: Object }) + challenge?: ChallengeTypes; + + @property({ type: Boolean }) + loading = false; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFLogin, + PFTitle, + css` + /* login page's icons */ + .pf-c-login__main-footer-links-item button { + background-color: transparent; + border: 0; + display: flex; + align-items: stretch; + } + .pf-c-login__main-footer-links-item img { + fill: var(--pf-c-login__main-footer-links-item-link-svg--Fill); + width: 100px; + max-width: var(--pf-c-login__main-footer-links-item-link-svg--Width); + height: 100%; + max-height: var(--pf-c-login__main-footer-links-item-link-svg--Height); + } + `, + ]; + } + + render() { + if (!this.challenge || this.loading) { + return html` + `; + } + // No title if the challenge doesn't provide a title and no custom title is set + let title = undefined; + if (this.hasSlotted("title")) { + title = html`

`; + } else if (this.challenge.flowInfo?.title) { + title = html`

${this.challenge.flowInfo.title}

`; + } + return html`${title + ? html`
${title}
` + : nothing} +
+ +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ak-flow-card": FlowCard; + } +} diff --git a/web/src/flow/providers/SessionEnd.ts b/web/src/flow/providers/SessionEnd.ts index edd526258d..cec9176b2a 100644 --- a/web/src/flow/providers/SessionEnd.ts +++ b/web/src/flow/providers/SessionEnd.ts @@ -1,5 +1,6 @@ import { globalAK } from "@goauthentik/common/global"; import "@goauthentik/flow/FormStatic"; +import "@goauthentik/flow/components/ak-flow-card.js"; import { BaseStage } from "@goauthentik/flow/stages/base"; import { msg, str } from "@lit/localize"; @@ -23,13 +24,7 @@ export class SessionEnd extends BaseStage { } render(): TemplateResult { - if (!this.challenge) { - return html``; - } - return html` - -
- -
`; + `; } } diff --git a/web/src/flow/providers/oauth2/DeviceCode.ts b/web/src/flow/providers/oauth2/DeviceCode.ts index b1303c5a80..9410f66de0 100644 --- a/web/src/flow/providers/oauth2/DeviceCode.ts +++ b/web/src/flow/providers/oauth2/DeviceCode.ts @@ -1,6 +1,6 @@ -import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/forms/FormElement"; import "@goauthentik/flow/FormStatic"; +import "@goauthentik/flow/components/ak-flow-card.js"; import { BaseStage } from "@goauthentik/flow/stages/base"; import { msg } from "@lit/localize"; @@ -29,13 +29,7 @@ export class OAuth2DeviceCode extends BaseStage< } render(): TemplateResult { - if (!this.challenge) { - return html` `; - } - return html` - -
- -
`; + `; } } diff --git a/web/src/flow/providers/oauth2/DeviceCodeFinish.ts b/web/src/flow/providers/oauth2/DeviceCodeFinish.ts index 95056eabda..a9d120806d 100644 --- a/web/src/flow/providers/oauth2/DeviceCodeFinish.ts +++ b/web/src/flow/providers/oauth2/DeviceCodeFinish.ts @@ -1,5 +1,6 @@ import "@goauthentik/elements/EmptyState"; import "@goauthentik/flow/FormStatic"; +import "@goauthentik/flow/components/ak-flow-card.js"; import { BaseStage } from "@goauthentik/flow/stages/base"; import { msg } from "@lit/localize"; @@ -14,13 +15,12 @@ export class DeviceCodeFinish extends BaseStage< OAuthDeviceCodeFinishChallenge > { render(): TemplateResult { - if (!this.challenge) { - return html` `; - } - return html` - ${msg("You may close this page now.")} - ${msg("You've successfully authenticated your device.")} - `; + return html` + + ${msg("You may close this page now.")} + ${msg("You've successfully authenticated your device.")} + + `; } } diff --git a/web/src/flow/sources/apple/AppleLoginInit.ts b/web/src/flow/sources/apple/AppleLoginInit.ts index ccc3f58617..b8377f1846 100644 --- a/web/src/flow/sources/apple/AppleLoginInit.ts +++ b/web/src/flow/sources/apple/AppleLoginInit.ts @@ -1,8 +1,8 @@ -import "@goauthentik/elements/EmptyState"; +import "@goauthentik/flow/components/ak-flow-card.js"; import { BaseStage } from "@goauthentik/flow/stages/base"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, TemplateResult, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -52,27 +52,22 @@ export class AppleLoginInit extends BaseStage