mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
3842641abd
* ## What
window.authentik.flow = {
"layout": "{{ flow.layout }}",
+ "background": "{{ flow.background }}",
+ "title": "{{ flow.title }}",
};
Amends the `flow.html` template and `GlobalAuthentik` parser to include new parameters, `background` and `title`, in the flow-specific part of the configuration written to the HTML `<head>` object, and to provide those parameters to client code.
## Why
The `layout` is start-up critical: it tells the Flow interface how the admin wants the Flow page to look, and allows the HTML and CSS to be pre-aligned to that condition. `layout` is determined on a per-Flow bases, not a per-Stage basis; Flows are derived from a tuple of `(Brand, Application?)`, where the opening policy *may* direct a user to a different flow if the user reached authentik via a redirect from a specific application, but will otherwise fall back to the default Flow for the Brand.
The `background` is a field that is required if the `Flow`’s layout is of type `frame_background`; in this case, the part of the viewport not dedicated to the FlowExecutor is reserved for an `<iframe>` that will be filled in with whatever the administrator specifies. Although this gives it the same priority as `layout` (whether it’s provided or undefined) for describing the [chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome) around a challenge, it is currently not provided to the application in the start-up config; it is provided in the `challenge` and renders the IFrame as part of the initial challenge.
This patch fixes that; if `layout` is provided, `background` ought to be as well, even if it’s empty. The execution of a Challenge ought not have any influence over the look and feel of the Flow-defined appearance *around* that Challenge.
I have added `title` as well; with that, all of the current theme-and-appearance related configuration details are placed into `<head>` and can be removed from the FlowExecutor.
Server-side, `background` is currently specified: `background = FileField(blank=True, default="")` which is … interesting since we also appear to store URLs in it. I don’t see anything in the FlowSerializer that would change that from a client’s point of view.
This patch furthers the effort to separate flow execution from flow presentation.
- \[🐰\] The code has been formatted (`make web`)
* web/maint: Move notifications into the components folder; adjust imports accordingly
# What
1. Moves the notifications folder from elements to components: the API and Notifications drawers are API-aware. If we want to separate that out and do something unique, we can, but for now, let’s just get things where they should be.
2. Adjusts all the imports correctly.
3. (Minor): Mutating the array and then calling `requestUpdate()`, especially when the array is then sorted-and-reversed, doesn’t save anything over creating a new array with the new item shifted onto the head, sorted once, and then saved to the property, which triggers an update automatically.
229 lines
8.9 KiB
TypeScript
229 lines
8.9 KiB
TypeScript
import "#elements/EmptyState";
|
|
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
|
|
|
import { isAPIResultReady } from "#common/api/responses";
|
|
import { pluckErrorDetail } from "#common/errors/network";
|
|
import { globalAK } from "#common/global";
|
|
import { actionToLabel, severityToLevel } from "#common/labels";
|
|
import { formatElapsedTime } from "#common/temporal";
|
|
|
|
import { AKElement } from "#elements/Base";
|
|
import { WithNotifications } from "#elements/mixins/notifications";
|
|
import { WithSession } from "#elements/mixins/session";
|
|
import { SlottedTemplateResult } from "#elements/types";
|
|
import { ifPresent } from "#elements/utils/attributes";
|
|
|
|
import { AKDrawerChangeEvent } from "#components/notifications/events";
|
|
|
|
import { Notification } from "@goauthentik/api";
|
|
|
|
import { msg, str } from "@lit/localize";
|
|
import { css, CSSResult, html, nothing, TemplateResult } from "lit";
|
|
import { customElement } from "lit/decorators.js";
|
|
import { guard } from "lit/directives/guard.js";
|
|
import { repeat } from "lit/directives/repeat.js";
|
|
|
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
|
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
|
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
|
import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";
|
|
|
|
@customElement("ak-notification-drawer")
|
|
export class NotificationDrawer extends WithNotifications(WithSession(AKElement)) {
|
|
static styles: CSSResult[] = [
|
|
PFButton,
|
|
PFNotificationDrawer,
|
|
PFContent,
|
|
PFDropdown,
|
|
css`
|
|
.pf-c-drawer__body {
|
|
height: 100%;
|
|
}
|
|
|
|
.pf-c-notification-drawer__body {
|
|
flex-grow: 1;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.pf-c-notification-drawer__header {
|
|
align-items: center;
|
|
}
|
|
|
|
.pf-c-notification-drawer__header-action,
|
|
.pf-c-notification-drawer__header-action-close,
|
|
.pf-c-notification-drawer__header-action-close > .pf-c-button.pf-m-plain {
|
|
height: 100%;
|
|
}
|
|
|
|
.pf-c-notification-drawer__list-item-description {
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.pf-c-notification-drawer__list-item-action {
|
|
display: flex;
|
|
flex-flow: row;
|
|
align-items: start;
|
|
gap: var(--pf-global--spacer--sm);
|
|
}
|
|
`,
|
|
];
|
|
|
|
#APIBase = globalAK().api.base;
|
|
|
|
//#region Rendering
|
|
|
|
protected renderHyperlink(item: Notification) {
|
|
if (!item.hyperlink) {
|
|
return nothing;
|
|
}
|
|
|
|
return html`<small><a href=${item.hyperlink}>${item.hyperlinkLabel}</a></small>`;
|
|
}
|
|
|
|
#renderItem = (item: Notification): TemplateResult => {
|
|
const label = actionToLabel(item.event?.action);
|
|
const level = severityToLevel(item.severity);
|
|
|
|
// There's little information we can have to determine if the body
|
|
// contains code, but if it looks like JSON, we can at least style it better.
|
|
const code = item.body.includes("{");
|
|
|
|
return html`<li
|
|
class="pf-c-notification-drawer__list-item"
|
|
data-notification-action=${ifPresent(item.event?.action)}
|
|
>
|
|
<div class="pf-c-notification-drawer__list-item-header">
|
|
<span class="pf-c-notification-drawer__list-item-header-icon ${level}">
|
|
<i class="fas fa-info-circle" aria-hidden="true"></i>
|
|
</span>
|
|
<h2 class="pf-c-notification-drawer__list-item-header-title">${label}</h2>
|
|
</div>
|
|
<div class="pf-c-notification-drawer__list-item-action">
|
|
${item.event &&
|
|
html`
|
|
<a
|
|
class="pf-c-dropdown__toggle pf-m-plain"
|
|
href="${this.#APIBase}if/admin/#/events/log/${item.event?.pk}"
|
|
aria-label=${msg(str`View details for ${label}`)}
|
|
>
|
|
<pf-tooltip position="top" content=${msg("Show details")}>
|
|
<i class="fas fa-share-square" aria-hidden="true"></i>
|
|
</pf-tooltip>
|
|
</a>
|
|
`}
|
|
<button
|
|
class="pf-c-dropdown__toggle pf-m-plain"
|
|
type="button"
|
|
@click=${() => this.markAsRead(item.pk)}
|
|
aria-label=${msg("Mark as read")}
|
|
>
|
|
<i class="fas fa-times" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
${code && item.event?.context
|
|
? html`<pre class="pf-c-notification-drawer__list-item-description">
|
|
${JSON.stringify(item.event.context, null, 2)}</pre
|
|
>`
|
|
: html`<p class="pf-c-notification-drawer__list-item-description">${item.body}</p>`}
|
|
<small class="pf-c-notification-drawer__list-item-timestamp"
|
|
><pf-tooltip position="top" .content=${item.created?.toLocaleString()}>
|
|
${formatElapsedTime(item.created!)}
|
|
</pf-tooltip></small
|
|
>
|
|
${this.renderHyperlink(item)}
|
|
</li>`;
|
|
};
|
|
|
|
protected renderEmpty() {
|
|
return html`<ak-empty-state
|
|
><span>${msg("No notifications found.")}</span>
|
|
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
|
</ak-empty-state>`;
|
|
}
|
|
|
|
protected renderBody() {
|
|
return guard([this.notifications], () => {
|
|
if (this.notifications.loading) {
|
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
|
}
|
|
|
|
if (this.notifications.error) {
|
|
return html`<ak-empty-state icon="fa-ban"
|
|
><span>${msg("Failed to fetch notifications.")}</span>
|
|
<div slot="body">${pluckErrorDetail(this.notifications.error)}</div>
|
|
</ak-empty-state>`;
|
|
}
|
|
|
|
if (!this.notificationCount) {
|
|
return this.renderEmpty();
|
|
}
|
|
|
|
return html`<ul class="pf-c-notification-drawer__list" role="list">
|
|
${repeat(
|
|
this.notifications.results,
|
|
(n) => n.pk,
|
|
(n) => this.#renderItem(n),
|
|
)}
|
|
</ul>`;
|
|
});
|
|
}
|
|
|
|
protected override render(): SlottedTemplateResult {
|
|
const unreadCount = isAPIResultReady(this.notifications) ? this.notificationCount : 0;
|
|
|
|
return html`<aside
|
|
class="pf-c-drawer__body pf-m-no-padding"
|
|
aria-labelledby="notification-drawer-title"
|
|
>
|
|
<div class="pf-c-notification-drawer">
|
|
<header class="pf-c-notification-drawer__header">
|
|
<div class="text">
|
|
<h2
|
|
id="notification-drawer-title"
|
|
class="pf-c-notification-drawer__header-title"
|
|
>
|
|
${msg("Notifications")}
|
|
</h2>
|
|
<span aria-live="polite" aria-atomic="true">
|
|
${msg(str`${unreadCount} unread`, {
|
|
id: "notification-unread-count",
|
|
desc: "Indicates the number of unread notifications in the notification drawer",
|
|
})}
|
|
</span>
|
|
</div>
|
|
<div class="pf-c-notification-drawer__header-action">
|
|
<button
|
|
@click=${this.clearNotifications}
|
|
class="pf-c-button pf-m-plain"
|
|
type="button"
|
|
aria-label=${msg("Clear all notifications", {
|
|
id: "notification-drawer-clear-all",
|
|
})}
|
|
?disabled=${!unreadCount}
|
|
>
|
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
|
</button>
|
|
<button
|
|
@click=${AKDrawerChangeEvent.dispatchNotificationsToggle}
|
|
class="pf-c-button pf-m-plain"
|
|
type="button"
|
|
aria-label=${msg("Close notification drawer", {
|
|
id: "notification-drawer-close",
|
|
})}
|
|
>
|
|
<i class="fas fa-times" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<div class="pf-c-notification-drawer__body">${this.renderBody()}</div>
|
|
</div>
|
|
</aside>`;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ak-notification-drawer": NotificationDrawer;
|
|
}
|
|
}
|