diff --git a/authentik/enterprise/api.py b/authentik/enterprise/api.py index ca7c18cb1e35..367430504440 100644 --- a/authentik/enterprise/api.py +++ b/authentik/enterprise/api.py @@ -5,7 +5,7 @@ from django.utils.timezone import now from django.utils.translation import gettext as _ from drf_spectacular.types import OpenApiTypes -from drf_spectacular.utils import extend_schema, inline_serializer +from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer from rest_framework.decorators import action from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, IntegerField @@ -86,7 +86,7 @@ class LicenseViewSet(UsedByMixin, ModelViewSet): }, ) @action(detail=False, methods=["GET"]) - def get_install_id(self, request: Request) -> Response: + def install_id(self, request: Request) -> Response: """Get install_id""" return Response( data={ @@ -99,11 +99,22 @@ def get_install_id(self, request: Request) -> Response: responses={ 200: LicenseSummarySerializer(), }, + parameters=[ + OpenApiParameter( + name="cached", + location=OpenApiParameter.QUERY, + type=OpenApiTypes.BOOL, + default=True, + ) + ], ) @action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated]) def summary(self, request: Request) -> Response: """Get the total license status""" - response = LicenseSummarySerializer(instance=LicenseKey.cached_summary()) + summary = LicenseKey.cached_summary() + if request.query_params.get("cached").lower() == "false": + summary = LicenseKey.get_total().summary() + response = LicenseSummarySerializer(instance=summary) return Response(response.data) @permission_required(None, ["authentik_enterprise.view_license"]) diff --git a/schema.yml b/schema.yml index 8e809bcc57ba..000dcfa911f0 100644 --- a/schema.yml +++ b/schema.yml @@ -5842,9 +5842,9 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' - /enterprise/license/get_install_id/: + /enterprise/license/install_id/: get: - operationId: enterprise_license_get_install_id_retrieve + operationId: enterprise_license_install_id_retrieve description: Get install_id tags: - enterprise @@ -5873,6 +5873,12 @@ paths: get: operationId: enterprise_license_summary_retrieve description: Get the total license status + parameters: + - in: query + name: cached + schema: + type: boolean + default: true tags: - enterprise security: diff --git a/web/src/admin/enterprise/EnterpriseLicenseForm.ts b/web/src/admin/enterprise/EnterpriseLicenseForm.ts index d1ce5e38af1f..363af1f4fbed 100644 --- a/web/src/admin/enterprise/EnterpriseLicenseForm.ts +++ b/web/src/admin/enterprise/EnterpriseLicenseForm.ts @@ -30,7 +30,7 @@ export class EnterpriseLicenseForm extends ModelForm { async load(): Promise { this.installID = ( - await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseGetInstallIdRetrieve() + await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseInstallIdRetrieve() ).installId; } diff --git a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts index 84dad792922b..8da628dc555e 100644 --- a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts +++ b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts @@ -14,7 +14,7 @@ import { TablePage } from "@goauthentik/elements/table/TablePage"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html } from "lit"; +import { CSSResult, TemplateResult, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; @@ -22,7 +22,9 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFProgress from "@patternfly/patternfly/components/Progress/progress.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFSplit from "@patternfly/patternfly/layouts/Split/split.css"; import { EnterpriseApi, @@ -70,6 +72,8 @@ export class EnterpriseLicenseListPage extends TablePage { PFBanner, PFFormControl, PFButton, + PFProgress, + PFSplit, PFCard, css` .pf-m-no-padding-bottom { @@ -84,9 +88,11 @@ export class EnterpriseLicenseListPage extends TablePage { async apiEndpoint(): Promise> { this.forecast = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseForecastRetrieve(); - this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve(); + this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve({ + cached: false, + }); this.installID = ( - await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseGetInstallIdRetrieve() + await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseInstallIdRetrieve() ).installId; return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseList( await this.defaultEndpointConfig(), @@ -191,9 +197,104 @@ export class EnterpriseLicenseListPage extends TablePage { +
+ ${this.renderCurrentSummary()} +
`; } + renderSummaryBadge() { + switch (this.summary?.status) { + case LicenseSummaryStatusEnum.Expired: + return html`${msg("Expired")}`; + case LicenseSummaryStatusEnum.ExpirySoon: + return html`${msg("Expiring soon")}`; + case LicenseSummaryStatusEnum.Unlicensed: + return html`${msg("Unlicensed")}`; + case LicenseSummaryStatusEnum.ReadOnly: + return html`${msg("Read Only")}`; + case LicenseSummaryStatusEnum.Valid: + return html`${msg("Valid")}`; + default: + return nothing; + } + } + + renderCurrentSummary() { + if (!this.forecast || !this.summary) { + return html`${msg("Loading")}`; + } + const internalUserPercentage = + this.summary.internalUsers > 0 + ? Math.ceil(this.forecast.internalUsers / (this.summary.internalUsers / 100)) + : 0; + const externalUserPercentage = + this.summary.externalUsers > 0 + ? Math.ceil(this.forecast.externalUsers / (this.summary.externalUsers / 100)) + : 0; + return html`
+
${msg("Current license status")}
+
+
+
+
+ ${msg("Overall license status")} +
+
+
+ ${this.renderSummaryBadge()} +
+
+
+
+
+
+
${msg("Internal user usage")}
+ +
+
+
+
+
+
${msg("External user usage")}
+ +
+
+
+
+
+
+
`; + } + row(item: License): TemplateResult[] { let color = PFColor.Green; if (item.expiry) {