Add tsx syntax to Unseal.
This commit is contained in:
parent
ed26eba220
commit
4930b8e727
|
@ -42,7 +42,6 @@
|
|||
"webpack": "^5.37.1",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"z-makeelement": "^1.0.3",
|
||||
"z-pagerouter": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, JSX, createRef } from "preact";
|
||||
import { MarginInline } from "./ReactMarginInline";
|
||||
import { MarginInline } from "./MarginInline";
|
||||
import { addClipboardNotifications } from "../pageUtils";
|
||||
import ClipboardJS from "clipboard";
|
||||
import i18next from "i18next";
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import { ElementInfo, makeElement } from "z-makeelement";
|
||||
/* eslint-enable */
|
||||
|
||||
export function Form(
|
||||
children: Element[],
|
||||
submit: (form: HTMLFormElement) => unknown,
|
||||
options: Partial<ElementInfo> = {},
|
||||
): HTMLFormElement {
|
||||
const form = makeElement({
|
||||
tag: "form",
|
||||
children: children,
|
||||
...options,
|
||||
}) as HTMLFormElement;
|
||||
|
||||
form.addEventListener("submit", (e: Event) => {
|
||||
e.preventDefault();
|
||||
submit(form);
|
||||
});
|
||||
|
||||
return form;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { JSX } from "preact";
|
||||
import { Margin } from "./ReactMargin";
|
||||
import { Margin } from "./Margin";
|
||||
|
||||
export type InputWithTitleProps = {
|
||||
title: string;
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { makeElement } from "z-makeelement";
|
||||
|
||||
export function Margin(children: Element | Element[]): Element {
|
||||
return makeElement({
|
||||
tag: "div",
|
||||
class: "uk-margin",
|
||||
children: children,
|
||||
});
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { makeElement } from "z-makeelement";
|
||||
|
||||
export function MarginInline(children: Element | Element[]): Element {
|
||||
return makeElement({
|
||||
tag: "div",
|
||||
class: "uk-margin",
|
||||
children: makeElement({
|
||||
tag: "div",
|
||||
class: "uk-inline",
|
||||
children: children,
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
import { Margin } from "./Margin";
|
||||
import { makeElement } from "z-makeelement";
|
||||
import QrScanner from "qr-scanner";
|
||||
|
||||
/* eslint-disable import/no-unresolved */
|
||||
// @ts-ignore
|
||||
import qrScannerWorkerSource from "!!raw-loader!qr-scanner/qr-scanner-worker.min.js";
|
||||
QrScanner.WORKER_PATH = URL.createObjectURL(new Blob([qrScannerWorkerSource]));
|
||||
|
||||
export interface QRScannerType extends HTMLElement {
|
||||
deinit(): void;
|
||||
}
|
||||
|
||||
export async function QRScanner(onScan: (code: string) => void): Promise<QRScannerType> {
|
||||
const webcamVideo = makeElement({
|
||||
tag: "video",
|
||||
}) as HTMLVideoElement;
|
||||
|
||||
const QRInput = makeElement({
|
||||
tag: "div",
|
||||
children: [Margin(webcamVideo)],
|
||||
}) as QRScannerType;
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
facingMode: "environment",
|
||||
},
|
||||
audio: false,
|
||||
});
|
||||
webcamVideo.srcObject = stream;
|
||||
const lastSeenValue = "";
|
||||
const qrScanner = new QrScanner(webcamVideo, function (value) {
|
||||
if (lastSeenValue == value) return;
|
||||
onScan(value);
|
||||
});
|
||||
await qrScanner.start();
|
||||
|
||||
QRInput.deinit = () => {
|
||||
try {
|
||||
stream.getTracks().forEach(function (track) {
|
||||
track.stop();
|
||||
});
|
||||
} catch (_) {
|
||||
() => {};
|
||||
}
|
||||
};
|
||||
|
||||
return QRInput;
|
||||
}
|
58
src/elements/QRScanner.tsx
Normal file
58
src/elements/QRScanner.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* eslint-disable import/no-unresolved */
|
||||
// @ts-ignore
|
||||
import qrScannerWorkerSource from "!!raw-loader!qr-scanner/qr-scanner-worker.min.js";
|
||||
QrScanner.WORKER_PATH = URL.createObjectURL(new Blob([qrScannerWorkerSource]));
|
||||
// end ignore
|
||||
|
||||
import { Component, JSX, createRef } from "preact";
|
||||
import { Margin } from "./Margin";
|
||||
import QrScanner from "qr-scanner";
|
||||
|
||||
export type QRScannerProps = {
|
||||
onScan: (code: string) => void;
|
||||
};
|
||||
|
||||
export class QRScanner extends Component<QRScannerProps, unknown> {
|
||||
videoElement = createRef<HTMLVideoElement>();
|
||||
|
||||
stream: MediaStream;
|
||||
|
||||
componentDidMount(): void {
|
||||
void navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
video: {
|
||||
facingMode: "environment",
|
||||
},
|
||||
audio: false,
|
||||
})
|
||||
.then((stream) => {
|
||||
this.stream = stream;
|
||||
this.videoElement.current.srcObject = stream;
|
||||
const lastSeenValue = "";
|
||||
const qrScanner = new QrScanner(this.videoElement.current, (value) => {
|
||||
if (lastSeenValue == value) return;
|
||||
this.props.onScan(value);
|
||||
});
|
||||
void qrScanner.start();
|
||||
});
|
||||
}
|
||||
componentWillUnmount(): void {
|
||||
try {
|
||||
this.stream.getTracks().forEach(function (track) {
|
||||
track.stop();
|
||||
});
|
||||
} catch (_) {
|
||||
() => {};
|
||||
}
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<Margin>
|
||||
<video ref={this.videoElement}></video>
|
||||
</Margin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Page } from "../../types/Page";
|
||||
import { Tile } from "../../elements/ReactTile";
|
||||
import { Tile } from "../../elements/Tile";
|
||||
import { notImplemented, prePageChecks } from "../../pageUtils";
|
||||
import { render } from "preact";
|
||||
import i18next from "i18next";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Form } from "../../../../elements/ReactForm";
|
||||
import { Form } from "../../../../elements/Form";
|
||||
import { InputWithTitle } from "../../../../elements/InputWithTitle";
|
||||
import { MarginInline } from "../../../../elements/ReactMarginInline";
|
||||
import { MarginInline } from "../../../../elements/MarginInline";
|
||||
import { Page } from "../../../../types/Page";
|
||||
import { UserType } from "../../../../api/types/userpass/user";
|
||||
import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Form } from "../../../../elements/ReactForm";
|
||||
import { Margin } from "../../../../elements/ReactMargin";
|
||||
import { MarginInline } from "../../../../elements/ReactMarginInline";
|
||||
import { Form } from "../../../../elements/Form";
|
||||
import { Margin } from "../../../../elements/Margin";
|
||||
import { MarginInline } from "../../../../elements/MarginInline";
|
||||
import { Page } from "../../../../types/Page";
|
||||
import { UserType } from "../../../../api/types/userpass/user";
|
||||
import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Page } from "../types/Page";
|
||||
import { Tile } from "../elements/ReactTile";
|
||||
import { Tile } from "../elements/Tile";
|
||||
import { TokenInfo } from "../api/types/token";
|
||||
import { lookupSelf } from "../api/sys/lookupSelf";
|
||||
import { prePageChecks, setErrorText } from "../pageUtils";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, JSX, render } from "preact";
|
||||
import { Form } from "../elements/ReactForm";
|
||||
import { Margin } from "../elements/ReactMargin";
|
||||
import { MarginInline } from "../elements/ReactMarginInline";
|
||||
import { Form } from "../elements/Form";
|
||||
import { Margin } from "../elements/Margin";
|
||||
import { MarginInline } from "../elements/MarginInline";
|
||||
import { Page } from "../types/Page";
|
||||
import { lookupSelf } from "../api/sys/lookupSelf";
|
||||
import { setErrorText } from "../pageUtils";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, JSX, createRef, render } from "preact";
|
||||
import { CopyableInputBox } from "../elements/CopyableInputBox";
|
||||
import { Form } from "../elements/ReactForm";
|
||||
import { Margin } from "../elements/ReactMargin";
|
||||
import { Form } from "../elements/Form";
|
||||
import { Margin } from "../elements/Margin";
|
||||
import { Page } from "../types/Page";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { createOrUpdateSecret } from "../../../api/kv/createOrUpdateSecret";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { MarginInline } from "../../../elements/MarginInline";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { newMount } from "../../../api/sys/newMount";
|
||||
import { render } from "preact";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { MarginInline } from "../../../elements/MarginInline";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { newMount } from "../../../api/sys/newMount";
|
||||
import { render } from "preact";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { MarginInline } from "../../../elements/MarginInline";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { newMount } from "../../../api/sys/newMount";
|
||||
import { render } from "preact";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Page } from "../../types/Page";
|
||||
import { Tile } from "../../elements/ReactTile";
|
||||
import { Tile } from "../../elements/Tile";
|
||||
import { render } from "preact";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { MarginInline } from "../../../elements/MarginInline";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { addNewTOTP } from "../../../api/totp/addNewTOTP";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, JSX, render } from "preact";
|
||||
import { CopyableInputBox } from "../../../elements/CopyableInputBox";
|
||||
import { DoesNotExistError } from "../../../types/internalErrors";
|
||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
||||
import { MarginInline } from "../../../elements/MarginInline";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { PageRouter } from "z-pagerouter";
|
||||
import { PageState } from "../../../PageState";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { MarginInline } from "../../../elements/MarginInline";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { newTransitKey } from "../../../api/transit/newTransitKey";
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CopyableModal } from "../../../elements/CopyableModal";
|
||||
import { FileUploadInput } from "../../../elements/FileUploadInput";
|
||||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { InputWithTitle } from "../../../elements/InputWithTitle";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { fileToBase64 } from "../../../htmlUtils";
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CopyableModal } from "../../../elements/CopyableModal";
|
||||
import { FileUploadInput } from "../../../elements/FileUploadInput";
|
||||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { InputWithTitle } from "../../../elements/InputWithTitle";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { fileToBase64 } from "../../../htmlUtils";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { CopyableModal } from "../../../elements/CopyableModal";
|
||||
import { Form } from "../../../elements/ReactForm";
|
||||
import { Margin } from "../../../elements/ReactMargin";
|
||||
import { Form } from "../../../elements/Form";
|
||||
import { Margin } from "../../../elements/Margin";
|
||||
import { Page } from "../../../types/Page";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { getTransitKey } from "../../../api/transit/getTransitKey";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Page } from "../../../types/Page";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { Tile } from "../../../elements/ReactTile";
|
||||
import { Tile } from "../../../elements/Tile";
|
||||
import { getTransitKey } from "../../../api/transit/getTransitKey";
|
||||
import { render } from "preact";
|
||||
import i18next from "i18next";
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
import translations from "../translations/index.mjs";
|
||||
// ts-unignore
|
||||
|
||||
import { Form } from "../elements/ReactForm";
|
||||
import { Margin } from "../elements/ReactMargin";
|
||||
import { MarginInline } from "../elements/ReactMarginInline";
|
||||
import { Form } from "../elements/Form";
|
||||
import { Margin } from "../elements/Margin";
|
||||
import { MarginInline } from "../elements/MarginInline";
|
||||
import { Page } from "../types/Page";
|
||||
import { reloadNavBar } from "../elements/NavBar";
|
||||
import { render } from "preact";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Form } from "../elements/ReactForm";
|
||||
import { Margin } from "../elements/ReactMargin";
|
||||
import { Form } from "../elements/Form";
|
||||
import { Margin } from "../elements/Margin";
|
||||
import { Page } from "../types/Page";
|
||||
import { render } from "preact";
|
||||
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
import { Form } from "../elements/Form";
|
||||
import { MarginInline } from "../elements/MarginInline";
|
||||
import { Page } from "../types/Page";
|
||||
import { QRScanner, QRScannerType } from "../elements/QRScanner";
|
||||
import { SealStatusType, getSealStatus } from "../api/sys/getSealStatus";
|
||||
import { makeElement } from "z-makeelement";
|
||||
import { setErrorText } from "../pageUtils";
|
||||
import { submitUnsealKey } from "../api/sys/submitUnsealKey";
|
||||
import i18next from "i18next";
|
||||
|
||||
const UnsealInputModes = {
|
||||
FORM_INPUT: "FORM_INPUT",
|
||||
QR_INPUT: "QR_INPUT",
|
||||
};
|
||||
|
||||
export class UnsealPage extends Page {
|
||||
constructor() {
|
||||
super();
|
||||
this.mode = UnsealInputModes.FORM_INPUT;
|
||||
}
|
||||
|
||||
mode: string;
|
||||
refresher: number;
|
||||
qrScanner: QRScannerType;
|
||||
unsealProgress: HTMLProgressElement;
|
||||
unsealProgressText: HTMLParagraphElement;
|
||||
unsealInputContent: HTMLElement;
|
||||
unsealKeyForm: HTMLFormElement;
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
this.deinitWebcam();
|
||||
clearInterval(this.refresher);
|
||||
}
|
||||
|
||||
deinitWebcam(): void {
|
||||
try {
|
||||
this.qrScanner.deinit();
|
||||
} catch (_) {
|
||||
// Do Nothing
|
||||
}
|
||||
}
|
||||
|
||||
makeRefresher(): void {
|
||||
const id = setInterval(async () => {
|
||||
await this.doRefresh();
|
||||
return;
|
||||
}, 1000);
|
||||
this.refresher = id as unknown as number;
|
||||
}
|
||||
|
||||
async doRefresh(): Promise<void> {
|
||||
const status = await getSealStatus();
|
||||
await this.updateSealProgress(status);
|
||||
}
|
||||
|
||||
async render(): Promise<void> {
|
||||
this.unsealProgress = makeElement({
|
||||
tag: "progress",
|
||||
class: "uk-progress",
|
||||
attributes: { value: "0", max: "0" },
|
||||
}) as HTMLProgressElement;
|
||||
this.unsealProgressText = makeElement({
|
||||
tag: "p",
|
||||
text: i18next.t("unseal_keys_progress", {
|
||||
progress: "0",
|
||||
keys_needed: "0",
|
||||
}),
|
||||
}) as HTMLParagraphElement;
|
||||
this.unsealInputContent = makeElement({
|
||||
tag: "div",
|
||||
});
|
||||
await this.router.setPageContent(
|
||||
makeElement({
|
||||
tag: "div",
|
||||
children: [
|
||||
this.unsealProgress,
|
||||
makeElement({
|
||||
tag: "p",
|
||||
id: "errorText",
|
||||
class: ["uk-text-danger", "uk-margin-top"],
|
||||
}),
|
||||
this.unsealProgressText,
|
||||
this.unsealInputContent,
|
||||
],
|
||||
}),
|
||||
);
|
||||
await this.switchInputMode(this.mode);
|
||||
await this.updateSealProgress(await getSealStatus());
|
||||
this.makeRefresher();
|
||||
}
|
||||
|
||||
setButtons(method: string): void {
|
||||
const newMethod: string =
|
||||
method == UnsealInputModes.FORM_INPUT
|
||||
? UnsealInputModes.QR_INPUT
|
||||
: UnsealInputModes.FORM_INPUT;
|
||||
const buttonText: string =
|
||||
newMethod == UnsealInputModes.FORM_INPUT
|
||||
? i18next.t("unseal_input_btn")
|
||||
: i18next.t("unseal_qr_btn");
|
||||
this.unsealInputContent.appendChild(
|
||||
makeElement({
|
||||
tag: "button",
|
||||
class: ["uk-button", "uk-button-primary"],
|
||||
text: buttonText,
|
||||
onclick: async () => {
|
||||
await this.switchInputMode(newMethod);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async switchInputMode(method: string): Promise<void> {
|
||||
this.deinitWebcam();
|
||||
this.unsealInputContent.querySelectorAll("*").forEach((n) => n.remove());
|
||||
if (method == UnsealInputModes.FORM_INPUT) this.makeUnsealForm();
|
||||
if (method == UnsealInputModes.QR_INPUT) await this.makeQRInput();
|
||||
this.setButtons(method);
|
||||
}
|
||||
|
||||
makeUnsealForm(): void {
|
||||
this.unsealKeyForm = Form(
|
||||
[
|
||||
MarginInline(
|
||||
makeElement({
|
||||
tag: "input",
|
||||
class: ["uk-input", "uk-form-width-medium"],
|
||||
attributes: {
|
||||
required: "true",
|
||||
type: "password",
|
||||
placeholder: i18next.t("key_input_placeholder"),
|
||||
name: "key",
|
||||
},
|
||||
}),
|
||||
),
|
||||
MarginInline(
|
||||
makeElement({
|
||||
tag: "button",
|
||||
class: ["uk-button", "uk-button-primary"],
|
||||
text: i18next.t("submit_key_btn"),
|
||||
}),
|
||||
),
|
||||
],
|
||||
async (_) => {
|
||||
await this.handleKeySubmit();
|
||||
},
|
||||
);
|
||||
this.unsealInputContent.appendChild(this.unsealKeyForm);
|
||||
}
|
||||
|
||||
async makeQRInput(): Promise<void> {
|
||||
this.qrScanner = await QRScanner(async (code: string) => {
|
||||
await this.submitKey(code);
|
||||
});
|
||||
this.unsealInputContent.appendChild(this.qrScanner);
|
||||
}
|
||||
|
||||
async updateSealProgress(data: SealStatusType): Promise<void> {
|
||||
const progress = data.progress;
|
||||
const keysNeeded = data.t;
|
||||
const text = this.unsealProgressText;
|
||||
text.innerText = i18next.t("unseal_keys_progress", {
|
||||
progress: String(progress),
|
||||
keys_needed: String(keysNeeded),
|
||||
});
|
||||
const progressBar = this.unsealProgress;
|
||||
progressBar.value = progress;
|
||||
progressBar.max = keysNeeded;
|
||||
if (!data.sealed) {
|
||||
progressBar.value = keysNeeded;
|
||||
await this.router.changePage("HOME");
|
||||
}
|
||||
}
|
||||
|
||||
async submitKey(key: string): Promise<void> {
|
||||
try {
|
||||
await submitUnsealKey(key);
|
||||
await this.updateSealProgress(await getSealStatus());
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error;
|
||||
setErrorText(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async handleKeySubmit(): Promise<void> {
|
||||
const formData = new FormData(this.unsealKeyForm);
|
||||
|
||||
await this.submitKey(formData.get("key") as string);
|
||||
}
|
||||
get name(): string {
|
||||
return i18next.t("unseal_vault_text");
|
||||
}
|
||||
}
|
157
src/pages/Unseal.tsx
Normal file
157
src/pages/Unseal.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
import { Component, JSX } from "preact";
|
||||
import { Form } from "../elements/Form";
|
||||
import { MarginInline } from "../elements/MarginInline";
|
||||
import { Page } from "../types/Page";
|
||||
import { QRScanner } from "../elements/QRScanner";
|
||||
import { getSealStatus } from "../api/sys/getSealStatus";
|
||||
import { render } from "preact/compat";
|
||||
import { setErrorText } from "../pageUtils";
|
||||
import { submitUnsealKey } from "../api/sys/submitUnsealKey";
|
||||
import { toStr } from "../utils";
|
||||
import i18next from "i18next";
|
||||
|
||||
const UnsealInputModes = {
|
||||
FORM_INPUT: "FORM_INPUT",
|
||||
QR_INPUT: "QR_INPUT",
|
||||
};
|
||||
|
||||
export type UnsealFormInputProps = {
|
||||
onSubmit: (code: string) => void;
|
||||
};
|
||||
|
||||
export function UnsealFormInput(props: UnsealFormInputProps): JSX.Element {
|
||||
return (
|
||||
<Form
|
||||
onSubmit={(data: FormData) => {
|
||||
props.onSubmit(data.get("unsealKey") as string);
|
||||
}}
|
||||
>
|
||||
<MarginInline>
|
||||
<input
|
||||
class="uk-input uk-form-width-medium"
|
||||
name="unsealKey"
|
||||
type="password"
|
||||
placeholder={i18next.t("key_input_placeholder")}
|
||||
required
|
||||
/>
|
||||
</MarginInline>
|
||||
<MarginInline>
|
||||
<button class="uk-button uk-button-primary" type="submit">
|
||||
{i18next.t("submit_key_btn")}
|
||||
</button>
|
||||
</MarginInline>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
type UnsealPageState = {
|
||||
mode: string;
|
||||
keys_submitted: number;
|
||||
keys_needed: number;
|
||||
};
|
||||
|
||||
export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState> {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
mode: UnsealInputModes.FORM_INPUT,
|
||||
keys_submitted: 0,
|
||||
keys_needed: 0,
|
||||
};
|
||||
}
|
||||
|
||||
timer: number;
|
||||
|
||||
async submitKey(key: string): Promise<void> {
|
||||
try {
|
||||
await submitUnsealKey(key);
|
||||
this.updateStateWithSealStatus();
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error;
|
||||
setErrorText(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
updateStateWithSealStatus(): void {
|
||||
void getSealStatus().then((data) => {
|
||||
this.setState({
|
||||
keys_submitted: data.progress,
|
||||
keys_needed: data.t,
|
||||
});
|
||||
if (!data.sealed) {
|
||||
void this.props.page.router.changePage("HOME");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.updateStateWithSealStatus();
|
||||
this.timer = setInterval(() => {
|
||||
this.updateStateWithSealStatus();
|
||||
}, 500) as unknown as number;
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<progress
|
||||
class="uk-progress"
|
||||
value={this.state.keys_submitted}
|
||||
max={this.state.keys_needed}
|
||||
/>
|
||||
|
||||
<p id="errorText" class="uk-text-danger uk-margin-top" />
|
||||
|
||||
<p>
|
||||
{i18next.t("unseal_keys_progress", {
|
||||
progress: toStr(this.state.keys_submitted),
|
||||
keys_needed: toStr(this.state.keys_needed),
|
||||
})}
|
||||
</p>
|
||||
|
||||
{this.state.mode == UnsealInputModes.FORM_INPUT && (
|
||||
<UnsealFormInput onSubmit={(code) => this.submitKey(code)} />
|
||||
)}
|
||||
|
||||
{this.state.mode == UnsealInputModes.QR_INPUT && (
|
||||
<QRScanner onScan={(code) => this.submitKey(code)} />
|
||||
)}
|
||||
|
||||
<button
|
||||
class="uk-button uk-button-primary"
|
||||
onClick={async () => {
|
||||
let newMethod: string;
|
||||
if (this.state.mode == UnsealInputModes.FORM_INPUT) {
|
||||
newMethod = UnsealInputModes.QR_INPUT;
|
||||
} else {
|
||||
newMethod = UnsealInputModes.FORM_INPUT;
|
||||
}
|
||||
this.setState({ mode: newMethod });
|
||||
}}
|
||||
>
|
||||
{this.state.mode == UnsealInputModes.QR_INPUT
|
||||
? i18next.t("unseal_input_btn")
|
||||
: i18next.t("unseal_qr_btn")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsealPage extends Page {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async render(): Promise<void> {
|
||||
render(<UnsealPageElement page={this} />, this.router.pageContentElement);
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return i18next.t("unseal_vault_text");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue