mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-4619 user scope settings (#2471)
* CB-4619 feat: update gql schema * CB-4808 bulk update user parameters * CB-4808 param type fix * CB-4619 feat: updateUserPreferences API * CB-4619 refactor: settings api * CB-4619 refactor: move file * CB-4619 refactor: settings api * CB-4619 fix: pr review * CB-4619 refactor: remove web-based product settings * CB-4619 fix: after pr review * CB-4619 refactor: move product conf to cloudbeaver conf * CB-4619 fix: hide settings bar and log exception with changing session language * CB-4619 fix: revert after merge * CB-4808 new settings api * CB-4808 update product.conf format * CB-4808 update product.conf format * CB-4619 feat: support server provided settings * CB-4619 feat: prettify settings * CB-4619 chore: map sql editor settings group * CB-4808 add use short/long object name settings to CloudBeaver * CB-4808 update default product.conf * CB-4808 mode short/long settings to SQLModelPreferences * CB-4808 review fix * CB-4619 refactor: unit tests * CB-4619 fix: prevent websocket opening spam * CB-4619 fix: dependencies * CB-4619 fix: remove extra theme loading * CB-4619 fix: rename settings alias resolver file * CB-4619 fix: after review * CB-4619 fix conflicts * CB-4619 rename server property * CB-4619 remove shortname property * CB-4619 fix legacy product conf reading * CB-4619 merge root and runtime configurations * CB-4619 chore: remove log viewer setting * CB-4619 integer fields with env variables fix * CB-4619 fix: property naming * CB-4619 fix: parseJSONFlat null values * CB-4619 fix: settings loading * CB-4619 fix: save only actual settings --------- Co-authored-by: Aleksandr Skoblikov <aleksandr.skoblikov@dbeaver.com> Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Aleksandr Skoblikov
kseniaguzeeva
Evgenia Bezborodova
parent
b54dd5380d
commit
eaa26dfe52
Vendored
+6
-9
@@ -7,16 +7,13 @@
|
||||
"name": "Chrome",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}/..",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/../cloudbeaver/packages/**/dist/**/*.{js,jsx}"
|
||||
],
|
||||
"smartStep": true,
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///*": "${workspaceFolder}/../*"
|
||||
},
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/**",
|
||||
"${workspaceFolder}/webapp/**/node_modules/**/*.js",
|
||||
"${workspaceFolder}/webapp/**/dist/**/*.js"
|
||||
]
|
||||
"disableNetworkCache": true,
|
||||
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Product configuration. Customized web application behavior
|
||||
// It is in JSONC format
|
||||
{
|
||||
// Global properties
|
||||
core: {
|
||||
// User defaults
|
||||
user: {
|
||||
defaultTheme: "light",
|
||||
defaultLanguage: "en"
|
||||
},
|
||||
app: {
|
||||
// Log viewer config
|
||||
logViewer: {
|
||||
refreshTimeout: 3000,
|
||||
logBatchSize: 1000,
|
||||
maxLogRecords: 2000,
|
||||
maxFailedRequests: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
// Notifications config
|
||||
core_events: {
|
||||
notificationsPool: 20
|
||||
},
|
||||
plugin_data_spreadsheet_new: {
|
||||
hidden: false
|
||||
},
|
||||
plugin_data_export: {
|
||||
disabled: false
|
||||
},
|
||||
sql.proposals.insert.table.alias: true
|
||||
}
|
||||
@@ -11,7 +11,21 @@
|
||||
rootURI: "/",
|
||||
serviceURI: "/api/",
|
||||
|
||||
productConfiguration: "conf/product.conf",
|
||||
productSettings: {
|
||||
# Global properties
|
||||
core.theming.theme: 'light',
|
||||
core.localization.localization: 'en',
|
||||
plugin.sql-editor.autoSave: true,
|
||||
plugin.sql-editor.disabled: false,
|
||||
# max size of the file that can be uploaded to the editor (in kilobytes)
|
||||
plugin.sql-editor.maxFileSize: 10240,
|
||||
plugin.log-viewer.disabled: false,
|
||||
plugin.log-viewer.logBatchSize: 1000,
|
||||
plugin.log-viewer.maxFailedRequests: 3,
|
||||
plugin.log-viewer.maxLogRecords: 2000,
|
||||
plugin.log-viewer.refreshTimeout: 3000,
|
||||
sql.proposals.insert.table.alias: PLAIN
|
||||
},
|
||||
|
||||
expireSessionAfterPeriod: 1800000,
|
||||
|
||||
|
||||
@@ -9,7 +9,21 @@
|
||||
rootURI: "/",
|
||||
serviceURI: "/api/",
|
||||
|
||||
productConfiguration: "conf/product.conf",
|
||||
productSettings: {
|
||||
# Global properties
|
||||
core.theming.theme: 'light',
|
||||
core.localization.localization: 'en',
|
||||
plugin.sql-editor.autoSave: true,
|
||||
plugin.sql-editor.disabled: false,
|
||||
# max size of the file that can be uploaded to the editor (in kilobytes)
|
||||
plugin.sql-editor.maxFileSize: 10240,
|
||||
plugin.log-viewer.disabled: false,
|
||||
plugin.log-viewer.logBatchSize: 1000,
|
||||
plugin.log-viewer.maxFailedRequests: 3,
|
||||
plugin.log-viewer.maxLogRecords: 2000,
|
||||
plugin.log-viewer.refreshTimeout: 3000,
|
||||
sql.proposals.insert.table.alias: PLAIN
|
||||
},
|
||||
|
||||
expireSessionAfterPeriod: 1800000,
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.*;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
|
||||
@@ -27,6 +28,7 @@ import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.meta.PropertyLength;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
|
||||
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
|
||||
import org.jkiss.dbeaver.runtime.properties.ObjectPropertyDescriptor;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
@@ -242,4 +244,12 @@ public class WebPropertyInfo {
|
||||
return CommonUtils.toString(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public List<String> getScopes() {
|
||||
if (property instanceof ProductSettingDescriptor productSettingDescriptor) {
|
||||
return productSettingDescriptor.getScopes();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ type ObjectPropertyInfo {
|
||||
supportedConfigurationTypes: [ String! ]
|
||||
# Required parameters
|
||||
required: Boolean! @since(version: "23.3.1")
|
||||
# Parameter scopes
|
||||
scopes:[String!]
|
||||
}
|
||||
|
||||
enum ObjectPropertyLength {
|
||||
@@ -162,6 +164,16 @@ type ServerConfig {
|
||||
passwordPolicyConfiguration: PasswordPolicyConfig! @since(version: "23.3.3")
|
||||
}
|
||||
|
||||
type ProductSettingsGroup @since(version: "24.0.1") {
|
||||
id: ID!
|
||||
displayName: String!
|
||||
}
|
||||
|
||||
type ProductSettings @since(version: "24.0.1") {
|
||||
groups: [ProductSettingsGroup!]!
|
||||
settings: [ObjectPropertyInfo!]! # each property is associated with a group by category
|
||||
}
|
||||
|
||||
type SessionInfo {
|
||||
createTime: String!
|
||||
lastAccessTime: String!
|
||||
@@ -530,6 +542,8 @@ extend type Query {
|
||||
# Returns server config
|
||||
serverConfig: ServerConfig!
|
||||
|
||||
productSettings: ProductSettings! @since(version: "24.0.1")
|
||||
|
||||
# Returns session state ( initialize if not )
|
||||
sessionState: SessionInfo!
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.impl.PropertyGroupDescriptor;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WebProductSettings {
|
||||
@NotNull
|
||||
private final WebSession webSession;
|
||||
@NotNull
|
||||
private final List<PropertyGroupDescriptor<ProductSettingDescriptor>> groups;
|
||||
|
||||
public WebProductSettings(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull List<PropertyGroupDescriptor<ProductSettingDescriptor>> groups
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public List<WebSettingsGroupInfo> getGroups() {
|
||||
return groups.stream()
|
||||
.map(WebSettingsGroupInfo::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public List<WebPropertyInfo> getSettings() {
|
||||
return groups.stream()
|
||||
.flatMap(group -> group.getSettings().stream())
|
||||
.map(setting -> new WebPropertyInfo(webSession, setting))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.impl.PropertyGroupDescriptor;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
|
||||
|
||||
public class WebSettingsGroupInfo {
|
||||
@NotNull
|
||||
private final PropertyGroupDescriptor<ProductSettingDescriptor> groupDescriptor;
|
||||
|
||||
public WebSettingsGroupInfo(@NotNull PropertyGroupDescriptor<ProductSettingDescriptor> groupDescriptor) {
|
||||
this.groupDescriptor = groupDescriptor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getId() {
|
||||
return groupDescriptor.getFullId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getDisplayName() {
|
||||
return groupDescriptor.getDisplayName();
|
||||
}
|
||||
}
|
||||
@@ -728,6 +728,7 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
|
||||
|
||||
public void saveProductConfiguration(SMCredentialsProvider credentialsProvider, Map<String, Object> productConfiguration) throws DBException {
|
||||
getServerConfigurationController().saveProductConfiguration(productConfiguration);
|
||||
flushConfiguration(credentialsProvider);
|
||||
sendConfigChangedEvent(credentialsProvider);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,10 @@ public class CBConstants {
|
||||
public static final String PARAM_SERVICES_URI = "serviceURI";
|
||||
public static final String PARAM_DRIVERS_LOCATION = "driversLocation";
|
||||
public static final String PARAM_WORKSPACE_LOCATION = "workspaceLocation";
|
||||
//legacy path to a product.conf file
|
||||
@Deprecated
|
||||
public static final String PARAM_PRODUCT_CONFIGURATION = "productConfiguration";
|
||||
public static final String PARAM_PRODUCT_SETTINGS = "productSettings";
|
||||
public static final String PARAM_EXTERNAL_PROPERTIES = "externalProperties";
|
||||
public static final String PARAM_STATIC_CONTENT = "staticContent";
|
||||
public static final String PARAM_RESOURCE_QUOTAS = "resourceQuotas";
|
||||
|
||||
@@ -20,11 +20,13 @@ import com.google.gson.annotations.SerializedName;
|
||||
import io.cloudbeaver.auth.CBAuthConstants;
|
||||
import io.cloudbeaver.model.app.WebServerConfiguration;
|
||||
import io.cloudbeaver.service.security.db.WebDatabaseConfig;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CBServerConfig implements WebServerConfiguration {
|
||||
@@ -46,6 +48,7 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
private long maxSessionIdleTime = CBAuthConstants.MAX_SESSION_IDLE_TIME;
|
||||
private boolean develMode = false;
|
||||
private boolean enableSecurityManager = false;
|
||||
private final Map<String, Object> productSettings = new HashMap<>();
|
||||
|
||||
@SerializedName("database")
|
||||
private WebDatabaseConfig databaseConfiguration = new WebDatabaseConfig();
|
||||
@@ -184,4 +187,8 @@ public class CBServerConfig implements WebServerConfiguration {
|
||||
return enableSecurityManager;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Object> getProductSettings() {
|
||||
return productSettings;
|
||||
}
|
||||
}
|
||||
|
||||
+62
-40
@@ -50,12 +50,12 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class CBServerConfigurationController<T extends CBServerConfig> extends BaseServerConfigurationController<T> {
|
||||
public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
extends BaseServerConfigurationController<T> {
|
||||
|
||||
private static final Log log = Log.getLog(CBServerConfigurationController.class);
|
||||
|
||||
// Configurations
|
||||
protected final Map<String, Object> productConfiguration = new HashMap<>();
|
||||
protected final SMControllerConfiguration securityManagerConfiguration = new SMControllerConfiguration();
|
||||
private final T serverConfiguration;
|
||||
private final CBAppConfig appConfiguration = new CBAppConfig();
|
||||
@@ -113,7 +113,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
// Merge new config with old one
|
||||
mergeOldConfiguration(prevConfig);
|
||||
|
||||
patchConfigurationWithProperties(productConfiguration);
|
||||
patchConfigurationWithProperties(getServerConfiguration().getProductSettings());
|
||||
}
|
||||
|
||||
protected void parseConfiguration(Map<String, Object> configProps) throws DBException {
|
||||
@@ -123,8 +123,11 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
patchConfigurationWithProperties(configProps); // patch again because properties can be changed
|
||||
|
||||
Gson gson = getGson();
|
||||
Map<String, Object> currentConfigurationAsMap = gson.fromJson(gson.toJson(getServerConfiguration()),
|
||||
JSONUtils.MAP_TYPE_TOKEN);
|
||||
serverConfig = WebAppUtils.mergeConfigurations(currentConfigurationAsMap, serverConfig);
|
||||
gson.fromJson(
|
||||
gson.toJsonTree(serverConfig),
|
||||
gson.toJson(serverConfig),
|
||||
getServerConfiguration().getClass()
|
||||
);
|
||||
|
||||
@@ -132,16 +135,14 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
|
||||
//SM config
|
||||
gson.fromJson(
|
||||
gson.toJsonTree(JSONUtils.getObject(serverConfig, CBConstants.PARAM_SM_CONFIGURATION)),
|
||||
gson.toJson(JSONUtils.getObject(serverConfig, CBConstants.PARAM_SM_CONFIGURATION)),
|
||||
SMControllerConfiguration.class
|
||||
);
|
||||
// App config
|
||||
Map<String, Object> appConfig = JSONUtils.getObject(configProps, "app");
|
||||
validateConfiguration(appConfig);
|
||||
gson.fromJson(gson.toJsonTree(appConfig), CBAppConfig.class);
|
||||
|
||||
gson.fromJson(gson.toJson(appConfig), CBAppConfig.class);
|
||||
readProductConfiguration(serverConfig, gson);
|
||||
|
||||
}
|
||||
|
||||
public T parseServerConfiguration() {
|
||||
@@ -212,26 +213,31 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
|
||||
protected void readProductConfiguration(Map<String, Object> serverConfig, Gson gson)
|
||||
throws DBException {
|
||||
String productConfigPath = WebAppUtils.getRelativePath(
|
||||
JSONUtils.getString(
|
||||
serverConfig,
|
||||
CBConstants.PARAM_PRODUCT_CONFIGURATION,
|
||||
CBConstants.DEFAULT_PRODUCT_CONFIGURATION
|
||||
),
|
||||
CBApplication.getInstance().getHomeDirectory().toString()
|
||||
);
|
||||
|
||||
if (!CommonUtils.isEmpty(productConfigPath)) {
|
||||
File productConfigFile = new File(productConfigPath);
|
||||
if (!productConfigFile.exists()) {
|
||||
log.error("Product configuration file not found (" + productConfigFile.getAbsolutePath() + "'");
|
||||
} else {
|
||||
log.debug("Load product configuration from '" + productConfigFile.getAbsolutePath() + "'");
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(productConfigFile),
|
||||
StandardCharsets.UTF_8)) {
|
||||
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Error reading product configuration", e);
|
||||
// legacy configuration with path to product.conf file
|
||||
if (!serverConfig.containsKey(CBConstants.PARAM_PRODUCT_SETTINGS)
|
||||
&& serverConfig.get(CBConstants.PARAM_PRODUCT_CONFIGURATION) instanceof String
|
||||
) {
|
||||
String productConfigPath = WebAppUtils.getRelativePath(
|
||||
JSONUtils.getString(
|
||||
serverConfig,
|
||||
CBConstants.PARAM_PRODUCT_CONFIGURATION,
|
||||
CBConstants.DEFAULT_PRODUCT_CONFIGURATION
|
||||
),
|
||||
CBApplication.getInstance().getHomeDirectory().toString()
|
||||
);
|
||||
if (!CommonUtils.isEmpty(productConfigPath)) {
|
||||
File productConfigFile = new File(productConfigPath);
|
||||
if (!productConfigFile.exists()) {
|
||||
log.error("Product configuration file not found (" + productConfigFile.getAbsolutePath() + "'");
|
||||
} else {
|
||||
log.debug("Load product configuration from '" + productConfigFile.getAbsolutePath() + "'");
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(productConfigFile),
|
||||
StandardCharsets.UTF_8)) {
|
||||
serverConfiguration.getProductSettings()
|
||||
.putAll(WebAppUtils.flattenMap(JSONUtils.parseMap(gson, reader)));
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Error reading product configuration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,10 +247,12 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
if (rtConfig.exists()) {
|
||||
log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'");
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) {
|
||||
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
|
||||
Map<String, Object> flattenConfig = WebAppUtils.flattenMap(this.productConfiguration);
|
||||
this.productConfiguration.clear();
|
||||
this.productConfiguration.putAll(flattenConfig);
|
||||
var runtimeProductSettings = JSONUtils.parseMap(gson, reader);
|
||||
var productSettings = serverConfiguration.getProductSettings();
|
||||
productSettings.putAll(runtimeProductSettings);
|
||||
Map<String, Object> flattenConfig = WebAppUtils.flattenMap(productSettings);
|
||||
productSettings.clear();
|
||||
productSettings.putAll(flattenConfig);
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Error reading product runtime configuration", e);
|
||||
}
|
||||
@@ -332,12 +340,14 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
writeRuntimeConfig(getRuntimeAppConfigPath(), configurationProperties);
|
||||
}
|
||||
|
||||
private void writeRuntimeConfig(Path runtimeConfigPath, Map<String, Object> configurationProperties) throws DBException {
|
||||
private void writeRuntimeConfig(Path runtimeConfigPath, Map<String, Object> configurationProperties)
|
||||
throws DBException {
|
||||
if (Files.exists(runtimeConfigPath)) {
|
||||
ContentUtils.makeFileBackup(runtimeConfigPath);
|
||||
}
|
||||
|
||||
try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigPath.toFile()), StandardCharsets.UTF_8)) {
|
||||
try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigPath.toFile()),
|
||||
StandardCharsets.UTF_8)) {
|
||||
Gson gson = new GsonBuilder()
|
||||
.setLenient()
|
||||
.setPrettyPrinting()
|
||||
@@ -358,7 +368,10 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
Map<String, Object> rootConfig = new LinkedHashMap<>();
|
||||
{
|
||||
var originServerConfig = BaseWebApplication.getServerConfigProps(this.originalConfigurationProperties); // get server properties from original configuration file
|
||||
var serverConfigProperties = collectServerConfigProperties(newServerName, newServerURL, sessionExpireTime, originServerConfig);
|
||||
var serverConfigProperties = collectServerConfigProperties(newServerName,
|
||||
newServerURL,
|
||||
sessionExpireTime,
|
||||
originServerConfig);
|
||||
rootConfig.put("server", serverConfigProperties);
|
||||
}
|
||||
{
|
||||
@@ -494,6 +507,15 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
CBConstants.PARAM_SESSION_EXPIRE_PERIOD,
|
||||
sessionExpireTime);
|
||||
}
|
||||
var productConfigProperties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
Map<String, Object> oldProductRuntimeConfig = JSONUtils.getObject(originServerConfig,
|
||||
CBConstants.PARAM_PRODUCT_SETTINGS);
|
||||
if (!CommonUtils.isEmpty(getServerConfiguration().getProductSettings())) {
|
||||
for (Map.Entry<String, Object> mp : getServerConfiguration().getProductSettings().entrySet()) {
|
||||
copyConfigValue(oldProductRuntimeConfig, productConfigProperties, mp.getKey(), mp.getValue());
|
||||
}
|
||||
serverConfigProperties.put(CBConstants.PARAM_PRODUCT_SETTINGS, productConfigProperties);
|
||||
}
|
||||
return serverConfigProperties;
|
||||
}
|
||||
|
||||
@@ -557,10 +579,10 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
}
|
||||
|
||||
public void saveProductConfiguration(Map<String, Object> productConfiguration) throws DBException {
|
||||
Map<String, Object> mergedConfig = WebAppUtils.mergeConfigurations(this.productConfiguration, productConfiguration);
|
||||
writeRuntimeConfig(getRuntimeProductConfigFilePath(), mergedConfig);
|
||||
this.productConfiguration.clear();
|
||||
this.productConfiguration.putAll(WebAppUtils.flattenMap(mergedConfig));
|
||||
Map<String, Object> productSettings = getServerConfiguration().getProductSettings();
|
||||
Map<String, Object> mergedConfig = WebAppUtils.mergeConfigurations(productSettings, productConfiguration);
|
||||
productSettings.clear();
|
||||
productSettings.putAll(WebAppUtils.flattenMap(mergedConfig));
|
||||
}
|
||||
|
||||
public T getServerConfiguration() {
|
||||
@@ -572,7 +594,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
}
|
||||
|
||||
public Map<String, Object> getProductConfiguration() {
|
||||
return productConfiguration;
|
||||
return getServerConfiguration().getProductSettings();
|
||||
}
|
||||
|
||||
public SMControllerConfiguration getSecurityManagerConfiguration() {
|
||||
|
||||
-46
@@ -20,18 +20,11 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import io.cloudbeaver.service.security.db.WebDatabaseConfig;
|
||||
import io.cloudbeaver.utils.WebAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -46,45 +39,6 @@ public class CBServerConfigurationControllerEmbedded<T extends CBServerConfig> e
|
||||
super(serverConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readProductConfiguration(Map<String, Object> serverConfig, Gson gson)
|
||||
throws DBException {
|
||||
String productConfigPath = WebAppUtils.getRelativePath(
|
||||
JSONUtils.getString(
|
||||
serverConfig,
|
||||
CBConstants.PARAM_PRODUCT_CONFIGURATION,
|
||||
CBConstants.DEFAULT_PRODUCT_CONFIGURATION
|
||||
),
|
||||
WebAppUtils.getWebApplication().getHomeDirectory().toString()
|
||||
);
|
||||
|
||||
if (!CommonUtils.isEmpty(productConfigPath)) {
|
||||
File productConfigFile = new File(productConfigPath);
|
||||
if (!productConfigFile.exists()) {
|
||||
log.error("Product configuration file not found (" + productConfigFile.getAbsolutePath() + "'");
|
||||
} else {
|
||||
log.debug("Load product configuration from '" + productConfigFile.getAbsolutePath() + "'");
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(productConfigFile),
|
||||
StandardCharsets.UTF_8)) {
|
||||
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Error reading product configuration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add product config from runtime
|
||||
File rtConfig = getRuntimeProductConfigFilePath().toFile();
|
||||
if (rtConfig.exists()) {
|
||||
log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'");
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) {
|
||||
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Error reading product runtime configuration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Map<String, Object> collectServerConfigProperties(
|
||||
|
||||
+3
@@ -41,6 +41,9 @@ public interface DBWServiceCore extends DBWService {
|
||||
@WebAction(authRequired = false)
|
||||
WebServerConfig getServerConfig() throws DBWebException;
|
||||
|
||||
@WebAction(authRequired = false)
|
||||
WebProductSettings getProductSettings(@NotNull WebSession webSession);
|
||||
|
||||
@WebAction
|
||||
List<WebDatabaseDriverInfo> getDriverList(@NotNull WebSession webSession, String driverId) throws DBWebException;
|
||||
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
|
||||
WebSessionManager sessionManager = platform.getSessionManager();
|
||||
model.getQueryType()
|
||||
.dataFetcher("serverConfig", env -> getService(env).getServerConfig())
|
||||
.dataFetcher("productSettings", env -> getService(env).getProductSettings(getWebSession(env)))
|
||||
|
||||
.dataFetcher("driverList", env -> getService(env).getDriverList(getWebSession(env), env.getArgument("id")))
|
||||
.dataFetcher("authModels", env -> getService(env).getAuthModels(getWebSession(env)))
|
||||
|
||||
+62
-29
@@ -62,6 +62,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.ProductSettingsRegistry;
|
||||
import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -177,8 +178,7 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
|
||||
for (DBPDataSourceContainer ds : registry.getDataSources()) {
|
||||
if (ds.isTemplate() &&
|
||||
CBPlatform.getInstance().getApplicableDrivers().contains(ds.getDriver()))
|
||||
{
|
||||
CBPlatform.getInstance().getApplicableDrivers().contains(ds.getDriver())) {
|
||||
result.add(new WebConnectionInfo(webSession, ds));
|
||||
}
|
||||
}
|
||||
@@ -206,13 +206,13 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
@NotNull DBPProject project
|
||||
) {
|
||||
return project.getDataSourceRegistry().getAllFolders().stream()
|
||||
.map(f -> new WebConnectionFolderInfo(webSession, f)).collect(Collectors.toList());
|
||||
.map(f -> new WebConnectionFolderInfo(webSession, f)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSessionPermissions(@NotNull WebSession webSession) throws DBWebException {
|
||||
if (CBApplication.getInstance().isConfigurationMode()) {
|
||||
return new String[] {
|
||||
return new String[]{
|
||||
DBWConstants.PERMISSION_ADMIN
|
||||
};
|
||||
}
|
||||
@@ -224,8 +224,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable String defaultLocale,
|
||||
@NotNull HttpServletRequest servletRequest,
|
||||
@NotNull HttpServletResponse servletResponse) throws DBWebException
|
||||
{
|
||||
@NotNull HttpServletResponse servletResponse
|
||||
) throws DBWebException {
|
||||
for (WebSessionHandlerDescriptor hd : WebHandlerRegistry.getInstance().getSessionHandlers()) {
|
||||
try {
|
||||
hd.getInstance().handleSessionOpen(webSession, servletRequest, servletResponse);
|
||||
@@ -255,7 +255,11 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebServerMessage> readSessionLog(@NotNull WebSession webSession, Integer maxEntries, Boolean clearEntries) {
|
||||
public List<WebServerMessage> readSessionLog(
|
||||
@NotNull WebSession webSession,
|
||||
Integer maxEntries,
|
||||
Boolean clearEntries
|
||||
) {
|
||||
return webSession.readLog(maxEntries, clearEntries);
|
||||
}
|
||||
|
||||
@@ -297,7 +301,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException {
|
||||
public boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response)
|
||||
throws DBWebException {
|
||||
WebSession session = CBPlatform.getInstance().getSessionManager().getWebSession(request, response);
|
||||
if (session == null) {
|
||||
return false;
|
||||
@@ -372,10 +377,12 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
if (networkCredentials != null) {
|
||||
networkCredentials.forEach(c -> {
|
||||
if (CommonUtils.toBoolean(c.isSavePassword())) {
|
||||
DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration().getHandler(c.getId());
|
||||
DBWHandlerConfiguration handlerCfg = dataSourceContainer.getConnectionConfiguration()
|
||||
.getHandler(c.getId());
|
||||
if (handlerCfg != null &&
|
||||
// check username param only for ssh config
|
||||
!(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(handlerCfg.getType(), DBWHandlerType.TUNNEL))
|
||||
!(CommonUtils.isEmpty(c.getUserName()) && CommonUtils.equalObjects(handlerCfg.getType(),
|
||||
DBWHandlerType.TUNNEL))
|
||||
) {
|
||||
WebDataSourceUtils.updateHandlerCredentials(handlerCfg, c);
|
||||
handlerCfg.setSavePassword(true);
|
||||
@@ -398,14 +405,16 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
if (project.isUseSecretStorage()) {
|
||||
try {
|
||||
dataSourceContainer.persistSecrets(
|
||||
DBSSecretController.getProjectSecretController(dataSourceContainer.getProject())
|
||||
DBSSecretController.getProjectSecretController(dataSourceContainer.getProject())
|
||||
);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Failed to save credentials", e);
|
||||
}
|
||||
}
|
||||
|
||||
WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo, dataSourceContainer, dataSourceContainer.getConnectionConfiguration());
|
||||
WebDataSourceUtils.saveCredentialsInDataSource(connectionInfo,
|
||||
dataSourceContainer,
|
||||
dataSourceContainer.getConnectionConfiguration());
|
||||
saveConfig[0] = true;
|
||||
}
|
||||
if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) {
|
||||
@@ -440,7 +449,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
if (connectionConfig.isTemplate()) {
|
||||
connectionConfig.setSaveCredentials(false);
|
||||
}
|
||||
DBPDataSourceContainer newDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig, sessionRegistry);
|
||||
DBPDataSourceContainer newDataSource = WebServiceUtils.createConnectionFromConfig(connectionConfig,
|
||||
sessionRegistry);
|
||||
if (CommonUtils.isEmpty(newDataSource.getName())) {
|
||||
newDataSource.setName(CommonUtils.notNull(connectionConfig.getName(), "NewConnection"));
|
||||
}
|
||||
@@ -456,7 +466,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
|
||||
WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, newDataSource);
|
||||
webSession.addConnection(connectionInfo);
|
||||
webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo(newDataSource));
|
||||
webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo(
|
||||
newDataSource));
|
||||
WebEventUtils.addDataSourceUpdatedEvent(
|
||||
webSession.getProjectById(projectId),
|
||||
webSession,
|
||||
@@ -471,7 +482,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
public WebConnectionInfo updateConnection(
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable String projectId,
|
||||
@NotNull WebConnectionConfig config) throws DBWebException {
|
||||
@NotNull WebConnectionConfig config
|
||||
) throws DBWebException {
|
||||
// Do not check for custom connection option. Already created connections can be edited.
|
||||
// Also template connections can be edited
|
||||
// if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) {
|
||||
@@ -494,7 +506,9 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
|
||||
dataSource.setFolder(config.getFolder() != null ? sessionRegistry.getFolder(config.getFolder()) : null);
|
||||
|
||||
WebServiceUtils.setConnectionConfiguration(dataSource.getDriver(), dataSource.getConnectionConfiguration(), config);
|
||||
WebServiceUtils.setConnectionConfiguration(dataSource.getDriver(),
|
||||
dataSource.getConnectionConfiguration(),
|
||||
config);
|
||||
|
||||
// we should check that the config has changed but not check for password changes
|
||||
dataSource.setSharedCredentials(config.isSharedCredentials());
|
||||
@@ -543,7 +557,10 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
private WSDataSourceProperty getDatasourceEventProperty(DataSourceDescriptor oldDataSource, DBPDataSourceContainer dataSource) {
|
||||
private WSDataSourceProperty getDatasourceEventProperty(
|
||||
DataSourceDescriptor oldDataSource,
|
||||
DBPDataSourceContainer dataSource
|
||||
) {
|
||||
if (!oldDataSource.equalConfiguration((DataSourceDescriptor) dataSource)) {
|
||||
return WSDataSourceProperty.CONFIGURATION;
|
||||
}
|
||||
@@ -583,8 +600,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String projectId,
|
||||
@NotNull String templateId,
|
||||
@Nullable String connectionName) throws DBWebException
|
||||
{
|
||||
@Nullable String connectionName
|
||||
) throws DBWebException {
|
||||
DBPDataSourceRegistry templateRegistry = getProjectById(webSession, projectId).getDataSourceRegistry();
|
||||
DBPDataSourceContainer dataSourceTemplate = templateRegistry.getDataSource(templateId);
|
||||
if (dataSourceTemplate == null) {
|
||||
@@ -618,7 +635,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable String projectId,
|
||||
@NotNull String nodePath,
|
||||
@NotNull WebConnectionConfig config) throws DBWebException {
|
||||
@NotNull WebConnectionConfig config
|
||||
) throws DBWebException {
|
||||
try {
|
||||
DBNModel navigatorModel = webSession.getNavigatorModel();
|
||||
DBPDataSourceRegistry dataSourceRegistry = getProjectById(webSession, projectId).getDataSourceRegistry();
|
||||
@@ -630,7 +648,7 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
if (!(srcNode instanceof DBNDataSource)) {
|
||||
throw new DBException("Node '" + nodePath + "' is not a datasource node");
|
||||
}
|
||||
DBPDataSourceContainer dataSourceTemplate = ((DBNDataSource)srcNode).getDataSourceContainer();
|
||||
DBPDataSourceContainer dataSourceTemplate = ((DBNDataSource) srcNode).getDataSourceContainer();
|
||||
|
||||
DBPDataSourceContainer newDataSource = dataSourceRegistry.createDataSource(dataSourceTemplate);
|
||||
|
||||
@@ -718,7 +736,9 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
testDataSource = (DataSourceDescriptor) WebServiceUtils.createConnectionFromConfig(connectionConfig,
|
||||
sessionRegistry);
|
||||
}
|
||||
webSession.provideAuthParameters(webSession.getProgressMonitor(), testDataSource, testDataSource.getConnectionConfiguration());
|
||||
webSession.provideAuthParameters(webSession.getProgressMonitor(),
|
||||
testDataSource,
|
||||
testDataSource.getConnectionConfiguration());
|
||||
testDataSource.setSavePassword(true); // We need for test to avoid password callback
|
||||
if (DataSourceDescriptor.class.isAssignableFrom(testDataSource.getClass())) {
|
||||
((DataSourceDescriptor) testDataSource).setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN));
|
||||
@@ -742,21 +762,26 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException {
|
||||
public WebNetworkEndpointInfo testNetworkHandler(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebNetworkHandlerConfigInput nhConfig
|
||||
) throws DBWebException {
|
||||
DBRProgressMonitor monitor = webSession.getProgressMonitor();
|
||||
monitor.beginTask("Instantiate SSH tunnel", 2);
|
||||
|
||||
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance().getDescriptor(nhConfig.getId());
|
||||
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance()
|
||||
.getDescriptor(nhConfig.getId());
|
||||
if (handlerDescriptor == null) {
|
||||
throw new DBWebException("Network handler '" + nhConfig.getId() + "' not found");
|
||||
}
|
||||
try {
|
||||
DBWNetworkHandler handler = handlerDescriptor.createHandler(DBWNetworkHandler.class);
|
||||
if (handler instanceof DBWTunnel) {
|
||||
DBWTunnel tunnel = (DBWTunnel)handler;
|
||||
DBWTunnel tunnel = (DBWTunnel) handler;
|
||||
DBPConnectionConfiguration connectionConfig = new DBPConnectionConfiguration();
|
||||
connectionConfig.setHostName(DBConstants.HOST_LOCALHOST);
|
||||
connectionConfig.setHostPort(CommonUtils.toString(nhConfig.getProperties().get(DBWHandlerConfiguration.PROP_PORT)));
|
||||
connectionConfig.setHostPort(CommonUtils.toString(nhConfig.getProperties()
|
||||
.get(DBWHandlerConfiguration.PROP_PORT)));
|
||||
try {
|
||||
monitor.subTask("Initialize tunnel");
|
||||
|
||||
@@ -860,7 +885,9 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
WebProjectImpl project = getProjectById(session, projectId);
|
||||
DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry();
|
||||
DBPDataSourceFolder newFolder = WebConnectionFolderUtils.createFolder(parentNode, folderName, sessionRegistry);
|
||||
DBPDataSourceFolder newFolder = WebConnectionFolderUtils.createFolder(parentNode,
|
||||
folderName,
|
||||
sessionRegistry);
|
||||
WebConnectionFolderInfo folderInfo = new WebConnectionFolderInfo(session, newFolder);
|
||||
WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId);
|
||||
WebEventUtils.addNavigatorNodeUpdatedEvent(
|
||||
@@ -936,7 +963,7 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
WebSession webSession, @Nullable String projectId, String id, DBNBrowseSettings settings
|
||||
) throws DBWebException {
|
||||
WebConnectionInfo connectionInfo = webSession.getWebConnectionInfo(projectId, id);
|
||||
DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor)connectionInfo.getDataSourceContainer());
|
||||
DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor) connectionInfo.getDataSourceContainer());
|
||||
dataSourceDescriptor.setNavigatorSettings(settings);
|
||||
dataSourceDescriptor.persistConfiguration();
|
||||
WebEventUtils.addDataSourceUpdatedEvent(
|
||||
@@ -949,7 +976,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAsyncTaskInfo getAsyncTaskInfo(WebSession webSession, String taskId, Boolean removeOnFinish) throws DBWebException {
|
||||
public WebAsyncTaskInfo getAsyncTaskInfo(WebSession webSession, String taskId, Boolean removeOnFinish)
|
||||
throws DBWebException {
|
||||
return webSession.asyncTaskStatus(taskId, CommonUtils.toBoolean(removeOnFinish));
|
||||
}
|
||||
|
||||
@@ -958,6 +986,11 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
return webSession.asyncTaskCancel(taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebProductSettings getProductSettings(@NotNull WebSession webSession) {
|
||||
return new WebProductSettings(webSession, ProductSettingsRegistry.getInstance().getSettings());
|
||||
}
|
||||
|
||||
private WebProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException {
|
||||
WebProjectImpl project = webSession.getProjectById(projectId);
|
||||
if (project == null) {
|
||||
|
||||
+5
-1
@@ -16,11 +16,13 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.*;
|
||||
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
|
||||
import org.jkiss.dbeaver.model.sql.SQLModelPreferences;
|
||||
import org.jkiss.dbeaver.model.sql.SQLSyntaxManager;
|
||||
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionContext;
|
||||
import org.jkiss.dbeaver.model.sql.completion.SQLCompletionProposalBase;
|
||||
@@ -66,7 +68,9 @@ public class WebSQLCompletionContext implements SQLCompletionContext {
|
||||
|
||||
@Override
|
||||
public boolean isUseFQNames() {
|
||||
return false;
|
||||
return CBPlatform.getInstance()
|
||||
.getPreferenceStore()
|
||||
.getBoolean(SQLModelPreferences.SQL_EDITOR_PROPOSAL_ALWAYS_FQ);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -163,9 +163,12 @@ extend type Query {
|
||||
|
||||
}
|
||||
|
||||
|
||||
extend type Mutation {
|
||||
|
||||
# Set user config parameter. If parameter value is null then removes the parameter
|
||||
setUserConfigurationParameter(name: String!, value: Object): Boolean!
|
||||
# Updates user preferences
|
||||
setUserPreferences(preferences: Object!): UserInfo! @since(version: "24.0.1")
|
||||
|
||||
}
|
||||
|
||||
+6
@@ -69,4 +69,10 @@ public interface DBWServiceAuth extends DBWService {
|
||||
@NotNull String name,
|
||||
@Nullable String value) throws DBWebException;
|
||||
|
||||
@WebAction()
|
||||
WebUserInfo setUserConfigurationParameters(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull Map<String, Object> parameters
|
||||
) throws DBWebException;
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -73,6 +73,9 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
|
||||
env -> getService(env).setUserConfigurationParameter(getWebSession(env),
|
||||
env.getArgument("name"),
|
||||
env.getArgument("value")))
|
||||
.dataFetcher("setUserPreferences",
|
||||
env -> getService(env).setUserConfigurationParameters(getWebSession(env),
|
||||
env.getArgument("preferences")))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
+27
-2
@@ -18,7 +18,6 @@ package io.cloudbeaver.service.auth.impl;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.auth.SMAuthProviderFederated;
|
||||
import io.cloudbeaver.auth.SMSignOutLinkProvider;
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
@@ -249,14 +248,40 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String name,
|
||||
@Nullable String value
|
||||
) throws DBWebException {
|
||||
if (webSession.getUser() == null) {
|
||||
throw new DBWebException("Preferences cannot be changed for anonymous user");
|
||||
}
|
||||
return setPreference(webSession, name, value);
|
||||
}
|
||||
|
||||
private static boolean setPreference(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String name,
|
||||
@Nullable Object value
|
||||
) throws DBWebException {
|
||||
webSession.addInfoMessage("Set user parameter - " + name);
|
||||
try {
|
||||
webSession.getSecurityController().setCurrentUserParameter(name, value);
|
||||
String serializedValue = value == null ? null : value.toString();
|
||||
webSession.getSecurityController().setCurrentUserParameter(name, serializedValue);
|
||||
return true;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error setting user parameter", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebUserInfo setUserConfigurationParameters(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull Map<String, Object> parameters
|
||||
) throws DBWebException {
|
||||
if (webSession.getUser() == null) {
|
||||
throw new DBWebException("Preferences cannot be changed for anonymous user");
|
||||
}
|
||||
for (Map.Entry<String, Object> parameter : parameters.entrySet()) {
|
||||
setPreference(webSession, parameter.getKey(), parameter.getValue());
|
||||
}
|
||||
return new WebUserInfo(webSession, webSession.getUser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
rootURI: "/",
|
||||
serviceURI: "/api/",
|
||||
|
||||
productConfiguration: "workspace/conf/product.conf",
|
||||
productSettings: {},
|
||||
|
||||
expireSessionAfterPeriod: 1800000,
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Product configuration. Customized web application behavior
|
||||
// It is in JSONC format
|
||||
{
|
||||
// Global properties
|
||||
core: {
|
||||
// User defaults
|
||||
user: {
|
||||
defaultTheme: "light",
|
||||
defaultLanguage: "en"
|
||||
},
|
||||
app: {
|
||||
// Log viewer config
|
||||
logViewer: {
|
||||
refreshTimeout: 3000,
|
||||
logBatchSize: 1000,
|
||||
maxLogRecords: 2000,
|
||||
maxFailedRequests: 3
|
||||
},
|
||||
sqlEditor: {
|
||||
# max size of the file that can be uploaded to the editor (in kilobytes)
|
||||
maxFileSize: 100
|
||||
}
|
||||
}
|
||||
},
|
||||
// Notifications config
|
||||
core_events: {
|
||||
notificationsPool: 20
|
||||
},
|
||||
plugin_data_spreadsheet_new: {
|
||||
hidden: false
|
||||
},
|
||||
plugin_data_export: {
|
||||
disabled: false
|
||||
},
|
||||
// ERD config
|
||||
plugin_erd_viewer: {
|
||||
maxColumnsToDisplay: 7500
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-storage": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { EAdminPermission, PermissionsService, ServerConfigResource, SessionPermissionsResource } from '@cloudbeaver/core-root';
|
||||
import { RouterState, ScreenService } from '@cloudbeaver/core-routing';
|
||||
import { SettingsService } from '@cloudbeaver/core-settings';
|
||||
import { StorageService } from '@cloudbeaver/core-storage';
|
||||
import { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { AdministrationItemService } from '../AdministrationItem/AdministrationItemService';
|
||||
@@ -69,7 +69,7 @@ export class AdministrationScreenService {
|
||||
private readonly permissionsService: PermissionsService,
|
||||
private readonly screenService: ScreenService,
|
||||
private readonly administrationItemService: AdministrationItemService,
|
||||
private readonly settingsService: SettingsService,
|
||||
private readonly storageService: StorageService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly notificationService: NotificationService,
|
||||
) {
|
||||
@@ -84,8 +84,8 @@ export class AdministrationScreenService {
|
||||
activeScreen: computed,
|
||||
});
|
||||
|
||||
this.settingsService.registerSettings(ADMINISTRATION_ITEMS_STATE, this.itemState, () => new Map());
|
||||
this.settingsService.registerSettings(ADMINISTRATION_INFO, this.info, getDefaultAdministrationScreenInfo);
|
||||
this.storageService.registerSettings(ADMINISTRATION_ITEMS_STATE, this.itemState, () => new Map());
|
||||
this.storageService.registerSettings(ADMINISTRATION_INFO, this.info, getDefaultAdministrationScreenInfo);
|
||||
this.permissionsResource.onDataUpdate.addPostHandler(() => {
|
||||
this.checkPermissions(this.screenService.routerService.state);
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
"path": "../core-storage/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
|
||||
@@ -22,29 +22,23 @@
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-resource": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-storage": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@cloudbeaver/core-browser": "~0.1.0",
|
||||
"@cloudbeaver/core-client-activity": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/tests-runner": "~0.1.0",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-settings';
|
||||
|
||||
export const AUTH_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_authentication_auth_settings_group');
|
||||
|
||||
@@ -37,10 +37,6 @@ export class AuthInfoService {
|
||||
);
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
await this.userInfoResource.logout();
|
||||
}
|
||||
|
||||
private federatedAuthentication(
|
||||
providerId: string,
|
||||
options: ILoginOptions,
|
||||
|
||||
@@ -7,53 +7,38 @@
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { coreBrowserManifest } from '@cloudbeaver/core-browser';
|
||||
import { coreClientActivityManifest } from '@cloudbeaver/core-client-activity';
|
||||
import { coreEventsManifest } from '@cloudbeaver/core-events';
|
||||
import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
|
||||
import { corePluginManifest } from '@cloudbeaver/core-plugin';
|
||||
import { coreProductManifest } from '@cloudbeaver/core-product';
|
||||
import { coreRootManifest, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { createGQLEndpoint } from '@cloudbeaver/core-root/dist/__custom_mocks__/createGQLEndpoint';
|
||||
import '@cloudbeaver/core-root/dist/__custom_mocks__/expectWebsocketClosedMessage';
|
||||
import { mockAppInit } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockAppInit';
|
||||
import { mockGraphQL } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockGraphQL';
|
||||
import { mockServerConfig } from '@cloudbeaver/core-root/dist/__custom_mocks__/resolvers/mockServerConfig';
|
||||
import { coreRoutingManifest } from '@cloudbeaver/core-routing';
|
||||
import { coreSDKManifest } from '@cloudbeaver/core-sdk';
|
||||
import { coreSettingsManifest } from '@cloudbeaver/core-settings';
|
||||
import { coreThemingManifest } from '@cloudbeaver/core-theming';
|
||||
import { expectNoDeprecatedSettingMessage } from '@cloudbeaver/core-settings/dist/__custom_mocks__/expectDeprecatedSettingMessage';
|
||||
import { createApp } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import { mockAuthentication } from './__custom_mocks__/mockAuthentication';
|
||||
import { AuthSettings, AuthSettingsService } from './AuthSettingsService';
|
||||
import { AuthSettingsService } from './AuthSettingsService';
|
||||
import { coreAuthenticationManifest } from './manifest';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
|
||||
const app = createApp(
|
||||
coreAuthenticationManifest,
|
||||
coreEventsManifest,
|
||||
corePluginManifest,
|
||||
coreProductManifest,
|
||||
coreRootManifest,
|
||||
coreSDKManifest,
|
||||
coreSettingsManifest,
|
||||
coreBrowserManifest,
|
||||
coreRoutingManifest,
|
||||
coreThemingManifest,
|
||||
coreLocalizationManifest,
|
||||
coreClientActivityManifest,
|
||||
);
|
||||
|
||||
const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
|
||||
|
||||
beforeAll(() => app.init());
|
||||
|
||||
const equalConfig = {
|
||||
core: {
|
||||
authentication: {
|
||||
disableAnonymousAccess: true,
|
||||
} as AuthSettings,
|
||||
},
|
||||
'core.authentication.disableAnonymousAccess': true,
|
||||
};
|
||||
|
||||
test('Read settings', async () => {
|
||||
@@ -64,5 +49,6 @@ test('Read settings', async () => {
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('disableAnonymousAccess')).toBe(true);
|
||||
expect(settings.disableAnonymousAccess).toBe(true);
|
||||
expectNoDeprecatedSettingMessage();
|
||||
});
|
||||
|
||||
@@ -6,21 +6,27 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { PluginManagerService, PluginSettings, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
import { SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
|
||||
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
|
||||
|
||||
const settingsSchema = schema.object({
|
||||
disableAnonymousAccess: schema.coerce.boolean().default(false),
|
||||
'core.authentication.disableAnonymousAccess': schemaExtra.stringedBoolean().default(false),
|
||||
});
|
||||
|
||||
export type AuthSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class AuthSettingsService {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
get disableAnonymousAccess(): boolean {
|
||||
return this.settings.getValue('core.authentication.disableAnonymousAccess');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService, private readonly settingsManagerService: SettingsManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('authentication', 'core', settingsSchema);
|
||||
constructor(
|
||||
private readonly settingsProviderService: SettingsProviderService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
) {
|
||||
this.settings = this.settingsProviderService.createSettings(settingsSchema);
|
||||
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
@@ -6,58 +6,15 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { SessionResource } from '@cloudbeaver/core-root';
|
||||
import { ThemeService } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { UserInfoResource } from './UserInfoResource';
|
||||
|
||||
const USER_APP_THEME = 'app.theme';
|
||||
const USER_APP_LANGUAGE = 'app.language';
|
||||
|
||||
@injectable()
|
||||
export class UserConfigurationBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly userInfoResource: UserInfoResource,
|
||||
private readonly themeService: ThemeService,
|
||||
private readonly localizationService: LocalizationService,
|
||||
private readonly sessionResource: SessionResource,
|
||||
) {
|
||||
constructor(private readonly userInfoResource: UserInfoResource) {
|
||||
super();
|
||||
this.userInfoResource.onDataUpdate.addHandler(() => {
|
||||
const theme = this.userInfoResource.getConfigurationParameter(USER_APP_THEME);
|
||||
|
||||
if (typeof theme === 'string') {
|
||||
this.themeService.setTheme(theme);
|
||||
}
|
||||
|
||||
const language = this.userInfoResource.getConfigurationParameter(USER_APP_LANGUAGE);
|
||||
|
||||
if (typeof language === 'string') {
|
||||
this.localizationService.setLocale(language);
|
||||
this.sessionResource.changeLanguage(language);
|
||||
}
|
||||
});
|
||||
|
||||
this.themeService.onChange.addHandler(async theme => {
|
||||
const currentTheme = this.userInfoResource.getConfigurationParameter(USER_APP_THEME);
|
||||
|
||||
if (currentTheme !== theme.id) {
|
||||
await this.userInfoResource.setConfigurationParameter(USER_APP_THEME, theme.id);
|
||||
}
|
||||
});
|
||||
|
||||
this.localizationService.onChange.addHandler(async locale => {
|
||||
const currentLocale = this.userInfoResource.getConfigurationParameter(USER_APP_LANGUAGE);
|
||||
|
||||
if (currentLocale !== locale) {
|
||||
await this.userInfoResource.setConfigurationParameter(USER_APP_LANGUAGE, locale);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
register(): void {}
|
||||
|
||||
async load(): Promise<void> {
|
||||
await this.userInfoResource.load();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SettingsService } from '@cloudbeaver/core-settings';
|
||||
import { StorageService } from '@cloudbeaver/core-storage';
|
||||
import { TempMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { UserInfoResource } from './UserInfoResource';
|
||||
@@ -18,7 +18,10 @@ export class UserDataService {
|
||||
private readonly userData: Map<string, Record<string, any>>;
|
||||
private readonly tempData: TempMap<string, Record<string, any>>;
|
||||
|
||||
constructor(private readonly userInfoResource: UserInfoResource, private readonly settingsService: SettingsService) {
|
||||
constructor(
|
||||
private readonly userInfoResource: UserInfoResource,
|
||||
private readonly storageService: StorageService,
|
||||
) {
|
||||
this.userData = new Map();
|
||||
|
||||
makeObservable<this, 'userData'>(this, {
|
||||
@@ -27,7 +30,7 @@ export class UserDataService {
|
||||
|
||||
this.tempData = new TempMap(this.userData);
|
||||
|
||||
this.settingsService.registerSettings('user_data', this.userData, () => new Map());
|
||||
this.storageService.registerSettings('user_data', this.userData, () => new Map());
|
||||
}
|
||||
|
||||
getUserData<T extends Record<any, any>>(key: string, defaultValue: () => T, validate?: (data: T) => boolean): T {
|
||||
|
||||
@@ -166,6 +166,20 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
|
||||
return result;
|
||||
}
|
||||
|
||||
async updatePreferences(preferences: Record<string, any>): Promise<UserInfo | null> {
|
||||
await this.performUpdate(undefined, [], async () => {
|
||||
const { user } = await this.graphQLService.sdk.updateUserPreferences({
|
||||
preferences,
|
||||
...this.getDefaultIncludes(),
|
||||
...this.getIncludesMap(),
|
||||
});
|
||||
|
||||
this.setData(user as UserInfo | null);
|
||||
});
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async setConfigurationParameter(key: string, value: any): Promise<UserInfo | null> {
|
||||
await this.load();
|
||||
|
||||
@@ -173,7 +187,7 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
|
||||
return this.data;
|
||||
}
|
||||
|
||||
this.performUpdate(undefined, [], async () => {
|
||||
await this.performUpdate(undefined, [], async () => {
|
||||
await this.graphQLService.sdk.setUserConfigurationParameter({
|
||||
name: key,
|
||||
value,
|
||||
@@ -189,6 +203,15 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async updateLocalPassword(oldPassword: string, newPassword: string): Promise<void> {
|
||||
await this.performUpdate(undefined, [], async () => {
|
||||
await this.graphQLService.sdk.authChangeLocalPassword({
|
||||
oldPassword: this.authProviderService.hashValue(oldPassword),
|
||||
newPassword: this.authProviderService.hashValue(newPassword),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async deleteConfigurationParameter(key: ResourceKeySimple<string>): Promise<UserInfo | null> {
|
||||
await this.load();
|
||||
|
||||
@@ -197,7 +220,7 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
|
||||
}
|
||||
|
||||
const keyList: string[] = [];
|
||||
this.performUpdate(undefined, [], async () => {
|
||||
await this.performUpdate(undefined, [], async () => {
|
||||
await ResourceKeyUtils.forEachAsync(key, async name => {
|
||||
await this.graphQLService.sdk.setUserConfigurationParameter({
|
||||
name,
|
||||
|
||||
@@ -217,13 +217,6 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
|
||||
await this.refresh(userId);
|
||||
}
|
||||
|
||||
async updateLocalPassword(oldPassword: string, newPassword: string): Promise<void> {
|
||||
await this.graphQLService.sdk.authChangeLocalPassword({
|
||||
oldPassword: this.authProviderService.hashValue(oldPassword),
|
||||
newPassword: this.authProviderService.hashValue(newPassword),
|
||||
});
|
||||
}
|
||||
|
||||
async delete(key: ResourceKeySimple<string>): Promise<void> {
|
||||
await ResourceKeyUtils.forEachAsync(key, async key => {
|
||||
if (this.isActiveUser(key)) {
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-client-activity/tsconfig.json"
|
||||
},
|
||||
@@ -23,9 +20,6 @@
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
@@ -35,15 +29,6 @@
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
@@ -72,10 +57,7 @@
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
"path": "../core-storage/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
|
||||
@@ -39,14 +39,8 @@
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@cloudbeaver/core-browser": "~0.1.0",
|
||||
"@cloudbeaver/core-client-activity": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/tests-runner": "~0.1.0",
|
||||
|
||||
@@ -20,7 +20,7 @@ export const AppRefreshButton: React.FC<IProps> = function AppRefreshButton({ cl
|
||||
const app = useService(App);
|
||||
|
||||
function refresh() {
|
||||
app.start();
|
||||
app.restart();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,8 +18,18 @@
|
||||
padding: 24px;
|
||||
border-radius: var(--theme-group-element-radius);
|
||||
|
||||
> .groupTitle {
|
||||
padding: 24px 0;
|
||||
margin: -24px 0;
|
||||
}
|
||||
|
||||
&.dense {
|
||||
padding: 8px;
|
||||
|
||||
> .groupTitle {
|
||||
padding: 8px 0;
|
||||
margin: -8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.form > * {
|
||||
@@ -32,6 +42,7 @@
|
||||
|
||||
> .groupTitle {
|
||||
padding: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,4 +5,12 @@
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.9;
|
||||
background-color: inherit;
|
||||
|
||||
&.sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,17 @@ import styles from './GroupTitle.m.css';
|
||||
import type { ILayoutSizeProps } from './ILayoutSizeProps';
|
||||
import elementsSizeStyles from './shared/ElementsSize.m.css';
|
||||
|
||||
export const GroupTitle: React.FC<ILayoutSizeProps & React.HTMLAttributes<HTMLHeadingElement>> = function GroupTitle({ className, ...rest }) {
|
||||
interface Props {
|
||||
sticky?: boolean;
|
||||
}
|
||||
|
||||
export const GroupTitle: React.FC<Props & ILayoutSizeProps & React.HTMLAttributes<HTMLHeadingElement>> = function GroupTitle({
|
||||
sticky,
|
||||
className,
|
||||
...rest
|
||||
}) {
|
||||
const style = useS(styles, elementsSizeStyles);
|
||||
const divProps = filterLayoutFakeProps(rest);
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
return <h2 {...divProps} className={s(style, { groupTitle: true, ...layoutProps }, className)} />;
|
||||
return <h2 {...divProps} className={s(style, { groupTitle: true, sticky, ...layoutProps }, className)} />;
|
||||
};
|
||||
|
||||
@@ -8,40 +8,15 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { coreBrowserManifest } from '@cloudbeaver/core-browser';
|
||||
import { coreClientActivityManifest } from '@cloudbeaver/core-client-activity';
|
||||
import { coreEventsManifest } from '@cloudbeaver/core-events';
|
||||
import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
|
||||
import { corePluginManifest } from '@cloudbeaver/core-plugin';
|
||||
import { coreProductManifest } from '@cloudbeaver/core-product';
|
||||
import { coreRootManifest } from '@cloudbeaver/core-root';
|
||||
import { createGQLEndpoint } from '@cloudbeaver/core-root/dist/__custom_mocks__/createGQLEndpoint';
|
||||
import { mockAppInit } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockAppInit';
|
||||
import { mockGraphQL } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockGraphQL';
|
||||
import { coreSDKManifest } from '@cloudbeaver/core-sdk';
|
||||
import { coreSettingsManifest } from '@cloudbeaver/core-settings';
|
||||
import { coreThemingManifest } from '@cloudbeaver/core-theming';
|
||||
import { createApp, renderInApp } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import { ErrorMessage } from './ErrorMessage';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const app = createApp(
|
||||
coreEventsManifest,
|
||||
corePluginManifest,
|
||||
coreProductManifest,
|
||||
coreRootManifest,
|
||||
coreSDKManifest,
|
||||
coreSettingsManifest,
|
||||
coreBrowserManifest,
|
||||
coreThemingManifest,
|
||||
coreLocalizationManifest,
|
||||
coreClientActivityManifest,
|
||||
);
|
||||
|
||||
mockGraphQL(...mockAppInit(endpoint));
|
||||
|
||||
beforeAll(() => app.init());
|
||||
const app = createApp(coreEventsManifest, coreSettingsManifest, coreThemingManifest, coreLocalizationManifest);
|
||||
|
||||
test('icons.svg#name', () => {
|
||||
renderInApp(<ErrorMessage text="error" />, app);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { runInAction } from 'mobx';
|
||||
import { useContext, useState } from 'react';
|
||||
|
||||
import { useObjectRef } from '../../useObjectRef';
|
||||
@@ -65,36 +66,38 @@ export function useCheckboxState<TKey extends string>(options: CheckboxStateOpti
|
||||
() => ({
|
||||
checked,
|
||||
change(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { inverse, state, name, value, onChange, count, context } = optionsRef;
|
||||
let checked = event.target.checked;
|
||||
runInAction(() => {
|
||||
const { inverse, state, name, value, onChange, count, context } = optionsRef;
|
||||
let checked = event.target.checked;
|
||||
|
||||
if (inverse) {
|
||||
checked = !checked;
|
||||
}
|
||||
if (inverse) {
|
||||
checked = !checked;
|
||||
}
|
||||
|
||||
if (state !== undefined && name !== undefined) {
|
||||
const currentState = state[name as TKey];
|
||||
if (state !== undefined && name !== undefined) {
|
||||
const currentState = state[name as TKey];
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (Array.isArray(currentState)) {
|
||||
const elementIndex = currentState.indexOf(value);
|
||||
if (checked && elementIndex === -1) {
|
||||
currentState.push(value);
|
||||
} else if (elementIndex !== -1) {
|
||||
currentState.splice(elementIndex, 1);
|
||||
if (typeof value === 'string') {
|
||||
if (Array.isArray(currentState)) {
|
||||
const elementIndex = currentState.indexOf(value);
|
||||
if (checked && elementIndex === -1) {
|
||||
currentState.push(value);
|
||||
} else if (elementIndex !== -1) {
|
||||
currentState.splice(elementIndex, 1);
|
||||
}
|
||||
} else {
|
||||
state[name as TKey] = value;
|
||||
}
|
||||
} else {
|
||||
state[name as TKey] = value;
|
||||
state[name as TKey] = checked;
|
||||
}
|
||||
} else {
|
||||
state[name as TKey] = checked;
|
||||
}
|
||||
}
|
||||
|
||||
onChange?.(checked, name as TKey);
|
||||
context?.change(checked, name);
|
||||
onChange?.(checked, name as TKey);
|
||||
context?.change(checked, name);
|
||||
|
||||
refresh(count + 1);
|
||||
refresh(count + 1);
|
||||
});
|
||||
},
|
||||
}),
|
||||
{ checked },
|
||||
|
||||
@@ -11,13 +11,13 @@ import type { ILoadableState } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { getComputed } from '../getComputed';
|
||||
|
||||
export function useAutoLoad(component: { name: string }, state: ILoadableState | ILoadableState[], enabled = true, lazy = false) {
|
||||
export function useAutoLoad(component: { name: string }, state: ILoadableState | ReadonlyArray<ILoadableState>, enabled = true, lazy = false) {
|
||||
const [loadFunctionName] = useState(`${component.name}.useAutoLoad(...)` as const);
|
||||
if (!Array.isArray(state)) {
|
||||
state = [state];
|
||||
state = [state] as ReadonlyArray<ILoadableState>;
|
||||
}
|
||||
|
||||
for (const loader of state as ILoadableState[]) {
|
||||
for (const loader of state as ReadonlyArray<ILoadableState>) {
|
||||
getComputed(
|
||||
// activate mobx subscriptions
|
||||
() => (!loader.isLoaded() || loader.isOutdated?.() === true) && !loader.isError(),
|
||||
@@ -30,7 +30,7 @@ export function useAutoLoad(component: { name: string }, state: ILoadableState |
|
||||
return;
|
||||
}
|
||||
|
||||
for (const loader of state as ILoadableState[]) {
|
||||
for (const loader of state as ReadonlyArray<ILoadableState>) {
|
||||
if (loader.isError() || (loader.lazy === true && !lazy)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+4
-5
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
|
||||
import { getObjectPropertyType, type ObjectPropertyInfo, type ObjectPropertyType } from '@cloudbeaver/core-sdk';
|
||||
import { removeMetadataFromDataURL } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { FieldCheckbox } from '../../FormControls/Checkboxes/FieldCheckbox';
|
||||
@@ -19,7 +19,6 @@ import { isControlPresented } from '../../FormControls/isControlPresented';
|
||||
import { Textarea } from '../../FormControls/Textarea';
|
||||
import { Link } from '../../Link';
|
||||
import { useTranslate } from '../../localization/useTranslate';
|
||||
import { type ControlType, getPropertyControlType } from './getPropertyControlType';
|
||||
|
||||
const RESERVED_KEYWORDS = ['no', 'off', 'new-password'];
|
||||
|
||||
@@ -39,7 +38,7 @@ interface RenderFieldProps {
|
||||
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
function getValue(value: any, controlType: ControlType) {
|
||||
function getValue(value: any, controlType: ObjectPropertyType) {
|
||||
const checkbox = controlType === 'checkbox';
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
@@ -70,7 +69,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
|
||||
const controlType = getPropertyControlType(property);
|
||||
const controlType = getObjectPropertyType(property);
|
||||
const password = property.features.includes('password');
|
||||
const required = property.required && !readOnly;
|
||||
|
||||
@@ -132,7 +131,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
);
|
||||
}
|
||||
|
||||
if (controlType === 'combobox') {
|
||||
if (controlType === 'selector') {
|
||||
if (state !== undefined) {
|
||||
return (
|
||||
<Combobox
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
import { addKnownError, consoleSpy } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import ErrorBoundary from './__custom__mocks__/ErrorBoundaryMock';
|
||||
import { importLazyComponent } from './importLazyComponent';
|
||||
|
||||
addKnownError(/The above error occurred in one of your React components.*/);
|
||||
|
||||
describe('importLazyComponent', () => {
|
||||
const fallback = 'Loading...';
|
||||
|
||||
@@ -45,6 +49,7 @@ describe('importLazyComponent', () => {
|
||||
|
||||
expect(screen.getByText(fallback)).toBeTruthy();
|
||||
await waitFor(() => expect(screen.getByText(errorText)).toBeTruthy());
|
||||
expect(consoleSpy.error).toHaveBeenCalledWith(expect.stringMatching(/The above error occurred in one of your React components.*/));
|
||||
expect(componentImporter).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,7 +54,6 @@ export { MenuTrigger, type MenuState } from './MenuPanel/MenuTrigger';
|
||||
|
||||
export * from './ObjectPropertyInfo/ObjectPropertyInfoForm/ObjectPropertyInfoForm';
|
||||
export * from './ObjectPropertyInfo/useObjectPropertyCategories';
|
||||
export * from './ObjectPropertyInfo/ObjectPropertyInfoForm/getPropertyControlType';
|
||||
|
||||
export * from './Overlay/Overlay';
|
||||
export * from './Overlay/OverlayActions';
|
||||
|
||||
@@ -27,10 +27,10 @@ type ExtractStyles<T extends ComponentStyle[]> = Intersect<
|
||||
? U extends BaseStyles
|
||||
? U
|
||||
: U extends (theme: string) => Promise<infer A>
|
||||
? A extends BaseStyles
|
||||
? A
|
||||
? A extends BaseStyles
|
||||
? A
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
>;
|
||||
|
||||
@@ -47,7 +47,7 @@ export function useS<T extends ComponentStyle[]>(...componentStyles: [...T]): Ex
|
||||
const [patch, forceUpdate] = useState(0);
|
||||
const loadedStyles = useRef<BaseStyles[]>([]);
|
||||
const themeService = useService(ThemeService);
|
||||
const [currentThemeId, setCurrentThemeId] = useState(() => themeService.currentThemeId);
|
||||
const [currentThemeId, setCurrentThemeId] = useState(() => themeService.themeId);
|
||||
const lastThemeRef = useRef<string>(currentThemeId);
|
||||
// @ts-ignore
|
||||
const filteredStyles = themeService.mapStyles(componentStyles.flat(Infinity).filter(Boolean) as Style[], context);
|
||||
@@ -57,7 +57,7 @@ export function useS<T extends ComponentStyle[]>(...componentStyles: [...T]): Ex
|
||||
executor: themeService.onChange,
|
||||
handlers: [
|
||||
function updateThemeId(theme) {
|
||||
if (currentThemeId !== themeService.currentThemeId && trackTheme) {
|
||||
if (currentThemeId !== themeService.themeId && trackTheme) {
|
||||
setCurrentThemeId(theme.id);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record<string,
|
||||
const stylesRef = useRef<ComponentStyle[]>([]);
|
||||
const loadedStyles = useRef<BaseStyles[]>([]);
|
||||
const themeService = useService(ThemeService);
|
||||
const [currentThemeId, setCurrentThemeId] = useState(() => themeService.currentThemeId);
|
||||
const [currentThemeId, setCurrentThemeId] = useState(() => themeService.themeId);
|
||||
const lastThemeRef = useRef<string>(currentThemeId);
|
||||
//@ts-ignore
|
||||
const filteredStyles = componentStyles.flat(Infinity).filter(Boolean) as Style[];
|
||||
@@ -39,7 +39,7 @@ export function useStyles(...componentStyles: ComponentStyle[]): Record<string,
|
||||
executor: themeService.onChange,
|
||||
handlers: [
|
||||
function updateThemeId(theme) {
|
||||
if (currentThemeId !== themeService.currentThemeId && trackTheme) {
|
||||
if (currentThemeId !== themeService.themeId && trackTheme) {
|
||||
setCurrentThemeId(theme.id);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,12 +9,6 @@
|
||||
{
|
||||
"path": "../core-authentication/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-client-activity/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
@@ -36,24 +30,12 @@
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-navigation-tree": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-projects": "~0.1.0",
|
||||
"@cloudbeaver/core-resource": "~0.1.0",
|
||||
@@ -44,6 +43,8 @@
|
||||
"@cloudbeaver/core-session-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-settings-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-settings-user": "~0.1.0",
|
||||
"@cloudbeaver/core-storage": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-version": "~0.1.0",
|
||||
|
||||
@@ -19,7 +19,6 @@ import { coreDialogsManifest } from '@cloudbeaver/core-dialogs';
|
||||
import { coreEventsManifest } from '@cloudbeaver/core-events';
|
||||
import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
|
||||
import { coreNavigationTree } from '@cloudbeaver/core-navigation-tree';
|
||||
import { corePluginManifest } from '@cloudbeaver/core-plugin';
|
||||
import { coreProductManifest } from '@cloudbeaver/core-product';
|
||||
import { coreProjectsManifest } from '@cloudbeaver/core-projects';
|
||||
import { coreResourceManifest } from '@cloudbeaver/core-resource';
|
||||
@@ -31,6 +30,8 @@ import { coreServerLocalization } from '@cloudbeaver/core-server-localization';
|
||||
import { coreSessionLocalization } from '@cloudbeaver/core-session-localization';
|
||||
import { coreSettingsManifest } from '@cloudbeaver/core-settings';
|
||||
import { coreSettingsLocalizationManifest } from '@cloudbeaver/core-settings-localization';
|
||||
import { coreSettingsUserManifest } from '@cloudbeaver/core-settings-user';
|
||||
import { coreStorageManifest } from '@cloudbeaver/core-storage';
|
||||
import { coreThemingManifest } from '@cloudbeaver/core-theming';
|
||||
import { coreUIManifest } from '@cloudbeaver/core-ui';
|
||||
import { coreVersionManifest } from '@cloudbeaver/core-version';
|
||||
@@ -55,11 +56,11 @@ export const coreManifests: PluginManifest[] = [
|
||||
coreServerLocalization,
|
||||
coreBlocksManifest,
|
||||
coreSettingsManifest,
|
||||
coreStorageManifest,
|
||||
coreEventsManifest,
|
||||
coreResourceManifest,
|
||||
coreSDKManifest,
|
||||
coreRootManifest,
|
||||
corePluginManifest,
|
||||
coreBrowserManifest,
|
||||
coreBrowserSettingsManifest,
|
||||
coreBrowserCookiesManifest,
|
||||
@@ -77,4 +78,5 @@ export const coreManifests: PluginManifest[] = [
|
||||
coreAppManifest,
|
||||
coreClientActivityManifest,
|
||||
coreNavigationTree,
|
||||
coreSettingsUserManifest,
|
||||
];
|
||||
|
||||
@@ -51,9 +51,6 @@
|
||||
{
|
||||
"path": "../core-navigation-tree/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
@@ -84,9 +81,15 @@
|
||||
{
|
||||
"path": "../core-settings-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings-user/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-storage/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import { injectable } from '@cloudbeaver/core-di';
|
||||
@injectable()
|
||||
export class CookiesService {
|
||||
get cookiesEnabled() {
|
||||
return !this.browserSettingsService.settings.getValue('cookies.disabled');
|
||||
return !this.browserSettingsService.disabled;
|
||||
}
|
||||
|
||||
constructor(private readonly browserSettingsService: BrowserSettingsService) {}
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
@@ -29,8 +28,6 @@
|
||||
"@cloudbeaver/core-browser": "~0.1.0",
|
||||
"@cloudbeaver/core-client-activity": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-settings';
|
||||
|
||||
export const BROWSER_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_browser_settings_browser_settings_group');
|
||||
|
||||
@@ -10,26 +10,28 @@ import '@testing-library/jest-dom';
|
||||
import { coreBrowserManifest } from '@cloudbeaver/core-browser';
|
||||
import { coreClientActivityManifest } from '@cloudbeaver/core-client-activity';
|
||||
import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
|
||||
import { corePluginManifest } from '@cloudbeaver/core-plugin';
|
||||
import { coreProductManifest } from '@cloudbeaver/core-product';
|
||||
import { coreRootManifest, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { createGQLEndpoint } from '@cloudbeaver/core-root/dist/__custom_mocks__/createGQLEndpoint';
|
||||
import '@cloudbeaver/core-root/dist/__custom_mocks__/expectWebsocketClosedMessage';
|
||||
import { mockAppInit } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockAppInit';
|
||||
import { mockGraphQL } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockGraphQL';
|
||||
import { mockServerConfig } from '@cloudbeaver/core-root/dist/__custom_mocks__/resolvers/mockServerConfig';
|
||||
import { coreSDKManifest } from '@cloudbeaver/core-sdk';
|
||||
import { coreSettingsManifest } from '@cloudbeaver/core-settings';
|
||||
import {
|
||||
expectDeprecatedSettingMessage,
|
||||
expectNoDeprecatedSettingMessage,
|
||||
} from '@cloudbeaver/core-settings/dist/__custom_mocks__/expectDeprecatedSettingMessage';
|
||||
import { createApp } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import { BrowserSettingsService, CookiesSettings } from './BrowserSettingsService';
|
||||
import { BrowserSettingsService } from './BrowserSettingsService';
|
||||
import { coreBrowserSettingsManifest } from './manifest';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const server = mockGraphQL(...mockAppInit(endpoint));
|
||||
const app = createApp(
|
||||
coreBrowserManifest,
|
||||
coreBrowserSettingsManifest,
|
||||
corePluginManifest,
|
||||
coreProductManifest,
|
||||
coreRootManifest,
|
||||
coreSDKManifest,
|
||||
coreSettingsManifest,
|
||||
@@ -37,30 +39,16 @@ const app = createApp(
|
||||
coreClientActivityManifest,
|
||||
);
|
||||
|
||||
const server = mockGraphQL(...mockAppInit(endpoint));
|
||||
|
||||
beforeAll(() => app.init());
|
||||
|
||||
const testValueA = false;
|
||||
const testValueB = true;
|
||||
|
||||
const equalConfigA = {
|
||||
core: {
|
||||
cookies: {
|
||||
disabled: testValueA,
|
||||
},
|
||||
browser: {
|
||||
'cookies.disabled': testValueA,
|
||||
} as CookiesSettings,
|
||||
},
|
||||
'core.cookies.disabled': testValueA,
|
||||
'core.browser.cookies.disabled': testValueA,
|
||||
};
|
||||
|
||||
const equalConfigB = {
|
||||
core: {
|
||||
cookies: {
|
||||
disabled: testValueB,
|
||||
},
|
||||
},
|
||||
'core.cookies.disabled': testValueB,
|
||||
};
|
||||
|
||||
test('New settings override deprecated settings', async () => {
|
||||
@@ -71,7 +59,8 @@ test('New settings override deprecated settings', async () => {
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('cookies.disabled')).toBe(testValueA);
|
||||
expect(settings.disabled).toBe(testValueA);
|
||||
expectNoDeprecatedSettingMessage();
|
||||
});
|
||||
|
||||
test('New settings fall back to deprecated settings', async () => {
|
||||
@@ -82,5 +71,6 @@ test('New settings fall back to deprecated settings', async () => {
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('cookies.disabled')).toBe(testValueB);
|
||||
expect(settings.disabled).toBe(testValueB);
|
||||
expectDeprecatedSettingMessage();
|
||||
});
|
||||
|
||||
@@ -6,32 +6,43 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import { createSettingsAliasResolver, PluginManagerService, PluginSettings, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { ServerSettingsResolverService, ServerSettingsService } from '@cloudbeaver/core-root';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
import {
|
||||
createSettingsAliasResolver,
|
||||
ROOT_SETTINGS_LAYER,
|
||||
SettingsManagerService,
|
||||
SettingsProvider,
|
||||
SettingsProviderService,
|
||||
SettingsResolverService,
|
||||
} from '@cloudbeaver/core-settings';
|
||||
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
|
||||
|
||||
const settingsSchema = schema.object({
|
||||
'cookies.disabled': schema.coerce.boolean().default(false),
|
||||
'core.browser.cookies.disabled': schemaExtra.stringedBoolean().default(false),
|
||||
});
|
||||
|
||||
export type CookiesSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class BrowserSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
get disabled(): boolean {
|
||||
return this.settings.getValue('core.browser.cookies.disabled');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof settingsSchema>;
|
||||
|
||||
constructor(
|
||||
private readonly pluginManagerService: PluginManagerService,
|
||||
private readonly settingsProviderService: SettingsProviderService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly serverSettingsService: ServerSettingsService,
|
||||
private readonly serverSettingsResolverService: ServerSettingsResolverService,
|
||||
private readonly settingsResolverService: SettingsResolverService,
|
||||
) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('browser', 'core', settingsSchema);
|
||||
this.settings = this.settingsProviderService.createSettings(settingsSchema);
|
||||
|
||||
this.serverSettingsResolverService.addResolver(
|
||||
this.settingsResolverService.addResolver(
|
||||
ROOT_SETTINGS_LAYER,
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
createSettingsAliasResolver(this.serverSettingsService, this.settings, 'core'),
|
||||
createSettingsAliasResolver(this.settingsResolverService, this.settings, {
|
||||
'core.browser.cookies.disabled': 'core.cookies.disabled',
|
||||
}),
|
||||
);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
@@ -26,18 +26,6 @@
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
@@ -47,6 +35,9 @@
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
|
||||
@@ -26,24 +26,34 @@ typescriptConfig.references = [];
|
||||
// typescriptRootConfig.references = typescriptRootConfig.references || [];
|
||||
|
||||
for (const dependency of dependencies) {
|
||||
if (!dependency.startsWith('@cloudbeaver')) {
|
||||
if (dependency === pkg.name) {
|
||||
console.error(`Self reference detected: ${dependency}`);
|
||||
continue;
|
||||
}
|
||||
const dependencyPath = resolve(require.resolve(join(dependency, 'src', 'index.ts'), { paths: nodeModules }), '../../tsconfig.json');
|
||||
typescriptConfig.references.push({
|
||||
path: upath.relative(currentDir, dependencyPath),
|
||||
});
|
||||
typescriptConfig.references.sort((a, b) => a.path.localeCompare(b.path));
|
||||
try {
|
||||
if (!dependency.startsWith('@cloudbeaver')) {
|
||||
continue;
|
||||
}
|
||||
const dependencyPath = resolve(require.resolve(join(dependency, 'src', 'index.ts'), { paths: nodeModules }), '../../tsconfig.json');
|
||||
typescriptConfig.references.push({
|
||||
path: upath.relative(currentDir, dependencyPath),
|
||||
});
|
||||
|
||||
// const relativePath = relative(tsRootPath, dependencyPath);
|
||||
// const relativePath = relative(tsRootPath, dependencyPath);
|
||||
|
||||
// if (!typescriptRootConfig.references.find(ref => ref.path === relativePath)) {
|
||||
// typescriptRootConfig.references.push({
|
||||
// path: relativePath,
|
||||
// });
|
||||
// }
|
||||
// if (!typescriptRootConfig.references.find(ref => ref.path === relativePath)) {
|
||||
// typescriptRootConfig.references.push({
|
||||
// path: relativePath,
|
||||
// });
|
||||
// }
|
||||
} catch (e) {
|
||||
console.error(`Failed to resolve ${dependency}`);
|
||||
}
|
||||
}
|
||||
|
||||
typescriptConfig.references = [...new Set(typescriptConfig.references)];
|
||||
typescriptConfig.references.sort((a, b) => a.path.localeCompare(b.path));
|
||||
|
||||
typescriptConfig.compilerOptions = {
|
||||
rootDir: 'src',
|
||||
outDir: 'dist',
|
||||
|
||||
@@ -26,7 +26,7 @@ const devDependencies = new Set();
|
||||
let isSuccess = true;
|
||||
|
||||
const sourceFilesIterator = glob.globIterateSync('**/*.{ts,tsx,scss,css}', { cwd: currentPackageSrcPath });
|
||||
const importRegex = /(import|export) ((type |)([\w,\s]*?)(\{[\w\s\n,]*?\}|) from |)['"]((@[\w-]*\/[\w-]*)|([^\\.].*?))(\/.*)*['"]/g;
|
||||
const importRegex = /(import|export) ((type |)([\w,\s*]*?)(\{[\w\s\n,]*?\}|) from |)['"]((@[\w-]*\/[\w-]*)|([^\\.].*?))(\/.*)*['"]/g;
|
||||
const testFileRegex = /((__custom_mocks__|__tests__).*|\.test)\.tsx?$/i;
|
||||
const tsFileRegex = /\.ts$/i;
|
||||
const tsxFileRegex = /\.tsx$/i;
|
||||
|
||||
@@ -45,6 +45,9 @@ module.exports = (env, argv) => {
|
||||
index,
|
||||
sso,
|
||||
},
|
||||
output: {
|
||||
devtoolModuleFilenameTemplate: 'file:///[absolute-resource-path]',
|
||||
},
|
||||
watchOptions: {
|
||||
aggregateTimeout: 3000,
|
||||
ignored: ['**/node_modules', '**/packages/*/src/**/*.{ts,tsx}'],
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
"@cloudbeaver/core-extensions": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-navigation-tree": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-projects": "~0.1.0",
|
||||
"@cloudbeaver/core-resource": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0",
|
||||
"react": "^18.2.0"
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-settings';
|
||||
|
||||
export const CONNECTIONS_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_connections_connections_settings_group');
|
||||
|
||||
@@ -6,22 +6,28 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import { PluginManagerService, PluginSettings, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
import { SettingsManagerService, SettingsProvider, SettingsProviderService } from '@cloudbeaver/core-settings';
|
||||
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
|
||||
|
||||
const settingsSchema = schema.object({
|
||||
disabled: schema.coerce.boolean().default(false),
|
||||
'core.connections.disabled': schemaExtra.stringedBoolean().default(false),
|
||||
});
|
||||
|
||||
export type ConnectionsSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class ConnectionsSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
get disabled(): boolean {
|
||||
return this.settings.getValue('core.connections.disabled');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService, private readonly settingsManagerService: SettingsManagerService) {
|
||||
constructor(
|
||||
private readonly settingsProviderService: SettingsProviderService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('connections', 'core', settingsSchema);
|
||||
this.settings = this.settingsProviderService.createSettings(settingsSchema);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@
|
||||
{
|
||||
"path": "../core-navigation-tree/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-projects/tsconfig.json"
|
||||
},
|
||||
@@ -51,6 +48,9 @@
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { Bootstrap } from './Bootstrap';
|
||||
import { Dependency } from './Dependency';
|
||||
import type { DIContainer } from './DIContainer';
|
||||
import { Disposable } from './Disposable';
|
||||
import type { IServiceCollection, IServiceConstructor, IServiceInjector } from './IApp';
|
||||
import { IDiWrapper, inversifyWrapper } from './inversifyWrapper';
|
||||
import type { PluginManifest } from './PluginManifest';
|
||||
@@ -34,6 +35,15 @@ export class App {
|
||||
await this.onStart.execute();
|
||||
}
|
||||
|
||||
async restart(): Promise<void> {
|
||||
this.dispose();
|
||||
await this.start();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.diWrapper.collection.unbindAll();
|
||||
}
|
||||
|
||||
getPlugins(): PluginManifest[] {
|
||||
return [...this.plugins];
|
||||
}
|
||||
@@ -60,8 +70,6 @@ export class App {
|
||||
|
||||
// first phase register all dependencies
|
||||
private registerServices(): void {
|
||||
this.diWrapper.collection.unbindAll();
|
||||
|
||||
this.getServiceCollection().addServiceByClass(App, this);
|
||||
|
||||
for (const service of this.getServices()) {
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Disposable } from './Disposable';
|
||||
import { injectable } from './injectable';
|
||||
|
||||
@injectable()
|
||||
export abstract class Bootstrap {
|
||||
export abstract class Bootstrap extends Disposable {
|
||||
register(): void | Promise<void> {}
|
||||
load(): void | Promise<void> {}
|
||||
}
|
||||
|
||||
+6
-5
@@ -5,11 +5,12 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import type { IFormStateInfo } from '@cloudbeaver/core-ui';
|
||||
import { preDestroy } from 'inversify';
|
||||
|
||||
export interface IUserProfileFormState {
|
||||
info: IFormStateInfo;
|
||||
validate: () => Promise<void>;
|
||||
import { injectable } from './injectable';
|
||||
|
||||
dispose: () => void;
|
||||
@injectable()
|
||||
export abstract class Disposable {
|
||||
@preDestroy()
|
||||
dispose(): void {}
|
||||
}
|
||||
@@ -20,19 +20,15 @@
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@cloudbeaver/core-browser": "~0.1.0",
|
||||
"@cloudbeaver/core-client-activity": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
|
||||
@@ -7,55 +7,45 @@
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { coreBrowserManifest } from '@cloudbeaver/core-browser';
|
||||
import { coreClientActivityManifest } from '@cloudbeaver/core-client-activity';
|
||||
import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
|
||||
import { corePluginManifest } from '@cloudbeaver/core-plugin';
|
||||
import { coreProductManifest } from '@cloudbeaver/core-product';
|
||||
import { coreRootManifest, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { createGQLEndpoint } from '@cloudbeaver/core-root/dist/__custom_mocks__/createGQLEndpoint';
|
||||
import '@cloudbeaver/core-root/dist/__custom_mocks__/expectWebsocketClosedMessage';
|
||||
import { mockAppInit } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockAppInit';
|
||||
import { mockGraphQL } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockGraphQL';
|
||||
import { mockServerConfig } from '@cloudbeaver/core-root/dist/__custom_mocks__/resolvers/mockServerConfig';
|
||||
import { coreSDKManifest } from '@cloudbeaver/core-sdk';
|
||||
import { coreSettingsManifest } from '@cloudbeaver/core-settings';
|
||||
import {
|
||||
expectDeprecatedSettingMessage,
|
||||
expectNoDeprecatedSettingMessage,
|
||||
} from '@cloudbeaver/core-settings/dist/__custom_mocks__/expectDeprecatedSettingMessage';
|
||||
import { createApp } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import { EventsSettings, EventsSettingsService } from './EventsSettingsService';
|
||||
import { EventsSettingsService } from './EventsSettingsService';
|
||||
import { coreEventsManifest } from './manifest';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const server = mockGraphQL(...mockAppInit(endpoint));
|
||||
const app = createApp(
|
||||
coreEventsManifest,
|
||||
corePluginManifest,
|
||||
coreSettingsManifest,
|
||||
coreLocalizationManifest,
|
||||
coreBrowserManifest,
|
||||
coreProductManifest,
|
||||
coreRootManifest,
|
||||
coreSDKManifest,
|
||||
coreClientActivityManifest,
|
||||
);
|
||||
|
||||
const server = mockGraphQL(...mockAppInit(endpoint));
|
||||
|
||||
beforeAll(() => app.init());
|
||||
|
||||
const oldConfig = {
|
||||
core_events: {
|
||||
notificationsPool: 1,
|
||||
maxPersistentAllow: 2,
|
||||
} as EventsSettings,
|
||||
'core_events.notificationsPool': '1',
|
||||
'core_events.maxPersistentAllow': '2',
|
||||
};
|
||||
|
||||
const overrideConfig = {
|
||||
...oldConfig,
|
||||
plugin: {
|
||||
notifications: {
|
||||
notificationsPool: 3,
|
||||
maxPersistentAllow: 4,
|
||||
} as EventsSettings,
|
||||
},
|
||||
'plugin.notifications.notificationsPool': '3',
|
||||
'plugin.notifications.maxPersistentAllow': '4',
|
||||
};
|
||||
|
||||
test('New settings override deprecated settings', async () => {
|
||||
@@ -66,8 +56,9 @@ test('New settings override deprecated settings', async () => {
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('notificationsPool')).toBe(3);
|
||||
expect(settings.settings.getValue('maxPersistentAllow')).toBe(4);
|
||||
expect(settings.notificationsPool).toBe(3);
|
||||
expect(settings.maxPersistentAllow).toBe(4);
|
||||
expectNoDeprecatedSettingMessage();
|
||||
});
|
||||
|
||||
test('Deprecated settings accessible with new settings', async () => {
|
||||
@@ -78,6 +69,7 @@ test('Deprecated settings accessible with new settings', async () => {
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('notificationsPool')).toBe(1);
|
||||
expect(settings.settings.getValue('maxPersistentAllow')).toBe(2);
|
||||
expect(settings.notificationsPool).toBe(1);
|
||||
expect(settings.maxPersistentAllow).toBe(2);
|
||||
expectDeprecatedSettingMessage();
|
||||
});
|
||||
|
||||
@@ -6,33 +6,48 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import { createSettingsAliasResolver, PluginManagerService, PluginSettings, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { ServerSettingsResolverService, ServerSettingsService } from '@cloudbeaver/core-root';
|
||||
import {
|
||||
createSettingsAliasResolver,
|
||||
ROOT_SETTINGS_LAYER,
|
||||
SettingsManagerService,
|
||||
SettingsProvider,
|
||||
SettingsProviderService,
|
||||
SettingsResolverService,
|
||||
} from '@cloudbeaver/core-settings';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
const settingsSchema = schema.object({
|
||||
notificationsPool: schema.coerce.number().default(20),
|
||||
maxPersistentAllow: schema.coerce.number().default(5),
|
||||
'plugin.notifications.notificationsPool': schema.coerce.number().default(20),
|
||||
'plugin.notifications.maxPersistentAllow': schema.coerce.number().default(5),
|
||||
});
|
||||
|
||||
export type EventsSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class EventsSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
get maxPersistentAllow(): number {
|
||||
return this.settings.getValue('plugin.notifications.maxPersistentAllow');
|
||||
}
|
||||
get notificationsPool(): number {
|
||||
return this.settings.getValue('plugin.notifications.notificationsPool');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof settingsSchema>;
|
||||
|
||||
constructor(
|
||||
private readonly pluginManagerService: PluginManagerService,
|
||||
private readonly settingsProviderService: SettingsProviderService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly serverSettingsService: ServerSettingsService,
|
||||
private readonly serverSettingsResolverService: ServerSettingsResolverService,
|
||||
private readonly settingsResolverService: SettingsResolverService,
|
||||
) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('notifications', 'plugin', settingsSchema);
|
||||
this.settings = this.settingsProviderService.createSettings(settingsSchema);
|
||||
|
||||
this.serverSettingsResolverService.addResolver(
|
||||
this.settingsResolverService.addResolver(
|
||||
ROOT_SETTINGS_LAYER,
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
createSettingsAliasResolver(this.serverSettingsService, this.settings, 'core_events'),
|
||||
createSettingsAliasResolver(this.settingsResolverService, this.settings, {
|
||||
'plugin.notifications.maxPersistentAllow': 'core_events.maxPersistentAllow',
|
||||
'plugin.notifications.notificationsPool': 'core_events.notificationsPool',
|
||||
}),
|
||||
);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-settings';
|
||||
|
||||
export const NOTIFICATIONS_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_events_notifications_settings_group');
|
||||
|
||||
@@ -52,7 +52,7 @@ export class NotificationService {
|
||||
if (options.persistent) {
|
||||
const persistentNotifications = this.notificationList.values.filter(value => value.persistent);
|
||||
|
||||
const maxPersistentAllow = this.settings.settings.getValue('maxPersistentAllow');
|
||||
const maxPersistentAllow = this.settings.maxPersistentAllow;
|
||||
|
||||
if (persistentNotifications.length >= maxPersistentAllow) {
|
||||
throw new Error(`You cannot create more than ${maxPersistentAllow} persistent notification`);
|
||||
@@ -105,7 +105,7 @@ export class NotificationService {
|
||||
|
||||
const filteredNotificationList = this.notificationList.values.filter(notification => !notification.persistent);
|
||||
|
||||
const notificationsPool = this.settings.settings.getValue('notificationsPool');
|
||||
const notificationsPool = this.settings.notificationsPool;
|
||||
|
||||
if (filteredNotificationList.length > notificationsPool) {
|
||||
let i = 0;
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-client-activity/tsconfig.json"
|
||||
},
|
||||
@@ -21,18 +18,6 @@
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
@@ -45,6 +30,9 @@
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
|
||||
@@ -32,9 +32,9 @@ export interface IExecutorHandlersCollection<T = unknown, TResult = any | Promis
|
||||
addContextCreator<TContext>(context: ISyncContextLoader<TContext, T>, creator: ISyncContextLoader<TContext, T>): this;
|
||||
addContextCreator<TContext>(context: IAsyncContextLoader<TContext, T>, creator: IAsyncContextLoader<TContext, T>): this;
|
||||
|
||||
before: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
|
||||
before: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>, filter?: ExecutorDataFilter<T>) => this;
|
||||
removeBefore: (executor: IExecutorHandlersCollection<any, TResult>) => void;
|
||||
next: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
|
||||
next: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>, filter?: ExecutorDataFilter<T>) => this;
|
||||
removeNext: (executor: IExecutorHandlersCollection<any, TResult>) => void;
|
||||
addCollection: (collection: IExecutorHandlersCollection<T, TResult>) => this;
|
||||
hasHandler: (handler: IExecutorHandler<T, TResult>) => boolean;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
*/
|
||||
import type { ILocale } from './ILocale';
|
||||
|
||||
export const DEFAULT_LOCALE: ILocale = { isoCode: 'en', displayName: 'English' };
|
||||
export const DEFAULT_LOCALE: ILocale = { isoCode: 'en', name: 'English' };
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
|
||||
export interface ILocale {
|
||||
isoCode: string;
|
||||
displayName: string;
|
||||
name: string;
|
||||
nativeName?: string;
|
||||
}
|
||||
|
||||
@@ -5,61 +5,52 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { action, makeObservable, observable, reaction } from 'mobx';
|
||||
import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
|
||||
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { SettingsService } from '@cloudbeaver/core-settings';
|
||||
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
import { DEFAULT_LOCALE } from './DEFAULT_LOCALE';
|
||||
import type { ILocale } from './ILocale';
|
||||
import type { ILocaleProvider } from './ILocaleProvider';
|
||||
import type { TLocalizationToken } from './TLocalizationToken';
|
||||
|
||||
const LANG_SETTINGS_KEY = 'langSettings';
|
||||
|
||||
interface ISettings {
|
||||
language: string | null;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class LocalizationService extends Bootstrap {
|
||||
get currentLanguage(): string {
|
||||
const lang = this.settings.language;
|
||||
const lang = this.language;
|
||||
|
||||
if (lang !== null && this.isLanguageSupported(lang)) {
|
||||
return lang;
|
||||
}
|
||||
|
||||
if (this.isLanguageSupported(this.defaultLanguage)) {
|
||||
return this.defaultLanguage;
|
||||
if (this.isLanguageSupported(DEFAULT_LOCALE.isoCode)) {
|
||||
return DEFAULT_LOCALE.isoCode;
|
||||
}
|
||||
|
||||
return this.supportedLanguages[0].isoCode;
|
||||
}
|
||||
|
||||
settings: ISettings;
|
||||
supportedLanguages: ILocale[];
|
||||
|
||||
readonly onChange: ISyncExecutor<string>;
|
||||
private defaultLanguage: string;
|
||||
readonly onChange: IExecutor<string>;
|
||||
private language: string | null;
|
||||
private readonly localeMap: Map<string, Map<string, string>> = new Map();
|
||||
private readonly localeProviders: ILocaleProvider[] = [];
|
||||
private reactionDisposer: IReactionDisposer | null;
|
||||
|
||||
constructor(private readonly settingsService: SettingsService) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.settings = getDefaultLocalizationSettings();
|
||||
this.supportedLanguages = [DEFAULT_LOCALE];
|
||||
this.defaultLanguage = DEFAULT_LOCALE.isoCode;
|
||||
this.onChange = new SyncExecutor();
|
||||
this.language = null;
|
||||
this.reactionDisposer = null;
|
||||
this.onChange = new Executor();
|
||||
|
||||
makeObservable<LocalizationService, 'localeMap' | 'setCurrentLocale' | 'supportedLanguages' | 'defaultLanguage'>(this, {
|
||||
defaultLanguage: observable,
|
||||
makeObservable<LocalizationService, 'localeMap' | 'supportedLanguages' | 'language'>(this, {
|
||||
language: observable,
|
||||
supportedLanguages: observable,
|
||||
settings: observable,
|
||||
localeMap: observable.shallow, // observable.shallow - don't treat locales as observables
|
||||
setCurrentLocale: action,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,8 +69,8 @@ export class LocalizationService extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultLanguage(lang: string) {
|
||||
this.defaultLanguage = lang;
|
||||
setLanguage(lang: string) {
|
||||
this.language = lang;
|
||||
}
|
||||
|
||||
readonly translate = <T extends TLocalizationToken | undefined>(token: T, fallback?: T, args: Record<string | number, any> = {}): T => {
|
||||
@@ -89,8 +80,8 @@ export class LocalizationService extends Bootstrap {
|
||||
|
||||
let translation = this.localeMap.get(this.currentLanguage)?.get(token as TLocalizationToken);
|
||||
|
||||
if (!translation) {
|
||||
translation = this.localeMap.get(this.defaultLanguage)?.get(token as TLocalizationToken);
|
||||
if (translation === undefined) {
|
||||
translation = this.localeMap.get(DEFAULT_LOCALE.isoCode)?.get(token as TLocalizationToken);
|
||||
}
|
||||
|
||||
if (typeof translation === 'string') {
|
||||
@@ -110,40 +101,69 @@ export class LocalizationService extends Bootstrap {
|
||||
return token;
|
||||
};
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.settingsService.registerSettings(LANG_SETTINGS_KEY, this.settings, getDefaultLocalizationSettings); // load user state locale
|
||||
register(): void {
|
||||
this.setSupportedLanguages([
|
||||
{
|
||||
isoCode: 'en',
|
||||
displayName: 'English',
|
||||
name: 'English',
|
||||
nativeName: 'English',
|
||||
},
|
||||
{
|
||||
isoCode: 'ru',
|
||||
displayName: 'Русский',
|
||||
name: 'Russian',
|
||||
nativeName: 'Русский',
|
||||
},
|
||||
{
|
||||
isoCode: 'it',
|
||||
displayName: 'Italiano',
|
||||
name: 'Italian',
|
||||
nativeName: 'Italiano',
|
||||
},
|
||||
{
|
||||
isoCode: 'zh',
|
||||
displayName: '中文',
|
||||
name: 'Chinese',
|
||||
nativeName: '中文',
|
||||
},
|
||||
]);
|
||||
this.addProvider(this.coreProvider.bind(this));
|
||||
this.reactionDisposer = reaction(
|
||||
() => this.currentLanguage,
|
||||
lang => {
|
||||
this.loadLocale(lang);
|
||||
},
|
||||
{
|
||||
fireImmediately: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
await this.loadLocaleAsync(this.defaultLanguage);
|
||||
await this.autoLoadCurrentLanguage();
|
||||
await this.loadLocale(DEFAULT_LOCALE.isoCode);
|
||||
await this.loadLocale(this.currentLanguage);
|
||||
}
|
||||
|
||||
async changeLocaleAsync(key: string): Promise<void> {
|
||||
dispose(): void {
|
||||
if (this.reactionDisposer) {
|
||||
this.reactionDisposer();
|
||||
}
|
||||
}
|
||||
|
||||
async changeLocale(key: string): Promise<void> {
|
||||
const prevLocale = this.currentLanguage;
|
||||
if (key === this.currentLanguage) {
|
||||
return;
|
||||
}
|
||||
await this.setLocale(key);
|
||||
this.onChange.execute(this.currentLanguage);
|
||||
if (!this.isLanguageSupported(key)) {
|
||||
throw new Error(`Language '${key}' is not supported`);
|
||||
}
|
||||
|
||||
await this.loadLocale(key);
|
||||
try {
|
||||
this.setLanguage(key);
|
||||
await this.onChange.execute(key);
|
||||
} catch (e) {
|
||||
this.setLanguage(prevLocale);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private async coreProvider(locale: string) {
|
||||
@@ -159,20 +179,7 @@ export class LocalizationService extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private setCurrentLocale(lang: string | null) {
|
||||
this.settings.language = lang;
|
||||
}
|
||||
|
||||
async setLocale(key: string) {
|
||||
if (!this.isLanguageSupported(key)) {
|
||||
throw new Error(`Language '${key}' is not supported`);
|
||||
}
|
||||
|
||||
this.setCurrentLocale(key);
|
||||
await this.loadLocaleAsync(key);
|
||||
}
|
||||
|
||||
private async loadLocaleAsync(localeKey: string): Promise<void> {
|
||||
private async loadLocale(localeKey: string): Promise<void> {
|
||||
if (this.localeMap.has(localeKey)) {
|
||||
return;
|
||||
}
|
||||
@@ -185,35 +192,4 @@ export class LocalizationService extends Bootstrap {
|
||||
}
|
||||
this.localeMap.set(localeKey, locale);
|
||||
}
|
||||
|
||||
private async autoLoadCurrentLanguage() {
|
||||
let resolve: (value: void | PromiseLike<void>) => void;
|
||||
let reject: (reason?: any) => void;
|
||||
let promise: Promise<void> | null = new Promise<void>((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
|
||||
reaction(
|
||||
() => this.currentLanguage,
|
||||
lang => {
|
||||
this.loadLocaleAsync(lang)
|
||||
.then(resolve, reject)
|
||||
.finally(() => {
|
||||
promise = null;
|
||||
});
|
||||
},
|
||||
{
|
||||
fireImmediately: true,
|
||||
},
|
||||
);
|
||||
|
||||
await promise;
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultLocalizationSettings(): ISettings {
|
||||
return {
|
||||
language: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": [
|
||||
|
||||
@@ -24,31 +24,28 @@
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-extensions": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-projects": "~0.1.0",
|
||||
"@cloudbeaver/core-resource": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@cloudbeaver/core-app": "~0.1.0",
|
||||
"@cloudbeaver/core-authentication": "~0.1.0",
|
||||
"@cloudbeaver/core-browser": "~0.1.0",
|
||||
"@cloudbeaver/core-client-activity": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-projects": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-storage": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-view": "~0.1.0",
|
||||
"@cloudbeaver/tests-runner": "~0.1.0",
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-settings';
|
||||
|
||||
export const NAVIGATION_TREE_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_navigation_tree_navigation_tree_settings_group');
|
||||
|
||||
@@ -7,78 +7,64 @@
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { coreAppManifest } from '@cloudbeaver/core-app';
|
||||
import { coreAuthenticationManifest } from '@cloudbeaver/core-authentication';
|
||||
import { mockAuthentication } from '@cloudbeaver/core-authentication/dist/__custom_mocks__/mockAuthentication';
|
||||
import { coreBrowserManifest } from '@cloudbeaver/core-browser';
|
||||
import { coreClientActivityManifest } from '@cloudbeaver/core-client-activity';
|
||||
import { coreEventsManifest } from '@cloudbeaver/core-events';
|
||||
import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
|
||||
import { corePluginManifest } from '@cloudbeaver/core-plugin';
|
||||
import { coreProductManifest } from '@cloudbeaver/core-product';
|
||||
import { coreProjectsManifest } from '@cloudbeaver/core-projects';
|
||||
import { coreRootManifest, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { createGQLEndpoint } from '@cloudbeaver/core-root/dist/__custom_mocks__/createGQLEndpoint';
|
||||
import '@cloudbeaver/core-root/dist/__custom_mocks__/expectWebsocketClosedMessage';
|
||||
import { mockAppInit } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockAppInit';
|
||||
import { mockGraphQL } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockGraphQL';
|
||||
import { mockServerConfig } from '@cloudbeaver/core-root/dist/__custom_mocks__/resolvers/mockServerConfig';
|
||||
import { coreRoutingManifest } from '@cloudbeaver/core-routing';
|
||||
import { coreSDKManifest } from '@cloudbeaver/core-sdk';
|
||||
import { coreSettingsManifest } from '@cloudbeaver/core-settings';
|
||||
import { coreThemingManifest } from '@cloudbeaver/core-theming';
|
||||
import {
|
||||
expectDeprecatedSettingMessage,
|
||||
expectNoDeprecatedSettingMessage,
|
||||
} from '@cloudbeaver/core-settings/dist/__custom_mocks__/expectDeprecatedSettingMessage';
|
||||
import { coreStorageManifest } from '@cloudbeaver/core-storage';
|
||||
import { coreUIManifest } from '@cloudbeaver/core-ui';
|
||||
import { coreViewManifest } from '@cloudbeaver/core-view';
|
||||
import { createApp } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import { coreNavigationTree } from './manifest';
|
||||
import { NavTreeSettings, NavTreeSettingsService } from './NavTreeSettingsService';
|
||||
import { NavTreeSettingsService } from './NavTreeSettingsService';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
|
||||
const app = createApp(
|
||||
coreNavigationTree,
|
||||
coreEventsManifest,
|
||||
corePluginManifest,
|
||||
coreProductManifest,
|
||||
coreStorageManifest,
|
||||
coreRootManifest,
|
||||
coreSDKManifest,
|
||||
coreSettingsManifest,
|
||||
coreBrowserManifest,
|
||||
coreRoutingManifest,
|
||||
coreThemingManifest,
|
||||
coreLocalizationManifest,
|
||||
coreAuthenticationManifest,
|
||||
coreAppManifest,
|
||||
coreProjectsManifest,
|
||||
coreUIManifest,
|
||||
coreViewManifest,
|
||||
coreClientActivityManifest,
|
||||
);
|
||||
|
||||
const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
|
||||
|
||||
beforeAll(() => app.init());
|
||||
|
||||
const deprecatedSettings = {
|
||||
'core.app': {
|
||||
navigationTree: {
|
||||
childrenLimit: 1,
|
||||
},
|
||||
metadata: {
|
||||
editing: false,
|
||||
deleting: false,
|
||||
},
|
||||
},
|
||||
'core.app.navigationTree.childrenLimit': 100,
|
||||
'core.app.metadata.editing': false,
|
||||
'core.app.metadata.deleting': false,
|
||||
};
|
||||
|
||||
const newSettings = {
|
||||
...deprecatedSettings,
|
||||
core: {
|
||||
'navigation-tree': {
|
||||
childrenLimit: 2,
|
||||
editing: true,
|
||||
deleting: true,
|
||||
} as NavTreeSettings,
|
||||
},
|
||||
'core.navigation-tree.childrenLimit': 200,
|
||||
'core.navigation-tree.editing': true,
|
||||
'core.navigation-tree.deleting': true,
|
||||
};
|
||||
|
||||
test('New settings override deprecated', async () => {
|
||||
@@ -89,9 +75,10 @@ test('New settings override deprecated', async () => {
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('childrenLimit')).toBe(2);
|
||||
expect(settings.settings.getValue('editing')).toBe(true);
|
||||
expect(settings.settings.getValue('deleting')).toBe(true);
|
||||
expect(settings.childrenLimit).toBe(200);
|
||||
expect(settings.editing).toBe(true);
|
||||
expect(settings.deleting).toBe(true);
|
||||
expectNoDeprecatedSettingMessage();
|
||||
});
|
||||
|
||||
test('Deprecated settings are used if new settings are not defined', async () => {
|
||||
@@ -102,7 +89,8 @@ test('Deprecated settings are used if new settings are not defined', async () =>
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('childrenLimit')).toBe(1);
|
||||
expect(settings.settings.getValue('editing')).toBe(false);
|
||||
expect(settings.settings.getValue('deleting')).toBe(false);
|
||||
expect(settings.childrenLimit).toBe(100);
|
||||
expect(settings.editing).toBe(false);
|
||||
expect(settings.deleting).toBe(false);
|
||||
expectDeprecatedSettingMessage();
|
||||
});
|
||||
|
||||
@@ -6,34 +6,54 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import { createSettingsAliasResolver, PluginManagerService, PluginSettings, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { ServerSettingsResolverService, ServerSettingsService } from '@cloudbeaver/core-root';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
import {
|
||||
createSettingsAliasResolver,
|
||||
ROOT_SETTINGS_LAYER,
|
||||
SettingsManagerService,
|
||||
SettingsProvider,
|
||||
SettingsProviderService,
|
||||
SettingsResolverService,
|
||||
} from '@cloudbeaver/core-settings';
|
||||
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
|
||||
|
||||
const settingsSchema = schema.object({
|
||||
childrenLimit: schema.coerce.number().default(100),
|
||||
editing: schema.coerce.boolean().default(true),
|
||||
deleting: schema.coerce.boolean().default(true),
|
||||
'core.navigation-tree.childrenLimit': schema.coerce.number().min(10).max(1000).default(100),
|
||||
'core.navigation-tree.editing': schemaExtra.stringedBoolean().default(true),
|
||||
'core.navigation-tree.deleting': schemaExtra.stringedBoolean().default(true),
|
||||
});
|
||||
|
||||
export type NavTreeSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class NavTreeSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
get childrenLimit(): number {
|
||||
return this.settings.getValue('core.navigation-tree.childrenLimit');
|
||||
}
|
||||
get editing(): boolean {
|
||||
return this.settings.getValue('core.navigation-tree.editing');
|
||||
}
|
||||
get deleting(): boolean {
|
||||
return this.settings.getValue('core.navigation-tree.deleting');
|
||||
}
|
||||
readonly settings: SettingsProvider<typeof settingsSchema>;
|
||||
|
||||
constructor(
|
||||
private readonly pluginManagerService: PluginManagerService,
|
||||
private readonly settingsProviderService: SettingsProviderService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly serverSettingsService: ServerSettingsService,
|
||||
private readonly serverSettingsResolverService: ServerSettingsResolverService,
|
||||
private readonly settingsResolverService: SettingsResolverService,
|
||||
) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('navigation-tree', 'core', settingsSchema);
|
||||
this.serverSettingsResolverService.addResolver(
|
||||
this.settings = this.settingsProviderService.createSettings(settingsSchema);
|
||||
this.settingsResolverService.addResolver(
|
||||
ROOT_SETTINGS_LAYER,
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
createSettingsAliasResolver(this.serverSettingsService, this.settings, 'core.app.navigationTree'),
|
||||
createSettingsAliasResolver(this.serverSettingsService, this.settings, 'core.app.metadata'),
|
||||
createSettingsAliasResolver(this.settingsResolverService, this.settings, {
|
||||
'core.navigation-tree.childrenLimit': 'core.app.navigationTree.childrenLimit',
|
||||
}),
|
||||
createSettingsAliasResolver(this.settingsResolverService, this.settings, {
|
||||
'core.navigation-tree.deleting': 'core.app.metadata.deleting',
|
||||
'core.navigation-tree.editing': 'core.app.metadata.editing',
|
||||
}),
|
||||
);
|
||||
|
||||
this.registerSettings();
|
||||
@@ -42,8 +62,11 @@ export class NavTreeSettingsService extends Dependency {
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
// {
|
||||
// group: NAVIGATION_TREE_SETTINGS_GROUP,
|
||||
// key: 'childrenLimit',
|
||||
// access: {
|
||||
// accessor: ['server'],
|
||||
// },
|
||||
// group: NAVIGATION_TREE_SETTINGS_GROUP,
|
||||
// name: 'Children limit',
|
||||
// type: ESettingsValueType.Input,
|
||||
// },
|
||||
|
||||
@@ -65,7 +65,7 @@ export class NavTreeResource extends CachedMapResource<string, string[], Record<
|
||||
readonly onNodeMove: IExecutor<INavNodeMoveData>;
|
||||
|
||||
get childrenLimit(): number {
|
||||
return this.navTreeSettingsService.settings.getValue('childrenLimit');
|
||||
return this.navTreeSettingsService.childrenLimit;
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-app/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-authentication/tsconfig.json"
|
||||
},
|
||||
@@ -42,15 +39,6 @@
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-projects/tsconfig.json"
|
||||
},
|
||||
@@ -79,7 +67,10 @@
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-storage/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-ui/tsconfig.json"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ProductSettingsService } from '@cloudbeaver/core-product';
|
||||
import type { SettingsScopeType } from '@cloudbeaver/core-settings';
|
||||
import type { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { PluginSettings } from './PluginSettings';
|
||||
|
||||
@injectable()
|
||||
export class PluginManagerService {
|
||||
readonly store: Map<string, PluginSettings>;
|
||||
constructor(private readonly productSettingsService: ProductSettingsService) {
|
||||
this.store = new Map();
|
||||
}
|
||||
|
||||
createSettings<TSchema extends schema.SomeZodObject = schema.AnyZodObject>(scope: string, scopeType: SettingsScopeType, schema: TSchema) {
|
||||
const key = scopeType + '.' + scope;
|
||||
const settings = new PluginSettings(this.productSettingsService, key, schema);
|
||||
|
||||
this.store.set(key, settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
getSettings(id: string): PluginSettings<any> | undefined;
|
||||
getSettings(scope: string, scopeType: SettingsScopeType): PluginSettings<any> | undefined;
|
||||
getSettings(scope: string, scopeType?: SettingsScopeType): PluginSettings<any> | undefined {
|
||||
if (scopeType === undefined) {
|
||||
return this.store.get(scope);
|
||||
}
|
||||
return this.store.get(scopeType + '.' + scope);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 '@cloudbeaver/core-settings';
|
||||
import type { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
export class PluginSettings<TSchema extends schema.SomeZodObject = schema.AnyZodObject> implements ISettingsSource {
|
||||
constructor(private readonly source: ISettingsSource, readonly scope: string, readonly schema: TSchema) {}
|
||||
|
||||
isReadOnly<TKey extends keyof schema.infer<TSchema>>(key: TKey): boolean {
|
||||
return this.source.has(key) || false;
|
||||
}
|
||||
|
||||
has<TKey extends keyof schema.infer<TSchema>>(key: TKey): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isValueDefault<TKey extends keyof schema.infer<TSchema>>(key: TKey): boolean {
|
||||
return !this.source.has(this.scopedKey(key));
|
||||
}
|
||||
|
||||
getDefaultValue<TKey extends keyof schema.infer<TSchema>>(key: TKey): schema.infer<TSchema>[TKey] {
|
||||
const value = this.source.getDefaultValue(this.scopedKey(key));
|
||||
const schema = this.schema.shape[key as any];
|
||||
const result = schema.safeParse(value);
|
||||
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
return schema.parse(undefined);
|
||||
}
|
||||
|
||||
getValue<TKey extends keyof schema.infer<TSchema>>(key: TKey): schema.infer<TSchema>[TKey] {
|
||||
let value = undefined;
|
||||
|
||||
if (this.source.has(this.scopedKey(key))) {
|
||||
value = this.source.getValue(this.scopedKey(key));
|
||||
}
|
||||
|
||||
const schema = this.schema.shape[key as any];
|
||||
const result = schema.safeParse(value);
|
||||
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
return schema.parse(undefined);
|
||||
}
|
||||
|
||||
setValue<TKey extends keyof schema.infer<TSchema>>(key: TKey, value: schema.infer<TSchema>[TKey]): void {
|
||||
throw new Error(`Can't set value for key ${String(this.scopedKey(key))}`);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
throw new Error("Can't clear settings");
|
||||
}
|
||||
|
||||
private scopedKey(key: string | number | symbol): string {
|
||||
return `${this.scope}.${String(key)}`;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
export class SettingsGroup {
|
||||
readonly id: string;
|
||||
get subGroups(): ReadonlyArray<SettingsGroup> {
|
||||
return this.#subGroups;
|
||||
}
|
||||
|
||||
protected groups: Map<string, SettingsGroup>;
|
||||
#subGroups: SettingsGroup[];
|
||||
|
||||
constructor(readonly name: string, readonly parent?: SettingsGroup) {
|
||||
this.id = uuid();
|
||||
this.#subGroups = [];
|
||||
this.groups = parent?.groups || new Map();
|
||||
|
||||
this.groups.set(this.id, this);
|
||||
}
|
||||
|
||||
get(id: string): SettingsGroup | undefined {
|
||||
return this.groups.get(id);
|
||||
}
|
||||
|
||||
createSubGroup(name: string): SettingsGroup {
|
||||
const subGroup = new SettingsGroup(name, this);
|
||||
|
||||
this.#subGroups.push(subGroup);
|
||||
|
||||
return subGroup;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import type { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { PluginSettings } from '../PluginSettings';
|
||||
import type { ISettingDescriptionWithScope, SettingsDescriptionGetter } from './ISettingDescription';
|
||||
|
||||
interface ScopeSettingsItem<T = any> {
|
||||
scope: string;
|
||||
schema: schema.AnyZodObject;
|
||||
settingsGetter: SettingsDescriptionGetter<T>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsManagerService {
|
||||
get settings(): ReadonlyArray<ScopeSettingsItem> {
|
||||
return this.#settings;
|
||||
}
|
||||
|
||||
#settings: ScopeSettingsItem[];
|
||||
|
||||
constructor() {
|
||||
this.#settings = [];
|
||||
}
|
||||
|
||||
registerSettings<TSchema extends schema.SomeZodObject>(
|
||||
settingsSource: PluginSettings<TSchema>,
|
||||
settingsGetter: SettingsDescriptionGetter<schema.infer<TSchema>>,
|
||||
) {
|
||||
this.#settings.push({
|
||||
scope: settingsSource.scope,
|
||||
schema: settingsSource.schema,
|
||||
settingsGetter,
|
||||
});
|
||||
}
|
||||
|
||||
getSettings(): ISettingDescriptionWithScope<any>[] {
|
||||
const settings: ISettingDescriptionWithScope<any>[] = [];
|
||||
|
||||
for (const { schema, scope, settingsGetter } of this.settings) {
|
||||
for (const setting of settingsGetter()) {
|
||||
settings.push({ ...setting, scope, schema: schema.shape[setting.key] });
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 '@cloudbeaver/core-settings';
|
||||
import type { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { PluginSettings } from './PluginSettings';
|
||||
|
||||
type SettingsMapping<TTarget, TSource> = {
|
||||
[key in keyof TTarget]: keyof TSource;
|
||||
};
|
||||
|
||||
export function createSettingsAliasResolver<TSource extends schema.SomeZodObject, TTarget extends schema.SomeZodObject = TSource>(
|
||||
source: ISettingsSource,
|
||||
target: PluginSettings<TTarget>,
|
||||
scope: string,
|
||||
mappings?: SettingsMapping<schema.infer<TTarget>, schema.infer<TSource>>,
|
||||
): ISettingsSource {
|
||||
type targetSchema = schema.infer<TTarget>;
|
||||
type sourceSchema = schema.infer<TSource>;
|
||||
|
||||
function isApplicable(key: keyof targetSchema): boolean {
|
||||
return String(key).startsWith(target.scope + '.');
|
||||
}
|
||||
function unscopeKey(key: keyof targetSchema): keyof targetSchema {
|
||||
return String(key).replace(target.scope + '.', '') as unknown as keyof targetSchema;
|
||||
}
|
||||
function scopeKey(key: keyof sourceSchema): keyof sourceSchema {
|
||||
return `${scope}.${String(key)}` as unknown as keyof sourceSchema;
|
||||
}
|
||||
function mapKey(key: keyof targetSchema): keyof sourceSchema {
|
||||
key = unscopeKey(key);
|
||||
|
||||
return scopeKey(mappings?.[key] || (key as unknown as keyof sourceSchema));
|
||||
}
|
||||
return {
|
||||
has(key) {
|
||||
if (!isApplicable(key)) {
|
||||
return false;
|
||||
}
|
||||
return source.has(mapKey(key));
|
||||
},
|
||||
isReadOnly(key) {
|
||||
if (!isApplicable(key)) {
|
||||
return true;
|
||||
}
|
||||
return source.isReadOnly(mapKey(key));
|
||||
},
|
||||
getDefaultValue(key) {
|
||||
if (!isApplicable(key)) {
|
||||
return undefined;
|
||||
}
|
||||
return source.getDefaultValue(mapKey(key));
|
||||
},
|
||||
getValue(key) {
|
||||
if (!isApplicable(key)) {
|
||||
return undefined;
|
||||
}
|
||||
return source.getValue(mapKey(key));
|
||||
},
|
||||
setValue(key, value) {
|
||||
if (!isApplicable(key)) {
|
||||
return;
|
||||
}
|
||||
source.setValue(mapKey(key), value);
|
||||
},
|
||||
clear() {
|
||||
source.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export * from './SettingsManager/ESettingsValueType';
|
||||
export * from './SettingsManager/ISettingDescription';
|
||||
export * from './SettingsManager/SettingsGroup';
|
||||
export * from './SettingsManager/SettingsManagerService';
|
||||
export * from './SettingsManager/SettingsGroups/INTERFACE_SETTINGS_GROUP';
|
||||
export * from './SettingsManager/SettingsGroups/ROOT_SETTINGS_GROUP';
|
||||
export * from './PluginSettings';
|
||||
export * from './PluginManagerService';
|
||||
export * from './createDeprecatedResolver';
|
||||
export * from './manifest';
|
||||
@@ -18,7 +18,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { parseJSONFlat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ProductSettingsService } from './ProductSettingsService';
|
||||
|
||||
@injectable()
|
||||
export class ProductManagerService {
|
||||
constructor(private readonly productSettingsService: ProductSettingsService) {}
|
||||
|
||||
setSettings(object: any) {
|
||||
parseJSONFlat(object, this.productSettingsService.setValue.bind(this.productSettingsService), undefined);
|
||||
}
|
||||
}
|
||||
@@ -5,47 +5,64 @@
|
||||
* 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 { action, makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SessionSettingsService } from '@cloudbeaver/core-root';
|
||||
import type { ISettingsSource } from '@cloudbeaver/core-settings';
|
||||
import { createSettingsLayer, ROOT_SETTINGS_LAYER, SettingsSource } from '@cloudbeaver/core-settings';
|
||||
|
||||
export const PRODUCT_SETTINGS_LAYER = createSettingsLayer(ROOT_SETTINGS_LAYER, 'product');
|
||||
|
||||
@injectable()
|
||||
export class ProductSettingsService implements ISettingsSource {
|
||||
export class ProductSettingsService extends SettingsSource {
|
||||
private readonly settings: Map<string, any>;
|
||||
|
||||
constructor(private readonly sessionSettingsService: SessionSettingsService) {
|
||||
constructor() {
|
||||
super();
|
||||
this.settings = new Map();
|
||||
makeObservable<this, 'settings'>(this, {
|
||||
settings: observable,
|
||||
clear: action,
|
||||
settings: observable.shallow,
|
||||
});
|
||||
}
|
||||
|
||||
has(key: any): boolean {
|
||||
return this.settings.has(key) || this.sessionSettingsService?.has(key) || false;
|
||||
return this.settings.has(key) || super.has(key) || false;
|
||||
}
|
||||
|
||||
isReadOnly(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
getDefaultValue(key: any): any {
|
||||
return this.sessionSettingsService.getDefaultValue(key) ?? this.settings.get(key);
|
||||
}
|
||||
|
||||
getValue(key: any): any {
|
||||
if (this.sessionSettingsService.has(key)) {
|
||||
return this.sessionSettingsService.getValue(key);
|
||||
}
|
||||
return this.settings.get(key);
|
||||
}
|
||||
|
||||
setValue(key: any, value: any): void {
|
||||
this.settings.set(key, value);
|
||||
this.update(() => {
|
||||
this.settings.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
setSettingsObject(settings: Record<string, any>): void {
|
||||
this.update(() => {
|
||||
if (settings && typeof settings === 'object') {
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
this.setValue(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.settings.clear();
|
||||
this.update(() => {
|
||||
super.clear();
|
||||
this.settings.clear();
|
||||
});
|
||||
}
|
||||
|
||||
async save(): Promise<void> {}
|
||||
|
||||
protected getSnapshot(): Record<string, any> {
|
||||
return Object.fromEntries(this.settings.entries());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './ProductManagerService';
|
||||
export * from './ProductSettingsService';
|
||||
export * from './manifest';
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { ProductManagerService } from './ProductManagerService';
|
||||
import { ProductSettingsService } from './ProductSettingsService';
|
||||
|
||||
export const coreProductManifest: PluginManifest = {
|
||||
@@ -15,5 +14,5 @@ export const coreProductManifest: PluginManifest = {
|
||||
name: 'Core Product',
|
||||
},
|
||||
|
||||
providers: [ProductManagerService, ProductSettingsService],
|
||||
providers: [ProductSettingsService],
|
||||
};
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-navigation-tree": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-projects": "~0.1.0",
|
||||
"@cloudbeaver/core-resource": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user