add settings page
This commit is contained in:
parent
cbc52b7e63
commit
d4e9865ada
|
@ -9,7 +9,6 @@
|
||||||
"@babel/plugin-transform-runtime": "^7.16.8",
|
"@babel/plugin-transform-runtime": "^7.16.8",
|
||||||
"@babel/preset-env": "^7.16.8",
|
"@babel/preset-env": "^7.16.8",
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
"@types/hjson": "^2.4.3",
|
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/prismjs": "^1.16.6",
|
"@types/prismjs": "^1.16.6",
|
||||||
"@types/uikit": "^3.3.2",
|
"@types/uikit": "^3.3.2",
|
||||||
|
@ -41,7 +40,6 @@
|
||||||
"core-js": "^3.20.3",
|
"core-js": "^3.20.3",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"hjson": "^3.2.2",
|
|
||||||
"i18next": "^21.6.6",
|
"i18next": "^21.6.6",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json5": "^2.2.0",
|
"json5": "^2.2.0",
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { PolicyView } from "./ui/pages/Policies/PolicyView";
|
||||||
import { Secrets } from "./ui/pages/Secrets/SecretsHome";
|
import { Secrets } from "./ui/pages/Secrets/SecretsHome";
|
||||||
import { SetLanguage } from "./ui/pages/SetLanguage";
|
import { SetLanguage } from "./ui/pages/SetLanguage";
|
||||||
import { SetVaultURL } from "./ui/pages/SetVaultURL";
|
import { SetVaultURL } from "./ui/pages/SetVaultURL";
|
||||||
|
import { Settings } from "./ui/pages/Settings/Settings";
|
||||||
import { TOTPDelete } from "./ui/pages/Secrets/TOTP/TOTPDelete";
|
import { TOTPDelete } from "./ui/pages/Secrets/TOTP/TOTPDelete";
|
||||||
import { TOTPList } from "./ui/pages/Secrets/TOTP/TOTPList";
|
import { TOTPList } from "./ui/pages/Secrets/TOTP/TOTPList";
|
||||||
import { TOTPNew } from "./ui/pages/Secrets/TOTP/TOTPNew";
|
import { TOTPNew } from "./ui/pages/Secrets/TOTP/TOTPNew";
|
||||||
|
@ -59,6 +60,7 @@ export const Main = () => (
|
||||||
<SetVaultURL path="/set_vault_url" settings={settings} api={api} />
|
<SetVaultURL path="/set_vault_url" settings={settings} api={api} />
|
||||||
<Unseal path="/unseal" settings={settings} api={api} />
|
<Unseal path="/unseal" settings={settings} api={api} />
|
||||||
<SetLanguage path="/set_language" settings={settings} api={api} />
|
<SetLanguage path="/set_language" settings={settings} api={api} />
|
||||||
|
<Settings path="/settings" settings={settings} api={api} />
|
||||||
|
|
||||||
<Secrets path="/secrets" settings={settings} api={api} />
|
<Secrets path="/secrets" settings={settings} api={api} />
|
||||||
<DeleteSecretsEngine path="/secrets/delete_engine/:mount" settings={settings} api={api} />
|
<DeleteSecretsEngine path="/secrets/delete_engine/:mount" settings={settings} api={api} />
|
||||||
|
|
|
@ -64,4 +64,24 @@ export class Settings {
|
||||||
this.storage.setItem("theme", value);
|
this.storage.setItem("theme", value);
|
||||||
this.alertChange("theme");
|
this.alertChange("theme");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get kvEditorDefaultLanguage(): string {
|
||||||
|
return this.storage.getItem("kvEditorDefaultLanguage") || "yaml";
|
||||||
|
}
|
||||||
|
|
||||||
|
set kvEditorDefaultLanguage(value: string) {
|
||||||
|
this.storage.setItem("kvEditorDefaultLanguage", value);
|
||||||
|
this.alertChange("kvEditorDefaultLanguage");
|
||||||
|
}
|
||||||
|
|
||||||
|
get kvEditorIndent(): number {
|
||||||
|
const value = this.storage.getItem("kvEditorIndent");
|
||||||
|
if (value) return parseInt(value);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
set kvEditorIndent(value: number) {
|
||||||
|
this.storage.setItem("kvEditorIndent", String(value));
|
||||||
|
this.alertChange("kvEditorIndent");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
26
src/translations/en.js
vendored
26
src/translations/en.js
vendored
|
@ -35,9 +35,9 @@ module.exports = {
|
||||||
not_implemented: "Not Yet Implemented",
|
not_implemented: "Not Yet Implemented",
|
||||||
|
|
||||||
// Copyable Modal
|
// Copyable Modal
|
||||||
copy_box_download_btn: "Download",
|
copy_modal_download_btn: "Download",
|
||||||
copy_box_copy_btn: "Copy",
|
copy_modal_copy_btn: "Copy",
|
||||||
copy_box_close_btn: "Close",
|
copy_modal_close_btn: "Close",
|
||||||
|
|
||||||
// Generic Loading Text
|
// Generic Loading Text
|
||||||
content_loading: "Loading..",
|
content_loading: "Loading..",
|
||||||
|
@ -54,8 +54,7 @@ module.exports = {
|
||||||
me_seal_vault_btn: "Seal Vault",
|
me_seal_vault_btn: "Seal Vault",
|
||||||
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_settings_btn: "Settings",
|
||||||
me_set_vault_url_btn: "Set Vault URL",
|
|
||||||
|
|
||||||
// Home Page
|
// Home Page
|
||||||
home_page_title: "Home",
|
home_page_title: "Home",
|
||||||
|
@ -69,6 +68,23 @@ module.exports = {
|
||||||
home_policies_title: "Policies",
|
home_policies_title: "Policies",
|
||||||
home_policies_description: "Manage policies and permissions.",
|
home_policies_description: "Manage policies and permissions.",
|
||||||
|
|
||||||
|
// Settings Page
|
||||||
|
settings_title: "Settings",
|
||||||
|
|
||||||
|
// General Settings
|
||||||
|
settings_general_title: "General",
|
||||||
|
settings_general_theme: "Theme",
|
||||||
|
settings_general_vault_url: "Vault API URL",
|
||||||
|
settings_general_language: "Language",
|
||||||
|
settings_general_page_direction: "Page Direction",
|
||||||
|
settings_general_page_direction_ltr: "Left to Right",
|
||||||
|
settings_general_page_direction_rtl: "Right to Left",
|
||||||
|
|
||||||
|
// Key/Value Editor Settings
|
||||||
|
settings_kveditor_title: "Key/Value Editor",
|
||||||
|
settings_kveditor_default_language: "Default Data Interchange Format",
|
||||||
|
settings_kveditor_default_indent: "Default Indent",
|
||||||
|
|
||||||
// Set Vault URL Page
|
// Set Vault URL Page
|
||||||
set_vault_url_title: "Set Vault URL",
|
set_vault_url_title: "Set Vault URL",
|
||||||
set_vault_url_placeholder: "Vault URL",
|
set_vault_url_placeholder: "Vault URL",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { Button } from "../elements/Button";
|
||||||
import { Component, JSX, createRef } from "preact";
|
import { Component, JSX, createRef } from "preact";
|
||||||
import { DefaultPageProps } from "../../types/DefaultPageProps";
|
import { DefaultPageProps } from "../../types/DefaultPageProps";
|
||||||
import { InputWithTitle } from "../elements/InputWithTitle";
|
|
||||||
import { PageTitle } from "../elements/PageTitle";
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
import { addClipboardNotifications, setErrorText } from "../../pageUtils";
|
import { addClipboardNotifications, setErrorText } from "../../pageUtils";
|
||||||
import { route } from "preact-router";
|
import { route } from "preact-router";
|
||||||
|
@ -103,30 +103,10 @@ export class Me extends Component<DefaultPageProps, MeState> {
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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>
|
|
||||||
|
|
||||||
<InputWithTitle title="Theme">
|
<br />
|
||||||
<select
|
|
||||||
ref={this.themeSelectRef}
|
<Button text={i18next.t("me_settings_btn")} color="primary" route="/settings" />
|
||||||
class="uk-select uk-form-width-medium"
|
|
||||||
onChange={() => {
|
|
||||||
const newTheme = this.themeSelectRef.current.value;
|
|
||||||
this.props.settings.theme = newTheme;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option label="Dark" value="dark" selected={this.props.settings.theme == "dark"} />
|
|
||||||
<option
|
|
||||||
label="Light"
|
|
||||||
value="light"
|
|
||||||
selected={this.props.settings.theme == "light"}
|
|
||||||
/>
|
|
||||||
</select>
|
|
||||||
</InputWithTitle>
|
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,38 +6,31 @@ import { InputWithTitle } from "../../../elements/InputWithTitle";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { setErrorText } from "../../../../pageUtils";
|
import { setErrorText } from "../../../../pageUtils";
|
||||||
import { sortedObjectMap } from "../../../../utils";
|
import { sortedObjectMap } from "../../../../utils";
|
||||||
import Hjson from "hjson";
|
|
||||||
import JSON5 from "json5";
|
import JSON5 from "json5";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
|
|
||||||
// todo: put this in settings
|
export const SupportedEditorLanguages = [
|
||||||
const defaultIndent = 2;
|
{ name: "json", readable: "JSON" },
|
||||||
const defaultSyntax = "json";
|
{ name: "json5", readable: "JSON5" },
|
||||||
|
{ name: "yaml", readable: "YAML" },
|
||||||
|
];
|
||||||
|
|
||||||
function parseData(data: string, syntax = "json"): Record<string, unknown> {
|
function parseData(data: string, syntax = "json"): Record<string, unknown> {
|
||||||
if (syntax == "json") {
|
if (syntax == "json") {
|
||||||
return JSON.parse(data) as Record<string, unknown>;
|
return JSON.parse(data) as Record<string, unknown>;
|
||||||
} else if (syntax == "json5") {
|
} else if (syntax == "json5") {
|
||||||
return JSON5.parse(data);
|
return JSON5.parse(data);
|
||||||
} else if (syntax == "hjson") {
|
|
||||||
return Hjson.parse(data) as Record<string, unknown>;
|
|
||||||
} else if (syntax == "yaml") {
|
} else if (syntax == "yaml") {
|
||||||
return yaml.load(data) as Record<string, unknown>;
|
return yaml.load(data) as Record<string, unknown>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dumpData(
|
function dumpData(data: Record<string, unknown>, space = 4, syntax = "json"): string {
|
||||||
data: Record<string, unknown>,
|
|
||||||
space: number = defaultIndent,
|
|
||||||
syntax = "json",
|
|
||||||
): string {
|
|
||||||
if (syntax == "json") {
|
if (syntax == "json") {
|
||||||
return JSON.stringify(data, null, space);
|
return JSON.stringify(data, null, space);
|
||||||
} else if (syntax == "json5") {
|
} else if (syntax == "json5") {
|
||||||
return JSON5.stringify(data, null, space);
|
return JSON5.stringify(data, null, space);
|
||||||
} else if (syntax == "hjson") {
|
|
||||||
return Hjson.stringify(data, { space: space });
|
|
||||||
} else if (syntax == "yaml") {
|
} else if (syntax == "yaml") {
|
||||||
return yaml.dump(data, { indent: space });
|
return yaml.dump(data, { indent: space });
|
||||||
}
|
}
|
||||||
|
@ -77,7 +70,7 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
dataLoaded: false,
|
dataLoaded: false,
|
||||||
syntax: defaultSyntax,
|
syntax: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,13 +112,18 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
this.setState({ syntax: this.props.settings.kvEditorDefaultLanguage });
|
||||||
if (!this.state.dataLoaded) {
|
if (!this.state.dataLoaded) {
|
||||||
await this.loadData();
|
await this.loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStringKVData(data: Record<string, unknown>): string {
|
getStringKVData(data: Record<string, unknown>): string {
|
||||||
return dumpData(Object.fromEntries(sortedObjectMap(data)), defaultIndent, this.state.syntax);
|
return dumpData(
|
||||||
|
Object.fromEntries(sortedObjectMap(data)),
|
||||||
|
this.props.settings.kvEditorIndent,
|
||||||
|
this.state.syntax,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
syntaxSelectRef = createRef<HTMLSelectElement>();
|
syntaxSelectRef = createRef<HTMLSelectElement>();
|
||||||
|
@ -141,8 +139,6 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
codeEditorLanguage = "json";
|
codeEditorLanguage = "json";
|
||||||
} else if (this.state.syntax == "json5") {
|
} else if (this.state.syntax == "json5") {
|
||||||
codeEditorLanguage = "json5";
|
codeEditorLanguage = "json5";
|
||||||
} else if (this.state.syntax == "hjson") {
|
|
||||||
codeEditorLanguage = "js";
|
|
||||||
} else if (this.state.syntax == "yaml") {
|
} else if (this.state.syntax == "yaml") {
|
||||||
codeEditorLanguage = "yaml";
|
codeEditorLanguage = "yaml";
|
||||||
}
|
}
|
||||||
|
@ -157,16 +153,21 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
|
||||||
this.setState({ syntax: this.syntaxSelectRef.current.value });
|
this.setState({ syntax: this.syntaxSelectRef.current.value });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option label="JSON" value="json" />
|
{SupportedEditorLanguages.map((lang) => {
|
||||||
<option label="JSON5" value="json5" />
|
return (
|
||||||
<option label="Hjson" value="hjson" />
|
<option
|
||||||
<option label="Yaml" value="yaml" />
|
label={lang.readable}
|
||||||
|
value={lang.name}
|
||||||
|
selected={this.props.settings.kvEditorDefaultLanguage == lang.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</select>
|
</select>
|
||||||
</InputWithTitle>
|
</InputWithTitle>
|
||||||
<p class="uk-text-danger" id="errorText" />
|
<p class="uk-text-danger" id="errorText" />
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
language={codeEditorLanguage}
|
language={codeEditorLanguage}
|
||||||
tabSize={defaultIndent}
|
tabSize={this.props.settings.kvEditorIndent}
|
||||||
code={this.getStringKVData(this.state.kvData)}
|
code={this.getStringKVData(this.state.kvData)}
|
||||||
onUpdate={(code) => this.onCodeUpdate(code)}
|
onUpdate={(code) => this.onCodeUpdate(code)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -25,12 +25,7 @@ function SecretsList(baseMount: string, secretPath: string[], secrets: string[])
|
||||||
<a
|
<a
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (secret.endsWith("/")) {
|
if (secret.endsWith("/")) {
|
||||||
route(
|
route(kvListURL(baseMount, [...secretPath, secret.replace("/", "")]));
|
||||||
kvListURL(
|
|
||||||
baseMount,
|
|
||||||
[...secretPath, secret.replace("/", "")]
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
route(kvViewURL(baseMount, secretPath, secret));
|
route(kvViewURL(baseMount, secretPath, secret));
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class KeyValueNew extends Component<DefaultPageProps> {
|
||||||
const mountInfo = await this.props.api.getMount(baseMount);
|
const mountInfo = await this.props.api.getMount(baseMount);
|
||||||
if (mountInfo.options.version == "1") {
|
if (mountInfo.options.version == "1") {
|
||||||
// Can't have a empty secret on KV V1
|
// Can't have a empty secret on KV V1
|
||||||
keyData = { "placeholder_on_kv1": "placeholder_on_kv1" };
|
keyData = { placeholder_on_kv1: "placeholder_on_kv1" };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -71,7 +71,7 @@ export class RefreshingTOTPGridItem extends Component<TOTPGridItemProps, { totpV
|
||||||
type TOTPItem = {
|
type TOTPItem = {
|
||||||
totpKey: string;
|
totpKey: string;
|
||||||
canDelete: boolean;
|
canDelete: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
type TOTPListState = {
|
type TOTPListState = {
|
||||||
capabilities?: CapabilitiesType;
|
capabilities?: CapabilitiesType;
|
||||||
|
@ -98,19 +98,25 @@ export class TOTPList extends Component<DefaultPageProps, TOTPListState> {
|
||||||
try {
|
try {
|
||||||
const totpKeys = await api.getTOTPKeys(baseMount);
|
const totpKeys = await api.getTOTPKeys(baseMount);
|
||||||
|
|
||||||
let totpKeyPermissions = await Promise.all(Array.from(totpKeys.map(async (key) => {
|
const totpKeyPermissions = await Promise.all(
|
||||||
const totpCaps = await api.getCapsPath(removeDoubleSlash(baseMount + "/code/" + key));
|
Array.from(
|
||||||
return {key: key, caps: totpCaps};
|
totpKeys.map(async (key) => {
|
||||||
})));
|
const totpCaps = await api.getCapsPath(removeDoubleSlash(baseMount + "/code/" + key));
|
||||||
|
return { key: key, caps: totpCaps };
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
totpItems = Array.from(totpKeyPermissions.map((keyData) => {
|
totpItems = Array.from(
|
||||||
// Filter out all non-readable totp keys.
|
totpKeyPermissions.map((keyData) => {
|
||||||
if (!keyData.caps.includes("read")) return;
|
// Filter out all non-readable totp keys.
|
||||||
return {
|
if (!keyData.caps.includes("read")) return;
|
||||||
totpKey: keyData.key,
|
return {
|
||||||
canDelete: keyData.caps.includes("delete"),
|
totpKey: keyData.key,
|
||||||
};
|
canDelete: keyData.caps.includes("delete"),
|
||||||
}));
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
if (error != DoesNotExistError) {
|
if (error != DoesNotExistError) {
|
||||||
|
@ -166,11 +172,7 @@ export class TOTPList extends Component<DefaultPageProps, TOTPListState> {
|
||||||
} else {
|
} else {
|
||||||
return this.state.totpItems.map((totpItem) => {
|
return this.state.totpItems.map((totpItem) => {
|
||||||
return (
|
return (
|
||||||
<RefreshingTOTPGridItem
|
<RefreshingTOTPGridItem {...this.props} baseMount={baseMount} {...totpItem} />
|
||||||
{...this.props}
|
|
||||||
baseMount={baseMount}
|
|
||||||
{...totpItem}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,8 @@ 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 { PageTitle } from "../elements/PageTitle";
|
import { PageTitle } from "../elements/PageTitle";
|
||||||
import i18next from "i18next";
|
|
||||||
import { route } from "preact-router";
|
import { route } from "preact-router";
|
||||||
|
import i18next from "i18next";
|
||||||
const languageIDs = Object.getOwnPropertyNames(translations);
|
|
||||||
|
|
||||||
export class SetLanguage extends Component<DefaultPageProps> {
|
export class SetLanguage extends Component<DefaultPageProps> {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -25,8 +23,8 @@ export class SetLanguage extends Component<DefaultPageProps> {
|
||||||
<Form onSubmit={(data) => this.onSubmit(data)}>
|
<Form onSubmit={(data) => this.onSubmit(data)}>
|
||||||
<Margin>
|
<Margin>
|
||||||
<select class="uk-select uk-form-width-large" name="language">
|
<select class="uk-select uk-form-width-large" name="language">
|
||||||
{languageIDs.map((languageID) => (
|
{Object.getOwnPropertyNames(translations).map((languageID) => (
|
||||||
<option value={languageID}>
|
<option value={languageID} selected={this.props.settings.language == languageID}>
|
||||||
{i18next.getFixedT(languageID, null)("language_name")}
|
{i18next.getFixedT(languageID, null)("language_name")}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
|
105
src/ui/pages/Settings/GeneralSettings.tsx
Normal file
105
src/ui/pages/Settings/GeneralSettings.tsx
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import { Component, createRef } from "preact";
|
||||||
|
import { DefaultPageProps } from "../../../types/DefaultPageProps";
|
||||||
|
import { InputWithTitle } from "../../elements/InputWithTitle";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import translations from "../../../translations/index.mjs";
|
||||||
|
|
||||||
|
const Themes = [
|
||||||
|
{ name: "dark", readable: "Dark" },
|
||||||
|
{ name: "light", readable: "Light" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export class GeneralSettings extends Component<DefaultPageProps> {
|
||||||
|
themeSelectRef = createRef<HTMLSelectElement>();
|
||||||
|
vaultURLInputRef = createRef<HTMLInputElement>();
|
||||||
|
pageDirectionRef = createRef<HTMLSelectElement>();
|
||||||
|
languageSelectRef = createRef<HTMLSelectElement>();
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4>{i18next.t("settings_general_title")}</h4>
|
||||||
|
|
||||||
|
<InputWithTitle title={i18next.t("settings_general_theme")}>
|
||||||
|
<select
|
||||||
|
ref={this.themeSelectRef}
|
||||||
|
class="uk-select uk-form-width-medium"
|
||||||
|
onChange={() => {
|
||||||
|
const newTheme = this.themeSelectRef.current.value;
|
||||||
|
this.props.settings.theme = newTheme;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Themes.map((theme) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
label={theme.readable}
|
||||||
|
value={theme.name}
|
||||||
|
selected={this.props.settings.theme == theme.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</InputWithTitle>
|
||||||
|
|
||||||
|
<InputWithTitle title={i18next.t("settings_general_vault_url")}>
|
||||||
|
<input
|
||||||
|
ref={this.vaultURLInputRef}
|
||||||
|
class="uk-input uk-form-width-medium"
|
||||||
|
value={this.props.settings.apiURL}
|
||||||
|
onChange={() => {
|
||||||
|
// TODO: check for api health to see if is valid api url.
|
||||||
|
this.props.settings.apiURL = this.vaultURLInputRef.current.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InputWithTitle>
|
||||||
|
|
||||||
|
<InputWithTitle title={i18next.t("settings_general_language")}>
|
||||||
|
<select
|
||||||
|
ref={this.languageSelectRef}
|
||||||
|
class="uk-select uk-form-width-medium"
|
||||||
|
onChange={async () => {
|
||||||
|
const language = this.languageSelectRef.current.value;
|
||||||
|
this.props.settings.language = language;
|
||||||
|
|
||||||
|
const t = await i18next.changeLanguage(language);
|
||||||
|
this.props.settings.pageDirection = t("language_direction");
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.getOwnPropertyNames(translations).map((languageID) => (
|
||||||
|
<option value={languageID} selected={this.props.settings.language == languageID}>
|
||||||
|
{i18next.getFixedT(languageID, null)("language_name")}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</InputWithTitle>
|
||||||
|
|
||||||
|
<InputWithTitle title={i18next.t("settings_general_page_direction")}>
|
||||||
|
<select
|
||||||
|
ref={this.pageDirectionRef}
|
||||||
|
class="uk-select uk-form-width-medium"
|
||||||
|
onChange={() => {
|
||||||
|
this.props.settings.pageDirection = this.pageDirectionRef.current.value;
|
||||||
|
document.documentElement.dir = this.props.settings.pageDirection;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ name: "ltr", readable: i18next.t("settings_general_page_direction_ltr") },
|
||||||
|
{ name: "rtl", readable: i18next.t("settings_general_page_direction_rtl") },
|
||||||
|
].map((direction) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
label={direction.readable}
|
||||||
|
value={direction.name}
|
||||||
|
selected={this.props.settings.pageDirection == direction.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</InputWithTitle>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
51
src/ui/pages/Settings/KeyValueEditorSettings.tsx
Normal file
51
src/ui/pages/Settings/KeyValueEditorSettings.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { Component, createRef } from "preact";
|
||||||
|
import { DefaultPageProps } from "../../../types/DefaultPageProps";
|
||||||
|
import { InputWithTitle } from "../../elements/InputWithTitle";
|
||||||
|
import { SupportedEditorLanguages } from "../Secrets/KeyValue/KeyValueEdit";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
export class KeyValueEditorSettings extends Component<DefaultPageProps> {
|
||||||
|
syntaxSelectRef = createRef<HTMLSelectElement>();
|
||||||
|
indentInputRef = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4>{i18next.t("settings_kveditor_title")}</h4>
|
||||||
|
|
||||||
|
<InputWithTitle title={i18next.t("settings_kveditor_default_language")}>
|
||||||
|
<select
|
||||||
|
ref={this.syntaxSelectRef}
|
||||||
|
class="uk-select uk-form-width-medium"
|
||||||
|
onChange={() => {
|
||||||
|
this.props.settings.kvEditorDefaultLanguage = this.syntaxSelectRef.current.value;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{SupportedEditorLanguages.map((lang) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
label={lang.readable}
|
||||||
|
value={lang.name}
|
||||||
|
selected={this.props.settings.kvEditorDefaultLanguage == lang.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</InputWithTitle>
|
||||||
|
<InputWithTitle title={i18next.t("settings_kveditor_default_indent")}>
|
||||||
|
<input
|
||||||
|
ref={this.indentInputRef}
|
||||||
|
class="uk-input uk-form-width-medium"
|
||||||
|
type="number"
|
||||||
|
value={this.props.settings.kvEditorIndent}
|
||||||
|
onChange={() => {
|
||||||
|
const value = this.indentInputRef.current.value;
|
||||||
|
const indent = parseInt(value);
|
||||||
|
this.props.settings.kvEditorIndent = indent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InputWithTitle>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
src/ui/pages/Settings/Settings.tsx
Normal file
22
src/ui/pages/Settings/Settings.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Component } from "preact";
|
||||||
|
import { DefaultPageProps } from "../../../types/DefaultPageProps";
|
||||||
|
import { GeneralSettings } from "./GeneralSettings";
|
||||||
|
import { KeyValueEditorSettings } from "./KeyValueEditorSettings";
|
||||||
|
import { PageTitle } from "../../elements/PageTitle";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
export class Settings extends Component<DefaultPageProps> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageTitle title={i18next.t("settings_title")} />
|
||||||
|
<div>
|
||||||
|
<GeneralSettings {...this.props} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<KeyValueEditorSettings {...this.props} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue