initial work on moving to a real page router
This commit is contained in:
parent
da163c931a
commit
c62cd89771
|
@ -17,6 +17,7 @@
|
||||||
"codejar": "^3.5.0",
|
"codejar": "^3.5.0",
|
||||||
"core-js": "^3.20.2",
|
"core-js": "^3.20.2",
|
||||||
"css-loader": "^6.5.1",
|
"css-loader": "^6.5.1",
|
||||||
|
"css-minimizer-webpack-plugin": "^3.3.1",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"eslint": "^8.6.0",
|
"eslint": "^8.6.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
@ -41,7 +42,9 @@
|
||||||
"uikit": "^3.9.4",
|
"uikit": "^3.9.4",
|
||||||
"webpack": "^5.65.0",
|
"webpack": "^5.65.0",
|
||||||
"webpack-cli": "^4.9.1",
|
"webpack-cli": "^4.9.1",
|
||||||
"webpack-dev-server": "^4.7.2",
|
"webpack-dev-server": "^4.7.2"
|
||||||
"css-minimizer-webpack-plugin": "^3.3.1"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"preact-router": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
242
src/allPages.ts
242
src/allPages.ts
|
@ -1,121 +1,121 @@
|
||||||
import { PageType } from "./pagerouter/PageType";
|
//import { PageType } from "./pagerouter/PageType";
|
||||||
|
//
|
||||||
import { AccessHomePage } from "./ui/pages/Access/AccessHome";
|
//import { AccessHomePage } from "./ui/pages/Access/AccessHome";
|
||||||
import { AuthHomePage } from "./ui/pages/Access/Auth/AuthHome";
|
//import { AuthHomePage } from "./ui/pages/Access/Auth/AuthHome";
|
||||||
import { AuthViewConfigPage } from "./ui/pages/Access/Auth/AuthViewConfig";
|
//import { AuthViewConfigPage } from "./ui/pages/Access/Auth/AuthViewConfig";
|
||||||
import { DeleteSecretsEnginePage } from "./ui/pages/Secrets/DeleteSecretsEngine";
|
//import { DeleteSecretsEnginePage } from "./ui/pages/Secrets/DeleteSecretsEngine";
|
||||||
import { HomePage } from "./ui/pages/Home";
|
//import { HomePage } from "./ui/pages/Home";
|
||||||
import { KeyValueDeletePage } from "./ui/pages/Secrets/KeyValue/KeyValueDelete";
|
//import { KeyValueDeletePage } from "./ui/pages/Secrets/KeyValue/KeyValueDelete";
|
||||||
import { KeyValueNewPage } from "./ui/pages/Secrets/KeyValue/KeyValueNew";
|
//import { KeyValueNewPage } from "./ui/pages/Secrets/KeyValue/KeyValueNew";
|
||||||
import { KeyValueSecretEditPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit";
|
//import { KeyValueSecretEditPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit";
|
||||||
import { KeyValueSecretPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecret";
|
//import { KeyValueSecretPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecret";
|
||||||
import { KeyValueVersionsPage } from "./ui/pages/Secrets/KeyValue/KeyValueVersions";
|
//import { KeyValueVersionsPage } from "./ui/pages/Secrets/KeyValue/KeyValueVersions";
|
||||||
import { KeyValueViewPage } from "./ui/pages/Secrets/KeyValue/KeyValueView";
|
//import { KeyValueViewPage } from "./ui/pages/Secrets/KeyValue/KeyValueView";
|
||||||
import { LoginPage } from "./ui/pages/Login";
|
//import { LoginPage } from "./ui/pages/Login";
|
||||||
import { MePage } from "./ui/pages/Me";
|
//import { MePage } from "./ui/pages/Me";
|
||||||
import { NewKVEnginePage } from "./ui/pages/Secrets/NewEngines/NewKVEngine";
|
//import { NewKVEnginePage } from "./ui/pages/Secrets/NewEngines/NewKVEngine";
|
||||||
import { NewSecretsEnginePage } from "./ui/pages/Secrets/NewSecretsEngine";
|
//import { NewSecretsEnginePage } from "./ui/pages/Secrets/NewSecretsEngine";
|
||||||
import { NewTOTPEnginePage } from "./ui/pages/Secrets/NewEngines/NewTOTPEngine";
|
//import { NewTOTPEnginePage } from "./ui/pages/Secrets/NewEngines/NewTOTPEngine";
|
||||||
import { NewTransitEnginePage } from "./ui/pages/Secrets/NewEngines/NewTransitEngine";
|
//import { NewTransitEnginePage } from "./ui/pages/Secrets/NewEngines/NewTransitEngine";
|
||||||
import { NewTransitKeyPage } from "./ui/pages/Secrets/Transit/NewTransitKey";
|
//import { NewTransitKeyPage } from "./ui/pages/Secrets/Transit/NewTransitKey";
|
||||||
import { Page } from "./types/Page";
|
//import { Page } from "./types/Page";
|
||||||
import { PoliciesHomePage } from "./ui/pages/Policies/PoliciesHome";
|
//import { PoliciesHomePage } from "./ui/pages/Policies/PoliciesHome";
|
||||||
import { PolicyDeletePage } from "./ui/pages/Policies/PolicyDelete";
|
//import { PolicyDeletePage } from "./ui/pages/Policies/PolicyDelete";
|
||||||
import { PolicyEditPage } from "./ui/pages/Policies/PolicyEdit";
|
//import { PolicyEditPage } from "./ui/pages/Policies/PolicyEdit";
|
||||||
import { PolicyNewPage } from "./ui/pages/Policies/PolicyNew";
|
//import { PolicyNewPage } from "./ui/pages/Policies/PolicyNew";
|
||||||
import { PolicyViewPage } from "./ui/pages/Policies/PolicyView";
|
//import { PolicyViewPage } from "./ui/pages/Policies/PolicyView";
|
||||||
import { PwGenPage } from "./ui/pages/PwGen";
|
//import { PwGenPage } from "./ui/pages/PwGen";
|
||||||
import { SecretsHomePage } from "./ui/pages/Secrets/SecretsHome";
|
//import { SecretsHomePage } from "./ui/pages/Secrets/SecretsHome";
|
||||||
import { SetLanguagePage } from "./ui/pages/SetLanguage";
|
//import { SetLanguagePage } from "./ui/pages/SetLanguage";
|
||||||
import { SetVaultURLPage } from "./ui/pages/SetVaultURL";
|
//import { SetVaultURLPage } from "./ui/pages/SetVaultURL";
|
||||||
import { TOTPDeletePage } from "./ui/pages/Secrets/TOTP/TOTPDelete";
|
//import { TOTPDeletePage } from "./ui/pages/Secrets/TOTP/TOTPDelete";
|
||||||
import { TOTPNewPage } from "./ui/pages/Secrets/TOTP/TOTPNew";
|
//import { TOTPNewPage } from "./ui/pages/Secrets/TOTP/TOTPNew";
|
||||||
import { TOTPViewPage } from "./ui/pages/Secrets/TOTP/TOTPView";
|
//import { TOTPViewPage } from "./ui/pages/Secrets/TOTP/TOTPView";
|
||||||
import { TransitDecryptPage } from "./ui/pages/Secrets/Transit/TransitDecrypt";
|
//import { TransitDecryptPage } from "./ui/pages/Secrets/Transit/TransitDecrypt";
|
||||||
import { TransitEncryptPage } from "./ui/pages/Secrets/Transit/TransitEncrypt";
|
//import { TransitEncryptPage } from "./ui/pages/Secrets/Transit/TransitEncrypt";
|
||||||
import { TransitRewrapPage } from "./ui/pages/Secrets/Transit/TransitRewrap";
|
//import { TransitRewrapPage } from "./ui/pages/Secrets/Transit/TransitRewrap";
|
||||||
import { TransitViewPage } from "./ui/pages/Secrets/Transit/TransitView";
|
//import { TransitViewPage } from "./ui/pages/Secrets/Transit/TransitView";
|
||||||
import { TransitViewSecretPage } from "./ui/pages/Secrets/Transit/TransitViewSecret";
|
//import { TransitViewSecretPage } from "./ui/pages/Secrets/Transit/TransitViewSecret";
|
||||||
import { UnsealPage } from "./ui/pages/Unseal";
|
//import { UnsealPage } from "./ui/pages/Unseal";
|
||||||
import { UserPassUserDeletePage } from "./ui/pages/Access/Auth/userpass/UserPassUserDelete";
|
//import { UserPassUserDeletePage } from "./ui/pages/Access/Auth/userpass/UserPassUserDelete";
|
||||||
import { UserPassUserEditPage } from "./ui/pages/Access/Auth/userpass/UserPassUserEdit";
|
//import { UserPassUserEditPage } from "./ui/pages/Access/Auth/userpass/UserPassUserEdit";
|
||||||
import { UserPassUserNewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserNew";
|
//import { UserPassUserNewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserNew";
|
||||||
import { UserPassUserViewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserView";
|
//import { UserPassUserViewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserView";
|
||||||
import { UserPassUsersListPage } from "./ui/pages/Access/Auth/userpass/UserPassUsersList";
|
//import { UserPassUsersListPage } from "./ui/pages/Access/Auth/userpass/UserPassUsersList";
|
||||||
import { getObjectKeys } from "./utils";
|
//import { getObjectKeys } from "./utils";
|
||||||
|
//
|
||||||
type pagesList = {
|
//type pagesList = {
|
||||||
[key: string]: Page;
|
// [key: string]: Page;
|
||||||
};
|
//};
|
||||||
|
//
|
||||||
export const allPages: pagesList = {
|
//export const allPages: pagesList = {
|
||||||
HOME: new HomePage(),
|
// HOME: new HomePage(),
|
||||||
LOGIN: new LoginPage(),
|
// LOGIN: new LoginPage(),
|
||||||
SET_VAULT_URL: new SetVaultURLPage(),
|
// SET_VAULT_URL: new SetVaultURLPage(),
|
||||||
UNSEAL: new UnsealPage(),
|
// UNSEAL: new UnsealPage(),
|
||||||
SET_LANGUAGE: new SetLanguagePage(),
|
// SET_LANGUAGE: new SetLanguagePage(),
|
||||||
ME: new MePage(),
|
// ME: new MePage(),
|
||||||
PW_GEN: new PwGenPage(),
|
// PW_GEN: new PwGenPage(),
|
||||||
|
//
|
||||||
POLICIES_HOME: new PoliciesHomePage(),
|
// POLICIES_HOME: new PoliciesHomePage(),
|
||||||
POLICY_VIEW: new PolicyViewPage(),
|
// POLICY_VIEW: new PolicyViewPage(),
|
||||||
POLICY_NEW: new PolicyNewPage(),
|
// POLICY_NEW: new PolicyNewPage(),
|
||||||
POLICY_EDIT: new PolicyEditPage(),
|
// POLICY_EDIT: new PolicyEditPage(),
|
||||||
POLICY_DELETE: new PolicyDeletePage(),
|
// POLICY_DELETE: new PolicyDeletePage(),
|
||||||
|
//
|
||||||
ACCESS_HOME: new AccessHomePage(),
|
// ACCESS_HOME: new AccessHomePage(),
|
||||||
|
//
|
||||||
AUTH_HOME: new AuthHomePage(),
|
// AUTH_HOME: new AuthHomePage(),
|
||||||
AUTH_VIEW_CONFIG: new AuthViewConfigPage(),
|
// AUTH_VIEW_CONFIG: new AuthViewConfigPage(),
|
||||||
|
//
|
||||||
USERPASS_USERS_LIST: new UserPassUsersListPage(),
|
// USERPASS_USERS_LIST: new UserPassUsersListPage(),
|
||||||
USERPASS_USER_VIEW: new UserPassUserViewPage(),
|
// USERPASS_USER_VIEW: new UserPassUserViewPage(),
|
||||||
USERPASS_USER_EDIT: new UserPassUserEditPage(),
|
// USERPASS_USER_EDIT: new UserPassUserEditPage(),
|
||||||
USERPASS_USER_NEW: new UserPassUserNewPage(),
|
// USERPASS_USER_NEW: new UserPassUserNewPage(),
|
||||||
USERPASS_USER_DELETE: new UserPassUserDeletePage(),
|
// USERPASS_USER_DELETE: new UserPassUserDeletePage(),
|
||||||
|
//
|
||||||
SECRETS_HOME: new SecretsHomePage(),
|
// SECRETS_HOME: new SecretsHomePage(),
|
||||||
|
//
|
||||||
TOTP_VIEW: new TOTPViewPage(),
|
// TOTP_VIEW: new TOTPViewPage(),
|
||||||
TOTP_NEW: new TOTPNewPage(),
|
// TOTP_NEW: new TOTPNewPage(),
|
||||||
TOTP_DELETE: new TOTPDeletePage(),
|
// TOTP_DELETE: new TOTPDeletePage(),
|
||||||
|
//
|
||||||
TRANSIT_VIEW: new TransitViewPage(),
|
// TRANSIT_VIEW: new TransitViewPage(),
|
||||||
TRANSIT_NEW_KEY: new NewTransitKeyPage(),
|
// TRANSIT_NEW_KEY: new NewTransitKeyPage(),
|
||||||
TRANSIT_VIEW_SECRET: new TransitViewSecretPage(),
|
// TRANSIT_VIEW_SECRET: new TransitViewSecretPage(),
|
||||||
TRANSIT_ENCRYPT: new TransitEncryptPage(),
|
// TRANSIT_ENCRYPT: new TransitEncryptPage(),
|
||||||
TRANSIT_DECRYPT: new TransitDecryptPage(),
|
// TRANSIT_DECRYPT: new TransitDecryptPage(),
|
||||||
TRANSIT_REWRAP: new TransitRewrapPage(),
|
// TRANSIT_REWRAP: new TransitRewrapPage(),
|
||||||
|
//
|
||||||
KEY_VALUE_VIEW: new KeyValueViewPage(),
|
// KEY_VALUE_VIEW: new KeyValueViewPage(),
|
||||||
KEY_VALUE_SECRET: new KeyValueSecretPage(),
|
// KEY_VALUE_SECRET: new KeyValueSecretPage(),
|
||||||
KEY_VALUE_VERSIONS: new KeyValueVersionsPage(),
|
// KEY_VALUE_VERSIONS: new KeyValueVersionsPage(),
|
||||||
KEY_VALUE_NEW_SECRET: new KeyValueNewPage(),
|
// KEY_VALUE_NEW_SECRET: new KeyValueNewPage(),
|
||||||
KEY_VALUE_DELETE: new KeyValueDeletePage(),
|
// KEY_VALUE_DELETE: new KeyValueDeletePage(),
|
||||||
KEY_VALUE_SECRET_EDIT: new KeyValueSecretEditPage(),
|
// KEY_VALUE_SECRET_EDIT: new KeyValueSecretEditPage(),
|
||||||
|
//
|
||||||
DELETE_SECRET_ENGINE: new DeleteSecretsEnginePage(),
|
// DELETE_SECRET_ENGINE: new DeleteSecretsEnginePage(),
|
||||||
|
//
|
||||||
NEW_SECRETS_ENGINE: new NewSecretsEnginePage(),
|
// NEW_SECRETS_ENGINE: new NewSecretsEnginePage(),
|
||||||
NEW_KV_ENGINE: new NewKVEnginePage(),
|
// NEW_KV_ENGINE: new NewKVEnginePage(),
|
||||||
NEW_TOTP_ENGINE: new NewTOTPEnginePage(),
|
// NEW_TOTP_ENGINE: new NewTOTPEnginePage(),
|
||||||
NEW_TRANSIT_ENGINE: new NewTransitEnginePage(),
|
// NEW_TRANSIT_ENGINE: new NewTransitEnginePage(),
|
||||||
};
|
//};
|
||||||
|
//
|
||||||
// This should implement all o PageListType
|
//// This should implement all o PageListType
|
||||||
class PageList {
|
//class PageList {
|
||||||
constructor(pages: pagesList) {
|
// constructor(pages: pagesList) {
|
||||||
this.pages = pages;
|
// this.pages = pages;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private pages: pagesList;
|
// private pages: pagesList;
|
||||||
|
//
|
||||||
async getPageIDs(): Promise<string[]> {
|
// async getPageIDs(): Promise<string[]> {
|
||||||
return getObjectKeys(this.pages);
|
// return getObjectKeys(this.pages);
|
||||||
}
|
// }
|
||||||
async getPage(pageID: string): Promise<PageType> {
|
// async getPage(pageID: string): Promise<PageType> {
|
||||||
return this.pages[pageID];
|
// return this.pages[pageID];
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
export const pageList = new PageList(allPages);
|
//export const pageList = new PageList(allPages);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
|
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
|
||||||
import { removeDoubleSlash } from "../../utils";
|
import { removeDoubleSlash } from "../../utils";
|
||||||
|
import { getMount } from "../sys/getMounts";
|
||||||
|
|
||||||
export async function createOrUpdateSecret(
|
export async function createOrUpdateSecret(
|
||||||
baseMount: string,
|
baseMount: string,
|
||||||
secretMountType: string,
|
|
||||||
secretPath: string[],
|
secretPath: string[],
|
||||||
name: string,
|
name: string,
|
||||||
data: Record<string, unknown>,
|
data: Record<string, unknown>,
|
||||||
|
@ -11,7 +11,8 @@ export async function createOrUpdateSecret(
|
||||||
let secretURL = "";
|
let secretURL = "";
|
||||||
let APIData = {};
|
let APIData = {};
|
||||||
|
|
||||||
if (secretMountType == "kv-v2") {
|
const mountInfo = await getMount(baseMount);
|
||||||
|
if (mountInfo.options.version == "2") {
|
||||||
secretURL = `/v1/${baseMount}/data/${secretPath.join("/")}/${name}`;
|
secretURL = `/v1/${baseMount}/data/${secretPath.join("/")}/${name}`;
|
||||||
APIData = { data: data };
|
APIData = { data: data };
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,40 +1,26 @@
|
||||||
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
|
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
|
||||||
import { removeDoubleSlash } from "../../utils";
|
import { removeDoubleSlash } from "../../utils";
|
||||||
|
import { getMount } from "../sys/getMounts";
|
||||||
|
|
||||||
export async function deleteSecret(
|
export async function deleteSecret(
|
||||||
baseMount: string,
|
baseMount: string,
|
||||||
secretMountType: string,
|
|
||||||
secretPath: string[],
|
secretPath: string[],
|
||||||
name: string,
|
name: string,
|
||||||
version: string | null = null,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let secretURL = "";
|
let secretURL = "";
|
||||||
|
|
||||||
let request;
|
let request;
|
||||||
|
|
||||||
if (secretMountType == "kv-v2" && version != null) {
|
const mountInfo = await getMount(baseMount);
|
||||||
secretURL = `/v1/${baseMount}/delete/${secretPath.join("")}/${name}`;
|
if (mountInfo.options.version == "2") {
|
||||||
secretURL = removeDoubleSlash(secretURL).replace(/\/$/, "");
|
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`;
|
||||||
request = new Request(appendAPIURL(secretURL), {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
...getHeaders(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: version != null ? JSON.stringify({ versions: [version] }) : "{}",
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (secretMountType == "kv-v2") {
|
secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`;
|
||||||
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`;
|
|
||||||
} else {
|
|
||||||
secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`;
|
|
||||||
}
|
|
||||||
secretURL = removeDoubleSlash(secretURL).replace(/\/$/, "");
|
|
||||||
request = new Request(appendAPIURL(secretURL), {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: getHeaders(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
secretURL = removeDoubleSlash(secretURL).replace(/\/$/, "");
|
||||||
|
request = new Request(appendAPIURL(secretURL), {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: getHeaders(),
|
||||||
|
});
|
||||||
const resp = await fetch(request);
|
const resp = await fetch(request);
|
||||||
await checkResponse(resp);
|
await checkResponse(resp);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
|
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
|
||||||
|
import { getMount } from "../sys/getMounts";
|
||||||
|
|
||||||
export async function getSecretKV1(
|
export async function getSecretKV1(
|
||||||
baseMount: string,
|
baseMount: string,
|
||||||
|
@ -21,12 +22,10 @@ export async function getSecretKV2(
|
||||||
baseMount: string,
|
baseMount: string,
|
||||||
secretPath: string[],
|
secretPath: string[],
|
||||||
name: string,
|
name: string,
|
||||||
version: string | null = null,
|
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
let secretURL = "";
|
let secretURL = "";
|
||||||
|
|
||||||
secretURL = `/v1/${baseMount}/data/${secretPath.join("")}/${name}`;
|
secretURL = `/v1/${baseMount}/data/${secretPath.join("")}/${name}`;
|
||||||
if (version != null) secretURL += `?version=${version}`;
|
|
||||||
|
|
||||||
const request = new Request(appendAPIURL(secretURL), {
|
const request = new Request(appendAPIURL(secretURL), {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
|
@ -41,13 +40,12 @@ export async function getSecretKV2(
|
||||||
|
|
||||||
export async function getSecret(
|
export async function getSecret(
|
||||||
baseMount: string,
|
baseMount: string,
|
||||||
secretMountType: string,
|
|
||||||
secretPath: string[],
|
secretPath: string[],
|
||||||
name: string,
|
name: string,
|
||||||
version: string | null = null,
|
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
if (secretMountType == "kv-v2") {
|
const mountInfo = await getMount(baseMount);
|
||||||
return await getSecretKV2(baseMount, secretPath, name, version);
|
if (mountInfo.options.version == "2") {
|
||||||
|
return await getSecretKV2(baseMount, secretPath, name);
|
||||||
} else {
|
} else {
|
||||||
return await getSecretKV1(baseMount, secretPath, name);
|
return await getSecretKV1(baseMount, secretPath, name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,13 @@ import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
|
||||||
|
|
||||||
export async function getSecrets(
|
export async function getSecrets(
|
||||||
baseMount: string,
|
baseMount: string,
|
||||||
secretMountType: string,
|
|
||||||
secretPath: string[],
|
secretPath: string[],
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
let secretURL = "";
|
let secretURL = "";
|
||||||
|
|
||||||
|
// TODO: FIX THIS
|
||||||
|
let secretMountType = "kv-v2"
|
||||||
|
|
||||||
if (secretMountType == "kv-v2") {
|
if (secretMountType == "kv-v2") {
|
||||||
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}?list=true`;
|
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}?list=true`;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -21,3 +21,15 @@ export async function getMounts(): Promise<MountsType> {
|
||||||
const data = (await resp.json()) as { data: { secret: MountsType } };
|
const data = (await resp.json()) as { data: { secret: MountsType } };
|
||||||
return data.data.secret;
|
return data.data.secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getMount(mountName: string): Promise<MountType> {
|
||||||
|
const request = new Request(appendAPIURL("/v1/sys/internal/ui/mounts/" + mountName), {
|
||||||
|
headers: getHeaders(),
|
||||||
|
});
|
||||||
|
const resp = await fetch(request);
|
||||||
|
await checkResponse(resp);
|
||||||
|
|
||||||
|
const data = (await resp.json()) as { data: MountType };
|
||||||
|
return data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
117
src/main.tsx
117
src/main.tsx
|
@ -25,59 +25,110 @@ import translations from "./translations/index.mjs";
|
||||||
import { PageRouter } from "./pagerouter/PageRouter";
|
import { PageRouter } from "./pagerouter/PageRouter";
|
||||||
import { formatDistance } from "./formatDistance";
|
import { formatDistance } from "./formatDistance";
|
||||||
import { getSealStatus } from "./api/sys/getSealStatus";
|
import { getSealStatus } from "./api/sys/getSealStatus";
|
||||||
import { pageList } from "./allPages";
|
//import { pageList } from "./allPages";
|
||||||
import { pageState } from "./globalPageState";
|
import { pageState } from "./globalPageState";
|
||||||
import { playground } from "./playground";
|
import { playground } from "./playground";
|
||||||
import { reloadNavBar } from "./ui/elements/NavBar";
|
import { NavBar } from "./ui/elements/NavBar";
|
||||||
import { render } from "preact";
|
import { render, Component } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import Router from 'preact-router';
|
||||||
|
import { Home } from "./ui/pages/Home";
|
||||||
|
import { Me } from "./ui/pages/Me";
|
||||||
|
import { Login } from "./ui/pages/Login";
|
||||||
|
import { PasswordGenerator } from "./ui/pages/PwGen";
|
||||||
|
import { SetVaultURL } from "./ui/pages/SetVaultURL";
|
||||||
|
import { Unseal } from "./ui/pages/Unseal";
|
||||||
|
import { SetLanguage } from "./ui/pages/SetLanguage";
|
||||||
|
import { Secrets } from "./ui/pages/Secrets/SecretsHome";
|
||||||
|
import { TOTPView } from "./ui/pages/Secrets/TOTP/TOTPView";
|
||||||
|
import { TOTPNew } from "./ui/pages/Secrets/TOTP/TOTPNew";
|
||||||
|
import { TOTPDelete } from "./ui/pages/Secrets/TOTP/TOTPDelete";
|
||||||
|
import { NewSecretsEngine } from "./ui/pages/Secrets/NewSecretsEngine";
|
||||||
|
import { NewKVEngine } from "./ui/pages/Secrets/NewEngines/NewKVEngine";
|
||||||
|
import { NewTOTPEngine } from "./ui/pages/Secrets/NewEngines/NewTOTPEngine";
|
||||||
|
import { NewTransitEngine } from "./ui/pages/Secrets/NewEngines/NewTransitEngine";
|
||||||
|
import { DeleteSecretsEngine } from "./ui/pages/Secrets/DeleteSecretsEngine";
|
||||||
|
import { KeyValueView } from "./ui/pages/Secrets/KeyValue/KeyValueView";
|
||||||
|
import { KeyValueSecret } from "./ui/pages/Secrets/KeyValue/KeyValueSecret";
|
||||||
|
import { KeyValueSecretEdit } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit";
|
||||||
|
import { KeyValueDelete } from "./ui/pages/Secrets/KeyValue/KeyValueDelete";
|
||||||
|
|
||||||
async function onLoad(): Promise<void> {
|
async function onLoad(): Promise<void> {
|
||||||
|
const Main = () => (
|
||||||
|
<Router>
|
||||||
|
<Home path="/" state={pageState} />
|
||||||
|
<Me path="/me" state={pageState} />
|
||||||
|
<Login path="/login" state={pageState} />
|
||||||
|
<PasswordGenerator path="/pw_gen" />
|
||||||
|
<SetVaultURL path="/set_vault_url" state={pageState} />
|
||||||
|
<Unseal path="/unseal" state={pageState} />
|
||||||
|
<SetLanguage path="/set_language" state={pageState} />
|
||||||
|
|
||||||
|
<Secrets path="/secrets" state={pageState} />
|
||||||
|
<DeleteSecretsEngine path="/secrets/delete_engine/:mount" state={pageState} />
|
||||||
|
|
||||||
|
<NewSecretsEngine path="/secrets/new_secrets_engine" />
|
||||||
|
<NewKVEngine path="/secrets/new_secrets_engine/kv" />
|
||||||
|
<NewTOTPEngine path="/secrets/new_secrets_engine/totp" />
|
||||||
|
<NewTransitEngine path="/secrets/new_secrets_engine/trasit" />
|
||||||
|
|
||||||
|
<TOTPView path="/secrets/totp/list/:mount" state={pageState} />
|
||||||
|
<TOTPNew path="/secrets/totp/new/:mount" state={pageState} />
|
||||||
|
<TOTPDelete path="/secrets/totp/delete/:mount/:item" state={pageState} />
|
||||||
|
|
||||||
|
<KeyValueView path="/secrets/kv/list/:mount+" state={pageState} />
|
||||||
|
<KeyValueSecret path="/secrets/kv/view/:item/:mount+" state={pageState} />
|
||||||
|
<KeyValueSecretEdit path="/secrets/kv/edit/:item/:mount+" state={pageState} />
|
||||||
|
<KeyValueDelete path="/secrets/kv/delete/:item/:mount+" state={pageState} />
|
||||||
|
|
||||||
|
<div default><p>PAGE NOT YET IMPLEMENTED</p></div>
|
||||||
|
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
render(
|
render(
|
||||||
<>
|
<>
|
||||||
<div id="navBarBox" />
|
<NavBar />
|
||||||
<div class="uk-container uk-container-medium uk-align-center">
|
<div class="uk-container uk-container-medium uk-align-center">
|
||||||
<div class="uk-card uk-card-body">
|
<div class="uk-card uk-card-body">
|
||||||
<h3 class="uk-card-title" id="pageTitle" />
|
<Main />
|
||||||
<div id="pageContent" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>,
|
</>,
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageRouter = new PageRouter({
|
//const pageRouter = new PageRouter({
|
||||||
pageList: pageList,
|
// pageList: pageList,
|
||||||
state: pageState,
|
// state: pageState,
|
||||||
pageTitleElement: document.querySelector("#pageTitle"),
|
// pageTitleElement: document.querySelector("#pageTitle"),
|
||||||
pageContentElement: document.querySelector("#pageContent"),
|
// pageContentElement: document.querySelector("#pageContent"),
|
||||||
resetElementContent: !true,
|
// resetElementContent: !true,
|
||||||
onPageChange: async function () {
|
// onPageChange: async function () {
|
||||||
pageState.currentPage = await pageRouter.getCurrentPageID();
|
// pageState.currentPage = await pageRouter.getCurrentPageID();
|
||||||
document.documentElement.dir = pageState.pageDirection;
|
// document.documentElement.dir = pageState.pageDirection;
|
||||||
},
|
// },
|
||||||
});
|
//});
|
||||||
|
//
|
||||||
reloadNavBar(pageRouter);
|
//reloadNavBar(pageRouter);
|
||||||
|
|
||||||
if (process.env.NODE_ENV == "development") {
|
if (process.env.NODE_ENV == "development") {
|
||||||
await playground(pageRouter);
|
// await playground(pageRouter);
|
||||||
}
|
}
|
||||||
|
|
||||||
await pageRouter.changePage(pageState.currentPage);
|
//await pageRouter.changePage(pageState.currentPage);
|
||||||
|
|
||||||
setInterval(async () => {
|
//setInterval(async () => {
|
||||||
if ((await pageRouter.getCurrentPageID()) != "UNSEAL") {
|
// if ((await pageRouter.getCurrentPageID()) != "UNSEAL") {
|
||||||
if (pageState.apiURL.length != 0) {
|
// if (pageState.apiURL.length != 0) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
const sealStatus = await getSealStatus();
|
// const sealStatus = await getSealStatus();
|
||||||
if (sealStatus.sealed) {
|
// if (sealStatus.sealed) {
|
||||||
await pageRouter.changePage("UNSEAL");
|
// await pageRouter.changePage("UNSEAL");
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}, 5000);
|
//}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
|
|
|
@ -5,35 +5,36 @@ import { lookupSelf } from "./api/sys/lookupSelf";
|
||||||
import ClipboardJS from "clipboard";
|
import ClipboardJS from "clipboard";
|
||||||
import UIkit from "uikit";
|
import UIkit from "uikit";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
async function prePageChecksReal(router: PageRouter) {
|
async function prePageChecksReal(state: PageState) {
|
||||||
const state = router.state as PageState;
|
|
||||||
if (state.language.length == 0) {
|
if (state.language.length == 0) {
|
||||||
await router.changePage("SET_LANGUAGE");
|
route("/set_language", true);
|
||||||
throw new Error("Language Not Set");
|
throw new Error("Language Not Set");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.apiURL) {
|
if (!state.apiURL) {
|
||||||
await router.changePage("SET_VAULT_URL");
|
route("/set_vault_url", true);
|
||||||
throw new Error("Vault URL Not Set");
|
throw new Error("Vault URL Not Set");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sealStatus = await getSealStatus();
|
const sealStatus = await getSealStatus();
|
||||||
if (sealStatus.sealed) {
|
if (sealStatus.sealed) {
|
||||||
await router.changePage("UNSEAL");
|
route("/unseal", true);
|
||||||
throw new Error("Vault Sealed");
|
throw new Error("Vault Sealed");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await lookupSelf();
|
await lookupSelf();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await router.changePage("LOGIN");
|
route("/login", true);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prePageChecks(router: PageRouter): Promise<boolean> {
|
export async function prePageChecks(state: PageState): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await prePageChecksReal(router);
|
await prePageChecksReal(state);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("OHNO", e);
|
console.log("OHNO", e);
|
||||||
return false;
|
return false;
|
||||||
|
|
1
src/translations/en.js
vendored
1
src/translations/en.js
vendored
|
@ -35,6 +35,7 @@ module.exports = {
|
||||||
me_copy_token_btn: "Copy Token",
|
me_copy_token_btn: "Copy Token",
|
||||||
me_renew_lease_btn: "Renew Token Lease",
|
me_renew_lease_btn: "Renew Token Lease",
|
||||||
me_change_language_btn: "Change Language",
|
me_change_language_btn: "Change Language",
|
||||||
|
me_set_vault_url_btn: "Set Vault URL",
|
||||||
|
|
||||||
// Home Page
|
// Home Page
|
||||||
home_page_title: "Home",
|
home_page_title: "Home",
|
||||||
|
|
6
src/types/DefaultPageProps.ts
Normal file
6
src/types/DefaultPageProps.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { PageState } from "../state/PageState";
|
||||||
|
|
||||||
|
export type DefaultPageProps = {
|
||||||
|
state: PageState;
|
||||||
|
matches?: {[key: string]: string}
|
||||||
|
};
|
|
@ -1,29 +1,24 @@
|
||||||
import { JSX, render } from "preact";
|
import { JSX, render } from "preact";
|
||||||
import { PageRouter } from "../../pagerouter/PageRouter";
|
import { PageRouter } from "../../pagerouter/PageRouter";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { getCurrentUrl, route } from "preact-router";
|
||||||
|
|
||||||
export type NavBarProps = {
|
|
||||||
router: PageRouter;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function NavBar(props: NavBarProps): JSX.Element {
|
export function NavBar(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<nav class="uk-navbar uk-navbar-container">
|
<nav class="uk-navbar uk-navbar-container">
|
||||||
<div class="uk-navbar-left">
|
<div class="uk-navbar-left">
|
||||||
<ul class="uk-navbar-nav">
|
<ul class="uk-navbar-nav">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a href="/">
|
||||||
onClick={async () => {
|
|
||||||
await props.router.changePage("HOME");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18next.t("home_btn")}
|
{i18next.t("home_btn")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await props.router.goBack();
|
window.history.back()
|
||||||
|
// maybe???
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("back_btn")}
|
{i18next.t("back_btn")}
|
||||||
|
@ -32,7 +27,7 @@ export function NavBar(props: NavBarProps): JSX.Element {
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await props.router.refresh();
|
route(getCurrentUrl(), false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("refresh_btn")}
|
{i18next.t("refresh_btn")}
|
||||||
|
@ -45,7 +40,7 @@ export function NavBar(props: NavBarProps): JSX.Element {
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await props.router.changePage("ME");
|
route("/me", true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("me_btn")}
|
{i18next.t("me_btn")}
|
||||||
|
@ -55,8 +50,4 @@ export function NavBar(props: NavBarProps): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reloadNavBar(router: PageRouter): void {
|
|
||||||
render(<NavBar router={router} />, document.querySelector("#navBarBox"));
|
|
||||||
}
|
|
9
src/ui/elements/PageTitle.tsx
Normal file
9
src/ui/elements/PageTitle.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { JSX } from "preact";
|
||||||
|
|
||||||
|
export type PageTitleProps = {
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PageTitle(props: PageTitleProps): JSX.Element {
|
||||||
|
return <h3 class="uk-card-title" id="pageTitle">{props.title}</h3>;
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { Tile } from "../../elements/Tile";
|
||||||
import { notImplemented, prePageChecks } from "../../../pageUtils";
|
import { notImplemented, prePageChecks } from "../../../pageUtils";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { PageState } from "../../../state/PageState";
|
||||||
|
|
||||||
export class AccessHomePage extends Page {
|
export class AccessHomePage extends Page {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -14,7 +15,7 @@ export class AccessHomePage extends Page {
|
||||||
}
|
}
|
||||||
async render(): Promise<void> {
|
async render(): Promise<void> {
|
||||||
this.router.pageContentElement.innerHTML = "";
|
this.router.pageContentElement.innerHTML = "";
|
||||||
if (!(await prePageChecks(this.router))) return;
|
if (!(await prePageChecks(this.router.state as PageState))) return;
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Grid size={GridSizes.MATCHING_TWO_ROWS}>
|
<Grid size={GridSizes.MATCHING_TWO_ROWS}>
|
||||||
|
|
|
@ -6,22 +6,26 @@ import { TokenInfo } from "../../api/types/token";
|
||||||
import { getCapabilitiesPath } from "../../api/sys/getCapabilities";
|
import { getCapabilitiesPath } from "../../api/sys/getCapabilities";
|
||||||
import { lookupSelf } from "../../api/sys/lookupSelf";
|
import { lookupSelf } from "../../api/sys/lookupSelf";
|
||||||
import { prePageChecks, setErrorText } from "../../pageUtils";
|
import { prePageChecks, setErrorText } from "../../pageUtils";
|
||||||
import { render } from "preact";
|
import { Component, JSX, render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { PageState } from "../../state/PageState";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
|
|
||||||
export class HomePage extends Page {
|
type HomeProps = {
|
||||||
constructor() {
|
state: PageState;
|
||||||
super();
|
}
|
||||||
}
|
|
||||||
async render(): Promise<void> {
|
|
||||||
await this.router.setPageContent("");
|
|
||||||
if (!(await prePageChecks(this.router))) return;
|
|
||||||
|
|
||||||
this.state.baseMount = "";
|
type HomeState = {
|
||||||
this.state.secretPath = [];
|
selfTokenInfo: TokenInfo;
|
||||||
this.state.secretItem = "";
|
authCaps: string[];
|
||||||
this.state.secretVersion = null;
|
policiesCaps: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Home extends Component<HomeProps, HomeState> {
|
||||||
|
async componentDidMount() {
|
||||||
|
if (!(await prePageChecks(this.props.state))) return;
|
||||||
|
|
||||||
let selfTokenInfo: TokenInfo;
|
let selfTokenInfo: TokenInfo;
|
||||||
try {
|
try {
|
||||||
selfTokenInfo = await lookupSelf();
|
selfTokenInfo = await lookupSelf();
|
||||||
|
@ -29,8 +33,9 @@ export class HomePage extends Page {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
setErrorText(error.message);
|
setErrorText(error.message);
|
||||||
if (error.message == "permission denied") {
|
if (error.message == "permission denied") {
|
||||||
this.state.token = "";
|
this.props.state.token = "";
|
||||||
await this.router.changePage("LOGIN");
|
route('/login', true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,61 +43,67 @@ export class HomePage extends Page {
|
||||||
const authCaps = caps["sys/auth"];
|
const authCaps = caps["sys/auth"];
|
||||||
const policiesCaps = caps["sys/policies"];
|
const policiesCaps = caps["sys/policies"];
|
||||||
|
|
||||||
render(
|
this.setState({
|
||||||
<div>
|
selfTokenInfo: selfTokenInfo,
|
||||||
<ul id="textList" class="uk-nav">
|
authCaps: authCaps,
|
||||||
<li>
|
policiesCaps: policiesCaps,
|
||||||
<span>{i18next.t("home_vaulturl_text", { text: this.state.apiURL })}</span>
|
})
|
||||||
</li>
|
}
|
||||||
<li>
|
|
||||||
<a
|
render(): JSX.Element {
|
||||||
onClick={async () => {
|
return this.state.selfTokenInfo && (
|
||||||
await this.router.changePage("PW_GEN");
|
<>
|
||||||
}}
|
<PageTitle title={this.name} />
|
||||||
>
|
<div>
|
||||||
{i18next.t("home_password_generator_btn")}
|
<ul id="textList" class="uk-nav">
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<span>{i18next.t("home_vaulturl_text", { text: this.props.state.apiURL })}</span>
|
||||||
<li>
|
</li>
|
||||||
<span>
|
<li>
|
||||||
{i18next.t("home_your_token_expires_in", {
|
<a href="/pw_gen">
|
||||||
date: new Date(selfTokenInfo.expire_time),
|
{i18next.t("home_password_generator_btn")}
|
||||||
})}
|
</a>
|
||||||
</span>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
</ul>
|
<span>
|
||||||
<Margin>
|
{i18next.t("home_your_token_expires_in", {
|
||||||
<Grid size={GridSizes.MATCHING_TWO_ROWS}>
|
date: new Date(this.state.selfTokenInfo.expire_time),
|
||||||
<Tile
|
})}
|
||||||
title={i18next.t("home_secrets_title")}
|
</span>
|
||||||
description={i18next.t("home_secrets_description")}
|
</li>
|
||||||
icon="file-edit"
|
</ul>
|
||||||
onclick={async () => {
|
<Margin>
|
||||||
await this.router.changePage("SECRETS_HOME");
|
<Grid size={GridSizes.MATCHING_TWO_ROWS}>
|
||||||
}}
|
<Tile
|
||||||
/>
|
title={i18next.t("home_secrets_title")}
|
||||||
<Tile
|
description={i18next.t("home_secrets_description")}
|
||||||
title={i18next.t("home_access_title")}
|
icon="file-edit"
|
||||||
description={i18next.t("home_access_description")}
|
onclick={async () => {
|
||||||
icon="users"
|
route("/secrets");
|
||||||
disabled={!authCaps.includes("read")}
|
}}
|
||||||
onclick={async () => {
|
/>
|
||||||
await this.router.changePage("ACCESS_HOME");
|
<Tile
|
||||||
}}
|
title={i18next.t("home_access_title")}
|
||||||
/>
|
description={i18next.t("home_access_description")}
|
||||||
<Tile
|
icon="users"
|
||||||
title={i18next.t("home_policies_title")}
|
disabled={!this.state.authCaps.includes("read")}
|
||||||
description={i18next.t("home_policies_description")}
|
onclick={async () => {
|
||||||
icon="pencil"
|
route("/access");
|
||||||
disabled={!policiesCaps.includes("read")}
|
}}
|
||||||
onclick={async () => {
|
/>
|
||||||
await this.router.changePage("POLICIES_HOME");
|
<Tile
|
||||||
}}
|
title={i18next.t("home_policies_title")}
|
||||||
/>
|
description={i18next.t("home_policies_description")}
|
||||||
</Grid>
|
icon="pencil"
|
||||||
</Margin>
|
disabled={!this.state.policiesCaps.includes("read")}
|
||||||
</div>,
|
onclick={async () => {
|
||||||
this.router.pageContentElement,
|
route("/policies");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Margin>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,12 @@ import { lookupSelf } from "../../api/sys/lookupSelf";
|
||||||
import { setErrorText } from "../../pageUtils";
|
import { setErrorText } from "../../pageUtils";
|
||||||
import { usernameLogin } from "../../api/auth/usernameLogin";
|
import { usernameLogin } from "../../api/auth/usernameLogin";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
|
import { DefaultPageProps } from "../../types/DefaultPageProps";
|
||||||
|
import { PageState } from "../../state/PageState";
|
||||||
|
|
||||||
export class TokenLoginForm extends Component<{ page: Page }, unknown> {
|
export class TokenLoginForm extends Component<{state: PageState}, unknown> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -36,13 +40,12 @@ export class TokenLoginForm extends Component<{ page: Page }, unknown> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmit(data: FormData): Promise<void> {
|
async onSubmit(data: FormData): Promise<void> {
|
||||||
const page = this.props.page;
|
|
||||||
const token = data.get("token");
|
const token = data.get("token");
|
||||||
page.state.token = token as string;
|
this.props.state.token = token as string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await lookupSelf();
|
await lookupSelf();
|
||||||
await page.router.changePage("HOME");
|
route("/");
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
document.querySelector("#tokenInput").classList.add("uk-form-danger");
|
document.querySelector("#tokenInput").classList.add("uk-form-danger");
|
||||||
|
@ -55,7 +58,7 @@ export class TokenLoginForm extends Component<{ page: Page }, unknown> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UsernameLoginForm extends Component<{ page: Page }, unknown> {
|
export class UsernameLoginForm extends Component<{state: PageState}, unknown> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -93,15 +96,13 @@ export class UsernameLoginForm extends Component<{ page: Page }, unknown> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmit(data: FormData): Promise<void> {
|
async onSubmit(data: FormData): Promise<void> {
|
||||||
const page = this.props.page;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await usernameLogin(
|
const res = await usernameLogin(
|
||||||
data.get("username") as string,
|
data.get("username") as string,
|
||||||
data.get("password") as string,
|
data.get("password") as string,
|
||||||
);
|
);
|
||||||
page.state.token = res;
|
this.props.state.token = res;
|
||||||
await page.router.changePage("HOME");
|
route("/");
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
document.querySelector("#usernameInput").classList.add("uk-form-danger");
|
document.querySelector("#usernameInput").classList.add("uk-form-danger");
|
||||||
|
@ -111,32 +112,31 @@ export class UsernameLoginForm extends Component<{ page: Page }, unknown> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoginPage extends Page {
|
export class Login extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
render(): JSX.Element {
|
||||||
super();
|
return (
|
||||||
}
|
<>
|
||||||
async render(): Promise<void> {
|
<PageTitle title={this.name} />
|
||||||
render(
|
<div>
|
||||||
<div>
|
<ul class="uk-subnav uk-subnav-pill" uk-switcher=".switcher-container">
|
||||||
<ul class="uk-subnav uk-subnav-pill" uk-switcher=".switcher-container">
|
<li>
|
||||||
<li>
|
<a>{i18next.t("log_in_with_token")}</a>
|
||||||
<a>{i18next.t("log_in_with_token")}</a>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a>{i18next.t("log_in_with_username")}</a>
|
||||||
<a>{i18next.t("log_in_with_username")}</a>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
<p id="errorText" class="uk-text-danger" />
|
||||||
<p id="errorText" class="uk-text-danger" />
|
<ul class="uk-switcher uk-margin switcher-container">
|
||||||
<ul class="uk-switcher uk-margin switcher-container">
|
<li>
|
||||||
<li>
|
<TokenLoginForm state={this.props.state} />
|
||||||
<TokenLoginForm page={this} />
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<UsernameLoginForm state={this.props.state} />
|
||||||
<UsernameLoginForm page={this} />
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>,
|
</>
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { Component, JSX, createRef, render } from "preact";
|
import { Component, JSX, createRef } from "preact";
|
||||||
import { Page } from "../../types/Page";
|
|
||||||
import { addClipboardNotifications, prePageChecks, setErrorText } from "../../pageUtils";
|
import { addClipboardNotifications, prePageChecks, setErrorText } from "../../pageUtils";
|
||||||
import { getCapsPath } from "../../api/sys/getCapabilities";
|
import { getCapsPath } from "../../api/sys/getCapabilities";
|
||||||
import { renewSelf } from "../../api/sys/renewSelf";
|
import { renewSelf } from "../../api/sys/renewSelf";
|
||||||
import { sealVault } from "../../api/sys/sealVault";
|
import { sealVault } from "../../api/sys/sealVault";
|
||||||
import ClipboardJS from "clipboard";
|
import ClipboardJS from "clipboard";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../types/DefaultPageProps";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
|
|
||||||
export class CopyLink extends Component<{ text: string; data: string }, unknown> {
|
export class CopyLink extends Component<{ text: string; data: string }, unknown> {
|
||||||
linkRef = createRef();
|
linkRef = createRef();
|
||||||
|
@ -24,14 +26,23 @@ export class CopyLink extends Component<{ text: string; data: string }, unknown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MePage extends Page {
|
type MeState = {
|
||||||
|
loaded: boolean;
|
||||||
|
canSealVault: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Me extends Component<DefaultPageProps, MeState> {
|
||||||
|
defaultState = { loaded: false, canSealVault: false }
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
this.state = this.defaultState;
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(): Promise<void> {
|
componentWillUnmount() {
|
||||||
if (!(await prePageChecks(this.router))) return;
|
this.setState(this.defaultState);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
let canSealVault = false;
|
let canSealVault = false;
|
||||||
try {
|
try {
|
||||||
const caps = await getCapsPath("sys/seal");
|
const caps = await getCapsPath("sys/seal");
|
||||||
|
@ -40,59 +51,69 @@ export class MePage extends Page {
|
||||||
canSealVault = false;
|
canSealVault = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
this.setState({
|
||||||
<ul class="uk-nav">
|
loaded: true,
|
||||||
<li>
|
canSealVault: canSealVault,
|
||||||
<a
|
});
|
||||||
onClick={async () => {
|
}
|
||||||
this.state.token = "";
|
|
||||||
await this.router.changePage("HOME");
|
render(): JSX.Element {
|
||||||
}}
|
return this.state.loaded && (
|
||||||
>
|
<>
|
||||||
{i18next.t("me_log_out_btn")}
|
<PageTitle title={this.name} />
|
||||||
</a>
|
<ul class="uk-nav">
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<CopyLink text={i18next.t("me_copy_token_btn")} data={this.state.token} />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await renewSelf();
|
|
||||||
await this.router.changePage("HOME");
|
|
||||||
} catch (e: unknown) {
|
|
||||||
const error = e as Error;
|
|
||||||
setErrorText(error.message);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18next.t("me_renew_lease_btn")}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{canSealVault && (
|
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await sealVault();
|
this.props.state.token = "";
|
||||||
await this.router.changePage("UNSEAL");
|
route("/");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("me_seal_vault_btn")}
|
{i18next.t("me_log_out_btn")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
<li>
|
||||||
<li>
|
<CopyLink text={i18next.t("me_copy_token_btn")} data={this.props.state.token} />
|
||||||
<a
|
</li>
|
||||||
onClick={async () => {
|
<li>
|
||||||
await this.router.changePage("SET_LANGUAGE");
|
<a
|
||||||
}}
|
onClick={async () => {
|
||||||
>
|
try {
|
||||||
{i18next.t("me_change_language_btn")}
|
await renewSelf();
|
||||||
</a>
|
route("/");
|
||||||
</li>
|
} catch (e: unknown) {
|
||||||
</ul>,
|
const error = e as Error;
|
||||||
this.router.pageContentElement,
|
setErrorText(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("me_renew_lease_btn")}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{this.state.canSealVault && (
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
onClick={async () => {
|
||||||
|
await sealVault();
|
||||||
|
route("/unseal", true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("me_seal_vault_btn")}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
<li>
|
||||||
|
<a href="/set_language">
|
||||||
|
{i18next.t("me_change_language_btn")}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/set_vault_url">
|
||||||
|
{i18next.t("me_set_vault_url_btn")}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { getPolicies } from "../../../api/sys/policies/getPolicies";
|
||||||
import { prePageChecks } from "../../../pageUtils";
|
import { prePageChecks } from "../../../pageUtils";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { PageState } from "../../../state/PageState";
|
||||||
|
|
||||||
export class PoliciesHomePage extends Page {
|
export class PoliciesHomePage extends Page {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -14,7 +15,7 @@ export class PoliciesHomePage extends Page {
|
||||||
}
|
}
|
||||||
async render(): Promise<void> {
|
async render(): Promise<void> {
|
||||||
await this.router.setPageContent("");
|
await this.router.setPageContent("");
|
||||||
if (!(await prePageChecks(this.router))) return;
|
if (!(await prePageChecks(this.router.state as PageState))) return;
|
||||||
|
|
||||||
let policies = await getPolicies();
|
let policies = await getPolicies();
|
||||||
policies = policies.sort();
|
policies = policies.sort();
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { getPolicy } from "../../../api/sys/policies/getPolicy";
|
||||||
import { prePageChecks } from "../../../pageUtils";
|
import { prePageChecks } from "../../../pageUtils";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { PageState } from "../../../state/PageState";
|
||||||
|
|
||||||
export class PolicyViewPage extends Page {
|
export class PolicyViewPage extends Page {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -15,7 +16,7 @@ export class PolicyViewPage extends Page {
|
||||||
}
|
}
|
||||||
async render(): Promise<void> {
|
async render(): Promise<void> {
|
||||||
await this.router.setPageContent("");
|
await this.router.setPageContent("");
|
||||||
if (!(await prePageChecks(this.router))) return;
|
if (!(await prePageChecks(this.router.state as PageState))) return;
|
||||||
|
|
||||||
const policy = await getPolicy(this.state.policyItem);
|
const policy = await getPolicy(this.state.policyItem);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Component, JSX, createRef, render } from "preact";
|
import { Component, JSX, createRef, render } from "preact";
|
||||||
import { CopyableInputBox } from "../elements/CopyableInputBox";
|
import { CopyableInputBox } from "../elements/CopyableInputBox";
|
||||||
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
import { Form } from "../elements/Form";
|
import { Form } from "../elements/Form";
|
||||||
import { Margin } from "../elements/Margin";
|
import { Margin } from "../elements/Margin";
|
||||||
import { Page } from "../../types/Page";
|
import { Page } from "../../types/Page";
|
||||||
|
@ -51,7 +52,7 @@ type PasswordGeneratorState = {
|
||||||
alphabet: string;
|
alphabet: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PasswordGenerator extends Component<unknown, PasswordGeneratorState> {
|
export class PasswordGenerator extends Component<{}, PasswordGeneratorState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -89,65 +90,54 @@ export class PasswordGenerator extends Component<unknown, PasswordGeneratorState
|
||||||
// createRef
|
// createRef
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={() => this.onSubmit()}>
|
<>
|
||||||
<Margin>
|
<PageTitle title={i18next.t("password_generator_title")} />
|
||||||
<h4>{this.getPasswordLengthText(this.state.length)}</h4>
|
<Form onSubmit={() => this.onSubmit()}>
|
||||||
</Margin>
|
<Margin>
|
||||||
<Margin>
|
<h4>{this.getPasswordLengthText(this.state.length)}</h4>
|
||||||
<input
|
</Margin>
|
||||||
class="uk-range uk-width-1-2"
|
<Margin>
|
||||||
name="length"
|
<input
|
||||||
type="range"
|
class="uk-range uk-width-1-2"
|
||||||
value={this.state.length}
|
name="length"
|
||||||
max={passwordLengthMax.toString()}
|
type="range"
|
||||||
min={passwordLengthMin.toString()}
|
value={this.state.length}
|
||||||
ref={this.passwordLengthSlider}
|
max={passwordLengthMax.toString()}
|
||||||
onInput={() => {
|
min={passwordLengthMin.toString()}
|
||||||
this.updateLength();
|
ref={this.passwordLengthSlider}
|
||||||
}}
|
onInput={() => {
|
||||||
/>
|
this.updateLength();
|
||||||
</Margin>
|
}}
|
||||||
<Margin>
|
/>
|
||||||
<select
|
</Margin>
|
||||||
class="uk-select uk-width-1-2"
|
<Margin>
|
||||||
ref={this.alphabetSelector}
|
<select
|
||||||
onInput={() => {
|
class="uk-select uk-width-1-2"
|
||||||
this.updateAlphabet();
|
ref={this.alphabetSelector}
|
||||||
}}
|
onInput={() => {
|
||||||
>
|
this.updateAlphabet();
|
||||||
<option value={alphabets.SECURE}>a-z a-Z 0-9 specials</option>
|
}}
|
||||||
<option value={alphabets.SMOL}>a-z 0-9</option>
|
>
|
||||||
<option value={alphabets.HEX}>A-F 1-9</option>
|
<option value={alphabets.SECURE}>a-z a-Z 0-9 specials</option>
|
||||||
</select>
|
<option value={alphabets.SMOL}>a-z 0-9</option>
|
||||||
</Margin>
|
<option value={alphabets.HEX}>A-F 1-9</option>
|
||||||
|
</select>
|
||||||
|
</Margin>
|
||||||
|
|
||||||
<CopyableInputBox
|
<CopyableInputBox
|
||||||
text={genPassword({
|
text={genPassword({
|
||||||
length: this.state.length,
|
length: this.state.length,
|
||||||
alphabet: this.state.alphabet,
|
alphabet: this.state.alphabet,
|
||||||
})}
|
})}
|
||||||
copyable
|
copyable
|
||||||
/>
|
/>
|
||||||
<Margin>
|
<Margin>
|
||||||
<button class="uk-button uk-button-primary uk-margin-bottom" type="submit">
|
<button class="uk-button uk-button-primary uk-margin-bottom" type="submit">
|
||||||
{i18next.t("password_generator_generate_btn")}
|
{i18next.t("password_generator_generate_btn")}
|
||||||
</button>
|
</button>
|
||||||
</Margin>
|
</Margin>
|
||||||
</Form>
|
</Form>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PwGenPage extends Page {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async render(): Promise<void> {
|
|
||||||
render(<PasswordGenerator />, this.router.pageContentElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("password_generator_title");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,47 +2,45 @@ import { Form } from "../../elements/Form";
|
||||||
import { MarginInline } from "../../elements/MarginInline";
|
import { MarginInline } from "../../elements/MarginInline";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { deleteMount } from "../../../api/sys/deleteMount";
|
import { deleteMount } from "../../../api/sys/deleteMount";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
import { setErrorText } from "../../../pageUtils";
|
import { setErrorText } from "../../../pageUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../../types/DefaultPageProps";
|
||||||
|
import { PageTitle } from "../../elements/PageTitle";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
export class DeleteSecretsEnginePage extends Page {
|
export class DeleteSecretsEngine extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async render(): Promise<void> {
|
return (
|
||||||
render(
|
<>
|
||||||
<Form
|
<PageTitle title={i18next.t("delete_secrets_engine_title", { mount: this.props.matches["mount"] })}/>
|
||||||
onSubmit={async () => {
|
<Form
|
||||||
await this.onSubmit();
|
onSubmit={async () => {
|
||||||
}}
|
await this.onSubmit();
|
||||||
>
|
}}
|
||||||
<p>{i18next.t("delete_secrets_engine_message")}</p>
|
>
|
||||||
|
<p>{i18next.t("delete_secrets_engine_message")}</p>
|
||||||
|
|
||||||
<p class="uk-text-danger" id="errorText" />
|
<p class="uk-text-danger" id="errorText" />
|
||||||
|
|
||||||
<MarginInline>
|
<MarginInline>
|
||||||
<button class="uk-button uk-button-danger" type="submit">
|
<button class="uk-button uk-button-danger" type="submit">
|
||||||
{i18next.t("delete_secrets_engine_delete_btn")}
|
{i18next.t("delete_secrets_engine_delete_btn")}
|
||||||
</button>
|
</button>
|
||||||
</MarginInline>
|
</MarginInline>
|
||||||
</Form>,
|
</Form>
|
||||||
this.router.pageContentElement,
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmit(): Promise<void> {
|
async onSubmit(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await deleteMount(this.state.baseMount);
|
await deleteMount(this.props.matches["mount"]);
|
||||||
await this.router.changePage("SECRETS_HOME");
|
route("/secrets");
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
setErrorText(error.message);
|
setErrorText(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("delete_secrets_engine_title", { mount: this.state.baseMount });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,40 @@
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { deleteSecret } from "../../../../api/kv/deleteSecret";
|
import { deleteSecret } from "../../../../api/kv/deleteSecret";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||||
|
import { splitKVMount } from "./splitKVMount";
|
||||||
|
|
||||||
|
export class KeyValueDelete extends Component<DefaultPageProps> {
|
||||||
|
render() {
|
||||||
|
const mount = this.props.matches["mount"];
|
||||||
|
const mountSplit = splitKVMount(mount);
|
||||||
|
const baseMount = mountSplit.baseMount;
|
||||||
|
const secretPath = mountSplit.restOfMount;
|
||||||
|
const item = this.props.matches["item"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretTitleElement type="kv" item={this.props.matches["item"]} mount={mount} suffix={i18next.t("kv_sec_edit_suffix")} />
|
||||||
|
<div>
|
||||||
|
<h5>{i18next.t("kv_delete_text")}</h5>
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-danger"
|
||||||
|
onClick={async () => {
|
||||||
|
await deleteSecret(
|
||||||
|
baseMount,
|
||||||
|
secretPath.map((e) => e + "/"),
|
||||||
|
item,
|
||||||
|
);
|
||||||
|
window.history.back();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("kv_delete_btn")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
|
||||||
export class KeyValueDeletePage extends Page {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
async goBack(): Promise<void> {
|
|
||||||
if (this.state.secretVersion != null) {
|
|
||||||
this.state.secretVersion = null;
|
|
||||||
await this.router.changePage("KEY_VALUE_SECRET");
|
|
||||||
} else {
|
|
||||||
this.state.secretItem = "";
|
|
||||||
await this.router.changePage("KEY_VALUE_VIEW");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async render(): Promise<void> {
|
|
||||||
render(
|
|
||||||
<div>
|
|
||||||
<h5>{i18next.t("kv_delete_text")}</h5>
|
|
||||||
<button
|
|
||||||
class="uk-button uk-button-danger"
|
|
||||||
onClick={async () => {
|
|
||||||
await deleteSecret(
|
|
||||||
this.state.baseMount,
|
|
||||||
this.state.secretMountType,
|
|
||||||
this.state.secretPath,
|
|
||||||
this.state.secretItem,
|
|
||||||
this.state.secretVersion,
|
|
||||||
);
|
|
||||||
await this.goBack();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18next.t("kv_delete_btn")}
|
|
||||||
</button>
|
|
||||||
</div>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
|
||||||
render(
|
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("kv_delete_suffix")} />,
|
|
||||||
this.router.pageTitleElement,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("kv_delete_title");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,6 @@ export class KeyValueNewPage extends Page {
|
||||||
try {
|
try {
|
||||||
await createOrUpdateSecret(
|
await createOrUpdateSecret(
|
||||||
this.state.baseMount,
|
this.state.baseMount,
|
||||||
this.state.secretMountType,
|
|
||||||
this.state.secretPath,
|
this.state.secretPath,
|
||||||
path,
|
path,
|
||||||
keyData,
|
keyData,
|
||||||
|
@ -61,12 +60,12 @@ export class KeyValueNewPage extends Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(
|
// render(
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("kv_new_suffix")} />,
|
// <SecretTitleElement page={this} suffix={i18next.t("kv_new_suffix")} />,
|
||||||
this.router.pageTitleElement,
|
// this.router.pageTitleElement,
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("kv_new_title");
|
return i18next.t("kv_new_title");
|
||||||
|
|
|
@ -9,6 +9,10 @@ import { getSecret } from "../../../../api/kv/getSecret";
|
||||||
import { sortedObjectMap } from "../../../../utils";
|
import { sortedObjectMap } from "../../../../utils";
|
||||||
import { undeleteSecret } from "../../../../api/kv/undeleteSecret";
|
import { undeleteSecret } from "../../../../api/kv/undeleteSecret";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { splitKVMount } from "./splitKVMount";
|
||||||
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||||
|
import { getMount } from "../../../../api/sys/getMounts";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
export type KVSecretViewProps = {
|
export type KVSecretViewProps = {
|
||||||
kvData: Record<string, unknown>;
|
kvData: Record<string, unknown>;
|
||||||
|
@ -41,119 +45,92 @@ export class KVSecretVew extends Component<KVSecretViewProps, unknown> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KeyValueSecretPage extends Page {
|
type KeyValueSecretState = {
|
||||||
constructor() {
|
baseMount: string;
|
||||||
super();
|
secretPath: string[];
|
||||||
}
|
secretItem: string;
|
||||||
async goBack(): Promise<void> {
|
caps: string[];
|
||||||
if (this.state.secretVersion != null) {
|
secretInfo: Record<string, unknown>;
|
||||||
this.state.secretVersion = null;
|
}
|
||||||
await this.router.changePage("KEY_VALUE_VERSIONS");
|
|
||||||
} else {
|
export class KeyValueSecret extends Component<DefaultPageProps, KeyValueSecretState> {
|
||||||
this.state.secretItem = "";
|
async componentDidMount() {
|
||||||
await this.router.changePage("KEY_VALUE_VIEW");
|
const mount = this.props.matches["mount"];
|
||||||
}
|
const mountSplit = splitKVMount(mount);
|
||||||
}
|
const baseMount = mountSplit.baseMount;
|
||||||
async render(): Promise<void> {
|
const secretPath = mountSplit.restOfMount;
|
||||||
|
const secretItem = this.props.matches["item"];
|
||||||
|
|
||||||
const caps = (
|
const caps = (
|
||||||
await getCapabilities(this.state.baseMount, this.state.secretPath, this.state.secretItem)
|
await getCapabilities(baseMount, secretPath, secretItem)
|
||||||
).capabilities;
|
).capabilities;
|
||||||
|
|
||||||
|
let 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(
|
const secretInfo = await getSecret(
|
||||||
this.state.baseMount,
|
baseMount,
|
||||||
this.state.secretMountType,
|
secretPathAPI,
|
||||||
this.state.secretPath,
|
secretItem,
|
||||||
this.state.secretItem,
|
|
||||||
this.state.secretVersion,
|
|
||||||
);
|
);
|
||||||
|
this.setState({
|
||||||
|
baseMount,
|
||||||
|
secretPath,
|
||||||
|
secretItem,
|
||||||
|
caps,
|
||||||
|
secretInfo
|
||||||
|
})
|
||||||
|
|
||||||
// On kv-v2, secrets can be deleted temporarily with the ability to restore
|
}
|
||||||
// Do not show any buttons when the secret is deleted.
|
render() {
|
||||||
const secretIsDeleted = secretInfo == null && this.state.secretMountType == "kv-v2";
|
if (!this.state.baseMount) return;
|
||||||
|
|
||||||
render(
|
// TODO: maybe add some sort of version viewing
|
||||||
<div>
|
// no idea what gonna do for that
|
||||||
<p id="buttonsBlock">
|
|
||||||
{
|
return (
|
||||||
// Delete Button
|
<>
|
||||||
!secretIsDeleted && caps.includes("delete") && (
|
<SecretTitleElement type="kv" item={this.props.matches["item"]} mount={this.props.matches["mount"]} suffix={i18next.t("kv_sec_edit_suffix")} />
|
||||||
|
<div>
|
||||||
|
<p id="buttonsBlock">
|
||||||
|
{
|
||||||
|
// Delete Button
|
||||||
|
this.state.caps.includes("delete") && (
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-danger"
|
||||||
|
onClick={async () => {
|
||||||
|
route("/secrets/kv/delete/" + this.state.secretItem + "/" + this.state.baseMount + "/" + this.state.secretPath.join("/"))
|
||||||
|
//await this.router.changePage("KEY_VALUE_DELETE");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("kv_secret_delete_btn")}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{this.state.caps.includes("update") && (
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-danger"
|
class="uk-button uk-button-primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await this.router.changePage("KEY_VALUE_DELETE");
|
route("/secrets/kv/edit/" + this.state.secretItem + "/" + this.state.baseMount + "/" + this.state.secretPath.join("/"))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{((): string => {
|
{i18next.t("kv_secret_edit_btn")}
|
||||||
// Delete Secret on kv-v1
|
|
||||||
let deleteButtonText = i18next.t("kv_secret_delete_btn");
|
|
||||||
if (this.state.secretMountType == "kv-v2" && this.state.secretVersion == null) {
|
|
||||||
// Delete All
|
|
||||||
deleteButtonText = i18next.t("kv_secret_delete_all_btn");
|
|
||||||
} else if (
|
|
||||||
this.state.secretMountType == "kv-v2" &&
|
|
||||||
this.state.secretVersion != null
|
|
||||||
) {
|
|
||||||
// Delete Version X
|
|
||||||
deleteButtonText = i18next.t("kv_secret_delete_version_btn", {
|
|
||||||
version: this.state.secretVersion,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return deleteButtonText;
|
|
||||||
})()}
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)}
|
||||||
}
|
</p>
|
||||||
{!secretIsDeleted && caps.includes("update") && this.state.secretVersion == null && (
|
|
||||||
<button
|
|
||||||
class="uk-button uk-button-primary"
|
|
||||||
onClick={async () => {
|
|
||||||
await this.router.changePage("KEY_VALUE_SECRET_EDIT");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18next.t("kv_secret_edit_btn")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{!secretIsDeleted && this.state.secretMountType == "kv-v2" && (
|
|
||||||
<button
|
|
||||||
class="uk-button uk-button-secondary"
|
|
||||||
onClick={async () => {
|
|
||||||
await this.router.changePage("KEY_VALUE_VERSIONS");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18next.t("kv_secret_versions_btn")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{!secretIsDeleted && <KVSecretVew kvData={secretInfo} />}
|
{<KVSecretVew kvData={this.state.secretInfo} />}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
|
||||||
{secretIsDeleted && (
|
|
||||||
<>
|
|
||||||
<p>{i18next.t("kv_secret_deleted_text")}</p>
|
|
||||||
<button
|
|
||||||
class="uk-button uk-button-primary"
|
|
||||||
onClick={async () => {
|
|
||||||
await undeleteSecret(
|
|
||||||
this.state.baseMount,
|
|
||||||
this.state.secretPath,
|
|
||||||
this.state.secretItem,
|
|
||||||
this.state.secretVersion,
|
|
||||||
);
|
|
||||||
await this.router.refresh();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18next.t("kv_secret_restore_btn")}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
// render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("kv_secret_title");
|
return i18next.t("kv_secret_title");
|
||||||
|
|
|
@ -7,21 +7,26 @@ import { getSecret } from "../../../../api/kv/getSecret";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import { sortedObjectMap, verifyJSONString } from "../../../../utils";
|
import { sortedObjectMap, verifyJSONString } from "../../../../utils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||||
|
import { splitKVMount } from "./splitKVMount";
|
||||||
|
import { route } from "preact-router";
|
||||||
//import { highlightElement } from "prismjs";
|
//import { highlightElement } from "prismjs";
|
||||||
|
|
||||||
export type KVEditProps = {
|
export type KVEditProps = {
|
||||||
page: Page;
|
baseMount: string;
|
||||||
|
secretPath: string[];
|
||||||
|
secretItem: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type KVEditState =
|
type KVEditState =
|
||||||
| {
|
| {
|
||||||
dataLoaded: false;
|
dataLoaded: false;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
dataLoaded: true;
|
dataLoaded: true;
|
||||||
kvData: Record<string, unknown>;
|
kvData: Record<string, unknown>;
|
||||||
code: string;
|
code: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class KVEditor extends Component<KVEditProps, KVEditState> {
|
export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -41,13 +46,12 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
await createOrUpdateSecret(
|
await createOrUpdateSecret(
|
||||||
this.props.page.state.baseMount,
|
this.props.baseMount,
|
||||||
this.props.page.state.secretMountType,
|
this.props.secretPath.map((e) => e + "/"),
|
||||||
this.props.page.state.secretPath,
|
this.props.secretItem,
|
||||||
this.props.page.state.secretItem,
|
|
||||||
JSON.parse(editorContent),
|
JSON.parse(editorContent),
|
||||||
);
|
);
|
||||||
await this.props.page.router.changePage("KEY_VALUE_SECRET");
|
window.history.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
onCodeUpdate(code: string): void {
|
onCodeUpdate(code: string): void {
|
||||||
|
@ -58,10 +62,9 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
|
|
||||||
loadData(): void {
|
loadData(): void {
|
||||||
void getSecret(
|
void getSecret(
|
||||||
this.props.page.state.baseMount,
|
this.props.baseMount,
|
||||||
this.props.page.state.secretMountType,
|
this.props.secretPath.map((e) => e + "/"),
|
||||||
this.props.page.state.secretPath,
|
this.props.secretItem,
|
||||||
this.props.page.state.secretItem,
|
|
||||||
).then((kvData) => {
|
).then((kvData) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
dataLoaded: true,
|
dataLoaded: true,
|
||||||
|
@ -104,24 +107,29 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KeyValueSecretEditPage extends Page {
|
export class KeyValueSecretEdit extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
const mount = this.props.matches["mount"];
|
||||||
}
|
const mountSplit = splitKVMount(mount);
|
||||||
async goBack(): Promise<void> {
|
const baseMount = mountSplit.baseMount;
|
||||||
await this.router.changePage("KEY_VALUE_SECRET");
|
const restOfMount = mountSplit.restOfMount;
|
||||||
}
|
const item = this.props.matches["item"];
|
||||||
async render(): Promise<void> {
|
|
||||||
render(<KVEditor page={this} />, this.router.pageContentElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
return (
|
||||||
render(
|
<>
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("kv_sec_edit_suffix")} />,
|
<SecretTitleElement type="kv" mount={mount} item={this.props.matches["item"]} suffix={i18next.t("kv_sec_edit_suffix")} />
|
||||||
this.router.pageTitleElement,
|
<KVEditor baseMount={baseMount} secretPath={restOfMount} secretItem={item} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//async renderPageTitle(): Promise<void> {
|
||||||
|
// render(
|
||||||
|
// <SecretTitleElement page={this} suffix={i18next.t("kv_sec_edit_suffix")} />,
|
||||||
|
// this.router.pageTitleElement,
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("kv_sec_edit_title");
|
return i18next.t("kv_sec_edit_title");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
import { Page } from "../../../../types/Page";
|
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
|
||||||
import { getSecretMetadata } from "../../../../api/kv/getSecretMetadata";
|
|
||||||
import { objectToMap } from "../../../../utils";
|
|
||||||
import { render } from "preact";
|
|
||||||
import i18next from "i18next";
|
|
||||||
|
|
||||||
export class KeyValueVersionsPage extends Page {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
async goBack(): Promise<void> {
|
|
||||||
if (this.state.secretVersion != null) {
|
|
||||||
this.state.secretVersion = null;
|
|
||||||
}
|
|
||||||
await this.router.changePage("KEY_VALUE_SECRET");
|
|
||||||
}
|
|
||||||
async render(): Promise<void> {
|
|
||||||
const metadata = await getSecretMetadata(
|
|
||||||
this.state.baseMount,
|
|
||||||
this.state.secretPath,
|
|
||||||
this.state.secretItem,
|
|
||||||
);
|
|
||||||
const versions = Array.from(objectToMap(metadata.versions).keys());
|
|
||||||
|
|
||||||
render(
|
|
||||||
<ul class="uk-nav uk-nav-default">
|
|
||||||
{versions.map((ver) => (
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
onClick={async () => {
|
|
||||||
this.state.secretVersion = ver;
|
|
||||||
await this.router.changePage("KEY_VALUE_SECRET");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{`v${ver}`}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
|
||||||
render(
|
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("kv_sec_versions_suffix")} />,
|
|
||||||
this.router.pageTitleElement,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("kv_sec_versions_title");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,15 +2,17 @@ import { Component, createRef, JSX, render } from "preact";
|
||||||
import { DoesNotExistError } from "../../../../types/internalErrors";
|
import { DoesNotExistError } from "../../../../types/internalErrors";
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { getCapabilitiesPath } from "../../../../api/sys/getCapabilities";
|
import { CapabilitiesType, getCapabilitiesPath } from "../../../../api/sys/getCapabilities";
|
||||||
import { getSecrets } from "../../../../api/kv/getSecrets";
|
import { getSecrets } from "../../../../api/kv/getSecrets";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { splitKVMount } from "./splitKVMount";
|
||||||
|
|
||||||
export type KVKeysListProps = {
|
|
||||||
page: Page;
|
export type KVKeysListProps = DefaultPageProps & {
|
||||||
baseMount: string;
|
baseMount: string;
|
||||||
secretMountType: string;
|
|
||||||
secretPath: string[];
|
secretPath: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,17 +22,15 @@ type KVKeysListState = {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SecretsList(secrets: string[], page: Page): JSX.Element[] {
|
function SecretsList(baseMount: string, restOfMount: string[], secrets: string[]): JSX.Element[] {
|
||||||
return secrets.map((secret) => (
|
return secrets.map((secret) => (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (secret.endsWith("/")) {
|
if (secret.endsWith("/")) {
|
||||||
page.state.pushSecretPath(secret);
|
route("/secrets/kv/list/" + baseMount + "/" + restOfMount.join("/") + "/" + secret)
|
||||||
await page.router.changePage("KEY_VALUE_VIEW");
|
|
||||||
} else {
|
} else {
|
||||||
page.state.secretItem = secret;
|
route("/secrets/kv/view/" + secret + "/" + baseMount + "/" + restOfMount.join("/"))
|
||||||
await page.router.changePage("KEY_VALUE_SECRET");
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -51,12 +51,10 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData(): Promise<void> {
|
async loadData(): Promise<void> {
|
||||||
const page = this.props.page;
|
|
||||||
try {
|
try {
|
||||||
const keys = await getSecrets(
|
const keys = await getSecrets(
|
||||||
this.props.baseMount,
|
this.props.baseMount,
|
||||||
this.props.secretMountType,
|
this.props.secretPath.map((e) => e + "/"),
|
||||||
this.props.secretPath,
|
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
dataLoaded: true,
|
dataLoaded: true,
|
||||||
|
@ -68,12 +66,13 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
|
||||||
if (error == DoesNotExistError) {
|
if (error == DoesNotExistError) {
|
||||||
// getSecrets also 404's on no keys so dont go all the way back.
|
// getSecrets also 404's on no keys so dont go all the way back.
|
||||||
if (this.props.secretPath.length != 0) {
|
if (this.props.secretPath.length != 0) {
|
||||||
await page.goBack();
|
window.history.back();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setErrorText(error.message);
|
setErrorText(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
dataLoaded: true,
|
dataLoaded: true,
|
||||||
keys: null,
|
keys: null,
|
||||||
|
@ -84,7 +83,6 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
|
||||||
componentDidUpdate(prevProps: KVKeysListProps): void {
|
componentDidUpdate(prevProps: KVKeysListProps): void {
|
||||||
if (
|
if (
|
||||||
prevProps.baseMount !== this.props.baseMount ||
|
prevProps.baseMount !== this.props.baseMount ||
|
||||||
prevProps.secretMountType !== this.props.secretMountType ||
|
|
||||||
prevProps.secretPath !== this.props.secretPath
|
prevProps.secretPath !== this.props.secretPath
|
||||||
) {
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -131,7 +129,7 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
|
||||||
if (this.state.searchQuery.length > 0) {
|
if (this.state.searchQuery.length > 0) {
|
||||||
secrets = secrets.filter((secret) => secret.includes(this.state.searchQuery));
|
secrets = secrets.filter((secret) => secret.includes(this.state.searchQuery));
|
||||||
}
|
}
|
||||||
return SecretsList(secrets, this.props.page);
|
return SecretsList(this.props.baseMount, this.props.secretPath, secrets);
|
||||||
})()}
|
})()}
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
|
@ -139,65 +137,66 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KeyValueViewPage extends Page {
|
type KeyValueViewState = {
|
||||||
constructor() {
|
pathCaps: string[];
|
||||||
super();
|
mountCaps: string[];
|
||||||
}
|
}
|
||||||
async goBack(): Promise<void> {
|
|
||||||
if (this.state.secretPath.length != 0) {
|
|
||||||
this.state.popSecretPath();
|
|
||||||
await this.router.changePage("KEY_VALUE_VIEW");
|
|
||||||
} else {
|
|
||||||
await this.router.changePage("SECRETS_HOME");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async render(): Promise<void> {
|
|
||||||
const mountsPath = "/sys/mounts/" + this.state.baseMount;
|
|
||||||
const currentPath = this.state.baseMount + this.state.secretPath.join("/");
|
|
||||||
const caps = await getCapabilitiesPath([mountsPath, currentPath]);
|
|
||||||
const mountCaps = caps[mountsPath];
|
|
||||||
const pathCaps = caps[currentPath];
|
|
||||||
|
|
||||||
render(
|
export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState> {
|
||||||
|
async componentDidMount() {
|
||||||
|
const mount = this.props.matches["mount"];
|
||||||
|
const mountSplit = splitKVMount(mount);
|
||||||
|
const baseMount = mountSplit.baseMount;
|
||||||
|
const restOfMount = mountSplit.restOfMount;
|
||||||
|
|
||||||
|
const mountsPath = "/sys/mounts/" + baseMount;
|
||||||
|
const currentPath = baseMount + restOfMount.join();
|
||||||
|
const caps = await getCapabilitiesPath([mountsPath, currentPath]);
|
||||||
|
this.setState({
|
||||||
|
mountCaps: caps[mountsPath],
|
||||||
|
pathCaps: caps[currentPath],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.pathCaps) return;
|
||||||
|
|
||||||
|
const mount = this.props.matches["mount"];
|
||||||
|
const mountSplit = splitKVMount(mount);
|
||||||
|
const baseMount = mountSplit.baseMount;
|
||||||
|
const restOfMount = mountSplit.restOfMount;
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
|
<SecretTitleElement type="kv" mount={mount} item={this.props.matches["item"]} />
|
||||||
<p>
|
<p>
|
||||||
{pathCaps.includes("create") && (
|
{this.state.pathCaps.includes("create") && (
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-primary"
|
class="uk-button uk-button-primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await this.router.changePage("KEY_VALUE_NEW_SECRET");
|
route("/secrets/kv/new/" + mount)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("kv_view_new_btn")}
|
{i18next.t("kv_view_new_btn")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{this.state.secretPath.length == 0 && mountCaps.includes("delete") && (
|
{restOfMount.length == 0 && this.state.mountCaps.includes("delete") && (
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-danger"
|
class="uk-button uk-button-danger"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await this.router.changePage("DELETE_SECRET_ENGINE");
|
route("/secrets/delete_engine/" + mount)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("kv_view_delete_btn")}
|
{i18next.t("kv_view_delete_btn")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{this.state.secretMountType == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>}
|
{//"TODO: " == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>}
|
||||||
<KVKeysList
|
}
|
||||||
page={this}
|
<KVKeysList baseMount={baseMount} secretPath={restOfMount} state={this.props.state} />
|
||||||
baseMount={this.state.baseMount}
|
</>
|
||||||
secretMountType={this.state.secretMountType}
|
|
||||||
secretPath={this.state.secretPath}
|
|
||||||
/>
|
|
||||||
</>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
|
||||||
render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("kv_view_title");
|
return i18next.t("kv_view_title");
|
||||||
}
|
}
|
||||||
|
|
10
src/ui/pages/Secrets/KeyValue/splitKVMount.tsx
Normal file
10
src/ui/pages/Secrets/KeyValue/splitKVMount.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export function splitKVMount(mount: string): { baseMount: string, restOfMount: string[] } {
|
||||||
|
const baseMount = mount.split("/")[0];
|
||||||
|
const restOfMount = mount.split("/");
|
||||||
|
restOfMount.shift();
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseMount,
|
||||||
|
restOfMount
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,47 +3,45 @@ import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/MarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { newMount } from "../../../../api/sys/newMount";
|
import { newMount } from "../../../../api/sys/newMount";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { PageTitle } from "../../../elements/PageTitle";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
export class NewKVEnginePage extends Page {
|
export class NewKVEngine extends Component {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
return (
|
||||||
}
|
<>
|
||||||
async goBack(): Promise<void> {
|
<PageTitle title={i18next.t("new_kv_engine_title")} />
|
||||||
await this.router.changePage("SECRETS_HOME");
|
<Form onSubmit={(data) => this.submit(data)}>
|
||||||
}
|
<Margin>
|
||||||
async render(): Promise<void> {
|
<input
|
||||||
render(
|
class="uk-input uk-form-width-medium"
|
||||||
<Form onSubmit={(data) => this.submit(data)}>
|
name="name"
|
||||||
<Margin>
|
type="text"
|
||||||
<input
|
placeholder={i18next.t("new_kv_engine_name_input")}
|
||||||
class="uk-input uk-form-width-medium"
|
required
|
||||||
name="name"
|
/>
|
||||||
type="text"
|
</Margin>
|
||||||
placeholder={i18next.t("new_kv_engine_name_input")}
|
<Margin>
|
||||||
required
|
<select class="uk-input uk-form-width-medium" name="version">
|
||||||
/>
|
<option label={i18next.t("new_kv_engine_version_2")} value="2">
|
||||||
</Margin>
|
{i18next.t("new_kv_engine_version_2")}
|
||||||
<Margin>
|
</option>
|
||||||
<select class="uk-input uk-form-width-medium" name="version">
|
<option label={i18next.t("new_kv_engine_version_1")} value="1">
|
||||||
<option label={i18next.t("new_kv_engine_version_2")} value="2">
|
{i18next.t("new_kv_engine_version_1")}
|
||||||
{i18next.t("new_kv_engine_version_2")}
|
</option>
|
||||||
</option>
|
</select>
|
||||||
<option label={i18next.t("new_kv_engine_version_1")} value="1">
|
</Margin>
|
||||||
{i18next.t("new_kv_engine_version_1")}
|
<p class="uk-text-danger" id="errorText" />
|
||||||
</option>
|
<MarginInline>
|
||||||
</select>
|
<button class="uk-button uk-button-primary" type="submit">
|
||||||
</Margin>
|
{i18next.t("new_kv_engine_create_btn")}
|
||||||
<p class="uk-text-danger" id="errorText" />
|
</button>
|
||||||
<MarginInline>
|
</MarginInline>
|
||||||
<button class="uk-button uk-button-primary" type="submit">
|
</Form>
|
||||||
{i18next.t("new_kv_engine_create_btn")}
|
</>
|
||||||
</button>
|
|
||||||
</MarginInline>
|
|
||||||
</Form>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,16 +57,10 @@ export class NewKVEnginePage extends Page {
|
||||||
version: version,
|
version: version,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.state.secretMountType = "kv-v" + version;
|
route("/secrets/kv/list/" + name + "/")
|
||||||
this.state.baseMount = name + "/";
|
|
||||||
await this.router.changePage("KEY_VALUE_VIEW");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
setErrorText(error.message);
|
setErrorText(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("new_kv_engine_title");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,37 +3,35 @@ import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/MarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { newMount } from "../../../../api/sys/newMount";
|
import { newMount } from "../../../../api/sys/newMount";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { PageTitle } from "../../../elements/PageTitle";
|
||||||
|
|
||||||
export class NewTOTPEnginePage extends Page {
|
export class NewTOTPEngine extends Component {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
return (
|
||||||
}
|
<>
|
||||||
async goBack(): Promise<void> {
|
<PageTitle title={i18next.t("new_totp_engine_title")} />
|
||||||
await this.router.changePage("SECRETS_HOME");
|
<Form onSubmit={(data) => this.submit(data)}>
|
||||||
}
|
<Margin>
|
||||||
async render(): Promise<void> {
|
<input
|
||||||
render(
|
class="uk-input uk-form-width-medium"
|
||||||
<Form onSubmit={(data) => this.submit(data)}>
|
name="name"
|
||||||
<Margin>
|
type="text"
|
||||||
<input
|
placeholder={i18next.t("new_totp_engine_name_input")}
|
||||||
class="uk-input uk-form-width-medium"
|
required
|
||||||
name="name"
|
/>
|
||||||
type="text"
|
</Margin>
|
||||||
placeholder={i18next.t("new_totp_engine_name_input")}
|
<p class="uk-text-danger" id="errorText" />
|
||||||
required
|
<MarginInline>
|
||||||
/>
|
<button class="uk-button uk-button-primary" type="submit">
|
||||||
</Margin>
|
{i18next.t("new_totp_engine_create_btn")}
|
||||||
<p class="uk-text-danger" id="errorText" />
|
</button>
|
||||||
<MarginInline>
|
</MarginInline>
|
||||||
<button class="uk-button uk-button-primary" type="submit">
|
</Form>,
|
||||||
{i18next.t("new_totp_engine_create_btn")}
|
</>
|
||||||
</button>
|
|
||||||
</MarginInline>
|
|
||||||
</Form>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +43,7 @@ export class NewTOTPEnginePage extends Page {
|
||||||
name: name,
|
name: name,
|
||||||
type: "totp",
|
type: "totp",
|
||||||
});
|
});
|
||||||
this.state.secretMountType = "totp";
|
route("/secrets/totp/list/" + name + "/")
|
||||||
this.state.baseMount = name + "/";
|
|
||||||
await this.router.changePage("TOTP_VIEW");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
setErrorText(error.message);
|
setErrorText(error.message);
|
||||||
|
|
|
@ -3,37 +3,35 @@ import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/MarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { newMount } from "../../../../api/sys/newMount";
|
import { newMount } from "../../../../api/sys/newMount";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { PageTitle } from "../../../elements/PageTitle";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
export class NewTransitEnginePage extends Page {
|
export class NewTransitEngine extends Component {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
return (
|
||||||
}
|
<>
|
||||||
async goBack(): Promise<void> {
|
<PageTitle title={i18next.t("new_totp_engine_title")} />
|
||||||
await this.router.changePage("SECRETS_HOME");
|
<Form onSubmit={(data) => this.submit(data)}>
|
||||||
}
|
<Margin>
|
||||||
async render(): Promise<void> {
|
<input
|
||||||
render(
|
class="uk-input uk-form-width-medium"
|
||||||
<Form onSubmit={(data) => this.submit(data)}>
|
name="name"
|
||||||
<Margin>
|
type="text"
|
||||||
<input
|
placeholder={i18next.t("new_transit_engine_name_input")}
|
||||||
class="uk-input uk-form-width-medium"
|
required
|
||||||
name="name"
|
/>
|
||||||
type="text"
|
</Margin>
|
||||||
placeholder={i18next.t("new_transit_engine_name_input")}
|
<p class="uk-text-danger" id="errorText" />
|
||||||
required
|
<MarginInline>
|
||||||
/>
|
<button class="uk-button uk-button-primary" type="submit">
|
||||||
</Margin>
|
{i18next.t("new_transit_engine_create_btn")}
|
||||||
<p class="uk-text-danger" id="errorText" />
|
</button>
|
||||||
<MarginInline>
|
</MarginInline>
|
||||||
<button class="uk-button uk-button-primary" type="submit">
|
</Form>,
|
||||||
{i18next.t("new_transit_engine_create_btn")}
|
</>
|
||||||
</button>
|
|
||||||
</MarginInline>
|
|
||||||
</Form>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +43,7 @@ export class NewTransitEnginePage extends Page {
|
||||||
name: name,
|
name: name,
|
||||||
type: "transit",
|
type: "transit",
|
||||||
});
|
});
|
||||||
this.state.secretMountType = "transit";
|
route("/secrets/transit/list/" + name + "/")
|
||||||
this.state.baseMount = name + "/";
|
|
||||||
await this.router.changePage("TRANSIT_VIEW");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
setErrorText(error.message);
|
setErrorText(error.message);
|
||||||
|
|
|
@ -1,44 +1,40 @@
|
||||||
import { Grid, GridSizes } from "../../elements/Grid";
|
import { Grid, GridSizes } from "../../elements/Grid";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { Tile } from "../../elements/Tile";
|
import { Tile } from "../../elements/Tile";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { PageTitle } from "../../elements/PageTitle";
|
||||||
|
|
||||||
export class NewSecretsEnginePage extends Page {
|
export class NewSecretsEngine extends Component {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
return (
|
||||||
}
|
<>
|
||||||
|
<PageTitle title={i18next.t("new_secrets_engine_title")} />
|
||||||
async render(): Promise<void> {
|
|
||||||
render(
|
|
||||||
<Grid size={GridSizes.MATCHING_TWO_ROWS}>
|
<Grid size={GridSizes.MATCHING_TWO_ROWS}>
|
||||||
<Tile
|
<Tile
|
||||||
title={i18next.t("new_secrets_engine_kv_title")}
|
title={i18next.t("new_secrets_engine_kv_title")}
|
||||||
description={i18next.t("new_secrets_engine_kv_description")}
|
description={i18next.t("new_secrets_engine_kv_description")}
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
await this.router.changePage("NEW_KV_ENGINE");
|
route("/secrets/new_secrets_engine/kv")
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tile
|
<Tile
|
||||||
title={i18next.t("new_secrets_engine_totp_title")}
|
title={i18next.t("new_secrets_engine_totp_title")}
|
||||||
description={i18next.t("new_secrets_engine_totp_description")}
|
description={i18next.t("new_secrets_engine_totp_description")}
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
await this.router.changePage("NEW_TOTP_ENGINE");
|
route("/secrets/new_secrets_engine/totp")
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tile
|
<Tile
|
||||||
title={i18next.t("new_secrets_engine_transit_title")}
|
title={i18next.t("new_secrets_engine_transit_title")}
|
||||||
description={i18next.t("new_secrets_engine_transit_description")}
|
description={i18next.t("new_secrets_engine_transit_description")}
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
await this.router.changePage("NEW_TRANSIT_ENGINE");
|
route("/secrets/new_secrets_engine/transit")
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>,
|
</Grid>
|
||||||
this.router.pageContentElement,
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("new_secrets_engine_title");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +1,55 @@
|
||||||
|
import { route } from "preact-router";
|
||||||
import { JSX } from "preact/jsx-runtime";
|
import { JSX } from "preact/jsx-runtime";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { CopyStateLinkButton } from "../../elements/CopyStateLinkButton";
|
|
||||||
|
|
||||||
|
|
||||||
function currentTitleSecretText(page: Page): string {
|
|
||||||
let secretItemText = page.state.secretItem;
|
|
||||||
if (page.state.secretVersion !== null) secretItemText += ` (v${page.state.secretVersion})`;
|
|
||||||
return secretItemText;
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecretTitleElementProps = {
|
type SecretTitleElementProps = {
|
||||||
page: Page;
|
type: string;
|
||||||
|
mount: string;
|
||||||
|
item?: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element {
|
export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element {
|
||||||
const page = props.page;
|
const type = props.type;
|
||||||
|
const mount = props.mount;
|
||||||
|
const item = props.item || "";
|
||||||
const suffix = props.suffix || "";
|
const suffix = props.suffix || "";
|
||||||
|
|
||||||
|
const baseMount = mount.split("/")[0];
|
||||||
|
const restOfMount = mount.split("/");
|
||||||
|
restOfMount.shift();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<h3 class="uk-card-title" id="pageTitle">
|
||||||
<a
|
|
||||||
onClick={async () => {
|
|
||||||
await page.router.changePage("SECRETS_HOME");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{"/ "}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
<div>
|
||||||
onClick={async () => {
|
|
||||||
page.state.secretPath = [];
|
|
||||||
page.state.secretItem = "";
|
|
||||||
page.state.secretVersion = null;
|
|
||||||
|
|
||||||
if (
|
|
||||||
page.state.secretMountType.startsWith("kv") ||
|
|
||||||
page.state.secretMountType == "cubbyhole"
|
|
||||||
) {
|
|
||||||
await page.router.changePage("KEY_VALUE_VIEW");
|
|
||||||
} else if (page.state.secretMountType == "totp") {
|
|
||||||
await page.router.changePage("TOTP_VIEW");
|
|
||||||
} else if (page.state.secretMountType == "transit") {
|
|
||||||
await page.router.changePage("TRANSIT_VIEW");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{page.state.baseMount + " "}
|
|
||||||
</a>
|
|
||||||
{...page.state.secretPath.map((secretPath, index, secretPaths) => (
|
|
||||||
<a
|
<a
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
page.state.secretVersion = null;
|
route("/secrets");
|
||||||
if (page.state.secretMountType.startsWith("kv")) {
|
|
||||||
page.state.secretPath = secretPaths.slice(0, index + 1);
|
|
||||||
await page.router.changePage("KEY_VALUE_VIEW");
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{secretPath + " "}
|
{"/ "}
|
||||||
</a>
|
</a>
|
||||||
))}
|
|
||||||
{page.state.secretItem.length != 0 && <span>{currentTitleSecretText(page)}</span>}
|
<a href={"/secrets/" + type + "/list/" + baseMount + "/"}>
|
||||||
{suffix.length != 0 && <span>{suffix}</span>}
|
{baseMount + " "}
|
||||||
<CopyStateLinkButton state={page.state} />
|
</a>
|
||||||
</div>
|
|
||||||
|
{...restOfMount.map((secretPath, index, secretPaths) => (
|
||||||
|
<a
|
||||||
|
onClick={async () => {
|
||||||
|
if (type == "kv") {
|
||||||
|
let secretPath = secretPaths.slice(0, index + 1);
|
||||||
|
route("/secrets/kv/list/" + baseMount + "/" + secretPath.join("/"))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{secretPath + "/" + " "}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
{item.length != 0 && <span>{item}</span>}
|
||||||
|
{suffix.length != 0 && <span>{suffix}</span>}
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { JSX, render } from "preact";
|
import { Component, JSX, render } from "preact";
|
||||||
import { MountType, getMounts } from "../../../api/sys/getMounts";
|
import { MountType, getMounts } from "../../../api/sys/getMounts";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { getCapsPath } from "../../../api/sys/getCapabilities";
|
import { getCapsPath } from "../../../api/sys/getCapabilities";
|
||||||
import { prePageChecks } from "../../../pageUtils";
|
import { prePageChecks } from "../../../pageUtils";
|
||||||
import { sortedObjectMap } from "../../../utils";
|
import { sortedObjectMap } from "../../../utils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { PageState } from "../../../state/PageState";
|
||||||
export type MountLinkProps = {
|
import { DefaultPageProps } from "../../../types/DefaultPageProps";
|
||||||
page: Page;
|
import { route } from "preact-router";
|
||||||
mount: MountType;
|
import { PageTitle } from "../../elements/PageTitle";
|
||||||
baseMount: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const supportedMountTypes = ["kv", "totp", "transit", "cubbyhole"];
|
const supportedMountTypes = ["kv", "totp", "transit", "cubbyhole"];
|
||||||
|
|
||||||
|
@ -22,97 +20,96 @@ export function isSupportedMount(mount: MountType): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MountLinkProps = {
|
||||||
|
state: PageState;
|
||||||
|
mount: MountType;
|
||||||
|
baseMount: string;
|
||||||
|
};
|
||||||
|
|
||||||
function MountLink(props: MountLinkProps): JSX.Element {
|
function MountLink(props: MountLinkProps): JSX.Element {
|
||||||
const mount = props.mount;
|
const mount = props.mount;
|
||||||
const baseMount = props.baseMount;
|
const baseMount = props.baseMount;
|
||||||
const page = props.page;
|
|
||||||
|
|
||||||
const secretMountType = mount.type == "kv" ? "kv-v" + String(mount.options.version) : mount.type;
|
|
||||||
|
|
||||||
let linkText = "";
|
let linkText = "";
|
||||||
let linkPage: string;
|
let mountPathType: string;
|
||||||
if (mount.type == "kv") {
|
if (mount.type == "kv") {
|
||||||
linkText = `K/V (v${mount.options.version}) - ${baseMount}`;
|
linkText = `K/V (v${mount.options.version}) - ${baseMount}`;
|
||||||
linkPage = "KEY_VALUE_VIEW";
|
mountPathType = "kv";
|
||||||
} else if (mount.type == "totp") {
|
} else if (mount.type == "totp") {
|
||||||
linkText = `TOTP - ${baseMount}`;
|
linkText = `TOTP - ${baseMount}`;
|
||||||
linkPage = "TOTP_VIEW";
|
mountPathType = "totp";
|
||||||
} else if (mount.type == "transit") {
|
} else if (mount.type == "transit") {
|
||||||
linkText = `Transit - ${baseMount}`;
|
linkText = `Transit - ${baseMount}`;
|
||||||
linkPage = "TRANSIT_VIEW";
|
mountPathType = "transit";
|
||||||
} else if (mount.type == "cubbyhole") {
|
} else if (mount.type == "cubbyhole") {
|
||||||
linkText = `Cubbyhole - ${baseMount}`;
|
linkText = `Cubbyhole - ${baseMount}`;
|
||||||
linkPage = "KEY_VALUE_VIEW";
|
mountPathType = "kv";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let link = "/secrets/" + mountPathType + "/list/" + baseMount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a href={link}>
|
||||||
onClick={async () => {
|
|
||||||
page.state.baseMount = baseMount;
|
|
||||||
page.state.secretMountType = secretMountType;
|
|
||||||
await page.router.changePage(linkPage);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{linkText}
|
{linkText}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SecretsHomePage extends Page {
|
type SecretsState = {
|
||||||
constructor() {
|
mountsMap: Map<String, MountType>;
|
||||||
super();
|
capabilities: string[];
|
||||||
}
|
}
|
||||||
async goBack(): Promise<void> {
|
|
||||||
await this.router.changePage("HOME");
|
|
||||||
}
|
|
||||||
async render(): Promise<void> {
|
|
||||||
if (!(await prePageChecks(this.router))) return;
|
|
||||||
|
|
||||||
this.state.baseMount = "";
|
export class Secrets extends Component<DefaultPageProps, SecretsState> {
|
||||||
this.state.secretPath = [];
|
async componentDidMount() {
|
||||||
this.state.secretItem = "";
|
if (!(await prePageChecks(this.props.state))) return;
|
||||||
this.state.secretVersion = null;
|
|
||||||
|
|
||||||
const mountsCapabilities = await getCapsPath("/sys/mounts");
|
const mountsCapabilities = await getCapsPath("/sys/mounts");
|
||||||
const mounts = await getMounts();
|
const mounts = await getMounts();
|
||||||
// sort it by secretPath so it's in alphabetical order consistantly.
|
// sort it by secretPath so it's in alphabetical order consistantly.
|
||||||
const mountsMap = sortedObjectMap(mounts);
|
const mountsMap = sortedObjectMap(mounts);
|
||||||
|
this.setState({
|
||||||
|
capabilities: mountsCapabilities,
|
||||||
|
mountsMap: mountsMap as Map<String, MountType>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render(
|
|
||||||
<div>
|
render() {
|
||||||
|
return this.state.mountsMap && (
|
||||||
|
<>
|
||||||
|
<PageTitle title={i18next.t("secrets_home_page_title")} />
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<div>
|
||||||
{mountsCapabilities.includes("sudo") && mountsCapabilities.includes("create") && (
|
<p>
|
||||||
<button
|
{this.state.capabilities.includes("sudo") && this.state.capabilities.includes("create") && (
|
||||||
class="uk-button uk-button-primary"
|
<button
|
||||||
onClick={async () => {
|
class="uk-button uk-button-primary"
|
||||||
await this.router.changePage("NEW_SECRETS_ENGINE");
|
onClick={() => {
|
||||||
}}
|
route("/secrets/new_secrets_engine")
|
||||||
>
|
}}
|
||||||
{i18next.t("secrets_home_new_secrets_engine_button")}
|
>
|
||||||
</button>
|
{i18next.t("secrets_home_new_secrets_engine_button")}
|
||||||
)}
|
</button>
|
||||||
</p>
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin-top">
|
||||||
|
<ul class="uk-nav uk-nav-default">
|
||||||
|
{Array.from(this.state.mountsMap).map((args: [string, MountType]) => {
|
||||||
|
const baseMount = args[0];
|
||||||
|
const mount = args[1];
|
||||||
|
console.log(baseMount, mount)
|
||||||
|
if (isSupportedMount(mount)) {
|
||||||
|
return <MountLink state={this.props.state} mount={mount} baseMount={baseMount} />;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin-top">
|
</>
|
||||||
<ul class="uk-nav uk-nav-default">
|
|
||||||
{Array.from(mountsMap as Map<string, MountType>).map((args: [string, MountType]) => {
|
|
||||||
const baseMount = args[0];
|
|
||||||
const mount = args[1];
|
|
||||||
if (isSupportedMount(mount)) {
|
|
||||||
return <MountLink page={this} mount={mount} baseMount={baseMount} />;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("secrets_home_page_title");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,37 @@
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { deleteTOTP } from "../../../../api/totp/deleteTOTP";
|
import { deleteTOTP } from "../../../../api/totp/deleteTOTP";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
export class TOTPDeletePage extends Page {
|
export class TOTPDelete extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
return (
|
||||||
}
|
|
||||||
|
|
||||||
async cleanup(): Promise<void> {
|
|
||||||
this.state.secretItem = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async goBack(): Promise<void> {
|
|
||||||
this.state.secretItem = "";
|
|
||||||
await this.router.changePage("TOTP_VIEW");
|
|
||||||
}
|
|
||||||
async render(): Promise<void> {
|
|
||||||
render(
|
|
||||||
<div>
|
<div>
|
||||||
<h5>{i18next.t("totp_delete_text")}</h5>
|
<h5>{i18next.t("totp_delete_text")}</h5>
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-danger"
|
class="uk-button uk-button-danger"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await deleteTOTP(this.state.baseMount, this.state.secretItem);
|
let mount = this.props.matches["mount"];
|
||||||
await this.goBack();
|
let item = this.props.matches["item"];
|
||||||
|
await deleteTOTP(mount, item);
|
||||||
|
route("/secrets/totp/list/" + mount)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("kv_delete_btn")}
|
{i18next.t("kv_delete_btn")}
|
||||||
</button>
|
</button>
|
||||||
</div>,
|
</div>
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(
|
// render(
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("totp_delete_suffix")} />,
|
// <SecretTitleElement page={this} suffix={i18next.t("totp_delete_suffix")} />,
|
||||||
this.router.pageTitleElement,
|
// this.router.pageTitleElement,
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("totp_delete_title");
|
return i18next.t("totp_delete_title");
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { addNewTOTP } from "../../../../api/totp/addNewTOTP";
|
import { addNewTOTP } from "../../../../api/totp/addNewTOTP";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||||
|
|
||||||
function replaceAll(str: string, replace: string, replaceWith: string): string {
|
function replaceAll(str: string, replace: string, replaceWith: string): string {
|
||||||
return str.replace(new RegExp(replace, "g"), replaceWith);
|
return str.replace(new RegExp(replace, "g"), replaceWith);
|
||||||
|
@ -19,7 +21,7 @@ function removeDashSpaces(str: string): string {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }> {
|
export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean }> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -30,7 +32,6 @@ export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }>
|
||||||
uriInputRef = createRef<HTMLInputElement>();
|
uriInputRef = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
async onSubmit(data: FormData): Promise<void> {
|
async onSubmit(data: FormData): Promise<void> {
|
||||||
const page = this.props.page;
|
|
||||||
const parms = {
|
const parms = {
|
||||||
url: data.get("uri") as string,
|
url: data.get("uri") as string,
|
||||||
key: removeDashSpaces(data.get("key") as string).toUpperCase(),
|
key: removeDashSpaces(data.get("key") as string).toUpperCase(),
|
||||||
|
@ -39,8 +40,8 @@ export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }>
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await addNewTOTP(page.state.baseMount, parms);
|
await addNewTOTP(this.props.mount, parms);
|
||||||
await page.router.changePage("TOTP_VIEW");
|
route("/secrets/totp/list/" + this.props.mount)
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
setErrorText(`API Error: ${error.message}`);
|
setErrorText(`API Error: ${error.message}`);
|
||||||
|
@ -118,24 +119,24 @@ export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TOTPNewPage extends Page {
|
export class TOTPNew extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
let mount = this.props.matches["mount"];
|
||||||
}
|
return (
|
||||||
async goBack(): Promise<void> {
|
<>
|
||||||
await this.router.changePage("TOTP_VIEW");
|
<SecretTitleElement type="totp" mount={mount} suffix={i18next.t("totp_new_suffix")} />
|
||||||
}
|
<TOTPNewForm mount={this.props.matches["mount"]} />
|
||||||
async render(): Promise<void> {
|
</>
|
||||||
render(<TOTPNewForm page={this} />, this.router.pageContentElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
|
||||||
render(
|
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("totp_new_suffix")} />,
|
|
||||||
this.router.pageTitleElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//async renderPageTitle(): Promise<void> {
|
||||||
|
// render(
|
||||||
|
// <SecretTitleElement page={this} suffix={i18next.t("totp_new_suffix")} />,
|
||||||
|
// this.router.pageTitleElement,
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("totp_new_title");
|
return i18next.t("totp_new_title");
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,21 @@ import { Grid, GridSizes } from "../../../elements/Grid";
|
||||||
import { MarginInline } from "../../../elements/MarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { getCapabilitiesPath, getCapsPath } from "../../../../api/sys/getCapabilities";
|
import { CapabilitiesType, getCapabilitiesPath, getCapsPath } from "../../../../api/sys/getCapabilities";
|
||||||
import { getTOTPCode } from "../../../../api/totp/getTOTPCode";
|
import { getTOTPCode } from "../../../../api/totp/getTOTPCode";
|
||||||
import { getTOTPKeys } from "../../../../api/totp/getTOTPKeys";
|
import { getTOTPKeys } from "../../../../api/totp/getTOTPKeys";
|
||||||
import { removeDoubleSlash } from "../../../../utils";
|
import { removeDoubleSlash } from "../../../../utils";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
|
type TOTPGridItemProps = {
|
||||||
|
mount: string; totpKey: string; canDelete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class RefreshingTOTPGridItem extends Component<
|
export class RefreshingTOTPGridItem extends Component<
|
||||||
{ baseMount: string; totpKey: string; page: Page; canDelete: boolean },
|
TOTPGridItemProps,
|
||||||
{ totpValue: string }
|
{ totpValue: string }
|
||||||
> {
|
> {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -23,7 +29,7 @@ export class RefreshingTOTPGridItem extends Component<
|
||||||
timer: unknown;
|
timer: unknown;
|
||||||
|
|
||||||
updateTOTPCode(): void {
|
updateTOTPCode(): void {
|
||||||
void getTOTPCode(this.props.baseMount, this.props.totpKey).then((code) => {
|
void getTOTPCode(this.props.mount, this.props.totpKey).then((code) => {
|
||||||
this.setState({ totpValue: code });
|
this.setState({ totpValue: code });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -50,9 +56,7 @@ export class RefreshingTOTPGridItem extends Component<
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-danger"
|
class="uk-button uk-button-danger"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const page = this.props.page;
|
route("/secrets/totp/delete/" + this.props.mount + "/" + this.props.totpKey);
|
||||||
page.state.secretItem = this.props.totpKey;
|
|
||||||
await page.router.changePage("TOTP_DELETE");
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("totp_view_secret_delete_btn")}
|
{i18next.t("totp_view_secret_delete_btn")}
|
||||||
|
@ -65,34 +69,77 @@ export class RefreshingTOTPGridItem extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TOTPViewPage extends Page {
|
type TOTPViewProps = DefaultPageProps & {
|
||||||
|
}
|
||||||
|
|
||||||
|
type TOTPViewState = {
|
||||||
|
capabilities?: CapabilitiesType;
|
||||||
|
totpItems: TOTPGridItemProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TOTPView extends Component<TOTPViewProps, TOTPViewState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.refresher = undefined;
|
this.refresher = undefined;
|
||||||
|
this.state = { capabilities: null, totpItems: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
refresher: number;
|
refresher: number;
|
||||||
|
|
||||||
async goBack(): Promise<void> {
|
async componentDidMount() {
|
||||||
await this.router.changePage("SECRETS_HOME");
|
var mount = this.props.matches["mount"];
|
||||||
|
const mountsPath = "/sys/mounts/" + mount;
|
||||||
|
const caps = await getCapabilitiesPath([mountsPath, mount]);
|
||||||
|
|
||||||
|
let totpItems: TOTPGridItemProps[] = [];
|
||||||
|
|
||||||
|
// TODO: tidy this up i guess
|
||||||
|
try {
|
||||||
|
totpItems = await Promise.all(
|
||||||
|
Array.from(await getTOTPKeys(mount)).map(async (key) => {
|
||||||
|
const totpCaps = await getCapsPath(
|
||||||
|
removeDoubleSlash(mount + "code/" + key),
|
||||||
|
);
|
||||||
|
if (totpCaps.includes("read")) {
|
||||||
|
return {
|
||||||
|
mount: mount,
|
||||||
|
totpKey: key,
|
||||||
|
canDelete: totpCaps.includes("delete"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const error = e as Error;
|
||||||
|
if (error != DoesNotExistError) {
|
||||||
|
setErrorText(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
capabilities: caps,
|
||||||
|
totpItems: totpItems
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(): Promise<void> {
|
render() {
|
||||||
this.state.secretItem = "";
|
if (!this.state.capabilities) return;
|
||||||
|
var mount = this.props.matches["mount"];
|
||||||
|
|
||||||
const mountsPath = "/sys/mounts/" + this.state.baseMount;
|
const mountsPath = "/sys/mounts/" + mount;
|
||||||
const caps = await getCapabilitiesPath([mountsPath, this.state.baseMount]);
|
const mountCaps = this.state.capabilities[mountsPath];
|
||||||
const mountCaps = caps[mountsPath];
|
const totpCaps = this.state.capabilities[mount];
|
||||||
const totpCaps = caps[this.state.baseMount];
|
|
||||||
|
|
||||||
render(
|
return (
|
||||||
|
<>
|
||||||
|
<SecretTitleElement type="totp" mount={mount} />
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
{totpCaps.includes("create") && (
|
{totpCaps.includes("create") && (
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-primary"
|
class="uk-button uk-button-primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await this.router.changePage("TOTP_NEW");
|
route("/secrets/totp/new/" + mount)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("totp_view_new_btn")}
|
{i18next.t("totp_view_new_btn")}
|
||||||
|
@ -102,7 +149,7 @@ export class TOTPViewPage extends Page {
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-danger"
|
class="uk-button uk-button-danger"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await this.router.changePage("DELETE_SECRET_ENGINE");
|
route("/secrets/delete_engine/" + mount)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("totp_view_delete_btn")}
|
{i18next.t("totp_view_delete_btn")}
|
||||||
|
@ -111,44 +158,25 @@ export class TOTPViewPage extends Page {
|
||||||
</p>
|
</p>
|
||||||
<div id="totpList">
|
<div id="totpList">
|
||||||
{
|
{
|
||||||
await (async () => {
|
(() => {
|
||||||
try {
|
if (this.state.totpItems.length == 0) {
|
||||||
const elem = await Promise.all(
|
return <p>{i18next.t("totp_view_empty")}</p>;
|
||||||
Array.from(await getTOTPKeys(this.state.baseMount)).map(async (key) => {
|
} else {
|
||||||
const caps = await getCapsPath(
|
return this.state.totpItems.map((totpItem) => {
|
||||||
removeDoubleSlash(this.state.baseMount + "code/" + key),
|
return <RefreshingTOTPGridItem
|
||||||
);
|
mount={totpItem.mount}
|
||||||
if (caps.includes("read")) {
|
totpKey={totpItem.totpKey}
|
||||||
return (
|
canDelete={totpItem.canDelete}
|
||||||
<RefreshingTOTPGridItem
|
/>
|
||||||
baseMount={this.state.baseMount}
|
})
|
||||||
totpKey={key}
|
|
||||||
page={this}
|
|
||||||
canDelete={caps.includes("delete")}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return elem;
|
|
||||||
} catch (e: unknown) {
|
|
||||||
const error = e as Error;
|
|
||||||
if (error == DoesNotExistError) {
|
|
||||||
return <p>{i18next.t("totp_view_empty")}</p>;
|
|
||||||
} else {
|
|
||||||
setErrorText(error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>
|
||||||
this.router.pageContentElement,
|
</>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
);
|
||||||
render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
|
|
|
@ -76,12 +76,12 @@ export class NewTransitKeyPage extends Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(
|
// render(
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("transit_new_key_suffix")} />,
|
// <SecretTitleElement page={this} suffix={i18next.t("transit_new_key_suffix")} />,
|
||||||
this.router.pageTitleElement,
|
// this.router.pageTitleElement,
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("transit_new_key_title");
|
return i18next.t("transit_new_key_title");
|
||||||
|
|
|
@ -82,12 +82,12 @@ export class TransitDecryptPage extends Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(
|
// render(
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("transit_decrypt_suffix")} />,
|
// <SecretTitleElement page={this} suffix={i18next.t("transit_decrypt_suffix")} />,
|
||||||
this.router.pageTitleElement,
|
// this.router.pageTitleElement,
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("transit_decrypt_title");
|
return i18next.t("transit_decrypt_title");
|
||||||
|
|
|
@ -79,12 +79,12 @@ export class TransitEncryptPage extends Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(
|
// render(
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("transit_encrypt_suffix")} />,
|
// <SecretTitleElement page={this} suffix={i18next.t("transit_encrypt_suffix")} />,
|
||||||
this.router.pageTitleElement,
|
// this.router.pageTitleElement,
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("transit_encrypt_title");
|
return i18next.t("transit_encrypt_title");
|
||||||
|
|
|
@ -94,12 +94,12 @@ export class TransitRewrapPage extends Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(
|
// render(
|
||||||
<SecretTitleElement page={this} suffix={i18next.t("transit_rewrap_suffix")} />,
|
// <SecretTitleElement page={this} suffix={i18next.t("transit_rewrap_suffix")} />,
|
||||||
this.router.pageTitleElement,
|
// this.router.pageTitleElement,
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("transit_rewrap_title");
|
return i18next.t("transit_rewrap_title");
|
||||||
|
|
|
@ -116,9 +116,9 @@ export class TransitViewPage extends Page {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
// render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("transit_view_title");
|
return i18next.t("transit_view_title");
|
||||||
|
|
|
@ -52,9 +52,9 @@ export class TransitViewSecretPage extends Page {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderPageTitle(): Promise<void> {
|
//async renderPageTitle(): Promise<void> {
|
||||||
render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
// render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
|
||||||
}
|
//}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t("transit_view_secret_title");
|
return i18next.t("transit_view_secret_title");
|
||||||
|
|
|
@ -5,51 +5,52 @@ import translations from "../../translations/index.mjs";
|
||||||
import { Form } from "../elements/Form";
|
import { Form } from "../elements/Form";
|
||||||
import { Margin } from "../elements/Margin";
|
import { Margin } from "../elements/Margin";
|
||||||
import { MarginInline } from "../elements/MarginInline";
|
import { MarginInline } from "../elements/MarginInline";
|
||||||
import { Page } from "../../types/Page";
|
import { Component } from "preact";
|
||||||
import { reloadNavBar } from "../elements/NavBar";
|
|
||||||
import { render } from "preact";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
|
import { DefaultPageProps } from "../../types/DefaultPageProps.js";
|
||||||
|
|
||||||
const languageIDs = Object.getOwnPropertyNames(translations);
|
const languageIDs = Object.getOwnPropertyNames(translations);
|
||||||
|
|
||||||
export class SetLanguagePage extends Page {
|
export class SetLanguage extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
async render(): Promise<void> {
|
render() {
|
||||||
render(
|
return (
|
||||||
<Form onSubmit={(data) => this.onSubmit(data)}>
|
<>
|
||||||
<Margin>
|
<PageTitle title={i18next.t("set_language_title")} />
|
||||||
<select class="uk-select uk-form-width-large" name="language">
|
<Form onSubmit={(data) => this.onSubmit(data)}>
|
||||||
{languageIDs.map((languageID) => (
|
<Margin>
|
||||||
<option value={languageID}>
|
<select class="uk-select uk-form-width-large" name="language">
|
||||||
{i18next.getFixedT(languageID, null)("language_name")}
|
{languageIDs.map((languageID) => (
|
||||||
</option>
|
<option value={languageID}>
|
||||||
))}
|
{i18next.getFixedT(languageID, null)("language_name")}
|
||||||
</select>
|
</option>
|
||||||
</Margin>
|
))}
|
||||||
<p class="uk-text-danger" id="errorText" />
|
</select>
|
||||||
<MarginInline>
|
</Margin>
|
||||||
<button class="uk-button uk-button-primary" type="submit">
|
<p class="uk-text-danger" id="errorText" />
|
||||||
{i18next.t("set_language_btn")}
|
<MarginInline>
|
||||||
</button>
|
<button class="uk-button uk-button-primary" type="submit">
|
||||||
</MarginInline>
|
{i18next.t("set_language_btn")}
|
||||||
</Form>,
|
</button>
|
||||||
this.router.pageContentElement,
|
</MarginInline>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmit(data: FormData): Promise<void> {
|
async onSubmit(data: FormData): Promise<void> {
|
||||||
const language = data.get("language") as string;
|
const language = data.get("language") as string;
|
||||||
this.state.language = language;
|
this.props.state.language = language;
|
||||||
|
|
||||||
const t = await i18next.changeLanguage(language);
|
const t = await i18next.changeLanguage(language);
|
||||||
this.state.pageDirection = t("language_direction");
|
this.props.state.pageDirection = t("language_direction");
|
||||||
reloadNavBar(this.router);
|
// TODO: make navbar somethingy
|
||||||
await this.router.changePage("HOME");
|
//reloadNavBar(this.router);
|
||||||
}
|
route("/");
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("set_language_title");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import { Form } from "../elements/Form";
|
import { Form } from "../elements/Form";
|
||||||
import { Margin } from "../elements/Margin";
|
import { Margin } from "../elements/Margin";
|
||||||
import { Page } from "../../types/Page";
|
import { Page } from "../../types/Page";
|
||||||
import { render } from "preact";
|
import { Component, render } from "preact";
|
||||||
|
import { DefaultPageProps } from "../../types/DefaultPageProps";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
|
|
||||||
export class SetVaultURLPage extends Page {
|
export class SetVaultURL extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
render() {
|
||||||
super();
|
return (
|
||||||
}
|
<>
|
||||||
async render(): Promise<void> {
|
<PageTitle title={this.name} />
|
||||||
render(
|
|
||||||
<Form onSubmit={(data) => this.onSubmit(data)}>
|
<Form onSubmit={(data) => this.onSubmit(data)}>
|
||||||
<Margin>
|
<Margin>
|
||||||
<input
|
<input
|
||||||
|
@ -25,18 +27,19 @@ export class SetVaultURLPage extends Page {
|
||||||
Set
|
Set
|
||||||
</button>
|
</button>
|
||||||
</Margin>
|
</Margin>
|
||||||
</Form>,
|
</Form>
|
||||||
|
</>
|
||||||
this.router.pageContentElement,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmit(data: FormData): Promise<void> {
|
async onSubmit(data: FormData): Promise<void> {
|
||||||
this.state.apiURL = data.get("vaultURL") as string;
|
// TODO: check if vault is actually working here.
|
||||||
await this.router.changePage("HOME");
|
this.props.state.apiURL = data.get("vaultURL") as string;
|
||||||
|
route("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
|
// TODO:
|
||||||
return "Set Vault URL";
|
return "Set Vault URL";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import { setErrorText } from "../../pageUtils";
|
||||||
import { submitUnsealKey } from "../../api/sys/submitUnsealKey";
|
import { submitUnsealKey } from "../../api/sys/submitUnsealKey";
|
||||||
import { toStr } from "../../utils";
|
import { toStr } from "../../utils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { DefaultPageProps } from "../../types/DefaultPageProps";
|
||||||
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
|
import { route } from "preact-router";
|
||||||
|
|
||||||
const UnsealInputModes = {
|
const UnsealInputModes = {
|
||||||
FORM_INPUT: "FORM_INPUT",
|
FORM_INPUT: "FORM_INPUT",
|
||||||
|
@ -50,7 +53,7 @@ type UnsealPageState = {
|
||||||
keys_needed: number;
|
keys_needed: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState> {
|
export class Unseal extends Component<DefaultPageProps, UnsealPageState> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -79,7 +82,7 @@ export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState
|
||||||
keys_needed: data.t,
|
keys_needed: data.t,
|
||||||
});
|
});
|
||||||
if (!data.sealed) {
|
if (!data.sealed) {
|
||||||
void this.props.page.router.changePage("HOME");
|
route("/");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -97,61 +100,50 @@ export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<progress
|
<PageTitle title={i18next.t("unseal_vault_text")} />
|
||||||
class="uk-progress"
|
<div>
|
||||||
value={this.state.keys_submitted}
|
<progress
|
||||||
max={this.state.keys_needed}
|
class="uk-progress"
|
||||||
/>
|
value={this.state.keys_submitted}
|
||||||
|
max={this.state.keys_needed}
|
||||||
|
/>
|
||||||
|
|
||||||
<p id="errorText" class="uk-text-danger uk-margin-top" />
|
<p id="errorText" class="uk-text-danger uk-margin-top" />
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{i18next.t("unseal_keys_progress", {
|
{i18next.t("unseal_keys_progress", {
|
||||||
progress: toStr(this.state.keys_submitted),
|
progress: toStr(this.state.keys_submitted),
|
||||||
keys_needed: toStr(this.state.keys_needed),
|
keys_needed: toStr(this.state.keys_needed),
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{this.state.mode == UnsealInputModes.FORM_INPUT && (
|
{this.state.mode == UnsealInputModes.FORM_INPUT && (
|
||||||
<UnsealFormInput onSubmit={(code) => this.submitKey(code)} />
|
<UnsealFormInput onSubmit={(code) => this.submitKey(code)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.mode == UnsealInputModes.QR_INPUT && (
|
{this.state.mode == UnsealInputModes.QR_INPUT && (
|
||||||
<QRScanner onScan={(code) => this.submitKey(code)} />
|
<QRScanner onScan={(code) => this.submitKey(code)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-primary"
|
class="uk-button uk-button-primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
let newMethod: string;
|
let newMethod: string;
|
||||||
if (this.state.mode == UnsealInputModes.FORM_INPUT) {
|
if (this.state.mode == UnsealInputModes.FORM_INPUT) {
|
||||||
newMethod = UnsealInputModes.QR_INPUT;
|
newMethod = UnsealInputModes.QR_INPUT;
|
||||||
} else {
|
} else {
|
||||||
newMethod = UnsealInputModes.FORM_INPUT;
|
newMethod = UnsealInputModes.FORM_INPUT;
|
||||||
}
|
}
|
||||||
this.setState({ mode: newMethod });
|
this.setState({ mode: newMethod });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.state.mode == UnsealInputModes.QR_INPUT
|
{this.state.mode == UnsealInputModes.QR_INPUT
|
||||||
? i18next.t("unseal_input_btn")
|
? i18next.t("unseal_input_btn")
|
||||||
: i18next.t("unseal_qr_btn")}
|
: i18next.t("unseal_qr_btn")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnsealPage extends Page {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async render(): Promise<void> {
|
|
||||||
render(<UnsealPageElement page={this} />, this.router.pageContentElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("unseal_vault_text");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,6 +16,7 @@ module.exports = {
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
|
publicPath: '/',
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
colors: true,
|
colors: true,
|
||||||
|
@ -31,6 +32,7 @@ module.exports = {
|
||||||
],
|
],
|
||||||
devServer: {
|
devServer: {
|
||||||
open: process.env.BROWSER,
|
open: process.env.BROWSER,
|
||||||
|
historyApiFallback: true,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['node_modules'],
|
modules: ['node_modules'],
|
||||||
|
|
|
@ -41,6 +41,7 @@ module.exports = {
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
|
publicPath: '/',
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
colors: true,
|
colors: true,
|
||||||
|
@ -56,6 +57,7 @@ module.exports = {
|
||||||
],
|
],
|
||||||
devServer: {
|
devServer: {
|
||||||
open: process.env.BROWSER || "microsoft-edge-dev",
|
open: process.env.BROWSER || "microsoft-edge-dev",
|
||||||
|
historyApiFallback: true,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['node_modules'],
|
modules: ['node_modules'],
|
||||||
|
|
Loading…
Reference in a new issue