web: sort components into folders, implement pagination for table
This commit is contained in:
parent
606e32603e
commit
f7022dd11f
22
web/dist/main.js
vendored
22
web/dist/main.js
vendored
File diff suppressed because one or more lines are too long
2
web/dist/main.js.map
vendored
2
web/dist/main.js.map
vendored
File diff suppressed because one or more lines are too long
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>`;
|
||||
}
|
||||
}
|
|
@ -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")
|
|
@ -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);
|
||||
});
|
||||
})
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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;
|
|
@ -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 {
|
|
@ -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 {
|
86
web/src/elements/table/Table.ts
Normal file
86
web/src/elements/table/Table.ts
Normal 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>`;
|
||||
}
|
||||
}
|
71
web/src/elements/table/TablePagination.ts
Normal file
71
web/src/elements/table/TablePagination.ts
Normal 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>`;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Reference in a new issue