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} /> </> ); } }