web: improve notification and API drawers (#12659)

* web: move clear all notification button to header, add empty state

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

* improve sorting for API requests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2025-01-13 22:40:48 +01:00
committed by GitHub
parent baf8f18d54
commit 1f49ee77df
3 changed files with 52 additions and 37 deletions

View File

@ -12,6 +12,7 @@ import {
export const CSRFHeaderName = "X-authentik-CSRF"; export const CSRFHeaderName = "X-authentik-CSRF";
export interface RequestInfo { export interface RequestInfo {
time: number;
method: string; method: string;
path: string; path: string;
status: number; status: number;
@ -47,6 +48,7 @@ export class CSRFMiddleware implements Middleware {
export class EventMiddleware implements Middleware { export class EventMiddleware implements Middleware {
post?(context: ResponseContext): Promise<Response | void> { post?(context: ResponseContext): Promise<Response | void> {
const request: RequestInfo = { const request: RequestInfo = {
time: new Date().getTime(),
method: (context.init.method || "GET").toUpperCase(), method: (context.init.method || "GET").toUpperCase(),
path: context.url, path: context.url,
status: context.response.status, status: context.response.status,

View File

@ -1,6 +1,7 @@
import { RequestInfo } from "@goauthentik/common/api/middleware"; import { RequestInfo } from "@goauthentik/common/api/middleware";
import { EVENT_API_DRAWER_TOGGLE, EVENT_REQUEST_POST } from "@goauthentik/common/constants"; import { EVENT_API_DRAWER_TOGGLE, EVENT_REQUEST_POST } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { globalAK } from "@goauthentik/common/global";
import { getRelativeTime } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -55,7 +56,8 @@ export class APIDrawer extends AKElement {
constructor() { constructor() {
super(); super();
window.addEventListener(EVENT_REQUEST_POST, ((e: CustomEvent<RequestInfo>) => { window.addEventListener(EVENT_REQUEST_POST, ((e: CustomEvent<RequestInfo>) => {
this.requests.splice(0, 0, e.detail); this.requests.push(e.detail);
this.requests.sort((a, b) => a.time - b.time).reverse();
if (this.requests.length > 50) { if (this.requests.length > 50) {
this.requests.shift(); this.requests.shift();
} }
@ -76,6 +78,9 @@ export class APIDrawer extends AKElement {
href=${item.path} href=${item.path}
>${item.path}</a >${item.path}</a
> >
<div class="pf-c-notification-drawer__list-item-timestamp">
${getRelativeTime(new Date(item.time))}
</div>
</li>`; </li>`;
} }

View File

@ -6,6 +6,7 @@ import { MessageLevel } from "@goauthentik/common/messages";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { getRelativeTime } from "@goauthentik/common/utils"; import { getRelativeTime } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/EmptyState";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
@ -51,9 +52,6 @@ export class NotificationDrawer extends AKElement {
.pf-c-notification-drawer__list-item-description { .pf-c-notification-drawer__list-item-description {
white-space: pre-wrap; white-space: pre-wrap;
} }
.pf-c-notification-drawer__footer {
margin: 1rem;
}
`); `);
} }
@ -142,6 +140,34 @@ export class NotificationDrawer extends AKElement {
</li>`; </li>`;
} }
clearNotifications() {
new EventsApi(DEFAULT_CONFIG).eventsNotificationsMarkAllSeenCreate().then(() => {
showMessage({
level: MessageLevel.success,
message: msg("Successfully cleared notifications"),
});
this.firstUpdated();
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
});
}
renderEmpty() {
return html`<ak-empty-state header=${msg("No notifications found.")}>
<div slot="body">${msg("You don't have any notifications currently.")}</div>
</ak-empty-state>`;
}
render(): TemplateResult { render(): TemplateResult {
if (!this.notifications) { if (!this.notifications) {
return html``; return html``;
@ -156,6 +182,18 @@ export class NotificationDrawer extends AKElement {
<span> ${msg(str`${this.unread} unread`)} </span> <span> ${msg(str`${this.unread} unread`)} </span>
</div> </div>
<div class="pf-c-notification-drawer__header-action"> <div class="pf-c-notification-drawer__header-action">
<div>
<button
@click=${() => {
this.clearNotifications();
}}
class="pf-c-button pf-m-plain"
type="button"
aria-label=${msg("Clear all")}
>
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
<div class="pf-c-notification-drawer__header-action-close"> <div class="pf-c-notification-drawer__header-action-close">
<button <button
@click=${() => { @click=${() => {
@ -177,41 +215,11 @@ export class NotificationDrawer extends AKElement {
</div> </div>
<div class="pf-c-notification-drawer__body"> <div class="pf-c-notification-drawer__body">
<ul class="pf-c-notification-drawer__list"> <ul class="pf-c-notification-drawer__list">
${this.notifications.results.map((n) => this.renderItem(n))} ${this.notifications.pagination.count < 1
? this.renderEmpty()
: this.notifications.results.map((n) => this.renderItem(n))}
</ul> </ul>
</div> </div>
<div class="pf-c-notification-drawer__footer">
<button
@click=${() => {
new EventsApi(DEFAULT_CONFIG)
.eventsNotificationsMarkAllSeenCreate()
.then(() => {
showMessage({
level: MessageLevel.success,
message: msg("Successfully cleared notifications"),
});
this.firstUpdated();
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
});
}}
class="pf-c-button pf-m-primary pf-m-block"
type="button"
aria-label=${msg("Clear all")}
>
${msg("Clear all")}
</button>
</div>
</div> </div>
</div>`; </div>`;
} }