web: switch to eslint

This commit is contained in:
Jens Langhammer 2020-12-01 09:15:41 +01:00
parent a312ad2ad1
commit 0231bcf685
25 changed files with 1010 additions and 151 deletions

6
web/.eslintignore Normal file
View File

@ -0,0 +1,6 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
# don't lint nyc coverage output
coverage

19
web/.eslintrc.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["@typescript-eslint"],
rules: {
indent: ["error", 4],
"linebreak-style": ["error", "unix"],
quotes: ["error", "double"],
semi: ["error", "always"],
},
};

850
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,11 +24,14 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "^8.0.0", "@rollup/plugin-typescript": "^8.0.0",
"prettier": "2.2.1", "@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"eslint": "^7.14.0",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-minify-html-literals": "^1.2.5", "rollup-plugin-minify-html-literals": "^1.2.5",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-terser": "^7.0.2" "rollup-plugin-terser": "^7.0.2",
"typescript": "^4.1.2"
} }
} }

View File

@ -6,7 +6,7 @@ export class Client {
makeUrl(url: string[], query?: { [key: string]: string }): string { makeUrl(url: string[], query?: { [key: string]: string }): string {
let builtUrl = `/api/${VERSION}/${url.join("/")}/`; let builtUrl = `/api/${VERSION}/${url.join("/")}/`;
if (query) { if (query) {
let queryString = Object.keys(query) const queryString = Object.keys(query)
.map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(query[k])) .map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(query[k]))
.join("&"); .join("&");
builtUrl += `?${queryString}`; builtUrl += `?${queryString}`;

View File

@ -21,7 +21,7 @@ export class Config {
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
environment: config.error_reporting_environment, environment: config.error_reporting_environment,
}); });
console.debug(`passbook/config: Sentry enabled.`); console.debug("passbook/config: Sentry enabled.");
} }
return config; return config;
}); });

View File

@ -0,0 +1,15 @@
export interface Policy {
pk: string;
name: string;
[key: string]: any;
}
export interface PolicyBinding {
pk: string;
policy: string,
policy_obj: Policy;
target: string;
enabled: boolean;
order: number;
timeout: number;
}

View File

