diff --git a/src/main.tsx b/src/main.tsx index ed315b2..a08196e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -30,9 +30,9 @@ import { Component, render } from "preact"; import { DeleteSecretsEngine } from "./ui/pages/Secrets/DeleteSecretsEngine"; import { Home } from "./ui/pages/Home"; import { KeyValueDelete } from "./ui/pages/Secrets/KeyValue/KeyValueDelete"; -import { KeyValueSecret } from "./ui/pages/Secrets/KeyValue/KeyValueSecret"; -import { KeyValueSecretEdit } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit"; import { KeyValueView } from "./ui/pages/Secrets/KeyValue/KeyValueView"; +import { KeyValueEdit } from "./ui/pages/Secrets/KeyValue/KeyValueEdit"; +import { KeyValueList } from "./ui/pages/Secrets/KeyValue/KeyValueList"; import { Login } from "./ui/pages/Login"; import { Me } from "./ui/pages/Me"; import { NavBar } from "./ui/elements/NavBar"; @@ -40,21 +40,25 @@ import { NewKVEngine } from "./ui/pages/Secrets/NewEngines/NewKVEngine"; import { NewSecretsEngine } from "./ui/pages/Secrets/NewSecretsEngine"; import { NewTOTPEngine } from "./ui/pages/Secrets/NewEngines/NewTOTPEngine"; import { NewTransitEngine } from "./ui/pages/Secrets/NewEngines/NewTransitEngine"; -import { NewTransitKey } from "./ui/pages/Secrets/Transit/NewTransitKey"; +import { TransitNew } from "./ui/pages/Secrets/Transit/TransitNew"; import { PasswordGenerator } from "./ui/pages/PwGen"; import { Secrets } from "./ui/pages/Secrets/SecretsHome"; import { SetLanguage } from "./ui/pages/SetLanguage"; import { SetVaultURL } from "./ui/pages/SetVaultURL"; import { TOTPDelete } from "./ui/pages/Secrets/TOTP/TOTPDelete"; import { TOTPNew } from "./ui/pages/Secrets/TOTP/TOTPNew"; -import { TOTPView } from "./ui/pages/Secrets/TOTP/TOTPView"; +import { TOTPList } from "./ui/pages/Secrets/TOTP/TOTPList"; +import { TransitList } from "./ui/pages/Secrets/Transit/TransitList"; import { TransitView } from "./ui/pages/Secrets/Transit/TransitView"; -import { TransitViewSecret } from "./ui/pages/Secrets/Transit/TransitViewSecret"; import { Unseal } from "./ui/pages/Unseal"; import { pageState } from "./globalPageState"; import { playground } from "./playground"; import Router from "preact-router"; import i18next from "i18next"; +import { KeyValueNew } from "./ui/pages/Secrets/KeyValue/KeyValueNew"; +import { TransitDecrypt } from "./ui/pages/Secrets/Transit/TransitDecrypt"; +import { TransitEncrypt } from "./ui/pages/Secrets/Transit/TransitEncrypt"; +import { TransitRewrap } from "./ui/pages/Secrets/Transit/TransitRewrap"; async function onLoad(): Promise { const Main = () => ( @@ -75,18 +79,23 @@ async function onLoad(): Promise { - - - - + + + + + - + - - - + + + + + + +

PAGE NOT YET IMPLEMENTED

