enterprise: add up-to-date license status (#11042)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-08-23 14:05:19 +02:00
committed by GitHub
parent b301048a27
commit 41fbb6dbd7
4 changed files with 127 additions and 9 deletions

View File

@ -5,7 +5,7 @@ from datetime import timedelta
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes 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.decorators import action
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, IntegerField from rest_framework.fields import CharField, IntegerField
@ -86,7 +86,7 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
}, },
) )
@action(detail=False, methods=["GET"]) @action(detail=False, methods=["GET"])
def get_install_id(self, request: Request) -> Response: def install_id(self, request: Request) -> Response:
"""Get install_id""" """Get install_id"""
return Response( return Response(
data={ data={
@ -99,11 +99,22 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
responses={ responses={
200: LicenseSummarySerializer(), 200: LicenseSummarySerializer(),
}, },
parameters=[
OpenApiParameter(
name="cached",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.BOOL,
default=True,
)
],
) )
@action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated]) @action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
def summary(self, request: Request) -> Response: def summary(self, request: Request) -> Response:
"""Get the total license status""" """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) return Response(response.data)
@permission_required(None, ["authentik_enterprise.view_license"]) @permission_required(None, ["authentik_enterprise.view_license"])

View File

@ -5842,9 +5842,9 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/enterprise/license/get_install_id/: /enterprise/license/install_id/:
get: get:
operationId: enterprise_license_get_install_id_retrieve operationId: enterprise_license_install_id_retrieve
description: Get install_id description: Get install_id
tags: tags:
- enterprise - enterprise
@ -5873,6 +5873,12 @@ paths:
get: get:
operationId: enterprise_license_summary_retrieve operationId: enterprise_license_summary_retrieve
description: Get the total license status description: Get the total license status
parameters:
- in: query
name: cached
schema:
type: boolean
default: true
tags: tags:
- enterprise - enterprise
security: security:

View File

@ -30,7 +30,7 @@ export class EnterpriseLicenseForm extends ModelForm<License, string> {
async load(): Promise<void> { async load(): Promise<void> {
this.installID = ( this.installID = (
await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseGetInstallIdRetrieve() await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseInstallIdRetrieve()
).installId; ).installId;
} }

View File

@ -14,7 +14,7 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg, str } from "@lit/localize"; 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 { customElement, property, state } from "lit/decorators.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; 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 PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.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 PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFSplit from "@patternfly/patternfly/layouts/Split/split.css";
import { import {
EnterpriseApi, EnterpriseApi,
@ -70,6 +72,8 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
PFBanner, PFBanner,
PFFormControl, PFFormControl,
PFButton, PFButton,
PFProgress,
PFSplit,
PFCard, PFCard,
css` css`
.pf-m-no-padding-bottom { .pf-m-no-padding-bottom {
@ -84,9 +88,11 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
async apiEndpoint(): Promise<PaginatedResponse<License>> { async apiEndpoint(): Promise<PaginatedResponse<License>> {
this.forecast = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseForecastRetrieve(); 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 = ( this.installID = (
await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseGetInstallIdRetrieve() await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseInstallIdRetrieve()
).installId; ).installId;
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseList( return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseList(
await this.defaultEndpointConfig(), await this.defaultEndpointConfig(),
@ -191,9 +197,104 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
</ak-aggregate-card> </ak-aggregate-card>
</div> </div>
</section> </section>
<section class="pf-c-page__main-section pf-m-no-padding-bottom">
${this.renderCurrentSummary()}
</section>
`; `;
} }
renderSummaryBadge() {
switch (this.summary?.status) {
case LicenseSummaryStatusEnum.Expired:
return html`<ak-label color=${PFColor.Red}>${msg("Expired")}</ak-label>`;
case LicenseSummaryStatusEnum.ExpirySoon:
return html`<ak-label color=${PFColor.Orange}>${msg("Expiring soon")}</ak-label>`;
case LicenseSummaryStatusEnum.Unlicensed:
return html`<ak-label color=${PFColor.Grey}>${msg("Unlicensed")}</ak-label>`;
case LicenseSummaryStatusEnum.ReadOnly:
return html`<ak-label color=${PFColor.Red}>${msg("Read Only")}</ak-label>`;
case LicenseSummaryStatusEnum.Valid:
return html`<ak-label color=${PFColor.Green}>${msg("Valid")}</ak-label>`;
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`<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Current license status")}</div>
<div class="pf-c-card__body pf-l-split pf-m-gutter">
<dl class="pf-l-split__item pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Overall license status")}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.renderSummaryBadge()}
</div>
</dd>
</div>
</dl>
<div class="pf-l-split__item pf-m-fill">
<div class="pf-c-progress">
<div class="pf-c-progress__description">${msg("Internal user usage")}</div>
<div class="pf-c-progress__status" aria-hidden="true">
<span class="pf-c-progress__measure"
>${msg(str`${internalUserPercentage}%`)}</span
>
</div>
<div
class="pf-c-progress__bar"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="${internalUserPercentage}"
>
<div
class="pf-c-progress__indicator"
style="width:${internalUserPercentage}%;"
></div>
</div>
</div>
<div class="pf-c-progress">
<div class="pf-c-progress__description">${msg("External user usage")}</div>
<div class="pf-c-progress__status" aria-hidden="true">
<span class="pf-c-progress__measure"
>${msg(str`${externalUserPercentage}%`)}</span
>
</div>
<div
class="pf-c-progress__bar"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="${externalUserPercentage}"
>
<div
class="pf-c-progress__indicator"
style="width:${externalUserPercentage}%;"
></div>
</div>
</div>
</div>
</div>
</div>`;
}
row(item: License): TemplateResult[] { row(item: License): TemplateResult[] {
let color = PFColor.Green; let color = PFColor.Green;
if (item.expiry) { if (item.expiry) {