web: sort components into folders, implement pagination for table

This commit is contained in:
Jens Langhammer 2020-11-29 22:14:48 +01:00
parent 606e32603e
commit f7022dd11f
17 changed files with 213 additions and 109 deletions

22
web/dist/main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,7 +14,7 @@ export class Client {
return builtUrl;
}
fetch<T>(url: string[], query?: { [key: string]: string }): Promise<T> {
fetch<T>(url: string[], query?: { [key: string]: any }): Promise<T> {
const finalUrl = this.makeUrl(url, query);
return fetch(finalUrl)
.then((r) => {
@ -35,9 +35,20 @@ export class Client {
export const DefaultClient = new Client();
export interface PBResponse {
export interface PBPagination {
next?: number;
previous?: number;
count: number;
next: string;
previous: string;
current: number;
total_pages: number;
start_index: number;
end_index: number;
}
export interface PBResponse {
pagination: PBPagination;
results: Array<any>;
}

View file

@ -12,19 +12,18 @@ export class Config {
error_reporting_send_pii?: boolean;
static get(): Promise<Config> {
return DefaultClient.fetch<Config>(["root", "config"])
.then((config) => {
if (config.error_reporting_enabled) {
Sentry.init({
dsn: "https://33cdbcb23f8b436dbe0ee06847410b67@sentry.beryju.org/3",
release: `passbook@${VERSION}`,
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 1.0,
environment: config.error_reporting_environment,
});
console.debug(`passbook/config: Sentry enabled.`);
}
return config;
});
return DefaultClient.fetch<Config>(["root", "config"]).then((config) => {
if (config.error_reporting_enabled) {
Sentry.init({
dsn: "https://33cdbcb23f8b436dbe0ee06847410b67@sentry.beryju.org/3",
release: `passbook@${VERSION}`,
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 1.0,
environment: config.error_reporting_environment,
});
console.debug(`passbook/config: Sentry enabled.`);
}
return config;
});
}
}

View file

@ -1,47 +0,0 @@
import { html, LitElement } from "lit-element";
import { until } from "lit-html/directives/until.js";
import { PBResponse } from "../api/client";
import { COMMON_STYLES } from "../common/styles";
export abstract class Table extends LitElement {
abstract apiEndpoint(): Promise<PBResponse>;
abstract columns(): Array<string>;
abstract row(item: any): Array<string>;
private data: PBResponse = <PBResponse>{};
static get styles() {
return [COMMON_STYLES];
}
private renderRows() {
return this.apiEndpoint()
.then((r) => (this.data = r))
.then(() => {
return this.data.results.map((item) => {
const fullRow = [`<tr role="row">`].concat(
this.row(item).map((col) => {
return `<td role="cell">${col}</td>`;
})
);
fullRow.push(`</tr>`);
return html(<any>fullRow);
});
});
}
render() {
return html`<table class="pf-c-table pf-m-compact pf-m-grid-md">
<thead>
<tr role="row">
${this.columns().map(
(col) => html`<th role="columnheader" scope="col">${col}</th>`
)}
</tr>
</thead>
<tbody role="rowgroup">
${until(this.renderRows(), html`<tr role="row"><td>loading...</tr></td>`)}
</tbody>
</table>`;
}
}

View file

@ -1,6 +1,6 @@
import { getCookie } from "../utils";
import { getCookie } from "../../utils";
import { customElement, html, property } from "lit-element";
import { ERROR_CLASS, SUCCESS_CLASS } from "../constants";
import { ERROR_CLASS, SUCCESS_CLASS } from "../../constants";
import { SpinnerButton } from "./SpinnerButton";
@customElement("pb-action-button")

View file

@ -10,9 +10,9 @@ import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
// @ts-ignore
import fa from "@fortawesome/fontawesome-free/css/solid.css";
import { convertToSlug } from "../utils";
import { convertToSlug } from "../../utils";
import { SpinnerButton } from "./SpinnerButton";
import { PRIMARY_CLASS } from "../constants";
import { PRIMARY_CLASS } from "../../constants";
@customElement("pb-modal-button")
export class ModalButton extends LitElement {
@ -124,7 +124,7 @@ export class ModalButton extends LitElement {
this.querySelector("[slot=modal]")!.innerHTML = t;
this.updateHandlers();
this.open = true;
this.querySelectorAll<SpinnerButton>("pb-spinner-button").forEach(sb => {
this.querySelectorAll<SpinnerButton>("pb-spinner-button").forEach((sb) => {
sb.setDone(PRIMARY_CLASS);
});
})

View file

@ -5,11 +5,7 @@ import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
// @ts-ignore
import SpinnerStyle from "@patternfly/patternfly/components/Spinner/spinner.css";
import {
ColorStyles,
PRIMARY_CLASS,
PROGRESS_CLASS,
} from "../constants";
import { ColorStyles, PRIMARY_CLASS, PROGRESS_CLASS } from "../../constants";
@customElement("pb-spinner-button")
export class SpinnerButton extends LitElement {

View file

@ -3,8 +3,8 @@ import { css, customElement, html, LitElement, property } from "lit-element";
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
// @ts-ignore
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
import { tokenByIdentifier } from "../api/token";
import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../constants";
import { tokenByIdentifier } from "../../api/token";
import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
@customElement("pb-token-copy-button")
export class TokenCopyButton extends LitElement {

View file

@ -6,7 +6,7 @@ import NavStyle from "@patternfly/patternfly/components/Nav/nav.css";
// @ts-ignore
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
import { User } from "../api/user";
import { User } from "../../api/user";
export interface SidebarItem {
name: string;

View file

@ -3,7 +3,7 @@ import { css, customElement, html, LitElement, property } from "lit-element";
import PageStyle from "@patternfly/patternfly/components/Page/page.css";
// @ts-ignore
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
import { Config } from "../api/config";
import { Config } from "../../api/config";
@customElement("pb-sidebar-brand")
export class SidebarBrand extends LitElement {

View file

@ -5,7 +5,7 @@ import NavStyle from "@patternfly/patternfly/components/Nav/nav.css";
import fa from "@fortawesome/fontawesome-free/css/all.css";
// @ts-ignore
import AvatarStyle from "@patternfly/patternfly/components/Avatar/avatar.css";
import { User } from "../api/user";
import { User } from "../../api/user";
@customElement("pb-sidebar-user")
export class SidebarUser extends LitElement {

View file

@ -0,0 +1,86 @@
import { html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until.js";
import { PBResponse } from "../../api/client";
import { COMMON_STYLES } from "../../common/styles";
export abstract class Table extends LitElement {
abstract apiEndpoint(page: number): Promise<PBResponse>;
abstract columns(): Array<string>;
abstract row(item: any): Array<string>;
@property()
data?: PBResponse;
@property()
page: number = 1;
static get styles() {
return [COMMON_STYLES];
}
public fetch() {
this.apiEndpoint(this.page).then((r) => {
this.data = r;
this.page = r.pagination.current;
});
}
private renderRows(): TemplateResult[] | undefined {
if (!this.data) {
return;
}
return this.data.results.map((item) => {
const fullRow = [`<tr role="row">`].concat(
this.row(item).map((col) => {
return `<td role="cell">${col}</td>`;
})
);
fullRow.push(`</tr>`);
return html(<any>fullRow);
});
}
render() {
if (!this.data) {
this.fetch();
return;
}
return html`<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<div class="pf-c-toolbar__bulk-select">
<slot name="create-button"></slot>
<button
@click=${() => {
this.fetch();
}}
class="pf-c-button pf-m-primary"
>
Refresh
</button>
</div>
<pb-table-pagination
class="pf-c-toolbar__item pf-m-pagination"
.table=${this}
></pb-table-pagination>
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-md">
<thead>
<tr role="row">
${this.columns().map(
(col) => html`<th role="columnheader" scope="col">${col}</th>`
)}
</tr>
</thead>
<tbody role="rowgroup">
${this.renderRows()}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
<pb-table-pagination
class="pf-c-toolbar__item pf-m-pagination"
.table=${this}
></pb-table-pagination>
</div>`;
}
}

View file

@ -0,0 +1,71 @@
import { customElement, html, LitElement, property } from "lit-element";
import { Table } from "./Table";
import { COMMON_STYLES } from "../../common/styles";
@customElement("pb-table-pagination")
export class TablePagination extends LitElement {
@property()
table?: Table;
static get styles() {
return [COMMON_STYLES];
}
previousHandler() {
if (!this.table?.data?.pagination.previous) {
console.debug(`passbook/tables: no previous`);
return;
}
this.table.page = this.table?.data?.pagination.previous;
}
nextHandler() {
if (!this.table?.data?.pagination.next) {
console.debug(`passbook/tables: no next`);
return;
}
this.table.page = this.table?.data?.pagination.next;
}
render() {
return html` <div class="pf-c-pagination pf-m-compact pf-m-hidden pf-m-visible-on-md">
<div class="pf-c-pagination pf-m-compact pf-m-compact pf-m-hidden pf-m-visible-on-md">
<div class="pf-c-options-menu">
<div class="pf-c-options-menu__toggle pf-m-text pf-m-plain">
<span class="pf-c-options-menu__toggle-text">
${this.table?.data?.pagination.start_index} -
${this.table?.data?.pagination.end_index} of
${this.table?.data?.pagination.count}
</span>
</div>
</div>
<nav class="pf-c-pagination__nav" aria-label="Pagination">
<div class="pf-c-pagination__nav-control pf-m-prev">
<button
class="pf-c-button pf-m-plain"
@click=${() => {
this.previousHandler();
}}
disabled="${this.table?.data?.pagination.previous ? "true" : "false"}"
aria-label="{% trans 'Go to previous page' %}"
>
<i class="fas fa-angle-left" aria-hidden="true"></i>
</button>
</div>
<div class="pf-c-pagination__nav-control pf-m-next">
<button
class="pf-c-button pf-m-plain"
@click=${() => {
this.nextHandler();
}}
disabled="${this.table?.data?.pagination.next ? "true" : "false"}"
aria-label="{% trans 'Go to next page' %}"
>
<i class="fas fa-angle-right" aria-hidden="true"></i>
</button>
</div>
</nav>
</div>
</div>`;
}
}

View file

@ -1,18 +1,19 @@
import "construct-style-sheets-polyfill";
import "./elements/ActionButton";
import "./elements/SpinnerButton";
import "./elements/AdminLoginsChart";
import "./elements/buttons/ActionButton";
import "./elements/buttons/Dropdown";
import "./elements/buttons/ModalButton";
import "./elements/buttons/SpinnerButton";
import "./elements/buttons/TokenCopyButton";
import "./elements/CodeMirror";
import "./elements/Dropdown";
import "./elements/FetchFillSlot";
import "./elements/Messages";
import "./elements/ModalButton";
import "./elements/Sidebar";
import "./elements/SidebarBrand";
import "./elements/SidebarUser";
import "./elements/sidebar/Sidebar";
import "./elements/sidebar/SidebarBrand";
import "./elements/sidebar/SidebarUser";
import "./elements/Tabs";
import "./elements/TokenCopyButton";
import "./elements/table/TablePagination";
import "./pages/applications/ApplicationViewPage";
import "./pages/FlowShellCard";
import "./pages/RouterOutlet";

View file

@ -2,17 +2,18 @@ import { css, customElement, html, LitElement, property, TemplateResult } from "
import { Application } from "../../api/application";
import { DefaultClient, PBResponse } from "../../api/client";
import { COMMON_STYLES } from "../../common/styles";
import { Table } from "../../elements/Table";
import { Table } from "../../elements/table/Table";
@customElement("pb-bound-policies-list")
export class BoundPoliciesList extends Table {
@property()
target?: string;
apiEndpoint(): Promise<PBResponse> {
apiEndpoint(page: number): Promise<PBResponse> {
return DefaultClient.fetch<PBResponse>(["policies", "bindings"], {
target: this.target!,
ordering: "order",
page: page,
});
}