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:
Jens L
2020-12-12 20:46:02 +01:00
committed by GitHub
parent 488e8f769a
commit 0a874c98cb
10 changed files with 108 additions and 57 deletions

View 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>`;
}
}

View File

@ -0,0 +1,104 @@
import { gettext } from "django";
import { LitElement, html, customElement, TemplateResult, property } from "lit-element";
import { DefaultClient } from "../../api/client";
import "./Message";
import { APIMessage } from "./Message";
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-message-container")
export class MessageContainer extends LitElement {
url = DefaultClient.makeUrl(["root", "messages"]);
@property({attribute: false})
messages: APIMessage[] = [];
messageSocket?: WebSocket;
retryDelay = 200;
createRenderRoot(): ShadowRoot | Element {
return this;
}
constructor() {
super();
try {
this.connect();
} catch (error) {
console.warn(`authentik/messages: failed to connect to ws ${error}`);
}
}
firstUpdated(): void {
this.fetchMessages();
}
connect(): void {
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
window.location.host
}/ws/client/`;
this.messageSocket = new WebSocket(wsUrl);
this.messageSocket.addEventListener("open", () => {
console.debug(`authentik/messages: connected to ${wsUrl}`);
});
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();
}, this.retryDelay);
this.retryDelay = this.retryDelay * 2;
});
this.messageSocket.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
this.messages.push(data);
this.requestUpdate();
});
this.messageSocket.addEventListener("error", (e) => {
console.warn(`authentik/messages: error ${e}`);
this.retryDelay = this.retryDelay * 2;
});
}
/* Fetch messages which were stored in the session.
* This mostly gets messages which were created when the user arrives/leaves the site
* and especially the login flow */
fetchMessages(): Promise<void> {
console.debug("authentik/messages: fetching messages over direct api");
return fetch(this.url)
.then((r) => r.json())
.then((r: APIMessage[]) => {
r.forEach((m: APIMessage) => {
this.messages.push(m);
this.requestUpdate();
});
});
}
render(): TemplateResult {
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>`;
}
}