enterprise: UI improvements, better handling of expiry (#10828)

* web/admin: show enterprise banner on the very top

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework license

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix a bunch of things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add some more tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add more tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix middleware

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better api

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add tests for and fix read only mode

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* field name consistency

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-08-09 14:26:38 +02:00
committed by GitHub
parent 3265b4af01
commit 4b5bb77d99
20 changed files with 749 additions and 194 deletions

View File

@ -71,6 +71,12 @@ export class AdminInterface extends EnterpriseAwareInterface {
:host([theme="dark"]) .pf-c-page {
--pf-c-page--BackgroundColor: var(--ak-dark-background);
}
ak-enterprise-status {
grid-area: header;
}
ak-admin-sidebar {
grid-area: nav;
}
`,
];
}
@ -118,6 +124,7 @@ export class AdminInterface extends EnterpriseAwareInterface {
return html` <ak-locale-context>
<div class="pf-c-page">
<ak-enterprise-status interface="admin"></ak-enterprise-status>
<ak-admin-sidebar
class="pf-c-page__sidebar ${classMap(sidebarClasses)}"
></ak-admin-sidebar>

View File

@ -29,6 +29,7 @@ import {
License,
LicenseForecast,
LicenseSummary,
LicenseSummaryStatusEnum,
RbacPermissionsAssignedByUsersListModelEnum,
} from "@goauthentik/api";
@ -182,7 +183,7 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
header=${msg("Expiry")}
subtext=${msg("Cumulative license expiry")}
>
${this.summary?.hasLicense
${this.summary?.status === LicenseSummaryStatusEnum.Unlicensed
? html`<div>${getRelativeTime(this.summary.latestValid)}</div>
<small>${this.summary.latestValid.toLocaleString()}</small>`
: "-"}

View File

@ -4,7 +4,7 @@ import { Constructor } from "@goauthentik/elements/types.js";
import { consume } from "@lit/context";
import type { LitElement } from "lit";
import type { LicenseSummary } from "@goauthentik/api";
import { type LicenseSummary, LicenseSummaryStatusEnum } from "@goauthentik/api";
export function WithLicenseSummary<T extends Constructor<LitElement>>(
superclass: T,
@ -15,7 +15,7 @@ export function WithLicenseSummary<T extends Constructor<LitElement>>(
public licenseSummary!: LicenseSummary;
get hasEnterpriseLicense() {
return this.licenseSummary?.hasLicense;
return this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed;
}
}

View File

@ -138,63 +138,62 @@ export class PageHeader extends WithBrandConfig(AKElement) {
}
render(): TemplateResult {
return html` <ak-enterprise-status interface="admin"></ak-enterprise-status>
<div class="bar">
<button
class="sidebar-trigger pf-c-button pf-m-plain"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
>
<i class="fas fa-bars"></i>
</button>
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<slot name="icon">${this.renderIcon()}</slot>&nbsp;
<slot name="header">${this.header}</slot>
</h1>
${this.description ? html`<p>${this.description}</p>` : html``}
</div>
</section>
<button
class="notification-trigger pf-c-button pf-m-plain"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_API_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
>
<pf-tooltip position="top" content=${msg("Open API drawer")}>
<i class="fas fa-code"></i>
</pf-tooltip>
</button>
<button
class="notification-trigger pf-c-button pf-m-plain ${this.hasNotifications
? "has-notifications"
: ""}"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
>
<pf-tooltip position="top" content=${msg("Open Notification drawer")}>
<i class="fas fa-bell"></i>
</pf-tooltip>
</button>
</div>`;
return html`<div class="bar">
<button
class="sidebar-trigger pf-c-button pf-m-plain"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
>
<i class="fas fa-bars"></i>
</button>
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<slot name="icon">${this.renderIcon()}</slot>&nbsp;
<slot name="header">${this.header}</slot>
</h1>
${this.description ? html`<p>${this.description}</p>` : html``}
</div>
</section>
<button
class="notification-trigger pf-c-button pf-m-plain"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_API_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
>
<pf-tooltip position="top" content=${msg("Open API drawer")}>
<i class="fas fa-code"></i>
</pf-tooltip>
</button>
<button
class="notification-trigger pf-c-button pf-m-plain ${this.hasNotifications
? "has-notifications"
: ""}"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
>
<pf-tooltip position="top" content=${msg("Open Notification drawer")}>
<i class="fas fa-bell"></i>
</pf-tooltip>
</button>
</div>`;
}
}

View File

@ -7,6 +7,8 @@ import { customElement, property } from "lit/decorators.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
import { LicenseSummaryStatusEnum } from "@goauthentik/api";
@customElement("ak-enterprise-status")
export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
@property()
@ -17,26 +19,58 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
}
renderBanner(): TemplateResult {
let message = "";
switch (this.licenseSummary.status) {
case LicenseSummaryStatusEnum.LimitExceededAdmin:
case LicenseSummaryStatusEnum.LimitExceededUser:
message = msg(
"Warning: The current user count has exceeded the configured licenses.",
);
break;
case LicenseSummaryStatusEnum.Expired:
message = msg("Warning: One or more license(s) have expired.");
break;
case LicenseSummaryStatusEnum.ExpirySoon:
message = msg(
"Warning: One or more license(s) will expire within the next 2 weeks.",
);
break;
case LicenseSummaryStatusEnum.ReadOnly:
message = msg(
"Caution: This authentik instance has entered read-only mode due to expired/exceeded licenses.",
);
break;
default:
break;
}
return html`<div
class="pf-c-banner ${this.licenseSummary?.readOnly ? "pf-m-red" : "pf-m-gold"}"
class="pf-c-banner ${this.licenseSummary?.status === LicenseSummaryStatusEnum.ReadOnly
? "pf-m-red"
: "pf-m-gold"}"
>
${msg("Warning: The current user count has exceeded the configured licenses.")}
${message}
<a href="/if/admin/#/enterprise/licenses"> ${msg("Click here for more info.")} </a>
</div>`;
}
render(): TemplateResult {
switch (this.interface.toLowerCase()) {
case "admin":
if (this.licenseSummary?.showAdminWarning || this.licenseSummary?.readOnly) {
switch (this.licenseSummary.status) {
case LicenseSummaryStatusEnum.LimitExceededUser:
if (this.interface.toLowerCase() === "user") {
return this.renderBanner();
}
break;
case "user":
if (this.licenseSummary?.showUserWarning || this.licenseSummary?.readOnly) {
case LicenseSummaryStatusEnum.ExpirySoon:
case LicenseSummaryStatusEnum.Expired:
case LicenseSummaryStatusEnum.LimitExceededAdmin:
if (this.interface.toLowerCase() === "admin") {
return this.renderBanner();
}
break;
case LicenseSummaryStatusEnum.ReadOnly:
return this.renderBanner();
default:
break;
}
return html``;
}

View File

@ -42,7 +42,6 @@ export class Sidebar extends AKElement {
nav {
display: flex;
flex-direction: column;
max-height: 100vh;
height: 100%;
overflow-y: hidden;
}