1
0
Fork 0
VaultUI/src/ui/pages/Secrets/KeyValue/KeyValueList.tsx
2022-08-28 16:08:24 +01:00

219 lines
5.9 KiB
TypeScript

import { Button } from "../../../elements/Button";
import { Component, JSX, createRef } from "preact";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { DoesNotExistError } from "../../../../types/internalErrors";
import { InlineButtonBox } from "../../../elements/InlineButtonBox";
import { Margin } from "../../../elements/Margin";
import { SecretTitleElement } from "../SecretTitleElement";
import { TextInput } from "../../../elements/forms/TextInput";
import { combineKVPath, splitKVPath } from "./kvPathUtils";
import { delSecretsEngineURL, kvListURL, kvNewURL, kvViewURL } from "../../pageLinks";
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) => {
let link = "";
const combined = combineKVPath(secretPath, secret);
if (secret.endsWith("/")) {
link = kvListURL(baseMount, [...combined.secretPath, combined.secretItem]);
} else {
link = kvViewURL(baseMount, combined.secretPath, combined.secretItem);
}
return (
<li>
<a href={link}>{secret}</a>
</li>
);
});
}
export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
constructor() {
super();
this.state = {
dataLoaded: false,
keys: [],
searchQuery: "",
};
}
async componentDidMount() {
this.setState({
searchQuery: "",
});
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,
searchQuery: "",
});
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")}
onInput={async () => {
this.setState({
searchQuery: this.searchBarRef.current.value,
});
}}
/>
</Margin>
<Margin>
<ul class="uk-nav">
{...((): 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 = splitKVPath(this.props.matches["secretPath"]);
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 = splitKVPath(this.props.matches["secretPath"]);
return (
<>
<SecretTitleElement
type="kv"
baseMount={baseMount}
secretPath={secretPath}
item={this.props.matches["item"]}
/>
<InlineButtonBox>
{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)}
/>
)}
</InlineButtonBox>
{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}
/>
</>
);
}
}