root: move webapp to /web (#347)
* root: move webapp to /web * root: fix static build * root: fix static files not being served for e2e tests
This commit is contained in:
		
							
								
								
									
										169
									
								
								web/src/pages/FlowShellCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								web/src/pages/FlowShellCard.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,169 @@
 | 
			
		||||
import { LitElement, html, customElement, property } from "lit-element";
 | 
			
		||||
 | 
			
		||||
enum ResponseType {
 | 
			
		||||
    redirect = "redirect",
 | 
			
		||||
    template = "template",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Response {
 | 
			
		||||
    type: ResponseType;
 | 
			
		||||
    to?: string;
 | 
			
		||||
    body?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@customElement("pb-flow-shell-card")
 | 
			
		||||
export class FlowShellCard extends LitElement {
 | 
			
		||||
    @property()
 | 
			
		||||
    flowBodyUrl: string = "";
 | 
			
		||||
 | 
			
		||||
    @property()
 | 
			
		||||
    flowBody?: string;
 | 
			
		||||
 | 
			
		||||
    createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    firstUpdated() {
 | 
			
		||||
        fetch(this.flowBodyUrl)
 | 
			
		||||
            .then((r) => {
 | 
			
		||||
                if (r.status === 404) {
 | 
			
		||||
                    // Fallback when the flow does not exist, just redirect to the root
 | 
			
		||||
                    window.location.pathname = "/";
 | 
			
		||||
                } else if (!r.ok) {
 | 
			
		||||
                    throw Error(r.statusText);
 | 
			
		||||
                }
 | 
			
		||||
                return r;
 | 
			
		||||
            })
 | 
			
		||||
            .then((r) => {
 | 
			
		||||
                return r.json();
 | 
			
		||||
            })
 | 
			
		||||
            .then((r) => {
 | 
			
		||||
                this.updateCard(r);
 | 
			
		||||
            })
 | 
			
		||||
            .catch((e) => {
 | 
			
		||||
                // Catch JSON or Update errors
 | 
			
		||||
                this.errorMessage(e);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async updateCard(data: Response) {
 | 
			
		||||
        switch (data.type) {
 | 
			
		||||
            case ResponseType.redirect:
 | 
			
		||||
                window.location.assign(data.to!);
 | 
			
		||||
                break;
 | 
			
		||||
            case ResponseType.template:
 | 
			
		||||
                this.flowBody = data.body;
 | 
			
		||||
                await this.requestUpdate();
 | 
			
		||||
                this.checkAutofocus();
 | 
			
		||||
                this.loadFormCode();
 | 
			
		||||
                this.setFormSubmitHandlers();
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                console.debug(`passbook/flows: unexpected data type ${data.type}`);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    loadFormCode() {
 | 
			
		||||
        this.querySelectorAll("script").forEach((script) => {
 | 
			
		||||
            let newScript = document.createElement("script");
 | 
			
		||||
            newScript.src = script.src;
 | 
			
		||||
            document.head.appendChild(newScript);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    checkAutofocus() {
 | 
			
		||||
        const autofocusElement = <HTMLElement>this.querySelector("[autofocus]");
 | 
			
		||||
        if (autofocusElement !== null) {
 | 
			
		||||
            autofocusElement.focus();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateFormAction(form: HTMLFormElement) {
 | 
			
		||||
        for (let index = 0; index < form.elements.length; index++) {
 | 
			
		||||
            const element = <HTMLInputElement>form.elements[index];
 | 
			
		||||
            if (element.value === form.action) {
 | 
			
		||||
                console.debug(
 | 
			
		||||
                    "passbook/flows: Found Form action URL in form elements, not changing form action."
 | 
			
		||||
                );
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        form.action = this.flowBodyUrl;
 | 
			
		||||
        console.debug(`passbook/flows: updated form.action ${this.flowBodyUrl}`);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    checkAutosubmit(form: HTMLFormElement) {
 | 
			
		||||
        if ("autosubmit" in form.attributes) {
 | 
			
		||||
            return form.submit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setFormSubmitHandlers() {
 | 
			
		||||
        this.querySelectorAll("form").forEach((form) => {
 | 
			
		||||
            console.debug(`passbook/flows: Checking for autosubmit attribute ${form}`);
 | 
			
		||||
            this.checkAutosubmit(form);
 | 
			
		||||
            console.debug(`passbook/flows: Setting action for form ${form}`);
 | 
			
		||||
            this.updateFormAction(form);
 | 
			
		||||
            console.debug(`passbook/flows: Adding handler for form ${form}`);
 | 
			
		||||
            form.addEventListener("submit", (e) => {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                let formData = new FormData(form);
 | 
			
		||||
                this.flowBody = undefined;
 | 
			
		||||
                fetch(this.flowBodyUrl, {
 | 
			
		||||
                    method: "post",
 | 
			
		||||
                    body: formData,
 | 
			
		||||
                })
 | 
			
		||||
                    .then((response) => {
 | 
			
		||||
                        return response.json();
 | 
			
		||||
                    })
 | 
			
		||||
                    .then((data) => {
 | 
			
		||||
                        this.updateCard(data);
 | 
			
		||||
                    })
 | 
			
		||||
                    .catch((e) => {
 | 
			
		||||
                        this.errorMessage(e);
 | 
			
		||||
                    });
 | 
			
		||||
            });
 | 
			
		||||
            form.classList.add("pb-flow-wrapped");
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    errorMessage(error: string) {
 | 
			
		||||
        this.flowBody = `
 | 
			
		||||
            <style>
 | 
			
		||||
                .pb-exception {
 | 
			
		||||
                    font-family: monospace;
 | 
			
		||||
                    overflow-x: scroll;
 | 
			
		||||
                }
 | 
			
		||||
            </style>
 | 
			
		||||
            <header class="pf-c-login__main-header">
 | 
			
		||||
                <h1 class="pf-c-title pf-m-3xl">
 | 
			
		||||
                    Whoops!
 | 
			
		||||
                </h1>
 | 
			
		||||
            </header>
 | 
			
		||||
            <div class="pf-c-login__main-body">
 | 
			
		||||
                <h3>
 | 
			
		||||
                    Something went wrong! Please try again later.
 | 
			
		||||
                </h3>
 | 
			
		||||
                <pre class="pb-exception">${error}</pre>
 | 
			
		||||
            </div>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    loading() {
 | 
			
		||||
        return html` <div class="pf-c-login__main-body pb-loading">
 | 
			
		||||
            <span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
 | 
			
		||||
                <span class="pf-c-spinner__clipper"></span>
 | 
			
		||||
                <span class="pf-c-spinner__lead-ball"></span>
 | 
			
		||||
                <span class="pf-c-spinner__tail-ball"></span>
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        if (this.flowBody) {
 | 
			
		||||
            return html(<TemplateStringsArray>(<unknown>[this.flowBody]));
 | 
			
		||||
        }
 | 
			
		||||
        return this.loading();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										146
									
								
								web/src/pages/RouterOutlet.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								web/src/pages/RouterOutlet.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,146 @@
 | 
			
		||||
import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import CodeMirrorStyle from "codemirror/lib/codemirror.css";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import CodeMirrorTheme from "codemirror/theme/monokai.css";
 | 
			
		||||
import { ColorStyles } from "../constants";
 | 
			
		||||
import { COMMON_STYLES } from "../common/styles";
 | 
			
		||||
 | 
			
		||||
export class Route {
 | 
			
		||||
    url: RegExp;
 | 
			
		||||
 | 
			
		||||
    private element?: TemplateResult;
 | 
			
		||||
    private callback?: (args: { [key: string]: string }) => TemplateResult;
 | 
			
		||||
 | 
			
		||||
    constructor(url: RegExp, element?: TemplateResult) {
 | 
			
		||||
        this.url = url;
 | 
			
		||||
        this.element = element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    redirect(to: string): Route {
 | 
			
		||||
        this.callback = () => {
 | 
			
		||||
            console.debug(`passbook/router: redirecting ${to}`);
 | 
			
		||||
            window.location.hash = `#${to}`;
 | 
			
		||||
            return html``;
 | 
			
		||||
        };
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    then(render: (args: { [key: string]: string }) => TemplateResult): Route {
 | 
			
		||||
        this.callback = render;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(args: { [key: string]: string }): TemplateResult {
 | 
			
		||||
        if (this.callback) {
 | 
			
		||||
            return this.callback(args);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.element) {
 | 
			
		||||
            return this.element;
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error("Route does not have callback or element");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toString(): string {
 | 
			
		||||
        return `<Route url=${this.url} callback=${this.callback ? "true" : "false"}>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SLUG_REGEX = "[-a-zA-Z0-9_]+";
 | 
			
		||||
export const ROUTES: Route[] = [
 | 
			
		||||
    // Prevent infinite Shell loops
 | 
			
		||||
    new Route(new RegExp(`^/$`)).redirect("/-/overview/"),
 | 
			
		||||
    new Route(new RegExp(`^#.*`)).redirect("/-/overview/"),
 | 
			
		||||
    new Route(new RegExp(`^/applications/$`), html`<h1>test</h1>`),
 | 
			
		||||
    new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => {
 | 
			
		||||
        return html`<pb-application-view .args=${args}></pb-application-view>`;
 | 
			
		||||
    }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
class RouteMatch {
 | 
			
		||||
    route: Route;
 | 
			
		||||
    arguments?: RegExpExecArray;
 | 
			
		||||
    fullUrl?: string;
 | 
			
		||||
 | 
			
		||||
    constructor(route: Route) {
 | 
			
		||||
        this.route = route;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): TemplateResult {
 | 
			
		||||
        return this.route.render(this.arguments!.groups || {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toString(): string {
 | 
			
		||||
        return `<RouteMatch url=${this.fullUrl} route=${this.route} arguments=${this.arguments}>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@customElement("pb-router-outlet")
 | 
			
		||||
export class RouterOutlet extends LitElement {
 | 
			
		||||
    @property()
 | 
			
		||||
    current?: RouteMatch;
 | 
			
		||||
 | 
			
		||||
    @property()
 | 
			
		||||
    defaultUrl?: string;
 | 
			
		||||
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return [
 | 
			
		||||
            CodeMirrorStyle,
 | 
			
		||||
            CodeMirrorTheme,
 | 
			
		||||
            ColorStyles,
 | 
			
		||||
            css`
 | 
			
		||||
                :host {
 | 
			
		||||
                    height: 100%;
 | 
			
		||||
                }
 | 
			
		||||
            `,
 | 
			
		||||
        ].concat(...COMMON_STYLES);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        window.addEventListener("hashchange", (e) => this.navigate());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    firstUpdated() {
 | 
			
		||||
        this.navigate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    navigate() {
 | 
			
		||||
        let activeUrl = window.location.hash.slice(1, Infinity);
 | 
			
		||||
        if (activeUrl === "") {
 | 
			
		||||
            activeUrl = this.defaultUrl!;
 | 
			
		||||
            window.location.hash = `#${activeUrl}`;
 | 
			
		||||
            console.debug(`passbook/router: set to ${window.location.hash}`);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        let matchedRoute: RouteMatch | null = null;
 | 
			
		||||
        ROUTES.forEach((route) => {
 | 
			
		||||
            console.debug(`passbook/router: matching ${activeUrl} against ${route.url}`);
 | 
			
		||||
            const match = route.url.exec(activeUrl);
 | 
			
		||||
            if (match != null) {
 | 
			
		||||
                matchedRoute = new RouteMatch(route);
 | 
			
		||||
                matchedRoute.arguments = match;
 | 
			
		||||
                matchedRoute.fullUrl = activeUrl;
 | 
			
		||||
                console.debug(`passbook/router: found match ${matchedRoute}`);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if (!matchedRoute) {
 | 
			
		||||
            console.debug(`passbook/router: route "${activeUrl}" not defined, defaulting to shell`);
 | 
			
		||||
            const route = new Route(
 | 
			
		||||
                RegExp(""),
 | 
			
		||||
                html`<pb-site-shell url=${activeUrl}>
 | 
			
		||||
                    <div slot="body"></div>
 | 
			
		||||
                </pb-site-shell>`
 | 
			
		||||
            );
 | 
			
		||||
            matchedRoute = new RouteMatch(route);
 | 
			
		||||
            matchedRoute.arguments = route.url.exec(activeUrl)!;
 | 
			
		||||
            matchedRoute.fullUrl = activeUrl;
 | 
			
		||||
        }
 | 
			
		||||
        this.current = matchedRoute;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return this.current?.render();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								web/src/pages/SiteShell.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								web/src/pages/SiteShell.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
			
		||||
import { css, customElement, html, LitElement, property } from "lit-element";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import BullseyeStyle from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import SpinnerStyle from "@patternfly/patternfly/components/Spinner/spinner.css";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import BackdropStyle from "@patternfly/patternfly/components/Backdrop/backdrop.css";
 | 
			
		||||
 | 
			
		||||
@customElement("pb-site-shell")
 | 
			
		||||
export class SiteShell extends LitElement {
 | 
			
		||||
    @property()
 | 
			
		||||
    set url(value: string) {
 | 
			
		||||
        this._url = value;
 | 
			
		||||
        this.loadContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _url?: string;
 | 
			
		||||
 | 
			
		||||
    @property()
 | 
			
		||||
    loading: boolean = false;
 | 
			
		||||
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return [
 | 
			
		||||
            css`
 | 
			
		||||
                :host,
 | 
			
		||||
                ::slotted(*) {
 | 
			
		||||
                    height: 100%;
 | 
			
		||||
                }
 | 
			
		||||
                :host .pf-l-bullseye {
 | 
			
		||||
                    position: absolute;
 | 
			
		||||
                    height: 100%;
 | 
			
		||||
                    width: 100%;
 | 
			
		||||
                    top: 0;
 | 
			
		||||
                    left: 0;
 | 
			
		||||
                    z-index: 2000;
 | 
			
		||||
                }
 | 
			
		||||
                .pf-c-backdrop {
 | 
			
		||||
                    --pf-c-backdrop--BackgroundColor: rgba(0, 0, 0, 0) !important;
 | 
			
		||||
                }
 | 
			
		||||
            `,
 | 
			
		||||
            BackdropStyle,
 | 
			
		||||
            BullseyeStyle,
 | 
			
		||||
            SpinnerStyle,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    loadContent() {
 | 
			
		||||
        if (!this._url) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.loading = true;
 | 
			
		||||
        fetch(this._url)
 | 
			
		||||
            .then((r) => {
 | 
			
		||||
                if (r.ok) {
 | 
			
		||||
                    return r;
 | 
			
		||||
                }
 | 
			
		||||
                console.debug(`passbook/site-shell: Request failed ${this._url}`);
 | 
			
		||||
                window.location.hash = "#/";
 | 
			
		||||
                throw new Error("Request failed");
 | 
			
		||||
            })
 | 
			
		||||
            .then((r) => r.text())
 | 
			
		||||
            .then((t) => {
 | 
			
		||||
                this.querySelector("[slot=body]")!.innerHTML = t;
 | 
			
		||||
            })
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                // Ensure anchors only change the hash
 | 
			
		||||
                this.querySelectorAll<HTMLAnchorElement>("a:not(.pb-root-link)").forEach((a) => {
 | 
			
		||||
                    if (a.href === "") {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    try {
 | 
			
		||||
                        const url = new URL(a.href);
 | 
			
		||||
                        const qs = url.search || "";
 | 
			
		||||
                        a.href = `#${url.pathname}${qs}`;
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                        a.href = `#${a.href}`;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                // Create refresh buttons
 | 
			
		||||
                this.querySelectorAll("[role=pb-refresh]").forEach((rt) => {
 | 
			
		||||
                    rt.addEventListener("click", (e) => {
 | 
			
		||||
                        this.loadContent();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                // Make get forms (search bar) notify us on submit so we can change the hash
 | 
			
		||||
                this.querySelectorAll("form").forEach((f) => {
 | 
			
		||||
                    f.addEventListener("submit", (e) => {
 | 
			
		||||
                        e.preventDefault();
 | 
			
		||||
                        const formData = new FormData(f);
 | 
			
		||||
                        const qs = new URLSearchParams(<any>(<unknown>formData)).toString();
 | 
			
		||||
                        window.location.hash = `#${this._url}?${qs}`;
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    this.loading = false;
 | 
			
		||||
                }, 100);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return html` ${this.loading
 | 
			
		||||
                ? html`<div class="pf-c-backdrop">
 | 
			
		||||
                      <div class="pf-l-bullseye">
 | 
			
		||||
                          <div class="pf-l-bullseye__item">
 | 
			
		||||
                              <span
 | 
			
		||||
                                  class="pf-c-spinner pf-m-xl"
 | 
			
		||||
                                  role="progressbar"
 | 
			
		||||
                                  aria-valuetext="Loading..."
 | 
			
		||||
                              >
 | 
			
		||||
                                  <span class="pf-c-spinner__clipper"></span>
 | 
			
		||||
                                  <span class="pf-c-spinner__lead-ball"></span>
 | 
			
		||||
                                  <span class="pf-c-spinner__tail-ball"></span>
 | 
			
		||||
                              </span>
 | 
			
		||||
                          </div>
 | 
			
		||||
                      </div>
 | 
			
		||||
                  </div>`
 | 
			
		||||
                : ""}
 | 
			
		||||
            <slot name="body"> </slot>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										111
									
								
								web/src/pages/applications/ApplicationViewPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								web/src/pages/applications/ApplicationViewPage.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
			
		||||
import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element";
 | 
			
		||||
import { Application } from "../../api/application";
 | 
			
		||||
import { DefaultClient } from "../../api/client";
 | 
			
		||||
import { COMMON_STYLES } from "../../common/styles";
 | 
			
		||||
import { Table } from "../../elements/Table";
 | 
			
		||||
 | 
			
		||||
@customElement("pb-bound-policies-list")
 | 
			
		||||
export class BoundPoliciesList extends Table {
 | 
			
		||||
    @property()
 | 
			
		||||
    target?: string;
 | 
			
		||||
 | 
			
		||||
    apiEndpoint(): string[] {
 | 
			
		||||
        return ["policies", "bindings", `?target=${this.target}`];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    columns(): string[] {
 | 
			
		||||
        return ["Foo"];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    row(item: any): TemplateResult[] {
 | 
			
		||||
        return [html`${item}`];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@customElement("pb-application-view")
 | 
			
		||||
export class ApplicationViewPage extends LitElement {
 | 
			
		||||
    @property()
 | 
			
		||||
    set args(value: { [key: string]: string }) {
 | 
			
		||||
        this.applicationSlug = value.slug;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property()
 | 
			
		||||
    set applicationSlug(value: string) {
 | 
			
		||||
        Application.get(value).then((app) => (this.application = app));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property()
 | 
			
		||||
    application?: Application;
 | 
			
		||||
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return COMMON_STYLES.concat(
 | 
			
		||||
            css`
 | 
			
		||||
                img.pf-icon {
 | 
			
		||||
                    max-height: 24px;
 | 
			
		||||
                }
 | 
			
		||||
            `
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        if (!this.application) {
 | 
			
		||||
            return html``;
 | 
			
		||||
        }
 | 
			
		||||
        return html`<section class="pf-c-page__main-section pf-m-light">
 | 
			
		||||
                <div class="pf-c-content">
 | 
			
		||||
                    <h1>
 | 
			
		||||
                        <img class="pf-icon" src="${this.application?.meta_icon || ""}" />
 | 
			
		||||
                        ${this.application?.name}
 | 
			
		||||
                    </h1>
 | 
			
		||||
                    <p>${this.application?.meta_publisher}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </section>
 | 
			
		||||
            <pb-tabs>
 | 
			
		||||
                <section
 | 
			
		||||
                    slot="page-1"
 | 
			
		||||
                    tab-title="Users"
 | 
			
		||||
                    class="pf-c-page__main-section pf-m-no-padding-mobile"
 | 
			
		||||
                >
 | 
			
		||||
                    <div class="pf-l-gallery pf-m-gutter">
 | 
			
		||||
                        <div
 | 
			
		||||
                            class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col"
 | 
			
		||||
                            style="grid-column-end: span 3;grid-row-end: span 2;"
 | 
			
		||||
                        >
 | 
			
		||||
                            <div class="pf-c-card__header">
 | 
			
		||||
                                <div class="pf-c-card__header-main">
 | 
			
		||||
                                    <i class="pf-icon pf-icon-server"></i> Logins over the last 24
 | 
			
		||||
                                    hours
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="pf-c-card__body">
 | 
			
		||||
                                <pb-admin-logins-chart
 | 
			
		||||
                                    url="${DefaultClient.makeUrl(
 | 
			
		||||
                                        "core",
 | 
			
		||||
                                        "applications",
 | 
			
		||||
                                        this.application?.slug!,
 | 
			
		||||
                                        "metrics"
 | 
			
		||||
                                    )}"
 | 
			
		||||
                                ></pb-admin-logins-chart>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </section>
 | 
			
		||||
                <div
 | 
			
		||||
                    slot="page-2"
 | 
			
		||||
                    tab-title="Policy Bindings"
 | 
			
		||||
                    class="pf-c-page__main-section pf-m-no-padding-mobile"
 | 
			
		||||
                >
 | 
			
		||||
                    <div class="pf-l-gallery pf-m-gutter">
 | 
			
		||||
                        <div
 | 
			
		||||
                            class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col"
 | 
			
		||||
                            style="grid-column-end: span 3;grid-row-end: span 2;"
 | 
			
		||||
                        >
 | 
			
		||||
                            <pb-bound-policies-list
 | 
			
		||||
                                .target=${this.application.pk}
 | 
			
		||||
                            ></pb-bound-policies-list>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </pb-tabs>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user