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:
parent
488e8f769a
commit
0a874c98cb
|
@ -3,7 +3,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<ak-messages></ak-messages>
|
<ak-message-container></ak-message-container>
|
||||||
<div class="pf-c-page">
|
<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>
|
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<ak-messages></ak-messages>
|
<ak-message-container></ak-message-container>
|
||||||
|
|
||||||
<header class="pf-c-login__main-header">
|
<header class="pf-c-login__main-header">
|
||||||
<h1 class="pf-c-title pf-m-3xl">
|
<h1 class="pf-c-title pf-m-3xl">
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</filter>
|
</filter>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<ak-messages></ak-messages>
|
<ak-message-container></ak-message-container>
|
||||||
<div class="pf-c-login">
|
<div class="pf-c-login">
|
||||||
<div class="pf-c-login__container">
|
<div class="pf-c-login__container">
|
||||||
<header class="pf-c-login__header">
|
<header class="pf-c-login__header">
|
||||||
|
|
|
@ -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 { gettext } from "django";
|
||||||
import { DefaultClient } from "../api/client";
|
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 } = {
|
export function showMessage(message: APIMessage): void {
|
||||||
error: "fas fa-exclamation-circle",
|
const container = document.querySelector<MessageContainer>("ak-message-container");
|
||||||
warning: "fas fa-exclamation-triangle",
|
if (!container) {
|
||||||
success: "fas fa-check-circle",
|
throw new Error("failed to find message container");
|
||||||
info: "fas fa-info",
|
}
|
||||||
};
|
container.messages.push(message);
|
||||||
|
container.requestUpdate();
|
||||||
const ID = function (prefix: string) {
|
|
||||||
return prefix + Math.random().toString(36).substr(2, 9);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Message {
|
|
||||||
level_tag: string;
|
|
||||||
tags: string;
|
|
||||||
message: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ak-messages")
|
@customElement("ak-message-container")
|
||||||
export class Messages extends LitElement {
|
export class MessageContainer extends LitElement {
|
||||||
url = DefaultClient.makeUrl(["root", "messages"]);
|
url = DefaultClient.makeUrl(["root", "messages"]);
|
||||||
|
|
||||||
|
@property({attribute: false})
|
||||||
|
messages: APIMessage[] = [];
|
||||||
|
|
||||||
messageSocket?: WebSocket;
|
messageSocket?: WebSocket;
|
||||||
retryDelay = 200;
|
retryDelay = 200;
|
||||||
|
|
||||||
|
@ -52,6 +50,12 @@ export class Messages extends LitElement {
|
||||||
});
|
});
|
||||||
this.messageSocket.addEventListener("close", (e) => {
|
this.messageSocket.addEventListener("close", (e) => {
|
||||||
console.debug(`authentik/messages: closed ws connection: ${e}`);
|
console.debug(`authentik/messages: closed ws connection: ${e}`);
|
||||||
|
if (this.retryDelay > 3000) {
|
||||||
|
showMessage({
|
||||||
|
level_tag: "error",
|
||||||
|
message: gettext("Connection error, reconnecting...")
|
||||||
|
});
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.debug(`authentik/messages: reconnecting ws in ${this.retryDelay}ms`);
|
console.debug(`authentik/messages: reconnecting ws in ${this.retryDelay}ms`);
|
||||||
this.connect();
|
this.connect();
|
||||||
|
@ -60,7 +64,8 @@ export class Messages extends LitElement {
|
||||||
});
|
});
|
||||||
this.messageSocket.addEventListener("message", (e) => {
|
this.messageSocket.addEventListener("message", (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
this.renderMessage(data);
|
this.messages.push(data);
|
||||||
|
this.requestUpdate();
|
||||||
});
|
});
|
||||||
this.messageSocket.addEventListener("error", (e) => {
|
this.messageSocket.addEventListener("error", (e) => {
|
||||||
console.warn(`authentik/messages: 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");
|
console.debug("authentik/messages: fetching messages over direct api");
|
||||||
return fetch(this.url)
|
return fetch(this.url)
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((r: Message[]) => {
|
.then((r: APIMessage[]) => {
|
||||||
r.forEach((m: Message) => {
|
r.forEach((m: APIMessage) => {
|
||||||
this.renderMessage(m);
|
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 {
|
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>
|
<script src="/static/dist/main.js" type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ak-messages></ak-messages>
|
<ak-message-container></ak-message-container>
|
||||||
<div class="pf-c-page">
|
<div class="pf-c-page">
|
||||||
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content"
|
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content"
|
||||||
>Skip to content</a
|
>Skip to content</a
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { gettext } from "django";
|
||||||
import { html, LitElement, TemplateResult } from "lit-element";
|
import { html, LitElement, TemplateResult } from "lit-element";
|
||||||
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
||||||
|
|
||||||
import "../elements/Messages";
|
|
||||||
import "../elements/router/RouterOutlet";
|
import "../elements/router/RouterOutlet";
|
||||||
|
import "../elements/messages/MessageContainer";
|
||||||
|
|
||||||
export abstract class Interface extends LitElement {
|
export abstract class Interface extends LitElement {
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export abstract class Interface extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html`<ak-messages></ak-messages>
|
return html`<ak-message-container></ak-message-container>
|
||||||
<div class="pf-c-page">
|
<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>
|
<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}>
|
<ak-sidebar class="pf-c-page__sidebar" .items=${this.sidebar}>
|
||||||
|
|
|
@ -17,7 +17,7 @@ import "./elements/EmptyState";
|
||||||
import "./elements/cards/AggregateCard";
|
import "./elements/cards/AggregateCard";
|
||||||
import "./elements/cards/AggregatePromiseCard";
|
import "./elements/cards/AggregatePromiseCard";
|
||||||
import "./elements/CodeMirror";
|
import "./elements/CodeMirror";
|
||||||
import "./elements/Messages";
|
import "./elements/messages/MessageContainer";
|
||||||
import "./elements/Spinner";
|
import "./elements/Spinner";
|
||||||
import "./elements/Tabs";
|
import "./elements/Tabs";
|
||||||
import "./elements/router/RouterOutlet";
|
import "./elements/router/RouterOutlet";
|
||||||
|
|
|
@ -6,6 +6,8 @@ import SpinnerStyle from "@patternfly/patternfly/components/Spinner/spinner.css"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import BackdropStyle from "@patternfly/patternfly/components/Backdrop/backdrop.css";
|
import BackdropStyle from "@patternfly/patternfly/components/Backdrop/backdrop.css";
|
||||||
import { SpinnerSize } from "../../elements/Spinner";
|
import { SpinnerSize } from "../../elements/Spinner";
|
||||||
|
import { showMessage } from "../../elements/messages/MessageContainer";
|
||||||
|
import { gettext } from "django";
|
||||||
|
|
||||||
@customElement("ak-site-shell")
|
@customElement("ak-site-shell")
|
||||||
export class SiteShell extends LitElement {
|
export class SiteShell extends LitElement {
|
||||||
|
@ -64,6 +66,10 @@ export class SiteShell extends LitElement {
|
||||||
}
|
}
|
||||||
console.debug(`authentik/site-shell: Request failed ${this._url}`);
|
console.debug(`authentik/site-shell: Request failed ${this._url}`);
|
||||||
window.location.hash = "#/";
|
window.location.hash = "#/";
|
||||||
|
showMessage({
|
||||||
|
level_tag: "error",
|
||||||
|
message: gettext(`Request failed: ${r.statusText}`),
|
||||||
|
});
|
||||||
throw new Error("Request failed");
|
throw new Error("Request failed");
|
||||||
})
|
})
|
||||||
.then((r) => r.text())
|
.then((r) => r.text())
|
||||||
|
@ -115,7 +121,7 @@ export class SiteShell extends LitElement {
|
||||||
html`<div class="pf-c-backdrop">
|
html`<div class="pf-c-backdrop">
|
||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
<div class="pf-l-bullseye__item">
|
<div class="pf-l-bullseye__item">
|
||||||
<ak-spinner size=${SpinnerSize.Large}></ak-spinner>
|
<ak-spinner size=${SpinnerSize.XLarge}></ak-spinner>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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-c-empty-state__content">
|
||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
<div class="pf-l-bullseye__item">
|
<div class="pf-l-bullseye__item">
|
||||||
<ak-spinner size="${SpinnerSize.Large}"></ak-spinner>
|
<ak-spinner size="${SpinnerSize.XLarge}"></ak-spinner>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in New Issue