1
0
Fork 0
VaultUI/src/ui/pages/Secrets/KeyValue/KeyValueEdit.tsx
2022-01-13 18:39:02 +00:00

198 lines
5.4 KiB
TypeScript

import { CodeEditor } from "../../../elements/CodeEditor";
import { Component, createRef, JSX } from "preact";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { SecretTitleElement } from "../SecretTitleElement";
import { setErrorText } from "../../../../pageUtils";
import { sortedObjectMap } from "../../../../utils";
import { InputWithTitle } from "../../../elements/InputWithTitle";
import Hjson from "hjson";
import JSON5 from "json5";
import yaml from "js-yaml";
import i18next from "i18next";
// todo: put this in settings
const defaultIndent = 2;
function parseData(data: string, syntax: string = "json"): Record<string, unknown> {
if (syntax == "json") {
return JSON.parse(data);
} else if (syntax == "json5") {
return JSON5.parse(data);
} else if (syntax == "hjson") {
return Hjson.parse(data);
} else if (syntax == "yaml") {
return yaml.load(data) as Record<string, unknown>;
}
}
function dumpData(data: Record<string, unknown>, space: number = defaultIndent, syntax: string = "json"): string {
if (syntax == "json") {
return JSON.stringify(data, null, space);
} else if (syntax == "json5") {
return JSON5.stringify(data, null, space);
} else if (syntax == "hjson") {
return Hjson.stringify(data, { space: space });
} else if (syntax == "yaml") {
return yaml.dump(data, { indent: space });
}
}
export function validateData(str: string, syntax: string = "json"): boolean {
try {
parseData(str, syntax);
} catch (e) {
console.log(e);
return false;
}
return true;
}
export type KVEditProps = DefaultPageProps & {
baseMount: string;
secretPath: string[];
secretItem: string;
};
type KVEditState =
| {
dataLoaded: false;
syntax: string;
}
| {
dataLoaded: true;
kvData: Record<string, unknown>;
code: string;
syntax: string;
};
export class KVEditor extends Component<KVEditProps, KVEditState> {
constructor() {
super();
// TODO: add a global syntax option to settings for default
this.state = {
dataLoaded: false,
syntax: "json",
};
}
async editorSave(): Promise<void> {
if (!this.state.dataLoaded) return;
const editorContent = this.state.code;
if (!validateData(editorContent, this.state.syntax)) {
setErrorText(i18next.t("kv_sec_edit_invalid_json_err"));
return;
}
await this.props.api.createOrUpdateSecret(
this.props.baseMount,
this.props.secretPath.map((e) => e + "/"),
this.props.secretItem,
parseData(editorContent, this.state.syntax),
);
window.history.back();
}
onCodeUpdate(code: string): void {
this.setState({
code: code,
});
}
async loadData() {
let kvData = await this.props.api.getSecret(
this.props.baseMount,
this.props.secretPath.map((e) => e + "/"),
this.props.secretItem,
)
this.setState({
dataLoaded: true,
kvData: kvData,
code: this.getStringKVData(kvData)
});
}
async componentDidMount() {
if (!this.state.dataLoaded) {
await this.loadData();
}
}
getStringKVData(data: Record<string, unknown>): string {
return dumpData(Object.fromEntries(sortedObjectMap(data)), defaultIndent, this.state.syntax);
}
syntaxSelectRef = createRef<HTMLSelectElement>()
render(): JSX.Element {
if (!this.state.dataLoaded) {
return <p>{i18next.t("content_loading")}</p>;
}
let codeEditorLanguage: string;
if (this.state.syntax == "json") {
codeEditorLanguage = "json"
} else if (this.state.syntax == "json5") {
codeEditorLanguage = "json5"
}else if (this.state.syntax == "hjson") {
codeEditorLanguage = "js"
} else if (this.state.syntax == "yaml") {
codeEditorLanguage = "yaml"
}
return (
<div>
<InputWithTitle title={i18next.t("kv_sec_edit_syntax")}>
<select ref={this.syntaxSelectRef} class="uk-select uk-form-width-medium" onChange={() => {
this.setState({ syntax: this.syntaxSelectRef.current.value })
}}>
<option label="JSON" value="json" />
<option label="JSON5" value="json5" />
<option label="Hjson" value="hjson" />
<option label="Yaml" value="yaml" />
</select>
</InputWithTitle>
<p class="uk-text-danger" id="errorText" />
<CodeEditor
language={codeEditorLanguage}
tabSize={defaultIndent}
code={this.getStringKVData(this.state.kvData)}
onUpdate={(code) => this.onCodeUpdate(code)}
/>
<button class="uk-button uk-button-primary" onClick={() => this.editorSave()}>
{i18next.t("common_edit")}
</button>
</div>
);
}
}
export class KeyValueEdit extends Component<DefaultPageProps> {
render() {
const baseMount = this.props.matches["baseMount"];
const secretPath = this.props.matches["secretPath"].split("/");
const item = this.props.matches["item"];
return (
<>
<SecretTitleElement
type="kv"
baseMount={baseMount}
secretPath={secretPath}
item={this.props.matches["item"]}
suffix={i18next.t("kv_sec_edit_suffix")}
/>
<KVEditor
settings={this.props.settings}
api={this.props.api}
baseMount={baseMount}
secretPath={secretPath}
secretItem={item}
/>
</>
);
}
}