static: format code

This commit is contained in:
Jens Langhammer 2020-11-21 20:48:49 +01:00
parent e42ad3f659
commit 1cb227305c
21 changed files with 1221 additions and 1077 deletions

View File

@ -0,0 +1,3 @@
# Ignore artifacts:
dist
coverage

View File

@ -0,0 +1 @@
{}

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,29 @@
{ {
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build": "rollup -c ./rollup.config.js", "build": "rollup -c ./rollup.config.js",
"watch": "rollup -c -w" "watch": "rollup -c -w"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1", "@fortawesome/fontawesome-free": "^5.15.1",
"@patternfly/patternfly": "^4.65.6", "@patternfly/patternfly": "^4.65.6",
"@types/chart.js": "^2.9.28", "@types/chart.js": "^2.9.28",
"@types/codemirror": "0.0.98", "@types/codemirror": "0.0.98",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"codemirror": "^5.58.3", "codemirror": "^5.58.3",
"lit-element": "^2.4.0", "lit-element": "^2.4.0",
"lit-html": "^1.3.0", "lit-html": "^1.3.0",
"rollup": "^2.33.3", "rollup": "^2.33.3",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
"tslib": "^2.0.3" "tslib": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "^6.1.0", "@rollup/plugin-typescript": "^6.1.0",
"rollup-plugin-commonjs": "^10.1.0", "prettier": "2.2.0",
"rollup-plugin-minify-html-literals": "^1.2.5", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-minify-html-literals": "^1.2.5",
"rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^7.0.2" "rollup-plugin-sourcemaps": "^0.6.3",
} "rollup-plugin-terser": "^7.0.2"
}
} }

View File

@ -1,7 +1,7 @@
@font-face { @font-face {
font-family: 'DIN 1451 Std'; font-family: "DIN 1451 Std";
src: url('fonts/DINEngschriftStd.woff2') format('woff2'), src: url("fonts/DINEngschriftStd.woff2") format("woff2"),
url('fonts/DINEngschriftStd.woff') format('woff'); url("fonts/DINEngschriftStd.woff") format("woff");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
@ -13,7 +13,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
margin-right: .5em; margin-right: 0.5em;
color: var(--pf-global--Color--light-200); color: var(--pf-global--Color--light-200);
} }
@ -23,11 +23,11 @@
.pb-brand > img { .pb-brand > img {
max-height: 68px; max-height: 68px;
margin-right: .5em; margin-right: 0.5em;
} }
.pf-c-background-image::before { .pf-c-background-image::before {
background-image: url('./flow_background.jpg'); background-image: url("./flow_background.jpg");
background-position: center; background-position: center;
} }
@ -91,7 +91,8 @@ select[multiple] {
flex: 1 1; flex: 1 1;
} }
.selector-available, .selector-chosen { .selector-available,
.selector-chosen {
width: auto; width: auto;
flex: 1 1; flex: 1 1;
display: flex; display: flex;
@ -114,7 +115,8 @@ select[multiple] {
list-style: none; list-style: none;
} }
.selector-add, .selector-remove { .selector-add,
.selector-remove {
width: 20px; width: 20px;
height: 20px; height: 20px;
background-size: 20px auto; background-size: 20px auto;
@ -128,7 +130,8 @@ select[multiple] {
background-position: 0 -80px; background-position: 0 -80px;
} }
a.selector-chooseall, a.selector-clearall { a.selector-chooseall,
a.selector-clearall {
align-self: center; align-self: center;
} }
@ -145,7 +148,8 @@ a.selector-chooseall, a.selector-clearall {
margin-bottom: 0; margin-bottom: 0;
} }
.stacked .selector-available, .stacked .selector-chosen { .stacked .selector-available,
.stacked .selector-chosen {
width: auto; width: auto;
} }
@ -161,7 +165,8 @@ a.selector-chooseall, a.selector-clearall {
padding: 3px; padding: 3px;
} }
.stacked .selector-add, .stacked .selector-remove { .stacked .selector-add,
.stacked .selector-remove {
background-size: 20px auto; background-size: 20px auto;
} }
@ -181,7 +186,8 @@ a.selector-chooseall, a.selector-clearall {
background-position: 0 -20px; background-position: 0 -20px;
} }
.help-tooltip, .selector .help-icon { .help-tooltip,
.selector .help-icon {
display: none; display: none;
} }
@ -212,7 +218,8 @@ form .form-row p.datetime {
overflow: auto; overflow: auto;
} }
.selector-add, .selector-remove { .selector-add,
.selector-remove {
width: 16px; width: 16px;
height: 16px; height: 16px;
display: block; display: block;
@ -222,11 +229,13 @@ form .form-row p.datetime {
opacity: 0.3; opacity: 0.3;
} }
.active.selector-add, .active.selector-remove { .active.selector-add,
.active.selector-remove {
opacity: 1; opacity: 1;
} }
.active.selector-add:hover, .active.selector-remove:hover { .active.selector-add:hover,
.active.selector-remove:hover {
cursor: pointer; cursor: pointer;
} }
@ -234,7 +243,8 @@ form .form-row p.datetime {
background: url(../admin/img/selector-icons.svg) 0 -96px no-repeat; background: url(../admin/img/selector-icons.svg) 0 -96px no-repeat;
} }
.active.selector-add:focus, .active.selector-add:hover { .active.selector-add:focus,
.active.selector-add:hover {
background-position: 0 -112px; background-position: 0 -112px;
} }

View File

