mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
dbeaver/pro#5886 core api for permission management (#3519)
* dbeaver/pro#5886 new permissions api * dbeaver/pro#5886 new permissions api * dbeaver/pro#5886 test + forgotten commit * dbeaver/pro#5886 test + forgotten commit, review fixes * dbeaver/#5886 test + forgotten commits * dbeaver/pro#5886 clean up CE repos * dbeaver/pro#5886 gql api for global perms * dbeaver/pro#5886 new perms validation * dbeaver/pro#5886 remove unused class * dbeaver/pro#5886 new api * chore: update permission key * dbeaver/pro#5087 feat: permissions management * feat: check permission for query execution * fix: override only restrictive permissions * fix: permissions layer * fix: status gutter duplication * fix: script execution error message display * fix: hide not available script execution actions * dbeaver/pro#5886 anon perms fix, default value for global perms * fix: hide settings groups only for specific settings * feat: restore permissions * refactor: after review * fix: merge conflicts * fix: type import * fix: merge conflicts * fix: linting --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> Co-authored-by: Wroud <wrouds@gmail.com> Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com> Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
This commit is contained in:
co-authored by
mr-anton-t
Wroud
Evgenia
kseniaguzeeva
sergeyteleshev
parent
848b95113a
commit
8ebf7eba9c
@@ -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";
|
||||
|
||||
@@ -1022,6 +1022,9 @@ public class WebSession extends BaseWebSession
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasGlobalPermission(String permissionId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private record PersistentAttribute(Object value) {
|
||||
}
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
}
|
||||
|
||||
+8
-7
@@ -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<T extends PropertyDescriptor> {
|
||||
@NotNull
|
||||
private final WebSession webSession;
|
||||
@NotNull
|
||||
private final List<PropertyGroupDescriptor<ProductSettingDescriptor>> groups;
|
||||
private final Collection<PropertyGroupDescriptor<T>> groups;
|
||||
|
||||
public WebProductSettings(
|
||||
public WebGroupPropertiesInfo(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull List<PropertyGroupDescriptor<ProductSettingDescriptor>> groups
|
||||
@NotNull Collection<PropertyGroupDescriptor<T>> groups
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.groups = groups;
|
||||
@@ -41,7 +42,7 @@ public class WebProductSettings {
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public List<WebSettingsGroupInfo> getGroups() {
|
||||
public List<WebSettingsGroupInfo<T>> getGroups() {
|
||||
return groups.stream()
|
||||
.map(WebSettingsGroupInfo::new)
|
||||
.collect(Collectors.toList());
|
||||
+5
-5
@@ -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<T extends PropertyDescriptor> {
|
||||
@NotNull
|
||||
private final PropertyGroupDescriptor<ProductSettingDescriptor> groupDescriptor;
|
||||
private final PropertyGroupDescriptor<T> groupDescriptor;
|
||||
|
||||
public WebSettingsGroupInfo(@NotNull PropertyGroupDescriptor<ProductSettingDescriptor> groupDescriptor) {
|
||||
public WebSettingsGroupInfo(@NotNull PropertyGroupDescriptor<T> groupDescriptor) {
|
||||
this.groupDescriptor = groupDescriptor;
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -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<API_TYPE extends DBWService> 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<API_TYPE extends DBWService> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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<ProductSettingDescriptor> getProductSettings(@NotNull WebSession webSession);
|
||||
|
||||
@WebAction
|
||||
List<WebDatabaseDriverInfo> getDriverList(@NotNull WebSession webSession, String driverId) throws DBWebException;
|
||||
|
||||
+3
-2
@@ -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<ProductSettingDescriptor> getProductSettings(@NotNull WebSession webSession) {
|
||||
return new WebGroupPropertiesInfo<>(webSession, ProductSettingsRegistry.getInstance().getSettings());
|
||||
}
|
||||
|
||||
private WebSessionProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException {
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+10
-4
@@ -1433,9 +1433,14 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
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<T extends ServletAuthApplication>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Set<String> getUserPermissions(String userId, String authRole) throws DBException {
|
||||
return getUserPermissions(userId);
|
||||
}
|
||||
@@ -1584,7 +1590,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getAnonymousUserPermissions() throws DBException {
|
||||
protected Set<String> getAnonymousUserPermissions() throws DBException {
|
||||
var anonymousUserTeam = application.getAppConfiguration().getAnonymousUserTeam();
|
||||
return getSubjectPermissions(anonymousUserTeam);
|
||||
}
|
||||
@@ -2994,7 +3000,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
}
|
||||
|
||||
|
||||
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<T extends ServletAuthApplication>
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
||||
+8
-2
@@ -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<T extends ServletAuthApplication>
|
||||
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<T extends ServletAuthApplication>
|
||||
)).schedule();
|
||||
}
|
||||
}
|
||||
return createEmbeddedSecurityController(
|
||||
var controller = createEmbeddedSecurityController(
|
||||
application, DB_INSTANCE, credentialsProvider, smConfig
|
||||
);
|
||||
if (initialization) {
|
||||
controller.initialize();
|
||||
}
|
||||
return controller;
|
||||
}
|
||||
|
||||
protected @NotNull CBDatabase createAndInitDatabaseInstance(
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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<typeof settingsSchema>(() => [
|
||||
// {
|
||||
// key: 'core.authentication.disableAnonymousAccess',
|
||||
// access: {
|
||||
|
||||
@@ -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<typeof settingsSchema>;
|
||||
export type BrowserSettingsSchema = typeof settingsSchema;
|
||||
export type CookiesSettings = schema.infer<BrowserSettingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class BrowserSettingsService extends Dependency {
|
||||
get disabled(): boolean {
|
||||
return this.settings.getValue('core.browser.cookies.disabled');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof settingsSchema>;
|
||||
readonly settings: SettingsProvider<BrowserSettingsSchema>;
|
||||
|
||||
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<BrowserSettingsSchema>(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<typeof settingsSchema>(() => [
|
||||
// {
|
||||
// group: BROWSER_COOKIES_SETTINGS_GROUP,
|
||||
// key: 'cookies.disabled',
|
||||
|
||||
@@ -33,7 +33,7 @@ export class ConnectionsSettingsService extends Dependency {
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
this.settingsManagerService.registerSettings<typeof settingsSchema>(() => [
|
||||
{
|
||||
group: CONNECTIONS_SETTINGS_GROUP,
|
||||
key: 'core.connections.disabled',
|
||||
|
||||
@@ -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<typeof settingsSchema>;
|
||||
export type EventsSettingsSchema = typeof settingsSchema;
|
||||
export type EventsSettings = schema.infer<EventsSettingsSchema>;
|
||||
|
||||
@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<typeof settingsSchema>;
|
||||
readonly settings: SettingsProvider<EventsSettingsSchema>;
|
||||
|
||||
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<EventsSettingsSchema>(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<typeof settingsSchema>(() => [
|
||||
// {
|
||||
// group: NOTIFICATIONS_SETTINGS_GROUP,
|
||||
// key: 'plugin.notifications.maxPersistentAllow',
|
||||
|
||||
@@ -24,7 +24,8 @@ const settingsSchema = schema.object({
|
||||
'core.navigation-tree.deleting': schemaExtra.stringedBoolean().default(true),
|
||||
});
|
||||
|
||||
export type NavTreeSettings = schema.infer<typeof settingsSchema>;
|
||||
export type NavTreeSettingsSchema = typeof settingsSchema;
|
||||
export type NavTreeSettings = schema.infer<NavTreeSettingsSchema>;
|
||||
|
||||
@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<typeof settingsSchema>;
|
||||
readonly settings: SettingsProvider<NavTreeSettingsSchema>;
|
||||
|
||||
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<NavTreeSettingsSchema>(this.settingsResolverService, {
|
||||
'core.navigation-tree.childrenLimit': 'core.app.navigationTree.childrenLimit',
|
||||
}),
|
||||
createSettingsAliasResolver(this.settingsResolverService, this.settings, {
|
||||
createSettingsAliasResolver<NavTreeSettingsSchema>(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<typeof settingsSchema>(() => [
|
||||
{
|
||||
key: 'core.navigation-tree.childrenLimit',
|
||||
access: {
|
||||
|
||||
@@ -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, 'settings'>(this, {
|
||||
clear: action,
|
||||
settings: observable.shallow,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<schema.AnyZodObject>;
|
||||
readonly loaders: ReadonlyArray<ILoadableState>;
|
||||
|
||||
get providedSettings(): Set<string> {
|
||||
@@ -34,30 +24,25 @@ export class ServerSettingsManagerService {
|
||||
}
|
||||
|
||||
private get settings(): ReadonlyArray<ISettingDescription<any>> {
|
||||
return (this.serverSettingsResource.data?.settings || []).map(this.mapSetting.bind(this)).filter(Boolean) as ISettingDescription<any>[];
|
||||
return (this.serverSettingsResource.data?.settings || [])
|
||||
.map(this.settingsTransformationService.mapSetting.bind(this.settingsTransformationService, this.serverGroups))
|
||||
.filter(Boolean) as ISettingDescription<any>[];
|
||||
}
|
||||
|
||||
private serverGroups: Map<string, SettingsGroup>;
|
||||
private overrideGroups: Map<string, SettingsGroup>;
|
||||
private settingTransformers: Map<string, SettingDescriptionTransformer>;
|
||||
|
||||
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, 'settings' | 'providedSettings' | 'serverGroups' | 'overrideGroups' | 'settingTransformers' | 'loadSettings'>(this, {
|
||||
makeObservable<this, 'settings' | 'providedSettings' | 'serverGroups' | 'loadSettings'>(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<any> | null {
|
||||
const key = property.id!;
|
||||
const transformer = this.settingTransformers.get(key);
|
||||
|
||||
const setting: ISettingDescription<any> = {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ export class ServerSettingsService extends SettingsSource {
|
||||
makeObservable<this, 'settings' | 'refreshConfig'>(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<string, SettingsGroup>;
|
||||
private settingTransformers: Map<string, SettingDescriptionTransformer>;
|
||||
|
||||
constructor() {
|
||||
this.overrideGroups = new Map();
|
||||
this.settingTransformers = new Map();
|
||||
|
||||
makeObservable<this, 'overrideGroups' | 'settingTransformers'>(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<string, SettingsGroup>, property: ObjectPropertyInfo): ISettingDescription<any> | null {
|
||||
const key = property.id!;
|
||||
const transformer = this.settingTransformers.get(key);
|
||||
|
||||
const setting: ISettingDescription<any> = {
|
||||
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<string, SettingsGroup>, 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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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),
|
||||
|
||||
+3
-5
@@ -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<ILocalizationSettingsSchema>(() => [
|
||||
{
|
||||
group: INTERFACE_SETTINGS_GROUP,
|
||||
key: 'core.localization.language',
|
||||
|
||||
@@ -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<typeof settingsSchema>;
|
||||
export type ILocalizationSettingsSchema = typeof settingsSchema;
|
||||
export type ILocalizationSettings = schema.infer<ILocalizationSettingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class SettingsLocalizationService extends Dependency {
|
||||
get language(): string {
|
||||
return this.settingsProvider.getValue('core.localization.language');
|
||||
}
|
||||
readonly settingsProvider: SettingsProvider<typeof settingsSchema>;
|
||||
readonly settingsProvider: SettingsProvider<ILocalizationSettingsSchema>;
|
||||
|
||||
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<ILocalizationSettingsSchema>(this.settingsResolverService, {
|
||||
'core.localization.language': 'core.user.defaultLanguage',
|
||||
}),
|
||||
createSettingsAliasResolver<ILocalizationSettingsSchema>(this.settingsResolverService, {
|
||||
'core.localization.language': 'core.localization.defaultLanguage',
|
||||
}),
|
||||
createSettingsAliasResolver(this.settingsResolverService, this.settingsProvider, { 'core.localization.language': 'app.defaultLanguage' }),
|
||||
createSettingsAliasResolver<ILocalizationSettingsSchema>(this.settingsResolverService, { 'core.localization.language': 'app.defaultLanguage' }),
|
||||
);
|
||||
|
||||
makeObservable(this, {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void>;
|
||||
isOverrideDefaults?: () => boolean;
|
||||
restoreDefaults?: () => void;
|
||||
}
|
||||
|
||||
export function isEditableSettingsSource(source: ISettingsSource): source is IEditableSettingsSource {
|
||||
return 'isEdited' in source;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -15,13 +15,5 @@ export interface ISettingChangeData<T = any> {
|
||||
export interface ISettingsSource {
|
||||
readonly onChange: ISyncExecutor<ISettingChangeData>;
|
||||
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<void>;
|
||||
clear: () => void;
|
||||
isOverrideDefaults?: () => boolean;
|
||||
restoreDefaults?: () => void;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,7 @@ export interface ISettingOptions {
|
||||
}
|
||||
|
||||
export interface ISettingDescription<T = object> {
|
||||
key: keyof T;
|
||||
key: Exclude<keyof T, symbol | number>;
|
||||
access: ISettingAccess;
|
||||
|
||||
type: ESettingsValueType;
|
||||
|
||||
@@ -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<T extends schema.SomeZodObject = any> {
|
||||
@@ -41,10 +40,9 @@ export class SettingsManagerService {
|
||||
}
|
||||
|
||||
registerSettings<TSchema extends schema.SomeZodObject>(
|
||||
provider: SettingsProvider<TSchema>,
|
||||
settingsGetter: SettingsDescriptionGetter<schema.infer<TSchema>>,
|
||||
loaders?: ReadonlyArray<ILoadableState>,
|
||||
) {
|
||||
): void {
|
||||
this.settings.push({
|
||||
settingsGetter,
|
||||
loaders,
|
||||
|
||||
@@ -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<TSchema extends schema.SomeZodObject = any> implements ISettingsSource {
|
||||
export class SettingsProvider<TSchema extends schema.SomeZodObject = any> implements IEditableSettingsSource {
|
||||
readonly onChange: ISyncExecutor<ISettingChangeData<keyof schema.infer<TSchema>>>;
|
||||
constructor(
|
||||
private readonly source: ISettingsSource,
|
||||
protected readonly source: IEditableSettingsSource,
|
||||
readonly schema: TSchema,
|
||||
) {
|
||||
this.onChange = new SyncExecutor();
|
||||
@@ -69,6 +70,10 @@ export class SettingsProvider<TSchema extends schema.SomeZodObject = any> implem
|
||||
this.source.setValue(key, value);
|
||||
}
|
||||
|
||||
resetValue<TKey extends keyof schema.infer<TSchema>>(key: TKey): void {
|
||||
this.source.resetValue(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.source.clear();
|
||||
}
|
||||
|
||||
@@ -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<TSchema extends schema.SomeZodObject = schema.AnyZodObject>(schema: TSchema) {
|
||||
createSettings<TSchema extends schema.SomeZodObject = schema.AnyZodObject>(schema: TSchema): SettingsProvider<TSchema> {
|
||||
this.mergedSchema = this.mergedSchema.merge(schema);
|
||||
const settings = new SettingsProvider(this.settingsResolverService, schema);
|
||||
return settings;
|
||||
|
||||
@@ -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<typeof settingsSchema>(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<typeof settingsSchema>(settingsResolver, {
|
||||
value: 'deprecated',
|
||||
}),
|
||||
createSettingsAliasResolver(settingsResolver, new SettingsProvider(settingsResolver, settingsSchema), {
|
||||
createSettingsAliasResolver<typeof settingsSchema>(settingsResolver, {
|
||||
value: 'deprecated2',
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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<ISettingChangeData>;
|
||||
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<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ISettingChangeData>;
|
||||
private updating: boolean;
|
||||
protected readonly changes: Map<any, any>;
|
||||
@@ -24,6 +25,7 @@ export abstract class SettingsSource implements ISettingsSource {
|
||||
makeObservable<this, 'update' | 'changes'>(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<void>;
|
||||
|
||||
resetValue(key: any): void {
|
||||
this.setValue(key, null);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.changes.clear();
|
||||
}
|
||||
|
||||
@@ -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<any, any>): ISettingsSource {
|
||||
function createSource(settings: Record<any, any>): IEditableSettingsSource {
|
||||
return {
|
||||
onChange: new SyncExecutor(),
|
||||
has(key: any): boolean {
|
||||
@@ -45,13 +44,14 @@ function createSource(settings: Record<any, any>): ISettingsSource {
|
||||
return undefined;
|
||||
},
|
||||
setValue(key: any, value: any): void {},
|
||||
resetValue(key: any): void {},
|
||||
async save(): Promise<void> {},
|
||||
clear(): void {},
|
||||
};
|
||||
}
|
||||
|
||||
function createResolver(settings: Record<any, any>) {
|
||||
return createSettingsAliasResolver(createSource(settings), null as any, {
|
||||
return createSettingsAliasResolver(createSource(settings), {
|
||||
value: 'deprecated',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<T>(fn: (locked: boolean) => T): T {
|
||||
}
|
||||
|
||||
export function createSettingsAliasResolver<TTarget extends schema.SomeZodObject>(
|
||||
source: ISettingsSource,
|
||||
target: SettingsProvider<TTarget>,
|
||||
source: IEditableSettingsSource,
|
||||
mappings: SettingsMapping<schema.infer<TTarget>>,
|
||||
): ISettingsSource {
|
||||
): IEditableSettingsSource {
|
||||
type targetSchema = schema.infer<TTarget>;
|
||||
const reversed = invertObject(mappings);
|
||||
|
||||
@@ -122,6 +121,16 @@ export function createSettingsAliasResolver<TTarget extends schema.SomeZodObject
|
||||
}
|
||||
});
|
||||
},
|
||||
resetValue(key) {
|
||||
if (!(key in mappings)) {
|
||||
return;
|
||||
}
|
||||
withLock(locked => {
|
||||
if (!locked) {
|
||||
source.resetValue(key);
|
||||
}
|
||||
});
|
||||
},
|
||||
async save() {},
|
||||
clear() {},
|
||||
};
|
||||
|
||||
@@ -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<TTarget, TKey extends keyof TTarget> {
|
||||
key: string;
|
||||
map?: (value: any) => TTarget[TKey];
|
||||
filter?: (value: any) => boolean;
|
||||
}
|
||||
|
||||
type SettingsMapping<TTarget> = Partial<{
|
||||
[key in keyof TTarget]: string | ISettingsOverride<TTarget, key>;
|
||||
}>;
|
||||
|
||||
export function createSettingsOverrideResolver<TTarget extends schema.SomeZodObject>(
|
||||
source: ISettingsSource | IEditableSettingsSource,
|
||||
mappings: SettingsMapping<schema.infer<TTarget>>,
|
||||
): ISettingsSource {
|
||||
type targetSchema = schema.infer<TTarget>;
|
||||
const fullDescriptionOverrides: Record<keyof targetSchema, ISettingsOverride<targetSchema, keyof targetSchema>> = {} as any;
|
||||
const reversed: Record<string, keyof targetSchema> = {};
|
||||
|
||||
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<ISettingChangeData<any>> = 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;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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<void> {},
|
||||
clear: function (): void {},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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<IThemeSettingsSchema>(() => [
|
||||
{
|
||||
key: 'core.theming.theme',
|
||||
access: {
|
||||
|
||||
@@ -21,7 +21,8 @@ const settingsSchema = schema.object({
|
||||
'core.theming.theme': schema.string().default(FALLBACK_THEME_ID),
|
||||
});
|
||||
|
||||
export type IThemeSettings = schema.infer<typeof settingsSchema>;
|
||||
export type IThemeSettingsSchema = typeof settingsSchema;
|
||||
export type IThemeSettings = schema.infer<IThemeSettingsSchema>;
|
||||
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<typeof settingsSchema>;
|
||||
readonly settings: SettingsProvider<IThemeSettingsSchema>;
|
||||
|
||||
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<IThemeSettingsSchema>(this.settingsResolverService, { 'core.theming.theme': 'core.user.defaultTheme' }),
|
||||
createSettingsAliasResolver<IThemeSettingsSchema>(this.settingsResolverService, {
|
||||
'core.theming.theme': 'core.localization.defaultTheme',
|
||||
}),
|
||||
createSettingsAliasResolver(this.settingsResolverService, this.settings, { 'core.theming.theme': 'app.defaultTheme' }),
|
||||
createSettingsAliasResolver<IThemeSettingsSchema>(this.settingsResolverService, { 'core.theming.theme': 'app.defaultTheme' }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export abstract class FormPart<TPartState, TFormState = any> 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, 'loaded' | 'loading' | 'setInitialState'>(this, {
|
||||
makeObservable<this, 'loaded' | 'loading' | 'setInitialState' | 'setState'>(this, {
|
||||
initialState: observable,
|
||||
state: observable,
|
||||
exception: observable.ref,
|
||||
@@ -54,6 +54,7 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
|
||||
setInitialState: action,
|
||||
isDisabled: computed,
|
||||
isChanged: computed,
|
||||
setState: action,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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<Props>(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';
|
||||
|
||||
+26
-7
@@ -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 (
|
||||
<TabsState selectedId={subName} lazy onChange={openSub}>
|
||||
<TabsState selectedId={subName} autoSelect={false} container={usersAdministrationService.tabsContainer} lazy onChange={openSub}>
|
||||
<ToolsPanel bottomBorder>
|
||||
<TabList className={s(styles, { tabList: true, administrationTabs: true })} aria-label="User Administration pages" underline>
|
||||
<SContext registry={mainTabsRegistry}>
|
||||
<SContext registry={mainTabsRegistry}>
|
||||
<TabList
|
||||
className={s(styles, { tabList: true, administrationTabs: true })}
|
||||
aria-label={translate('authentication_administration_pages_label')}
|
||||
childrenFirst
|
||||
underline
|
||||
>
|
||||
<Tab tabId={EUsersAdministrationSub.Users}>
|
||||
<TabTitle>{translate('authentication_administration_item_users')}</TabTitle>
|
||||
</Tab>
|
||||
<Tab tabId={EUsersAdministrationSub.Teams}>
|
||||
<TabTitle>{translate('administration_teams_tab_title')}</TabTitle>
|
||||
</Tab>
|
||||
</SContext>
|
||||
</TabList>
|
||||
</TabList>
|
||||
</SContext>
|
||||
</ToolsPanel>
|
||||
<SContext registry={tabPanelRegistry}>
|
||||
<TabPanel tabId={EUsersAdministrationSub.Users}>
|
||||
@@ -64,6 +82,7 @@ export const UsersAdministration: AdministrationItemContentComponent = observer(
|
||||
<TabPanel tabId={EUsersAdministrationSub.Teams}>
|
||||
<TeamsPage param={param} />
|
||||
</TabPanel>
|
||||
<TabPanelList />
|
||||
</SContext>
|
||||
</TabsState>
|
||||
);
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -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<IUserDetailsInfoProps>();
|
||||
readonly tabsContainer: TabsContainer;
|
||||
readonly userDetailsInfoPlaceholder: PlaceholderContainer<IUserDetailsInfoProps>;
|
||||
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: [
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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', 'Не удалось загрузить доступные пользователю коннекшены'],
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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', '用户授予的连接加载失败'],
|
||||
|
||||
@@ -32,7 +32,7 @@ export class CustomConnectionSettingsService extends Dependency {
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
this.settingsManagerService.registerSettings<typeof settings>(() => [
|
||||
{
|
||||
key: 'plugin.connection-custom.disabled',
|
||||
access: {
|
||||
|
||||
@@ -32,7 +32,7 @@ export class ConnectionSearchSettingsService extends Dependency {
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
this.settingsManagerService.registerSettings<typeof settings>(() => [
|
||||
{
|
||||
group: CONNECTIONS_SETTINGS_GROUP,
|
||||
key: 'plugin.connection-search.disabled',
|
||||
|
||||
@@ -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<typeof defaultSettings>(() => [
|
||||
{
|
||||
key: 'plugin.connections.hideConnectionViewForUsers',
|
||||
type: ESettingsValueType.Checkbox,
|
||||
|
||||
@@ -29,7 +29,7 @@ export class DataImportSettingsService {
|
||||
) {
|
||||
this.settings = this.settingsProviderService.createSettings(defaultSettings);
|
||||
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
this.settingsManagerService.registerSettings<typeof defaultSettings>(() => [
|
||||
{
|
||||
group: DATA_EDITOR_SETTINGS_GROUP,
|
||||
key: 'plugin.data-import.disabled',
|
||||
|
||||
@@ -23,7 +23,8 @@ const defaultSettings = schema.object({
|
||||
'plugin.data-spreadsheet.showDescriptionInHeader': schemaExtra.stringedBoolean().default(true),
|
||||
});
|
||||
|
||||
export type DataGridSettings = schema.infer<typeof defaultSettings>;
|
||||
export type DataGridSettingsSchema = typeof defaultSettings;
|
||||
export type DataGridSettings = schema.infer<DataGridSettingsSchema>;
|
||||
|
||||
@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<typeof defaultSettings>;
|
||||
readonly settings: SettingsProvider<DataGridSettingsSchema>;
|
||||
|
||||
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<DataGridSettingsSchema>(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<typeof defaultSettings>(() => [
|
||||
{
|
||||
group: DATA_EDITOR_SETTINGS_GROUP,
|
||||
key: 'plugin.data-spreadsheet.hidden',
|
||||
|
||||
@@ -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<typeof defaultSettings>;
|
||||
export type DataViewerSettingsSchema = typeof defaultSettings;
|
||||
export type DataViewerSettings = schema.infer<DataViewerSettingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class DataViewerSettingsService extends Dependency {
|
||||
@@ -61,12 +62,13 @@ export class DataViewerSettingsService extends Dependency {
|
||||
return this.settings.getValue('resultset.maxrows');
|
||||
}
|
||||
|
||||
readonly settings: SettingsProvider<typeof defaultSettings>;
|
||||
readonly settings: SettingsProvider<DataViewerSettingsSchema>;
|
||||
|
||||
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<DataViewerSettingsSchema>(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<DataViewerSettingsSchema>(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<DataViewerSettingsSchema>(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<DataViewerSettings>,
|
||||
);
|
||||
|
||||
this.settingsManagerService.registerSettings(this.settings, () => {
|
||||
this.settingsManagerService.registerSettings<typeof defaultSettings>(() => {
|
||||
const settings: ISettingDescription<DataViewerSettings>[] = [
|
||||
{
|
||||
key: 'plugin.data-viewer.disableEdit',
|
||||
|
||||
@@ -24,7 +24,8 @@ const defaultSettings = schema.object({
|
||||
'plugin.log-viewer.disabled': schemaExtra.stringedBoolean().default(false),
|
||||
});
|
||||
|
||||
export type LogViewerSettings = schema.infer<typeof defaultSettings>;
|
||||
export type LogViewerSettingsSchema = typeof defaultSettings;
|
||||
export type LogViewerSettings = schema.infer<LogViewerSettingsSchema>;
|
||||
|
||||
@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<typeof defaultSettings>;
|
||||
readonly settings: SettingsProvider<LogViewerSettingsSchema>;
|
||||
|
||||
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<LogViewerSettingsSchema>(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<typeof defaultSettings>(() => [
|
||||
// {
|
||||
// group: LOG_VIEWER_SETTINGS_GROUP,
|
||||
// key: 'plugin.log-viewer.maxLogRecords',
|
||||
|
||||
+4
-7
@@ -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<Settings>;
|
||||
|
||||
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<Settings>(() => [
|
||||
{
|
||||
group: INTERFACE_SETTINGS_GROUP,
|
||||
key: 'plugin.navigation-tabs.multipleRows',
|
||||
|
||||
@@ -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<void> {
|
||||
this.appScreenService.rightAreaTop.add(NavigationTabsBar);
|
||||
this.navigationTabsSettingsService.registerSettingsUI();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export class NavigationTreeSettingsService extends Dependency {
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
this.settingsManagerService.registerSettings<typeof defaultSettings>(() => [
|
||||
{
|
||||
group: NAVIGATION_TREE_SETTINGS_GROUP,
|
||||
key: 'plugin.navigation-tree.disabled',
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export class ResourceManagerScriptsSettingsService extends Dependency {
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
this.settingsManagerService.registerSettings<Settings>(() => [
|
||||
{
|
||||
group: RESOURCE_MANAGER_SETTINGS_GROUP,
|
||||
key: 'plugin.resource-manager-scripts.disabled',
|
||||
|
||||
@@ -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<typeof defaultSettings>;
|
||||
export type ResourceManagerSettingsSchema = typeof defaultSettings;
|
||||
export type ResourceManagerSettings = schema.infer<ResourceManagerSettingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class ResourceManagerSettingsService extends Dependency {
|
||||
get disabled(): boolean {
|
||||
return this.settings.getValue('plugin.resource-manager.disabled');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof defaultSettings>;
|
||||
readonly settings: SettingsProvider<ResourceManagerSettingsSchema>;
|
||||
|
||||
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<ResourceManagerSettingsSchema>(this.settingsResolverService, {
|
||||
'plugin.resource-manager.disabled': 'plugin_resource_manager.disabled',
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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<AdministrationItemContentProps>(function SettingsAdministration() {
|
||||
const settingsResolverService = useService(SettingsResolverService);
|
||||
const settingsAdministrationService = useService(SettingsAdministrationService);
|
||||
const notificationService = useService(NotificationService);
|
||||
const [tabId, setTabId] = useState<string | null>(null);
|
||||
@@ -86,7 +98,11 @@ export const SettingsAdministration = observer<AdministrationItemContentProps>(f
|
||||
<Group hidden={hideSingleTab} box keepSize hideOverflow>
|
||||
<TabList underline />
|
||||
</Group>
|
||||
{settingsSource ? <Settings source={settingsSource} accessor={accessor} /> : <TextPlaceholder>{translate('plugin_settings_administration_no_settings')}</TextPlaceholder>}
|
||||
{settingsSource ? (
|
||||
<Settings resolver={settingsResolverService} source={settingsSource} accessor={accessor} />
|
||||
) : (
|
||||
<TextPlaceholder>{translate('plugin_settings_administration_no_settings')}</TextPlaceholder>
|
||||
)}
|
||||
</ColoredContainer>
|
||||
</Form>
|
||||
</TabsState>
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Props>(function Setting({ source, setting }) {
|
||||
export const Setting = observer<Props>(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 (
|
||||
<div className='tw:flex tw:relative tw:gap-2'>
|
||||
<div className="tw:w-1 tw:h-full" hidden>
|
||||
<div className="tw:flex tw:relative tw:gap-2">
|
||||
<div className="tw:w-1 tw:h-full" hidden={!displayRestore}>
|
||||
{isOverride && (
|
||||
<div className={clsx('tw:h-full tw:w-full tw:bg-zinc-100 tw:dark:bg-zinc-700')} title={translate('plugin_settings_panel_setting_set_in_scope')} />
|
||||
<div
|
||||
className={clsx('tw:h-full tw:w-full tw:bg-zinc-100 tw:dark:bg-zinc-700')}
|
||||
title={translate('plugin_settings_panel_setting_set_in_scope')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<SettingField setting={setting} source={source} />
|
||||
<SettingField resolver={resolver} setting={setting} source={source} />
|
||||
{isOverride && (
|
||||
<Link className="theme-typography--caption" title={translate('plugin_settings_panel_setting_reset_tooltip')} onClick={handleRestore}>
|
||||
{translate('plugin_settings_panel_setting_reset')}
|
||||
|
||||
@@ -11,21 +11,21 @@ import { useService } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
ESettingsValueType,
|
||||
SettingsProviderService,
|
||||
SettingsResolverService,
|
||||
SettingsResolverSource,
|
||||
type ISettingDescription,
|
||||
type ISettingsSource,
|
||||
type IEditableSettingsSource,
|
||||
} from '@cloudbeaver/core-settings';
|
||||
import { schemaValidationError } from '@cloudbeaver/core-utils';
|
||||
import { isNotNullDefined } from '@dbeaver/js-helpers';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
interface Props {
|
||||
source: ISettingsSource;
|
||||
resolver: SettingsResolverSource;
|
||||
source: IEditableSettingsSource;
|
||||
setting: ISettingDescription;
|
||||
}
|
||||
|
||||
export const SettingField = observer<Props>(function SettingField({ setting, source }) {
|
||||
const settingsResolverService = useService(SettingsResolverService);
|
||||
export const SettingField = observer<Props>(function SettingField({ resolver, setting, source }) {
|
||||
const settingsProviderService = useService(SettingsProviderService);
|
||||
const translate = useTranslate();
|
||||
|
||||
@@ -39,7 +39,7 @@ export const SettingField = observer<Props>(function SettingField({ setting, sou
|
||||
|
||||
let value = source.getEditedValue(setting.key);
|
||||
if (readOnly || !isNotNullDefined(value)) {
|
||||
value = settingsResolverService.getEditedValue(setting.key);
|
||||
value = resolver.getEditedValue(setting.key);
|
||||
}
|
||||
|
||||
if (setting.key in settingsProviderService.schema.shape) {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
import { useId, useState } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Container, Filter, Group, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { type ISettingsSource, ROOT_SETTINGS_GROUP, SettingsGroup } from '@cloudbeaver/core-settings';
|
||||
import { Container, Filter, getComputed, Group, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { type IEditableSettingsSource, ROOT_SETTINGS_GROUP, SettingsGroup, SettingsResolverSource } from '@cloudbeaver/core-settings';
|
||||
import { useTreeData, useTreeFilter } from '@cloudbeaver/plugin-navigation-tree';
|
||||
import { SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
@@ -20,11 +20,14 @@ import { SettingsList } from './SettingsList.js';
|
||||
import { useSettings } from './useSettings.js';
|
||||
|
||||
export interface ISettingsProps {
|
||||
source: ISettingsSource;
|
||||
resolver: SettingsResolverSource;
|
||||
source: IEditableSettingsSource;
|
||||
accessor?: string[];
|
||||
hideGroupsSettingsLimit?: number;
|
||||
displayRestore?: boolean;
|
||||
}
|
||||
|
||||
export const Settings = observer<ISettingsProps>(function Settings({ source, accessor }) {
|
||||
export const Settings = observer<ISettingsProps>(function Settings({ resolver, source, accessor, hideGroupsSettingsLimit = 0, displayRestore }) {
|
||||
const translate = useTranslate();
|
||||
const settingsId = useId();
|
||||
const settings = useSettings(accessor);
|
||||
@@ -78,13 +81,15 @@ export const Settings = observer<ISettingsProps>(function Settings({ source, acc
|
||||
groupSelectExecutor.execute(id);
|
||||
}
|
||||
|
||||
const isGroupsHidden = getComputed(() => [...settings.settings.values()].flat().length <= hideGroupsSettingsLimit);
|
||||
|
||||
return (
|
||||
<Container gap overflow noWrap>
|
||||
<Group className={s(classes, { settingsGroups: true })} vertical box keepSize overflow>
|
||||
<Group className={s(classes, { settingsGroups: true })} hidden={isGroupsHidden} vertical box keepSize overflow>
|
||||
<SettingsGroups treeData={treeData} onClick={handleClick} />
|
||||
</Group>
|
||||
<Container className={s(classes, { settingsContainer: true })} overflow vertical gap noWrap>
|
||||
<Container gap keepSize>
|
||||
<Container hidden={isGroupsHidden} gap keepSize>
|
||||
<Filter
|
||||
state={treeFilter}
|
||||
name="filter"
|
||||
@@ -97,9 +102,12 @@ export const Settings = observer<ISettingsProps>(function Settings({ source, acc
|
||||
settingsId={settingsId}
|
||||
treeData={treeData}
|
||||
treeFilter={treeFilter}
|
||||
resolver={resolver}
|
||||
source={source}
|
||||
settings={settings.settings}
|
||||
groupsHidden={isGroupsHidden}
|
||||
groupSelectExecutor={groupSelectExecutor}
|
||||
displayRestore={displayRestore}
|
||||
/>
|
||||
</Container>
|
||||
</Container>
|
||||
|
||||
@@ -8,7 +8,12 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { getComputed, Group, GroupTitle, useExecutor, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import type { ISettingDescription, ISettingsSource, SettingsGroup as SettingsGroupType } from '@cloudbeaver/core-settings';
|
||||
import type {
|
||||
IEditableSettingsSource,
|
||||
ISettingDescription,
|
||||
SettingsGroup as SettingsGroupType,
|
||||
SettingsResolverSource,
|
||||
} from '@cloudbeaver/core-settings';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
import type { ITreeFilter } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
@@ -22,13 +27,26 @@ import { getSettingGroupId } from './getSettingGroupId.js';
|
||||
interface Props {
|
||||
settingsId: string;
|
||||
group: SettingsGroupType;
|
||||
source: ISettingsSource;
|
||||
resolver: SettingsResolverSource;
|
||||
source: IEditableSettingsSource;
|
||||
settings: Map<SettingsGroupType, ISettingDescription<any>[]>;
|
||||
treeFilter: ITreeFilter;
|
||||
groupsHidden?: boolean;
|
||||
displayRestore?: boolean;
|
||||
groupSelectExecutor: ISyncExecutor<string>;
|
||||
}
|
||||
|
||||
export const SettingsGroup = observer<Props>(function SettingsGroup({ settingsId, group, source, settings, treeFilter, groupSelectExecutor }) {
|
||||
export const SettingsGroup = observer<Props>(function SettingsGroup({
|
||||
settingsId,
|
||||
group,
|
||||
resolver,
|
||||
source,
|
||||
settings,
|
||||
treeFilter,
|
||||
groupsHidden,
|
||||
displayRestore,
|
||||
groupSelectExecutor,
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const translate = useTranslate();
|
||||
const groupSettings = getComputed(() => settings.get(group)?.filter(settingsFilter(translate, treeFilter.filter)) || [], isArraysEqual);
|
||||
@@ -53,12 +71,12 @@ export const SettingsGroup = observer<Props>(function SettingsGroup({ settingsId
|
||||
});
|
||||
|
||||
return (
|
||||
<Group ref={ref} id={getSettingGroupId(settingsId, group.id)} hidden={hidden} vertical gap compact>
|
||||
<GroupTitle sticky>
|
||||
<Group ref={ref} id={getSettingGroupId(settingsId, group.id)} hidden={hidden} dense={groupsHidden} vertical gap compact>
|
||||
<GroupTitle hidden={groupsHidden} sticky>
|
||||
<SettingsGroupTitle group={group} />
|
||||
</GroupTitle>
|
||||
{groupSettings.map((setting, i) => (
|
||||
<Setting key={i} source={source} setting={setting} />
|
||||
{groupSettings.map(setting => (
|
||||
<Setting key={setting.key} resolver={resolver} source={source} setting={setting} displayRestore={displayRestore} />
|
||||
))}
|
||||
</Group>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,12 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Group, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { type ISettingDescription, type ISettingsSource, type SettingsGroup as SettingsGroupType } from '@cloudbeaver/core-settings';
|
||||
import {
|
||||
SettingsResolverSource,
|
||||
type IEditableSettingsSource,
|
||||
type ISettingDescription,
|
||||
type SettingsGroup as SettingsGroupType,
|
||||
} from '@cloudbeaver/core-settings';
|
||||
import type { ITreeData, ITreeFilter } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { getGroupsFromTree } from './getGroupsFromTree.js';
|
||||
@@ -21,9 +26,12 @@ interface Props {
|
||||
settingsId: string;
|
||||
treeData: ITreeData;
|
||||
treeFilter: ITreeFilter;
|
||||
source: ISettingsSource;
|
||||
source: IEditableSettingsSource;
|
||||
resolver: SettingsResolverSource;
|
||||
settings: Map<SettingsGroupType, ISettingDescription<any>[]>;
|
||||
groupSelectExecutor: ISyncExecutor<string>;
|
||||
groupsHidden?: boolean;
|
||||
displayRestore?: boolean;
|
||||
onSettingsOpen?: (groupId: string) => void;
|
||||
}
|
||||
|
||||
@@ -31,8 +39,11 @@ export const SettingsList = observer<Props>(function SettingsList({
|
||||
settingsId,
|
||||
treeData,
|
||||
treeFilter,
|
||||
resolver,
|
||||
source,
|
||||
settings,
|
||||
groupsHidden,
|
||||
displayRestore,
|
||||
groupSelectExecutor,
|
||||
onSettingsOpen,
|
||||
}) {
|
||||
@@ -47,10 +58,13 @@ export const SettingsList = observer<Props>(function SettingsList({
|
||||
key={group.id}
|
||||
settingsId={settingsId}
|
||||
group={group}
|
||||
resolver={resolver}
|
||||
source={source}
|
||||
settings={settings}
|
||||
treeFilter={treeFilter}
|
||||
groupsHidden={groupsHidden}
|
||||
groupSelectExecutor={groupSelectExecutor}
|
||||
displayRestore={displayRestore}
|
||||
/>
|
||||
))}
|
||||
{groups.length === 0 && <TextPlaceholder>{translate('plugin_settings_panel_no_settings')}</TextPlaceholder>}
|
||||
|
||||
@@ -4,6 +4,6 @@ export default [
|
||||
['plugin_settings_panel_no_settings', 'No settings found'],
|
||||
['plugin_settings_panel_group_empty', 'No settings available'],
|
||||
['plugin_settings_panel_setting_reset', 'Reset'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default value'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default'],
|
||||
['plugin_settings_panel_setting_set_in_scope', 'The setting has been configured in the current scope.'],
|
||||
];
|
||||
|
||||
@@ -4,6 +4,6 @@ export default [
|
||||
['plugin_settings_panel_no_settings', 'No settings found'],
|
||||
['plugin_settings_panel_group_empty', 'No settings available'],
|
||||
['plugin_settings_panel_setting_reset', 'Reset'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default value'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default'],
|
||||
['plugin_settings_panel_setting_set_in_scope', 'The setting has been configured in the current scope.'],
|
||||
];
|
||||
|
||||
@@ -4,6 +4,6 @@ export default [
|
||||
['plugin_settings_panel_no_settings', 'No settings found'],
|
||||
['plugin_settings_panel_group_empty', 'No settings available'],
|
||||
['plugin_settings_panel_setting_reset', 'Reset'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default value'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default'],
|
||||
['plugin_settings_panel_setting_set_in_scope', 'The setting has been configured in the current scope.'],
|
||||
];
|
||||
|
||||
@@ -4,6 +4,6 @@ export default [
|
||||
['plugin_settings_panel_no_settings', 'No settings found'],
|
||||
['plugin_settings_panel_group_empty', 'No settings available'],
|
||||
['plugin_settings_panel_setting_reset', 'Reset'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default value'],
|
||||
['plugin_settings_panel_setting_reset_tooltip', 'Reset to default'],
|
||||
['plugin_settings_panel_setting_set_in_scope', 'The setting has been configured in the current scope.'],
|
||||
];
|
||||
|
||||
+2
-2
@@ -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.
|
||||
@@ -30,6 +30,6 @@ export class ResourceEditorSettingsService extends Dependency {
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => []);
|
||||
this.settingsManagerService.registerSettings<ResourceEditorSettings>(() => []);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,12 +31,13 @@ const QUERY_GUTTER_EFFECT = StateEffect.define<{ pos: number; on: boolean; type:
|
||||
map: (val, mapping) => ({ pos: mapping.mapPos(val.pos), on: val.on, type: val.type }),
|
||||
});
|
||||
|
||||
const gutterExtension = StateField.define<RangeSet<GutterMarker>>({
|
||||
const statusGutterState = StateField.define<RangeSet<GutterMarker>>({
|
||||
create() {
|
||||
return RangeSet.empty;
|
||||
},
|
||||
update(set, transaction) {
|
||||
set = set.map(transaction.changes);
|
||||
|
||||
for (const effect of transaction.effects) {
|
||||
if (effect.is(QUERY_GUTTER_EFFECT)) {
|
||||
if (effect.value.on) {
|
||||
@@ -68,10 +69,10 @@ export function setGutter(view: EditorView, pos: number, type: QueryGutterEffect
|
||||
}
|
||||
|
||||
export const QUERY_STATUS_GUTTER_EXTENSION = [
|
||||
gutterExtension,
|
||||
statusGutterState,
|
||||
gutter({
|
||||
class: 'query-status',
|
||||
markers: view => view.state.field(gutterExtension),
|
||||
markers: view => view.state.field(statusGutterState),
|
||||
initialSpacer: () => QUERY_STATUS_SIZE_MARKER,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -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.
|
||||
@@ -159,7 +159,7 @@ export class MenuBootstrap extends Bootstrap {
|
||||
}
|
||||
|
||||
if (
|
||||
!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.executable) &&
|
||||
!sqlEditorData.isExecutionAllowed &&
|
||||
[
|
||||
ACTION_SQL_EDITOR_EXECUTE,
|
||||
ACTION_SQL_EDITOR_EXECUTE_NEW,
|
||||
@@ -207,7 +207,7 @@ export class MenuBootstrap extends Bootstrap {
|
||||
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA],
|
||||
isBindingApplicable: (contexts, action) => {
|
||||
const sqlEditorData = contexts.get(DATA_CONTEXT_SQL_EDITOR_DATA);
|
||||
return action === ACTION_SQL_EDITOR_EXECUTE_SCRIPT && sqlEditorData?.dataSource?.hasFeature(ESqlDataSourceFeatures.executable) === true;
|
||||
return action === ACTION_SQL_EDITOR_EXECUTE_SCRIPT && sqlEditorData?.isExecutionAllowed === true;
|
||||
},
|
||||
handler: this.sqlEditorActionHandler.bind(this),
|
||||
});
|
||||
|
||||
@@ -73,16 +73,16 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
return this.executionContext?.projectId ?? null;
|
||||
}
|
||||
|
||||
get features(): ESqlDataSourceFeatures[] {
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.query, ESqlDataSourceFeatures.executable];
|
||||
}
|
||||
|
||||
readonly icon: string;
|
||||
readonly history: ISqlDataSourceHistory;
|
||||
readonly onUpdate: ISyncExecutor;
|
||||
readonly onSetScript: ISyncExecutor<ISetScriptData>;
|
||||
readonly onDatabaseModelUpdate: ISyncExecutor<IDatabaseDataModel<QueryDataSource>[]>;
|
||||
|
||||
protected get features(): ESqlDataSourceFeatures[] {
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.query, ESqlDataSourceFeatures.executable];
|
||||
}
|
||||
|
||||
protected outdated: boolean;
|
||||
protected editing: boolean;
|
||||
protected innerCursorState: ISqlEditorCursor;
|
||||
|
||||
@@ -46,8 +46,6 @@ export interface ISqlDataSource extends ILoadableState {
|
||||
readonly databaseModels: IDatabaseDataModel<QueryDataSource>[];
|
||||
readonly executionContext?: IConnectionExecutionContextInfo;
|
||||
|
||||
readonly features: ESqlDataSourceFeatures[];
|
||||
|
||||
readonly isAutoSaveEnabled: boolean;
|
||||
readonly isIncomingChanges: boolean;
|
||||
readonly isSaved: boolean;
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface ISQLEditorData {
|
||||
readonly isIncomingChanges: boolean;
|
||||
readonly value: string;
|
||||
readonly incomingValue?: string;
|
||||
readonly isExecutionAllowed: boolean;
|
||||
readonly dataSource: ISqlDataSource | undefined;
|
||||
readonly onExecute: ISyncExecutor<boolean>;
|
||||
readonly onSegmentExecute: ISyncExecutor<ISegmentExecutionData>;
|
||||
|
||||
@@ -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.
|
||||
@@ -28,7 +28,7 @@ export const SQLEditorActions = observer<Props>(function SQLEditorActions({ data
|
||||
const isActiveSegmentMode = getComputed(() => data.activeSegmentMode.activeSegmentMode);
|
||||
const disabled = getComputed(() => data.isScriptEmpty || data.isDisabled);
|
||||
const isQuery = data.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = data.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
const isExecutable = data.isExecutionAllowed;
|
||||
|
||||
return (
|
||||
<div className={s(styles, { container: true })}>
|
||||
|
||||
@@ -30,6 +30,7 @@ import { SqlQueryService } from '../SqlResultTabs/SqlQueryService.js';
|
||||
import { SqlResultTabsService } from '../SqlResultTabs/SqlResultTabsService.js';
|
||||
import type { ISQLEditorData } from './ISQLEditorData.js';
|
||||
import { SQLEditorModeContext } from './SQLEditorModeContext.js';
|
||||
import { SqlEditorSettingsService } from '../SqlEditorSettingsService.js';
|
||||
|
||||
interface ISQLEditorDataPrivate extends ISQLEditorData {
|
||||
readonly sqlDialectInfoService: SqlDialectInfoService;
|
||||
@@ -38,6 +39,7 @@ interface ISQLEditorDataPrivate extends ISQLEditorData {
|
||||
readonly sqlEditorService: SqlEditorService;
|
||||
readonly notificationService: NotificationService;
|
||||
readonly sqlExecutionPlanService: SqlExecutionPlanService;
|
||||
readonly sqlEditorSettingsService: SqlEditorSettingsService;
|
||||
readonly commonDialogService: CommonDialogService;
|
||||
readonly sqlResultTabsService: SqlResultTabsService;
|
||||
readonly dataSource: ISqlDataSource | undefined;
|
||||
@@ -68,6 +70,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
const sqlResultTabsService = useService(SqlResultTabsService);
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
const sqlDataSourceService = useService(SqlDataSourceService);
|
||||
const sqlEditorSettingsService = useService(SqlEditorSettingsService);
|
||||
|
||||
const data = useObservableRef<ISQLEditorDataPrivate>(
|
||||
() => ({
|
||||
@@ -134,6 +137,10 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
return this.dataSource?.incomingScript;
|
||||
},
|
||||
|
||||
get isExecutionAllowed(): boolean {
|
||||
return !!this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable) && this.sqlEditorSettingsService.scriptExecutionEnabled;
|
||||
},
|
||||
|
||||
onMode: new SyncExecutor(),
|
||||
onExecute: new SyncExecutor(),
|
||||
onSegmentExecute: new SyncExecutor(),
|
||||
@@ -230,9 +237,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
|
||||
async executeQuery(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isQuery || !isExecutable) {
|
||||
if (!isQuery || !this.isExecutionAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -263,9 +269,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
|
||||
async executeQueryNewTab(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isQuery || !isExecutable) {
|
||||
if (!isQuery || !this.isExecutionAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -281,9 +286,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
|
||||
async showExecutionPlan(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isQuery || !isExecutable || !this.dialect?.supportsExplainExecutionPlan) {
|
||||
if (!isQuery || !this.isExecutionAllowed || !this.dialect?.supportsExplainExecutionPlan) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -302,9 +306,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
async executeScript(): Promise<void> {
|
||||
const isExecutable = this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (!isExecutable || this.isDisabled || this.isScriptEmpty) {
|
||||
if (!this.isExecutionAllowed || this.isDisabled || this.isScriptEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -410,13 +412,14 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
|
||||
this.onExecute.execute(true);
|
||||
|
||||
const id = setTimeout(() => this.onSegmentExecute.execute({ segment, type: 'start' }), 250);
|
||||
try {
|
||||
const id = setTimeout(() => this.onSegmentExecute.execute({ segment, type: 'start' }), 250);
|
||||
const result = await action(segment);
|
||||
clearTimeout(id);
|
||||
this.onSegmentExecute.execute({ segment, type: 'end' });
|
||||
return result;
|
||||
} catch (exception: any) {
|
||||
clearTimeout(id);
|
||||
this.onSegmentExecute.execute({ segment, type: 'end' });
|
||||
this.onSegmentExecute.execute({ segment, type: 'error' });
|
||||
throw exception;
|
||||
@@ -499,6 +502,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
hintsLimitIsMet: observable.ref,
|
||||
readonlyState: observable,
|
||||
executingScript: observable,
|
||||
sqlEditorSettingsService: observable.ref,
|
||||
},
|
||||
{
|
||||
state,
|
||||
@@ -510,6 +514,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
sqlResultTabsService,
|
||||
notificationService,
|
||||
commonDialogService,
|
||||
sqlEditorSettingsService,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -6,9 +6,16 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import { FEATURE_GIT_ID, ServerConfigResource, ServerSettingsManagerService } from '@cloudbeaver/core-root';
|
||||
import {
|
||||
FEATURE_GIT_ID,
|
||||
HIGHEST_SETTINGS_LAYER,
|
||||
ServerConfigResource,
|
||||
ServerSettingsManagerService,
|
||||
SettingsTransformationService,
|
||||
} from '@cloudbeaver/core-root';
|
||||
import {
|
||||
createSettingsAliasResolver,
|
||||
createSettingsOverrideResolver,
|
||||
ESettingsValueType,
|
||||
type ISettingDescription,
|
||||
ROOT_SETTINGS_LAYER,
|
||||
@@ -45,6 +52,7 @@ const ASSISTANT_MODE_OPTIONS_LOCALIZED = [
|
||||
];
|
||||
|
||||
const defaultSettings = schema.object({
|
||||
'plugin.sql-editor.script.executionEnabled': schemaExtra.stringedBoolean().default(true),
|
||||
'plugin.sql-editor.maxFileSize': schema.coerce.number().default(10 * 1024), // kilobyte
|
||||
'plugin.sql-editor.disabled': schemaExtra.stringedBoolean().default(false),
|
||||
'plugin.sql-editor.autoSave': schemaExtra.stringedBoolean().default(true),
|
||||
@@ -63,20 +71,27 @@ const defaultSettings = schema.object({
|
||||
.pipe(schema.enum(TABLE_ALIAS_OPTIONS))
|
||||
.default('PLAIN'),
|
||||
'SQLEditor.ContentAssistant.proposals.long.name': schema.coerce.boolean().default(false),
|
||||
'SQLEditor.ContentAssistant.experimental.mode': schema.coerce.string().transform(value => {
|
||||
switch (value) {
|
||||
case 'DEFAULT':
|
||||
return 'DEFAULT';
|
||||
default:
|
||||
return 'NEW';
|
||||
}
|
||||
}).pipe(schema.enum(ASSISTANT_MODE_OPTIONS).default('NEW')),
|
||||
'SQLEditor.ContentAssistant.experimental.mode': schema.coerce
|
||||
.string()
|
||||
.transform(value => {
|
||||
switch (value) {
|
||||
case 'DEFAULT':
|
||||
return 'DEFAULT';
|
||||
default:
|
||||
return 'NEW';
|
||||
}
|
||||
})
|
||||
.pipe(schema.enum(ASSISTANT_MODE_OPTIONS).default('NEW')),
|
||||
});
|
||||
|
||||
export type SqlEditorSettings = schema.infer<typeof defaultSettings>;
|
||||
type SqlEditorSettingsSchema = typeof defaultSettings;
|
||||
export type SqlEditorSettings = schema.infer<SqlEditorSettingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class SqlEditorSettingsService extends Dependency {
|
||||
get scriptExecutionEnabled(): boolean {
|
||||
return this.settings.getValue('plugin.sql-editor.script.executionEnabled');
|
||||
}
|
||||
get maxFileSize(): number {
|
||||
return this.settings.getValue('plugin.sql-editor.maxFileSize');
|
||||
}
|
||||
@@ -103,6 +118,7 @@ export class SqlEditorSettingsService extends Dependency {
|
||||
private readonly settingsProviderService: SettingsProviderService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly settingsResolverService: SettingsResolverService,
|
||||
private readonly settingsTransformationService: SettingsTransformationService,
|
||||
private readonly serverSettingsManagerService: ServerSettingsManagerService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
) {
|
||||
@@ -111,18 +127,27 @@ export class SqlEditorSettingsService 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<SqlEditorSettingsSchema>(this.settingsResolverService, {
|
||||
'plugin.sql-editor.autoSave': 'core.app.sqlEditor.autoSave',
|
||||
'plugin.sql-editor.maxFileSize': 'core.app.sqlEditor.maxFileSize',
|
||||
'plugin.sql-editor.disabled': 'core.app.sqlEditor.disabled',
|
||||
}),
|
||||
);
|
||||
this.settingsResolverService.addResolver(
|
||||
HIGHEST_SETTINGS_LAYER,
|
||||
createSettingsOverrideResolver<SqlEditorSettingsSchema>(this.settingsResolverService, {
|
||||
'plugin.sql-editor.script.executionEnabled': {
|
||||
key: 'permission.sql.script.execution',
|
||||
filter: value => !value,
|
||||
},
|
||||
}),
|
||||
);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.serverSettingsManagerService.setGroupOverride('editors/sqlEditor', SQL_EDITOR_SETTINGS_GROUP);
|
||||
this.serverSettingsManagerService.setSettingTransformer(
|
||||
this.settingsTransformationService.setGroupOverride('editors/sqlEditor', SQL_EDITOR_SETTINGS_GROUP);
|
||||
this.settingsTransformationService.setSettingTransformer(
|
||||
'sql.proposals.insert.table.alias',
|
||||
setting =>
|
||||
({
|
||||
@@ -133,20 +158,19 @@ export class SqlEditorSettingsService extends Dependency {
|
||||
options: [...(setting.options?.filter(option => !TABLE_ALIAS_OPTIONS.includes(option.value as any)) || []), ...TABLE_ALIAS_SETTING_OPTIONS],
|
||||
}) as ISettingDescription<SqlEditorSettings>,
|
||||
);
|
||||
this.serverSettingsManagerService.setSettingTransformer(
|
||||
this.settingsTransformationService.setSettingTransformer(
|
||||
'SQLEditor.ContentAssistant.experimental.mode',
|
||||
setting => {
|
||||
return {
|
||||
setting =>
|
||||
({
|
||||
...setting,
|
||||
group: SQL_EDITOR_SETTINGS_GROUP,
|
||||
name: 'sql_editor_settings_content_assistant_experimental_mode_name',
|
||||
description: 'sql_editor_settings_content_assistant_experimental_mode_desc',
|
||||
options: ASSISTANT_MODE_OPTIONS_LOCALIZED,
|
||||
} as ISettingDescription<SqlEditorSettings>;
|
||||
},
|
||||
)
|
||||
}) as ISettingDescription<SqlEditorSettings>,
|
||||
);
|
||||
|
||||
this.settingsManagerService.registerSettings(this.settings, () => {
|
||||
this.settingsManagerService.registerSettings<typeof defaultSettings>(() => {
|
||||
const settings: ISettingDescription<SqlEditorSettings>[] = [
|
||||
{
|
||||
group: SQL_EDITOR_SETTINGS_GROUP,
|
||||
@@ -204,7 +228,7 @@ export class SqlEditorSettingsService extends Dependency {
|
||||
group: SQL_EDITOR_SETTINGS_GROUP,
|
||||
type: ESettingsValueType.Select,
|
||||
name: 'sql_editor_settings_content_assistant_experimental_mode_name',
|
||||
options: ASSISTANT_MODE_OPTIONS_LOCALIZED
|
||||
options: ASSISTANT_MODE_OPTIONS_LOCALIZED,
|
||||
});
|
||||
}
|
||||
return settings;
|
||||
|
||||
+4
-2
@@ -21,6 +21,7 @@ import { OUTPUT_LOGS_MENU } from './OUTPUT_LOGS_MENU.js';
|
||||
import { OUTPUT_LOGS_SETTINGS_MENU } from './OUTPUT_LOGS_SETTINGS_MENU.js';
|
||||
import { OutputLogsService } from './OutputLogsService.js';
|
||||
import { ACTION_SQL_EDITOR_CLEAR_OUTPUT_LOGS } from './ACTION_SQL_EDITOR_CLEAR_OUTPUT_LOGS.js';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_DATA } from '../../SqlEditor/DATA_CONTEXT_SQL_EDITOR_DATA.js';
|
||||
|
||||
@injectable()
|
||||
export class OutputMenuBootstrap extends Bootstrap {
|
||||
@@ -120,13 +121,14 @@ export class OutputMenuBootstrap extends Bootstrap {
|
||||
this.actionService.addHandler({
|
||||
id: 'output-logs-handler',
|
||||
actions: [ACTION_SHOW_OUTPUT_LOGS, ACTION_SQL_EDITOR_CLEAR_OUTPUT_LOGS],
|
||||
contexts: [DATA_CONTEXT_SQL_EDITOR_STATE],
|
||||
contexts: [DATA_CONTEXT_SQL_EDITOR_STATE, DATA_CONTEXT_SQL_EDITOR_DATA],
|
||||
isActionApplicable: (context): boolean => {
|
||||
const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE)!;
|
||||
const sqlEditorData = context.get(DATA_CONTEXT_SQL_EDITOR_DATA);
|
||||
|
||||
const sqlDataSource = this.sqlDataSourceService.get(state.editorId);
|
||||
const isQuery = sqlDataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = sqlDataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
const isExecutable = sqlEditorData?.isExecutionAllowed;
|
||||
|
||||
return !!isQuery && !!isExecutable;
|
||||
},
|
||||
|
||||
@@ -27,6 +27,8 @@ import type { IResultGroup, ISqlEditorTabState } from '../ISqlEditorTabState.js'
|
||||
import { QueryDataSource } from '../QueryDataSource.js';
|
||||
import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService.js';
|
||||
import { SqlQueryResultService } from './SqlQueryResultService.js';
|
||||
import { SqlEditorSettingsService } from '../SqlEditorSettingsService.js';
|
||||
import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures.js';
|
||||
import { Executor, type IExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
interface IQueryExecutionOptions {
|
||||
@@ -60,6 +62,7 @@ export class SqlQueryService {
|
||||
private readonly dataViewerService: DataViewerService,
|
||||
private readonly sqlDataSourceService: SqlDataSourceService,
|
||||
private readonly dataViewerSettingsService: DataViewerSettingsService,
|
||||
private readonly sqlEditorSettingsService: SqlEditorSettingsService,
|
||||
) {
|
||||
this.statisticsMap = new Map();
|
||||
this.onQueryExecution = new Executor();
|
||||
@@ -115,118 +118,43 @@ export class SqlQueryService {
|
||||
|
||||
async executeEditorQuery(editorState: ISqlEditorTabState, query: string, inNewTab: boolean): Promise<void> {
|
||||
await this.onQueryExecution.execute(editorState);
|
||||
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
||||
const contextInfo = dataSource?.executionContext;
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
|
||||
if (!contextInfo || !executionContext) {
|
||||
console.error('executeEditorQuery executionContext is not provided');
|
||||
return;
|
||||
}
|
||||
|
||||
let source: QueryDataSource;
|
||||
let model: IDatabaseDataModel<QueryDataSource>;
|
||||
let source: QueryDataSource | undefined;
|
||||
let tabGroup: IResultGroup | null = null;
|
||||
let isNewTabCreated = false;
|
||||
|
||||
const connectionKey = createConnectionParam(contextInfo.projectId, contextInfo.connectionId);
|
||||
|
||||
const connectionInfo = await this.connectionInfoResource.load(connectionKey);
|
||||
let tabGroup = this.sqlQueryResultService.getSelectedGroup(editorState);
|
||||
|
||||
if (inNewTab || !tabGroup) {
|
||||
source = new QueryDataSource(this.serviceProvider, this.graphQLService, this.asyncTaskInfoService);
|
||||
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
|
||||
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
|
||||
tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query);
|
||||
this.switchTabToActiveRequest(editorState, tabGroup, model);
|
||||
|
||||
isNewTabCreated = true;
|
||||
} else {
|
||||
model = this.tableViewerStorageService.get(tabGroup.modelId)!;
|
||||
source = model.source as QueryDataSource;
|
||||
tabGroup.query = query;
|
||||
}
|
||||
|
||||
const editable = this.dataViewerService.isDataEditable(connectionInfo);
|
||||
const isOutputLogsTabOpened = !!editorState.outputLogsTab;
|
||||
|
||||
model
|
||||
.setAccess(editable ? DatabaseDataAccessMode.Default : DatabaseDataAccessMode.Readonly)
|
||||
.source.setExecutionContext(executionContext)
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats)
|
||||
.setKeepExecutionContextOnDispose(true)
|
||||
.setOptions({
|
||||
query: query,
|
||||
connectionKey,
|
||||
constraints: [],
|
||||
whereFilter: '',
|
||||
readLogs: isOutputLogsTabOpened,
|
||||
});
|
||||
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, true);
|
||||
|
||||
try {
|
||||
await model.setCountGain(this.dataViewerSettingsService.getDefaultRowsCount()).setSlice(0).request();
|
||||
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
||||
if (!this.sqlEditorSettingsService.scriptExecutionEnabled || !dataSource?.hasFeature(ESqlDataSourceFeatures.executable)) {
|
||||
throw new Error('Script execution is not allowed');
|
||||
}
|
||||
const contextInfo = dataSource?.executionContext;
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
|
||||
model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.source.getResults().length));
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId);
|
||||
} catch (exception: any) {
|
||||
// remove group if execution was cancelled
|
||||
if (source.currentTask?.cancelled && isNewTabCreated) {
|
||||
this.sqlQueryResultService.removeGroup(editorState, tabGroup.groupId);
|
||||
const message = 'Query execution has been canceled';
|
||||
this.notificationService.logException(exception, 'Query execution Error', message);
|
||||
if (!contextInfo || !executionContext) {
|
||||
console.error('executeEditorQuery executionContext is not provided');
|
||||
return;
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
async executeQueries(editorState: ISqlEditorTabState, queries: string[], options?: IQueryExecutionOptions): Promise<void> {
|
||||
await this.onQueryExecution.execute(editorState);
|
||||
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
||||
const contextInfo = dataSource?.executionContext;
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
let model: IDatabaseDataModel<QueryDataSource>;
|
||||
|
||||
if (!contextInfo || !executionContext) {
|
||||
console.error('executeEditorQuery executionContext is not provided');
|
||||
return;
|
||||
}
|
||||
const connectionKey = createConnectionParam(contextInfo.projectId, contextInfo.connectionId);
|
||||
|
||||
const groupNameOrder = this.sqlQueryResultService.getGroupNameOrder(editorState);
|
||||
const connectionKey = createConnectionParam(contextInfo.projectId, contextInfo.connectionId);
|
||||
const connectionInfo = await this.connectionInfoResource.load(connectionKey);
|
||||
tabGroup = this.sqlQueryResultService.getSelectedGroup(editorState);
|
||||
|
||||
const connectionInfo = await this.connectionInfoResource.load(connectionKey);
|
||||
|
||||
const statisticsTab = this.sqlQueryResultService.createStatisticsTab(editorState);
|
||||
|
||||
this.statisticsMap.set(statisticsTab.tabId, {
|
||||
queries: queries.length,
|
||||
executedQueries: 0,
|
||||
executeTime: 0,
|
||||
updatedRows: 0,
|
||||
modelId: null,
|
||||
});
|
||||
|
||||
editorState.currentTabId = statisticsTab.tabId;
|
||||
|
||||
const statistics = this.getStatistics(statisticsTab.tabId)!;
|
||||
|
||||
let source: QueryDataSource | undefined;
|
||||
let model: IDatabaseDataModel<QueryDataSource> | undefined;
|
||||
let resultCount = 0;
|
||||
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const query = queries[i]!;
|
||||
|
||||
options?.onQueryExecutionStart?.(query, i);
|
||||
|
||||
if (!model || !source) {
|
||||
if (inNewTab || !tabGroup) {
|
||||
source = new QueryDataSource(this.serviceProvider, this.graphQLService, this.asyncTaskInfoService);
|
||||
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
|
||||
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
|
||||
tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query);
|
||||
this.switchTabToActiveRequest(editorState, tabGroup, model);
|
||||
|
||||
isNewTabCreated = true;
|
||||
} else {
|
||||
model = this.tableViewerStorageService.get(tabGroup.modelId)!;
|
||||
source = model.source as QueryDataSource;
|
||||
tabGroup.query = query;
|
||||
}
|
||||
statistics.modelId = model.id;
|
||||
|
||||
const editable = this.dataViewerService.isDataEditable(connectionInfo);
|
||||
const isOutputLogsTabOpened = !!editorState.outputLogsTab;
|
||||
@@ -237,58 +165,148 @@ export class SqlQueryService {
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats)
|
||||
.setKeepExecutionContextOnDispose(true)
|
||||
.setOptions({
|
||||
query,
|
||||
query: query,
|
||||
connectionKey,
|
||||
constraints: [],
|
||||
whereFilter: '',
|
||||
readLogs: isOutputLogsTabOpened,
|
||||
});
|
||||
|
||||
try {
|
||||
await model.setCountGain(this.dataViewerSettingsService.getDefaultRowsCount()).setSlice(0).request();
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, true);
|
||||
|
||||
const cancelled = model.source.cancelled;
|
||||
await model.setCountGain(this.dataViewerSettingsService.getDefaultRowsCount()).setSlice(0).request();
|
||||
|
||||
statistics.executedQueries++;
|
||||
statistics.executeTime += source.requestInfo.requestDuration;
|
||||
model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.source.getResults().length));
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId);
|
||||
} catch (exception: any) {
|
||||
// remove group if execution was cancelled
|
||||
if (source?.currentTask?.cancelled && isNewTabCreated) {
|
||||
this.sqlQueryResultService.removeGroup(editorState, tabGroup!.groupId);
|
||||
const message = 'Query execution has been canceled';
|
||||
this.notificationService.logException(exception, 'Query execution Error', message);
|
||||
return;
|
||||
} else if (!source) {
|
||||
this.notificationService.logException(exception, 'Query execution Error', exception.message);
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
for (const result of source.results) {
|
||||
statistics.updatedRows += result.updateRowCount;
|
||||
async executeQueries(editorState: ISqlEditorTabState, queries: string[], options?: IQueryExecutionOptions): Promise<void> {
|
||||
await this.onQueryExecution.execute(editorState);
|
||||
try {
|
||||
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
||||
if (!this.sqlEditorSettingsService.scriptExecutionEnabled || !dataSource?.hasFeature(ESqlDataSourceFeatures.executable)) {
|
||||
throw new Error('Script execution is not allowed');
|
||||
}
|
||||
const contextInfo = dataSource?.executionContext;
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
|
||||
if (!contextInfo || !executionContext) {
|
||||
console.error('executeEditorQuery executionContext is not provided');
|
||||
return;
|
||||
}
|
||||
|
||||
const groupNameOrder = this.sqlQueryResultService.getGroupNameOrder(editorState);
|
||||
const connectionKey = createConnectionParam(contextInfo.projectId, contextInfo.connectionId);
|
||||
|
||||
const connectionInfo = await this.connectionInfoResource.load(connectionKey);
|
||||
|
||||
const statisticsTab = this.sqlQueryResultService.createStatisticsTab(editorState);
|
||||
|
||||
this.statisticsMap.set(statisticsTab.tabId, {
|
||||
queries: queries.length,
|
||||
executedQueries: 0,
|
||||
executeTime: 0,
|
||||
updatedRows: 0,
|
||||
modelId: null,
|
||||
});
|
||||
|
||||
editorState.currentTabId = statisticsTab.tabId;
|
||||
|
||||
const statistics = this.getStatistics(statisticsTab.tabId)!;
|
||||
|
||||
let source: QueryDataSource | undefined;
|
||||
let model: IDatabaseDataModel<QueryDataSource> | undefined;
|
||||
let resultCount = 0;
|
||||
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const query = queries[i]!;
|
||||
|
||||
options?.onQueryExecutionStart?.(query, i);
|
||||
|
||||
if (!model || !source) {
|
||||
source = new QueryDataSource(this.serviceProvider, this.graphQLService, this.asyncTaskInfoService);
|
||||
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
|
||||
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
|
||||
}
|
||||
statistics.modelId = model.id;
|
||||
|
||||
if (source.results.some(result => result.data)) {
|
||||
resultCount = resultCount + 1;
|
||||
const editable = this.dataViewerService.isDataEditable(connectionInfo);
|
||||
const isOutputLogsTabOpened = !!editorState.outputLogsTab;
|
||||
|
||||
const tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query, groupNameOrder);
|
||||
model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.source.getResults().length, resultCount));
|
||||
this.switchTabToActiveRequest(editorState, tabGroup, model);
|
||||
model
|
||||
.setAccess(editable ? DatabaseDataAccessMode.Default : DatabaseDataAccessMode.Readonly)
|
||||
.source.setExecutionContext(executionContext)
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats)
|
||||
.setKeepExecutionContextOnDispose(true)
|
||||
.setOptions({
|
||||
query,
|
||||
connectionKey,
|
||||
constraints: [],
|
||||
whereFilter: '',
|
||||
readLogs: isOutputLogsTabOpened,
|
||||
});
|
||||
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, false, resultCount);
|
||||
try {
|
||||
await model.setCountGain(this.dataViewerSettingsService.getDefaultRowsCount()).setSlice(0).request();
|
||||
|
||||
model = source = undefined;
|
||||
}
|
||||
const cancelled = model.source.cancelled;
|
||||
|
||||
options?.onQueryExecuted?.(query, i, true);
|
||||
statistics.executedQueries++;
|
||||
statistics.executeTime += source.requestInfo.requestDuration;
|
||||
|
||||
if (cancelled) {
|
||||
for (const result of source.results) {
|
||||
statistics.updatedRows += result.updateRowCount;
|
||||
}
|
||||
|
||||
if (source.results.some(result => result.data)) {
|
||||
resultCount = resultCount + 1;
|
||||
|
||||
const tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query, groupNameOrder);
|
||||
model.setName(this.sqlQueryResultService.getTabNameForOrder(tabGroup.nameOrder, 0, model.source.getResults().length, resultCount));
|
||||
this.switchTabToActiveRequest(editorState, tabGroup, model);
|
||||
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, false, resultCount);
|
||||
|
||||
model = source = undefined;
|
||||
}
|
||||
|
||||
options?.onQueryExecuted?.(query, i, true);
|
||||
|
||||
if (cancelled) {
|
||||
break;
|
||||
}
|
||||
} catch (exception: any) {
|
||||
if (model) {
|
||||
const tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query);
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, true, resultCount);
|
||||
|
||||
model = source = undefined;
|
||||
}
|
||||
options?.onQueryExecuted?.(query, i, false);
|
||||
break;
|
||||
}
|
||||
} catch (exception: any) {
|
||||
if (model) {
|
||||
const tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query);
|
||||
this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId, true, resultCount);
|
||||
|
||||
model = source = undefined;
|
||||
}
|
||||
options?.onQueryExecuted?.(query, i, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
statistics.modelId = null;
|
||||
statistics.modelId = null;
|
||||
|
||||
if (model) {
|
||||
this.tableViewerStorageService.remove(model.id);
|
||||
if (model) {
|
||||
this.tableViewerStorageService.remove(model.id);
|
||||
}
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'Query execution Error', exception.message);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export class ToolsPanelSettingsService extends Dependency {
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
this.settingsManagerService.registerSettings<Settings>(() => [
|
||||
{
|
||||
key: 'plugin.tools-panel.disabled',
|
||||
access: {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@cloudbeaver/core-events": "workspace:*",
|
||||
"@cloudbeaver/core-executor": "workspace:*",
|
||||
"@cloudbeaver/core-localization": "workspace:*",
|
||||
"@cloudbeaver/core-settings": "workspace:*",
|
||||
"@cloudbeaver/core-settings-user": "workspace:*",
|
||||
"@cloudbeaver/core-view": "workspace:*",
|
||||
"@cloudbeaver/plugin-settings-menu": "workspace:*",
|
||||
|
||||
@@ -12,11 +12,13 @@ import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { UserSettingsService } from '@cloudbeaver/core-settings-user';
|
||||
import { Settings } from '@cloudbeaver/plugin-settings-panel';
|
||||
import { SettingsResolverService } from '@cloudbeaver/core-settings';
|
||||
|
||||
const clientScope = ['client'];
|
||||
|
||||
export const UserProfileSettings = observer(function UserProfileSettings() {
|
||||
const translate = useTranslate();
|
||||
const settingsResolverService = useService(SettingsResolverService);
|
||||
const userSettingsService = useService(UserSettingsService);
|
||||
const notificationService = useService(NotificationService);
|
||||
|
||||
@@ -70,7 +72,7 @@ export const UserProfileSettings = observer(function UserProfileSettings() {
|
||||
</ToolsAction>
|
||||
</ToolsPanel>
|
||||
</Group>
|
||||
<Settings source={userSettingsService} accessor={clientScope} />
|
||||
<Settings resolver={settingsResolverService} source={userSettingsService} accessor={clientScope} />
|
||||
</ColoredContainer>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
{
|
||||
"path": "../core-localization"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings-user"
|
||||
},
|
||||
|
||||
@@ -4036,6 +4036,7 @@ __metadata:
|
||||
"@cloudbeaver/core-events": "workspace:*"
|
||||
"@cloudbeaver/core-executor": "workspace:*"
|
||||
"@cloudbeaver/core-localization": "workspace:*"
|
||||
"@cloudbeaver/core-settings": "workspace:*"
|
||||
"@cloudbeaver/core-settings-user": "workspace:*"
|
||||
"@cloudbeaver/core-view": "workspace:*"
|
||||
"@cloudbeaver/plugin-settings-menu": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user