Add tsx syntax to KeyValueSecret.
This commit is contained in:
parent
0309dac899
commit
e4d749cce0
46
src/elements/ReactCopyableInputBox.tsx
Normal file
46
src/elements/ReactCopyableInputBox.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
13
src/elements/ReactMarginInline.tsx
Normal file
13
src/elements/ReactMarginInline.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
172
src/pages/Secrets/KeyValue/KeyValueSecret.tsx
Normal file
172
src/pages/Secrets/KeyValue/KeyValueSecret.tsx
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue