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:
		
							
								
								
									
										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>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										104
									
								
								web/src/elements/messages/MessageContainer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								web/src/elements/messages/MessageContainer.ts
									
									
									
									
									
										Normal 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>`; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L