From 66fa4e30856d7c5b13588b97e968c68e32c94b30 Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Fri, 12 Jan 2024 10:21:02 -0800 Subject: [PATCH 1/5] web: add RAC Provider to the list of providers understood by the wizard This commit also creates a new, simple alert that knows how to look up the enterprise requirements and chooses to fill itself in with a notice saying "A license is required for this provider," or nothing. That harmonizes the display across both wizards, and reduces the demands on the wizards themselves to "know" about enterprise features. --- ...rd-authentication-method-choice.choices.ts | 23 ++++ ...ion-wizard-authentication-method-choice.ts | 6 +- ...pplication-wizard-authentication-by-rac.ts | 128 ++++++++++++++++++ web/src/admin/applications/wizard/types.ts | 2 + web/src/admin/common/ak-license-notice.ts | 35 +++++ web/src/admin/providers/ProviderWizard.ts | 16 +-- 6 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-by-rac.ts create mode 100644 web/src/admin/common/ak-license-notice.ts diff --git a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts index 2c0442915..d0e2c4d0c 100644 --- a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts +++ b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts @@ -1,3 +1,5 @@ +import "@goauthentik/admin/common/ak-license-notice"; + import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; @@ -8,6 +10,7 @@ import type { ModelRequest, OAuth2ProviderRequest, ProxyProviderRequest, + RACProviderRequest, RadiusProviderRequest, SAMLProviderRequest, SCIMProviderRequest, @@ -19,6 +22,9 @@ type ProviderRenderer = () => TemplateResult; type ModelConverter = (provider: OneOfProvider) => ModelRequest; +type ProviderNoteProvider = () => TemplateResult | undefined; +type ProviderNote = ProviderNoteProvider | undefined; + /** * There's an internal key and an API key because "Proxy" has three different subtypes. */ @@ -30,12 +36,14 @@ type ProviderType = [ ProviderRenderer, // Function that returns the provider's wizard panel as a TemplateResult ProviderModelEnumType, // key used by the API to distinguish between providers ModelConverter, // Handler that takes a generic provider and returns one specifically typed to its panel + ProviderNote?, ]; export type LocalTypeCreate = TypeCreate & { formName: string; modelName: ProviderModelEnumType; converter: ModelConverter; + note?: ProviderNote; }; // prettier-ignore @@ -103,6 +111,19 @@ const _providerModelsTable: ProviderType[] = [ mode: ProxyMode.ForwardDomain, }), ], + [ + "racprovider", + msg("Remote Access Provider"), + msg("Remotely access computers/servers via RDP/SSH/VNC"), + () => + html``, + ProviderModelEnum.RacRacprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.RacRacprovider, + ...(provider as RACProviderRequest), + }), + () => html`` + ], [ "samlprovider", msg("SAML (Security Assertion Markup Language)"), @@ -148,6 +169,7 @@ function mapProviders([ _, modelName, converter, + note, ]: ProviderType): LocalTypeCreate { return { formName, @@ -156,6 +178,7 @@ function mapProviders([ component: "", modelName, converter, + note, }; } diff --git a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts index e13e11eca..08cb7693d 100644 --- a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts +++ b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts @@ -7,7 +7,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement"; import { msg } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { html } from "lit"; +import { html, nothing } from "lit"; import { map } from "lit/directives/map.js"; import BasePanel from "../BasePanel"; @@ -48,7 +48,9 @@ export class ApplicationWizardAuthenticationMethodChoice extends BasePanel { @change=${this.handleChoice} /> - ${type.description} + ${type.description}${type.note ? type.note() : nothing} `; } diff --git a/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-by-rac.ts b/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-by-rac.ts new file mode 100644 index 000000000..e9c26cf31 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-by-rac.ts @@ -0,0 +1,128 @@ +import "@goauthentik/admin/applications/wizard/ak-wizard-title"; +import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import YAML from "yaml"; + +import { msg } from "@lit/localize"; +import { html } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + FlowsInstancesListDesignationEnum, + PaginatedEndpointList, + PaginatedRACPropertyMappingList, + PropertymappingsApi, + RACProvider, + RacApi, +} from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; + +@customElement("ak-application-wizard-authentication-by-rac") +export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel { + @state() + endpoints?: PaginatedEndpointList; + + @state() + propertyMappings?: PaginatedRACPropertyMappingList; + + constructor() { + super(); + new RacApi(DEFAULT_CONFIG).racEndpointsList({}).then((endpoints) => { + this.endpoints = endpoints; + }); + new PropertymappingsApi(DEFAULT_CONFIG) + .propertymappingsRacList({ + ordering: "name", + }) + .then((propertyMappings) => { + this.propertyMappings = propertyMappings; + }); + } + + render() { + const provider = this.wizard.provider as RACProvider | undefined; + const selected = new Set(Array.from(provider?.propertyMappings ?? [])); + const errors = this.wizard.errors.provider; + + return html`${msg("Configure Remote Access Provider Provider")} +
+ + + + +

+ ${msg("Flow used when authorizing this provider.")} +

+
+ + + + + ${msg("Protocol settings")} +
+ + +

+ ${msg("Hold control/command to select multiple items.")} +

+
+ + + +

${msg("Connection settings.")}

+
+
+
+
`; + } +} + +export default ApplicationWizardAuthenticationByRAC; diff --git a/web/src/admin/applications/wizard/types.ts b/web/src/admin/applications/wizard/types.ts index a6e86cac1..d36340c87 100644 --- a/web/src/admin/applications/wizard/types.ts +++ b/web/src/admin/applications/wizard/types.ts @@ -6,6 +6,7 @@ import { type OAuth2ProviderRequest, type ProvidersSamlImportMetadataCreateRequest, type ProxyProviderRequest, + type RACProviderRequest, type RadiusProviderRequest, type SAMLProviderRequest, type SCIMProviderRequest, @@ -16,6 +17,7 @@ export type OneOfProvider = | Partial | Partial | Partial + | Partial | Partial | Partial | Partial 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..b180179d2 --- /dev/null +++ b/web/src/admin/common/ak-license-notice.ts @@ -0,0 +1,35 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/elements/Alert"; +import { AKElement } from "@goauthentik/elements/Base"; + +import { msg } from "@lit/localize"; +import { html, nothing } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +import { EnterpriseApi } from "@goauthentik/api"; + +@customElement("ak-license-notice") +export class AkLicenceNotice extends AKElement { + @state() + hasLicense = false; + + constructor() { + console.log("Notice constructed"); + super(); + new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => { + this.hasLicense = enterprise.hasLicense; + }); + } + + render() { + console.log(this.hasLicense); + return this.hasLicense + ? nothing + : html` + + ${msg("Provider requires enterprise.")} + ${msg("Learn more")} + + `; + } +} diff --git a/web/src/admin/providers/ProviderWizard.ts b/web/src/admin/providers/ProviderWizard.ts index 7f19b4d02..094c784a5 100644 --- a/web/src/admin/providers/ProviderWizard.ts +++ b/web/src/admin/providers/ProviderWizard.ts @@ -1,3 +1,4 @@ +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"; @@ -86,15 +87,12 @@ export class InitialProviderWizardPage extends WizardPage { ?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false} /> - ${type.description} - ${type.requiresEnterprise && !this.enterprise?.hasLicense - ? html` - - ${msg("Provider require enterprise.")} - ${msg("Learn more")} - - ` - : nothing} + ${type.description} + ${type.requiresEnterprise + ? html`` + : nothing} + `; })} `; From ce90900bcdc0703bb78f13fb4d3b6c1d7c00542d Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Fri, 12 Jan 2024 10:46:48 -0800 Subject: [PATCH 2/5] web: remove console.log() from ak-license-notice --- web/src/admin/common/ak-license-notice.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/admin/common/ak-license-notice.ts b/web/src/admin/common/ak-license-notice.ts index b180179d2..4cc8acb6c 100644 --- a/web/src/admin/common/ak-license-notice.ts +++ b/web/src/admin/common/ak-license-notice.ts @@ -14,7 +14,6 @@ export class AkLicenceNotice extends AKElement { hasLicense = false; constructor() { - console.log("Notice constructed"); super(); new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => { this.hasLicense = enterprise.hasLicense; @@ -22,7 +21,6 @@ export class AkLicenceNotice extends AKElement { } render() { - console.log(this.hasLicense); return this.hasLicense ? nothing : html` From 88dafdcf3ec36fda61c2b61d74d889288e4f4b92 Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Fri, 12 Jan 2024 11:05:54 -0800 Subject: [PATCH 3/5] web: fix inconsistencies in identity passing. --- .../methods/ak-application-wizard-authentication-method.ts | 1 + ...c.ts => ak-application-wizard-authentication-for-rac.ts} | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) rename web/src/admin/applications/wizard/methods/rac/{ak-application-wizard-authentication-by-rac.ts => ak-application-wizard-authentication-for-rac.ts} (96%) diff --git a/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts index 9b7e813bf..9c940c942 100644 --- a/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts +++ b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts @@ -7,6 +7,7 @@ import "./oauth/ak-application-wizard-authentication-by-oauth"; import "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy"; import "./proxy/ak-application-wizard-authentication-for-reverse-proxy"; import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy"; +import "./rac/ak-application-wizard-authentication-for-rac"; import "./radius/ak-application-wizard-authentication-by-radius"; import "./saml/ak-application-wizard-authentication-by-saml-configuration"; import "./scim/ak-application-wizard-authentication-by-scim"; diff --git a/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-by-rac.ts b/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-for-rac.ts similarity index 96% rename from web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-by-rac.ts rename to web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-for-rac.ts index e9c26cf31..92ec346c5 100644 --- a/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-by-rac.ts +++ b/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-for-rac.ts @@ -23,7 +23,7 @@ import { import BaseProviderPanel from "../BaseProviderPanel"; -@customElement("ak-application-wizard-authentication-by-rac") +@customElement("ak-application-wizard-authentication-for-rac") export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel { @state() endpoints?: PaginatedEndpointList; @@ -83,7 +83,7 @@ export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel { required value="${provider?.connectionExpiry ?? "hours=8"}" help=${msg( - "Determines how long a session lasts before being disconnected and requiring re-authorization.", + "Determines how long a session lasts before being disconnected and requiring re-authorization." )} required > @@ -104,7 +104,7 @@ export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel { ?selected=${selected.has(mapping.pk)} > ${mapping.name} - `, + ` )}

