web: add API Drawer

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-08-05 22:04:31 +02:00
parent 562eb8af95
commit fdd8e66b91
8 changed files with 109 additions and 48 deletions

View File

@ -1,6 +1,6 @@
import { Config, Configuration, CoreApi, CurrentTenant, Middleware, ResponseContext, RootApi, Tenant } from "authentik-api"; import { Config, Configuration, CoreApi, CurrentTenant, Middleware, ResponseContext, RootApi, Tenant } from "authentik-api";
import { getCookie } from "../utils"; import { getCookie } from "../utils";
import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer"; import { APIMiddleware } from "../elements/notifications/APIDrawer";
import { MessageMiddleware } from "../elements/messages/Middleware"; import { MessageMiddleware } from "../elements/messages/Middleware";
export class LoggingMiddleware implements Middleware { export class LoggingMiddleware implements Middleware {
@ -62,7 +62,7 @@ export const DEFAULT_CONFIG = new Configuration({
"X-CSRFToken": getCookie("authentik_csrf"), "X-CSRFToken": getCookie("authentik_csrf"),
}, },
middleware: [ middleware: [
API_DRAWER_MIDDLEWARE, new APIMiddleware(),
new MessageMiddleware(), new MessageMiddleware(),
new LoggingMiddleware(), new LoggingMiddleware(),
], ],

View File

@ -230,6 +230,9 @@ body {
.pf-c-form-control[readonly] { .pf-c-form-control[readonly] {
background-color: var(--ak-dark-background-light); background-color: var(--ak-dark-background-light);
} }
.pf-c-button.pf-m-plain:hover {
color: var(--ak-dark-foreground);
}
.pf-c-button.pf-m-control { .pf-c-button.pf-m-control {
--pf-c-button--after--BorderColor: var(--ak-dark-background-lighter) --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter)
var(--ak-dark-background-lighter) var(--ak-dark-background-lighter)

View File

@ -9,7 +9,8 @@ export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";"; export const ROUTE_SEPARATOR = ";";
export const EVENT_REFRESH = "ak-refresh"; export const EVENT_REFRESH = "ak-refresh";
export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; export const EVENT_NOTIFICATION_DRAWER_TOGGLE = "ak-notification-toggle";
export const EVENT_API_DRAWER_TOGGLE = "ak-api-drawer-toggle";
export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle"; export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle";
export const EVENT_API_DRAWER_REFRESH = "ak-api-drawer-refresh"; export const EVENT_API_DRAWER_REFRESH = "ak-api-drawer-refresh";
export const EVENT_WS_MESSAGE = "ak-ws-message"; export const EVENT_WS_MESSAGE = "ak-ws-message";

View File

@ -12,7 +12,7 @@ import PFContent from "@patternfly/patternfly/components/Content/content.css";
import AKGlobal from "../authentik.css"; import AKGlobal from "../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants"; import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants";
import { DEFAULT_CONFIG, tenant } from "../api/Config"; import { DEFAULT_CONFIG, tenant } from "../api/Config";
import { EventsApi } from "../../api/dist"; import { EventsApi } from "../../api/dist";
@ -127,13 +127,26 @@ export class PageHeader extends LitElement {
${this.description ? html`<p>${this.description}</p>` : html``} ${this.description ? html`<p>${this.description}</p>` : html``}
</div> </div>
</section> </section>
<button
class="notification-trigger pf-c-button pf-m-plain"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_API_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
>
<i class="fas fa-code"></i>
</button>
<button <button
class="notification-trigger pf-c-button pf-m-plain ${this.hasNotifications class="notification-trigger pf-c-button pf-m-plain ${this.hasNotifications
? "has-notifications" ? "has-notifications"
: ""}" : ""}"
@click=${() => { @click=${() => {
this.dispatchEvent( this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_TOGGLE, { new CustomEvent(EVENT_NOTIFICATION_DRAWER_TOGGLE, {
bubbles: true, bubbles: true,
composed: true, composed: true,
}), }),
@ -141,6 +154,7 @@ export class PageHeader extends LitElement {
}} }}
> >
<i class="fas fa-bell"></i> <i class="fas fa-bell"></i>
</button>`; </button>
`;
} }
} }

View File

@ -1,77 +1,114 @@
import { Middleware, ResponseContext } from "authentik-api"; import { Middleware, ResponseContext } from "authentik-api";
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css"; import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css"; import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
import AKGlobal from "../../authentik.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
import AKGlobal from "../../authentik.css";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { EVENT_API_DRAWER_REFRESH } from "../../constants"; import { EVENT_API_DRAWER_REFRESH, EVENT_API_DRAWER_TOGGLE } from "../../constants";
export interface RequestInfo { export interface RequestInfo {
method: string; method: string;
path: string; path: string;
status: number;
} }
export class APIMiddleware implements Middleware { export class APIMiddleware implements Middleware {
requests: RequestInfo[];
constructor() {
this.requests = [];
}
post?(context: ResponseContext): Promise<Response | void> { post?(context: ResponseContext): Promise<Response | void> {
this.requests.push({ const request: RequestInfo = {
method: (context.init.method || "GET").toUpperCase(), method: (context.init.method || "GET").toUpperCase(),
path: context.url, path: context.url,
}); status: context.response.status,
if (this.requests.length > MAX_REQUESTS) { };
this.requests.shift();
}
window.dispatchEvent( window.dispatchEvent(
new CustomEvent(EVENT_API_DRAWER_REFRESH, { new CustomEvent(EVENT_API_DRAWER_REFRESH, {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: request,
}), }),
); );
return Promise.resolve(context.response); return Promise.resolve(context.response);
} }
}
export const MAX_REQUESTS = 50; }
export const API_DRAWER_MIDDLEWARE = new APIMiddleware();
@customElement("ak-api-drawer") @customElement("ak-api-drawer")
export class APIDrawer extends LitElement { export class APIDrawer extends LitElement {
@property({attribute: false})
requests: RequestInfo[] = [];
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFNotificationDrawer, PFContent, PFDropdown, AKGlobal]; return [PFBase, PFNotificationDrawer, PFButton, PFContent, PFDropdown, AKGlobal, css`
.pf-c-notification-drawer__header {
height: 114px;
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;
font-family: monospace;
}
`];
} }
constructor() { constructor() {
super(); super();
this.addEventListener(EVENT_API_DRAWER_REFRESH, () => { window.addEventListener(EVENT_API_DRAWER_REFRESH, ((e: CustomEvent<RequestInfo>) => {
this.requests.splice(0, 0, e.detail);
if (this.requests.length > 50) {
this.requests.shift();
}
this.requestUpdate(); this.requestUpdate();
}); }) as EventListener);
} }
renderItem(item: RequestInfo): TemplateResult { renderItem(item: RequestInfo): TemplateResult {
return html`<li class="pf-c-notification-drawer__list-item pf-m-read"> return html`<li class="pf-c-notification-drawer__list-item pf-m-read">
<div class="pf-c-notification-drawer__list-item-header"> <div class="pf-c-notification-drawer__list-item-header">
<h2 class="pf-c-notification-drawer__list-item-header-title">${item.method}</h2> <h2 class="pf-c-notification-drawer__list-item-header-title">${item.method}: ${item.status}</h2>
</div> </div>
<p class="pf-c-notification-drawer__list-item-description">${item.path}</p> <a class="pf-c-notification-drawer__list-item-description" href=${item.path}>${item.path}</a>
</li>`; </li>`;
} }
render(): TemplateResult { render(): TemplateResult {
return html`<div class="pf-c-drawer__body pf-m-no-padding"> return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer"> <div class="pf-c-notification-drawer">
<div class="pf-c-notification-drawer__header pf-c-content"> <div class="pf-c-notification-drawer__header">
<h1>${t`API Requests`}</h1> <div class="text">
<h1 class="pf-c-notification-drawer__header-title">${t`API Requests`}</h1>
</div>
<div class="pf-c-notification-drawer__header-action">
<div class="pf-c-notification-drawer__header-action-close">
<button
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_API_DRAWER_TOGGLE, {
bubbles: true,
composed: true,
}),
);
}}
class="pf-c-button pf-m-plain"
type="button"
aria-label="Close"
>
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
</div>
</div> </div>
<div class="pf-c-notification-drawer__body"> <div class="pf-c-notification-drawer__body">
<ul class="pf-c-notification-drawer__list"> <ul class="pf-c-notification-drawer__list">
${API_DRAWER_MIDDLEWARE.requests.map((n) => this.renderItem(n))} ${this.requests.map((n) => this.renderItem(n))}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -17,7 +17,7 @@ import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css"
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import AKGlobal from "../../authentik.css"; import AKGlobal from "../../authentik.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
import { EVENT_NOTIFICATION_TOGGLE } from "../../constants"; import { EVENT_NOTIFICATION_DRAWER_TOGGLE } from "../../constants";
import { ActionToLabel } from "../../pages/events/utils"; import { ActionToLabel } from "../../pages/events/utils";
@customElement("ak-notification-drawer") @customElement("ak-notification-drawer")
@ -135,7 +135,7 @@ export class NotificationDrawer extends LitElement {
<button <button
@click=${() => { @click=${() => {
this.dispatchEvent( this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_TOGGLE, { new CustomEvent(EVENT_NOTIFICATION_DRAWER_TOGGLE, {
bubbles: true, bubbles: true,
composed: true, composed: true,
}), }),

View File

@ -24,7 +24,7 @@ import "../elements/messages/MessageContainer";
import "../elements/notifications/NotificationDrawer"; import "../elements/notifications/NotificationDrawer";
import "../elements/sidebar/Sidebar"; import "../elements/sidebar/Sidebar";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE, VERSION } from "../constants"; import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_SIDEBAR_TOGGLE, VERSION } from "../constants";
import { AdminApi } from "authentik-api"; import { AdminApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../api/Config"; import { DEFAULT_CONFIG } from "../api/Config";
import { WebsocketClient } from "../common/ws"; import { WebsocketClient } from "../common/ws";
@ -37,6 +37,9 @@ export class AdminInterface extends LitElement {
@property({ type: Boolean }) @property({ type: Boolean })
notificationOpen = false; notificationOpen = false;
@property({ type: Boolean })
apiDrawerOpen = false;
ws: WebsocketClient; ws: WebsocketClient;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
@ -67,9 +70,12 @@ export class AdminInterface extends LitElement {
window.addEventListener(EVENT_SIDEBAR_TOGGLE, () => { window.addEventListener(EVENT_SIDEBAR_TOGGLE, () => {
this.sidebarOpen = !this.sidebarOpen; this.sidebarOpen = !this.sidebarOpen;
}); });
window.addEventListener(EVENT_NOTIFICATION_TOGGLE, () => { window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
this.notificationOpen = !this.notificationOpen; this.notificationOpen = !this.notificationOpen;
}); });
window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => {
this.apiDrawerOpen = !this.apiDrawerOpen;
});
} }
render(): TemplateResult { render(): TemplateResult {
@ -81,7 +87,7 @@ export class AdminInterface extends LitElement {
</ak-sidebar> </ak-sidebar>
<div class="pf-c-page__drawer"> <div class="pf-c-page__drawer">
<div <div
class="pf-c-drawer ${this.notificationOpen class="pf-c-drawer ${this.notificationOpen || this.apiDrawerOpen
? "pf-m-expanded" ? "pf-m-expanded"
: "pf-m-collapsed"}" : "pf-m-collapsed"}"
> >
@ -100,8 +106,8 @@ export class AdminInterface extends LitElement {
</main> </main>
</div> </div>
</div> </div>
<ak-notification-drawer class="pf-c-drawer__panel pf-m-width-33"> ${this.notificationOpen ? html`<ak-notification-drawer class="pf-c-drawer__panel pf-m-width-33"></ak-notification-drawer>` : html``}
</ak-notification-drawer> ${this.apiDrawerOpen ? html`<ak-api-drawer class="pf-c-drawer__panel pf-m-width-33"></ak-api-drawer>` : html``}
</div> </div>
</div> </div>
</div> </div>

View File

@ -77,16 +77,7 @@ export class TransportListPage extends TablePage<NotificationTransport> {
return [ return [
html`${item.name}`, html`${item.name}`,
html`${item.modeVerbose}`, html`${item.modeVerbose}`,
html` <ak-action-button html`<ak-forms-modal>
.apiRequest=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsTestCreate({
uuid: item.pk || "",
});
}}
>
${t`Test`}
</ak-action-button>
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span> <span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Notification Transport`} </span> <span slot="header"> ${t`Update Notification Transport`} </span>
<ak-event-transport-form slot="form" .instancePk=${item.pk}> <ak-event-transport-form slot="form" .instancePk=${item.pk}>
@ -94,7 +85,16 @@ export class TransportListPage extends TablePage<NotificationTransport> {
<button slot="trigger" class="pf-c-button pf-m-plain"> <button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
</ak-forms-modal>`, </ak-forms-modal>
<ak-action-button
.apiRequest=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsTestCreate({
uuid: item.pk || "",
});
}}
>
<i class="fas fa-vial" aria-hidden="true"></i>
</ak-action-button>`,
]; ];
} }