web: cleanup messages implementation (#386)
* web: rebuild messages without template objects * web: show error message when ws connection fails * web: show error message when siteshell page not found * web: fix spinner size for loading * web: fix linting error
This commit is contained in:
		@ -3,7 +3,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
<ak-messages></ak-messages>
 | 
			
		||||
<ak-message-container></ak-message-container>
 | 
			
		||||
<div class="pf-c-page">
 | 
			
		||||
    <a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
 | 
			
		||||
    {% block page_content %}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<ak-messages></ak-messages>
 | 
			
		||||
<ak-message-container></ak-message-container>
 | 
			
		||||
 | 
			
		||||
<header class="pf-c-login__main-header">
 | 
			
		||||
    <h1 class="pf-c-title pf-m-3xl">
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@
 | 
			
		||||
        </filter>
 | 
			
		||||
    </svg>
 | 
			
		||||
</div>
 | 
			
		||||
<ak-messages></ak-messages>
 | 
			
		||||
<ak-message-container></ak-message-container>
 | 
			
		||||
<div class="pf-c-login">
 | 
			
		||||
    <div class="pf-c-login__container">
 | 
			
		||||
        <header class="pf-c-login__header">
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										53
									
								
								web/src/elements/messages/Message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								web/src/elements/messages/Message.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
import { customElement, html, LitElement, property, TemplateResult } from "lit-element";
 | 
			
		||||
 | 
			
		||||
export interface APIMessage {
 | 
			
		||||
    level_tag: string;
 | 
			
		||||
    tags?: string;
 | 
			
		||||
    message: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const LEVEL_ICON_MAP: { [key: string]: string } = {
 | 
			
		||||
    error: "fas fa-exclamation-circle",
 | 
			
		||||
    warning: "fas fa-exclamation-triangle",
 | 
			
		||||
    success: "fas fa-check-circle",
 | 
			
		||||
    info: "fas fa-info",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@customElement("ak-message")
 | 
			
		||||
export class Message extends LitElement {
 | 
			
		||||
 | 
			
		||||
    @property({attribute: false})
 | 
			
		||||
    message?: APIMessage;
 | 
			
		||||
 | 
			
		||||
    @property({type: Number})
 | 
			
		||||
    removeAfter = 3000;
 | 
			
		||||
 | 
			
		||||
    @property({attribute: false})
 | 
			
		||||
    onRemove?: (m: APIMessage) => void;
 | 
			
		||||
 | 
			
		||||
    createRenderRoot(): ShadowRoot | Element {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    firstUpdated(): void {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            if (!this.message) return;
 | 
			
		||||
            if (!this.onRemove) return;
 | 
			
		||||
            this.onRemove(this.message);
 | 
			
		||||
        }, this.removeAfter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
        return html`<li class="pf-c-alert-group__item">
 | 
			
		||||
            <div class="pf-c-alert pf-m-${this.message?.level_tag} ${this.message?.level_tag === "error" ? "pf-m-danger" : ""}">
 | 
			
		||||
                <div class="pf-c-alert__icon">
 | 
			
		||||
                    <i class="${this.message ? LEVEL_ICON_MAP[this.message.level_tag] : ""}"></i>
 | 
			
		||||
                </div>
 | 
			
		||||
                <p class="pf-c-alert__title">
 | 
			
		||||
                    ${this.message?.message}
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </li>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,27 +1,25 @@
 | 
			
		||||
import { LitElement, html, customElement, TemplateResult } from "lit-element";
 | 
			
		||||
import { DefaultClient } from "../api/client";
 | 
			
		||||
import { gettext } from "django";
 | 
			
		||||
import { LitElement, html, customElement, TemplateResult, property } from "lit-element";
 | 
			
		||||
import { DefaultClient } from "../../api/client";
 | 
			
		||||
import "./Message";
 | 
			
		||||
import { APIMessage } from "./Message";
 | 
			
		||||
 | 
			
		||||
const LEVEL_ICON_MAP: { [key: string]: string } = {
 | 
			
		||||
    error: "fas fa-exclamation-circle",
 | 
			
		||||
    warning: "fas fa-exclamation-triangle",
 | 
			
		||||
    success: "fas fa-check-circle",
 | 
			
		||||
    info: "fas fa-info",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ID = function (prefix: string) {
 | 
			
		||||
    return prefix + Math.random().toString(36).substr(2, 9);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface Message {
 | 
			
		||||
    level_tag: string;
 | 
			
		||||
    tags: string;
 | 
			
		||||
    message: string;
 | 
			
		||||
export function showMessage(message: APIMessage): void {
 | 
			
		||||
    const container = document.querySelector<MessageContainer>("ak-message-container");
 | 
			
		||||
    if (!container) {
 | 
			
		||||
        throw new Error("failed to find message container");
 | 
			
		||||
    }
 | 
			
		||||
    container.messages.push(message);
 | 
			
		||||
    container.requestUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@customElement("ak-messages")
 | 
			
		||||
export class Messages extends LitElement {
 | 
			
		||||
@customElement("ak-message-container")
 | 
			
		||||
export class MessageContainer extends LitElement {
 | 
			
		||||
    url = DefaultClient.makeUrl(["root", "messages"]);
 | 
			
		||||
 | 
			
		||||
    @property({attribute: false})
 | 
			
		||||
    messages: APIMessage[] = [];
 | 
			
		||||
 | 
			
		||||
    messageSocket?: WebSocket;
 | 
			
		||||
    retryDelay = 200;
 | 
			
		||||
 | 
			
		||||
@ -52,6 +50,12 @@ export class Messages extends LitElement {
 | 
			
		||||
        });
 | 
			
		||||
        this.messageSocket.addEventListener("close", (e) => {
 | 
			
		||||
            console.debug(`authentik/messages: closed ws connection: ${e}`);
 | 
			
		||||
            if (this.retryDelay > 3000) {
 | 
			
		||||
                showMessage({
 | 
			
		||||
                    level_tag: "error",
 | 
			
		||||
                    message: gettext("Connection error, reconnecting...")
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                console.debug(`authentik/messages: reconnecting ws in ${this.retryDelay}ms`);
 | 
			
		||||
                this.connect();
 | 
			
		||||
@ -60,7 +64,8 @@ export class Messages extends LitElement {
 | 
			
		||||
        });
 | 
			
		||||
        this.messageSocket.addEventListener("message", (e) => {
 | 
			
		||||
            const data = JSON.parse(e.data);
 | 
			
		||||
            this.renderMessage(data);
 | 
			
		||||
            this.messages.push(data);
 | 
			
		||||
            this.requestUpdate();
 | 
			
		||||
        });
 | 
			
		||||
        this.messageSocket.addEventListener("error", (e) => {
 | 
			
		||||
            console.warn(`authentik/messages: error ${e}`);
 | 
			
		||||
@ -75,38 +80,25 @@ export class Messages extends LitElement {
 | 
			
		||||
        console.debug("authentik/messages: fetching messages over direct api");
 | 
			
		||||
        return fetch(this.url)
 | 
			
		||||
            .then((r) => r.json())
 | 
			
		||||
            .then((r: Message[]) => {
 | 
			
		||||
                r.forEach((m: Message) => {
 | 
			
		||||
                    this.renderMessage(m);
 | 
			
		||||
            .then((r: APIMessage[]) => {
 | 
			
		||||
                r.forEach((m: APIMessage) => {
 | 
			
		||||
                    this.messages.push(m);
 | 
			
		||||
                    this.requestUpdate();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderMessage(message: Message): void {
 | 
			
		||||
        const container = <HTMLElement>this.querySelector(".pf-c-alert-group");
 | 
			
		||||
        if (!container) {
 | 
			
		||||
            console.warn("authentik/messages: failed to find container");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const id = ID("ak-message");
 | 
			
		||||
        const el = document.createElement("template");
 | 
			
		||||
        el.innerHTML = `<li id=${id} class="pf-c-alert-group__item">
 | 
			
		||||
            <div class="pf-c-alert pf-m-${message.level_tag} ${message.level_tag === "error" ? "pf-m-danger" : ""}">
 | 
			
		||||
                <div class="pf-c-alert__icon">
 | 
			
		||||
                    <i class="${LEVEL_ICON_MAP[message.level_tag]}"></i>
 | 
			
		||||
                </div>
 | 
			
		||||
                <p class="pf-c-alert__title">
 | 
			
		||||
                    ${message.message}
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </li>`;
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            this.querySelector(`#${id}`)?.remove();
 | 
			
		||||
        }, 1500);
 | 
			
		||||
        container.appendChild(el.content.firstChild!); // eslint-disable-line
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
        return html`<ul class="pf-c-alert-group pf-m-toast"></ul>`;
 | 
			
		||||
        return html`<ul class="pf-c-alert-group pf-m-toast">
 | 
			
		||||
            ${this.messages.map((m) => {
 | 
			
		||||
        return html`<ak-message
 | 
			
		||||
                    .message=${m}
 | 
			
		||||
                    .onRemove=${(m: APIMessage) => {
 | 
			
		||||
        this.messages = this.messages.filter((v) => v !== m);
 | 
			
		||||
        this.requestUpdate();
 | 
			
		||||
    }}>
 | 
			
		||||
                </ak-message>`;
 | 
			
		||||
    })}
 | 
			
		||||
        </ul>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -39,7 +39,7 @@
 | 
			
		||||
        <script src="/static/dist/main.js" type="module"></script>
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <ak-messages></ak-messages>
 | 
			
		||||
        <ak-message-container></ak-message-container>
 | 
			
		||||
        <div class="pf-c-page">
 | 
			
		||||
            <a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content"
 | 
			
		||||
                >Skip to content</a
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,8 @@ import { gettext } from "django";
 | 
			
		||||
import { html, LitElement, TemplateResult } from "lit-element";
 | 
			
		||||
import { SidebarItem } from "../elements/sidebar/Sidebar";
 | 
			
		||||
 | 
			
		||||
import "../elements/Messages";
 | 
			
		||||
import "../elements/router/RouterOutlet";
 | 
			
		||||
import "../elements/messages/MessageContainer";
 | 
			
		||||
 | 
			
		||||
export abstract class Interface extends LitElement {
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ export abstract class Interface extends LitElement {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
        return html`<ak-messages></ak-messages>
 | 
			
		||||
        return html`<ak-message-container></ak-message-container>
 | 
			
		||||
            <div class="pf-c-page">
 | 
			
		||||
                <a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">${gettext("Skip to content")}</a>
 | 
			
		||||
                <ak-sidebar class="pf-c-page__sidebar" .items=${this.sidebar}>
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ import "./elements/EmptyState";
 | 
			
		||||
import "./elements/cards/AggregateCard";
 | 
			
		||||
import "./elements/cards/AggregatePromiseCard";
 | 
			
		||||
import "./elements/CodeMirror";
 | 
			
		||||
import "./elements/Messages";
 | 
			
		||||
import "./elements/messages/MessageContainer";
 | 
			
		||||
import "./elements/Spinner";
 | 
			
		||||
import "./elements/Tabs";
 | 
			
		||||
import "./elements/router/RouterOutlet";
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,8 @@ import SpinnerStyle from "@patternfly/patternfly/components/Spinner/spinner.css"
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import BackdropStyle from "@patternfly/patternfly/components/Backdrop/backdrop.css";
 | 
			
		||||
import { SpinnerSize } from "../../elements/Spinner";
 | 
			
		||||
import { showMessage } from "../../elements/messages/MessageContainer";
 | 
			
		||||
import { gettext } from "django";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-site-shell")
 | 
			
		||||
export class SiteShell extends LitElement {
 | 
			
		||||
@ -64,6 +66,10 @@ export class SiteShell extends LitElement {
 | 
			
		||||
                }
 | 
			
		||||
                console.debug(`authentik/site-shell: Request failed ${this._url}`);
 | 
			
		||||
                window.location.hash = "#/";
 | 
			
		||||
                showMessage({
 | 
			
		||||
                    level_tag: "error",
 | 
			
		||||
                    message: gettext(`Request failed: ${r.statusText}`),
 | 
			
		||||
                });
 | 
			
		||||
                throw new Error("Request failed");
 | 
			
		||||
            })
 | 
			
		||||
            .then((r) => r.text())
 | 
			
		||||
@ -115,7 +121,7 @@ export class SiteShell extends LitElement {
 | 
			
		||||
            html`<div class="pf-c-backdrop">
 | 
			
		||||
                    <div class="pf-l-bullseye">
 | 
			
		||||
                        <div class="pf-l-bullseye__item">
 | 
			
		||||
                            <ak-spinner size=${SpinnerSize.Large}></ak-spinner>
 | 
			
		||||
                            <ak-spinner size=${SpinnerSize.XLarge}></ak-spinner>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>`
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ export function loading<T>(v: T, actual: TemplateResult): TemplateResult {
 | 
			
		||||
            <div class="pf-c-empty-state__content">
 | 
			
		||||
                <div class="pf-l-bullseye">
 | 
			
		||||
                    <div class="pf-l-bullseye__item">
 | 
			
		||||
                        <ak-spinner size="${SpinnerSize.Large}"></ak-spinner>
 | 
			
		||||
                        <ak-spinner size="${SpinnerSize.XLarge}"></ak-spinner>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user