From e4d749cce0ca13383b3fa15631058813f77270ea Mon Sep 17 00:00:00 2001 From: Kitteh Date: Sat, 22 May 2021 12:22:00 +0100 Subject: [PATCH] Add tsx syntax to KeyValueSecret. --- src/elements/ReactCopyableInputBox.tsx | 46 +++++ src/elements/ReactMarginInline.tsx | 13 ++ src/pages/Secrets/KeyValue/KeyValueSecret.ts | 186 ------------------ src/pages/Secrets/KeyValue/KeyValueSecret.tsx | 172 ++++++++++++++++ 4 files changed, 231 insertions(+), 186 deletions(-) create mode 100644 src/elements/ReactCopyableInputBox.tsx create mode 100644 src/elements/ReactMarginInline.tsx delete mode 100644 src/pages/Secrets/KeyValue/KeyValueSecret.ts create mode 100644 src/pages/Secrets/KeyValue/KeyValueSecret.tsx diff --git a/src/elements/ReactCopyableInputBox.tsx b/src/elements/ReactCopyableInputBox.tsx new file mode 100644 index 0000000..346f115 --- /dev/null +++ b/src/elements/ReactCopyableInputBox.tsx @@ -0,0 +1,46 @@ +import { Component, JSX, createRef } from "preact"; +import { MarginInline } from "./ReactMarginInline"; +import { addClipboardNotifications } from "../pageUtils"; +import ClipboardJS from "clipboard"; +import i18next from "i18next"; + +export type CopyableInputBoxProps = { + text: string; + copyable?: boolean; +}; + +export class CopyableInputBox extends Component { + copyIconRef = createRef(); + inputBoxRef = createRef(); + + componentDidMount(): void { + const clipboard = new ClipboardJS(this.copyIconRef.current); + addClipboardNotifications(clipboard, 600); + } + + render(): JSX.Element { + return ( +
+ + {this.props.copyable && ( + + )} + + +
+ ); + } +} diff --git a/src/elements/ReactMarginInline.tsx b/src/elements/ReactMarginInline.tsx new file mode 100644 index 0000000..669bed8 --- /dev/null +++ b/src/elements/ReactMarginInline.tsx @@ -0,0 +1,13 @@ +import { JSX } from "preact"; + +export type MarginInlineProps = { + children?: JSX.Element | JSX.Element[]; +}; + +export function MarginInline(props: MarginInlineProps): JSX.Element { + return ( +
+
{props.children}
+
+ ); +} diff --git a/src/pages/Secrets/KeyValue/KeyValueSecret.ts b/src/pages/Secrets/KeyValue/KeyValueSecret.ts deleted file mode 100644 index 50f986f..0000000 --- a/src/pages/Secrets/KeyValue/KeyValueSecret.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { CopyableInputBox } from "../../../elements/CopyableInputBox"; -import { Page } from "../../../types/Page"; -import { SecretTitleElement } from "../SecretTitleElement"; -import { getCapabilities } from "../../../api/sys/getCapabilities"; -import { getSecret } from "../../../api/kv/getSecret"; -import { makeElement } from "z-makeelement"; -import { sortedObjectMap } from "../../../utils"; -import { undeleteSecret } from "../../../api/kv/undeleteSecret"; -import Prism from "prismjs"; -import i18next from "i18next"; - -export class KeyValueSecretPage extends Page { - constructor() { - super(); - } - async goBack(): Promise { - if (this.state.secretVersion != null) { - this.state.secretVersion = null; - await this.router.changePage("KEY_VALUE_VERSIONS"); - } else { - this.state.secretItem = ""; - await this.router.changePage("KEY_VALUE_VIEW"); - } - } - async render(): Promise { - await this.router.setPageContent( - makeElement({ - tag: "div", - children: [ - makeElement({ - tag: "p", - id: "buttonsBlock", - }), - makeElement({ - tag: "p", - text: i18next.t("kv_secret_loading"), - id: "loadingText", - }), - makeElement({ - tag: "div", - id: "kvList", - }), - ], - }), - ); - - const buttonsBlock = document.querySelector("#buttonsBlock"); - const kvList = document.querySelector("#kvList"); - let isSecretNestedJson = false; - const caps = await getCapabilities( - this.state.baseMount, - this.state.secretPath, - this.state.secretItem, - ); - if (caps.includes("delete")) { - let deleteButtonText = i18next.t("kv_secret_delete_btn"); - if (this.state.secretMountType == "kv-v2" && this.state.secretVersion == null) { - deleteButtonText = i18next.t("kv_secret_delete_all_btn"); - } else if (this.state.secretMountType == "kv-v2" && this.state.secretVersion != null) { - deleteButtonText = i18next.t("kv_secret_delete_version_btn", { - version: this.state.secretVersion, - }); - } - buttonsBlock.appendChild( - makeElement({ - tag: "button", - id: "deleteButton", - class: ["uk-button", "uk-button-danger"], - onclick: async () => { - await this.router.changePage("KEY_VALUE_DELETE"); - }, - text: deleteButtonText, - }), - ); - } - if (caps.includes("update")) { - if (this.state.secretVersion == null) { - buttonsBlock.appendChild( - makeElement({ - tag: "button", - id: "editButton", - class: ["uk-button", "uk-margin", "uk-button-primary"], - onclick: async () => { - await this.router.changePage("KEY_VALUE_SECRET_EDIT"); - }, - text: i18next.t("kv_secret_edit_btn"), - }), - ); - } - } - if (this.state.secretMountType == "kv-v2") { - buttonsBlock.appendChild( - makeElement({ - tag: "button", - id: "versionsButton", - class: ["uk-button", "uk-button-secondary"], - onclick: async () => { - await this.router.changePage("KEY_VALUE_VERSIONS"); - }, - text: i18next.t("kv_secret_versions_btn"), - }), - ); - } - - const secretInfo = await getSecret( - this.state.baseMount, - this.state.secretMountType, - this.state.secretPath, - this.state.secretItem, - this.state.secretVersion, - ); - - if (secretInfo == null && this.state.secretMountType == "kv-v2") { - document.querySelector("#buttonsBlock").remove(); - document.getElementById("loadingText").remove(); - - kvList.appendChild( - makeElement({ - tag: "p", - text: i18next.t("kv_secret_deleted_text"), - }), - ); - - kvList.appendChild( - makeElement({ - tag: "button", - text: i18next.t("kv_secret_restore_btn"), - id: "restoreButton", - class: ["uk-button", "uk-button-primary"], - onclick: async () => { - await undeleteSecret( - this.state.baseMount, - this.state.secretPath, - this.state.secretItem, - this.state.secretVersion, - ); - await this.router.refresh(); - }, - }), - ); - return; - } - - const secretsMap = sortedObjectMap(secretInfo); - - for (const value of secretsMap.values()) { - if (typeof value == "object") isSecretNestedJson = true; - } - - if (isSecretNestedJson) { - const jsonText = JSON.stringify( - sortedObjectMap(secretsMap as unknown as Record), - null, - 4, - ); - kvList.appendChild( - makeElement({ - tag: "pre", - class: ["code-block", "language-json", "line-numbers"], - html: Prism.highlight(jsonText, Prism.languages.json, "json"), - }), - ); - } else { - secretsMap.forEach((value: string, key: string) => { - const kvListElement = this.makeKVListElement(key, value); - kvList.appendChild(kvListElement); - }, this); - } - document.getElementById("loadingText").remove(); - } - makeKVListElement(key: string, value: string): HTMLElement { - return makeElement({ - tag: "div", - class: ["uk-grid", "uk-grid-small", "uk-text-left"], - children: [CopyableInputBox(key), CopyableInputBox(value)], - }); - } - - async getPageTitle(): Promise { - return await SecretTitleElement(this.router); - } - - get name(): string { - return i18next.t("kv_secret_title"); - } -} diff --git a/src/pages/Secrets/KeyValue/KeyValueSecret.tsx b/src/pages/Secrets/KeyValue/KeyValueSecret.tsx new file mode 100644 index 0000000..72cfc3e --- /dev/null +++ b/src/pages/Secrets/KeyValue/KeyValueSecret.tsx @@ -0,0 +1,172 @@ +import { CopyableInputBox } from "../../../elements/ReactCopyableInputBox"; +import { Page } from "../../../types/Page"; +import { SecretTitleElement } from "../SecretTitleElement"; +import { getCapabilities } from "../../../api/sys/getCapabilities"; +import { getSecret } from "../../../api/kv/getSecret"; +import { render } from "preact"; +import { sortedObjectMap } from "../../../utils"; +import { undeleteSecret } from "../../../api/kv/undeleteSecret"; +import Prism from "prismjs"; +import i18next from "i18next"; + +export class KeyValueSecretPage extends Page { + constructor() { + super(); + } + async goBack(): Promise { + if (this.state.secretVersion != null) { + this.state.secretVersion = null; + await this.router.changePage("KEY_VALUE_VERSIONS"); + } else { + this.state.secretItem = ""; + await this.router.changePage("KEY_VALUE_VIEW"); + } + } + async render(): Promise { + const caps = await getCapabilities( + this.state.baseMount, + this.state.secretPath, + this.state.secretItem, + ); + render( +
+

+ { + // Delete Button + caps.includes("delete") && ( + + ) + } + {caps.includes("update") && this.state.secretVersion == null && ( + + )} + {this.state.secretMountType == "kv-v2" && ( + + )} +

+

{i18next.t("kv_secret_loading")}

+
+
, + this.router.pageContentElement, + ); + + const kvList = document.querySelector("#kvList"); + let isSecretNestedJson = false; + + const secretInfo = await getSecret( + this.state.baseMount, + this.state.secretMountType, + this.state.secretPath, + this.state.secretItem, + this.state.secretVersion, + ); + + // On kv-v2, secrets can be deleted temporarily with the ability to restore + if (secretInfo == null && this.state.secretMountType == "kv-v2") { + try { + document.querySelector("#buttonsBlock").remove(); + document.getElementById("loadingText").remove(); + } catch (_) { + // Do Nothing + } + render( + <> +

{i18next.t("kv_secret_deleted_text")}

+ + , + kvList, + ); + return; + } + + const secretsMap = sortedObjectMap(secretInfo); + + for (const value of secretsMap.values()) { + if (typeof value == "object") isSecretNestedJson = true; + } + + if (isSecretNestedJson) { + const jsonText = JSON.stringify( + sortedObjectMap(secretsMap as unknown as Record), + null, + 4, + ); + const highlightedJson = Prism.highlight(jsonText, Prism.languages.json, "json"); + render( +
,
+        kvList,
+      );
+    } else {
+      render(
+        <>
+          {Array.from(secretsMap).map((data: [string, string]) => (
+            
+ + +
+ ))} + , + kvList, + ); + } + document.getElementById("loadingText").remove(); + } + + async getPageTitle(): Promise { + return await SecretTitleElement(this.router); + } + + get name(): string { + return i18next.t("kv_secret_title"); + } +}