1
0
Fork 0

Add tsx syntax to KeyValueSecret.

This commit is contained in:
Kitteh 2021-05-22 12:22:00 +01:00
parent 0309dac899
commit e4d749cce0
4 changed files with 231 additions and 186 deletions

View file

@ -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<CopyableInputBoxProps, unknown> {
copyIconRef = createRef();
inputBoxRef = createRef();
componentDidMount(): void {
const clipboard = new ClipboardJS(this.copyIconRef.current);
addClipboardNotifications(clipboard, 600);
}
render(): JSX.Element {
return (
<div>
<MarginInline>
{this.props.copyable && (
<a
ref={this.copyIconRef}
class="uk-form-icon"
uk-icon="icon: copy"
role="img"
data-clipboard-text={this.props.text}
aria-label={i18next.t("copy_input_box_copy_icon_text")}
/>
)}
<input
ref={this.inputBoxRef}
class="uk-input uk-input-copyable"
type="text"
value={this.props.text}
readonly
/>
</MarginInline>
</div>
);
}
}

View file

@ -0,0 +1,13 @@
import { JSX } from "preact";
export type MarginInlineProps = {
children?: JSX.Element | JSX.Element[];
};
export function MarginInline(props: MarginInlineProps): JSX.Element {
return (
<div class="uk-margin">
<div class="uk-inline">{props.children}</div>
</div>
);
}

View file

@ -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<void> {
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<void> {
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<string, unknown>),
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<Element | string> {
return await SecretTitleElement(this.router);
}
get name(): string {
return i18next.t("kv_secret_title");
}
}

View file

@ -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<void> {
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<void> {
const caps = await getCapabilities(
this.state.baseMount,
this.state.secretPath,
this.state.secretItem,
);
render(
<div>
<p id="buttonsBlock">
{
// Delete Button
caps.includes("delete") && (
<button
class="uk-button uk-button-danger"
onClick={async () => {
await this.router.changePage("KEY_VALUE_DELETE");
}}
>
{((): string => {
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,
});
}
return deleteButtonText;
})()}
</button>
)
}
{caps.includes("update") && this.state.secretVersion == null && (
<button
class="uk-button uk-button-primary"
onClick={async () => {
await this.router.changePage("KEY_VALUE_SECRET_EDIT");
}}
>
{i18next.t("kv_secret_edit_btn")}
</button>
)}
{this.state.secretMountType == "kv-v2" && (
<button
class="uk-button uk-button-secondary"
onClick={async () => {
await this.router.changePage("KEY_VALUE_VERSIONS");
}}
>
{i18next.t("kv_secret_versions_btn")}
</button>
)}
</p>
<p id="loadingText">{i18next.t("kv_secret_loading")}</p>
<div id="kvList"></div>
</div>,
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(
<>
<p>{i18next.t("kv_secret_deleted_text")}</p>
<button
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();
}}
>
{i18next.t("kv_secret_restore_btn")}
</button>
</>,
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<string, unknown>),
null,
4,
);
const highlightedJson = Prism.highlight(jsonText, Prism.languages.json, "json");
render(
<pre
class="code-block language-json line-numbers"
dangerouslySetInnerHTML={{ __html: highlightedJson }}
/>,
kvList,
);
} else {
render(
<>
{Array.from(secretsMap).map((data: [string, string]) => (
<div class="uk-grid uk-grid-small uk-text-left" uk-grid>
<CopyableInputBox text={data[0]} copyable />
<CopyableInputBox text={data[1]} copyable />
</div>
))}
</>,
kvList,
);
}
document.getElementById("loadingText").remove();
}
async getPageTitle(): Promise<Element | string> {
return await SecretTitleElement(this.router);
}
get name(): string {
return i18next.t("kv_secret_title");
}
}