From e6391b64f06b144f2996497c6d9585a2dd1bc5c2 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 1 Dec 2020 17:27:19 +0100 Subject: [PATCH] web: update to new formatting rules, make eslint warnings fail ci --- passbook/root/messages/storage.py | 2 +- web/package.json | 2 +- web/src/api/application.ts | 4 +- web/src/api/client.ts | 8 +- web/src/api/policy_binding.ts | 2 +- web/src/common/styles.ts | 3 +- web/src/django.d.ts | 2 +- web/src/elements/AdminLoginsChart.ts | 27 ++++-- web/src/elements/CodeMirror.ts | 8 +- web/src/elements/FetchFillSlot.ts | 91 ------------------- web/src/elements/Messages.ts | 43 ++++----- web/src/elements/Spinner.ts | 4 +- web/src/elements/Tabs.ts | 39 ++++---- web/src/elements/buttons/ActionButton.ts | 13 ++- web/src/elements/buttons/Dropdown.ts | 8 +- web/src/elements/buttons/ModalButton.ts | 29 ++++-- web/src/elements/buttons/SpinnerButton.ts | 12 +-- web/src/elements/buttons/TokenCopyButton.ts | 8 +- web/src/elements/sidebar/Sidebar.ts | 12 +-- web/src/elements/sidebar/SidebarBrand.ts | 10 +- web/src/elements/sidebar/SidebarUser.ts | 6 +- web/src/elements/table/Table.ts | 19 ++-- web/src/elements/table/TablePage.ts | 4 +- web/src/elements/table/TablePagination.ts | 22 ++--- web/src/main.ts | 1 - web/src/pages/AdminOverviewPage.ts | 6 +- web/src/pages/FlowShellCard.ts | 2 +- web/src/pages/LibraryPage.ts | 4 +- web/src/pages/RouterOutlet.ts | 15 +-- web/src/pages/SiteShell.ts | 19 ++-- .../pages/applications/ApplicationListPage.ts | 8 +- .../pages/applications/ApplicationViewPage.ts | 12 +-- web/src/utils.ts | 6 ++ 33 files changed, 192 insertions(+), 259 deletions(-) delete mode 100644 web/src/elements/FetchFillSlot.ts diff --git a/passbook/root/messages/storage.py b/passbook/root/messages/storage.py index 2f2aaef4f..4ca81bd86 100644 --- a/passbook/root/messages/storage.py +++ b/passbook/root/messages/storage.py @@ -25,7 +25,7 @@ class ChannelsStorage(FallbackStorage): uid, { "type": "event.update", - "levelTag": message.level_tag, + "level_tag": message.level_tag, "tags": message.tags, "message": message.message, }, diff --git a/web/package.json b/web/package.json index 0c12ee942..78416478d 100644 --- a/web/package.json +++ b/web/package.json @@ -3,7 +3,7 @@ "scripts": { "build": "rollup -c ./rollup.config.js", "watch": "rollup -c -w", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx" + "lint": "eslint . --max-warnings 0" }, "dependencies": { "@fortawesome/fontawesome-free": "^5.15.1", diff --git a/web/src/api/application.ts b/web/src/api/application.ts index 9448e8001..005bed0d9 100644 --- a/web/src/api/application.ts +++ b/web/src/api/application.ts @@ -1,4 +1,4 @@ -import { DefaultClient, PBResponse } from "./client"; +import { DefaultClient, PBResponse, QueryArguments } from "./client"; export class Application { pk: string; @@ -21,7 +21,7 @@ export class Application { return DefaultClient.fetch(["core", "applications", slug]); } - static list(filter?: { [key: string]: any }): Promise> { + static list(filter?: QueryArguments): Promise> { return DefaultClient.fetch>(["core", "applications"], filter); } } diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 3d9099c53..8bb1f6092 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -2,8 +2,12 @@ import { NotFoundError, RequestError } from "./errors"; export const VERSION = "v2beta"; +export interface QueryArguments { + [key: string]: number | string | boolean; +} + export class Client { - makeUrl(url: string[], query?: { [key: string]: string }): string { + makeUrl(url: string[], query?: QueryArguments): string { let builtUrl = `/api/${VERSION}/${url.join("/")}/`; if (query) { const queryString = Object.keys(query) @@ -14,7 +18,7 @@ export class Client { return builtUrl; } - fetch(url: string[], query?: { [key: string]: any }): Promise { + fetch(url: string[], query?: QueryArguments): Promise { const finalUrl = this.makeUrl(url, query); return fetch(finalUrl) .then((r) => { diff --git a/web/src/api/policy_binding.ts b/web/src/api/policy_binding.ts index e9f1017b0..94c40caaa 100644 --- a/web/src/api/policy_binding.ts +++ b/web/src/api/policy_binding.ts @@ -1,7 +1,7 @@ export interface Policy { pk: string; name: string; - [key: string]: any; + [key: string]: unknown; } export interface PolicyBinding { diff --git a/web/src/common/styles.ts b/web/src/common/styles.ts index 237ab84c0..fc234d22d 100644 --- a/web/src/common/styles.ts +++ b/web/src/common/styles.ts @@ -6,5 +6,6 @@ import PFAddons from "@patternfly/patternfly/patternfly-addons.css"; import FA from "@fortawesome/fontawesome-free/css/fontawesome.css"; // @ts-ignore import PBGlobal from "../passbook.css"; +import { CSSResult } from "lit-element"; -export const COMMON_STYLES = [PF, PFAddons, FA, PBGlobal]; +export const COMMON_STYLES: CSSResult[] = [PF, PFAddons, FA, PBGlobal]; diff --git a/web/src/django.d.ts b/web/src/django.d.ts index 9cb13388e..66cfe6379 100644 --- a/web/src/django.d.ts +++ b/web/src/django.d.ts @@ -6,5 +6,5 @@ declare namespace django { function ngettext(singular: string, plural: string, count: number): string; function gettext_noop(msgid: string): string; function pgettext(context: string, msgid: string): string; - function interpolate(fmt: string, obj: any, named: boolean): string; + function interpolate(fmt: string, obj: unknown, named: boolean): string; } diff --git a/web/src/elements/AdminLoginsChart.ts b/web/src/elements/AdminLoginsChart.ts index 11b0e69c5..04c5ad2af 100644 --- a/web/src/elements/AdminLoginsChart.ts +++ b/web/src/elements/AdminLoginsChart.ts @@ -1,4 +1,4 @@ -import { css, customElement, html, LitElement, property } from "lit-element"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import Chart from "chart.js"; interface TickValue { @@ -11,10 +11,10 @@ export class AdminLoginsChart extends LitElement { @property() url = ""; - chart: any; + chart?: Chart; - static get styles() { - return css` + static get styles(): CSSResult[] { + return [css` :host { position: relative; height: 100%; @@ -26,7 +26,7 @@ export class AdminLoginsChart extends LitElement { width: 100px; height: 100px; } - `; + `]; } constructor() { @@ -38,14 +38,21 @@ export class AdminLoginsChart extends LitElement { }); } - firstUpdated() { + firstUpdated(): void { fetch(this.url) .then((r) => r.json()) .catch((e) => console.error(e)) .then((r) => { - const ctx = (this.shadowRoot?.querySelector("canvas")).getContext( - "2d" - )!; + const canvas = this.shadowRoot?.querySelector("canvas"); + if (!canvas) { + console.warn("Failed to get canvas element"); + return false; + } + const ctx = canvas.getContext("2d"); + if (!ctx) { + console.warn("failed to get 2d context"); + return false; + } this.chart = new Chart(ctx, { type: "bar", data: { @@ -102,7 +109,7 @@ export class AdminLoginsChart extends LitElement { }); } - render() { + render(): TemplateResult { return html``; } } diff --git a/web/src/elements/CodeMirror.ts b/web/src/elements/CodeMirror.ts index 9e8076ea1..9bf90730a 100644 --- a/web/src/elements/CodeMirror.ts +++ b/web/src/elements/CodeMirror.ts @@ -1,4 +1,4 @@ -import { customElement, html, LitElement, property } from "lit-element"; +import { customElement, LitElement, property } from "lit-element"; // @ts-ignore import CodeMirror from "codemirror"; @@ -17,11 +17,11 @@ export class CodeMirrorTextarea extends LitElement { editor?: CodeMirror.EditorFromTextArea; - createRenderRoot() { + createRenderRoot() : ShadowRoot | Element { return this; } - firstUpdated() { + firstUpdated(): void { const textarea = this.querySelector("textarea"); if (!textarea) { return; @@ -33,7 +33,7 @@ export class CodeMirrorTextarea extends LitElement { readOnly: this.readOnly, autoRefresh: true, }); - this.editor.on("blur", (e) => { + this.editor.on("blur", () => { this.editor?.save(); }); } diff --git a/web/src/elements/FetchFillSlot.ts b/web/src/elements/FetchFillSlot.ts deleted file mode 100644 index 76af0b6da..000000000 --- a/web/src/elements/FetchFillSlot.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { LitElement, html, customElement, property } from "lit-element"; - -interface ComparisonHash { - [key: string]: (a: any, b: any) => boolean; -} - -@customElement("fetch-fill-slot") -export class FetchFillSlot extends LitElement { - @property() - url = ""; - - @property() - key = ""; - - @property() - value = ""; - - comparison(slotName: string) { - const comparisonOperatorsHash = { - "<": function (a: any, b: any) { - return a < b; - }, - ">": function (a: any, b: any) { - return a > b; - }, - ">=": function (a: any, b: any) { - return a >= b; - }, - "<=": function (a: any, b: any) { - return a <= b; - }, - "==": function (a: any, b: any) { - return a == b; - }, - "!=": function (a: any, b: any) { - return a != b; - }, - "===": function (a: any, b: any) { - return a === b; - }, - "!==": function (a: any, b: any) { - return a !== b; - }, - }; - const tokens = slotName.split(" "); - if (tokens.length < 3) { - throw new Error("nah"); - } - let a: any = tokens[0]; - if (a === "value") { - a = this.value; - } else { - a = parseInt(a, 10); - } - let b: any = tokens[2]; - if (b === "value") { - b = this.value; - } else { - b = parseInt(b, 10); - } - const comp = tokens[1]; - if (!(comp in comparisonOperatorsHash)) { - throw new Error("Invalid comparison"); - } - return comparisonOperatorsHash[comp](a, b); - } - - firstUpdated() { - fetch(this.url) - .then((r) => r.json()) - .then((r) => r[this.key]) - .then((r) => (this.value = r)); - } - - render() { - if (this.value === undefined) { - return html``; - } - let selectedSlot = ""; - this.querySelectorAll("[slot]").forEach((slot) => { - const comp = slot.getAttribute("slot")!; - if (this.comparison(comp)) { - selectedSlot = comp; - } - }); - this.querySelectorAll("[data-value]").forEach((dv) => { - dv.textContent = this.value; - }); - return html``; - } -} diff --git a/web/src/elements/Messages.ts b/web/src/elements/Messages.ts index 0b06d1e3b..8408fb9ac 100644 --- a/web/src/elements/Messages.ts +++ b/web/src/elements/Messages.ts @@ -1,4 +1,4 @@ -import { LitElement, html, customElement, property } from "lit-element"; +import { LitElement, html, customElement, property, TemplateResult } from "lit-element"; const LEVEL_ICON_MAP: { [key: string]: string } = { error: "fas fa-exclamation-circle", @@ -12,7 +12,7 @@ const ID = function (prefix: string) { }; interface Message { - levelTag: string; + level_tag: string; tags: string; message: string; } @@ -25,7 +25,7 @@ export class Messages extends LitElement { messageSocket?: WebSocket; retryDelay = 200; - createRenderRoot() { + createRenderRoot(): ShadowRoot | Element { return this; } @@ -38,16 +38,16 @@ export class Messages extends LitElement { } } - firstUpdated() { + firstUpdated(): void { this.fetchMessages(); } - connect() { + connect(): void { const wsUrl = `${window.location.protocol.replace("http", "ws")}//${ window.location.host }/ws/client/`; this.messageSocket = new WebSocket(wsUrl); - this.messageSocket.addEventListener("open", (e) => { + this.messageSocket.addEventListener("open", () => { console.debug(`passbook/messages: connected to ${wsUrl}`); }); this.messageSocket.addEventListener("close", (e) => { @@ -71,32 +71,29 @@ export class Messages extends LitElement { /* 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() { + fetchMessages(): Promise { console.debug("passbook/messages: fetching messages over direct api"); return fetch(this.url) .then((r) => r.json()) - .then((r) => { - r.forEach((m: any) => { - const message = { - levelTag: m.level_tag, - tags: m.tags, - message: m.message, - }; - this.renderMessage(message); + .then((r: Message[]) => { + r.forEach((m: Message) => { + this.renderMessage(m); }); }); } - renderMessage(message: Message) { - const container = this.querySelector(".pf-c-alert-group")!; + renderMessage(message: Message): void { + const container = this.querySelector(".pf-c-alert-group"); + if (!container) { + console.warn("passbook/messages: failed to find container"); + return; + } const id = ID("pb-message"); const el = document.createElement("template"); el.innerHTML = `
  • -
    +
    - +

    ${message.message} @@ -106,10 +103,10 @@ export class Messages extends LitElement { setTimeout(() => { this.querySelector(`#${id}`)?.remove(); }, 1500); - container.appendChild(el.content.firstChild!); + container.appendChild(el.content.firstChild!); // eslint-disable-line } - render() { + render(): TemplateResult { return html`

      `; } } diff --git a/web/src/elements/Spinner.ts b/web/src/elements/Spinner.ts index b3c4d6e71..d44340498 100644 --- a/web/src/elements/Spinner.ts +++ b/web/src/elements/Spinner.ts @@ -1,5 +1,5 @@ import { gettext } from "django"; -import { customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; // @ts-ignore import SpinnerStyle from "@patternfly/patternfly/components/Spinner/spinner.css"; @@ -15,7 +15,7 @@ export class Spinner extends LitElement { @property() size: SpinnerSize = SpinnerSize.Medium; - static get styles() { + static get styles(): CSSResult[] { return [SpinnerStyle]; } diff --git a/web/src/elements/Tabs.ts b/web/src/elements/Tabs.ts index 0e5c64ac5..49e57d1da 100644 --- a/web/src/elements/Tabs.ts +++ b/web/src/elements/Tabs.ts @@ -1,4 +1,4 @@ -import { LitElement, html, customElement, property } from "lit-element"; +import { LitElement, html, customElement, property, CSSResult, TemplateResult } from "lit-element"; // @ts-ignore import TabsStyle from "@patternfly/patternfly/components/Tabs/tabs.css"; // @ts-ignore @@ -10,12 +10,23 @@ export class Tabs extends LitElement { @property() currentPage?: string; - static get styles() { + static get styles(): CSSResult[] { return [GlobalsStyle, TabsStyle]; } - render() { - const pages = Array.from(this.querySelectorAll("[slot]")!); + renderTab(page: Element): TemplateResult { + const slot = page.attributes.getNamedItem("slot")?.value; + return html`
    • + +
    • `; + } + + render(): TemplateResult { + const pages = Array.from(this.querySelectorAll("[slot]")); if (!this.currentPage) { if (pages.length < 1) { return html`

      no tabs defined

      `; @@ -24,25 +35,7 @@ export class Tabs extends LitElement { } return html`
        - ${pages.map((page) => { - const slot = page.attributes.getNamedItem("slot")?.value; - return html`
      • - -
      • `; - })} + ${pages.map((page) => this.renderTab(page))}
      `; diff --git a/web/src/elements/buttons/ActionButton.ts b/web/src/elements/buttons/ActionButton.ts index f15303d40..770425d28 100644 --- a/web/src/elements/buttons/ActionButton.ts +++ b/web/src/elements/buttons/ActionButton.ts @@ -1,5 +1,5 @@ import { getCookie } from "../../utils"; -import { customElement, html, property } from "lit-element"; +import { customElement, property } from "lit-element"; import { ERROR_CLASS, SUCCESS_CLASS } from "../../constants"; import { SpinnerButton } from "./SpinnerButton"; @@ -8,21 +8,26 @@ export class ActionButton extends SpinnerButton { @property() url = ""; - callAction() { + callAction(): void { if (this.isRunning === true) { return; } this.setLoading(); const csrftoken = getCookie("passbook_csrf"); + if (!csrftoken) { + console.debug("No csrf token in cookie"); + this.setDone(ERROR_CLASS); + return; + } const request = new Request(this.url, { - headers: { "X-CSRFToken": csrftoken! }, + headers: { "X-CSRFToken": csrftoken }, }); fetch(request, { method: "POST", mode: "same-origin", }) .then((r) => r.json()) - .then((r) => { + .then(() => { this.setDone(SUCCESS_CLASS); }) .catch(() => { diff --git a/web/src/elements/buttons/Dropdown.ts b/web/src/elements/buttons/Dropdown.ts index 51d5e170e..3a76ad9ae 100644 --- a/web/src/elements/buttons/Dropdown.ts +++ b/web/src/elements/buttons/Dropdown.ts @@ -1,18 +1,18 @@ -import { customElement, html, LitElement } from "lit-element"; +import { customElement, html, LitElement, TemplateResult } from "lit-element"; @customElement("pb-dropdown") export class DropdownButton extends LitElement { constructor() { super(); - const menu = this.querySelector(".pf-c-dropdown__menu")!; + const menu = this.querySelector(".pf-c-dropdown__menu"); this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => { - btn.addEventListener("click", (e) => { + btn.addEventListener("click", () => { menu.hidden = !menu.hidden; }); }); } - render() { + render(): TemplateResult { return html``; } } diff --git a/web/src/elements/buttons/ModalButton.ts b/web/src/elements/buttons/ModalButton.ts index 85aed6db3..9fe1e8767 100644 --- a/web/src/elements/buttons/ModalButton.ts +++ b/web/src/elements/buttons/ModalButton.ts @@ -1,4 +1,4 @@ -import { css, customElement, html, LitElement, property } from "lit-element"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; // @ts-ignore import ModalBoxStyle from "@patternfly/patternfly/components/ModalBox/modal-box.css"; // @ts-ignore @@ -22,7 +22,7 @@ export class ModalButton extends LitElement { @property() open = false; - static get styles() { + static get styles(): CSSResult[] { return [ css` :host { @@ -49,7 +49,7 @@ export class ModalButton extends LitElement { }); } - updateHandlers() { + updateHandlers(): void { // Ensure links close the modal this.querySelectorAll("[slot=modal] a").forEach((a) => { if (a.target == "_blank") { @@ -63,7 +63,7 @@ export class ModalButton extends LitElement { }); // Make name field update slug field this.querySelectorAll("input[name=name]").forEach((input) => { - input.addEventListener("input", (e) => { + input.addEventListener("input", () => { const form = input.closest("form"); if (form === null) { return; @@ -90,7 +90,12 @@ export class ModalButton extends LitElement { }) .then((data) => { if (data.indexOf("csrfmiddlewaretoken") !== -1) { - this.querySelector("[slot=modal]")!.innerHTML = data; + const modalSlot = this.querySelector("[slot=modal]"); + if (!modalSlot) { + console.debug("passbook/modalbutton: modal slot not found?"); + return; + } + modalSlot.innerHTML = data; console.debug("passbook/modalbutton: re-showing form"); this.updateHandlers(); } else { @@ -110,7 +115,7 @@ export class ModalButton extends LitElement { }); } - onClick(e: MouseEvent) { + onClick(): void { if (!this.href) { this.updateHandlers(); this.open = true; @@ -121,7 +126,11 @@ export class ModalButton extends LitElement { }) .then((r) => r.text()) .then((t) => { - this.querySelector("[slot=modal]")!.innerHTML = t; + const modalSlot = this.querySelector("[slot=modal]"); + if (!modalSlot) { + return; + } + modalSlot.innerHTML = t; this.updateHandlers(); this.open = true; this.querySelectorAll("pb-spinner-button").forEach((sb) => { @@ -134,7 +143,7 @@ export class ModalButton extends LitElement { } } - renderModal() { + renderModal(): TemplateResult { return html`
      `; } - render() { - return html` this.onClick(e)}> + render(): TemplateResult { + return html` this.onClick()}> ${this.open ? this.renderModal() : ""}`; } } diff --git a/web/src/elements/buttons/SpinnerButton.ts b/web/src/elements/buttons/SpinnerButton.ts index 845677e54..b0645bc62 100644 --- a/web/src/elements/buttons/SpinnerButton.ts +++ b/web/src/elements/buttons/SpinnerButton.ts @@ -1,4 +1,4 @@ -import { css, customElement, html, LitElement, property } from "lit-element"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; // @ts-ignore import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css"; // @ts-ignore @@ -15,7 +15,7 @@ export class SpinnerButton extends LitElement { @property() form?: string; - static get styles() { + static get styles(): CSSResult[] { return [ GlobalsStyle, ButtonStyle, @@ -35,13 +35,13 @@ export class SpinnerButton extends LitElement { this.classList.add(PRIMARY_CLASS); } - setLoading() { + setLoading(): void { this.isRunning = true; this.classList.add(PROGRESS_CLASS); this.requestUpdate(); } - setDone(statusClass: string) { + setDone(statusClass: string): void { this.isRunning = false; this.classList.remove(PROGRESS_CLASS); this.classList.replace(PRIMARY_CLASS, statusClass); @@ -52,7 +52,7 @@ export class SpinnerButton extends LitElement { }, 1000); } - callAction() { + callAction(): void { if (this.isRunning === true) { return; } @@ -64,7 +64,7 @@ export class SpinnerButton extends LitElement { this.setLoading(); } - render() { + render(): TemplateResult { return html``; diff --git a/web/src/elements/sidebar/Sidebar.ts b/web/src/elements/sidebar/Sidebar.ts index 51233ebfb..570b55d44 100644 --- a/web/src/elements/sidebar/Sidebar.ts +++ b/web/src/elements/sidebar/Sidebar.ts @@ -1,4 +1,4 @@ -import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; // @ts-ignore import PageStyle from "@patternfly/patternfly/components/Page/page.css"; // @ts-ignore @@ -23,7 +23,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ { name: "Monitor", path: ["/audit/audit/"], - condition: (sb: Sidebar) => { + condition: (sb: Sidebar): boolean => { return sb.user?.is_superuser || false; }, }, @@ -123,7 +123,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ path: ["/administration/tokens/"], }, ], - condition: (sb: Sidebar) => { + condition: (sb: Sidebar): boolean => { return sb.user?.is_superuser || false; }, }, @@ -137,7 +137,7 @@ export class Sidebar extends LitElement { @property() user?: User; - static get styles() { + static get styles(): CSSResult[] { return [ GlobalsStyle, PageStyle, @@ -169,7 +169,7 @@ export class Sidebar extends LitElement { super(); User.me().then((u) => (this.user = u)); this.activePath = window.location.hash.slice(1, Infinity); - window.addEventListener("hashchange", (e) => { + window.addEventListener("hashchange", () => { this.activePath = window.location.hash.slice(1, Infinity); }); } @@ -200,7 +200,7 @@ export class Sidebar extends LitElement { `; } - render() { + render(): TemplateResult { return html`