diff --git a/web/src/admin/common/ak-license-notice.ts b/web/src/admin/common/ak-license-notice.ts new file mode 100644 index 0000000000..db8eeca1fa --- /dev/null +++ b/web/src/admin/common/ak-license-notice.ts @@ -0,0 +1,24 @@ +import "@goauthentik/elements/Alert"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; + +import { msg } from "@lit/localize"; +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +@customElement("ak-license-notice") +export class AkLicenceNotice extends WithLicenseSummary(AKElement) { + @property() + notice = msg("This feature requires an enterprise license."); + + render() { + return this.hasEnterpriseLicense + ? nothing + : html` + + ${this.notice} + ${msg("Learn more")} + + `; + } +} diff --git a/web/src/admin/property-mappings/PropertyMappingWizard.ts b/web/src/admin/property-mappings/PropertyMappingWizard.ts index 59b62c39c9..d591e56c9b 100644 --- a/web/src/admin/property-mappings/PropertyMappingWizard.ts +++ b/web/src/admin/property-mappings/PropertyMappingWizard.ts @@ -1,9 +1,11 @@ +import "@goauthentik/admin/common/ak-license-notice"; import "@goauthentik/admin/property-mappings/PropertyMappingLDAPForm"; import "@goauthentik/admin/property-mappings/PropertyMappingNotification"; import "@goauthentik/admin/property-mappings/PropertyMappingRACForm"; import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm"; import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm"; import "@goauthentik/admin/property-mappings/PropertyMappingTestForm"; +import { WithLicenseSummary } from "@goauthentik/app/elements/Interface/licenseSummaryProvider"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/elements/Alert"; import { AKElement } from "@goauthentik/elements/Base"; @@ -14,25 +16,22 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html, nothing } from "lit"; -import { property, state } from "lit/decorators.js"; +import { TemplateResult, html, nothing } from "lit"; +import { property } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { EnterpriseApi, LicenseSummary, PropertymappingsApi, TypeCreate } from "@goauthentik/api"; +import { PropertymappingsApi, TypeCreate } from "@goauthentik/api"; @customElement("ak-property-mapping-wizard-initial") -export class InitialPropertyMappingWizardPage extends WizardPage { +export class InitialPropertyMappingWizardPage extends WithLicenseSummary(WizardPage) { @property({ attribute: false }) mappingTypes: TypeCreate[] = []; - @property({ attribute: false }) - enterprise?: LicenseSummary; - - static get styles(): CSSResult[] { + static get styles() { return [PFBase, PFForm, PFButton, PFRadio]; } sidebarLabel = () => msg("Select type"); @@ -51,6 +50,7 @@ export class InitialPropertyMappingWizardPage extends WizardPage { render(): TemplateResult { return html`
${this.mappingTypes.map((type) => { + const requiresEnteprise = type.requiresEnterprise && !this.hasEnterpriseLicense; return html`
- ${type.description} - ${type.requiresEnterprise && !this.enterprise?.hasLicense - ? html` - - ${msg("Provider require enterprise.")} - ${msg("Learn more")} - - ` - : nothing} + ${type.description} + ${requiresEnteprise + ? html`` + : nothing}
`; })}
`; @@ -86,23 +83,17 @@ export class InitialPropertyMappingWizardPage extends WizardPage { @customElement("ak-property-mapping-wizard") export class PropertyMappingWizard extends AKElement { - static get styles(): CSSResult[] { + static get styles() { return [PFBase, PFButton, PFRadio]; } @property({ attribute: false }) mappingTypes: TypeCreate[] = []; - @state() - enterprise?: LicenseSummary; - async firstUpdated(): Promise { this.mappingTypes = await new PropertymappingsApi( DEFAULT_CONFIG, ).propertymappingsAllTypesList(); - this.enterprise = await new EnterpriseApi( - DEFAULT_CONFIG, - ).enterpriseLicenseSummaryRetrieve(); } render(): TemplateResult { @@ -115,7 +106,6 @@ export class PropertyMappingWizard extends AKElement { ${this.mappingTypes.map((type) => { diff --git a/web/src/admin/providers/ProviderWizard.ts b/web/src/admin/providers/ProviderWizard.ts index 7f19b4d024..ca80f995ee 100644 --- a/web/src/admin/providers/ProviderWizard.ts +++ b/web/src/admin/providers/ProviderWizard.ts @@ -1,8 +1,10 @@ +import "@goauthentik/admin/common/ak-license-notice"; import "@goauthentik/admin/providers/ldap/LDAPProviderForm"; import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; import "@goauthentik/admin/providers/proxy/ProxyProviderForm"; import "@goauthentik/admin/providers/saml/SAMLProviderForm"; import "@goauthentik/admin/providers/saml/SAMLProviderImportForm"; +import { WithLicenseSummary } from "@goauthentik/app/elements/Interface/licenseSummaryProvider"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/elements/Alert"; import { AKElement } from "@goauthentik/elements/Base"; @@ -15,7 +17,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { CSSResult, TemplateResult, html, nothing } from "lit"; -import { property, state } from "lit/decorators.js"; +import { property } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; @@ -23,16 +25,13 @@ import PFHint from "@patternfly/patternfly/components/Hint/hint.css"; import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { EnterpriseApi, LicenseSummary, ProvidersApi, TypeCreate } from "@goauthentik/api"; +import { ProvidersApi, TypeCreate } from "@goauthentik/api"; @customElement("ak-provider-wizard-initial") -export class InitialProviderWizardPage extends WizardPage { +export class InitialProviderWizardPage extends WithLicenseSummary(WizardPage) { @property({ attribute: false }) providerTypes: TypeCreate[] = []; - @property({ attribute: false }) - enterprise?: LicenseSummary; - static get styles(): CSSResult[] { return [PFBase, PFForm, PFHint, PFButton, PFRadio]; } @@ -73,6 +72,7 @@ export class InitialProviderWizardPage extends WizardPage { render(): TemplateResult { return html`
${this.providerTypes.map((type) => { + const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense; return html`
- ${type.description} - ${type.requiresEnterprise && !this.enterprise?.hasLicense - ? html` - - ${msg("Provider require enterprise.")} - ${msg("Learn more")} - - ` - : nothing} + ${type.description} + ${requiresEnterprise + ? html`` + : nothing} +
`; })}
`; @@ -113,9 +110,6 @@ export class ProviderWizard extends AKElement { @property({ attribute: false }) providerTypes: TypeCreate[] = []; - @state() - enterprise?: LicenseSummary; - @property({ attribute: false }) finalHandler: () => Promise = () => { return Promise.resolve(); @@ -123,9 +117,6 @@ export class ProviderWizard extends AKElement { async firstUpdated(): Promise { this.providerTypes = await new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList(); - this.enterprise = await new EnterpriseApi( - DEFAULT_CONFIG, - ).enterpriseLicenseSummaryRetrieve(); } render(): TemplateResult { @@ -138,11 +129,7 @@ export class ProviderWizard extends AKElement { return this.finalHandler(); }} > - + ${this.providerTypes.map((type) => { return html` diff --git a/web/src/elements/AuthentikContexts.ts b/web/src/elements/AuthentikContexts.ts index 3a0f1dd1b8..9d2aaf9b21 100644 --- a/web/src/elements/AuthentikContexts.ts +++ b/web/src/elements/AuthentikContexts.ts @@ -1,9 +1,13 @@ import { createContext } from "@lit-labs/context"; -import type { Config, CurrentBrand } from "@goauthentik/api"; +import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api"; export const authentikConfigContext = createContext(Symbol("authentik-config-context")); +export const authentikEnterpriseContext = createContext( + Symbol("authentik-enterprise-context"), +); + export const authentikBrandContext = createContext(Symbol("authentik-brand-context")); export default authentikConfigContext; diff --git a/web/src/elements/Interface/Interface.ts b/web/src/elements/Interface/Interface.ts index ed4e57c9ae..687a4964b5 100644 --- a/web/src/elements/Interface/Interface.ts +++ b/web/src/elements/Interface/Interface.ts @@ -1,8 +1,10 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { brand, config } from "@goauthentik/common/api/config"; import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; import { authentikBrandContext, authentikConfigContext, + authentikEnterpriseContext, } from "@goauthentik/elements/AuthentikContexts"; import type { AdoptedStyleSheetsElement } from "@goauthentik/elements/types"; import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; @@ -12,7 +14,8 @@ import { state } from "lit/decorators.js"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api"; +import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api"; +import { EnterpriseApi, UiThemeEnum } from "@goauthentik/api"; import { AKElement } from "../Base"; @@ -63,11 +66,33 @@ export class Interface extends AKElement implements AkInterface { return this._brand; } + _licenseSummaryContext = new ContextProvider(this, { + context: authentikEnterpriseContext, + initialValue: undefined, + }); + + _licenseSummary?: LicenseSummary; + + @state() + set licenseSummary(c: LicenseSummary) { + this._licenseSummary = c; + this._licenseSummaryContext.setValue(c); + this.requestUpdate(); + } + + get licenseSummary(): LicenseSummary | undefined { + return this._licenseSummary; + } + constructor() { super(); document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)]; brand().then((brand) => (this.brand = brand)); config().then((config) => (this.config = config)); + new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => { + this.licenseSummary = enterprise; + }); + this.dataset.akInterfaceRoot = "true"; } diff --git a/web/src/elements/Interface/licenseSummaryProvider.ts b/web/src/elements/Interface/licenseSummaryProvider.ts new file mode 100644 index 0000000000..64811ed127 --- /dev/null +++ b/web/src/elements/Interface/licenseSummaryProvider.ts @@ -0,0 +1,25 @@ +import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts"; + +import { consume } from "@lit-labs/context"; +import type { LitElement } from "lit"; + +import type { LicenseSummary } from "@goauthentik/api"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Constructor = abstract new (...args: any[]) => T; + +export function WithLicenseSummary>( + superclass: T, + subscribe = true, +) { + abstract class WithEnterpriseProvider extends superclass { + @consume({ context: authentikEnterpriseContext, subscribe }) + public licenseSummary!: LicenseSummary; + + get hasEnterpriseLicense() { + return this.licenseSummary?.hasLicense; + } + } + + return WithEnterpriseProvider; +} diff --git a/web/src/elements/enterprise/EnterpriseStatusBanner.ts b/web/src/elements/enterprise/EnterpriseStatusBanner.ts index 09d376759e..b3360fb59a 100644 --- a/web/src/elements/enterprise/EnterpriseStatusBanner.ts +++ b/web/src/elements/enterprise/EnterpriseStatusBanner.ts @@ -1,19 +1,14 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKElement } from "@goauthentik/elements/Base"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; -import { EnterpriseApi, LicenseSummary } from "@goauthentik/api"; - @customElement("ak-enterprise-status") -export class EnterpriseStatusBanner extends AKElement { - @state() - summary?: LicenseSummary; - +export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) { @property() interface: "admin" | "user" | "" = ""; @@ -21,12 +16,10 @@ export class EnterpriseStatusBanner extends AKElement { return [PFBanner]; } - async firstUpdated(): Promise { - this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve(); - } - renderBanner(): TemplateResult { - return html`
+ return html`
${msg("Warning: The current user count has exceeded the configured licenses.")} ${msg("Click here for more info.")}
`; @@ -35,12 +28,12 @@ export class EnterpriseStatusBanner extends AKElement { render(): TemplateResult { switch (this.interface.toLowerCase()) { case "admin": - if (this.summary?.showAdminWarning || this.summary?.readOnly) { + if (this.licenseSummary?.showAdminWarning || this.licenseSummary?.readOnly) { return this.renderBanner(); } break; case "user": - if (this.summary?.showUserWarning || this.summary?.readOnly) { + if (this.licenseSummary?.showUserWarning || this.licenseSummary?.readOnly) { return this.renderBanner(); } break;