import { Button } from "../../../elements/Button"; import { Component, JSX, createRef } from "preact"; import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DoesNotExistError } from "../../../../types/internalErrors"; import { Margin } from "../../../elements/Margin"; import { SecretTitleElement } from "../SecretTitleElement"; import { TextInput } from "../../../elements/forms/TextInput"; import { delSecretsEngineURL, kvListURL, kvNewURL, kvViewURL } from "../../pageLinks"; import { route } from "preact-router"; import { sendErrorNotification } from "../../../elements/ErrorMessage"; import i18next from "i18next"; export type KVKeysListProps = DefaultPageProps & { baseMount: string; secretPath: string[]; }; type KVKeysListState = { dataLoaded: boolean; keys: string[]; searchQuery: string; }; function SecretsList(baseMount: string, secretPath: string[], secrets: string[]): JSX.Element[] { return secrets.map((secret) => ( <li> <a onClick={async () => { if (secret.endsWith("/")) { route(kvListURL(baseMount, [...secretPath, secret.replace("/", "")])); } else { route(kvViewURL(baseMount, secretPath, secret)); } }} > {secret} </a> </li> )); } export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> { constructor() { super(); this.state = { dataLoaded: false, keys: [], searchQuery: "", }; } async componentDidMount() { try { await this.loadData(); } catch (e: unknown) { const error = e as Error; sendErrorNotification(error.message); } } async loadData(): Promise<void> { try { const keys = await this.props.api.getSecrets(this.props.baseMount, this.props.secretPath); this.setState({ dataLoaded: true, keys: keys, }); return; } catch (e: unknown) { const error = e as Error; if (error == DoesNotExistError) { // getSecrets also 404's on no keys so dont go all the way back. if (this.props.secretPath.length != 0) { window.history.back(); return; } } else { throw error; } this.setState({ dataLoaded: true, keys: null, }); } } componentDidUpdate(prevProps: KVKeysListProps): void { if ( prevProps.baseMount !== this.props.baseMount || prevProps.secretPath !== this.props.secretPath ) { this.setState({ dataLoaded: false, }); void this.loadData(); } } searchBarRef = createRef<HTMLInputElement>(); render(): JSX.Element { if (!this.state.dataLoaded) { return <p>{i18next.t("content_loading")}</p>; } if (this.state.keys == null) { return <p>{i18next.t("kv_view_none_here_text")}</p>; } return ( <> <Margin> <TextInput inputRef={this.searchBarRef} name="path" placeholder={i18next.t("kv_view_search_input_text")} onChange={async () => { this.setState({ searchQuery: this.searchBarRef.current.value, }); }} /> </Margin> <br /> <Margin> <ul class="uk-nav uk-nav-default"> {...((): JSX.Element[] => { let secrets: string[] = this.state.keys; if (this.state.searchQuery.length > 0) { secrets = secrets.filter((secret) => secret.includes(this.state.searchQuery)); } return SecretsList(this.props.baseMount, this.props.secretPath, secrets); })()} </ul> </Margin> </> ); } } type KeyValueListState = { pathCaps: string[]; mountCaps: string[]; mountType: string; }; export class KeyValueList extends Component<DefaultPageProps, KeyValueListState> { async componentDidMount() { const baseMount = this.props.matches["baseMount"]; const secretPath = this.props.matches["secretPath"].split("/").filter((e) => e.length > 0); const mountsPath = "/sys/mounts/" + baseMount; const currentPath = baseMount + secretPath.join(); try { const caps = await this.props.api.getCapabilitiesPath([mountsPath, currentPath]); const mount = await this.props.api.getMount(baseMount); this.setState({ mountCaps: caps[mountsPath], pathCaps: caps[currentPath], mountType: mount.type, }); } catch (e: unknown) { const error = e as Error; sendErrorNotification(error.message); } } render() { if (!this.state.pathCaps) return; const baseMount = this.props.matches["baseMount"]; const secretPath = this.props.matches["secretPath"].split("/"); return ( <> <SecretTitleElement type="kv" baseMount={baseMount} secretPath={secretPath} item={this.props.matches["item"]} /> <p> {this.state.pathCaps.includes("create") && ( <Button text={i18next.t("kv_view_new_btn")} color="primary" route={kvNewURL(baseMount, secretPath)} /> )} {secretPath.length == 0 && this.state.mountCaps.includes("delete") && ( <Button text={i18next.t("kv_view_delete_btn")} color="danger" route={delSecretsEngineURL(baseMount)} /> )} </p> {this.state.mountType == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>} <KVKeysList settings={this.props.settings} api={this.props.api} baseMount={baseMount} secretPath={secretPath} /> </> ); } }