diff --git a/src/allPages.ts b/src/allPages.ts index edb3469..2097c95 100644 --- a/src/allPages.ts +++ b/src/allPages.ts @@ -19,6 +19,8 @@ import { TransitRewrapPage } from "./pages/Transit/TransitRewrap"; import { TransitViewPage } from "./pages/Transit/TransitView"; import { TransitViewSecretPage } from "./pages/Transit/TransitViewSecret"; import { UnsealPage } from "./pages/Unseal"; +import { NewSecretsEnginePage } from "./pages/NewSecretsEngine"; +import { NewKVEnginePage } from "./pages/NewEngines/NewKVEngine"; type pagesList = { [key: string]: Page; @@ -45,4 +47,6 @@ export const allPages: pagesList = { KEY_VALUE_DELETE: new KeyValueDeletePage(), KEY_VALUE_SECRET_EDIT: new KeyValueSecretEditPage(), PW_GEN: new PwGenPage(), + NEW_SECRETS_ENGINE: new NewSecretsEnginePage(), + NEW_KV_ENGINE: new NewKVEnginePage(), }; diff --git a/src/api/sys/newMount.ts b/src/api/sys/newMount.ts new file mode 100644 index 0000000..2951bba --- /dev/null +++ b/src/api/sys/newMount.ts @@ -0,0 +1,31 @@ +import { appendAPIURL, getHeaders } from "../apiUtils"; +import { removeDoubleSlash } from "../../utils"; + +type NewMountParams = { + name: string; + type: string; + options?: { + version: string; + } +} + +export async function newMount(parms: NewMountParams): Promise { + const request = new Request( + appendAPIURL(removeDoubleSlash(`/v1/sys/mounts/${parms.name}`)), + { + method: "POST", + headers: { + "Content-Type": "application/json", + ...getHeaders(), + }, + body: JSON.stringify(parms), + }, + ); + const resp = await fetch(request); + if (!resp.ok) { + const data = (await resp.json()) as { errors?: string[] }; + if ("errors" in data) { + throw new Error(data.errors[0]); + } + } +} diff --git a/src/elements/Tile.ts b/src/elements/Tile.ts index 597f831..fc52099 100644 --- a/src/elements/Tile.ts +++ b/src/elements/Tile.ts @@ -1,16 +1,17 @@ import { makeElement } from "../htmlUtils"; type TileParams = { - condition: boolean; + condition?: boolean; title: string; description: string; - icon: string; - iconText: string; + icon?: string; + iconText?: string; onclick: () => void; }; export function Tile(params: TileParams): HTMLElement { - if (!params.condition) return; + console.log(params.condition == undefined, params.condition); + if (params.condition == false) return; return makeElement({ tag: "a", class: "uk-link-heading", @@ -24,6 +25,7 @@ export function Tile(params: TileParams): HTMLElement { class: "uk-h4", text: params.title, children: makeElement({ + condition: typeof params.icon == "string", tag: "span", class: ["uk-icon", "uk-margin-small-left"], attributes: { diff --git a/src/pages/Home.ts b/src/pages/Home.ts index 8d1850c..7c45c65 100644 --- a/src/pages/Home.ts +++ b/src/pages/Home.ts @@ -6,6 +6,7 @@ import { makeElement } from "../htmlUtils"; import { pageState } from "../globalPageState"; import { sortedObjectMap } from "../utils"; import i18next from "i18next"; +import { getCapabilitiesPath } from "../api/sys/getCapabilities"; export class HomePage extends Page { constructor() { @@ -61,6 +62,18 @@ export class HomePage extends Page { } } + const mountsCapabilities = await getCapabilitiesPath("/sys/mounts"); + if (mountsCapabilities.includes("sudo") && mountsCapabilities.includes("create")) { + textList.appendChild(makeElement({ + tag: "button", + text: i18next.t("home_new_secrets_engine_button"), + class: ["uk-button", "uk-button-primary", "uk-margin-top"], + onclick: async () => { + await changePage("NEW_SECRETS_ENGINE"); + }, + })); + } + pageState.currentBaseMount = ""; pageState.currentSecretPath = []; pageState.currentSecret = ""; diff --git a/src/pages/NewEngines/NewKVEngine.ts b/src/pages/NewEngines/NewKVEngine.ts new file mode 100644 index 0000000..b5f5362 --- /dev/null +++ b/src/pages/NewEngines/NewKVEngine.ts @@ -0,0 +1,89 @@ +import { Margin } from "../../elements/Margin"; +import { Option } from "../../elements/Option"; +import { Page } from "../../types/Page"; +import { changePage, setErrorText, setPageContent } from "../../pageUtils"; +import { makeElement } from "../../htmlUtils"; +import { pageState } from "../../globalPageState"; +import { reloadNavBar } from "../../elements/NavBar"; +import i18next from "i18next"; +import {newMount} from "../../api/sys/newMount"; + +export class NewKVEnginePage extends Page { + constructor() { + super(); + } + async render(): Promise { + const newEngineForm = makeElement({ + tag: "form", + children: [ + Margin( + makeElement({ + tag: "input", + class: ["uk-input", "uk-form-width-medium"], + attributes: { + required: "true", + type: "text", + placeholder: i18next.t("new_kv_engine_name_input"), + name: "name", + }, + }), + ), + Margin( + makeElement({ + tag: "select", + class: ["uk-select", "uk-form-width-medium"], + attributes: { + name: "version", + }, + children: [ + Option(i18next.t("new_kv_engine_version_2"), "2"), + Option(i18next.t("new_kv_engine_version_1"), "1"), + ] + }), + ), + makeElement({ + tag: "p", + id: "errorText", + class: "uk-text-danger", + }), + makeElement({ + tag: "button", + class: ["uk-button", "uk-button-primary"], + text: i18next.t("new_kv_engine_create_btn"), + attributes: { + type: "submit", + }, + }), + ], + }) as HTMLFormElement; + + setPageContent(newEngineForm); + + newEngineForm.addEventListener("submit", async function (e) { + e.preventDefault(); + const formData = new FormData(newEngineForm); + + const name = formData.get("name") as string; + const version = formData.get("version") as string; + + try { + await newMount({ + name: name, + type: "kv", + options: { + version: version + } + }); + pageState.currentMountType = "kv-v" + version; + pageState.currentBaseMount = name + "/"; + await changePage("KEY_VALUE_VIEW"); + } catch (e) { + const error = e as Error; + setErrorText(error.message); + } + }); + } + get name(): string { + return i18next.t("new_kv_engine_title"); + } +} diff --git a/src/pages/NewSecretsEngine.ts b/src/pages/NewSecretsEngine.ts new file mode 100644 index 0000000..3c91554 --- /dev/null +++ b/src/pages/NewSecretsEngine.ts @@ -0,0 +1,34 @@ +import { Page } from "../types/Page"; +import { Tile } from "../elements/Tile"; +import { changePage, setPageContent, setTitleElement } from "../pageUtils"; +import { makeElement } from "../htmlUtils"; +import i18next from "i18next"; + +export class NewSecretsEnginePage extends Page { + constructor() { + super(); + } + + async render(): Promise { + setPageContent( + makeElement({ + tag: "div", + class: "uk-child-width-1-1@s uk-child-width-1-2@m uk-grid-small uk-grid-match", + attributes: { "uk-grid": "" }, + children: [ + Tile({ + title: i18next.t("new_secrets_engine_kv_title"), + description: i18next.t("new_secrets_engine_kv_description"), + onclick: () => { + void changePage("NEW_KV_ENGINE"); + }, + }), + ], + }), + ); + } + + get name(): string { + return i18next.t("new_secrets_engine_title"); + } +} diff --git a/src/translations/en.js b/src/translations/en.js index ac874a9..9821672 100644 --- a/src/translations/en.js +++ b/src/translations/en.js @@ -37,6 +37,20 @@ module.exports = { vaulturl_text: "Vault URL: {{text}}", password_generator_btn: "Password Generator", your_token_expires_in: "Your token expires in {{date, until_date}}", + home_new_secrets_engine_button: "New Secrets Engine", + + // New Secrets Engine Page + new_secrets_engine_title: "New Secrets Engine", + new_secrets_engine_kv_title: "Key/Value Storage", + new_secrets_engine_kv_description: "For storing key/value mapped secrets.", + + // New KV Engine Page + new_kv_engine_title: "New Key/Value Engine", + new_kv_engine_name_input: "Name", + new_kv_engine_version_1: "Version 1", + new_kv_engine_version_2: "Version 2", + new_kv_engine_create_btn: "Create", + // Unseal Page unseal_vault_text: "Unseal the Vault",