web: provide a context for enterprise license status
There are a few places (currently 5) in our code where we have checks for the current enterprise licensing status of our product. While not particularly heavy or onerous, there's no reason to repeat those same lines, and since our UI is always running in the context of authentik, may as well make that status a client-side context in its own right. The status will update with an EVENT_REFRESH request. A context-aware custom alert has also been provided; it draws itself (or `nothing`) depending on the state of the license, and the default message, "This feature requires an enterprise license," can be overriden with the `notice` property. These two changes reduce the amount of code needed to manage our license alerting from 67 to 38 lines code, and while removing 29 lines from a product with 54,145 lines of code (a savings of 0.05%, oh boy!) isn't a miracle, it does mean there's a single source of truth for "Is this instance enterprise-licensed?" that's easy to access and use.
This commit is contained in:
parent
be66ee52cd
commit
b36e057a92
|
@ -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`
|
||||
<ak-alert class="pf-c-radio__description" inline>
|
||||
${this.notice}
|
||||
<a href="#/enterprise/licenses">${msg("Learn more")}</a>
|
||||
</ak-alert>
|
||||
`;
|
||||
}
|
||||
}
|
|
@ -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`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.mappingTypes.map((type) => {
|
||||
const requiresEnteprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
|
@ -63,20 +63,17 @@ export class InitialPropertyMappingWizardPage extends WizardPage {
|
|||
];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false}
|
||||
?disabled=${requiresEnteprise}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description">${type.description}</span>
|
||||
${type.requiresEnterprise && !this.enterprise?.hasLicense
|
||||
? html`
|
||||
<ak-alert class="pf-c-radio__description" ?inline=${true}>
|
||||
${msg("Provider require enterprise.")}
|
||||
<a href="#/enterprise/licenses">${msg("Learn more")}</a>
|
||||
</ak-alert>
|
||||
`
|
||||
: nothing}
|
||||
<span class="pf-c-radio__description"
|
||||
>${type.description}
|
||||
${requiresEnteprise
|
||||
? html`<ak-license-notice></ak-license-notice>`
|
||||
: nothing}</span
|
||||
>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
|
@ -92,16 +89,10 @@ export class PropertyMappingWizard extends AKElement {
|
|||
@property({ attribute: false })
|
||||
mappingTypes: TypeCreate[] = [];
|
||||
|
||||
@state()
|
||||
enterprise?: LicenseSummary;
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.mappingTypes = await new PropertymappingsApi(
|
||||
DEFAULT_CONFIG,
|
||||
).propertymappingsAllTypesList();
|
||||
this.enterprise = await new EnterpriseApi(
|
||||
DEFAULT_CONFIG,
|
||||
).enterpriseLicenseSummaryRetrieve();
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
|
|
|
@ -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`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.providerTypes.map((type) => {
|
||||
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
|
@ -83,18 +83,15 @@ export class InitialProviderWizardPage extends WizardPage {
|
|||
this.host.steps = ["initial", `type-${type.component}`];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false}
|
||||
?disabled=${requiresEnterprise}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
|
||||
<span class="pf-c-radio__description">${type.description}</span>
|
||||
${type.requiresEnterprise && !this.enterprise?.hasLicense
|
||||
? html`
|
||||
<ak-alert class="pf-c-radio__description" ?inline=${true}>
|
||||
${msg("Provider require enterprise.")}
|
||||
<a href="#/enterprise/licenses">${msg("Learn more")}</a>
|
||||
</ak-alert>
|
||||
`
|
||||
<span class="pf-c-radio__description"
|
||||
>${type.description}
|
||||
${requiresEnterprise
|
||||
? html`<ak-license-notice></ak-license-notice>`
|
||||
: nothing}
|
||||
</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
|
@ -113,9 +110,6 @@ export class ProviderWizard extends AKElement {
|
|||
@property({ attribute: false })
|
||||
providerTypes: TypeCreate[] = [];
|
||||
|
||||
@state()
|
||||
enterprise?: LicenseSummary;
|
||||
|
||||
@property({ attribute: false })
|
||||
finalHandler: () => Promise<void> = () => {
|
||||
return Promise.resolve();
|
||||
|
@ -123,9 +117,6 @@ export class ProviderWizard extends AKElement {
|
|||
|
||||
async firstUpdated(): Promise<void> {
|
||||
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();
|
||||
}}
|
||||
>
|
||||
<ak-provider-wizard-initial
|
||||
slot="initial"
|
||||
.providerTypes=${this.providerTypes}
|
||||
.enterprise=${this.enterprise}
|
||||
>
|
||||
<ak-provider-wizard-initial slot="initial" .providerTypes=${this.providerTypes}>
|
||||
</ak-provider-wizard-initial>
|
||||
${this.providerTypes.map((type) => {
|
||||
return html`
|
||||
|
|
|
@ -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<Config>(Symbol("authentik-config-context"));
|
||||
|
||||
export const authentikEnterpriseContext = createContext<LicenseSummary>(
|
||||
Symbol("authentik-enterprise-context"),
|
||||
);
|
||||
|
||||
export const authentikTenantContext = createContext<CurrentTenant>(
|
||||
Symbol("authentik-tenant-context"),
|
||||
);
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
@ -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<T = object> = abstract new (...args: any[]) => T;
|
||||
|
||||
export function WithLicenseSummary<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
subscribe = true,
|
||||
) {
|
||||
abstract class WithEnterpriseProvider extends superclass {
|
||||
@consume({ context: authentikEnterpriseContext, subscribe })
|
||||
public licenseSummary!: LicenseSummary;
|
||||
|
||||
get hasEnterpriseLicense() {
|
||||
return this.licenseSummary?.hasLicense;
|
||||
}
|
||||
}
|
||||
|
||||
return WithEnterpriseProvider;
|
||||
}
|
|
@ -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<void> {
|
||||
this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve();
|
||||
}
|
||||
|
||||
renderBanner(): TemplateResult {
|
||||
return html`<div class="pf-c-banner ${this.summary?.readOnly ? "pf-m-red" : "pf-m-gold"}">
|
||||
return html`<div
|
||||
class="pf-c-banner ${this.licenseSummary?.readOnly ? "pf-m-red" : "pf-m-gold"}"
|
||||
>
|
||||
${msg("Warning: The current user count has exceeded the configured licenses.")}
|
||||
<a href="/if/admin/#/enterprise/licenses"> ${msg("Click here for more info.")} </a>
|
||||
</div>`;
|
||||
|
@ -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;
|
||||
|
|
Reference in New Issue