dbeaver/pro#5087 feat: gql api (#3400)

* dbeaver/pro#5087 Add role settings

* dbeaver/pro#5087 feat: gql api

* dbeaver/pro#5087 feat: gql api

* dbeaver/pro#5087 Add role settings

* feat: user role settings

* chore: add tailwind intellisense

* chore: remove mock data

* dbeaver/pro#5087 Add role settings

* dbeaver/pro#5087 Add role settings

* dbeaver/pro#5087 Add role settings

* fix: update roles enum

* feat: settings restore and set status

* dbeaver/pro#5087 fix roles

* chore: hide single settings tab, allow reset for settings that doesn't support full reset

* chore: remove `isSet` method from settings API

* refactor: extract repeatable logic

* chore: add server setting

* chore: better settings grouping

* dbeaver/pro#5087 fixes

* fix: review

* chore: remove roles related code

* chore: hide spreadsheet hiding setting

* dbeaver/pro#5087 Removed everything related with authRole

* chore: hide some settings from UI

* chore: hide per-setting reset

* fix: selecting empty settings groups

---------

Co-authored-by: Ruslan <pupkinvasya7000@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
Alexey
2025-05-14 00:01:08 +08:00
committed by GitHub
co-authored by Ruslan Evgenia
parent ba4f1c9db7
commit 295fcf8701
115 changed files with 1049 additions and 398 deletions
@@ -19,8 +19,8 @@ package io.cloudbeaver.model.config;
import com.google.gson.annotations.Expose;
import io.cloudbeaver.auth.provider.local.LocalAuthProviderConstants;
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.app.ServletAuthConfiguration;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
import io.cloudbeaver.registry.WebAuthProviderRegistry;
import org.jkiss.code.NotNull;
@@ -37,7 +37,13 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
// to avoid redundant sm api call
public void updatePreferenceValues(@NotNull Map<String, Object> newValues) {
userPreferences.putAll(newValues);
for (Map.Entry<String, Object> entry : newValues.entrySet()) {
if (entry.getValue() == null) {
userPreferences.remove(entry.getKey());
} else {
userPreferences.put(entry.getKey(), entry.getValue());
}
}
}
@Override
@@ -89,13 +89,13 @@ public class ServletAppUtils {
var additionalValue = additional.get(rootKey);
if (originValue == null || additionalValue == null) {
Object resultValue = null;
if (additional.containsKey(rootKey)) {
resultValue = additionalValue;
if (additionalValue != null) {
resultConfig.put(rootKey, additionalValue);
}
} else if (originValue != null) {
resultValue = originValue;
resultConfig.put(rootKey, originValue);
}
resultConfig.put(rootKey, resultValue);
continue;
}
@@ -29,6 +29,7 @@
"@cloudbeaver/core-root": "workspace:*",
"@cloudbeaver/core-routing": "workspace:*",
"@cloudbeaver/core-sdk": "workspace:*",
"@cloudbeaver/core-settings": "workspace:*",
"@cloudbeaver/core-storage": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
"@dbeaver/js-helpers": "workspace:^",
@@ -0,0 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-settings';
export const ADMINISTRATION_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_administration_settings_group');
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -17,3 +17,4 @@ export * from './AdministrationScreen/ConfigurationWizard/ConfigurationWizardSer
export * from './DataContext/DATA_CONTEXT_ADMINISTRATION_ITEM_ROUTE.js';
export * from './AdministrationLocaleService.js';
export * from './PermissionsResource.js';
export * from './ADMINISTRATION_SETTINGS_GROUP.js';
@@ -1,5 +1,6 @@
export default [
['administration_menu_enter', 'Administration'],
['core_administration_settings_group', 'Administration'],
['administration_menu_back', 'Back to app'],
['administration_configuration_wizard_title', 'Initial Server Configuration'],
['administration_configuration_wizard_finish_success_title', 'Server configured'],
@@ -1,5 +1,6 @@
export default [
['administration_menu_enter', 'Administration'],
['core_administration_settings_group', 'Administration'],
['administration_menu_back', "Retour à l'application"],
['administration_configuration_wizard_title', 'Configuration initiale du serveur'],
['administration_configuration_wizard_finish_success_title', 'Serveur configuré'],
@@ -1,5 +1,6 @@
export default [
['administration_menu_enter', 'Amministrazione'],
['core_administration_settings_group', 'Administration'],
['administration_menu_back', 'Torna indietro'],
['administration_configuration_wizard_title', 'Configurazione Iniziale del Server'],
['administration_configuration_wizard_finish_success_title', 'Server configured'],
@@ -1,5 +1,6 @@
export default [
['administration_menu_enter', 'Администрирование'],
['core_administration_settings_group', 'Администрирование'],
['administration_menu_back', 'Вернуться в приложение'],
['administration_configuration_wizard_title', 'Начальная настройка сервера'],
['administration_configuration_wizard_finish_success_title', 'Сервер настроен'],
@@ -1,5 +1,6 @@
export default [
['administration_menu_enter', '管理'],
['core_administration_settings_group', 'Administration'],
['administration_menu_back', '返回应用'],
['administration_configuration_wizard_title', '初始化服务器配置'],
['administration_configuration_wizard_finish_success_title', '服务器已完成配置'],
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -43,6 +43,9 @@
{
"path": "../core-sdk"
},
{
"path": "../core-settings"
},
{
"path": "../core-storage"
},
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -9,7 +9,7 @@
export enum ELMRole {
ADMINISTRATOR = 'ADMINISTRATOR',
DEVELOPER = 'DEVELOPER',
DATA_MANAGER = 'DATA_MANAGER',
MANAGER = 'MANAGER',
EDITOR = 'EDITOR',
VIEWER = 'VIEWER',
}
@@ -11,23 +11,13 @@ import { injectable } from '@cloudbeaver/core-di';
import { AutoRunningTask, type ISyncExecutor, type ITask, SyncExecutor, whileTask } from '@cloudbeaver/core-executor';
import { CachedDataResource, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { SessionResource } from '@cloudbeaver/core-root';
import {
type FederatedAuthInfo,
type AuthInfo,
type AuthLogoutQuery,
AuthStatus,
type GetActiveUserQueryVariables,
GraphQLService,
type UserInfo,
} from '@cloudbeaver/core-sdk';
import { type FederatedAuthInfo, type AuthInfo, type AuthLogoutQuery, AuthStatus, GraphQLService, type UserInfo } from '@cloudbeaver/core-sdk';
import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID.js';
import { AuthProviderService } from './AuthProviderService.js';
import type { ELMRole } from './ELMRole.js';
import type { IAuthCredentials } from './IAuthCredentials.js';
export type UserInfoIncludes = GetActiveUserQueryVariables;
export type UserLogoutInfo = AuthLogoutQuery['result'];
export interface ILoginOptions {
@@ -42,7 +32,7 @@ export type IFederatedLoginOptions = Omit<ILoginOptions, 'credentials'>;
export const ANONYMOUS_USER_ID = 'anonymous';
@injectable()
export class UserInfoResource extends CachedDataResource<UserInfo | null, void, UserInfoIncludes> {
export class UserInfoResource extends CachedDataResource<UserInfo | null, void> {
readonly onUserChange: ISyncExecutor<string>;
readonly onException: ISyncExecutor<Error>;
@@ -63,7 +53,7 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
private readonly authProviderService: AuthProviderService,
private readonly sessionResource: SessionResource,
) {
super(() => null, undefined, ['includeConfigurationParameters']);
super(() => null);
this.onUserChange = new SyncExecutor();
this.onException = new SyncExecutor();
@@ -210,8 +200,6 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
await this.performUpdate(undefined, [], async () => {
const { user } = await this.graphQLService.sdk.updateUserPreferences({
preferences,
...this.getDefaultIncludes(),
...this.getIncludesMap(),
});
this.setData(user as UserInfo | null);
@@ -291,12 +279,9 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
this.sessionResource.markOutdated();
}
protected async loader(key: void, includes?: ReadonlyArray<string>): Promise<UserInfo | null> {
protected async loader(key: void): Promise<UserInfo | null> {
try {
const { user } = await this.graphQLService.sdk.getActiveUser({
...this.getDefaultIncludes(),
...this.getIncludesMap(key, includes),
});
const { user } = await this.graphQLService.sdk.getActiveUser({});
return (user as UserInfo | null) || null;
} catch (exception: any) {
@@ -317,10 +302,4 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
this.onUserChange.execute(currentUserId);
}
}
private getDefaultIncludes(): UserInfoIncludes {
return {
includeConfigurationParameters: false,
};
}
}
@@ -7,4 +7,10 @@ export default [
['core_authentication_password_policy_upper_lower_case', 'Password must contain both upper and lower case letters'],
['core_authentication_password_policy_min_digits', 'Password must contain at least {arg:min} digits'],
['core_authentication_password_policy_min_special_characters', 'Password must contain at least {arg:min} special characters'],
['core_authentication_role_administrator', 'Administrator'],
['core_authentication_role_manager', 'Manager'],
['core_authentication_role_editor', 'Editor'],
['core_authentication_role_viewer', 'Viewer'],
['core_authentication_role_developer', 'Developer'],
];
@@ -7,4 +7,10 @@ export default [
['core_authentication_password_policy_upper_lower_case', 'Le mot de passe doit contenir des lettres majuscules et minuscules'],
['core_authentication_password_policy_min_digits', 'Le mot de passe doit contenir au moins {arg:min} chiffres'],
['core_authentication_password_policy_min_special_characters', 'Le mot de passe doit contenir au moins {arg:min} caractères spéciaux'],
['core_authentication_role_administrator', 'Administrator'],
['core_authentication_role_manager', 'Manager'],
['core_authentication_role_editor', 'Editor'],
['core_authentication_role_viewer', 'Viewer'],
['core_authentication_role_developer', 'Developer'],
];
@@ -7,4 +7,10 @@ export default [
['core_authentication_password_policy_upper_lower_case', 'Password must contain both upper and lower case letters'],
['core_authentication_password_policy_min_digits', 'Password must contain at least {arg:min} digits'],
['core_authentication_password_policy_min_special_characters', 'Password must contain at least {arg:min} special characters'],
['core_authentication_role_administrator', 'Administrator'],
['core_authentication_role_manager', 'Manager'],
['core_authentication_role_editor', 'Editor'],
['core_authentication_role_viewer', 'Viewer'],
['core_authentication_role_developer', 'Developer'],
];
@@ -7,4 +7,10 @@ export default [
['core_authentication_password_policy_upper_lower_case', 'Пароль должен содержать как заглавные, так и строчные буквы'],
['core_authentication_password_policy_min_digits', 'Пароль должен содержать не менее {arg:min} цифр'],
['core_authentication_password_policy_min_special_characters', 'Пароль должен содержать не менее {arg:min} специальных символов'],
['core_authentication_role_administrator', 'Администратор'],
['core_authentication_role_manager', 'Менеджер'],
['core_authentication_role_editor', 'Редактор'],
['core_authentication_role_viewer', 'Зритель'],
['core_authentication_role_developer', 'Разработчик'],
];
@@ -7,4 +7,10 @@ export default [
['core_authentication_password_policy_upper_lower_case', '密码必须包含大写和小写字母'],
['core_authentication_password_policy_min_digits', '密码至少需要包含 {arg:min} 个数字'],
['core_authentication_password_policy_min_special_characters', '密码至少需要包含 {arg:min} 个特殊字符'],
['core_authentication_role_administrator', 'Administrator'],
['core_authentication_role_manager', 'Manager'],
['core_authentication_role_editor', 'Editor'],
['core_authentication_role_viewer', 'Viewer'],
['core_authentication_role_developer', 'Developer'],
];
@@ -41,6 +41,18 @@
}
}
&.compact {
&.gap > .groupTitle {
padding: 16px 0;
margin: -16px 0;
& > .groupClose {
right: 0px;
top: 16px;
}
}
}
&.form > * {
margin-right: 25%;
}
@@ -1,12 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import React, { forwardRef, useContext } from 'react';
import React, { forwardRef, useContext, useLayoutEffect, useRef } from 'react';
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
@@ -18,6 +18,7 @@ import { EventTreeNodeSelectFlag } from './EventTreeNodeSelectFlag.js';
import type { ITreeNodeState } from './ITreeNodeState.js';
import { TreeNodeContext } from './TreeNodeContext.js';
import style from './TreeNodeControl.module.css';
import { useMergeRefs } from '../../useMergeRefs.js';
const KEY = {
ENTER: 'Enter',
@@ -38,6 +39,8 @@ export const TreeNodeControl = observer<Props & React.HTMLAttributes<HTMLDivElem
) {
const styles = useS(style);
const context = useContext(TreeNodeContext);
const innerRef = useRef<HTMLDivElement>(null);
const mergedRef = useMergeRefs(innerRef, ref);
if (!context) {
throw new Error('Context not provided');
@@ -109,9 +112,15 @@ export const TreeNodeControl = observer<Props & React.HTMLAttributes<HTMLDivElem
}
}
useLayoutEffect(() => {
if (context.selected) {
innerRef.current?.focus();
}
}, [context.selected]);
return (
<div
ref={ref}
ref={mergedRef}
tabIndex={context.selected ? 0 : -1}
title={title}
aria-selected={context.selected}
@@ -1,13 +1,14 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Dependency, injectable } from '@cloudbeaver/core-di';
import { SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { ESettingsValueType, SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
import { CONNECTIONS_SETTINGS_GROUP } from './CONNECTIONS_SETTINGS_GROUP.js';
const settingsSchema = schema.object({
'core.connections.disabled': schemaExtra.stringedBoolean().default(false),
@@ -22,24 +23,27 @@ export class ConnectionsSettingsService extends Dependency {
}
readonly settings: SettingsProvider<typeof settingsSchema>;
constructor(private readonly settingsProviderService: SettingsProviderService) {
constructor(
private readonly settingsProviderService: SettingsProviderService,
private readonly settingsManagerService: SettingsManagerService,
) {
super();
this.settings = this.settingsProviderService.createSettings(settingsSchema);
this.registerSettings();
}
private registerSettings() {
// this.settingsManagerService.registerSettings(this.settings, () => [
// {
// group: CONNECTIONS_SETTINGS_GROUP,
// key: 'core.connections.disabled',
// access: {
// scope: ['server'],
// },
// name: 'core_connections_settings_disable',
// description: 'core_connections_settings_disable_description',
// type: ESettingsValueType.Checkbox,
// },
// ]);
this.settingsManagerService.registerSettings(this.settings, () => [
{
group: CONNECTIONS_SETTINGS_GROUP,
key: 'core.connections.disabled',
access: {
scope: ['role'],
},
name: 'core_connections_settings_disable',
description: 'core_connections_settings_disable_description',
type: ESettingsValueType.Checkbox,
},
]);
}
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -8,12 +8,15 @@
import { Dependency, injectable } from '@cloudbeaver/core-di';
import {
createSettingsAliasResolver,
ESettingsValueType,
ROOT_SETTINGS_LAYER,
SettingsManagerService,
SettingsProvider,
SettingsProviderService,
SettingsResolverService,
} from '@cloudbeaver/core-settings';
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
import { NAVIGATION_TREE_SETTINGS_GROUP } from './NAVIGATION_TREE_SETTINGS_GROUP.js';
const settingsSchema = schema.object({
'core.navigation-tree.childrenLimit': schema.coerce.number().min(10).max(1000).default(100),
@@ -39,6 +42,7 @@ export class NavTreeSettingsService extends Dependency {
constructor(
private readonly settingsProviderService: SettingsProviderService,
private readonly settingsResolverService: SettingsResolverService,
private readonly settingsManagerService: SettingsManagerService,
) {
super();
this.settings = this.settingsProviderService.createSettings(settingsSchema);
@@ -58,37 +62,37 @@ export class NavTreeSettingsService extends Dependency {
}
private registerSettings() {
// this.settingsManagerService.registerSettings(this.settings, () => [
// {
// key: 'core.navigation-tree.childrenLimit',
// access: {
// scope: ['server'],
// },
// group: NAVIGATION_TREE_SETTINGS_GROUP,
// name: 'core_navigation_tree_settings_children_limit',
// description: 'core_navigation_tree_settings_children_limit_description',
// type: ESettingsValueType.Input,
// },
// {
// group: NAVIGATION_TREE_SETTINGS_GROUP,
// key: 'core.navigation-tree.editing',
// access: {
// scope: ['server'],
// },
// name: 'core_navigation_tree_settings_editing',
// description: 'core_navigation_tree_settings_editing_description',
// type: ESettingsValueType.Checkbox,
// },
// {
// group: NAVIGATION_TREE_SETTINGS_GROUP,
// key: 'core.navigation-tree.deleting',
// access: {
// scope: ['server'],
// },
// name: 'core_navigation_tree_settings_deleting',
// description: 'core_navigation_tree_settings_deleting_description',
// type: ESettingsValueType.Checkbox,
// },
// ]);
this.settingsManagerService.registerSettings(this.settings, () => [
{
key: 'core.navigation-tree.childrenLimit',
access: {
scope: ['client', 'server'],
},
group: NAVIGATION_TREE_SETTINGS_GROUP,
name: 'core_navigation_tree_settings_children_limit',
description: 'core_navigation_tree_settings_children_limit_description',
type: ESettingsValueType.Input,
},
{
group: NAVIGATION_TREE_SETTINGS_GROUP,
key: 'core.navigation-tree.editing',
access: {
scope: ['role'],
},
name: 'core_navigation_tree_settings_editing',
description: 'core_navigation_tree_settings_editing_description',
type: ESettingsValueType.Checkbox,
},
{
group: NAVIGATION_TREE_SETTINGS_GROUP,
key: 'core.navigation-tree.deleting',
access: {
scope: ['role'],
},
name: 'core_navigation_tree_settings_deleting',
description: 'core_navigation_tree_settings_deleting_description',
type: ESettingsValueType.Checkbox,
},
]);
}
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -0,0 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createSettingsLayer } from '@cloudbeaver/core-settings';
import { ROLE_SETTINGS_LAYER } from './ROLE_SETTINGS_LAYER.js';
export const HIGHEST_SETTINGS_LAYER = createSettingsLayer(ROLE_SETTINGS_LAYER, 'highest');
@@ -0,0 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createSettingsLayer } from '@cloudbeaver/core-settings';
import { TEAM_SETTINGS_LAYER } from './TEAM_SETTINGS_LAYER.js';
export const ROLE_SETTINGS_LAYER = createSettingsLayer(TEAM_SETTINGS_LAYER, 'role');
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -42,6 +42,10 @@ export class ServerSettingsService extends SettingsSource {
return this.settings.has(key) || super.has(key);
}
isOverrideDefaults(): boolean {
return this.settings.size > 0;
}
isReadOnly(key: any): boolean {
return !this.sessionPermissionsResource.has(EAdminPermission.admin);
}
@@ -50,17 +54,6 @@ export class ServerSettingsService extends SettingsSource {
return this.settings.get(key);
}
override clear(): void {
this.update(() => {
super.clear();
this.settings.clear();
});
}
resetChanges() {
super.clear();
}
async save() {
await this.serverConfigResource.updateProductConfiguration(Object.fromEntries(this.changes));
}
@@ -69,9 +62,19 @@ export class ServerSettingsService extends SettingsSource {
return Object.fromEntries(this.settings);
}
restoreDefaults() {
this.update(() => {
this.clear();
for (const key of this.settings.keys()) {
this.setValue(key, null);
}
});
}
private refreshConfig() {
this.update(() => {
this.clear();
this.settings.clear();
if (!this.serverConfigResource.data) {
this.lastConfig = null;
@@ -0,0 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createSettingsLayer } from '@cloudbeaver/core-settings';
import { USER_SETTINGS_LAYER } from './USER_SETTINGS_LAYER.js';
export const TEAM_SETTINGS_LAYER = createSettingsLayer(USER_SETTINGS_LAYER, 'team');
@@ -0,0 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createSettingsLayer } from '@cloudbeaver/core-settings';
import { SESSION_SETTINGS_LAYER } from './SESSION_SETTINGS_LAYER.js';
export const USER_SETTINGS_LAYER = createSettingsLayer(SESSION_SETTINGS_LAYER, 'user');
+5 -1
View File
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -27,6 +27,10 @@ export * from './SessionResource.js';
export * from './Settings/ServerSettingsManagerService.js';
export * from './Settings/ServerSettingsService.js';
export * from './Settings/SESSION_SETTINGS_LAYER.js';
export * from './Settings/ROLE_SETTINGS_LAYER.js';
export * from './Settings/TEAM_SETTINGS_LAYER.js';
export * from './Settings/USER_SETTINGS_LAYER.js';
export * from './Settings/HIGHEST_SETTINGS_LAYER.js';
export * from './SessionActionService.js';
export * from './EAdminPermission.js';
export * from './SessionDataResource.js';
@@ -1,11 +1,11 @@
query getActiveUser($includeConfigurationParameters: Boolean!) {
query getActiveUser {
user: activeUser {
userId
isAnonymous
displayName
authRole
linkedAuthProviders
configurationParameters @include(if: $includeConfigurationParameters)
configurationParameters
teams {
teamId
teamName
@@ -1,3 +1,3 @@
query getAuthRoles {
roles: listAuthRoles
}
}
@@ -1,10 +1,10 @@
mutation updateUserPreferences($preferences: Object!, $includeConfigurationParameters: Boolean!) {
mutation updateUserPreferences($preferences: Object!) {
user: setUserPreferences(preferences: $preferences) {
userId
displayName
authRole
linkedAuthProviders
configurationParameters @include(if: $includeConfigurationParameters)
configurationParameters
teams {
teamId
teamName
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -49,6 +49,10 @@ export class UserSettingsService extends SettingsSource {
return this.getSource().has(key) || super.has(key);
}
isOverrideDefaults(): boolean {
return this.getSource().size > 0;
}
isReadOnly(key: any): boolean {
return false;
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -22,4 +22,6 @@ export interface ISettingsSource {
setValue: (key: any, value: any) => void;
save: () => Promise<void>;
clear: () => void;
isOverrideDefaults?: () => boolean;
restoreDefaults?: () => void;
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { computed, makeObservable, observable } from 'mobx';
import { action, computed, makeObservable, observable } from 'mobx';
import { type ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import { isNotNullDefined } from '@dbeaver/js-helpers';
@@ -29,13 +29,16 @@ export class SettingsResolverSource implements ISettingsResolverSource {
.reverse();
}
protected layers: ISettingsSourcesLayer[];
private updating: boolean;
constructor() {
this.onChange = new SyncExecutor();
this.layers = [];
makeObservable<this, 'layers' | 'sources'>(this, {
this.updating = false;
makeObservable<this, 'layers' | 'sources' | 'update'>(this, {
layers: observable.shallow,
sources: computed,
update: action,
});
}
@@ -81,21 +84,29 @@ export class SettingsResolverSource implements ISettingsResolverSource {
this.layers = [];
}
isOverrideDefaults(): boolean {
return this.sources.some(r => r.isOverrideDefaults?.());
}
isEdited(key?: any): boolean {
return this.sources.find(r => r.has(key))?.isEdited(key) || false;
}
isReadOnly(key: any): boolean {
isReadOnly(key: any, stopAt?: ISettingsSource): boolean {
for (const source of this.sources) {
if (!source.isReadOnly(key)) {
return false;
if (!source.has(key)) {
continue;
}
if (source.has(key)) {
if (source === stopAt) {
break;
}
if (source.isReadOnly(key)) {
return true;
}
}
return true;
return false;
}
has(key: any): boolean {
@@ -162,4 +173,36 @@ export class SettingsResolverSource implements ISettingsResolverSource {
return layerSources;
}
protected getSnapshot(): Record<string, any> {
return {};
}
protected update(action: () => void) {
if (this.updating) {
action();
return;
}
this.updating = true;
try {
const snapshot = this.getSnapshot();
action();
const newSnapshot = this.getSnapshot();
for (const [key, value] of Object.entries(newSnapshot)) {
if (snapshot[key] !== value) {
this.onChange.execute({ key, value });
}
}
for (const key of Object.keys(snapshot)) {
if (!(key in newSnapshot)) {
this.onChange.execute({ key, value: undefined });
}
}
} finally {
this.updating = false;
}
}
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -76,6 +76,7 @@ export function createSettingsAliasResolver<TTarget extends schema.SomeZodObject
return has;
},
isOverrideDefaults: source.isOverrideDefaults?.bind(source.isOverrideDefaults),
isEdited(key) {
if (!(key in mappings)) {
return false;
+3 -2
View File
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@ import { TabActions } from './TabActions.js';
import type { TabProps } from './TabProps.js';
import { useTab } from './useTab.js';
export const Tab = observer<TabProps>(function Tab(props) {
export const Tab = observer<TabProps>(function Tab({ after, ...props }) {
const translate = useTranslate();
const { tabId, title, disabled, className, children, onOpen, onClose, onClick } = props;
const ref = useRef<HTMLButtonElement>(null);
@@ -57,6 +57,7 @@ export const Tab = observer<TabProps>(function Tab(props) {
>
<div className={s(styles, { tabContainer: true })}>{children}</div>
</BaseTab>
{after}
</div>
</div>
</TabContext.Provider>
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ export type TabProps = PropsWithChildren<{
selected?: boolean;
disabled?: boolean;
className?: string;
after?: React.ReactElement;
onOpen?: (tab: ITabData<any>) => void;
onClose?: (tab: ITabData<any>) => void;
onClick?: (tabId: string) => void;
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@ export interface ITabData<T = void> {
props: T;
}
export type TabContainerTabComponent<TProps = void> = React.FC<TabProps & TProps>;
export type TabContainerPanelComponent<TProps = void> = React.FC<{ tabId: string; className?: string } & TProps>;
export type TabContainerTabComponent<TProps = {}> = React.FC<TabProps & TProps>;
export type TabContainerPanelComponent<TProps = {}> = React.FC<{ tabId: string; className?: string } & TProps>;
export interface ITabInfoOptions<TProps = void, TOptions extends Record<string, any> | unknown = unknown> {
key: string;
@@ -59,6 +59,7 @@ export interface ITabsContainer<TProps = void, TOptions extends Record<string, a
valueGetter?: MetadataValueGetter<string, T>,
schema?: schema.AnyZodObject,
) => T;
setTabState: <T>(state: MetadataMap<string, any>, tabId: string, value: T) => T;
getDisplayed: (props?: TProps) => Array<ITabInfo<TProps, TOptions>>;
getIdList: (props?: TProps) => string[];
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -76,6 +76,10 @@ export class TabsContainer<TProps = void, TOptions extends Record<string, any> |
return state.get(tabId, valueGetter || tabInfo?.stateGetter?.(props), schema);
}
setTabState<T>(state: MetadataMap<string, any>, tabId: string, value: T): T {
return state.set(tabId, value).get(tabId);
}
getDisplayed(props?: TProps): Array<ITabInfo<TProps, TOptions>> {
return this.tabInfoList.filter(tabInfo => !tabInfo.isHidden?.(tabInfo.key, props));
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ export interface ITabsContext<T = Record<string, any>> {
canClose: (tabId: string) => boolean;
getTabInfo: (tabId: string) => ITabInfo<T> | undefined;
getTabState: <T>(tabId: string, valueGetter?: MetadataValueGetter<string, T>, schema?: schema.AnyZodObject) => T;
setTabState: <T>(tabId: string, value: T) => T;
getLocalState: <T>(tabId: string, valueGetter?: MetadataValueGetter<string, T>, schema?: schema.AnyZodObject) => T;
open: (tabId: string) => Promise<void>;
close: (tabId: string) => Promise<void>;
@@ -24,8 +24,10 @@ type ExtractContainerProps<T> = T extends void ? Record<string, any> : T;
export type TabsStateProps<T = Record<string, any>> = ExtractContainerProps<T> &
React.PropsWithChildren<{
/** Default selected tab id */
selectedId?: string;
orientation?: 'horizontal' | 'vertical';
/** Provide a tab Id to control tabs state */
currentTabId?: string | null;
container?: ITabsContainer<T, any>;
localState?: MetadataMap<string, any>;
@@ -168,6 +170,9 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
getTabState(tabId: string, valueGetter?: MetadataValueGetter<string, any>, schema?: schema.AnyZodObject) {
return dynamic.container?.getTabState(dynamic.tabsState, tabId, dynamic.props, valueGetter, schema);
},
setTabState(tabId: string, value: any) {
return dynamic.container?.setTabState(dynamic.tabsState, tabId, value);
},
getLocalState(tabId: string, valueGetter?: MetadataValueGetter<string, any>, schema?: schema.AnyZodObject) {
return dynamic.tabsState.get(tabId, valueGetter, schema);
},
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -1,12 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { CONNECTIONS_SETTINGS_GROUP } from '@cloudbeaver/core-connections';
import { Dependency, injectable } from '@cloudbeaver/core-di';
import { SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { ESettingsValueType, SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
const settings = schema.object({
@@ -32,12 +33,15 @@ export class CustomConnectionSettingsService extends Dependency {
private registerSettings() {
this.settingsManagerService.registerSettings(this.settings, () => [
// {
// group: CONNECTIONS_SETTINGS_GROUP,
// key: 'disabled',
// type: ESettingsValueType.Checkbox,
// name: 'Disable custom connections',
// },
{
key: 'plugin.connection-custom.disabled',
access: {
scope: ['role'],
},
group: CONNECTIONS_SETTINGS_GROUP,
name: 'plugin_connection_custom_settings_disabled_name',
type: ESettingsValueType.Checkbox,
},
]);
}
}
@@ -1 +1,4 @@
export default [['plugin_connection_custom_action_custom_label', 'New Connection']];
export default [
['plugin_connection_custom_action_custom_label', 'New Connection'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
];
@@ -1 +1,4 @@
export default [['plugin_connection_custom_action_custom_label', 'Nouvelle connexion']];
export default [
['plugin_connection_custom_action_custom_label', 'Nouvelle connexion'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
];
@@ -1 +1,4 @@
export default [['plugin_connection_custom_action_custom_label', 'New Connection']];
export default [
['plugin_connection_custom_action_custom_label', 'New Connection'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
];
@@ -1 +1,4 @@
export default [['plugin_connection_custom_action_custom_label', 'Новое подключение']];
export default [
['plugin_connection_custom_action_custom_label', 'Новое подключение'],
['plugin_connection_custom_settings_disabled_name', 'Отключить пользовательские подключения'],
];
@@ -1 +1,4 @@
export default [['plugin_connection_custom_action_custom_label', '新建连接']];
export default [
['plugin_connection_custom_action_custom_label', '新建连接'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
];
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ export class ConnectionSearchSettingsService extends Dependency {
group: CONNECTIONS_SETTINGS_GROUP,
key: 'plugin.connection-search.disabled',
access: {
scope: ['server'],
scope: ['server', 'role'],
},
type: ESettingsValueType.Checkbox,
name: 'plugin_connection_search_settings_disable',
@@ -1,12 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { CONNECTIONS_SETTINGS_GROUP } from '@cloudbeaver/core-connections';
import { Dependency, injectable } from '@cloudbeaver/core-di';
import { SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { ESettingsValueType, SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
const defaultSettings = schema.object({
@@ -22,7 +23,10 @@ export class PluginConnectionsSettingsService extends Dependency {
}
readonly settings: SettingsProvider<typeof defaultSettings>;
constructor(private readonly settingsProviderService: SettingsProviderService) {
constructor(
private readonly settingsProviderService: SettingsProviderService,
private readonly settingsManagerService: SettingsManagerService,
) {
super();
this.settings = this.settingsProviderService.createSettings(defaultSettings);
@@ -31,17 +35,17 @@ export class PluginConnectionsSettingsService extends Dependency {
private registerSettings() {
// todo: probably not working as a separate setting
// this.settingsManagerService.registerSettings(this.settings, () => [
// {
// key: 'plugin.connections.hideConnectionViewForUsers',
// type: ESettingsValueType.Checkbox,
// access: {
// scope: ['server'],
// },
// name: 'plugin_connections_settings_hide_connections_view_name',
// description: 'plugin_connections_settings_hide_connections_view_description',
// group: CONNECTIONS_SETTINGS_GROUP,
// },
// ]);
this.settingsManagerService.registerSettings(this.settings, () => [
{
key: 'plugin.connections.hideConnectionViewForUsers',
type: ESettingsValueType.Checkbox,
access: {
scope: ['role'],
},
name: 'plugin_connections_settings_hide_connections_view_name',
description: 'plugin_connections_settings_hide_connections_view_description',
group: CONNECTIONS_SETTINGS_GROUP,
},
]);
}
}
@@ -26,8 +26,8 @@ export default [
['plugin_connections_menu_connections_label', 'Connection'],
['plugin_connections_action_disconnect_all_label', 'Disconnect All'],
['plugin_connections_settings', 'Connections'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connections'],
['plugin_connections_settings_hide_connections_view_description', 'Connections will be hidden for all users except administrators'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connection view management'],
['plugin_connections_settings_hide_connections_view_description', 'Connections view submenu will be hidden for all users except administrators'],
['plugin_connections_connection_ssl_enable', 'Enable SSL'],
@@ -38,8 +38,8 @@ export default [
['plugin_connections_menu_connections_label', 'Connexion'],
['plugin_connections_action_disconnect_all_label', 'Déconnecter tout'],
['plugin_connections_settings', 'Connexions'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connections'],
['plugin_connections_settings_hide_connections_view_description', 'Connections will be hidden for all users except administrators'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connection view management'],
['plugin_connections_settings_hide_connections_view_description', 'Connections view submenu will be hidden for all users except administrators'],
['plugin_connections_connection_ssl_enable', 'Activer SSL'],
@@ -28,8 +28,8 @@ export default [
['plugin_connections_menu_connections_label', 'Connessione'],
['plugin_connections_action_disconnect_all_label', 'Scollegati da tutto'],
['plugin_connections_settings', 'Connections'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connections'],
['plugin_connections_settings_hide_connections_view_description', 'Connections will be hidden for all users except administrators'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connection view management'],
['plugin_connections_settings_hide_connections_view_description', 'Connections view submenu will be hidden for all users except administrators'],
['plugin_connections_connection_ssl_enable', 'Enable SSL'],
@@ -26,8 +26,8 @@ export default [
['plugin_connections_menu_connections_label', 'Подключение'],
['plugin_connections_action_disconnect_all_label', 'Отключить все'],
['plugin_connections_settings', 'Подключения'],
['plugin_connections_settings_hide_connections_view_name', 'Скрыть подключения'],
['plugin_connections_settings_hide_connections_view_description', 'Подключения будут скрыты для всех пользователей, кроме администраторов'],
['plugin_connections_settings_hide_connections_view_name', 'Скрыть управление отображением подключений'],
['plugin_connections_settings_hide_connections_view_description', 'Подменю выбора отображения подключения будет скрыто для всех пользователей, кроме администраторов'],
['plugin_connections_connection_ssl_enable', 'Включить SSL'],
@@ -25,8 +25,8 @@ export default [
['plugin_connections_menu_connections_label', '连接'],
['plugin_connections_action_disconnect_all_label', '断开所有连接'],
['plugin_connections_settings', '连接'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connections'],
['plugin_connections_settings_hide_connections_view_description', 'Connections will be hidden for all users except administrators'],
['plugin_connections_settings_hide_connections_view_name', 'Hide connection view management'],
['plugin_connections_settings_hide_connections_view_description', 'Connections view submenu will be hidden for all users except administrators'],
['plugin_connections_connection_ssl_enable', '启用 SSL'],
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ export class DataImportSettingsService {
name: 'plugin_data_import_disable_data_import_name',
description: 'plugin_data_import_disable_data_import_description',
access: {
scope: ['server'],
scope: ['server', 'role'],
},
},
]);
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ export class DataGridSettingsService extends Dependency {
group: DATA_EDITOR_SETTINGS_GROUP,
key: 'plugin.data-spreadsheet.hidden',
access: {
scope: ['server'],
scope: ['role'],
},
type: ESettingsValueType.Checkbox,
name: 'plugin_data_spreadsheet_new_settings_disable',
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -118,7 +118,7 @@ export class DataViewerSettingsService extends Dependency {
{
key: 'plugin.data-viewer.disableEdit',
access: {
scope: ['server'],
scope: ['server', 'role'],
},
type: ESettingsValueType.Checkbox,
name: 'settings_data_editor_disable_edit_name',
@@ -128,7 +128,7 @@ export class DataViewerSettingsService extends Dependency {
{
key: 'plugin.data-viewer.disableCopyData',
access: {
scope: ['server'],
scope: ['server', 'role'],
},
type: ESettingsValueType.Checkbox,
name: 'settings_data_editor_disable_data_copy_name',
@@ -138,7 +138,7 @@ export class DataViewerSettingsService extends Dependency {
{
key: 'plugin.data-viewer.fetchMax',
access: {
scope: ['server'],
scope: ['server', 'role'],
},
type: ESettingsValueType.Input,
name: 'settings_data_editor_fetch_max_name',
@@ -152,7 +152,7 @@ export class DataViewerSettingsService extends Dependency {
name: 'settings_data_editor_disable_data_export_name',
description: 'settings_data_editor_disable_data_export_description',
access: {
scope: ['server'],
scope: ['server', 'role'],
},
},
];
@@ -161,7 +161,7 @@ export class DataViewerSettingsService extends Dependency {
settings.push({
key: 'resultset.maxrows',
access: {
scope: ['server'],
scope: ['server', 'client'],
},
type: ESettingsValueType.Input,
name: 'settings_data_editor_fetch_default_name',
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -87,7 +87,7 @@ export class LogViewerSettingsService extends Dependency {
{
key: 'plugin.log-viewer.disabled',
access: {
scope: ['server'],
scope: ['server', 'role'],
},
group: TOOLS_PANEL_SETTINGS_GROUP,
type: ESettingsValueType.Checkbox,
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -8,8 +8,9 @@
import { computed, makeObservable } from 'mobx';
import { Dependency, injectable } from '@cloudbeaver/core-di';
import { SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { ESettingsValueType, SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
import { NAVIGATION_TREE_SETTINGS_GROUP } from '@cloudbeaver/core-navigation-tree';
const defaultSettings = schema.object({
'plugin.navigation-tree.disabled': schemaExtra.stringedBoolean().default(false),
@@ -41,16 +42,16 @@ export class NavigationTreeSettingsService extends Dependency {
private registerSettings() {
this.settingsManagerService.registerSettings(this.settings, () => [
// {
// group: NAVIGATION_TREE_SETTINGS_GROUP,
// key: 'plugin.navigation-tree.disabled',
// access: {
// scope: ['server'],
// },
// type: ESettingsValueType.Checkbox,
// name: 'plugin_navigation_tree_settings_disable',
// description: 'plugin_navigation_tree_settings_disable_description',
// },
{
group: NAVIGATION_TREE_SETTINGS_GROUP,
key: 'plugin.navigation-tree.disabled',
access: {
scope: ['role'],
},
type: ESettingsValueType.Checkbox,
name: 'plugin_navigation_tree_settings_disable',
description: 'plugin_navigation_tree_settings_disable_description',
},
]);
}
}
@@ -1,12 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Dependency, injectable } from '@cloudbeaver/core-di';
import { SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { RESOURCE_MANAGER_SETTINGS_GROUP } from '@cloudbeaver/core-resource-manager';
import { ESettingsValueType, SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
const settingsSchema = schema.object({
@@ -35,16 +36,16 @@ export class ResourceManagerScriptsSettingsService extends Dependency {
private registerSettings() {
this.settingsManagerService.registerSettings(this.settings, () => [
// {
// group: RESOURCE_MANAGER_SETTINGS_GROUP,
// key: 'plugin.resource-manager-scripts.disabled',
// access: {
// scope: ['server'],
// },
// type: ESettingsValueType.Checkbox,
// name: 'plugin_resource_manager_scripts_disable',
// description: 'plugin_resource_manager_scripts_disable_description',
// },
{
group: RESOURCE_MANAGER_SETTINGS_GROUP,
key: 'plugin.resource-manager-scripts.disabled',
access: {
scope: ['role'],
},
type: ESettingsValueType.Checkbox,
name: 'plugin_resource_manager_scripts_disable',
description: 'plugin_resource_manager_scripts_disable_description',
},
]);
}
}
@@ -26,6 +26,7 @@
"@cloudbeaver/core-events": "workspace:*",
"@cloudbeaver/core-localization": "workspace:*",
"@cloudbeaver/core-root": "workspace:*",
"@cloudbeaver/core-settings": "workspace:*",
"@cloudbeaver/core-ui": "workspace:*",
"@cloudbeaver/plugin-settings-panel": "workspace:*",
"mobx": "^6",
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -8,26 +8,30 @@
import { observer } from 'mobx-react-lite';
import type { AdministrationItemContentProps } from '@cloudbeaver/core-administration';
import { ColoredContainer, Form, Group, ToolsAction, ToolsPanel, useForm, useTranslate } from '@cloudbeaver/core-blocks';
import { ColoredContainer, Form, Group, ToolsAction, ToolsPanel, useForm, useTranslate, getComputed, TextPlaceholder } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ServerSettingsService } from '@cloudbeaver/core-root';
import { Settings } from '@cloudbeaver/plugin-settings-panel';
const clientScope = ['server'];
import { TabList, TabsState, type ITabData } from '@cloudbeaver/core-ui';
import { SettingsAdministrationService } from './SettingsAdministrationService.js';
import { useState } from 'react';
export const SettingsAdministration = observer<AdministrationItemContentProps>(function SettingsAdministration() {
const translate = useTranslate();
const serverSettingsService = useService(ServerSettingsService);
const settingsAdministrationService = useService(SettingsAdministrationService);
const notificationService = useService(NotificationService);
const changed = serverSettingsService.isEdited();
const [tabId, setTabId] = useState<string | null>(null);
const translate = useTranslate();
const tabInfo = getComputed(() => (tabId ? settingsAdministrationService.tabsContainer.getTabInfo(tabId) : null));
const settingsSource = tabInfo?.options?.source;
const accessor = tabInfo?.options?.accessor;
const changed = settingsSource?.isEdited() || false;
async function handleSave() {
if (!changed) {
return;
}
try {
await serverSettingsService.save();
await settingsSource?.save();
notificationService.logSuccess({ title: translate('plugin_settings_administration_settings_save_success') });
} catch (error: any) {
notificationService.logException(error, 'plugin_settings_administration_settings_save_fail');
@@ -41,24 +45,50 @@ export const SettingsAdministration = observer<AdministrationItemContentProps>(f
});
function handleReset() {
serverSettingsService.resetChanges();
settingsSource?.clear();
}
function handleRestoreDefaults() {
settingsSource?.restoreDefaults?.();
}
function handleTabChange(tab: ITabData<void>) {
setTabId(tab.tabId);
}
const hideSingleTab = settingsAdministrationService.tabsContainer.getDisplayed().length === 1;
return (
<Form context={form} contents>
<ColoredContainer overflow parent vertical noWrap gap>
<Group box keepSize>
<ToolsPanel rounded>
<ToolsAction icon="admin-save" viewBox="0 0 24 24" disabled={!changed} onClick={() => form.submit()}>
{translate('ui_processing_save')}
</ToolsAction>
<ToolsAction icon="admin-cancel" viewBox="0 0 24 24" disabled={!changed} onClick={handleReset}>
{translate('ui_processing_cancel')}
</ToolsAction>
</ToolsPanel>
</Group>
<Settings source={serverSettingsService} accessor={clientScope} />
</ColoredContainer>
</Form>
<TabsState container={settingsAdministrationService.tabsContainer} currentTabId={tabId} onChange={handleTabChange}>
<Form context={form} contents>
<ColoredContainer overflow parent vertical noWrap gap>
<Group box keepSize>
<ToolsPanel rounded>
<ToolsAction icon="admin-save" viewBox="0 0 24 24" disabled={!changed} onClick={() => form.submit()}>
{translate('ui_processing_save')}
</ToolsAction>
<ToolsAction icon="admin-cancel" viewBox="0 0 24 24" disabled={!changed} onClick={handleReset}>
{translate('ui_processing_cancel')}
</ToolsAction>
{settingsSource?.restoreDefaults && (
<ToolsAction
icon="/icons/settings_restore_defaults2_m.svg#root"
viewBox="0 0 24 24"
disabled={settingsSource.isOverrideDefaults?.() === false}
svg
onClick={handleRestoreDefaults}
>
{translate('plugin_user_profile_settings_restore_defaults')}
</ToolsAction>
)}
</ToolsPanel>
</Group>
<Group hidden={hideSingleTab} box keepSize hideOverflow>
<TabList underline />
</Group>
{settingsSource ? <Settings source={settingsSource} accessor={accessor} /> : <TextPlaceholder>{translate('plugin_settings_administration_no_settings')}</TextPlaceholder>}
</ColoredContainer>
</Form>
</TabsState>
);
});
@@ -0,0 +1,23 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import type { ISettingsSource } from '@cloudbeaver/core-settings';
import { TabsContainer } from '@cloudbeaver/core-ui';
export interface IAdministrationSettingsOptions {
source: ISettingsSource;
accessor: string[];
}
@injectable()
export class SettingsAdministrationService {
readonly tabsContainer: TabsContainer<void, IAdministrationSettingsOptions>;
constructor() {
this.tabsContainer = new TabsContainer('layer');
}
}
@@ -1,11 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { settingsAdministrationPlugin } from './manifest.js';
export { SettingsAdministrationService } from './SettingsAdministrationService.js';
export { settingsAdministrationPlugin };
export default settingsAdministrationPlugin;
@@ -1,5 +1,6 @@
export default [
['plugin_settings_administration_settings_save_success', 'Preferences saved successfully'],
['plugin_settings_administration_settings_save_fail', 'Preferences save failed'],
['plugin_settings_administration_drawer_item_title', 'Global Preferences'],
['plugin_settings_administration_settings_save_success', 'Settings saved successfully'],
['plugin_settings_administration_settings_save_fail', 'Settings save failed'],
['plugin_settings_administration_drawer_item_title', 'Settings'],
['plugin_settings_administration_no_settings', 'Settings not available'],
];
@@ -1,5 +1,6 @@
export default [
['plugin_settings_administration_settings_save_success', 'Préférences enregistrées avec succès'],
['plugin_settings_administration_settings_save_fail', "Échec de l'enregistrement des préférences"],
['plugin_settings_administration_drawer_item_title', 'Global Preferences'],
['plugin_settings_administration_drawer_item_title', 'Settings'],
['plugin_settings_administration_no_settings', 'Settings not available'],
];
@@ -1,5 +1,6 @@
export default [
['plugin_settings_administration_settings_save_success', 'Preferences saved successfully'],
['plugin_settings_administration_settings_save_fail', 'Preferences save failed'],
['plugin_settings_administration_drawer_item_title', 'Global Preferences'],
['plugin_settings_administration_drawer_item_title', 'Settings'],
['plugin_settings_administration_no_settings', 'Settings not available'],
];
@@ -1,5 +1,6 @@
export default [
['plugin_settings_administration_settings_save_success', 'Настройки сохранены успешно'],
['plugin_settings_administration_settings_save_fail', 'Ошибка сохранения настроек'],
['plugin_settings_administration_drawer_item_title', ' Глобальные настройки'],
['plugin_settings_administration_drawer_item_title', 'Настройки'],
['plugin_settings_administration_no_settings', 'Настройки недоступны'],
];
@@ -1,5 +1,6 @@
export default [
['plugin_settings_administration_settings_save_success', '偏好设置保存成功'],
['plugin_settings_administration_settings_save_fail', '偏好设置保存失败'],
['plugin_settings_administration_drawer_item_title', '全局偏好设置'],
['plugin_settings_administration_drawer_item_title', 'Settings'],
['plugin_settings_administration_no_settings', 'Settings not available'],
];
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -12,5 +12,6 @@ export const settingsAdministrationPlugin: PluginManifest = {
providers: [
() => import('./SettingsAdministrationPluginBootstrap.js').then(m => m.SettingsAdministrationPluginBootstrap),
() => import('./LocaleService.js').then(m => m.LocaleService),
() => import('./SettingsAdministrationService.js').then(m => m.SettingsAdministrationService),
],
};
@@ -31,6 +31,9 @@
{
"path": "../core-root"
},
{
"path": "../core-settings"
},
{
"path": "../core-ui"
},
@@ -0,0 +1,17 @@
# dependencies
/node_modules
# testing
/coverage
# production
/lib
# misc
.DS_Store
.env*
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@@ -0,0 +1,37 @@
{
"name": "@cloudbeaver/plugin-settings-default-administration",
"type": "module",
"sideEffects": [
"src/**/*.css",
"src/**/*.scss",
"public/**/*"
],
"version": "0.1.0",
"description": "",
"license": "Apache-2.0",
"exports": {
".": "./lib/index.js"
},
"scripts": {
"build": "tsc -b",
"clean": "rimraf --glob lib",
"lint": "eslint ./src/ --ext .ts,.tsx",
"validate-dependencies": "core-cli-validate-dependencies"
},
"dependencies": {
"@cloudbeaver/core-authentication": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-localization": "workspace:*",
"@cloudbeaver/core-root": "workspace:*",
"@cloudbeaver/core-settings": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
"@cloudbeaver/plugin-settings-administration": "workspace:*",
"mobx": "^6",
"tslib": "^2"
},
"devDependencies": {
"@cloudbeaver/core-cli": "workspace:*",
"@cloudbeaver/tsconfig": "workspace:*",
"typescript": "^5"
}
}
@@ -0,0 +1,36 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
@injectable()
export class LocaleService extends Bootstrap {
constructor(private readonly localizationService: LocalizationService) {
super();
}
override register(): void {
this.localizationService.addProvider(this.provider.bind(this));
}
private async provider(locale: string) {
switch (locale) {
case 'ru':
return (await import('./locales/ru.js')).default;
case 'it':
return (await import('./locales/it.js')).default;
case 'zh':
return (await import('./locales/zh.js')).default;
case 'fr':
return (await import('./locales/fr.js')).default;
default:
return (await import('./locales/en.js')).default;
}
}
}
@@ -0,0 +1,34 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { SettingsAdministrationService } from '@cloudbeaver/plugin-settings-administration';
import { ServerSettingsService } from '@cloudbeaver/core-root';
@injectable()
export class SettingsDefaultAdministrationBootstrap extends Bootstrap {
constructor(
private readonly settingsAdministrationService: SettingsAdministrationService,
private readonly serverSettingsService: ServerSettingsService,
) {
super();
}
override register(): void {
this.settingsAdministrationService.tabsContainer.add({
key: 'default',
name: 'plugin_settings_default_administration_settings_tab_name',
order: 0,
options: {
source: this.serverSettingsService,
accessor: ['server'],
},
panel: () => () => null,
});
}
}
@@ -0,0 +1,9 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export * from './manifest.js';
@@ -0,0 +1,3 @@
export default [
['plugin_settings_default_administration_settings_tab_name', 'Default'],
];
@@ -0,0 +1,3 @@
export default [
['plugin_settings_default_administration_settings_tab_name', 'Default'],
];
@@ -0,0 +1,3 @@
export default [
['plugin_settings_default_administration_settings_tab_name', 'Default'],
];
@@ -0,0 +1,3 @@
export default [
['plugin_settings_default_administration_settings_tab_name', 'По умолчанию'],
];
@@ -0,0 +1,3 @@
export default [
['plugin_settings_default_administration_settings_tab_name', 'Default'],
];
@@ -0,0 +1,20 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { PluginManifest } from '@cloudbeaver/core-di';
export const pluginSettingsDefaultAdministrationManifest: PluginManifest = {
info: {
name: 'Plugin Default Settings Administration',
},
providers: [
() => import('./SettingsDefaultBootstrap.js').then(m => m.SettingsDefaultAdministrationBootstrap),
() => import('./LocaleService.js').then(m => m.LocaleService),
],
};
@@ -0,0 +1,46 @@
{
"extends": "@cloudbeaver/tsconfig/tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"tsBuildInfoFile": "lib/tsconfig.tsbuildinfo",
"composite": true
},
"exclude": [
"**/node_modules",
"lib/**/*"
],
"references": [
{
"path": "../core-authentication"
},
{
"path": "../core-cli"
},
{
"path": "../core-di"
},
{
"path": "../core-localization"
},
{
"path": "../core-root"
},
{
"path": "../core-settings"
},
{
"path": "../core-utils"
},
{
"path": "../plugin-settings-administration"
}
],
"include": [
"__custom_mocks__/**/*",
"src/**/*",
"src/**/*.json",
"src/**/*.css",
"src/**/*.scss"
]
}
@@ -22,11 +22,13 @@
"dependencies": {
"@cloudbeaver/core-blocks": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-executor": "workspace:*",
"@cloudbeaver/core-localization": "workspace:*",
"@cloudbeaver/core-settings": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
"@cloudbeaver/plugin-navigation-tree": "workspace:*",
"@dbeaver/js-helpers": "workspace:^",
"@dbeaver/ui-kit": "workspace:^",
"mobx": "^6",
"mobx-react-lite": "^4",
"react": "^19",
@@ -7,17 +7,10 @@
*/
import { observer } from 'mobx-react-lite';
import { Combobox, FieldCheckbox, InputField, Textarea, useCustomInputValidation, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import {
ESettingsValueType,
type ISettingDescription,
type ISettingsSource,
SettingsProviderService,
SettingsResolverService,
} from '@cloudbeaver/core-settings';
import { schemaValidationError } from '@cloudbeaver/core-utils';
import { isNotNullDefined } from '@dbeaver/js-helpers';
import { Link, useTranslate } from '@cloudbeaver/core-blocks';
import { clsx } from '@dbeaver/ui-kit';
import { type ISettingDescription, type ISettingsSource } from '@cloudbeaver/core-settings';
import { SettingField } from './SettingField.js';
interface Props {
source: ISettingsSource;
@@ -25,117 +18,29 @@ interface Props {
}
export const Setting = observer<Props>(function Setting({ source, setting }) {
const settingsResolverService = useService(SettingsResolverService);
const settingsProviderService = useService(SettingsProviderService);
const translate = useTranslate();
// DODO: hide this logic until we have more than one scope
const isOverride = source.has(setting.key) && source.getEditedValue(setting.key) !== null && false;
const name = translate(setting.name);
const description = translate(setting.description);
const disabled = false;
const readOnly = settingsResolverService.isReadOnly(setting.key) ?? false;
let value = source.getEditedValue(setting.key);
if (readOnly || !isNotNullDefined(value)) {
value = settingsResolverService.getEditedValue(setting.key);
}
if (setting.key in settingsProviderService.schema.shape) {
const schema = settingsProviderService.schema.shape[setting.key];
if (!isNotNullDefined(value)) {
const result = schema.safeParse(undefined);
value = result.success ? result.data : '';
}
const result = schema.safeParse(value);
value = result.success ? result.data : value;
}
value = value ?? '';
const customValidation = useCustomInputValidation(value => {
if (!(setting.key in settingsProviderService.schema.shape)) {
return null;
}
const result = settingsProviderService.schema.shape[setting.key].safeParse(value);
if (result.success) {
return null;
}
return schemaValidationError(result.error, { prefix: null }).toString();
});
function handleChange(value: any) {
source.setValue(setting.key, value);
}
if (setting.type === ESettingsValueType.Checkbox) {
return (
<FieldCheckbox
id={String(setting.key)}
checked={value}
label={name}
title={name}
caption={description}
disabled={disabled}
readOnly={readOnly}
groupGap
onChange={handleChange}
/>
);
}
if (setting.type === ESettingsValueType.Select) {
const options = setting.options?.map(option => ({ ...option, name: translate(option.name) })) || [];
return (
<Combobox
id={String(setting.key)}
items={options}
keySelector={value => value.value}
valueSelector={value => value.name}
value={value}
title={name}
disabled={disabled}
readOnly={readOnly}
description={description}
small
onSelect={handleChange}
>
{name}
</Combobox>
);
}
if (setting.type === ESettingsValueType.Textarea) {
return (
<Textarea
id={String(setting.key)}
title={value}
labelTooltip={description}
value={value}
disabled={disabled}
readOnly={readOnly}
onChange={handleChange}
>
{name}
</Textarea>
);
function handleRestore() {
source.setValue(setting.key, null);
}
return (
<InputField
ref={customValidation}
id={String(setting.key)}
type="text"
title={value}
labelTooltip={description}
value={value}
description={description}
readOnly={readOnly || disabled}
small
onChange={handleChange}
>
{name}
</InputField>
<div className='tw:flex tw:relative tw:gap-2'>
<div className="tw:w-1 tw:h-full" hidden>
{isOverride && (
<div className={clsx('tw:h-full tw:w-full tw:bg-zinc-100 tw:dark:bg-zinc-700')} title={translate('plugin_settings_panel_setting_set_in_scope')} />
)}
</div>
<div>
<SettingField setting={setting} source={source} />
{isOverride && (
<Link className="theme-typography--caption" title={translate('plugin_settings_panel_setting_reset_tooltip')} onClick={handleRestore}>
{translate('plugin_settings_panel_setting_reset')}
</Link>
)}
</div>
</div>
);
});
@@ -0,0 +1,144 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Combobox, FieldCheckbox, InputField, Textarea, useCustomInputValidation, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import {
ESettingsValueType,
SettingsProviderService,
SettingsResolverService,
type ISettingDescription,
type ISettingsSource,
} from '@cloudbeaver/core-settings';
import { schemaValidationError } from '@cloudbeaver/core-utils';
import { isNotNullDefined } from '@dbeaver/js-helpers';
import { observer } from 'mobx-react-lite';
interface Props {
source: ISettingsSource;
setting: ISettingDescription;
}
export const SettingField = observer<Props>(function SettingField({ setting, source }) {
const settingsResolverService = useService(SettingsResolverService);
const settingsProviderService = useService(SettingsProviderService);
const translate = useTranslate();
const name = translate(setting.name);
const description = translate(setting.description);
const disabled = false;
// TODO: need to figure out how to ignore user scope settings during configuring admin settings
// probably we can use layers to skip some layers when checking settings
// const readOnly = settingsResolverService.isReadOnly(setting.key, source) ?? false;
const readOnly = false;
let value = source.getEditedValue(setting.key);
if (readOnly || !isNotNullDefined(value)) {
value = settingsResolverService.getEditedValue(setting.key);
}
if (setting.key in settingsProviderService.schema.shape) {
const schema = settingsProviderService.schema.shape[setting.key];
if (!isNotNullDefined(value)) {
const result = schema.safeParse(undefined);
value = result.success ? result.data : '';
}
const result = schema.safeParse(value);
value = result.success ? result.data : value;
}
value = value ?? '';
const customValidation = useCustomInputValidation(value => {
if (!(setting.key in settingsProviderService.schema.shape)) {
return null;
}
const result = settingsProviderService.schema.shape[setting.key].safeParse(value);
if (result.success) {
return null;
}
return schemaValidationError(result.error, { prefix: null }).toString();
});
function handleChange(value: any) {
source.setValue(setting.key, value);
}
if (setting.type === ESettingsValueType.Checkbox) {
return (
<FieldCheckbox
id={String(setting.key)}
checked={value}
label={name}
title={name}
caption={description}
disabled={disabled}
readOnly={readOnly}
groupGap
onChange={handleChange}
/>
);
}
if (setting.type === ESettingsValueType.Select) {
const options = setting.options?.map(option => ({ ...option, name: translate(option.name) })) || [];
return (
<Combobox
id={String(setting.key)}
items={options}
keySelector={value => value.value}
valueSelector={value => value.name}
value={value}
title={name}
disabled={disabled}
readOnly={readOnly}
description={description}
small
onSelect={handleChange}
>
{name}
</Combobox>
);
}
if (setting.type === ESettingsValueType.Textarea) {
return (
<Textarea
id={String(setting.key)}
title={value}
labelTooltip={description}
value={value}
disabled={disabled}
readOnly={readOnly}
onChange={handleChange}
>
{name}
</Textarea>
);
}
return (
<InputField
ref={customValidation}
id={String(setting.key)}
type="text"
title={value}
labelTooltip={description}
value={value}
description={description}
readOnly={readOnly || disabled}
small
onChange={handleChange}
>
{name}
</InputField>
);
});
@@ -5,13 +5,14 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { useId, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { Container, Filter, Group, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
import { type ISettingsSource, ROOT_SETTINGS_GROUP, SettingsGroup } from '@cloudbeaver/core-settings';
import { useTreeData, useTreeFilter } from '@cloudbeaver/plugin-navigation-tree';
import { SyncExecutor } from '@cloudbeaver/core-executor';
import { getSettingGroupId } from './getSettingGroupId.js';
import classes from './Settings.module.css';
import { settingsFilter } from './settingsFilter.js';
import { SettingsGroups } from './SettingsGroups/SettingsGroups.js';
@@ -25,7 +26,9 @@ export interface ISettingsProps {
export const Settings = observer<ISettingsProps>(function Settings({ source, accessor }) {
const translate = useTranslate();
const settingsId = useId();
const settings = useSettings(accessor);
const [groupSelectExecutor] = useState(() => new SyncExecutor<string>());
function filterExistsGroups(group: SettingsGroup) {
return settings.groups.has(group);
@@ -71,12 +74,12 @@ export const Settings = observer<ISettingsProps>(function Settings({ source, acc
}
function handleClick(id: string) {
document.querySelector('#' + getSettingGroupId(id))?.scrollIntoView();
groupSelectExecutor.execute(id);
}
return (
<Container gap overflow noWrap>
<Group className={s(classes, { settingsGroups: true })} vertical box keepSize overflow hidden>
<Group className={s(classes, { settingsGroups: true })} vertical box keepSize overflow>
<SettingsGroups treeData={treeData} onClick={handleClick} />
</Group>
<Container className={s(classes, { settingsContainer: true })} overflow vertical gap noWrap>
@@ -89,7 +92,14 @@ export const Settings = observer<ISettingsProps>(function Settings({ source, acc
/>
</Container>
<Container overflow vertical>
<SettingsList treeData={treeData} treeFilter={treeFilter} source={source} settings={settings.settings} />
<SettingsList
settingsId={settingsId}
treeData={treeData}
treeFilter={treeFilter}
source={source}
settings={settings.settings}
groupSelectExecutor={groupSelectExecutor}
/>
</Container>
</Container>
</Container>
@@ -1,35 +1,59 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { getComputed, Group, GroupTitle, useTranslate } from '@cloudbeaver/core-blocks';
import { getComputed, Group, GroupTitle, useExecutor, useTranslate } from '@cloudbeaver/core-blocks';
import type { ISettingDescription, ISettingsSource, SettingsGroup as SettingsGroupType } from '@cloudbeaver/core-settings';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import type { ITreeFilter } from '@cloudbeaver/plugin-navigation-tree';
import { getSettingGroupId } from './getSettingGroupId.js';
import { Setting } from './Setting.js';
import { settingsFilter } from './settingsFilter.js';
import { SettingsGroupTitle } from './SettingsGroupTitle.js';
import type { ISyncExecutor } from '@cloudbeaver/core-executor';
import { useRef } from 'react';
import { getSettingGroupId } from './getSettingGroupId.js';
interface Props {
settingsId: string;
group: SettingsGroupType;
source: ISettingsSource;
settings: Map<SettingsGroupType, ISettingDescription<any>[]>;
treeFilter: ITreeFilter;
groupSelectExecutor: ISyncExecutor<string>;
}
export const SettingsGroup = observer<Props>(function SettingsGroup({ group, source, settings, treeFilter }) {
export const SettingsGroup = observer<Props>(function SettingsGroup({ settingsId, group, source, settings, treeFilter, groupSelectExecutor }) {
const ref = useRef<HTMLDivElement>(null);
const translate = useTranslate();
const groupSettings = getComputed(() => settings.get(group)?.filter(settingsFilter(translate, treeFilter.filter)) || [], isArraysEqual);
const hidden = groupSettings.length === 0;
useExecutor({
executor: groupSelectExecutor,
handlers: [
id => {
if (id === group.id) {
if (hidden) {
const next = ref.current?.nextSibling;
if (next instanceof HTMLElement) {
next?.scrollIntoView();
}
} else {
ref.current?.scrollIntoView();
}
}
},
],
});
return (
<Group id={getSettingGroupId(group.id)} hidden={groupSettings.length === 0} vertical gap>
<Group ref={ref} id={getSettingGroupId(settingsId, group.id)} hidden={hidden} vertical gap compact>
<GroupTitle sticky>
<SettingsGroupTitle group={group} />
</GroupTitle>
@@ -1,13 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { Container, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
import { Group, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
import { type ISettingDescription, type ISettingsSource, type SettingsGroup as SettingsGroupType } from '@cloudbeaver/core-settings';
import type { ITreeData, ITreeFilter } from '@cloudbeaver/plugin-navigation-tree';
@@ -15,27 +15,46 @@ import { getGroupsFromTree } from './getGroupsFromTree.js';
import { SettingsGroup } from './SettingsGroup.js';
import classes from './SettingsList.module.css';
import { useTreeScrollSync } from './useTreeScrollSync.js';
import type { ISyncExecutor } from '@cloudbeaver/core-executor';
interface Props {
settingsId: string;
treeData: ITreeData;
treeFilter: ITreeFilter;
source: ISettingsSource;
settings: Map<SettingsGroupType, ISettingDescription<any>[]>;
groupSelectExecutor: ISyncExecutor<string>;
onSettingsOpen?: (groupId: string) => void;
}
export const SettingsList = observer<Props>(function SettingsList({ treeData, treeFilter, source, settings, onSettingsOpen }) {
export const SettingsList = observer<Props>(function SettingsList({
settingsId,
treeData,
treeFilter,
source,
settings,
groupSelectExecutor,
onSettingsOpen,
}) {
const translate = useTranslate();
const ref = useTreeScrollSync(treeData, onSettingsOpen);
const ref = useTreeScrollSync(settingsId, treeData, onSettingsOpen);
const groups = Array.from(getGroupsFromTree(treeData, treeData.getChildren(treeData.rootId)));
return (
<Container ref={ref} gap overflow>
<Group ref={ref} parent box overflow>
{groups.map(group => (
<SettingsGroup key={group.id} group={group} source={source} settings={settings} treeFilter={treeFilter} />
<SettingsGroup
key={group.id}
settingsId={settingsId}
group={group}
source={source}
settings={settings}
treeFilter={treeFilter}
groupSelectExecutor={groupSelectExecutor}
/>
))}
{groups.length === 0 && <TextPlaceholder>{translate('plugin_settings_panel_no_settings')}</TextPlaceholder>}
<div className={s(classes, { spaceFill: true })} />
</Container>
</Group>
);
});
@@ -1,12 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { SETTINGS_GROUP_ID_PREFIX } from './SETTINGS_GROUP_ID_PREFIX.js';
export function getSettingGroupId(id: string): string {
return `${SETTINGS_GROUP_ID_PREFIX}${id}`;
export function getSettingGroupId(settingsId: string, id: string): string {
return `${SETTINGS_GROUP_ID_PREFIX}${settingsId}-${id}`;
}
@@ -1,12 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { SETTINGS_GROUP_ID_PREFIX } from './SETTINGS_GROUP_ID_PREFIX.js';
export function getSettingGroupIdFromElementId(id: string): string {
return id.replace(SETTINGS_GROUP_ID_PREFIX, '');
export function getSettingGroupIdFromElementId(settingsId: string, id: string): string {
return id.replace(SETTINGS_GROUP_ID_PREFIX + `${settingsId}-`, '');
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ export function useSettings(accessor?: string[]): ISettings {
get settings() {
const map = new Map();
const settings = this.settingsManagerService.activeSettings
.filter(setting => accessor?.some(value => setting.access.scope.includes(value)))
.filter(setting => this.accessor?.some(value => setting.access.scope.includes(value)))
.sort((a, b) => a.name.localeCompare(b.name));
for (const setting of settings) {
@@ -54,6 +54,7 @@ export function useSettings(accessor?: string[]): ISettings {
settings: computed,
groups: computed,
settingsManagerService: observable.ref,
accessor: observable.ref,
},
{ settingsManagerService, accessor },
);
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -14,7 +14,11 @@ import type { ITreeData } from '@cloudbeaver/plugin-navigation-tree';
import { getSettingGroupIdFromElementId } from './getSettingGroupIdFromElementId.js';
import { querySettingsGroups } from './querySettingsGroups.js';
export function useTreeScrollSync(treeData: ITreeData, onSettingsOpen?: (groupId: string) => void): React.RefObject<HTMLDivElement | null> {
export function useTreeScrollSync(
settingsId: string,
treeData: ITreeData,
onSettingsOpen?: (groupId: string) => void,
): React.RefObject<HTMLDivElement | null> {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
@@ -38,7 +42,7 @@ export function useTreeScrollSync(treeData: ITreeData, onSettingsOpen?: (groupId
}
if (firstVisibleElement) {
const groupId = getSettingGroupIdFromElementId(firstVisibleElement.id);
const groupId = getSettingGroupIdFromElementId(settingsId, firstVisibleElement.id);
let group = ROOT_SETTINGS_GROUP.get(groupId)!;
treeData.updateAllState({ selected: false, expanded: false });

Some files were not shown because too many files have changed in this diff Show More