@ -9,7 +9,7 @@ interface TickValue {
@customElement("pb-admin-logins-chart") @customElement("pb-admin-logins-chart")
export class AdminLoginsChart extends LitElement { export class AdminLoginsChart extends LitElement {
@property() @property()
url: string = ""; url = "";
chart: any; chart: any;
@ -43,7 +43,7 @@ export class AdminLoginsChart extends LitElement {
.then((r) => r.json()) .then((r) => r.json())
.catch((e) => console.error(e)) .catch((e) => console.error(e))
.then((r) => { .then((r) => {
let ctx = (<HTMLCanvasElement>this.shadowRoot?.querySelector("canvas")).getContext( const ctx = (<HTMLCanvasElement>this.shadowRoot?.querySelector("canvas")).getContext(
"2d" "2d"
)!; )!;
this.chart = new Chart(ctx, { this.chart = new Chart(ctx, {

View File

@ -10,10 +10,10 @@ import "codemirror/mode/python/python.js";
@customElement("pb-codemirror") @customElement("pb-codemirror")
export class CodeMirrorTextarea extends LitElement { export class CodeMirrorTextarea extends LitElement {
@property() @property()
readOnly: boolean = false; readOnly = false;
@property() @property()
mode: string = "yaml"; mode = "yaml";
editor?: CodeMirror.EditorFromTextArea; editor?: CodeMirror.EditorFromTextArea;

View File

@ -7,16 +7,16 @@ interface ComparisonHash {
@customElement("fetch-fill-slot") @customElement("fetch-fill-slot")
export class FetchFillSlot extends LitElement { export class FetchFillSlot extends LitElement {
@property() @property()
url: string = ""; url = "";
@property() @property()
key: string = ""; key = "";
@property() @property()
value: string = ""; value = "";
comparison(slotName: string) { comparison(slotName: string) {
let comparisonOperatorsHash = <ComparisonHash>{ const comparisonOperatorsHash = <ComparisonHash>{
"<": function (a: any, b: any) { "<": function (a: any, b: any) {
return a < b; return a < b;
}, },

View File

@ -7,7 +7,7 @@ const LEVEL_ICON_MAP: { [key: string]: string } = {
info: "fas fa-info", info: "fas fa-info",
}; };
let ID = function (prefix: string) { const ID = function (prefix: string) {
return prefix + Math.random().toString(36).substr(2, 9); return prefix + Math.random().toString(36).substr(2, 9);
}; };
@ -20,10 +20,10 @@ interface Message {
@customElement("pb-messages") @customElement("pb-messages")
export class Messages extends LitElement { export class Messages extends LitElement {
@property() @property()
url: string = ""; url = "";
messageSocket?: WebSocket; messageSocket?: WebSocket;
retryDelay: number = 200; retryDelay = 200;
createRenderRoot() { createRenderRoot() {
return this; return this;
@ -72,7 +72,7 @@ export class Messages extends LitElement {
* This mostly gets messages which were created when the user arrives/leaves the site * This mostly gets messages which were created when the user arrives/leaves the site
* and especially the login flow */ * and especially the login flow */
fetchMessages() { fetchMessages() {
console.debug(`passbook/messages: fetching messages over direct api`); console.debug("passbook/messages: fetching messages over direct api");
return fetch(this.url) return fetch(this.url)
.then((r) => r.json()) .then((r) => r.json())
.then((r) => { .then((r) => {
@ -94,7 +94,7 @@ export class Messages extends LitElement {
el.innerHTML = `<li id=${id} class="pf-c-alert-group__item"> el.innerHTML = `<li id=${id} class="pf-c-alert-group__item">
<div class="pf-c-alert pf-m-${message.levelTag} ${ <div class="pf-c-alert pf-m-${message.levelTag} ${
message.levelTag === "error" ? "pf-m-danger" : "" message.levelTag === "error" ? "pf-m-danger" : ""
}"> }">
<div class="pf-c-alert__icon"> <div class="pf-c-alert__icon">
<i class="${LEVEL_ICON_MAP[message.levelTag]}"></i> <i class="${LEVEL_ICON_MAP[message.levelTag]}"></i>
</div> </div>

View File

@ -15,7 +15,7 @@ export class Tabs extends LitElement {
} }
render() { render() {
let pages = Array.from(this.querySelectorAll("[slot]")!); const pages = Array.from(this.querySelectorAll("[slot]")!);
if (!this.currentPage) { if (!this.currentPage) {
if (pages.length < 1) { if (pages.length < 1) {
return html`<h1>no tabs defined</h1>`; return html`<h1>no tabs defined</h1>`;

View File

@ -6,7 +6,7 @@ import { SpinnerButton } from "./SpinnerButton";
@customElement("pb-action-button") @customElement("pb-action-button")
export class ActionButton extends SpinnerButton { export class ActionButton extends SpinnerButton {
@property() @property()
url: string = ""; url = "";
callAction() { callAction() {
if (this.isRunning === true) { if (this.isRunning === true) {

View File

@ -20,7 +20,7 @@ export class ModalButton extends LitElement {
href?: string; href?: string;
@property() @property()
open: boolean = false; open = false;
static get styles() { static get styles() {
return [ return [
@ -79,7 +79,7 @@ export class ModalButton extends LitElement {
this.querySelectorAll<HTMLFormElement>("[slot=modal] form").forEach((form) => { this.querySelectorAll<HTMLFormElement>("[slot=modal] form").forEach((form) => {
form.addEventListener("submit", (e) => { form.addEventListener("submit", (e) => {
e.preventDefault(); e.preventDefault();
let formData = new FormData(form); const formData = new FormData(form);
fetch(this.href ? this.href : form.action, { fetch(this.href ? this.href : form.action, {
method: form.method, method: form.method,
body: formData, body: formData,
@ -91,11 +91,11 @@ export class ModalButton extends LitElement {
.then((data) => { .then((data) => {
if (data.indexOf("csrfmiddlewaretoken") !== -1) { if (data.indexOf("csrfmiddlewaretoken") !== -1) {
this.querySelector("[slot=modal]")!.innerHTML = data; this.querySelector("[slot=modal]")!.innerHTML = data;
console.debug(`passbook/modalbutton: re-showing form`); console.debug("passbook/modalbutton: re-showing form");
this.updateHandlers(); this.updateHandlers();
} else { } else {
this.open = false; this.open = false;
console.debug(`passbook/modalbutton: successful submit`); console.debug("passbook/modalbutton: successful submit");
this.dispatchEvent( this.dispatchEvent(
new CustomEvent("hashchange", { new CustomEvent("hashchange", {
bubbles: true, bubbles: true,

View File

@ -12,7 +12,7 @@ export abstract class Table<T> extends LitElement {
data?: PBResponse<T>; data?: PBResponse<T>;
@property() @property()
page: number = 1; page = 1;
static get styles() { static get styles() {
return [COMMON_STYLES]; return [COMMON_STYLES];
@ -51,12 +51,12 @@ export abstract class Table<T> extends LitElement {
return; return;
} }
return this.data.results.map((item) => { return this.data.results.map((item) => {
const fullRow = [`<tr role="row">`].concat( const fullRow = ["<tr role=\"row\">"].concat(
this.row(item).map((col) => { this.row(item).map((col) => {
return `<td role="cell">${col}</td>`; return `<td role="cell">${col}</td>`;
}) })
); );
fullRow.push(`</tr>`); fullRow.push("</tr>");
return html(<any>fullRow); return html(<any>fullRow);
}); });
} }

View File

@ -13,7 +13,7 @@ export class TablePagination extends LitElement {
previousHandler() { previousHandler() {
if (!this.table?.data?.pagination.previous) { if (!this.table?.data?.pagination.previous) {
console.debug(`passbook/tables: no previous`); console.debug("passbook/tables: no previous");
return; return;
} }
this.table.page = this.table?.data?.pagination.previous; this.table.page = this.table?.data?.pagination.previous;
@ -21,7 +21,7 @@ export class TablePagination extends LitElement {
nextHandler() { nextHandler() {
if (!this.table?.data?.pagination.next) { if (!this.table?.data?.pagination.next) {
console.debug(`passbook/tables: no next`); console.debug("passbook/tables: no next");
return; return;
} }
this.table.page = this.table?.data?.pagination.next; this.table.page = this.table?.data?.pagination.next;

View File

@ -1,4 +1,4 @@
import { LitElement, html, customElement, property } from "lit-element"; import { LitElement, html, customElement, property, TemplateResult } from "lit-element";
enum ResponseType { enum ResponseType {
redirect = "redirect", redirect = "redirect",
@ -14,16 +14,16 @@ interface Response {
@customElement("pb-flow-shell-card") @customElement("pb-flow-shell-card")
export class FlowShellCard extends LitElement { export class FlowShellCard extends LitElement {
@property() @property()
flowBodyUrl: string = ""; flowBodyUrl = "";
@property() @property()
flowBody?: string; flowBody?: string;
createRenderRoot() { createRenderRoot(): Element | ShadowRoot {
return this; return this;
} }
firstUpdated() { firstUpdated(): void {
fetch(this.flowBodyUrl) fetch(this.flowBodyUrl)
.then((r) => { .then((r) => {
if (r.status === 404) { if (r.status === 404) {
@ -46,7 +46,7 @@ export class FlowShellCard extends LitElement {
}); });
} }
async updateCard(data: Response) { async updateCard(data: Response): Promise<void> {
switch (data.type) { switch (data.type) {
case ResponseType.redirect: case ResponseType.redirect:
window.location.assign(data.to!); window.location.assign(data.to!);
@ -64,22 +64,22 @@ export class FlowShellCard extends LitElement {
} }
} }
loadFormCode() { loadFormCode(): void {
this.querySelectorAll("script").forEach((script) => { this.querySelectorAll("script").forEach((script) => {
let newScript = document.createElement("script"); const newScript = document.createElement("script");
newScript.src = script.src; newScript.src = script.src;
document.head.appendChild(newScript); document.head.appendChild(newScript);
}); });
} }
checkAutofocus() { checkAutofocus(): void {
const autofocusElement = <HTMLElement>this.querySelector("[autofocus]"); const autofocusElement = <HTMLElement>this.querySelector("[autofocus]");
if (autofocusElement !== null) { if (autofocusElement !== null) {
autofocusElement.focus(); autofocusElement.focus();
} }
} }
updateFormAction(form: HTMLFormElement) { updateFormAction(form: HTMLFormElement): boolean {
for (let index = 0; index < form.elements.length; index++) { for (let index = 0; index < form.elements.length; index++) {
const element = <HTMLInputElement>form.elements[index]; const element = <HTMLInputElement>form.elements[index];
if (element.value === form.action) { if (element.value === form.action) {
@ -94,13 +94,13 @@ export class FlowShellCard extends LitElement {
return true; return true;
} }
checkAutosubmit(form: HTMLFormElement) { checkAutosubmit(form: HTMLFormElement): void {
if ("autosubmit" in form.attributes) { if ("autosubmit" in form.attributes) {
return form.submit(); return form.submit();
} }
} }
setFormSubmitHandlers() { setFormSubmitHandlers(): void {
this.querySelectorAll("form").forEach((form) => { this.querySelectorAll("form").forEach((form) => {
console.debug(`passbook/flows: Checking for autosubmit attribute ${form}`); console.debug(`passbook/flows: Checking for autosubmit attribute ${form}`);
this.checkAutosubmit(form); this.checkAutosubmit(form);
@ -109,7 +109,7 @@ export class FlowShellCard extends LitElement {
console.debug(`passbook/flows: Adding handler for form ${form}`); console.debug(`passbook/flows: Adding handler for form ${form}`);
form.addEventListener("submit", (e) => { form.addEventListener("submit", (e) => {
e.preventDefault(); e.preventDefault();
let formData = new FormData(form); const formData = new FormData(form);
this.flowBody = undefined; this.flowBody = undefined;
fetch(this.flowBodyUrl, { fetch(this.flowBodyUrl, {
method: "post", method: "post",
@ -129,7 +129,7 @@ export class FlowShellCard extends LitElement {
}); });
} }
errorMessage(error: string) { errorMessage(error: string): void {
this.flowBody = ` this.flowBody = `
<style> <style>
.pb-exception { .pb-exception {
@ -150,7 +150,7 @@ export class FlowShellCard extends LitElement {
</div>`; </div>`;
} }
loading() { loading(): TemplateResult {
return html` <div class="pf-c-login__main-body pb-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" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span> <span class="pf-c-spinner__clipper"></span>
@ -160,7 +160,7 @@ export class FlowShellCard extends LitElement {
</div>`; </div>`;
} }
render() { render(): TemplateResult {
if (this.flowBody) { if (this.flowBody) {
return html(<TemplateStringsArray>(<unknown>[this.flowBody])); return html(<TemplateStringsArray>(<unknown>[this.flowBody]));
} }

View File

@ -1,6 +1,6 @@
import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { Application } from "../api/application"; import { Application } from "../api/application";
import { DefaultClient, PBResponse } from "../api/client"; import { PBResponse } from "../api/client";
import { COMMON_STYLES } from "../common/styles"; import { COMMON_STYLES } from "../common/styles";
import { truncate } from "../utils"; import { truncate } from "../utils";
@ -19,18 +19,17 @@ export class ApplicationViewPage extends LitElement {
); );
} }
firstUpdated() { firstUpdated(): void {
Application.list().then((r) => (this.apps = r)); Application.list().then((r) => (this.apps = r));
} }
renderEmptyState() { renderEmptyState(): TemplateResult {
return html` <div class="pf-c-empty-state pf-m-full-height"> return html` <div class="pf-c-empty-state pf-m-full-height">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">{% trans 'No Applications available.' %}</h1> <h1 class="pf-c-title pf-m-lg">No Applications available.</h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% trans "Either no applications are defined, or you don't have access to any." Either no applications are defined, or you don't have access to any.
%}
</div> </div>
{% if perms.passbook_core.add_application %} {% if perms.passbook_core.add_application %}
<a <a
@ -66,7 +65,7 @@ export class ApplicationViewPage extends LitElement {
</a>`; </a>`;
} }
renderLoading() { renderLoading(): TemplateResult {
return html`<div class="pf-c-empty-state pf-m-full-height"> return html`<div class="pf-c-empty-state pf-m-full-height">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<div class="pf-l-bullseye"> <div class="pf-l-bullseye">
@ -86,7 +85,7 @@ export class ApplicationViewPage extends LitElement {
</div>`; </div>`;
} }
render() { render(): TemplateResult {
return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content"> return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<section class="pf-c-page__main-section pf-m-light"> <section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content"> <div class="pf-c-content">

View File

@ -49,10 +49,10 @@ export class Route {
export const SLUG_REGEX = "[-a-zA-Z0-9_]+"; export const SLUG_REGEX = "[-a-zA-Z0-9_]+";
export const ROUTES: Route[] = [ export const ROUTES: Route[] = [
// Prevent infinite Shell loops // Prevent infinite Shell loops
new Route(new RegExp(`^/$`)).redirect("/library/"), new Route(new RegExp("^/$")).redirect("/library/"),
new Route(new RegExp(`^#.*`)).redirect("/library/"), new Route(new RegExp("^#.*")).redirect("/library/"),
new Route(new RegExp(`^/library/$`), html`<pb-library></pb-library>`), new Route(new RegExp("^/library/$"), html`<pb-library></pb-library>`),
new Route(new RegExp(`^/applications/$`), html`<pb-application-list></pb-application-list>`), new Route(new RegExp("^/applications/$"), html`<pb-application-list></pb-application-list>`),
new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => { new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => {
return html`<pb-application-view .args=${args}></pb-application-view>`; return html`<pb-application-view .args=${args}></pb-application-view>`;
}), }),
@ -99,14 +99,14 @@ export class RouterOutlet extends LitElement {
constructor() { constructor() {
super(); super();
window.addEventListener("hashchange", (e) => this.navigate()); window.addEventListener("hashchange", () => this.navigate());
} }
firstUpdated() { firstUpdated(): void {
this.navigate(); this.navigate();
} }
navigate() { navigate(): void {
let activeUrl = window.location.hash.slice(1, Infinity); let activeUrl = window.location.hash.slice(1, Infinity);
if (activeUrl === "") { if (activeUrl === "") {
activeUrl = this.defaultUrl!; activeUrl = this.defaultUrl!;
@ -141,7 +141,7 @@ export class RouterOutlet extends LitElement {
this.current = matchedRoute; this.current = matchedRoute;
} }
render() { render(): TemplateResult {
return this.current?.render(); return this.current?.render();
} }
} }

View File

@ -17,7 +17,7 @@ export class SiteShell extends LitElement {
_url?: string; _url?: string;
@property() @property()
loading: boolean = false; loading = false;
static get styles() { static get styles() {
return [ return [

View File

@ -1,16 +1,17 @@
import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { Application } from "../../api/application"; import { Application } from "../../api/application";
import { DefaultClient, PBResponse } from "../../api/client"; import { DefaultClient, PBResponse } from "../../api/client";
import { PolicyBinding } from "../../api/policy_binding";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import { Table } from "../../elements/table/Table"; import { Table } from "../../elements/table/Table";
@customElement("pb-bound-policies-list") @customElement("pb-bound-policies-list")
export class BoundPoliciesList extends Table<any> { export class BoundPoliciesList extends Table<PolicyBinding> {
@property() @property()
target?: string; target?: string;
apiEndpoint(page: number): Promise<PBResponse<any>> { apiEndpoint(page: number): Promise<PBResponse<PolicyBinding>> {
return DefaultClient.fetch<PBResponse<any>>(["policies", "bindings"], { return DefaultClient.fetch<PBResponse<PolicyBinding>>(["policies", "bindings"], {
target: this.target!, target: this.target!,
ordering: "order", ordering: "order",
page: page, page: page,
@ -21,12 +22,12 @@ export class BoundPoliciesList extends Table<any> {
return ["Policy", "Enabled", "Order", "Timeout", ""]; return ["Policy", "Enabled", "Order", "Timeout", ""];
} }
row(item: any): string[] { row(item: PolicyBinding): string[] {
return [ return [
item.policy_obj.name, item.policy_obj.name,
item.enabled, item.enabled ? "Yes" : "No",
item.order, item.order.toString(),
item.timeout, item.timeout.toString(),
` `
<pb-modal-button href="administration/policies/bindings/${item.pk}/update/"> <pb-modal-button href="administration/policies/bindings/${item.pk}/update/">
<pb-spinner-button slot="trigger" class="pf-m-secondary"> <pb-spinner-button slot="trigger" class="pf-m-secondary">
@ -70,7 +71,7 @@ export class ApplicationViewPage extends LitElement {
); );
} }
render() { render(): TemplateResult {
if (!this.application) { if (!this.application) {
return html``; return html``;
} }
@ -84,44 +85,24 @@ export class ApplicationViewPage extends LitElement {
</div> </div>
</section> </section>
<pb-tabs> <pb-tabs>
<section <section slot="page-1" tab-title="Users" class="pf-c-page__main-section pf-m-no-padding-mobile">
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-l-gallery pf-m-gutter">
<div <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;">
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">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> Logins over the last 24 <i class="pf-icon pf-icon-server"></i> Logins over the last 24 hours
hours
</div> </div>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<pb-admin-logins-chart ${this.application ?
url="${DefaultClient.makeUrl([ html`<pb-admin-logins-chart url="${DefaultClient.makeUrl(["core", "applications", this.application?.slug!, "metrics"])}"></pb-admin-logins-chart>` : ""}
"core",
"applications",
this.application?.slug!,
"metrics",
])}"
></pb-admin-logins-chart>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<div <div slot="page-2" tab-title="Policy Bindings" class="pf-c-page__main-section pf-m-no-padding-mobile">
slot="page-2"
tab-title="Policy Bindings"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card"> <div class="pf-c-card">
<pb-bound-policies-list <pb-bound-policies-list .target=${this.application.pk}></pb-bound-policies-list>
.target=${this.application.pk}
></pb-bound-policies-list>
</div> </div>
</div> </div>
</pb-tabs>`; </pb-tabs>`;

View File

@ -1,5 +1,5 @@
export function getCookie(name: string) { export function getCookie(name: string): string | undefined {
let cookieValue = null; let cookieValue = undefined;
if (document.cookie && document.cookie !== "") { if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";"); const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) { for (let i = 0; i < cookies.length; i++) {