211 lines
5.8 KiB
TypeScript
211 lines
5.8 KiB
TypeScript
import { CapabilitiesType, getCapabilitiesPath } from "../../../../api/sys/getCapabilities";
|
|
import { Component, JSX, createRef, render } from "preact";
|
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
|
import { DoesNotExistError } from "../../../../types/internalErrors";
|
|
import { Page } from "../../../../types/Page";
|
|
import { SecretTitleElement } from "../SecretTitleElement";
|
|
import { delSecretsEngineURL, kvListURL, kvNewURL, kvViewURL } from "../../pageLinks";
|
|
import { getMount } from "../../../../api/sys/getMounts";
|
|
import { getSecrets } from "../../../../api/kv/getSecrets";
|
|
import { route } from "preact-router";
|
|
import { setErrorText } from "../../../../pageUtils";
|
|
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 () => {
|
|
console.log(baseMount, secretPath, secret);
|
|
if (secret.endsWith("/")) {
|
|
route(
|
|
kvListURL(
|
|
baseMount,
|
|
[...secretPath, secret.replace("/", "")].filter((e) => e.length > 0),
|
|
),
|
|
);
|
|
} 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 loadData(): Promise<void> {
|
|
try {
|
|
const keys = await 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 {
|
|
setErrorText(error.message);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
void this.loadData();
|
|
}
|
|
|
|
searchBarRef = createRef();
|
|
|
|
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 (
|
|
<>
|
|
<input
|
|
ref={this.searchBarRef}
|
|
class="uk-input uk-form-width-medium uk-margin-bottom"
|
|
name="path"
|
|
placeholder={i18next.t("kv_view_search_input_text")}
|
|
onInput={async () => {
|
|
this.setState({
|
|
searchQuery: (this.searchBarRef.current as unknown as HTMLInputElement).value,
|
|
});
|
|
}}
|
|
/>
|
|
<br />
|
|
|
|
<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>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
type KeyValueViewState = {
|
|
pathCaps: string[];
|
|
mountCaps: string[];
|
|
mountType: string;
|
|
};
|
|
|
|
export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState> {
|
|
async componentDidMount() {
|
|
const baseMount = this.props.matches["baseMount"];
|
|
const secretPath = this.props.matches["secretPath"].split("/");
|
|
|
|
const mountsPath = "/sys/mounts/" + baseMount;
|
|
const currentPath = baseMount + secretPath.join();
|
|
const caps = await getCapabilitiesPath([mountsPath, currentPath]);
|
|
|
|
const mount = await getMount(baseMount);
|
|
|
|
this.setState({
|
|
mountCaps: caps[mountsPath],
|
|
pathCaps: caps[currentPath],
|
|
mountType: mount.type,
|
|
});
|
|
}
|
|
|
|
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
|
|
class="uk-button uk-button-primary"
|
|
onClick={async () => {
|
|
route(kvNewURL(baseMount, secretPath.length > 0 ? secretPath : null));
|
|
}}
|
|
>
|
|
{i18next.t("kv_view_new_btn")}
|
|
</button>
|
|
)}
|
|
{secretPath.length == 0 && this.state.mountCaps.includes("delete") && (
|
|
<button
|
|
class="uk-button uk-button-danger"
|
|
onClick={async () => {
|
|
route(delSecretsEngineURL(baseMount));
|
|
}}
|
|
>
|
|
{i18next.t("kv_view_delete_btn")}
|
|
</button>
|
|
)}
|
|
</p>
|
|
{this.state.mountType == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>}
|
|
<KVKeysList baseMount={baseMount} secretPath={secretPath} state={this.props.state} />
|
|
</>
|
|
);
|
|
}
|
|
get name(): string {
|
|
return i18next.t("kv_view_title");
|
|
}
|
|
}
|