From 9dfb0424a4ef29b372d1346e8a239217047a1d95 Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Fri, 12 Jan 2024 13:40:41 -0800 Subject: [PATCH 4/5] web: move the license summary information into a top-level context. Rather than repeatedly fetching the license summary, this commit fetches it once at the top-level and keeps it until an EVENT_REFRESH reaches the top level. This prevents the FOUC (Flash Of Unavailable Content) while loading and awaiting the end of the load. --- ...plication-wizard-authentication-for-rac.ts | 4 +-- web/src/admin/common/ak-license-notice.ts | 23 ++++-------- .../PropertyMappingWizard.ts | 35 +++++++------------ web/src/admin/providers/ProviderWizard.ts | 27 +++++--------- web/src/elements/AuthentikContexts.ts | 6 +++- web/src/elements/Interface/Interface.ts | 27 +++++++++++++- .../Interface/licenseSummaryProvider.ts | 25 +++++++++++++ .../enterprise/EnterpriseStatusBanner.ts | 23 +++++------- 8 files changed, 94 insertions(+), 76 deletions(-) create mode 100644 web/src/elements/Interface/licenseSummaryProvider.ts diff --git a/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-for-rac.ts b/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-for-rac.ts index 92ec346c5..a4fb8c00b 100644 --- a/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-for-rac.ts +++ b/web/src/admin/applications/wizard/methods/rac/ak-application-wizard-authentication-for-rac.ts @@ -83,7 +83,7 @@ export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel { required value="${provider?.connectionExpiry ?? "hours=8"}" help=${msg( - "Determines how long a session lasts before being disconnected and requiring re-authorization." + "Determines how long a session lasts before being disconnected and requiring re-authorization.", )} required > @@ -104,7 +104,7 @@ export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel { ?selected=${selected.has(mapping.pk)} > ${mapping.name} - ` + `, )}

