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 000000000..db8eeca1f --- /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 4f0ab6122..15dc6047a 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 { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ProxyForm"; @@ -14,23 +16,20 @@ 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"; 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[] { return [PFBase, PFForm, PFButton, PFRadio]; } @@ -50,6 +49,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}
`; })}
`; @@ -92,16 +89,10 @@ export class PropertyMappingWizard extends AKElement { @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 { diff --git a/web/src/admin/providers/ProviderWizard.ts b/web/src/admin/providers/ProviderWizard.ts index 7f19b4d02..ca80f995e 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 02fa89316..7e3a1e78b 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, CurrentTenant } from "@goauthentik/api"; +import type { Config, CurrentTenant, LicenseSummary } from "@goauthentik/api"; export const authentikConfigContext = createContext(Symbol("authentik-config-context")); +export const authentikEnterpriseContext = createContext( + Symbol("authentik-enterprise-context"), +); + export const authentikTenantContext = createContext( Symbol("authentik-tenant-context"), ); diff --git a/web/src/elements/Interface/Interface.ts b/web/src/elements/Interface/Interface.ts index b2470cfd2..5cdf7a082 100644 --- a/web/src/elements/Interface/Interface.ts +++ b/web/src/elements/Interface/Interface.ts @@ -1,7 +1,9 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { config, tenant } from "@goauthentik/common/api/config"; import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; import { authentikConfigContext, + authentikEnterpriseContext, authentikTenantContext, } from "@goauthentik/elements/AuthentikContexts"; import type { AdoptedStyleSheetsElement } from "@goauthentik/elements/types"; @@ -12,7 +14,8 @@ import { state } from "lit/decorators.js"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Config, CurrentTenant, UiThemeEnum } from "@goauthentik/api"; +import type { Config, CurrentTenant, 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._tenant; } + _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)]; tenant().then((tenant) => (this.tenant = tenant)); 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 000000000..64811ed12 --- /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 09d376759..b3360fb59 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;