@ -1,27 +1,29 @@
import resolve from 'rollup-plugin-node-resolve'; import resolve from "rollup-plugin-node-resolve";
import commonjs from 'rollup-plugin-commonjs'; import commonjs from "rollup-plugin-commonjs";
import minifyHTML from 'rollup-plugin-minify-html-literals'; import minifyHTML from "rollup-plugin-minify-html-literals";
import { terser } from 'rollup-plugin-terser'; import { terser } from "rollup-plugin-terser";
import sourcemaps from 'rollup-plugin-sourcemaps'; import sourcemaps from "rollup-plugin-sourcemaps";
import typescript from '@rollup/plugin-typescript'; import typescript from "@rollup/plugin-typescript";
import cssimport from "rollup-plugin-cssimport"; import cssimport from "rollup-plugin-cssimport";
export default [{ export default [
input: './src/main.ts',
output: [
{ {
format: 'es', input: "./src/main.ts",
dir: 'dist', output: [
sourcemap: true {
} format: "es",
], dir: "dist",
plugins: [ sourcemap: true,
cssimport(), },
typescript(), ],
resolve({browser: true}), plugins: [
commonjs(), cssimport(),
sourcemaps(), typescript(),
minifyHTML(), resolve({ browser: true }),
terser(), commonjs(),
], sourcemaps(),
}]; minifyHTML(),
terser(),
],
},
];

View File

@ -9,10 +9,11 @@ const PROGRESS_CLASSES = ["pf-m-progress", "pf-m-in-progress"];
@customElement("pb-action-button") @customElement("pb-action-button")
export class ActionButton extends LitElement { export class ActionButton extends LitElement {
constructor() { constructor() {
super(); super();
this.querySelector("button")?.addEventListener('click', e => this.callAction()); this.querySelector("button")?.addEventListener("click", (e) =>
this.callAction()
);
} }
@property() @property()
@ -41,33 +42,39 @@ export class ActionButton extends LitElement {
return; return;
} }
this.setLoading(); this.setLoading();
const csrftoken = getCookie('passbook_csrf'); const csrftoken = getCookie("passbook_csrf");
const request = new Request( const request = new Request(this.url, {
this.url, headers: { "X-CSRFToken": csrftoken! },
{ headers: { 'X-CSRFToken': csrftoken! } } });
);
fetch(request, { fetch(request, {
method: "POST", method: "POST",
mode: 'same-origin', mode: "same-origin",
}).then(r => r.json()).then(r => { })
this.setDone(SUCCESS_CLASS); .then((r) => r.json())
}).catch(() => { .then((r) => {
this.setDone(ERROR_CLASS); this.setDone(SUCCESS_CLASS);
}); })
.catch(() => {
this.setDone(ERROR_CLASS);
});
} }
render() { render() {
return html`<button class="pf-c-button pf-m-primary"> return html`<button class="pf-c-button pf-m-primary">
${this.isRunning ? html` ${this.isRunning
<span class="pf-c-button__progress"> ? html` <span class="pf-c-button__progress">
<span class="pf-c-spinner pf-m-md" role="progressbar" aria-valuetext="Loading..."> <span
<span class="pf-c-spinner__clipper"></span> class="pf-c-spinner pf-m-md"
<span class="pf-c-spinner__lead-ball"></span> role="progressbar"
<span class="pf-c-spinner__tail-ball"></span> aria-valuetext="Loading..."
</span> >
</span>` : ""} <span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</span>`
: ""}
<slot></slot> <slot></slot>
</button>`; </button>`;
} }
} }

View File

