mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-18 02:06:01 +00:00
SYNC: settings storage changed
This commit is contained in:
@@ -289,16 +289,11 @@ module.exports = {
|
|||||||
const res = await lock.acquire('settings', async () => {
|
const res = await lock.acquire('settings', async () => {
|
||||||
const currentValue = await this.loadSettings();
|
const currentValue = await this.loadSettings();
|
||||||
try {
|
try {
|
||||||
let updated = currentValue;
|
let updated = {
|
||||||
|
...currentValue,
|
||||||
|
...values,
|
||||||
|
};
|
||||||
if (process.env.STORAGE_DATABASE) {
|
if (process.env.STORAGE_DATABASE) {
|
||||||
updated = {
|
|
||||||
...currentValue,
|
|
||||||
..._.mapValues(values, v => {
|
|
||||||
if (v === true) return 'true';
|
|
||||||
if (v === false) return 'false';
|
|
||||||
return v;
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
await storage.writeConfig({
|
await storage.writeConfig({
|
||||||
group: 'settings',
|
group: 'settings',
|
||||||
config: updated,
|
config: updated,
|
||||||
|
|||||||
@@ -360,6 +360,12 @@ module.exports = {
|
|||||||
"columnName": "value",
|
"columnName": "value",
|
||||||
"dataType": "varchar(1000)",
|
"dataType": "varchar(1000)",
|
||||||
"notNull": false
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pureName": "config",
|
||||||
|
"columnName": "valueType",
|
||||||
|
"dataType": "varchar(50)",
|
||||||
|
"notNull": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"foreignKeys": [],
|
"foreignKeys": [],
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ import { getSettings } from '../utility/metadataLoaders';
|
|||||||
import { isMac, switchCurrentDatabase } from '../utility/common';
|
import { isMac, switchCurrentDatabase } from '../utility/common';
|
||||||
import { doLogout } from '../clientAuth';
|
import { doLogout } from '../clientAuth';
|
||||||
import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte';
|
import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||||
import UploadErrorModal from '../modals/UploadErrorModal.svelte';
|
|
||||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
|
||||||
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
||||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||||
import localforage from 'localforage';
|
import localforage from 'localforage';
|
||||||
@@ -73,7 +71,8 @@ registerCommand({
|
|||||||
category: __t('command.theme', { defaultMessage: 'Theme' }),
|
category: __t('command.theme', { defaultMessage: 'Theme' }),
|
||||||
name: __t('command.theme.change', { defaultMessage: 'Change' }),
|
name: __t('command.theme.change', { defaultMessage: 'Change' }),
|
||||||
toolbarName: __t('command.theme.changeToolbar', { defaultMessage: 'Change theme' }),
|
toolbarName: __t('command.theme.changeToolbar', { defaultMessage: 'Change theme' }),
|
||||||
onClick: () => openNewTab({
|
onClick: () =>
|
||||||
|
openNewTab({
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
icon: 'icon settings',
|
icon: 'icon settings',
|
||||||
tabComponent: 'SettingsTab',
|
tabComponent: 'SettingsTab',
|
||||||
@@ -1230,8 +1229,7 @@ registerCommand({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( hasPermission('application-log'))
|
if (hasPermission('application-log')) {
|
||||||
{
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'app.showLogs',
|
id: 'app.showLogs',
|
||||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||||
@@ -1246,8 +1244,7 @@ if ( hasPermission('application-log'))
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPermission('widgets/plugins'))
|
if (hasPermission('widgets/plugins')) {
|
||||||
{
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'app.managePlugins',
|
id: 'app.managePlugins',
|
||||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||||
|
|||||||
@@ -1,81 +1,64 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CheckboxField from "../forms/CheckboxField.svelte";
|
import CheckboxField from '../forms/CheckboxField.svelte';
|
||||||
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
|
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||||
import FormFieldTemplateLarge from "../forms/FormFieldTemplateLarge.svelte";
|
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||||
import FormSelectField from "../forms/FormSelectField.svelte";
|
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||||
import FormTextField from "../forms/FormTextField.svelte";
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
import FormValues from "../forms/FormValues.svelte";
|
import FormValues from '../forms/FormValues.svelte';
|
||||||
import { lockedDatabaseMode } from "../stores";
|
import { lockedDatabaseMode } from '../stores';
|
||||||
import { _t } from "../translations";
|
import { _t } from '../translations';
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<FormValues let:values>
|
<FormValues let:values>
|
||||||
<div class="heading">{_t('settings.connection', { defaultMessage: 'Connection' })}</div>
|
<div class="heading">{_t('settings.connection', { defaultMessage: 'Connection' })}</div>
|
||||||
|
|
||||||
<FormFieldTemplateLarge
|
|
||||||
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', {
|
|
||||||
defaultMessage: 'Show only tabs from selected database',
|
|
||||||
})}
|
|
||||||
type="checkbox"
|
|
||||||
labelProps={{
|
|
||||||
onClick: () => {
|
|
||||||
$lockedDatabaseMode = !$lockedDatabaseMode;
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CheckboxField checked={$lockedDatabaseMode} on:change={e => ($lockedDatabaseMode = e.target.checked)} data-testid="ConnectionSettings_lockedDatabaseMode"/>
|
|
||||||
</FormFieldTemplateLarge>
|
|
||||||
|
|
||||||
<FormCheckboxField
|
<FormCheckboxField
|
||||||
name="connection.autoRefresh"
|
name="connection.autoRefresh"
|
||||||
label={_t('settings.connection.autoRefresh', {
|
label={_t('settings.connection.autoRefresh', {
|
||||||
defaultMessage: 'Automatic refresh of database model on background',
|
defaultMessage: 'Automatic refresh of database model on background',
|
||||||
})}
|
})}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
/>
|
/>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
name="connection.autoRefreshInterval"
|
name="connection.autoRefreshInterval"
|
||||||
label={_t('settings.connection.autoRefreshInterval', {
|
label={_t('settings.connection.autoRefreshInterval', {
|
||||||
defaultMessage: 'Interval between automatic DB structure reloads in seconds',
|
defaultMessage: 'Interval between automatic DB structure reloads in seconds',
|
||||||
})}
|
})}
|
||||||
defaultValue="30"
|
defaultValue="30"
|
||||||
disabled={values['connection.autoRefresh'] === false}
|
disabled={values['connection.autoRefresh'] === false}
|
||||||
/>
|
/>
|
||||||
<FormSelectField
|
<FormSelectField
|
||||||
label={_t('settings.connection.sshBindHost', { defaultMessage: 'Local host address for SSH connections' })}
|
label={_t('settings.connection.sshBindHost', { defaultMessage: 'Local host address for SSH connections' })}
|
||||||
name="connection.sshBindHost"
|
name="connection.sshBindHost"
|
||||||
isNative
|
isNative
|
||||||
defaultValue="127.0.0.1"
|
defaultValue="127.0.0.1"
|
||||||
options={[
|
options={[
|
||||||
{ value: '127.0.0.1', label: '127.0.0.1 (IPv4)' },
|
{ value: '127.0.0.1', label: '127.0.0.1 (IPv4)' },
|
||||||
{ value: '::1', label: '::1 (IPv6)' },
|
{ value: '::1', label: '::1 (IPv6)' },
|
||||||
{ value: 'localhost', label: 'localhost (domain name)' },
|
{ value: 'localhost', label: 'localhost (domain name)' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="heading">{_t('settings.session', { defaultMessage: 'Query sessions' })}</div>
|
<div class="heading">{_t('settings.session', { defaultMessage: 'Query sessions' })}</div>
|
||||||
<FormCheckboxField
|
<FormCheckboxField
|
||||||
name="session.autoClose"
|
name="session.autoClose"
|
||||||
label={_t('settings.session.autoClose', {
|
label={_t('settings.session.autoClose', {
|
||||||
defaultMessage: 'Automatic close query sessions after period without any activity',
|
defaultMessage: 'Automatic close query sessions after period without any activity',
|
||||||
})}
|
})}
|
||||||
defaultValue={true}
|
defaultValue={true}
|
||||||
/>
|
/>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
name="session.autoCloseTimeout"
|
name="session.autoCloseTimeout"
|
||||||
label={_t('settings.session.autoCloseTimeout', {
|
label={_t('settings.session.autoCloseTimeout', {
|
||||||
defaultMessage: 'Interval, after which query session without activity is closed (in minutes)',
|
defaultMessage: 'Interval, after which query session without activity is closed (in minutes)',
|
||||||
})}
|
})}
|
||||||
defaultValue="15"
|
defaultValue="15"
|
||||||
disabled={values['session.autoClose'] === false}
|
disabled={values['session.autoClose'] === false}
|
||||||
/>
|
/>
|
||||||
</FormValues>
|
</FormValues>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.heading {
|
.heading {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@@ -84,12 +67,11 @@
|
|||||||
margin-top: var(--dim-large-form-margin);
|
margin-top: var(--dim-large-form-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper :global(input){
|
.wrapper :global(input) {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper :global(select){
|
.wrapper :global(select) {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -10,6 +10,9 @@
|
|||||||
import { isMac } from '../utility/common';
|
import { isMac } from '../utility/common';
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||||
|
import hasPermission from '../utility/hasPermission';
|
||||||
|
import CheckboxField from '../forms/CheckboxField.svelte';
|
||||||
|
import { lockedDatabaseMode } from '../stores';
|
||||||
|
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
let restartWarning = false;
|
let restartWarning = false;
|
||||||
@@ -78,6 +81,24 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<FormFieldTemplateLarge
|
||||||
|
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', {
|
||||||
|
defaultMessage: 'Show only tabs from selected database',
|
||||||
|
})}
|
||||||
|
type="checkbox"
|
||||||
|
labelProps={{
|
||||||
|
onClick: () => {
|
||||||
|
$lockedDatabaseMode = !$lockedDatabaseMode;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckboxField
|
||||||
|
checked={$lockedDatabaseMode}
|
||||||
|
on:change={e => ($lockedDatabaseMode = e.target['checked'])}
|
||||||
|
data-testid="ConnectionSettings_lockedDatabaseMode"
|
||||||
|
/>
|
||||||
|
</FormFieldTemplateLarge>
|
||||||
|
|
||||||
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
|
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
|
||||||
|
|
||||||
{#if electron}
|
{#if electron}
|
||||||
@@ -106,6 +127,7 @@
|
|||||||
defaultMessage: 'Show server name alongside database name in title of the tab group',
|
defaultMessage: 'Show server name alongside database name in title of the tab group',
|
||||||
})}
|
})}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
|
disabled={!hasPermission('settings/change')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ export function getStringSettingsValue(name, defaultValue) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getObjectSettingsValue(name, defaultValue) {
|
||||||
|
const settings = getCurrentSettings();
|
||||||
|
const res = settings[name];
|
||||||
|
if (res == null) return defaultValue;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
export function getConnectionClickActionSetting(): 'connect' | 'openDetails' | 'none' {
|
export function getConnectionClickActionSetting(): 'connect' | 'openDetails' | 'none' {
|
||||||
return getStringSettingsValue('defaultAction.connectionClick', 'connect');
|
return getStringSettingsValue('defaultAction.connectionClick', 'connect');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,111 +3,114 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SettingsMenuControl from "../elements/SettingsMenuControl.svelte";
|
import SettingsMenuControl from '../elements/SettingsMenuControl.svelte';
|
||||||
import GeneralSettings from "../settings/GeneralSettings.svelte";
|
import GeneralSettings from '../settings/GeneralSettings.svelte';
|
||||||
import SettingsFormProvider from "../forms/SettingsFormProvider.svelte";
|
import SettingsFormProvider from '../forms/SettingsFormProvider.svelte';
|
||||||
import ConnectionSettings from "../settings/ConnectionSettings.svelte";
|
import ConnectionSettings from '../settings/ConnectionSettings.svelte';
|
||||||
import ThemeSettings from "../settings/ThemeSettings.svelte";
|
import ThemeSettings from '../settings/ThemeSettings.svelte';
|
||||||
import DefaultActionsSettings from "../settings/DefaultActionsSettings.svelte";
|
import DefaultActionsSettings from '../settings/DefaultActionsSettings.svelte';
|
||||||
import BehaviourSettings from "../settings/BehaviourSettings.svelte";
|
import BehaviourSettings from '../settings/BehaviourSettings.svelte';
|
||||||
import ExternalToolsSettings from "../settings/ExternalToolsSettings.svelte";
|
import ExternalToolsSettings from '../settings/ExternalToolsSettings.svelte';
|
||||||
import LicenseSettings from "../settings/LicenseSettings.svelte";
|
import LicenseSettings from '../settings/LicenseSettings.svelte';
|
||||||
import { isProApp } from "../utility/proTools";
|
import { isProApp } from '../utility/proTools';
|
||||||
import { _t } from "../translations";
|
import { _t } from '../translations';
|
||||||
import CommandListTab from "./CommandListTab.svelte";
|
import CommandListTab from './CommandListTab.svelte';
|
||||||
import DataGridSettings from "../settings/DataGridSettings.svelte";
|
import DataGridSettings from '../settings/DataGridSettings.svelte';
|
||||||
import SQLEditorSettings from "../settings/SQLEditorSettings.svelte";
|
import SQLEditorSettings from '../settings/SQLEditorSettings.svelte';
|
||||||
import AiSettingsTab from "../settings/AiSettingsTab.svelte";
|
import AiSettingsTab from '../settings/AiSettingsTab.svelte';
|
||||||
|
import hasPermission from '../utility/hasPermission';
|
||||||
|
|
||||||
export let selectedItem = 'general';
|
export let selectedItem = 'general';
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
label: _t('settings.general', { defaultMessage: 'General' }),
|
label: _t('settings.general', { defaultMessage: 'General' }),
|
||||||
identifier: 'general',
|
identifier: 'general',
|
||||||
component: GeneralSettings,
|
component: GeneralSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-general',
|
testid: 'settings-general',
|
||||||
},
|
},
|
||||||
{
|
hasPermission('settings/change') && {
|
||||||
label: _t('settings.connection', { defaultMessage: 'Connection' }),
|
label: _t('settings.connection', { defaultMessage: 'Connection' }),
|
||||||
identifier: 'connection',
|
identifier: 'connection',
|
||||||
component: ConnectionSettings,
|
component: ConnectionSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-connection',
|
testid: 'settings-connection',
|
||||||
},
|
},
|
||||||
{
|
hasPermission('settings/change') && {
|
||||||
label: _t('settings.dataGrid.title', { defaultMessage: 'Data grid' }),
|
label: _t('settings.dataGrid.title', { defaultMessage: 'Data grid' }),
|
||||||
identifier: 'data-grid',
|
identifier: 'data-grid',
|
||||||
component: DataGridSettings,
|
component: DataGridSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-data-grid',
|
testid: 'settings-data-grid',
|
||||||
},
|
},
|
||||||
{
|
hasPermission('settings/change') && {
|
||||||
label: _t('settings.sqlEditor.title', { defaultMessage: 'SQL Editor' }),
|
label: _t('settings.sqlEditor.title', { defaultMessage: 'SQL Editor' }),
|
||||||
identifier: 'sql-editor',
|
identifier: 'sql-editor',
|
||||||
component: SQLEditorSettings,
|
component: SQLEditorSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-sql-editor',
|
testid: 'settings-sql-editor',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: _t('settings.theme', { defaultMessage: 'Themes' }),
|
label: _t('settings.theme', { defaultMessage: 'Themes' }),
|
||||||
identifier: 'theme',
|
identifier: 'theme',
|
||||||
component: ThemeSettings,
|
component: ThemeSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-themes',
|
testid: 'settings-themes',
|
||||||
},
|
},
|
||||||
{
|
hasPermission('settings/change') && {
|
||||||
label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }),
|
label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }),
|
||||||
identifier: 'default-actions',
|
identifier: 'default-actions',
|
||||||
component: DefaultActionsSettings,
|
component: DefaultActionsSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-default-actions',
|
testid: 'settings-default-actions',
|
||||||
},
|
},
|
||||||
{
|
hasPermission('settings/change') && {
|
||||||
label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }),
|
label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }),
|
||||||
identifier: 'behaviour',
|
identifier: 'behaviour',
|
||||||
component: BehaviourSettings,
|
component: BehaviourSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-behaviour',
|
testid: 'settings-behaviour',
|
||||||
},
|
},
|
||||||
{
|
hasPermission('settings/change') && {
|
||||||
label: _t('settings.externalTools', { defaultMessage: 'External Tools' }),
|
label: _t('settings.externalTools', { defaultMessage: 'External Tools' }),
|
||||||
identifier: 'external-tools',
|
identifier: 'external-tools',
|
||||||
component: ExternalToolsSettings,
|
component: ExternalToolsSettings,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-external-tools',
|
testid: 'settings-external-tools',
|
||||||
},
|
},
|
||||||
{
|
hasPermission('settings/change') && {
|
||||||
label: _t('command.settings.shortcuts', { defaultMessage: 'Keyboard shortcuts' }),
|
label: _t('command.settings.shortcuts', { defaultMessage: 'Keyboard shortcuts' }),
|
||||||
identifier: 'shortcuts',
|
identifier: 'shortcuts',
|
||||||
component: CommandListTab,
|
component: CommandListTab,
|
||||||
props: {},
|
props: {},
|
||||||
testid: 'settings-shortcuts',
|
testid: 'settings-shortcuts',
|
||||||
},
|
},
|
||||||
isProApp() && {
|
hasPermission('settings/change') &&
|
||||||
label: _t('settings.license', { defaultMessage: 'License' }),
|
isProApp() && {
|
||||||
identifier: 'license',
|
label: _t('settings.license', { defaultMessage: 'License' }),
|
||||||
component: LicenseSettings,
|
identifier: 'license',
|
||||||
props: {},
|
component: LicenseSettings,
|
||||||
testid: 'settings-license',
|
props: {},
|
||||||
},
|
testid: 'settings-license',
|
||||||
isProApp() && {
|
},
|
||||||
label: _t('settings.AI', { defaultMessage: 'AI'}),
|
hasPermission('settings/change') &&
|
||||||
identifier: 'ai',
|
isProApp() && {
|
||||||
component: AiSettingsTab,
|
label: _t('settings.AI', { defaultMessage: 'AI' }),
|
||||||
props: {},
|
identifier: 'ai',
|
||||||
testid: 'settings-ai',
|
component: AiSettingsTab,
|
||||||
},
|
props: {},
|
||||||
];
|
testid: 'settings-ai',
|
||||||
|
},
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SettingsFormProvider>
|
<SettingsFormProvider>
|
||||||
<SettingsMenuControl
|
<SettingsMenuControl
|
||||||
items={menuItems}
|
items={menuItems}
|
||||||
bind:value={selectedItem}
|
bind:value={selectedItem}
|
||||||
flex1={true}
|
flex1={true}
|
||||||
flexColContainer={true}
|
flexColContainer={true}
|
||||||
scrollableContentContainer={true}
|
scrollableContentContainer={true}
|
||||||
/>
|
/>
|
||||||
</SettingsFormProvider>
|
</SettingsFormProvider>
|
||||||
Reference in New Issue
Block a user