diff --git a/web/src/admin/common/ak-license-notice.ts b/web/src/admin/common/ak-license-notice.ts index 4cc8acb6c..57fc461a3 100644 --- a/web/src/admin/common/ak-license-notice.ts +++ b/web/src/admin/common/ak-license-notice.ts @@ -1,31 +1,22 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; 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, state } from "lit/decorators.js"; - -import { EnterpriseApi } from "@goauthentik/api"; +import { customElement, property } from "lit/decorators.js"; @customElement("ak-license-notice") -export class AkLicenceNotice extends AKElement { - @state() - hasLicense = false; - - constructor() { - super(); - new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => { - this.hasLicense = enterprise.hasLicense; - }); - } +export class AkLicenceNotice extends WithLicenseSummary(AKElement) { + @property() + message = msg("This feature requires an enterprise license."); render() { - return this.hasLicense + return this.hasEnterpriseLicense ? nothing : html` - ${msg("Provider requires enterprise.")} + ${this.message} ${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 094c784a5..ca80f995e 100644 --- a/web/src/admin/providers/ProviderWizard.ts +++ b/web/src/admin/providers/ProviderWizard.ts @@ -4,6 +4,7 @@ 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"; @@ -16,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"; @@ -24,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]; } @@ -74,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 + ${requiresEnterprise ? html`` : nothing} @@ -111,9 +110,6 @@ export class ProviderWizard extends AKElement { @property({ attribute: false }) providerTypes: TypeCreate[] = []; - @state() - enterprise?: LicenseSummary; - @property({ attribute: false }) finalHandler: () => Promise = () => { return Promise.resolve(); @@ -121,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 { @@ -136,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..7e1d92537 --- /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 false; + } + } + + 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; From b7c82ed1267046d9da56542334a7d97cd927bd7b Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Fri, 12 Jan 2024 13:55:14 -0800 Subject: [PATCH 5/5] Remove some debugging info, fix a misspelling. --- web/src/admin/common/ak-license-notice.ts | 4 ++-- web/src/elements/Interface/licenseSummaryProvider.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/admin/common/ak-license-notice.ts b/web/src/admin/common/ak-license-notice.ts index 57fc461a3..db8eeca1f 100644 --- a/web/src/admin/common/ak-license-notice.ts +++ b/web/src/admin/common/ak-license-notice.ts @@ -9,14 +9,14 @@ import { customElement, property } from "lit/decorators.js"; @customElement("ak-license-notice") export class AkLicenceNotice extends WithLicenseSummary(AKElement) { @property() - message = msg("This feature requires an enterprise license."); + notice = msg("This feature requires an enterprise license."); render() { return this.hasEnterpriseLicense ? nothing : html` - ${this.message} + ${this.notice} ${msg("Learn more")} `; diff --git a/web/src/elements/Interface/licenseSummaryProvider.ts b/web/src/elements/Interface/licenseSummaryProvider.ts index 7e1d92537..64811ed12 100644 --- a/web/src/elements/Interface/licenseSummaryProvider.ts +++ b/web/src/elements/Interface/licenseSummaryProvider.ts @@ -10,14 +10,14 @@ type Constructor = abstract new (...args: any[]) => T; export function WithLicenseSummary>( superclass: T, - subscribe = true + subscribe = true, ) { abstract class WithEnterpriseProvider extends superclass { @consume({ context: authentikEnterpriseContext, subscribe }) public licenseSummary!: LicenseSummary; get hasEnterpriseLicense() { - return false; + return this.licenseSummary?.hasLicense; } }