@ -8,7 +8,6 @@ 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: string = "";
@ -16,23 +15,23 @@ export class AdminLoginsChart extends LitElement {
static get styles() { static get styles() {
return css` return css`
:host { :host {
position: relative; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
display: block; display: block;
min-height: 25rem; min-height: 25rem;
} }
canvas { canvas {
width: 100px; width: 100px;
height: 100px; height: 100px;
} }
`; `;
} }
constructor() { constructor() {
super(); super();
window.addEventListener('resize', () => { window.addEventListener("resize", () => {
if (this.chart) { if (this.chart) {
this.chart.resize(); this.chart.resize();
} }
@ -40,62 +39,79 @@ export class AdminLoginsChart extends LitElement {
} }
firstUpdated() { firstUpdated() {
fetch(this.url).then(r => r.json()).catch(e => console.error(e)).then(r => { fetch(this.url)
let ctx = (<HTMLCanvasElement>this.shadowRoot?.querySelector("canvas")).getContext('2d')!; .then((r) => r.json())
this.chart = new Chart(ctx, { .catch((e) => console.error(e))
type: 'bar', .then((r) => {
data: { let ctx = (<HTMLCanvasElement>(
datasets: [ this.shadowRoot?.querySelector("canvas")
{ )).getContext("2d")!;
label: 'Failed Logins', this.chart = new Chart(ctx, {
backgroundColor: "rgba(201, 25, 11, .5)", type: "bar",
spanGaps: true, data: {
data: r.logins_failed_per_1h, datasets: [
}, {
{ label: "Failed Logins",
label: 'Successful Logins', backgroundColor: "rgba(201, 25, 11, .5)",
backgroundColor: "rgba(189, 229, 184, .5)", spanGaps: true,
spanGaps: true, data: r.logins_failed_per_1h,
data: r.logins_per_1h,
},
]
},
options: {
maintainAspectRatio: false,
spanGaps: true,
scales: {
xAxes: [{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
}, },
type: 'time', {
offset: true, label: "Successful Logins",
ticks: { backgroundColor: "rgba(189, 229, 184, .5)",
callback: function (value, index: number, values) { spanGaps: true,
const valueStamp = <TickValue>(<unknown>values[index]); data: r.logins_per_1h,
const delta = (Date.now() - valueStamp.value); },
const ago = Math.round(delta / 1000 / 3600); ],
return `${ago} Hours ago`; },
options: {
maintainAspectRatio: false,
spanGaps: true,
scales: {
xAxes: [
{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
type: "time",
offset: true,
ticks: {
callback: function (
value,
index: number,
values
) {
const valueStamp = <TickValue>(
(<unknown>values[index])
);
const delta =
Date.now() - valueStamp.value;
const ago = Math.round(
delta / 1000 / 3600
);
return `${ago} Hours ago`;
},
autoSkip: true,
maxTicksLimit: 8,
},
}, },
autoSkip: true, ],
maxTicksLimit: 8 yAxes: [
} {
}], stacked: true,
yAxes: [{ gridLines: {
stacked: true, color: "rgba(0, 0, 0, 0)",
gridLines: { },
color: "rgba(0, 0, 0, 0)", },
} ],
}] },
} },
} });
}); });
});
} }
render() { render() {
return html`<canvas></canvas>`; return html`<canvas></canvas>`;
} }
} }

View File

@ -7,7 +7,6 @@ interface RegexAnchor {
@customElement("pb-admin-sidebar") @customElement("pb-admin-sidebar")
export class AdminSideBar extends LitElement { export class AdminSideBar extends LitElement {
@property() @property()
activePath: string; activePath: string;
@ -16,33 +15,35 @@ export class AdminSideBar extends LitElement {
constructor() { constructor() {
super(); super();
this.activePath = window.location.hash.slice(1, Infinity); this.activePath = window.location.hash.slice(1, Infinity);
window.addEventListener("hashchange", e => { window.addEventListener("hashchange", (e) => {
this.activePath = window.location.hash.slice(1, Infinity); this.activePath = window.location.hash.slice(1, Infinity);
}); });
this.querySelectorAll<HTMLAnchorElement>(".pf-c-nav__link").forEach(a => { this.querySelectorAll<HTMLAnchorElement>(".pf-c-nav__link").forEach(
let rawValue = a.attributes.getNamedItem("pb-url-prefix")?.value; (a) => {
if (!rawValue) { let rawValue = a.attributes.getNamedItem("pb-url-prefix")
const parsedURL = new URL(a.href); ?.value;
if (parsedURL.hash === "") { if (!rawValue) {
console.log(`Ignoring ${a}`); const parsedURL = new URL(a.href);
return; if (parsedURL.hash === "") {
console.log(`Ignoring ${a}`);
return;
}
rawValue = `^${parsedURL.hash.slice(1, Infinity)}$`;
} }
rawValue = `^${parsedURL.hash.slice(1, Infinity)}$`; const regexp = RegExp(rawValue);
this.paths.push({ anchor: a, match: regexp });
} }
const regexp = RegExp(rawValue); );
this.paths.push({anchor: a, match: regexp});
});
} }
render() { render() {
this.paths.forEach(path => { this.paths.forEach((path) => {
if (path.match.exec(this.activePath)) { if (path.match.exec(this.activePath)) {
path.anchor.classList.add("pf-m-current"); path.anchor.classList.add("pf-m-current");
} else { } else {
path.anchor.classList.remove("pf-m-current"); path.anchor.classList.remove("pf-m-current");
} }
}) });
return html`<slot></slot>`; return html`<slot></slot>`;
} }
} }

View File

@ -9,7 +9,6 @@ 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: boolean = false;
@ -29,7 +28,7 @@ export class CodeMirrorTextarea extends LitElement {
} }
this.editor = CodeMirror.fromTextArea(textarea, { this.editor = CodeMirror.fromTextArea(textarea, {
mode: this.mode, mode: this.mode,
theme: 'monokai', theme: "monokai",
lineNumbers: false, lineNumbers: false,
readOnly: this.readOnly, readOnly: this.readOnly,
autoRefresh: true, autoRefresh: true,
@ -38,5 +37,4 @@ export class CodeMirrorTextarea extends LitElement {
this.editor?.save(); this.editor?.save();
}); });
} }
} }

View File

@ -2,12 +2,11 @@ import { customElement, html, LitElement } from "lit-element";
@customElement("pb-dropdown") @customElement("pb-dropdown")
export class DropdownButton extends LitElement { export class DropdownButton extends LitElement {
constructor() { constructor() {
super() super();
const menu = <HTMLElement>this.querySelector('.pf-c-dropdown__menu')!; const menu = <HTMLElement>this.querySelector(".pf-c-dropdown__menu")!;
this.querySelectorAll("button").forEach(btn => { this.querySelectorAll("button").forEach((btn) => {
btn.addEventListener("click", e => { btn.addEventListener("click", (e) => {
menu.hidden = !menu.hidden; menu.hidden = !menu.hidden;
}); });
}); });
@ -16,5 +15,4 @@ export class DropdownButton extends LitElement {
render() { render() {
return html`<slot></slot>`; return html`<slot></slot>`;
} }
} }

View File

@ -1,12 +1,11 @@
import { LitElement, html, customElement, property } from 'lit-element'; import { LitElement, html, customElement, property } from "lit-element";
interface ComparisonHash { interface ComparisonHash {
[key: string]: (a: any, b: any) => boolean [key: string]: (a: any, b: any) => boolean;
} }
@customElement("fetch-fill-slot") @customElement("fetch-fill-slot")
export class FetchFillSlot extends LitElement { export class FetchFillSlot extends LitElement {
@property() @property()
url: string = ""; url: string = "";
@ -18,14 +17,30 @@ export class FetchFillSlot extends LitElement {
comparison(slotName: string) { comparison(slotName: string) {
let comparisonOperatorsHash = <ComparisonHash>{ let comparisonOperatorsHash = <ComparisonHash>{
'<': function (a: any, b: any) { return a < b; }, "<": function (a: any, b: any) {
'>': function (a: any, b: any) { return a > b; }, return a < b;
'>=': function (a: any, b: any) { return a >= b; }, },
'<=': function (a: any, b: any) { return a <= b; }, ">": function (a: any, b: any) {
'==': function (a: any, b: any) { return a == b; }, return a > b;
'!=': function (a: any, b: any) { return a != b; }, },
'===': function (a: any, b: any) { return a === b; }, ">=": function (a: any, b: any) {
'!==': function (a: any, b: any) { return a !== b; }, 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(" "); const tokens = slotName.split(" ");
if (tokens.length < 3) { if (tokens.length < 3) {
@ -45,13 +60,16 @@ export class FetchFillSlot extends LitElement {
} }
const comp = tokens[1]; const comp = tokens[1];
if (!(comp in comparisonOperatorsHash)) { if (!(comp in comparisonOperatorsHash)) {
throw new Error("Invalid comparison") throw new Error("Invalid comparison");
} }
return comparisonOperatorsHash[comp](a, b); return comparisonOperatorsHash[comp](a, b);
} }
firstUpdated() { firstUpdated() {
fetch(this.url).then(r => r.json()).then(r => r[this.key]).then(r => this.value = r); fetch(this.url)
.then((r) => r.json())
.then((r) => r[this.key])
.then((r) => (this.value = r));
} }
render() { render() {
@ -59,13 +77,13 @@ export class FetchFillSlot extends LitElement {
return html`<slot></slot>`; return html`<slot></slot>`;
} }
let selectedSlot = ""; let selectedSlot = "";
this.querySelectorAll("[slot]").forEach(slot => { this.querySelectorAll("[slot]").forEach((slot) => {
const comp = slot.getAttribute("slot")!; const comp = slot.getAttribute("slot")!;
if (this.comparison(comp)) { if (this.comparison(comp)) {
selectedSlot = comp; selectedSlot = comp;
} }
}); });
this.querySelectorAll("[data-value]").forEach(dv => { this.querySelectorAll("[data-value]").forEach((dv) => {
dv.textContent = this.value; dv.textContent = this.value;
}); });
return html`<slot name=${selectedSlot}></slot>`; return html`<slot name=${selectedSlot}></slot>`;

View File

@ -1,10 +1,10 @@
import { LitElement, html, customElement, property } from 'lit-element'; import { LitElement, html, customElement, property } from "lit-element";
const LEVEL_ICON_MAP: { [key: string]: string } = { const LEVEL_ICON_MAP: { [key: string]: string } = {
"error": "fas fa-exclamation-circle", error: "fas fa-exclamation-circle",
"warning": "fas fa-exclamation-triangle", warning: "fas fa-exclamation-triangle",
"success": "fas fa-check-circle", success: "fas fa-check-circle",
"info": "fas fa-info", info: "fas fa-info",
}; };
let ID = function (prefix: string) { let ID = function (prefix: string) {
@ -23,7 +23,6 @@ interface Message {
@customElement("pb-messages") @customElement("pb-messages")
export class Messages extends LitElement { export class Messages extends LitElement {
@property() @property()
url: string = ""; url: string = "";
@ -39,20 +38,27 @@ export class Messages extends LitElement {
} }
fetchMessages() { fetchMessages() {
return fetch(this.url).then(r => r.json()).then(r => this.messages = r).then((r) => { return fetch(this.url)
const container = <HTMLElement>this.querySelector(".pf-c-alert-group")!; .then((r) => r.json())
r.forEach((message: Message) => { .then((r) => (this.messages = r))
const messageElement = this.renderMessage(message); .then((r) => {
container.appendChild(messageElement); const container = <HTMLElement>(
this.querySelector(".pf-c-alert-group")!
);
r.forEach((message: Message) => {
const messageElement = this.renderMessage(message);
container.appendChild(messageElement);
});
}); });
});
} }
renderMessage(message: Message): ChildNode { renderMessage(message: Message): ChildNode {
const id = ID("pb-message"); const id = ID("pb-message");
const el = document.createElement("template"); const el = document.createElement("template");
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.level_tag} ${message.level_tag === 'error' ? 'pf-m-danger': ''}"> <div class="pf-c-alert pf-m-${message.level_tag} ${
message.level_tag === "error" ? "pf-m-danger" : ""
}">
<div class="pf-c-alert__icon"> <div class="pf-c-alert__icon">
<i class="${LEVEL_ICON_MAP[message.level_tag]}"></i> <i class="${LEVEL_ICON_MAP[message.level_tag]}"></i>
</div> </div>

View File

@ -10,7 +10,6 @@ import { convertToSlug } from "../utils";
@customElement("pb-modal-button") @customElement("pb-modal-button")
export class ModalButton extends LitElement { export class ModalButton extends LitElement {
@property() @property()
href: string = ""; href: string = "";
@ -18,12 +17,12 @@ export class ModalButton extends LitElement {
open: boolean = false; open: boolean = false;
static get styles() { static get styles() {
return [ModalBoxStyle, BullseyeStyle, BackdropStyle] return [ModalBoxStyle, BullseyeStyle, BackdropStyle];
} }
constructor() { constructor() {
super(); super();
window.addEventListener("keyup", e => { window.addEventListener("keyup", (e) => {
if (e.code === "Escape") { if (e.code === "Escape") {
this.open = false; this.open = false;
} }
@ -33,84 +32,116 @@ export class ModalButton extends LitElement {
setContent(content: string) { setContent(content: string) {
this.querySelector("[slot=modal]")!.innerHTML = content; this.querySelector("[slot=modal]")!.innerHTML = content;
// Ensure links close the modal // Ensure links close the modal
this.querySelectorAll<HTMLAnchorElement>("[slot=modal] a").forEach(a => { this.querySelectorAll<HTMLAnchorElement>("[slot=modal] a").forEach(
// Make click on a close the modal (a) => {
a.addEventListener("click", e => { // Make click on a close the modal
e.preventDefault(); a.addEventListener("click", (e) => {
this.open = false; e.preventDefault();
}); this.open = false;
});
// Make name field update slug field
this.querySelectorAll<HTMLInputElement>("input[name=name]").forEach((input) => {
input.addEventListener("input", (e) => {
const form = input.closest("form");
if (form === null) {
return;
}
const slugField = form.querySelector<HTMLInputElement>("input[name=slug]");
if (!slugField) {
return;
}
slugField.value = convertToSlug(input.value);
});
});
// Ensure forms sends in AJAX
this.querySelectorAll<HTMLFormElement>("[slot=modal] form").forEach(form => {
form.addEventListener('submit', (e) => {
e.preventDefault();
let formData = new FormData(form);
fetch((form.action === window.location.toString()) ? this.href : form.action, {
method: form.method,
body: formData,
}).then((response) => {
return response.text();
}).then(data => {
if (data.indexOf("csrfmiddlewaretoken") !== -1) {
this.setContent(data);
} else {
this.open = false;
this.dispatchEvent(new CustomEvent('hashchange', { bubbles: true }));
updateMessages();
}
}).catch((e) => {
console.error(e);
}); });
}); }
}); );
// Make name field update slug field
this.querySelectorAll<HTMLInputElement>("input[name=name]").forEach(
(input) => {
input.addEventListener("input", (e) => {
const form = input.closest("form");
if (form === null) {
return;
}
const slugField = form.querySelector<HTMLInputElement>(
"input[name=slug]"
);
if (!slugField) {
return;
}
slugField.value = convertToSlug(input.value);
});
}
);
// Ensure forms sends in AJAX
this.querySelectorAll<HTMLFormElement>("[slot=modal] form").forEach(
(form) => {
form.addEventListener("submit", (e) => {
e.preventDefault();
let formData = new FormData(form);
fetch(
form.action === window.location.toString()
? this.href
: form.action,
{
method: form.method,
body: formData,
}
)
.then((response) => {
return response.text();
})
.then((data) => {
if (data.indexOf("csrfmiddlewaretoken") !== -1) {
this.setContent(data);
} else {
this.open = false;
this.dispatchEvent(
new CustomEvent("hashchange", {
bubbles: true,
})
);
updateMessages();
}
})
.catch((e) => {
console.error(e);
});
});
}
);
} }
onClick(e: MouseEvent) { onClick(e: MouseEvent) {
const request = new Request( const request = new Request(this.href);
this.href,
);
fetch(request, { fetch(request, {
mode: 'same-origin', mode: "same-origin",
}).then(r => r.text()).then((t) => { })
this.setContent(t); .then((r) => r.text())
this.open = true; .then((t) => {
}).catch(e => { this.setContent(t);
console.error(e); this.open = true;
}); })
.catch((e) => {
console.error(e);
});
} }
renderModal() { renderModal() {
return html`<div class="pf-c-backdrop"> return html`<div class="pf-c-backdrop">
<div class="pf-l-bullseye"> <div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-md" role="dialog" aria-modal="true" aria-labelledby="modal-md-title" aria-describedby="modal-md-description"> <div
<button @click=${() => this.open = false} class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog"> class="pf-c-modal-box pf-m-md"
role="dialog"
aria-modal="true"
aria-labelledby="modal-md-title"
aria-describedby="modal-md-description"
>
<button
@click=${() => (this.open = false)}
class="pf-c-button pf-m-plain"
type="button"
aria-label="Close dialog"
>
<i class="fas fa-times" aria-hidden="true"></i> <i class="fas fa-times" aria-hidden="true"></i>
</button> </button>
<slot name="modal"> <slot name="modal"> </slot>
</slot>
</div> </div>
</div> </div>
</div>`; </div>`;
} }
render() { render() {
return html` return html` <slot
<slot name="trigger" @click=${(e: any) => this.onClick(e)}></slot> name="trigger"
@click=${(e: any) => this.onClick(e)}
></slot>
${this.open ? this.renderModal() : ""}`; ${this.open ? this.renderModal() : ""}`;
} }
} }

View File

@ -1,27 +1,34 @@
import { LitElement, html, customElement } from 'lit-element'; import { LitElement, html, customElement } from "lit-element";
@customElement("pb-tabs") @customElement("pb-tabs")
export class Tabs extends LitElement { export class Tabs extends LitElement {
_currentPage? = ""; _currentPage? = "";
_firstPage? = ""; _firstPage? = "";
get currentPage() { get currentPage() {
return this._currentPage return this._currentPage;
} }
set currentPage(value) { set currentPage(value) {
try { try {
// Show active tab page // Show active tab page
this.querySelector(`.pf-c-tab-content[tab-name='${value}']`)?.removeAttribute("hidden"); this.querySelector(
`.pf-c-tab-content[tab-name='${value}']`
)?.removeAttribute("hidden");
// Update active status on buttons // Update active status on buttons
this.querySelector(`.pf-c-tabs__item[tab-name='${value}']`)?.classList.add("pf-m-current"); this.querySelector(
`.pf-c-tabs__item[tab-name='${value}']`
)?.classList.add("pf-m-current");
// Hide other tab pages // Hide other tab pages
this.querySelectorAll(`.pf-c-tab-content:not([tab-name='${value}'])`).forEach((el) => { this.querySelectorAll(
`.pf-c-tab-content:not([tab-name='${value}'])`
).forEach((el) => {
el.setAttribute("hidden", ""); el.setAttribute("hidden", "");
}); });
// Update active status on other buttons // Update active status on other buttons
this.querySelectorAll(`.pf-c-tabs__item:not([tab-name='${value}'])`).forEach((el) => { this.querySelectorAll(
`.pf-c-tabs__item:not([tab-name='${value}'])`
).forEach((el) => {
el.classList.remove("pf-m-current"); el.classList.remove("pf-m-current");
}); });
// Update window hash // Update window hash
@ -37,7 +44,9 @@ export class Tabs extends LitElement {
} }
firstUpdated() { firstUpdated() {
this._firstPage = this.querySelector(".pf-c-tab-content")?.getAttribute("tab-name")!; this._firstPage = this.querySelector(".pf-c-tab-content")?.getAttribute(
"tab-name"
)!;
if (window.location.hash) { if (window.location.hash) {
this.currentPage = window.location.hash; this.currentPage = window.location.hash;
} else { } else {
@ -47,8 +56,7 @@ export class Tabs extends LitElement {
button.addEventListener("click", (e) => { button.addEventListener("click", (e) => {
let tabPage = button.parentElement?.getAttribute("tab-name")!; let tabPage = button.parentElement?.getAttribute("tab-name")!;
this.currentPage = tabPage; this.currentPage = tabPage;
}) });
}); });
} }
} }

View File

@ -11,23 +11,25 @@ document.querySelectorAll("input[type=search]").forEach((si) => {
document.querySelectorAll("[data-pb-fetch-fill]").forEach((el) => { document.querySelectorAll("[data-pb-fetch-fill]").forEach((el) => {
const url = el.dataset.pbFetchFill; const url = el.dataset.pbFetchFill;
const key = el.dataset.pbFetchKey; const key = el.dataset.pbFetchKey;
fetch(url).then(r => r.json()).then(r => { fetch(url)
el.textContent = r[key]; .then((r) => r.json())
el.value = r[key]; .then((r) => {
}); el.textContent = r[key];
el.value = r[key];
});
}); });
// Modal // Modal
document.querySelectorAll("[data-target='modal']").forEach((m) => { document.querySelectorAll("[data-target='modal']").forEach((m) => {
m.addEventListener("click", (e) => { m.addEventListener("click", (e) => {
const parentContainer = e.target.closest('[data-target="modal"]'); const parentContainer = e.target.closest('[data-target="modal"]');
const modalId = parentContainer.attributes['data-modal'].value; const modalId = parentContainer.attributes["data-modal"].value;
document.querySelector(`#${modalId}`).removeAttribute("hidden"); document.querySelector(`#${modalId}`).removeAttribute("hidden");
}); });
}); });
document.querySelectorAll(".pf-c-modal-box [data-modal-close]").forEach((b) => { document.querySelectorAll(".pf-c-modal-box [data-modal-close]").forEach((b) => {
b.addEventListener("click", (e) => { b.addEventListener("click", (e) => {
const parentContainer = e.target.closest('.pf-c-backdrop'); const parentContainer = e.target.closest(".pf-c-backdrop");
parentContainer.setAttribute("hidden", true); parentContainer.setAttribute("hidden", true);
}); });
}); });
@ -35,31 +37,37 @@ document.querySelectorAll(".pf-c-modal-box [data-modal-close]").forEach((b) => {
// Make Checkbox label click trigger checkbox toggle // Make Checkbox label click trigger checkbox toggle
document.querySelectorAll(".pf-c-check__label").forEach((checkLabel) => { document.querySelectorAll(".pf-c-check__label").forEach((checkLabel) => {
checkLabel.addEventListener("click", (e) => { checkLabel.addEventListener("click", (e) => {
const checkbox = e.target.parentElement.querySelector("input[type=checkbox]"); const checkbox = e.target.parentElement.querySelector(
"input[type=checkbox]"
);
checkbox.checked = !checkbox.checked; checkbox.checked = !checkbox.checked;
}); });
}); });
// Hamburger Menu // Hamburger Menu
document.querySelectorAll(".pf-c-page__header-brand-toggle>button").forEach((toggle) => { document
toggle.addEventListener("click", (e) => { .querySelectorAll(".pf-c-page__header-brand-toggle>button")
const sidebar = document.querySelector(".pf-c-page__sidebar"); .forEach((toggle) => {
if (sidebar.classList.contains("pf-m-expanded")) { toggle.addEventListener("click", (e) => {
// Sidebar already expanded const sidebar = document.querySelector(".pf-c-page__sidebar");
sidebar.classList.remove("pf-m-expanded"); if (sidebar.classList.contains("pf-m-expanded")) {
sidebar.style.zIndex = 0; // Sidebar already expanded
} else { sidebar.classList.remove("pf-m-expanded");
// Sidebar not expanded yet sidebar.style.zIndex = 0;
sidebar.classList.add("pf-m-expanded"); } else {
sidebar.style.zIndex = 200; // Sidebar not expanded yet
} sidebar.classList.add("pf-m-expanded");
sidebar.style.zIndex = 200;
}
});
}); });
});
// Collapsable Menus in Sidebar // Collapsable Menus in Sidebar
document.querySelectorAll(".pf-m-expandable>.pf-c-nav__link").forEach((menu) => { document
menu.addEventListener("click", (e) => { .querySelectorAll(".pf-m-expandable>.pf-c-nav__link")
e.preventDefault(); .forEach((menu) => {
menu.parentElement.classList.toggle("pf-m-expanded"); menu.addEventListener("click", (e) => {
e.preventDefault();
menu.parentElement.classList.toggle("pf-m-expanded");
});
}); });
});

View File

@ -1,13 +1,13 @@
import "./legacy.js"; import "./legacy.js";
import './elements/ActionButton'; import "./elements/ActionButton";
import './elements/AdminSidebar'; import "./elements/AdminSidebar";
import './elements/CodeMirror'; import "./elements/CodeMirror";
import './elements/Dropdown'; import "./elements/Dropdown";
import './elements/FetchFillSlot'; import "./elements/FetchFillSlot";
import './elements/Messages'; import "./elements/Messages";
import './elements/ModalButton'; import "./elements/ModalButton";
import './elements/Tabs'; import "./elements/Tabs";
import './pages/AdminSiteShell'; import "./pages/AdminSiteShell";
import './pages/FlowShellCard'; import "./pages/FlowShellCard";
import "./elements/AdminLoginsChart"; import "./elements/AdminLoginsChart";

View File

@ -1,4 +1,11 @@
import { css, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import {
css,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
// @ts-ignore // @ts-ignore
import BullseyeStyle from "@patternfly/patternfly/layouts/Bullseye/bullseye.css"; import BullseyeStyle from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
// @ts-ignore // @ts-ignore
@ -6,7 +13,6 @@ import SpinnerStyle from "@patternfly/patternfly/components/Spinner/spinner.css"
@customElement("pb-admin-shell") @customElement("pb-admin-shell")
export class AdminSiteShell extends LitElement { export class AdminSiteShell extends LitElement {
@property() @property()
set defaultUrl(value: string) { set defaultUrl(value: string) {
if (window.location.hash === "" && value !== undefined) { if (window.location.hash === "" && value !== undefined) {
@ -18,18 +24,22 @@ export class AdminSiteShell extends LitElement {
loading: boolean = false; loading: boolean = false;
static get styles() { static get styles() {
return [css` return [
:host { css`
position: relative; :host {
} position: relative;
:host .pf-l-bullseye { }
position: absolute; :host .pf-l-bullseye {
height: 100%; position: absolute;
width: 100%; height: 100%;
top: 0; width: 100%;
left: 0; top: 0;
} left: 0;
`, BullseyeStyle, SpinnerStyle]; }
`,
BullseyeStyle,
SpinnerStyle,
];
} }
constructor() { constructor() {
@ -41,49 +51,56 @@ export class AdminSiteShell extends LitElement {
loadContent() { loadContent() {
let url = window.location.hash.slice(1, Infinity); let url = window.location.hash.slice(1, Infinity);
if (url === "") { if (url === "") {
return return;
} }
this.loading = true; this.loading = true;
fetch(url).then(r => r.text()).then((t) => { fetch(url)
this.querySelector("[slot=body]")!.innerHTML = t; .then((r) => r.text())
}).then(() => { .then((t) => {
// Ensure anchors only change the hash this.querySelector("[slot=body]")!.innerHTML = t;
this.querySelectorAll<HTMLAnchorElement>("a:not(.pb-root-link)").forEach(a => { })
if (a.href === "") { .then(() => {
return; // Ensure anchors only change the hash
} this.querySelectorAll<HTMLAnchorElement>(
try { "a:not(.pb-root-link)"
const url = new URL(a.href); ).forEach((a) => {
const qs = url.search || ""; if (a.href === "") {
a.href = `#${url.pathname}${qs}`; return;
} catch (e) { }
a.href = `#${a.href}`; try {
} const url = new URL(a.href);
}); const qs = url.search || "";
// Create refresh buttons a.href = `#${url.pathname}${qs}`;
this.querySelectorAll("[role=pb-refresh]").forEach(rt => { } catch (e) {
rt.addEventListener("click", e => { a.href = `#${a.href}`;
this.loadContent(); }
}); });
// Create refresh buttons
this.querySelectorAll("[role=pb-refresh]").forEach((rt) => {
rt.addEventListener("click", (e) => {
this.loadContent();
});
});
this.loading = false;
}); });
this.loading = false;
});
} }
render() { render() {
return html` return html` ${this.loading
${this.loading ? html` ? html` <div class="pf-l-bullseye">
<div class="pf-l-bullseye"> <div class="pf-l-bullseye__item">
<div class="pf-l-bullseye__item"> <span
<span class="pf-c-spinner pf-m-xl" role="progressbar" aria-valuetext="Loading..."> class="pf-c-spinner pf-m-xl"
<span class="pf-c-spinner__clipper"></span> role="progressbar"
<span class="pf-c-spinner__lead-ball"></span> aria-valuetext="Loading..."
<span class="pf-c-spinner__tail-ball"></span> >
</span> <span class="pf-c-spinner__clipper"></span>
</div> <span class="pf-c-spinner__lead-ball"></span>
</div>`: ""} <span class="pf-c-spinner__tail-ball"></span>
<slot name="body"> </span>
</slot>`; </div>
</div>`
: ""}
<slot name="body"> </slot>`;
} }
} }

View File

@ -1,9 +1,9 @@
import { LitElement, html, customElement, property } from 'lit-element'; import { LitElement, html, customElement, property } from "lit-element";
import { updateMessages } from "../elements/Messages"; import { updateMessages } from "../elements/Messages";
enum ResponseType { enum ResponseType {
redirect = "redirect", redirect = "redirect",
template = "template" template = "template",
} }
interface Response { interface Response {
@ -14,7 +14,6 @@ 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: string = "";
@ -26,19 +25,23 @@ export class FlowShellCard extends LitElement {
} }
firstUpdated() { firstUpdated() {
fetch(this.flowBodyUrl).then(r => { fetch(this.flowBodyUrl)
if (!r.ok) { .then((r) => {
throw Error(r.statusText); if (!r.ok) {
} throw Error(r.statusText);
return r; }
}).then((r) => { return r;
return r.json(); })
}).then((r) => { .then((r) => {
this.updateCard(r); return r.json();
}).catch((e) => { })
// Catch JSON or Update errors .then((r) => {
this.errorMessage(e); this.updateCard(r);
}); })
.catch((e) => {
// Catch JSON or Update errors
this.errorMessage(e);
});
} }
async updateCard(data: Response) { async updateCard(data: Response) {
@ -54,13 +57,15 @@ export class FlowShellCard extends LitElement {
this.loadFormCode(); this.loadFormCode();
this.setFormSubmitHandlers(); this.setFormSubmitHandlers();
default: default:
console.log(`passbook/flows: unexpected data type ${data.type}`); console.log(
`passbook/flows: unexpected data type ${data.type}`
);
break; break;
} }
}; }
loadFormCode() { loadFormCode() {
this.querySelectorAll("script").forEach(script => { this.querySelectorAll("script").forEach((script) => {
let newScript = document.createElement("script"); let newScript = document.createElement("script");
newScript.src = script.src; newScript.src = script.src;
document.head.appendChild(newScript); document.head.appendChild(newScript);
@ -78,7 +83,9 @@ export class FlowShellCard extends LitElement {
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) {
console.log("passbook/flows: Found Form action URL in form elements, not changing form action."); console.log(
"passbook/flows: Found Form action URL in form elements, not changing form action."
);
return false; return false;
} }
} }
@ -94,26 +101,31 @@ export class FlowShellCard extends LitElement {
} }
setFormSubmitHandlers() { setFormSubmitHandlers() {
this.querySelectorAll("form").forEach(form => { this.querySelectorAll("form").forEach((form) => {
console.log(`passbook/flows: Checking for autosubmit attribute ${form}`); console.log(
`passbook/flows: Checking for autosubmit attribute ${form}`
);
this.checkAutosubmit(form); this.checkAutosubmit(form);
console.log(`passbook/flows: Setting action for form ${form}`); console.log(`passbook/flows: Setting action for form ${form}`);
this.updateFormAction(form); this.updateFormAction(form);
console.log(`passbook/flows: Adding handler for form ${form}`); console.log(`passbook/flows: Adding handler for form ${form}`);
form.addEventListener('submit', (e) => { form.addEventListener("submit", (e) => {
e.preventDefault(); e.preventDefault();
let formData = new FormData(form); let formData = new FormData(form);
this.flowBody = undefined; this.flowBody = undefined;
fetch(this.flowBodyUrl, { fetch(this.flowBodyUrl, {
method: 'post', method: "post",
body: formData, body: formData,
}).then((response) => { })
return response.json() .then((response) => {
}).then(data => { return response.json();
this.updateCard(data); })
}).catch((e) => { .then((data) => {
this.errorMessage(e); this.updateCard(data);
}); })
.catch((e) => {
this.errorMessage(e);
});
}); });
form.classList.add("pb-flow-wrapped"); form.classList.add("pb-flow-wrapped");
}); });
@ -141,19 +153,22 @@ export class FlowShellCard extends LitElement {
} }
loading() { loading() {
return html` return html` <div class="pf-c-login__main-body pb-loading">
<div class="pf-c-login__main-body pb-loading"> <span
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading..."> class="pf-c-spinner"
<span class="pf-c-spinner__clipper"></span> role="progressbar"
<span class="pf-c-spinner__lead-ball"></span> aria-valuetext="Loading..."
<span class="pf-c-spinner__tail-ball"></span> >
</span> <span class="pf-c-spinner__clipper"></span>
</div>`; <span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>`;
} }
render() { render() {
if (this.flowBody) { if (this.flowBody) {
return html(<TemplateStringsArray><unknown>[this.flowBody]); return html(<TemplateStringsArray>(<unknown>[this.flowBody]));
} }
return this.loading(); return this.loading();
} }

View File

@ -1,12 +1,14 @@
export function getCookie(name: string) { export function getCookie(name: string) {
let cookieValue = null; let cookieValue = null;
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++) {
const cookie = cookies[i].trim(); const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want? // Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) { if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); cookieValue = decodeURIComponent(
cookie.substring(name.length + 1)
);
break; break;
} }
} }
@ -17,6 +19,6 @@ export function getCookie(name: string) {
export function convertToSlug(text: string): string { export function convertToSlug(text: string): string {
return text return text
.toLowerCase() .toLowerCase()
.replace(/ /g, '-') .replace(/ /g, "-")
.replace(/[^\w-]+/g, ''); .replace(/[^\w-]+/g, "");
} }

View File

@ -9,10 +9,6 @@
"target": "es2017", "target": "es2017",
"module": "es2015", "module": "es2015",
"moduleResolution": "node", "moduleResolution": "node",
"lib": [ "lib": ["es2017", "dom", "dom.iterable"]
"es2017",
"dom",
"dom.iterable"
],
} }
} }