diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java index 4b29e2e7d6..7f854034b3 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java @@ -30,6 +30,7 @@ public interface DBWConstants { String PERMISSION_PRIVATE_PROJECT_ACCESS = "private-project-access"; String PERMISSION_SECRET_MANAGER = "secret-manager"; + String GLOBAL_PERMISSION_SCRIPT_EXECUTE = "permission.sql.script.execution"; String PERMISSION_EDIT_STRUCTURE = "edit-meta"; String PERMISSION_EDIT_DATA = "edit-data"; diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java index 0b97cadd10..30e926ac38 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java @@ -1022,6 +1022,9 @@ public class WebSession extends BaseWebSession } } + public boolean hasGlobalPermission(String permissionId) { + return true; + } private record PersistentAttribute(Object value) { } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java index 873864f142..24bcaa85b6 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,4 +35,6 @@ public @interface WebAction { boolean initializationRequired() default true; + String[] requireGlobalPermissions() default {}; + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebProductSettings.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebGroupPropertiesInfo.java similarity index 78% rename from server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebProductSettings.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebGroupPropertiesInfo.java index 25cbd78f26..302533c818 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebProductSettings.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebGroupPropertiesInfo.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,22 +18,23 @@ package io.cloudbeaver.model; import io.cloudbeaver.model.session.WebSession; import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.model.impl.PropertyDescriptor; import org.jkiss.dbeaver.model.impl.PropertyGroupDescriptor; import org.jkiss.dbeaver.model.meta.Property; -import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; -public class WebProductSettings { +public class WebGroupPropertiesInfo { @NotNull private final WebSession webSession; @NotNull - private final List> groups; + private final Collection> groups; - public WebProductSettings( + public WebGroupPropertiesInfo( @NotNull WebSession webSession, - @NotNull List> groups + @NotNull Collection> groups ) { this.webSession = webSession; this.groups = groups; @@ -41,7 +42,7 @@ public class WebProductSettings { @NotNull @Property - public List getGroups() { + public List> getGroups() { return groups.stream() .map(WebSettingsGroupInfo::new) .collect(Collectors.toList()); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebSettingsGroupInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebSettingsGroupInfo.java index 4ba073e621..a825e3cb09 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebSettingsGroupInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebSettingsGroupInfo.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,15 @@ package io.cloudbeaver.model; import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.model.impl.PropertyDescriptor; import org.jkiss.dbeaver.model.impl.PropertyGroupDescriptor; import org.jkiss.dbeaver.model.meta.Property; -import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor; -public class WebSettingsGroupInfo { +public class WebSettingsGroupInfo { @NotNull - private final PropertyGroupDescriptor groupDescriptor; + private final PropertyGroupDescriptor groupDescriptor; - public WebSettingsGroupInfo(@NotNull PropertyGroupDescriptor groupDescriptor) { + public WebSettingsGroupInfo(@NotNull PropertyGroupDescriptor groupDescriptor) { this.groupDescriptor = groupDescriptor; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java index 0a095be922..bded959533 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -265,7 +265,8 @@ public abstract class WebServiceBindingBase impleme throw new DBWebExceptionServerNotInitialized(message); } String[] reqPermissions = webAction.requirePermissions(); - if (reqPermissions.length == 0 && !webAction.authRequired()) { + String[] reqGlobalPermissions = webAction.requireGlobalPermissions(); + if (reqPermissions.length == 0 && reqGlobalPermissions.length == 0 && !webAction.authRequired()) { return; } WebSession session = findWebSession(env); @@ -297,6 +298,13 @@ public abstract class WebServiceBindingBase impleme throw new DBWebExceptionAccessDenied("Access denied"); } } + // Check permissions + for (String gp : reqGlobalPermissions) { + if (!session.hasGlobalPermission(gp)) { + log.debug("Access to " + method.getName() + " denied for " + session.getUser()); + throw new DBWebExceptionAccessDenied("Access denied"); + } + } } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java index 855de4c9c4..1c8a48a4e5 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java @@ -29,6 +29,7 @@ import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; import org.jkiss.dbeaver.model.rm.RMConstants; +import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor; import java.util.List; import java.util.Map; @@ -48,7 +49,7 @@ public interface DBWServiceCore extends DBWService { WebPropertyInfo[] getSystemInformationProperties(@NotNull WebSession webSession); @WebAction - WebProductSettings getProductSettings(@NotNull WebSession webSession); + WebGroupPropertiesInfo getProductSettings(@NotNull WebSession webSession); @WebAction List getDriverList(@NotNull WebSession webSession, String driverId) throws DBWebException; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index 8b954ae6f9..2710ec36b2 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -54,6 +54,7 @@ import org.jkiss.dbeaver.registry.DataSourceDescriptor; import org.jkiss.dbeaver.registry.DataSourceProviderRegistry; import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor; import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry; +import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor; import org.jkiss.dbeaver.registry.settings.ProductSettingsRegistry; import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.utils.CommonUtils; @@ -644,8 +645,8 @@ public class WebServiceCore implements DBWServiceCore { } @Override - public WebProductSettings getProductSettings(@NotNull WebSession webSession) { - return new WebProductSettings(webSession, ProductSettingsRegistry.getInstance().getSettings()); + public WebGroupPropertiesInfo getProductSettings(@NotNull WebSession webSession) { + return new WebGroupPropertiesInfo<>(webSession, ProductSettingsRegistry.getInstance().getSettings()); } private WebSessionProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java index 0fcd071bb3..9b298c2f26 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java @@ -16,6 +16,7 @@ */ package io.cloudbeaver.service.sql; +import io.cloudbeaver.DBWConstants; import io.cloudbeaver.DBWebException; import io.cloudbeaver.WebAction; import io.cloudbeaver.model.WebAsyncTaskInfo; @@ -90,7 +91,7 @@ public interface DBWServiceSQL extends DBWService { @WebAction void setContextDefaults(@NotNull WebSQLContextInfo sqlContext, String catalogName, String schemaName) throws DBWebException; - @WebAction + @WebAction(requireGlobalPermissions = DBWConstants.GLOBAL_PERMISSION_SCRIPT_EXECUTE) WebAsyncTaskInfo asyncExecuteQuery( @NotNull WebSQLContextInfo contextInfo, @NotNull String sql, diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index 16b6de4c34..81f1fd7e0c 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -1433,9 +1433,14 @@ public class CBEmbeddedSecurityController addSubjectPermissionsUpdateEvent(subjectId, null); } + + public void initialize() throws DBException { + } + private void insertPermissions(Connection dbCon, String subjectId, String[] permissionIds, String grantorId) throws SQLException { if (!ArrayUtils.isEmpty(permissionIds)) { - try (PreparedStatement dbStat = dbCon.prepareStatement( + try ( + PreparedStatement dbStat = dbCon.prepareStatement( "INSERT INTO {table_prefix}CB_AUTH_PERMISSIONS" + "(SUBJECT_ID,PERMISSION_ID,GRANT_TIME,GRANTED_BY) VALUES(?,?,?,?)") ) { @@ -1503,6 +1508,7 @@ public class CBEmbeddedSecurityController } } + protected Set getUserPermissions(String userId, String authRole) throws DBException { return getUserPermissions(userId); } @@ -1584,7 +1590,7 @@ public class CBEmbeddedSecurityController } } - private Set getAnonymousUserPermissions() throws DBException { + protected Set getAnonymousUserPermissions() throws DBException { var anonymousUserTeam = application.getAppConfiguration().getAnonymousUserTeam(); return getSubjectPermissions(anonymousUserTeam); } @@ -2994,7 +3000,7 @@ public class CBEmbeddedSecurityController } - private void addSubjectPermissionsUpdateEvent(@NotNull String subjectId, @Nullable SMSubjectType subjectType) { + protected void addSubjectPermissionsUpdateEvent(@NotNull String subjectId, @Nullable SMSubjectType subjectType) { if (subjectType == null) { subjectType = getSubjectType(subjectId); } @@ -3408,7 +3414,7 @@ public class CBEmbeddedSecurityController } } - private SMSubjectType getSubjectType(@NotNull String subjectId) { + protected SMSubjectType getSubjectType(@NotNull String subjectId) { try (Connection dbCon = database.openConnection()) { String sqlBuilder = "SELECT SUBJECT_TYPE FROM {table_prefix}CB_AUTH_SUBJECT U WHERE SUBJECT_ID = ?"; try (var dbStat = dbCon.prepareStatement(sqlBuilder)) { diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java index 4aa8d40ecc..9208e4b281 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,9 @@ public class EmbeddedSecurityControllerFactory SMCredentialsProvider credentialsProvider, SMControllerConfiguration smConfig ) throws DBException { + boolean initialization = false; if (DB_INSTANCE == null) { + initialization = true; synchronized (EmbeddedSecurityControllerFactory.class) { if (DB_INSTANCE == null) { DB_INSTANCE = createAndInitDatabaseInstance( @@ -64,9 +66,13 @@ public class EmbeddedSecurityControllerFactory )).schedule(); } } - return createEmbeddedSecurityController( + var controller = createEmbeddedSecurityController( application, DB_INSTANCE, credentialsProvider, smConfig ); + if (initialization) { + controller.initialize(); + } + return controller; } protected @NotNull CBDatabase createAndInitDatabaseInstance( diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java index cd15535fe3..a339307bcd 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java @@ -24,7 +24,6 @@ import io.cloudbeaver.model.app.ServletApplication; import io.cloudbeaver.model.config.WebDatabaseConfig; import io.cloudbeaver.registry.WebAuthProviderDescriptor; import io.cloudbeaver.registry.WebAuthProviderRegistry; -import org.jkiss.dbeaver.model.sql.db.InternalProxyConnection; import io.cloudbeaver.utils.ServletAppUtils; import org.apache.commons.dbcp2.*; import org.apache.commons.pool2.impl.GenericObjectPool; @@ -44,6 +43,7 @@ import org.jkiss.dbeaver.model.security.SMAdminController; import org.jkiss.dbeaver.model.security.user.SMTeam; import org.jkiss.dbeaver.model.security.user.SMUser; import org.jkiss.dbeaver.model.sql.db.InternalDB; +import org.jkiss.dbeaver.model.sql.db.InternalProxyConnection; import org.jkiss.dbeaver.model.sql.schema.SQLSchemaConfig; import org.jkiss.dbeaver.registry.DataSourceProviderRegistry; import org.jkiss.dbeaver.registry.storage.H2Migrator; diff --git a/webapp/packages/core-authentication/src/AuthSettingsService.ts b/webapp/packages/core-authentication/src/AuthSettingsService.ts index 97a365d3ae..e4420161be 100644 --- a/webapp/packages/core-authentication/src/AuthSettingsService.ts +++ b/webapp/packages/core-authentication/src/AuthSettingsService.ts @@ -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. @@ -32,7 +32,7 @@ export class AuthSettingsService { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ // { // key: 'core.authentication.disableAnonymousAccess', // access: { diff --git a/webapp/packages/core-browser-settings/src/BrowserSettingsService.ts b/webapp/packages/core-browser-settings/src/BrowserSettingsService.ts index 3d1d982566..30ce80d6b9 100644 --- a/webapp/packages/core-browser-settings/src/BrowserSettingsService.ts +++ b/webapp/packages/core-browser-settings/src/BrowserSettingsService.ts @@ -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. @@ -20,14 +20,15 @@ const settingsSchema = schema.object({ 'core.browser.cookies.disabled': schemaExtra.stringedBoolean().default(false), }); -export type CookiesSettings = schema.infer; +export type BrowserSettingsSchema = typeof settingsSchema; +export type CookiesSettings = schema.infer; @injectable() export class BrowserSettingsService extends Dependency { get disabled(): boolean { return this.settings.getValue('core.browser.cookies.disabled'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -40,7 +41,7 @@ export class BrowserSettingsService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'core.browser.cookies.disabled': 'core.cookies.disabled', }), ); @@ -48,7 +49,7 @@ export class BrowserSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ // { // group: BROWSER_COOKIES_SETTINGS_GROUP, // key: 'cookies.disabled', diff --git a/webapp/packages/core-connections/src/ConnectionsSettingsService.ts b/webapp/packages/core-connections/src/ConnectionsSettingsService.ts index 421110e7e7..b46c6a920e 100644 --- a/webapp/packages/core-connections/src/ConnectionsSettingsService.ts +++ b/webapp/packages/core-connections/src/ConnectionsSettingsService.ts @@ -33,7 +33,7 @@ export class ConnectionsSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { group: CONNECTIONS_SETTINGS_GROUP, key: 'core.connections.disabled', diff --git a/webapp/packages/core-events/src/EventsSettingsService.ts b/webapp/packages/core-events/src/EventsSettingsService.ts index f43f830977..f7a6f1227e 100644 --- a/webapp/packages/core-events/src/EventsSettingsService.ts +++ b/webapp/packages/core-events/src/EventsSettingsService.ts @@ -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. @@ -21,7 +21,8 @@ const settingsSchema = schema.object({ 'plugin.notifications.maxPersistentAllow': schema.coerce.number().default(5), }); -export type EventsSettings = schema.infer; +export type EventsSettingsSchema = typeof settingsSchema; +export type EventsSettings = schema.infer; @injectable() export class EventsSettingsService extends Dependency { @@ -31,7 +32,7 @@ export class EventsSettingsService extends Dependency { get notificationsPool(): number { return this.settings.getValue('plugin.notifications.notificationsPool'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -44,7 +45,7 @@ export class EventsSettingsService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'plugin.notifications.maxPersistentAllow': 'core_events.maxPersistentAllow', 'plugin.notifications.notificationsPool': 'core_events.notificationsPool', }), @@ -53,7 +54,7 @@ export class EventsSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ // { // group: NOTIFICATIONS_SETTINGS_GROUP, // key: 'plugin.notifications.maxPersistentAllow', diff --git a/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.ts b/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.ts index ce7a80bb70..29f4c1fa0d 100644 --- a/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.ts +++ b/webapp/packages/core-navigation-tree/src/NavTreeSettingsService.ts @@ -24,7 +24,8 @@ const settingsSchema = schema.object({ 'core.navigation-tree.deleting': schemaExtra.stringedBoolean().default(true), }); -export type NavTreeSettings = schema.infer; +export type NavTreeSettingsSchema = typeof settingsSchema; +export type NavTreeSettings = schema.infer; @injectable() export class NavTreeSettingsService extends Dependency { @@ -37,7 +38,7 @@ export class NavTreeSettingsService extends Dependency { get deleting(): boolean { return this.settings.getValue('core.navigation-tree.deleting'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -49,10 +50,10 @@ export class NavTreeSettingsService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'core.navigation-tree.childrenLimit': 'core.app.navigationTree.childrenLimit', }), - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'core.navigation-tree.deleting': 'core.app.metadata.deleting', 'core.navigation-tree.editing': 'core.app.metadata.editing', }), @@ -62,7 +63,7 @@ export class NavTreeSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { key: 'core.navigation-tree.childrenLimit', access: { diff --git a/webapp/packages/core-product/src/ProductSettingsService.ts b/webapp/packages/core-product/src/ProductSettingsService.ts index 627119c2a1..4ad9d191f4 100644 --- a/webapp/packages/core-product/src/ProductSettingsService.ts +++ b/webapp/packages/core-product/src/ProductSettingsService.ts @@ -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 { action, makeObservable, observable } from 'mobx'; +import { makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { createSettingsLayer, ROOT_SETTINGS_LAYER, SettingsSource } from '@cloudbeaver/core-settings'; @@ -20,7 +20,6 @@ export class ProductSettingsService extends SettingsSource { super(); this.settings = new Map(); makeObservable(this, { - clear: action, settings: observable.shallow, }); } diff --git a/webapp/packages/core-root/src/RootBootstrap.ts b/webapp/packages/core-root/src/RootBootstrap.ts index 24d8d5651c..aced3f3afe 100644 --- a/webapp/packages/core-root/src/RootBootstrap.ts +++ b/webapp/packages/core-root/src/RootBootstrap.ts @@ -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. @@ -23,11 +23,7 @@ export class RootBootstrap extends Bootstrap { } override register(): void { - this.settingsManagerService.registerSettings( - this.serverSettingsManagerService.settingsProvider, - this.serverSettingsManagerService.getSettingsGetter(), - this.serverSettingsManagerService.loaders, - ); + this.settingsManagerService.registerSettings(this.serverSettingsManagerService.getSettingsGetter(), this.serverSettingsManagerService.loaders); this.settingsResolverService.addResolver(SERVER_SETTINGS_LAYER, this.serverSettingsService); } } diff --git a/webapp/packages/core-root/src/Settings/ServerSettingsManagerService.ts b/webapp/packages/core-root/src/Settings/ServerSettingsManagerService.ts index 3c35b5d344..41f74e5dd8 100644 --- a/webapp/packages/core-root/src/Settings/ServerSettingsManagerService.ts +++ b/webapp/packages/core-root/src/Settings/ServerSettingsManagerService.ts @@ -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,24 +9,14 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { getCachedDataResourceLoaderState } from '@cloudbeaver/core-resource'; -import { getObjectPropertyType, type ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; -import { - ESettingsValueType, - type ISettingDescription, - ROOT_SETTINGS_GROUP, - type SettingDescriptionTransformer, - type SettingsDescriptionGetter, - type SettingsGroup, - SettingsProvider, - SettingsResolverService, -} from '@cloudbeaver/core-settings'; -import { getPathParent, type ILoadableState, schema } from '@cloudbeaver/core-utils'; +import { type ISettingDescription, ROOT_SETTINGS_GROUP, type SettingsDescriptionGetter, type SettingsGroup } from '@cloudbeaver/core-settings'; +import { getPathParent, type ILoadableState } from '@cloudbeaver/core-utils'; import { ServerSettingsResource } from './ServerSettingsResource.js'; +import { SettingsTransformationService } from './SettingsTransformationService.js'; @injectable() export class ServerSettingsManagerService { - readonly settingsProvider: SettingsProvider; readonly loaders: ReadonlyArray; get providedSettings(): Set { @@ -34,30 +24,25 @@ export class ServerSettingsManagerService { } private get settings(): ReadonlyArray> { - return (this.serverSettingsResource.data?.settings || []).map(this.mapSetting.bind(this)).filter(Boolean) as ISettingDescription[]; + return (this.serverSettingsResource.data?.settings || []) + .map(this.settingsTransformationService.mapSetting.bind(this.settingsTransformationService, this.serverGroups)) + .filter(Boolean) as ISettingDescription[]; } private serverGroups: Map; - private overrideGroups: Map; - private settingTransformers: Map; constructor( + private readonly settingsTransformationService: SettingsTransformationService, private readonly serverSettingsResource: ServerSettingsResource, - private readonly settingsResolverService: SettingsResolverService, ) { this.serverGroups = new Map(); - this.overrideGroups = new Map(); - this.settingTransformers = new Map(); - this.settingsProvider = new SettingsProvider(this.settingsResolverService, schema.object({})); this.loaders = [getCachedDataResourceLoaderState(this.serverSettingsResource, () => undefined)]; serverSettingsResource.onDataUpdate.addHandler(this.loadSettings.bind(this)); - makeObservable(this, { + makeObservable(this, { settings: computed, providedSettings: computed, serverGroups: observable.shallow, - overrideGroups: observable.shallow, - settingTransformers: observable.shallow, loadSettings: action, }); } @@ -66,42 +51,6 @@ export class ServerSettingsManagerService { return () => this.settings; } - setGroupOverride(group: string, override: SettingsGroup) { - this.overrideGroups.set(group, override); - } - - setSettingTransformer(key: string, transformer: SettingDescriptionTransformer) { - if (this.settingTransformers.has(key)) { - throw new Error('Setting transformer already exists'); - } - - this.settingTransformers.set(key, transformer); - } - - private mapSetting(property: ObjectPropertyInfo): ISettingDescription | null { - const key = property.id!; - const transformer = this.settingTransformers.get(key); - - const setting: ISettingDescription = { - key, - access: { - scope: this.mapSettingScope(property.scopes || []), - }, - group: this.getSettingGroup(property.category), - type: convertObjectPropertyInfoType(property), - - name: property.displayName!, - description: property.description!, - options: property.validValues?.map(value => ({ value, name: value })) || [], - }; - - if (transformer) { - return transformer(setting); - } - - return setting; - } - private loadSettings() { this.serverGroups.forEach(group => group.parent?.deleteSubGroup(group.id)); this.serverGroups.clear(); @@ -119,47 +68,4 @@ export class ServerSettingsManagerService { this.serverGroups.set(group.id, parentGroup.createSubGroup(group.displayName)); } } - - private getSettingGroup(group?: string): SettingsGroup { - if (!group) { - return ROOT_SETTINGS_GROUP; - } - - const overrideGroup = this.overrideGroups.get(group); - if (overrideGroup) { - return overrideGroup; - } - return this.serverGroups.get(group) || ROOT_SETTINGS_GROUP; - } - - private mapSettingScope(scopes: string[]): string[] { - if (scopes.length === 0) { - return ['server']; - } - - return scopes.map(scope => { - switch (scope) { - case 'global': - return 'server'; - case 'user': - return 'client'; - default: - return scope; - } - }); - } -} - -function convertObjectPropertyInfoType(property: ObjectPropertyInfo): ESettingsValueType { - switch (getObjectPropertyType(property)) { - case 'selector': - return ESettingsValueType.Select; - case 'checkbox': - return ESettingsValueType.Checkbox; - case 'textarea': - return ESettingsValueType.Textarea; - case 'input': - default: - return ESettingsValueType.Input; - } } diff --git a/webapp/packages/core-root/src/Settings/ServerSettingsService.ts b/webapp/packages/core-root/src/Settings/ServerSettingsService.ts index f80400dd43..645c0fbdb6 100644 --- a/webapp/packages/core-root/src/Settings/ServerSettingsService.ts +++ b/webapp/packages/core-root/src/Settings/ServerSettingsService.ts @@ -34,7 +34,6 @@ export class ServerSettingsService extends SettingsSource { makeObservable(this, { refreshConfig: action, settings: observable.shallow, - clear: action, }); } @@ -66,7 +65,7 @@ export class ServerSettingsService extends SettingsSource { this.update(() => { this.clear(); for (const key of this.settings.keys()) { - this.setValue(key, null); + this.resetValue(key); } }); } diff --git a/webapp/packages/core-root/src/Settings/SettingsTransformationService.ts b/webapp/packages/core-root/src/Settings/SettingsTransformationService.ts new file mode 100644 index 0000000000..faf09c38e5 --- /dev/null +++ b/webapp/packages/core-root/src/Settings/SettingsTransformationService.ts @@ -0,0 +1,113 @@ +/* + * 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 { makeObservable, observable } from 'mobx'; + +import { injectable } from '@cloudbeaver/core-di'; +import { getObjectPropertyType, type ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; +import { + ESettingsValueType, + type ISettingDescription, + ROOT_SETTINGS_GROUP, + type SettingDescriptionTransformer, + type SettingsGroup, +} from '@cloudbeaver/core-settings'; + +@injectable() +export class SettingsTransformationService { + private overrideGroups: Map; + private settingTransformers: Map; + + constructor() { + this.overrideGroups = new Map(); + this.settingTransformers = new Map(); + + makeObservable(this, { + overrideGroups: observable.shallow, + settingTransformers: observable.shallow, + }); + } + + setGroupOverride(group: string, override: SettingsGroup): void { + this.overrideGroups.set(group, override); + } + + setSettingTransformer(key: string, transformer: SettingDescriptionTransformer): void { + if (this.settingTransformers.has(key)) { + throw new Error('Setting transformer already exists'); + } + + this.settingTransformers.set(key, transformer); + } + + mapSetting(groups: Map, property: ObjectPropertyInfo): ISettingDescription | null { + const key = property.id!; + const transformer = this.settingTransformers.get(key); + + const setting: ISettingDescription = { + key, + access: { + scope: this.mapSettingScope(property.scopes || []), + }, + group: this.getSettingGroup(groups, property.category), + type: convertObjectPropertyInfoType(property), + + name: property.displayName!, + description: property.description!, + options: property.validValues?.map(value => ({ value, name: value })) || [], + }; + + if (transformer) { + return transformer(setting); + } + + return setting; + } + + private getSettingGroup(groups: Map, group?: string): SettingsGroup { + if (!group) { + return ROOT_SETTINGS_GROUP; + } + + const overrideGroup = this.overrideGroups.get(group); + if (overrideGroup) { + return overrideGroup; + } + return groups.get(group) || ROOT_SETTINGS_GROUP; + } + + private mapSettingScope(scopes: string[]): string[] { + if (scopes.length === 0) { + return ['server']; + } + + return scopes.map(scope => { + switch (scope) { + case 'global': + return 'server'; + case 'user': + return 'client'; + default: + return scope; + } + }); + } +} + +function convertObjectPropertyInfoType(property: ObjectPropertyInfo): ESettingsValueType { + switch (getObjectPropertyType(property)) { + case 'selector': + return ESettingsValueType.Select; + case 'checkbox': + return ESettingsValueType.Checkbox; + case 'textarea': + return ESettingsValueType.Textarea; + case 'input': + default: + return ESettingsValueType.Input; + } +} diff --git a/webapp/packages/core-root/src/index.ts b/webapp/packages/core-root/src/index.ts index 4d74e3bb0b..e3e231db85 100644 --- a/webapp/packages/core-root/src/index.ts +++ b/webapp/packages/core-root/src/index.ts @@ -25,6 +25,7 @@ export * from './WorkspaceConfigEventHandler.js'; export * from './SessionEventSource.js'; export * from './QuotasService.js'; export * from './SessionResource.js'; +export * from './Settings/SettingsTransformationService.js'; export * from './Settings/ServerSettingsManagerService.js'; export * from './Settings/ServerSettingsService.js'; export * from './Settings/SESSION_SETTINGS_LAYER.js'; diff --git a/webapp/packages/core-root/src/manifest.ts b/webapp/packages/core-root/src/manifest.ts index 6c21653a91..554ed8ce5c 100644 --- a/webapp/packages/core-root/src/manifest.ts +++ b/webapp/packages/core-root/src/manifest.ts @@ -21,6 +21,7 @@ export const coreRootManifest: PluginManifest = { () => import('./PermissionsService.js').then(m => m.PermissionsService), () => import('./ServerConfigResource.js').then(m => m.ServerConfigResource), () => import('./Settings/ServerSettingsService.js').then(m => m.ServerSettingsService), + () => import('./Settings/SettingsTransformationService.js').then(m => m.SettingsTransformationService), () => import('./SessionActionService.js').then(m => m.SessionActionService), () => import('./SessionDataResource.js').then(m => m.SessionDataResource), () => import('./SessionExpireService.js').then(m => m.SessionExpireService), diff --git a/webapp/packages/core-settings-localization/src/LocalizationSettingsManagerService.ts b/webapp/packages/core-settings-localization/src/LocalizationSettingsManagerService.ts index dd1709f3b8..d46dea4dae 100644 --- a/webapp/packages/core-settings-localization/src/LocalizationSettingsManagerService.ts +++ b/webapp/packages/core-settings-localization/src/LocalizationSettingsManagerService.ts @@ -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,14 +8,12 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { LocalizationService } from '@cloudbeaver/core-localization'; import { ESettingsValueType, INTERFACE_SETTINGS_GROUP, SettingsManagerService } from '@cloudbeaver/core-settings'; - -import { SettingsLocalizationService } from './SettingsLocalizationService.js'; +import type { ILocalizationSettingsSchema } from './SettingsLocalizationService.js'; @injectable() export class LocalizationSettingsManagerService extends Bootstrap { constructor( private readonly localizationService: LocalizationService, - private readonly settingsLocalizationService: SettingsLocalizationService, private readonly settingsManagerService: SettingsManagerService, ) { super(); @@ -26,7 +24,7 @@ export class LocalizationSettingsManagerService extends Bootstrap { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settingsLocalizationService.settingsProvider, () => [ + this.settingsManagerService.registerSettings(() => [ { group: INTERFACE_SETTINGS_GROUP, key: 'core.localization.language', diff --git a/webapp/packages/core-settings-localization/src/SettingsLocalizationService.ts b/webapp/packages/core-settings-localization/src/SettingsLocalizationService.ts index eddae4f353..8218df9ac2 100644 --- a/webapp/packages/core-settings-localization/src/SettingsLocalizationService.ts +++ b/webapp/packages/core-settings-localization/src/SettingsLocalizationService.ts @@ -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,14 +22,15 @@ const settingsSchema = schema.object({ 'core.localization.language': schema.string().default(DEFAULT_LOCALE.isoCode), }); -export type ILocalizationSettings = schema.infer; +export type ILocalizationSettingsSchema = typeof settingsSchema; +export type ILocalizationSettings = schema.infer; @injectable() export class SettingsLocalizationService extends Dependency { get language(): string { return this.settingsProvider.getValue('core.localization.language'); } - readonly settingsProvider: SettingsProvider; + readonly settingsProvider: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -42,11 +43,13 @@ export class SettingsLocalizationService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settingsProvider, { 'core.localization.language': 'core.user.defaultLanguage' }), - createSettingsAliasResolver(this.settingsResolverService, this.settingsProvider, { + createSettingsAliasResolver(this.settingsResolverService, { + 'core.localization.language': 'core.user.defaultLanguage', + }), + createSettingsAliasResolver(this.settingsResolverService, { 'core.localization.language': 'core.localization.defaultLanguage', }), - createSettingsAliasResolver(this.settingsResolverService, this.settingsProvider, { 'core.localization.language': 'app.defaultLanguage' }), + createSettingsAliasResolver(this.settingsResolverService, { 'core.localization.language': 'app.defaultLanguage' }), ); makeObservable(this, { diff --git a/webapp/packages/core-settings-user/src/UserSettingsService.ts b/webapp/packages/core-settings-user/src/UserSettingsService.ts index 6b7276cca8..d6757d97d9 100644 --- a/webapp/packages/core-settings-user/src/UserSettingsService.ts +++ b/webapp/packages/core-settings-user/src/UserSettingsService.ts @@ -88,7 +88,7 @@ export class UserSettingsService extends SettingsSource { restoreDefaults() { this.resetChanges(); for (const key of this.getSource().keys()) { - this.setValue(key, null); + this.resetValue(key); } } diff --git a/webapp/packages/core-settings/src/IEditableSettingsSource.ts b/webapp/packages/core-settings/src/IEditableSettingsSource.ts new file mode 100644 index 0000000000..51c6f9d4b0 --- /dev/null +++ b/webapp/packages/core-settings/src/IEditableSettingsSource.ts @@ -0,0 +1,25 @@ +/* + * 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 { ISettingsSource } from './ISettingsSource.js'; + +export interface IEditableSettingsSource extends ISettingsSource { + clear: () => void; + isEdited: (key?: any) => boolean; + isReadOnly: (key: any) => boolean; + getEditedValue: (key: any) => any | undefined; + setValue: (key: any, value: any) => void; + resetValue: (key: any) => void; + save: () => Promise; + isOverrideDefaults?: () => boolean; + restoreDefaults?: () => void; +} + +export function isEditableSettingsSource(source: ISettingsSource): source is IEditableSettingsSource { + return 'isEdited' in source; +} diff --git a/webapp/packages/core-settings/src/ISettingsResolverSource.ts b/webapp/packages/core-settings/src/ISettingsResolverSource.ts index 7897db67fb..60fcfaa431 100644 --- a/webapp/packages/core-settings/src/ISettingsResolverSource.ts +++ b/webapp/packages/core-settings/src/ISettingsResolverSource.ts @@ -1,14 +1,15 @@ /* * 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 type { IEditableSettingsSource } from './IEditableSettingsSource.js'; import type { ISettingsSource } from './ISettingsSource.js'; import type { ISettingsLayer } from './SettingsLayer.js'; -export interface ISettingsResolverSource extends ISettingsSource { +export interface ISettingsResolverSource extends IEditableSettingsSource { hasResolver: (layer: ISettingsLayer, resolver: ISettingsSource) => boolean; addResolver: (layer: ISettingsLayer, ...resolvers: ISettingsSource[]) => void; removeResolver: (layer: ISettingsLayer, resolver: ISettingsSource) => void; diff --git a/webapp/packages/core-settings/src/ISettingsSource.ts b/webapp/packages/core-settings/src/ISettingsSource.ts index 8577da8ae0..084ff5ed37 100644 --- a/webapp/packages/core-settings/src/ISettingsSource.ts +++ b/webapp/packages/core-settings/src/ISettingsSource.ts @@ -15,13 +15,5 @@ export interface ISettingChangeData { export interface ISettingsSource { readonly onChange: ISyncExecutor; has: (key: any) => boolean; - isEdited: (key?: any) => boolean; - isReadOnly: (key: any) => boolean; getValue: (key: any) => any | undefined; - getEditedValue: (key: any) => any | undefined; - setValue: (key: any, value: any) => void; - save: () => Promise; - clear: () => void; - isOverrideDefaults?: () => boolean; - restoreDefaults?: () => void; } diff --git a/webapp/packages/core-settings/src/SettingsManager/ISettingDescription.ts b/webapp/packages/core-settings/src/SettingsManager/ISettingDescription.ts index e3c6f16189..82cabc6ae2 100644 --- a/webapp/packages/core-settings/src/SettingsManager/ISettingDescription.ts +++ b/webapp/packages/core-settings/src/SettingsManager/ISettingDescription.ts @@ -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. @@ -20,7 +20,7 @@ export interface ISettingOptions { } export interface ISettingDescription { - key: keyof T; + key: Exclude; access: ISettingAccess; type: ESettingsValueType; diff --git a/webapp/packages/core-settings/src/SettingsManager/SettingsManagerService.ts b/webapp/packages/core-settings/src/SettingsManager/SettingsManagerService.ts index 99893be01d..e5dfa5a22e 100644 --- a/webapp/packages/core-settings/src/SettingsManager/SettingsManagerService.ts +++ b/webapp/packages/core-settings/src/SettingsManager/SettingsManagerService.ts @@ -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. @@ -10,7 +10,6 @@ import { computed, makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import type { ILoadableState, schema } from '@cloudbeaver/core-utils'; -import type { SettingsProvider } from '../SettingsProvider.js'; import type { ISettingDescription, SettingsDescriptionGetter } from './ISettingDescription.js'; interface SettingsProviderItem { @@ -41,10 +40,9 @@ export class SettingsManagerService { } registerSettings( - provider: SettingsProvider, settingsGetter: SettingsDescriptionGetter>, loaders?: ReadonlyArray, - ) { + ): void { this.settings.push({ settingsGetter, loaders, diff --git a/webapp/packages/core-settings/src/SettingsProvider.ts b/webapp/packages/core-settings/src/SettingsProvider.ts index 60813f6c28..df28669729 100644 --- a/webapp/packages/core-settings/src/SettingsProvider.ts +++ b/webapp/packages/core-settings/src/SettingsProvider.ts @@ -8,12 +8,13 @@ import { type ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import type { schema } from '@cloudbeaver/core-utils'; -import type { ISettingChangeData, ISettingsSource } from './ISettingsSource.js'; +import type { ISettingChangeData } from './ISettingsSource.js'; +import type { IEditableSettingsSource } from './IEditableSettingsSource.js'; -export class SettingsProvider implements ISettingsSource { +export class SettingsProvider implements IEditableSettingsSource { readonly onChange: ISyncExecutor>>; constructor( - private readonly source: ISettingsSource, + protected readonly source: IEditableSettingsSource, readonly schema: TSchema, ) { this.onChange = new SyncExecutor(); @@ -69,6 +70,10 @@ export class SettingsProvider implem this.source.setValue(key, value); } + resetValue>(key: TKey): void { + this.source.resetValue(key); + } + clear(): void { this.source.clear(); } diff --git a/webapp/packages/core-settings/src/SettingsProviderService.ts b/webapp/packages/core-settings/src/SettingsProviderService.ts index f49a45e1b0..667e141502 100644 --- a/webapp/packages/core-settings/src/SettingsProviderService.ts +++ b/webapp/packages/core-settings/src/SettingsProviderService.ts @@ -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,7 +27,7 @@ export class SettingsProviderService { }); } - createSettings(schema: TSchema) { + createSettings(schema: TSchema): SettingsProvider { this.mergedSchema = this.mergedSchema.merge(schema); const settings = new SettingsProvider(this.settingsResolverService, schema); return settings; diff --git a/webapp/packages/core-settings/src/SettingsResolverSource.test.ts b/webapp/packages/core-settings/src/SettingsResolverSource.test.ts index bd214b837f..da1c9a9c4d 100644 --- a/webapp/packages/core-settings/src/SettingsResolverSource.test.ts +++ b/webapp/packages/core-settings/src/SettingsResolverSource.test.ts @@ -11,7 +11,6 @@ import { expectDeprecatedSettingMessage, expectNoDeprecatedSettingMessage, addDe import { SettingsResolverSource } from './SettingsResolverSource.js'; import { createSettingsLayer, ROOT_SETTINGS_LAYER } from './SettingsLayer.js'; import { createSettingsAliasResolver } from './createSettingsAliasResolver.js'; -import { SettingsProvider } from './SettingsProvider.js'; import { schema } from '@cloudbeaver/core-utils'; import { SettingsSource } from './SettingsSource.js'; import { initKnownConsoleMessages } from '@cloudbeaver/tests-runner'; @@ -62,7 +61,7 @@ export class MemorySettingsService extends SettingsSource { this.update(() => { this.clear(); for (const key of this.settings.keys()) { - this.setValue(key, null); + this.resetValue(key); } }); } @@ -100,7 +99,7 @@ describe('SettingsResolverSource', () => { settingsResolver.addResolver(MEMORY_SETTINGS_LAYER, memorySettingsSource); settingsResolver.addResolver( ROOT_SETTINGS_LAYER, - createSettingsAliasResolver(settingsResolver, new SettingsProvider(settingsResolver, settingsSchema), { + createSettingsAliasResolver(settingsResolver, { value: 'deprecated', }), ); @@ -126,10 +125,10 @@ describe('SettingsResolverSource', () => { settingsResolver.addResolver(MEMORY_SETTINGS_LAYER, memorySettingsSource); settingsResolver.addResolver( ROOT_SETTINGS_LAYER, - createSettingsAliasResolver(settingsResolver, new SettingsProvider(settingsResolver, settingsSchema), { + createSettingsAliasResolver(settingsResolver, { value: 'deprecated', }), - createSettingsAliasResolver(settingsResolver, new SettingsProvider(settingsResolver, settingsSchema), { + createSettingsAliasResolver(settingsResolver, { value: 'deprecated2', }), ); diff --git a/webapp/packages/core-settings/src/SettingsResolverSource.ts b/webapp/packages/core-settings/src/SettingsResolverSource.ts index f012403ee3..9dc01b7064 100644 --- a/webapp/packages/core-settings/src/SettingsResolverSource.ts +++ b/webapp/packages/core-settings/src/SettingsResolverSource.ts @@ -13,15 +13,18 @@ import { isNotNullDefined } from '@dbeaver/js-helpers'; import type { ISettingsResolverSource } from './ISettingsResolverSource.js'; import type { ISettingChangeData, ISettingsSource } from './ISettingsSource.js'; import type { ISettingsLayer } from './SettingsLayer.js'; +import { isEditableSettingsSource, type IEditableSettingsSource } from './IEditableSettingsSource.js'; + +type SettingsSource = ISettingsSource | IEditableSettingsSource; interface ISettingsSourcesLayer { layer: ISettingsLayer; - sources: ISettingsSource[]; + sources: SettingsSource[]; } export class SettingsResolverSource implements ISettingsResolverSource { readonly onChange: ISyncExecutor; - protected get sources(): ISettingsSource[] { + protected get sources(): SettingsSource[] { return this.layers .slice() .sort((a, b) => a.layer.level - b.layer.level) @@ -42,11 +45,11 @@ export class SettingsResolverSource implements ISettingsResolverSource { }); } - hasResolver(layer: ISettingsLayer, resolver: ISettingsSource): boolean { + hasResolver(layer: ISettingsLayer, resolver: SettingsSource): boolean { return this.tryGetLayerSources(layer)?.sources.includes(resolver) || false; } - removeResolver(layer: ISettingsLayer, resolver: ISettingsSource): void { + removeResolver(layer: ISettingsLayer, resolver: SettingsSource): void { const layerSources = this.getLayerSources(layer); const index = layerSources.sources.indexOf(resolver); @@ -57,7 +60,7 @@ export class SettingsResolverSource implements ISettingsResolverSource { } } - addResolver(layer: ISettingsLayer, ...resolvers: ISettingsSource[]): void { + addResolver(layer: ISettingsLayer, ...resolvers: SettingsSource[]): void { if (resolvers.some(this.hasResolver.bind(this, layer))) { return; } @@ -85,11 +88,15 @@ export class SettingsResolverSource implements ISettingsResolverSource { } isOverrideDefaults(): boolean { - return this.sources.some(r => r.isOverrideDefaults?.()); + return this.sources.some(r => isEditableSettingsSource(r) && r.isOverrideDefaults?.()); } isEdited(key?: any): boolean { - return this.sources.find(r => r.has(key))?.isEdited(key) || false; + const source = this.sources.find(r => r.has(key)); + if (!source || !isEditableSettingsSource(source)) { + return false; + } + return source.isEdited(key); } isReadOnly(key: any, stopAt?: ISettingsSource): boolean { @@ -102,7 +109,7 @@ export class SettingsResolverSource implements ISettingsResolverSource { break; } - if (source.isReadOnly(key)) { + if (!isEditableSettingsSource(source) || source.isReadOnly(key)) { return true; } } @@ -114,7 +121,8 @@ export class SettingsResolverSource implements ISettingsResolverSource { } getEditedValue(key: any): any { - return this.sources.find(r => r.has(key) && isNotNullDefined(r.getEditedValue(key)))?.getEditedValue(key); + const source = this.sources.filter(isEditableSettingsSource).find(r => r.has(key) && isNotNullDefined(r.getEditedValue(key))); + return source ? source.getEditedValue(key) : undefined; } getValue(key: any): any { @@ -123,9 +131,9 @@ export class SettingsResolverSource implements ISettingsResolverSource { setValue(key: any, value: any): void { for (const source of this.sources) { - const readonly = source.isReadOnly(key); + const readonly = !isEditableSettingsSource(source) || source.isReadOnly(key); - if (source.has(key) && readonly) { + if (source.has(key) || readonly) { throw new Error(`Can't set value for key ${key}`); } @@ -136,9 +144,21 @@ export class SettingsResolverSource implements ISettingsResolverSource { } } + resetValue(key: any): void { + for (const source of this.sources) { + const readonly = !isEditableSettingsSource(source) || source.isReadOnly(key); + if (source.has(key) || readonly) { + throw new Error(`Can't set value for key ${key}`); + } + + source.resetValue(key); + return; + } + } + async save(): Promise { for (const source of this.sources) { - if (source.isEdited()) { + if (isEditableSettingsSource(source) && source.isEdited()) { await source.save(); } } @@ -146,7 +166,9 @@ export class SettingsResolverSource implements ISettingsResolverSource { clear(): void { for (const resolver of this.sources) { - resolver.clear(); + if (isEditableSettingsSource(resolver)) { + resolver.clear(); + } } } diff --git a/webapp/packages/core-settings/src/SettingsSource.ts b/webapp/packages/core-settings/src/SettingsSource.ts index 622fb3b575..41b2239048 100644 --- a/webapp/packages/core-settings/src/SettingsSource.ts +++ b/webapp/packages/core-settings/src/SettingsSource.ts @@ -10,9 +10,10 @@ import { action, makeObservable, observable } from 'mobx'; import { type ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import { isNotNullDefined } from '@dbeaver/js-helpers'; -import type { ISettingChangeData, ISettingsSource } from './ISettingsSource.js'; +import type { ISettingChangeData } from './ISettingsSource.js'; +import type { IEditableSettingsSource } from './IEditableSettingsSource.js'; -export abstract class SettingsSource implements ISettingsSource { +export abstract class SettingsSource implements IEditableSettingsSource { readonly onChange: ISyncExecutor; private updating: boolean; protected readonly changes: Map; @@ -24,6 +25,7 @@ export abstract class SettingsSource implements ISettingsSource { makeObservable(this, { changes: observable.shallow, update: action, + clear: action, }); } @@ -44,6 +46,10 @@ export abstract class SettingsSource implements ISettingsSource { abstract getValue(key: any): any; abstract save(): Promise; + resetValue(key: any): void { + this.setValue(key, null); + } + clear(): void { this.changes.clear(); } diff --git a/webapp/packages/core-settings/src/createSettingsAliasResolver.test.ts b/webapp/packages/core-settings/src/createSettingsAliasResolver.test.ts index 6d702ddc4d..a13248e31e 100644 --- a/webapp/packages/core-settings/src/createSettingsAliasResolver.test.ts +++ b/webapp/packages/core-settings/src/createSettingsAliasResolver.test.ts @@ -7,14 +7,13 @@ */ import { describe, expect, test, beforeEach } from 'vitest'; import { SyncExecutor } from '@cloudbeaver/core-executor'; - +import type { IEditableSettingsSource } from './IEditableSettingsSource.js'; import { expectDeprecatedSettingMessage, expectNoDeprecatedSettingMessage, addDeprecatedSettingPattern, } from './__custom_mocks__/expectDeprecatedSettingMessage.js'; import { createSettingsAliasResolver, DEPRECATED_SETTINGS } from './createSettingsAliasResolver.js'; -import type { ISettingsSource } from './ISettingsSource.js'; import { initKnownConsoleMessages } from '@cloudbeaver/tests-runner'; const deprecatedSettings = { @@ -26,7 +25,7 @@ const newSettings = { value: 'value', }; -function createSource(settings: Record): ISettingsSource { +function createSource(settings: Record): IEditableSettingsSource { return { onChange: new SyncExecutor(), has(key: any): boolean { @@ -45,13 +44,14 @@ function createSource(settings: Record): ISettingsSource { return undefined; }, setValue(key: any, value: any): void {}, + resetValue(key: any): void {}, async save(): Promise {}, clear(): void {}, }; } function createResolver(settings: Record) { - return createSettingsAliasResolver(createSource(settings), null as any, { + return createSettingsAliasResolver(createSource(settings), { value: 'deprecated', }); } diff --git a/webapp/packages/core-settings/src/createSettingsAliasResolver.ts b/webapp/packages/core-settings/src/createSettingsAliasResolver.ts index 7b25a156ce..fc26b5d702 100644 --- a/webapp/packages/core-settings/src/createSettingsAliasResolver.ts +++ b/webapp/packages/core-settings/src/createSettingsAliasResolver.ts @@ -8,8 +8,8 @@ import { type ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import { invertObject, type schema } from '@cloudbeaver/core-utils'; -import type { ISettingChangeData, ISettingsSource } from './ISettingsSource.js'; -import type { SettingsProvider } from './SettingsProvider.js'; +import type { ISettingChangeData } from './ISettingsSource.js'; +import type { IEditableSettingsSource } from './IEditableSettingsSource.js'; export const DEPRECATED_SETTINGS = new Set(); @@ -42,10 +42,9 @@ function withLock(fn: (locked: boolean) => T): T { } export function createSettingsAliasResolver( - source: ISettingsSource, - target: SettingsProvider, + source: IEditableSettingsSource, mappings: SettingsMapping>, -): ISettingsSource { +): IEditableSettingsSource { type targetSchema = schema.infer; const reversed = invertObject(mappings); @@ -122,6 +121,16 @@ export function createSettingsAliasResolver { + if (!locked) { + source.resetValue(key); + } + }); + }, async save() {}, clear() {}, }; diff --git a/webapp/packages/core-settings/src/createSettingsOverrideResolver.ts b/webapp/packages/core-settings/src/createSettingsOverrideResolver.ts new file mode 100644 index 0000000000..61982f7c7c --- /dev/null +++ b/webapp/packages/core-settings/src/createSettingsOverrideResolver.ts @@ -0,0 +1,94 @@ +/* + * 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 ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; +import { type schema } from '@cloudbeaver/core-utils'; +import { isNotNullDefined } from '@dbeaver/js-helpers'; + +import type { ISettingChangeData, ISettingsSource } from './ISettingsSource.js'; +import type { IEditableSettingsSource } from './IEditableSettingsSource.js'; + +interface ISettingsOverride { + key: string; + map?: (value: any) => TTarget[TKey]; + filter?: (value: any) => boolean; +} + +type SettingsMapping = Partial<{ + [key in keyof TTarget]: string | ISettingsOverride; +}>; + +export function createSettingsOverrideResolver( + source: ISettingsSource | IEditableSettingsSource, + mappings: SettingsMapping>, +): ISettingsSource { + type targetSchema = schema.infer; + const fullDescriptionOverrides: Record> = {} as any; + const reversed: Record = {}; + + for (const key in mappings) { + const value = mappings[key]; + if (typeof value === 'string') { + reversed[value] = key; + fullDescriptionOverrides[key] = { + key: value, + filter: (value: any) => isNotNullDefined(value), + map: (value: any) => value, + }; + } else if (value && typeof value === 'object' && 'key' in value) { + reversed[value.key] = key; + fullDescriptionOverrides[key] = { + key: value.key, + filter: value.filter || ((value: any) => isNotNullDefined(value)), + map: value.map || ((value: any) => value), + }; + } + } + + function reverseMapKey(key: string): keyof targetSchema { + return reversed[key] || (key as keyof targetSchema); + } + + const onChange: ISyncExecutor> = new SyncExecutor(); + + source.onChange.next( + onChange, + data => ({ ...data, key: reverseMapKey(data.key) }), + data => data.key in reversed, + ); + + return { + onChange, + has(key) { + if (!(key in fullDescriptionOverrides)) { + return false; + } + + const override = fullDescriptionOverrides[key]!; + + if (source.has(override.key)) { + return override.filter!(source.getValue(override.key)); + } + + return false; + }, + getValue(key) { + if (!(key in fullDescriptionOverrides)) { + return undefined; + } + + const override = fullDescriptionOverrides[key]!; + const value = source.getValue(override.key); + + if (override) { + return override.map!(value); + } + + return value; + }, + }; +} diff --git a/webapp/packages/core-settings/src/index.ts b/webapp/packages/core-settings/src/index.ts index c7ec92d94d..b4ea35f491 100644 --- a/webapp/packages/core-settings/src/index.ts +++ b/webapp/packages/core-settings/src/index.ts @@ -1,15 +1,17 @@ /* * 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. */ export * from './manifest.js'; +export * from './createSettingsOverrideResolver.js'; export * from './createSettingsAliasResolver.js'; export * from './SettingsScopeType.js'; export * from './SettingsResolverSource.js'; export * from './ISettingsResolverSource.js'; +export * from './IEditableSettingsSource.js'; export * from './ISettingsSource.js'; export * from './SettingsLayer.js'; export * from './SettingsResolverService.js'; diff --git a/webapp/packages/core-theming/src/SystemThemeService.ts b/webapp/packages/core-theming/src/SystemThemeService.ts index e5633e2489..2cafef2fff 100644 --- a/webapp/packages/core-theming/src/SystemThemeService.ts +++ b/webapp/packages/core-theming/src/SystemThemeService.ts @@ -55,27 +55,12 @@ export class SystemThemeService extends Bootstrap { has: function (key: IThemeSettingsKey): boolean { return key === 'core.theming.theme'; }, - isEdited: function (key?: IThemeSettingsKey): boolean { - return false; - }, - isReadOnly: function (key: IThemeSettingsKey): boolean { - return true; - }, getValue: function (key: IThemeSettingsKey) { if (key === 'core.theming.theme') { return 'system'; } return undefined; }, - getEditedValue: function (key: IThemeSettingsKey) { - if (key === 'core.theming.theme') { - return 'system'; - } - return undefined; - }, - setValue: function (key: IThemeSettingsKey, value: any): void {}, - save: async function (): Promise {}, - clear: function (): void {}, }); } diff --git a/webapp/packages/core-theming/src/ThemeSettingsManagementService.ts b/webapp/packages/core-theming/src/ThemeSettingsManagementService.ts index df75cc8966..c76d599a04 100644 --- a/webapp/packages/core-theming/src/ThemeSettingsManagementService.ts +++ b/webapp/packages/core-theming/src/ThemeSettingsManagementService.ts @@ -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,13 +9,13 @@ import { Dependency, injectable } from '@cloudbeaver/core-di'; import { ESettingsValueType, INTERFACE_SETTINGS_GROUP, SettingsManagerService } from '@cloudbeaver/core-settings'; import { ThemeService } from './ThemeService.js'; -import { ThemeSettingsService } from './ThemeSettingsService.js'; +import type { IThemeSettingsSchema } from './ThemeSettingsService.js'; @injectable() export class ThemeSettingsManagementService extends Dependency { - constructor(themeSettingsService: ThemeSettingsService, themeService: ThemeService, settingsManagerService: SettingsManagerService) { + constructor(themeService: ThemeService, settingsManagerService: SettingsManagerService) { super(); - settingsManagerService.registerSettings(themeSettingsService.settings, () => [ + settingsManagerService.registerSettings(() => [ { key: 'core.theming.theme', access: { diff --git a/webapp/packages/core-theming/src/ThemeSettingsService.ts b/webapp/packages/core-theming/src/ThemeSettingsService.ts index 11d656f094..2c05a0f41e 100644 --- a/webapp/packages/core-theming/src/ThemeSettingsService.ts +++ b/webapp/packages/core-theming/src/ThemeSettingsService.ts @@ -21,7 +21,8 @@ const settingsSchema = schema.object({ 'core.theming.theme': schema.string().default(FALLBACK_THEME_ID), }); -export type IThemeSettings = schema.infer; +export type IThemeSettingsSchema = typeof settingsSchema; +export type IThemeSettings = schema.infer; export type IThemeSettingsKey = keyof IThemeSettings; @injectable() @@ -29,7 +30,7 @@ export class ThemeSettingsService { get theme(): string { return this.settings.getValue('core.theming.theme'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -40,11 +41,11 @@ export class ThemeSettingsService { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { 'core.theming.theme': 'core.user.defaultTheme' }), - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'core.theming.theme': 'core.user.defaultTheme' }), + createSettingsAliasResolver(this.settingsResolverService, { 'core.theming.theme': 'core.localization.defaultTheme', }), - createSettingsAliasResolver(this.settingsResolverService, this.settings, { 'core.theming.theme': 'app.defaultTheme' }), + createSettingsAliasResolver(this.settingsResolverService, { 'core.theming.theme': 'app.defaultTheme' }), ); } } diff --git a/webapp/packages/core-ui/src/Form/FormPart.ts b/webapp/packages/core-ui/src/Form/FormPart.ts index 2ea4271ad1..d6971b6bc0 100644 --- a/webapp/packages/core-ui/src/Form/FormPart.ts +++ b/webapp/packages/core-ui/src/Form/FormPart.ts @@ -43,7 +43,7 @@ export abstract class FormPart implements IFormPar this.formState.formatTask.addHandler(executorHandlerFilter(() => this.isLoaded() && this.isChanged, this.format.bind(this))); this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.handleValidation.bind(this))); - makeObservable(this, { + makeObservable(this, { initialState: observable, state: observable, exception: observable.ref, @@ -54,6 +54,7 @@ export abstract class FormPart implements IFormPar setInitialState: action, isDisabled: computed, isChanged: computed, + setState: action, }); } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsForm/TeamForm.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsForm/TeamForm.tsx index 2c25e6fb86..0fd70d4b50 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsForm/TeamForm.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsForm/TeamForm.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import { Button, Container, Form, s, StatusMessage, useForm, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { ENotificationType, NotificationService } from '@cloudbeaver/core-events'; -import { TabList, TabPanelList, TabsState } from '@cloudbeaver/core-ui'; +import { FormMode, TabList, TabPanelList, TabsState } from '@cloudbeaver/core-ui'; import { getFirstException } from '@cloudbeaver/core-utils'; import style from './TeamForm.module.css'; @@ -44,7 +44,9 @@ export const TeamForm = observer(function TeamForm({ state, onCancel, onS notificationService.logSuccess({ title, message }); onSave?.(); - onCancel?.(); + if (initialMode === FormMode.Create) { + onCancel?.(); + } } else { if (exception) { const errorKey = state.mode === 'create' ? 'administration_teams_team_create_error' : 'administration_teams_team_save_error'; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx index 03ad198ae5..847eae7ba5 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx @@ -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. @@ -10,7 +10,18 @@ import { observer } from 'mobx-react-lite'; import type { AdministrationItemContentComponent } from '@cloudbeaver/core-administration'; import { s, SContext, type StyleRegistry, ToolsPanel, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; -import { type ITabData, Tab, TabList, TabPanel, TabPanelStyles, TabsState, TabStyles, TabTitle, TabTitleStyles } from '@cloudbeaver/core-ui'; +import { + type ITabData, + Tab, + TabList, + TabPanel, + TabPanelList, + TabPanelStyles, + TabsState, + TabStyles, + TabTitle, + TabTitleStyles, +} from '@cloudbeaver/core-ui'; import style from './shared/UsersAdministration.module.css'; import tabStyle from './shared/UsersAdministrationTab.module.css'; @@ -19,6 +30,7 @@ import TabTitleModuleStyles from './shared/UsersAdministrationTabTitle.module.cs import { TeamsPage } from './Teams/TeamsTable/TeamsPage.js'; import { EUsersAdministrationSub, UsersAdministrationNavigationService } from './UsersAdministrationNavigationService.js'; import { UsersPage } from './UsersTable/UsersPage.js'; +import { UsersAdministrationService } from './UsersAdministrationService.js'; const tabPanelRegistry: StyleRegistry = [[TabPanelStyles, { mode: 'append', styles: [tabPanelStyle] }]]; @@ -30,6 +42,7 @@ const mainTabsRegistry: StyleRegistry = [ export const UsersAdministration: AdministrationItemContentComponent = observer(function UsersAdministration({ sub, param }) { const translate = useTranslate(); const usersAdministrationNavigationService = useService(UsersAdministrationNavigationService); + const usersAdministrationService = useService(UsersAdministrationService); const subName = sub?.name || EUsersAdministrationSub.Users; const styles = useS(style, tabStyle); @@ -44,18 +57,23 @@ export const UsersAdministration: AdministrationItemContentComponent = observer( } return ( - + - - + + {translate('authentication_administration_item_users')} {translate('administration_teams_tab_title')} - - + + @@ -64,6 +82,7 @@ export const UsersAdministration: AdministrationItemContentComponent = observer( + ); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts index cd8a39ae01..be9de3dfc7 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts @@ -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. @@ -32,7 +32,7 @@ export class UsersAdministrationNavigationService { this.navToSub(EUsersAdministrationSub.Users, ADMINISTRATION_ITEM_USER_CREATE_PARAM); } - navToSub(sub: EUsersAdministrationSub, param?: string): void { + navToSub(sub: EUsersAdministrationSub | string, param?: string): void { this.administrationScreenService.navigateToItemSub(UsersAdministrationNavigationService.ItemName, sub, param); } } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts index 2904b95afa..18ebbcdfd5 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts @@ -7,10 +7,11 @@ */ import React from 'react'; -import { AdministrationItemService } from '@cloudbeaver/core-administration'; +import { AdministrationItemService, type IAdministrationItem } from '@cloudbeaver/core-administration'; import { type AdminUser, TeamsResource, UsersResource } from '@cloudbeaver/core-authentication'; import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { TabsContainer } from '@cloudbeaver/core-ui'; import { CreateTeamService } from './Teams/TeamsTable/CreateTeamService.js'; import { EUsersAdministrationSub, UsersAdministrationNavigationService } from './UsersAdministrationNavigationService.js'; @@ -37,7 +38,9 @@ export interface IUserDetailsInfoProps { @injectable() export class UsersAdministrationService extends Bootstrap { - readonly userDetailsInfoPlaceholder = new PlaceholderContainer(); + readonly tabsContainer: TabsContainer; + readonly userDetailsInfoPlaceholder: PlaceholderContainer; + administrationItem!: IAdministrationItem; constructor( private readonly administrationItemService: AdministrationItemService, @@ -47,10 +50,12 @@ export class UsersAdministrationService extends Bootstrap { private readonly usersResource: UsersResource, ) { super(); + this.userDetailsInfoPlaceholder = new PlaceholderContainer(); + this.tabsContainer = new TabsContainer('Access Control'); } - override register() { - this.administrationItemService.create({ + override register(): void { + this.administrationItem = this.administrationItemService.create({ name: UsersAdministrationNavigationService.ItemName, order: 4, sub: [ diff --git a/webapp/packages/plugin-authentication-administration/src/locales/en.ts b/webapp/packages/plugin-authentication-administration/src/locales/en.ts index a1def90ca9..72f83690a9 100644 --- a/webapp/packages/plugin-authentication-administration/src/locales/en.ts +++ b/webapp/packages/plugin-authentication-administration/src/locales/en.ts @@ -1,4 +1,5 @@ export default [ + ['authentication_administration_pages_label', 'Pages'], ['authentication_administration_user_connections_user_add', 'User Creation'], ['authentication_administration_user_connections_user_new', 'New user'], ['authentication_administration_user_connections_access_load_fail', "User's granted connections loading failed"], diff --git a/webapp/packages/plugin-authentication-administration/src/locales/fr.ts b/webapp/packages/plugin-authentication-administration/src/locales/fr.ts index 07ba216043..1fd297d68b 100644 --- a/webapp/packages/plugin-authentication-administration/src/locales/fr.ts +++ b/webapp/packages/plugin-authentication-administration/src/locales/fr.ts @@ -1,4 +1,5 @@ export default [ + ['authentication_administration_pages_label', 'Pages'], ['authentication_administration_user_connections_user_add', "Création d'utilisateur"], ['authentication_administration_user_connections_user_new', 'Nouvel utilisateur'], ['authentication_administration_user_connections_access_load_fail', "Échec du chargement des connexions accordées à l'utilisateur"], diff --git a/webapp/packages/plugin-authentication-administration/src/locales/it.ts b/webapp/packages/plugin-authentication-administration/src/locales/it.ts index 8a7ebaa986..215cc9f80f 100644 --- a/webapp/packages/plugin-authentication-administration/src/locales/it.ts +++ b/webapp/packages/plugin-authentication-administration/src/locales/it.ts @@ -1,4 +1,5 @@ export default [ + ['authentication_administration_pages_label', 'Pagine'], ['authentication_administration_user_connections_user_add', 'Creazione di Utente'], ['authentication_administration_user_connections_user_new', 'Nuovo utente'], ['authentication_administration_user_connections_access_load_fail', "Errore in fase di caricamento delle connessioni autorizzate all'utente"], diff --git a/webapp/packages/plugin-authentication-administration/src/locales/ru.ts b/webapp/packages/plugin-authentication-administration/src/locales/ru.ts index b8141837d2..ef63645e25 100644 --- a/webapp/packages/plugin-authentication-administration/src/locales/ru.ts +++ b/webapp/packages/plugin-authentication-administration/src/locales/ru.ts @@ -1,4 +1,5 @@ export default [ + ['authentication_administration_pages_label', 'Страницы'], ['authentication_administration_user_connections_user_add', 'Создание пользователя'], ['authentication_administration_user_connections_user_new', 'Новый пользователь'], ['authentication_administration_user_connections_access_load_fail', 'Не удалось загрузить доступные пользователю коннекшены'], diff --git a/webapp/packages/plugin-authentication-administration/src/locales/vi.ts b/webapp/packages/plugin-authentication-administration/src/locales/vi.ts index 1c102ada45..7281f652b5 100644 --- a/webapp/packages/plugin-authentication-administration/src/locales/vi.ts +++ b/webapp/packages/plugin-authentication-administration/src/locales/vi.ts @@ -1,4 +1,5 @@ export default [ + ['authentication_administration_pages_label', 'Pages'], ['authentication_administration_user_connections_user_add', 'Tạo Người dùng'], ['authentication_administration_user_connections_user_new', 'Người dùng mới'], ['authentication_administration_user_connections_access_load_fail', 'Tải kết nối được cấp cho người dùng thất bại'], diff --git a/webapp/packages/plugin-authentication-administration/src/locales/zh.ts b/webapp/packages/plugin-authentication-administration/src/locales/zh.ts index f93843625c..bc8f83fcc8 100644 --- a/webapp/packages/plugin-authentication-administration/src/locales/zh.ts +++ b/webapp/packages/plugin-authentication-administration/src/locales/zh.ts @@ -1,4 +1,5 @@ export default [ + ['authentication_administration_pages_label', '页面'], ['authentication_administration_user_connections_user_add', '创建用户'], ['authentication_administration_user_connections_user_new', '新用户'], ['authentication_administration_user_connections_access_load_fail', '用户授予的连接加载失败'], diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnectionSettingsService.ts b/webapp/packages/plugin-connection-custom/src/CustomConnectionSettingsService.ts index 7a9f2f77a6..fa163c2749 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnectionSettingsService.ts +++ b/webapp/packages/plugin-connection-custom/src/CustomConnectionSettingsService.ts @@ -32,7 +32,7 @@ export class CustomConnectionSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { key: 'plugin.connection-custom.disabled', access: { diff --git a/webapp/packages/plugin-connection-search/src/ConnectionSearchSettingsService.ts b/webapp/packages/plugin-connection-search/src/ConnectionSearchSettingsService.ts index 35990204e9..ac5fb6a0b3 100644 --- a/webapp/packages/plugin-connection-search/src/ConnectionSearchSettingsService.ts +++ b/webapp/packages/plugin-connection-search/src/ConnectionSearchSettingsService.ts @@ -32,7 +32,7 @@ export class ConnectionSearchSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { group: CONNECTIONS_SETTINGS_GROUP, key: 'plugin.connection-search.disabled', diff --git a/webapp/packages/plugin-connections/src/PluginConnectionsSettingsService.ts b/webapp/packages/plugin-connections/src/PluginConnectionsSettingsService.ts index 5b4466df3e..303cff8508 100644 --- a/webapp/packages/plugin-connections/src/PluginConnectionsSettingsService.ts +++ b/webapp/packages/plugin-connections/src/PluginConnectionsSettingsService.ts @@ -35,7 +35,7 @@ export class PluginConnectionsSettingsService extends Dependency { private registerSettings() { // todo: probably not working as a separate setting - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { key: 'plugin.connections.hideConnectionViewForUsers', type: ESettingsValueType.Checkbox, diff --git a/webapp/packages/plugin-data-import/src/DataImportSettingsService.ts b/webapp/packages/plugin-data-import/src/DataImportSettingsService.ts index f592a79455..168c0ef28a 100644 --- a/webapp/packages/plugin-data-import/src/DataImportSettingsService.ts +++ b/webapp/packages/plugin-data-import/src/DataImportSettingsService.ts @@ -29,7 +29,7 @@ export class DataImportSettingsService { ) { this.settings = this.settingsProviderService.createSettings(defaultSettings); - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { group: DATA_EDITOR_SETTINGS_GROUP, key: 'plugin.data-import.disabled', diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.ts index a100079299..3b309d53ab 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGridSettingsService.ts @@ -23,7 +23,8 @@ const defaultSettings = schema.object({ 'plugin.data-spreadsheet.showDescriptionInHeader': schemaExtra.stringedBoolean().default(true), }); -export type DataGridSettings = schema.infer; +export type DataGridSettingsSchema = typeof defaultSettings; +export type DataGridSettings = schema.infer; @injectable() export class DataGridSettingsService extends Dependency { @@ -35,7 +36,7 @@ export class DataGridSettingsService extends Dependency { return this.settings.getValue('plugin.data-spreadsheet.showDescriptionInHeader'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -47,7 +48,7 @@ export class DataGridSettingsService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'plugin.data-spreadsheet.hidden': 'plugin_data_spreadsheet_new.hidden', }), ); @@ -56,7 +57,7 @@ export class DataGridSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { group: DATA_EDITOR_SETTINGS_GROUP, key: 'plugin.data-spreadsheet.hidden', diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts index 36a8244a79..695e4eafa1 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerSettingsService.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ import { Dependency, injectable } from '@cloudbeaver/core-di'; -import { ServerSettingsManagerService } from '@cloudbeaver/core-root'; +import { ServerSettingsManagerService, SettingsTransformationService } from '@cloudbeaver/core-root'; import { createSettingsAliasResolver, ESettingsValueType, @@ -33,7 +33,8 @@ const defaultSettings = schema.object({ 'plugin.data-viewer.export.disabled': schemaExtra.stringedBoolean().default(false), }); -export type DataViewerSettings = schema.infer; +export type DataViewerSettingsSchema = typeof defaultSettings; +export type DataViewerSettings = schema.infer; @injectable() export class DataViewerSettingsService extends Dependency { @@ -61,12 +62,13 @@ export class DataViewerSettingsService extends Dependency { return this.settings.getValue('resultset.maxrows'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, private readonly settingsManagerService: SettingsManagerService, private readonly settingsResolverService: SettingsResolverService, + private readonly settingsTransformationService: SettingsTransformationService, private readonly serverSettingsManagerService: ServerSettingsManagerService, ) { super(); @@ -74,7 +76,7 @@ export class DataViewerSettingsService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'plugin.data-viewer.disableEdit': 'core.app.dataViewer.disableEdit', 'plugin.data-viewer.disableCopyData': 'core.app.dataViewer.disableCopyData', 'plugin.data-viewer.fetchMax': 'core.app.dataViewer.fetchMax', @@ -82,11 +84,11 @@ export class DataViewerSettingsService extends Dependency { 'resultset.maxrows': 'core.app.dataViewer.fetchDefault', }), /** @deprecated Use settings instead, will be removed in 25.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'resultset.maxrows': 'plugin.data-viewer.fetchDefault', }), /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'plugin.data-viewer.export.disabled': 'plugin_data_export.disabled', }), ); @@ -102,7 +104,7 @@ export class DataViewerSettingsService extends Dependency { } private registerSettings() { - this.serverSettingsManagerService.setSettingTransformer( + this.settingsTransformationService.setSettingTransformer( 'resultset.maxrows', setting => ({ @@ -113,7 +115,7 @@ export class DataViewerSettingsService extends Dependency { }) as ISettingDescription, ); - this.settingsManagerService.registerSettings(this.settings, () => { + this.settingsManagerService.registerSettings(() => { const settings: ISettingDescription[] = [ { key: 'plugin.data-viewer.disableEdit', diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.ts b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.ts index 3ea0c41640..6842075975 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.ts +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerSettingsService.ts @@ -24,7 +24,8 @@ const defaultSettings = schema.object({ 'plugin.log-viewer.disabled': schemaExtra.stringedBoolean().default(false), }); -export type LogViewerSettings = schema.infer; +export type LogViewerSettingsSchema = typeof defaultSettings; +export type LogViewerSettings = schema.infer; @injectable() export class LogViewerSettingsService extends Dependency { @@ -40,7 +41,7 @@ export class LogViewerSettingsService extends Dependency { return this.settings.getValue('plugin.log-viewer.logBatchSize'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -52,7 +53,7 @@ export class LogViewerSettingsService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'plugin.log-viewer.disabled': 'core.app.logViewer.disabled', 'plugin.log-viewer.logBatchSize': 'core.app.logViewer.logBatchSize', 'plugin.log-viewer.maxLogRecords': 'core.app.logViewer.maxLogRecords', @@ -63,7 +64,7 @@ export class LogViewerSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ // { // group: LOG_VIEWER_SETTINGS_GROUP, // key: 'plugin.log-viewer.maxLogRecords', diff --git a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsSettingsService.ts b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsSettingsService.ts index 873b64be00..525d0016a0 100644 --- a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsSettingsService.ts +++ b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsSettingsService.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { Dependency, injectable } from '@cloudbeaver/core-di'; +import { injectable } from '@cloudbeaver/core-di'; import { ESettingsValueType, INTERFACE_SETTINGS_GROUP, @@ -23,7 +23,7 @@ const settings = schema.object({ type Settings = typeof settings; @injectable() -export class NavigationTabsSettingsService extends Dependency { +export class NavigationTabsSettingsService { readonly settings: SettingsProvider; get hasMultipleRows(): boolean { @@ -34,14 +34,11 @@ export class NavigationTabsSettingsService extends Dependency { private readonly settingsProviderService: SettingsProviderService, private readonly settingsManagerService: SettingsManagerService, ) { - super(); this.settings = this.settingsProviderService.createSettings(settings); - - this.registerSettings(); } - private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + registerSettingsUI() { + this.settingsManagerService.registerSettings(() => [ { group: INTERFACE_SETTINGS_GROUP, key: 'plugin.navigation-tabs.multipleRows', diff --git a/webapp/packages/plugin-navigation-tabs/src/PluginBootstrap.ts b/webapp/packages/plugin-navigation-tabs/src/PluginBootstrap.ts index a9a772100a..9f3160b489 100644 --- a/webapp/packages/plugin-navigation-tabs/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-navigation-tabs/src/PluginBootstrap.ts @@ -1,27 +1,29 @@ /* * 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 React from 'react'; import { AppScreenService } from '@cloudbeaver/core-app'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { NavigationTabsSettingsService } from './NavigationTabs/NavigationTabsSettingsService.js'; +import { importLazyComponent } from '@cloudbeaver/core-blocks'; -const NavigationTabsBar = React.lazy(async () => { - const { NavigationTabsBar } = await import('./NavigationTabs/NavigationTabsBar/index.js'); - return { default: NavigationTabsBar }; -}); +const NavigationTabsBar = importLazyComponent(() => import('./NavigationTabs/NavigationTabsBar/index.js').then(m => m.NavigationTabsBar)); @injectable() export class PluginBootstrap extends Bootstrap { - constructor(private readonly appScreenService: AppScreenService) { + constructor( + private readonly appScreenService: AppScreenService, + private readonly navigationTabsSettingsService: NavigationTabsSettingsService, + ) { super(); } override register(): void | Promise { this.appScreenService.rightAreaTop.add(NavigationTabsBar); + this.navigationTabsSettingsService.registerSettingsUI(); } } diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTreeSettingsService.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTreeSettingsService.ts index cc9860dfab..d7552695eb 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTreeSettingsService.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTreeSettingsService.ts @@ -41,7 +41,7 @@ export class NavigationTreeSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { group: NAVIGATION_TREE_SETTINGS_GROUP, key: 'plugin.navigation-tree.disabled', diff --git a/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScriptsSettingsService.ts b/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScriptsSettingsService.ts index b9ce73d15e..71269e178f 100644 --- a/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScriptsSettingsService.ts +++ b/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScriptsSettingsService.ts @@ -35,7 +35,7 @@ export class ResourceManagerScriptsSettingsService extends Dependency { } private registerSettings() { - this.settingsManagerService.registerSettings(this.settings, () => [ + this.settingsManagerService.registerSettings(() => [ { group: RESOURCE_MANAGER_SETTINGS_GROUP, key: 'plugin.resource-manager-scripts.disabled', diff --git a/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.ts b/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.ts index 191c2811a2..16d613128e 100644 --- a/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.ts +++ b/webapp/packages/plugin-resource-manager/src/ResourceManagerSettingsService.ts @@ -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. @@ -19,14 +19,15 @@ const defaultSettings = schema.object({ 'plugin.resource-manager.disabled': schemaExtra.stringedBoolean().default(false), //! use resourceManagerEnabled in server config instead }); -export type ResourceManagerSettings = schema.infer; +export type ResourceManagerSettingsSchema = typeof defaultSettings; +export type ResourceManagerSettings = schema.infer; @injectable() export class ResourceManagerSettingsService extends Dependency { get disabled(): boolean { return this.settings.getValue('plugin.resource-manager.disabled'); } - readonly settings: SettingsProvider; + readonly settings: SettingsProvider; constructor( private readonly settingsProviderService: SettingsProviderService, @@ -37,7 +38,7 @@ export class ResourceManagerSettingsService extends Dependency { this.settingsResolverService.addResolver( ROOT_SETTINGS_LAYER, /** @deprecated Use settings instead, will be removed in 23.0.0 */ - createSettingsAliasResolver(this.settingsResolverService, this.settings, { + createSettingsAliasResolver(this.settingsResolverService, { 'plugin.resource-manager.disabled': 'plugin_resource_manager.disabled', }), ); diff --git a/webapp/packages/plugin-settings-administration/src/SettingsAdministration.tsx b/webapp/packages/plugin-settings-administration/src/SettingsAdministration.tsx index 6146514cb6..dfd808f987 100644 --- a/webapp/packages/plugin-settings-administration/src/SettingsAdministration.tsx +++ b/webapp/packages/plugin-settings-administration/src/SettingsAdministration.tsx @@ -8,15 +8,27 @@ import { observer } from 'mobx-react-lite'; import type { AdministrationItemContentProps } from '@cloudbeaver/core-administration'; -import { ColoredContainer, Form, Group, ToolsAction, ToolsPanel, useForm, useTranslate, getComputed, TextPlaceholder } 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 { Settings } from '@cloudbeaver/plugin-settings-panel'; import { TabList, TabsState, type ITabData } from '@cloudbeaver/core-ui'; import { SettingsAdministrationService } from './SettingsAdministrationService.js'; import { useState } from 'react'; +import { SettingsResolverService } from '@cloudbeaver/core-settings'; export const SettingsAdministration = observer(function SettingsAdministration() { + const settingsResolverService = useService(SettingsResolverService); const settingsAdministrationService = useService(SettingsAdministrationService); const notificationService = useService(NotificationService); const [tabId, setTabId] = useState(null); @@ -86,7 +98,11 @@ export const SettingsAdministration = observer(f - {settingsSource ? : {translate('plugin_settings_administration_no_settings')}} + {settingsSource ? ( + + ) : ( + {translate('plugin_settings_administration_no_settings')} + )} diff --git a/webapp/packages/plugin-settings-administration/src/SettingsAdministrationService.ts b/webapp/packages/plugin-settings-administration/src/SettingsAdministrationService.ts index a24b3ee543..b491572542 100644 --- a/webapp/packages/plugin-settings-administration/src/SettingsAdministrationService.ts +++ b/webapp/packages/plugin-settings-administration/src/SettingsAdministrationService.ts @@ -6,11 +6,11 @@ * 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 type { IEditableSettingsSource } from '@cloudbeaver/core-settings'; import { TabsContainer } from '@cloudbeaver/core-ui'; export interface IAdministrationSettingsOptions { - source: ISettingsSource; + source: IEditableSettingsSource; accessor: string[]; } diff --git a/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx b/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx index 42567a0134..f6083f2e55 100644 --- a/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx +++ b/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx @@ -9,32 +9,37 @@ import { observer } from 'mobx-react-lite'; import { Link, useTranslate } from '@cloudbeaver/core-blocks'; import { clsx } from '@dbeaver/ui-kit'; -import { type ISettingDescription, type ISettingsSource } from '@cloudbeaver/core-settings'; +import { SettingsResolverSource, type ISettingDescription, type IEditableSettingsSource } from '@cloudbeaver/core-settings'; import { SettingField } from './SettingField.js'; interface Props { - source: ISettingsSource; + resolver: SettingsResolverSource; + source: IEditableSettingsSource; setting: ISettingDescription; + displayRestore?: boolean; } -export const Setting = observer(function Setting({ source, setting }) { +export const Setting = observer(function Setting({ resolver, source, setting, displayRestore }) { 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 isOverride = source.has(setting.key) && source.getEditedValue(setting.key) !== null && displayRestore; function handleRestore() { - source.setValue(setting.key, null); + source.resetValue(setting.key); } return ( -
-