enterprise: add up-to-date license status (#11042)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -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"])
|
||||||
|
10
schema.yml
10
schema.yml
@ -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:
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
Reference in New Issue
Block a user