diff --git a/src/ui/pages/Secrets/KeyValue/KeyValueSecretsEdit.tsx b/src/ui/pages/Secrets/KeyValue/KeyValueEdit.tsx similarity index 97% rename from src/ui/pages/Secrets/KeyValue/KeyValueSecretsEdit.tsx rename to src/ui/pages/Secrets/KeyValue/KeyValueEdit.tsx index f298b4a..6303b32 100644 --- a/src/ui/pages/Secrets/KeyValue/KeyValueSecretsEdit.tsx +++ b/src/ui/pages/Secrets/KeyValue/KeyValueEdit.tsx @@ -104,7 +104,7 @@ export class KVEditor extends Component { } } -export class KeyValueSecretEdit extends Component { +export class KeyValueEdit extends Component { render() { const baseMount = this.props.matches["baseMount"]; const secretPath = this.props.matches["secretPath"].split("/"); diff --git a/src/ui/pages/Secrets/KeyValue/KeyValueList.tsx b/src/ui/pages/Secrets/KeyValue/KeyValueList.tsx new file mode 100644 index 0000000..5dd41bd --- /dev/null +++ b/src/ui/pages/Secrets/KeyValue/KeyValueList.tsx @@ -0,0 +1,209 @@ +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) => ( +
  • + { + 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} + +
  • + )); +} + +export class KVKeysList extends Component { + constructor() { + super(); + this.state = { + dataLoaded: false, + keys: [], + searchQuery: "", + }; + } + + async loadData(): Promise { + 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

    {i18next.t("content_loading")}

    ; + } + + if (this.state.keys == null) { + return

    {i18next.t("kv_view_none_here_text")}

    ; + } + + return ( + <> + { + this.setState({ + searchQuery: (this.searchBarRef.current as unknown as HTMLInputElement).value, + }); + }} + /> +
    + +
      + {...((): 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); + })()} +
    + + ); + } +} + +type KeyValueListState = { + pathCaps: string[]; + mountCaps: string[]; + mountType: string; +}; + +export class KeyValueList extends Component { + 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 ( + <> + +

    + {this.state.pathCaps.includes("create") && ( + + )} + {secretPath.length == 0 && this.state.mountCaps.includes("delete") && ( + + )} +

    + {this.state.mountType == "cubbyhole" &&

    {i18next.t("kv_view_cubbyhole_text")}

    } + + + ); + } +} diff --git a/src/ui/pages/Secrets/KeyValue/KeyValueNew.tsx b/src/ui/pages/Secrets/KeyValue/KeyValueNew.tsx index 7caa02e..e79c714 100644 --- a/src/ui/pages/Secrets/KeyValue/KeyValueNew.tsx +++ b/src/ui/pages/Secrets/KeyValue/KeyValueNew.tsx @@ -9,7 +9,7 @@ import { route } from "preact-router"; import { setErrorText } from "../../../../pageUtils"; import i18next from "i18next"; -export class KeyValueNewPage extends Component { +export class KeyValueNew extends Component { render() { const baseMount = this.props.matches["baseMount"]; const secretPath = (this.props.matches["secretPath"] || "").split("/"); diff --git a/src/ui/pages/Secrets/KeyValue/KeyValueSecret.tsx b/src/ui/pages/Secrets/KeyValue/KeyValueSecret.tsx deleted file mode 100644 index 46877a1..0000000 --- a/src/ui/pages/Secrets/KeyValue/KeyValueSecret.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { CodeBlock } from "../../../elements/CodeBlock"; -import { Component, JSX, render } from "preact"; -import { CopyableInputBox } from "../../../elements/CopyableInputBox"; -import { DefaultPageProps } from "../../../../types/DefaultPageProps"; -import { Grid, GridSizes } from "../../../elements/Grid"; -import { SecretTitleElement } from "../SecretTitleElement"; -import { getCapabilities } from "../../../../api/sys/getCapabilities"; -import { getSecret } from "../../../../api/kv/getSecret"; -import { kvDeleteURL, kvEditURL } from "../../pageLinks"; -import { route } from "preact-router"; -import { sortedObjectMap } from "../../../../utils"; -import i18next from "i18next"; - -export type KVSecretViewProps = { - kvData: Record; -}; - -export class KVSecretVew extends Component { - render(): JSX.Element { - const secretsMap = sortedObjectMap(this.props.kvData); - let isMultiLevelJSON = false; - - for (const value of secretsMap.values()) { - if (typeof value == "object") isMultiLevelJSON = true; - } - - if (isMultiLevelJSON) { - const jsonText = JSON.stringify(Object.fromEntries(secretsMap), null, 4); - return ; - } else { - return ( - <> - {Array.from(secretsMap).map((data: [string, string]) => ( - - - - - ))} - - ); - } - } -} - -type KeyValueSecretState = { - baseMount: string; - secretPath: string[]; - secretItem: string; - caps: string[]; - secretInfo: Record; -}; - -export class KeyValueSecret extends Component { - async componentDidMount() { - const baseMount = this.props.matches["baseMount"]; - const secretPath = this.props.matches["secretPath"].split("/"); - const secretItem = this.props.matches["item"]; - - const caps = (await getCapabilities(baseMount, secretPath, secretItem)).capabilities; - - const secretPathAPI = secretPath.map((e) => e + "/"); - // TODO: this is a big hacky, fix when redo how api arguments work - secretPathAPI[secretPathAPI.length - 1] = String(secretPathAPI[secretPathAPI.length - 1]) - .replace("/", "") - .toString(); - - const secretInfo = await getSecret(baseMount, secretPathAPI, secretItem); - this.setState({ - baseMount, - secretPath, - secretItem, - caps, - secretInfo, - }); - } - render() { - if (!this.state.baseMount) return; - - return ( - <> - -
    -

    - {this.state.caps.includes("delete") && ( - - )} - {this.state.caps.includes("update") && ( - - )} -

    - - {} -
    - - ); - } -} diff --git a/src/ui/pages/Secrets/KeyValue/KeyValueView.tsx b/src/ui/pages/Secrets/KeyValue/KeyValueView.tsx index 8dd13d7..089e95c 100644 --- a/src/ui/pages/Secrets/KeyValue/KeyValueView.tsx +++ b/src/ui/pages/Secrets/KeyValue/KeyValueView.tsx @@ -1,210 +1,121 @@ -import { CapabilitiesType, getCapabilitiesPath } from "../../../../api/sys/getCapabilities"; -import { Component, JSX, createRef, render } from "preact"; +import { CodeBlock } from "../../../elements/CodeBlock"; +import { Component, JSX, render } from "preact"; +import { CopyableInputBox } from "../../../elements/CopyableInputBox"; import { DefaultPageProps } from "../../../../types/DefaultPageProps"; -import { DoesNotExistError } from "../../../../types/internalErrors"; -import { Page } from "../../../../types/Page"; +import { Grid, GridSizes } from "../../../elements/Grid"; import { SecretTitleElement } from "../SecretTitleElement"; -import { delSecretsEngineURL, kvListURL, kvNewURL, kvViewURL } from "../../pageLinks"; -import { getMount } from "../../../../api/sys/getMounts"; -import { getSecrets } from "../../../../api/kv/getSecrets"; +import { getCapabilities } from "../../../../api/sys/getCapabilities"; +import { getSecret } from "../../../../api/kv/getSecret"; +import { kvDeleteURL, kvEditURL } from "../../pageLinks"; import { route } from "preact-router"; -import { setErrorText } from "../../../../pageUtils"; +import { sortedObjectMap } from "../../../../utils"; import i18next from "i18next"; -export type KVKeysListProps = DefaultPageProps & { - baseMount: string; - secretPath: string[]; +export type KVSecretViewProps = { + kvData: Record; }; -type KVKeysListState = { - dataLoaded: boolean; - keys: string[]; - searchQuery: string; -}; - -function SecretsList(baseMount: string, secretPath: string[], secrets: string[]): JSX.Element[] { - return secrets.map((secret) => ( -
  • - { - 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} - -
  • - )); -} - -export class KVKeysList extends Component { - constructor() { - super(); - this.state = { - dataLoaded: false, - keys: [], - searchQuery: "", - }; - } - - async loadData(): Promise { - 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(); - +export class KVSecretVew extends Component { render(): JSX.Element { - if (!this.state.dataLoaded) { - return

    {i18next.t("content_loading")}

    ; + const secretsMap = sortedObjectMap(this.props.kvData); + let isMultiLevelJSON = false; + + for (const value of secretsMap.values()) { + if (typeof value == "object") isMultiLevelJSON = true; } - if (this.state.keys == null) { - return

    {i18next.t("kv_view_none_here_text")}

    ; + if (isMultiLevelJSON) { + const jsonText = JSON.stringify(Object.fromEntries(secretsMap), null, 4); + return ; + } else { + return ( + <> + {Array.from(secretsMap).map((data: [string, string]) => ( + + + + + ))} + + ); } - - return ( - <> - { - this.setState({ - searchQuery: (this.searchBarRef.current as unknown as HTMLInputElement).value, - }); - }} - /> -
    - -
      - {...((): 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); - })()} -
    - - ); } } type KeyValueViewState = { - pathCaps: string[]; - mountCaps: string[]; - mountType: string; + baseMount: string; + secretPath: string[]; + secretItem: string; + caps: string[]; + secretInfo: Record; }; export class KeyValueView extends Component { async componentDidMount() { const baseMount = this.props.matches["baseMount"]; const secretPath = this.props.matches["secretPath"].split("/"); + const secretItem = this.props.matches["item"]; - const mountsPath = "/sys/mounts/" + baseMount; - const currentPath = baseMount + secretPath.join(); - const caps = await getCapabilitiesPath([mountsPath, currentPath]); + const caps = (await getCapabilities(baseMount, secretPath, secretItem)).capabilities; - const mount = await getMount(baseMount); + const secretPathAPI = secretPath.map((e) => e + "/"); + // TODO: this is a big hacky, fix when redo how api arguments work + secretPathAPI[secretPathAPI.length - 1] = String(secretPathAPI[secretPathAPI.length - 1]) + .replace("/", "") + .toString(); + const secretInfo = await getSecret(baseMount, secretPathAPI, secretItem); this.setState({ - mountCaps: caps[mountsPath], - pathCaps: caps[currentPath], - mountType: mount.type, + baseMount, + secretPath, + secretItem, + caps, + secretInfo, }); } - render() { - if (!this.state.pathCaps) return; - - const baseMount = this.props.matches["baseMount"]; - const secretPath = this.props.matches["secretPath"].split("/"); + if (!this.state.baseMount) return; return ( <> -

    - {this.state.pathCaps.includes("create") && ( - - )} - {secretPath.length == 0 && this.state.mountCaps.includes("delete") && ( - - )} -

    - {this.state.mountType == "cubbyhole" &&

    {i18next.t("kv_view_cubbyhole_text")}

    } - +
    +

    + {this.state.caps.includes("delete") && ( + + )} + {this.state.caps.includes("update") && ( + + )} +

    + + {} +
    ); } - get name(): string { - return i18next.t("kv_view_title"); - } } diff --git a/src/ui/pages/Secrets/TOTP/TOTPView.tsx b/src/ui/pages/Secrets/TOTP/TOTPList.tsx similarity index 96% rename from src/ui/pages/Secrets/TOTP/TOTPView.tsx rename to src/ui/pages/Secrets/TOTP/TOTPList.tsx index 73156a2..08f9de8 100644 --- a/src/ui/pages/Secrets/TOTP/TOTPView.tsx +++ b/src/ui/pages/Secrets/TOTP/TOTPList.tsx @@ -3,13 +3,12 @@ import { getCapabilitiesPath, getCapsPath, } from "../../../../api/sys/getCapabilities"; -import { Component, JSX, render } from "preact"; +import { Component, JSX } from "preact"; import { CopyableInputBox } from "../../../elements/CopyableInputBox"; import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DoesNotExistError } from "../../../../types/internalErrors"; import { Grid, GridSizes } from "../../../elements/Grid"; import { MarginInline } from "../../../elements/MarginInline"; -import { Page } from "../../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { delSecretsEngineURL, totpNewURL } from "../../pageLinks"; import { getTOTPCode } from "../../../../api/totp/getTOTPCode"; @@ -73,12 +72,12 @@ export class RefreshingTOTPGridItem extends Component { +export class TOTPList extends Component { constructor() { super(); this.refresher = undefined; diff --git a/src/ui/pages/Secrets/Transit/TransitDecrypt.tsx b/src/ui/pages/Secrets/Transit/TransitDecrypt.tsx index 013365e..d692705 100644 --- a/src/ui/pages/Secrets/Transit/TransitDecrypt.tsx +++ b/src/ui/pages/Secrets/Transit/TransitDecrypt.tsx @@ -3,51 +3,55 @@ import { FileUploadInput } from "../../../elements/FileUploadInput"; import { Form } from "../../../elements/Form"; import { InputWithTitle } from "../../../elements/InputWithTitle"; import { Margin } from "../../../elements/Margin"; -import { Page } from "../../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { fileToBase64 } from "../../../../htmlUtils"; -import { render } from "preact"; +import { Component, render } from "preact"; import { setErrorText } from "../../../../pageUtils"; import { transitDecrypt } from "../../../../api/transit/transitDecrypt"; import UIkit from "uikit"; import i18next from "i18next"; +import { DefaultPageProps } from "../../../../types/DefaultPageProps"; -export class TransitDecryptPage extends Page { - constructor() { - super(); - } - - async goBack(): Promise { - await this.router.changePage("TRANSIT_VIEW_SECRET"); - } - - async render(): Promise { - render( -
    await this.onSubmit(data)}> - -