mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-3703 save product configuration from ui (#2334)
* CB-4560 api for saving product configuration * CB-4560 save product config in te * CB-4560 move update product config to mutations * CB-3703 feat: extend base components * CB-3703 feat: settings editor * CB-3703 chore: review fixes * CB-3703 chore: pr review * CB-3703 feat: update server configuration icon * CB-4560 add events * CB-3703 fix: localization * CB-3703 chore: localization --------- Co-authored-by: Alexey <wrouds@gmail.com> Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Alexey
Evgenia Bezborodova
parent
11adc7d3d6
commit
fdde4348a2
@@ -895,11 +895,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
}
|
||||
}
|
||||
|
||||
String sessionId = null;
|
||||
if (credentialsProvider != null && credentialsProvider.getActiveUserCredentials() != null) {
|
||||
sessionId = credentialsProvider.getActiveUserCredentials().getSmSessionId();
|
||||
}
|
||||
eventController.addEvent(new WSServerConfigurationChangedEvent(sessionId, null));
|
||||
sendConfigChangedEvent(credentialsProvider);
|
||||
eventController.setForceSkipEvents(isConfigurationMode());
|
||||
}
|
||||
|
||||
@@ -992,11 +988,10 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
sessionExpireTime,
|
||||
appConfig);
|
||||
validateConfiguration(configurationProperties);
|
||||
writeRuntimeConfig(configurationProperties);
|
||||
writeRuntimeConfig(getRuntimeAppConfigFile(), configurationProperties);
|
||||
}
|
||||
|
||||
private void writeRuntimeConfig(Map<String, Object> configurationProperties) throws DBException {
|
||||
File runtimeConfigFile = getRuntimeAppConfigFile();
|
||||
private void writeRuntimeConfig(File runtimeConfigFile, Map<String, Object> configurationProperties) throws DBException {
|
||||
if (runtimeConfigFile.exists()) {
|
||||
ContentUtils.makeFileBackup(runtimeConfigFile.toPath());
|
||||
}
|
||||
@@ -1024,33 +1019,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
var serverConfigProperties = new LinkedHashMap<String, Object>();
|
||||
var originServerConfig = getServerConfigProps(this.originalConfigurationProperties); // get server properties from original configuration file
|
||||
rootConfig.put("server", serverConfigProperties);
|
||||
if (!CommonUtils.isEmpty(newServerName)) {
|
||||
copyConfigValue(originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_SERVER_NAME,
|
||||
newServerName);
|
||||
}
|
||||
if (!CommonUtils.isEmpty(newServerURL)) {
|
||||
copyConfigValue(
|
||||
originServerConfig, serverConfigProperties, CBConstants.PARAM_SERVER_URL, newServerURL);
|
||||
}
|
||||
if (sessionExpireTime > 0) {
|
||||
copyConfigValue(
|
||||
originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_SESSION_EXPIRE_PERIOD,
|
||||
sessionExpireTime);
|
||||
}
|
||||
var databaseConfigProperties = new LinkedHashMap<String, Object>();
|
||||
Map<String, Object> oldRuntimeDBConfig = JSONUtils.getObject(originServerConfig,
|
||||
CBConstants.PARAM_DB_CONFIGURATION);
|
||||
if (!CommonUtils.isEmpty(databaseConfiguration) && !isDistributed()) {
|
||||
for (Map.Entry<String, Object> mp : databaseConfiguration.entrySet()) {
|
||||
copyConfigValue(oldRuntimeDBConfig, databaseConfigProperties, mp.getKey(), mp.getValue());
|
||||
}
|
||||
serverConfigProperties.put(CBConstants.PARAM_DB_CONFIGURATION, databaseConfigProperties);
|
||||
}
|
||||
savePasswordPolicyConfig(originServerConfig, serverConfigProperties);
|
||||
collectServerConfigProperties(newServerName, newServerURL, sessionExpireTime, originServerConfig, serverConfigProperties);
|
||||
}
|
||||
{
|
||||
var appConfigProperties = new LinkedHashMap<String, Object>();
|
||||
@@ -1160,7 +1129,43 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
return rootConfig;
|
||||
}
|
||||
|
||||
private void savePasswordPolicyConfig(Map<String, Object> originServerConfig, LinkedHashMap<String, Object> serverConfigProperties) {
|
||||
protected void collectServerConfigProperties(
|
||||
String newServerName,
|
||||
String newServerURL,
|
||||
long sessionExpireTime,
|
||||
Map<String, Object> originServerConfig,
|
||||
Map<String, Object> serverConfigProperties
|
||||
) {
|
||||
if (!CommonUtils.isEmpty(newServerName)) {
|
||||
copyConfigValue(originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_SERVER_NAME,
|
||||
newServerName);
|
||||
}
|
||||
if (!CommonUtils.isEmpty(newServerURL)) {
|
||||
copyConfigValue(
|
||||
originServerConfig, serverConfigProperties, CBConstants.PARAM_SERVER_URL, newServerURL);
|
||||
}
|
||||
if (sessionExpireTime > 0) {
|
||||
copyConfigValue(
|
||||
originServerConfig,
|
||||
serverConfigProperties,
|
||||
CBConstants.PARAM_SESSION_EXPIRE_PERIOD,
|
||||
sessionExpireTime);
|
||||
}
|
||||
var databaseConfigProperties = new LinkedHashMap<String, Object>();
|
||||
Map<String, Object> oldRuntimeDBConfig = JSONUtils.getObject(originServerConfig,
|
||||
CBConstants.PARAM_DB_CONFIGURATION);
|
||||
if (!CommonUtils.isEmpty(databaseConfiguration) && !isDistributed()) {
|
||||
for (Map.Entry<String, Object> mp : databaseConfiguration.entrySet()) {
|
||||
copyConfigValue(oldRuntimeDBConfig, databaseConfigProperties, mp.getKey(), mp.getValue());
|
||||
}
|
||||
serverConfigProperties.put(CBConstants.PARAM_DB_CONFIGURATION, databaseConfigProperties);
|
||||
}
|
||||
savePasswordPolicyConfig(originServerConfig, serverConfigProperties);
|
||||
}
|
||||
|
||||
private void savePasswordPolicyConfig(Map<String, Object> originServerConfig, Map<String, Object> serverConfigProperties) {
|
||||
// save password policy configuration
|
||||
var passwordPolicyProperties = new LinkedHashMap<String, Object>();
|
||||
|
||||
@@ -1297,4 +1302,19 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
public Class<? extends DBPPlatformUI> getPlatformUIClass() {
|
||||
return CBPlatformUI.class;
|
||||
}
|
||||
|
||||
public void saveProductConfiguration(SMCredentialsProvider credentialsProvider, Map<String, Object> productConfiguration) throws DBException {
|
||||
Map<String, Object> mergedConfig = WebAppUtils.mergeConfigurations(this.productConfiguration, productConfiguration);
|
||||
writeRuntimeConfig(getRuntimeProductConfigFilePath().toFile(), mergedConfig);
|
||||
this.productConfiguration.putAll(mergedConfig);
|
||||
sendConfigChangedEvent(credentialsProvider);
|
||||
}
|
||||
|
||||
protected void sendConfigChangedEvent(SMCredentialsProvider credentialsProvider) {
|
||||
String sessionId = null;
|
||||
if (credentialsProvider != null && credentialsProvider.getActiveUserCredentials() != null) {
|
||||
sessionId = credentialsProvider.getActiveUserCredentials().getSmSessionId();
|
||||
}
|
||||
eventController.addEvent(new WSServerConfigurationChangedEvent(sessionId, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,3 +206,8 @@ extend type Query {
|
||||
setDefaultNavigatorSettings( settings: NavigatorSettingsInput!): Boolean!
|
||||
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
# Updates product configuration
|
||||
adminUpdateProductConfiguration(configuration: Object!): Boolean! @since(version: "23.3.4")
|
||||
}
|
||||
|
||||
+5
-1
@@ -138,6 +138,9 @@ public interface DBWServiceAdmin extends DBWService {
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
boolean setDefaultNavigatorSettings(WebSession webSession, DBNBrowseSettings settings) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
boolean updateProductConfiguration(WebSession webSession, Map<String, Object> productConfiguration) throws DBWebException;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Permissions
|
||||
|
||||
@@ -179,6 +182,7 @@ public interface DBWServiceAdmin extends DBWService {
|
||||
DBWebException;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
// User meta parameters
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
@@ -187,11 +191,11 @@ public interface DBWServiceAdmin extends DBWService {
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
Boolean deleteUserMetaParameter(WebSession webSession, String id) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
Boolean setUserMetaParameterValues(WebSession webSession, String userId, Map<String, String> parameters) throws DBWebException;
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
Boolean setTeamMetaParameterValues(WebSession webSession, String teamId, Map<String, String> parameters) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
Boolean enableUser(WebSession webSession, String userId, Boolean enabled) throws DBWebException;
|
||||
|
||||
|
||||
+3
@@ -185,6 +185,9 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
|
||||
.dataFetcher("setDefaultNavigatorSettings",
|
||||
env -> getService(env).setDefaultNavigatorSettings(getWebSession(env), WebServiceUtils.parseNavigatorSettings(env.getArgument("settings"))))
|
||||
;
|
||||
model.getMutationType()
|
||||
.dataFetcher("adminUpdateProductConfiguration",
|
||||
env -> getService(env).updateProductConfiguration(getWebSession(env), env.getArgument("configuration")));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+11
@@ -609,6 +609,17 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean updateProductConfiguration(WebSession webSession, Map<String, Object> productConfiguration) throws DBWebException {
|
||||
try {
|
||||
CBApplication.getInstance().saveProductConfiguration(webSession, productConfiguration);
|
||||
return true;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error updating product configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Access management
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M9.5,6a2.47,2.47,0,0,0-1.77.71,2.38,2.38,0,0,0,0,3.43,2.46,2.46,0,0,0,1.77.7,2.46,2.46,0,0,0,1.77-.7,2.38,2.38,0,0,0,0-3.43A2.47,2.47,0,0,0,9.5,6Zm-6-4h25a1.49,1.49,0,0,1,1.07.42,1.35,1.35,0,0,1,.43,1V13.56a1.35,1.35,0,0,1-.43,1A1.49,1.49,0,0,1,28.5,15H3.5a1.49,1.49,0,0,1-1.07-.42,1.35,1.35,0,0,1-.43-1V3.44a1.35,1.35,0,0,1,.43-1A1.49,1.49,0,0,1,3.5,2ZM5,5v7H27V5ZM5,5" fill="#ee830c"/><path d="M7.73,21.71a2.38,2.38,0,0,0,0,3.43,2.46,2.46,0,0,0,1.77.7,2.46,2.46,0,0,0,1.77-.7,2.38,2.38,0,0,0,0-3.43,2.56,2.56,0,0,0-3.54,0Z" fill="#ee830c"/><path d="M16.59,27H5V20H17.08a8,8,0,0,1,3.05-3H3.5a1.49,1.49,0,0,0-1.07.42,1.35,1.35,0,0,0-.43,1V28.56a1.35,1.35,0,0,0,.43,1A1.49,1.49,0,0,0,3.5,30H18.72A8,8,0,0,1,16.59,27Z" fill="#ee830c"/><path d="M23.08,30l-.21-1.41a6.19,6.19,0,0,1-1.24-.52,4.67,4.67,0,0,1-1.06-.77l-1.18.25a1.1,1.1,0,0,1-1.21-.52l0,0a1.12,1.12,0,0,1,.2-1.4l1-1a5.82,5.82,0,0,1,0-1.8l-1-1a1.12,1.12,0,0,1-.2-1.4l0,0A1.13,1.13,0,0,1,19.39,20l1.18.26a4.41,4.41,0,0,1,1.06-.78,6.83,6.83,0,0,1,1.24-.52l.21-1.41a1.13,1.13,0,0,1,1.11-1h0a1.13,1.13,0,0,1,1.12,1l.21,1.41a6.83,6.83,0,0,1,1.24.52,4.62,4.62,0,0,1,1.06.78L29,20a1.14,1.14,0,0,1,1.21.52l0,0a1.12,1.12,0,0,1-.2,1.4l-1,1a5.82,5.82,0,0,1,0,1.8l1,1a1.12,1.12,0,0,1,.2,1.4l0,0a1.11,1.11,0,0,1-1.21.52l-1.18-.25a4.91,4.91,0,0,1-1.06.77,6.19,6.19,0,0,1-1.24.52L25.31,30a1.13,1.13,0,0,1-1.12,1h0A1.13,1.13,0,0,1,23.08,30Zm1.11-3.39a2.71,2.71,0,0,0,2.88-2.87,2.84,2.84,0,0,0-.79-2.08,2.81,2.81,0,0,0-2.09-.79,2.87,2.87,0,1,0,0,5.74Z" fill="#ee830c"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
+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 { LocalStorageSaveService } from '@cloudbeaver/core-settings';
|
||||
import { SettingsService } from '@cloudbeaver/core-settings';
|
||||
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 autoSaveService: LocalStorageSaveService,
|
||||
private readonly settingsService: SettingsService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly notificationService: NotificationService,
|
||||
) {
|
||||
@@ -84,8 +84,8 @@ export class AdministrationScreenService {
|
||||
activeScreen: computed,
|
||||
});
|
||||
|
||||
this.autoSaveService.withAutoSave(ADMINISTRATION_ITEMS_STATE, this.itemState, () => new Map());
|
||||
this.autoSaveService.withAutoSave(ADMINISTRATION_INFO, this.info, getDefaultAdministrationScreenInfo);
|
||||
this.settingsService.registerSettings(ADMINISTRATION_ITEMS_STATE, this.itemState, () => new Map());
|
||||
this.settingsService.registerSettings(ADMINISTRATION_INFO, this.info, getDefaultAdministrationScreenInfo);
|
||||
this.permissionsResource.onDataUpdate.addPostHandler(() => {
|
||||
this.checkPermissions(this.screenService.routerService.state);
|
||||
});
|
||||
|
||||
@@ -7,19 +7,20 @@
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
const defaultSettings = {
|
||||
baseFeatures: [] as string[],
|
||||
};
|
||||
const settingsSchema = schema.object({
|
||||
baseFeatures: schema.array(schema.string()).default([]),
|
||||
});
|
||||
|
||||
export type AdministrationSettings = typeof defaultSettings;
|
||||
export type AdministrationSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class AdministrationSettingsService {
|
||||
readonly settings: PluginSettings<AdministrationSettings>;
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('administration', 'core', defaultSettings);
|
||||
this.settings = this.pluginManagerService.createSettings('administration', 'core', settingsSchema);
|
||||
}
|
||||
|
||||
isBase(feature: string): boolean {
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-data-context/tsconfig.json"
|
||||
},
|
||||
@@ -15,27 +18,48 @@
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-routing/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
@@ -45,30 +69,6 @@
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../tests-runner/tsconfig.json"
|
||||
}
|
||||
|
||||
@@ -22,17 +22,16 @@
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-notifications": "~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-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"@cloudbeaver/core-version": "~0.1.0",
|
||||
"mobx-react-lite": "^4.0.5",
|
||||
"react": "^18.2.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,33 +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 { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
|
||||
const defaultSettings = {
|
||||
'app.logViewer.refreshTimeout': 3000,
|
||||
'app.logViewer.maxLogRecords': 1000,
|
||||
'app.logViewer.logBatchSize': 2000,
|
||||
'app.logViewer.maxFailedRequests': 3,
|
||||
'app.navigationTree.childrenLimit': 100,
|
||||
'app.metadata.editing': true,
|
||||
'app.metadata.deleting': true,
|
||||
};
|
||||
|
||||
export type CoreSettings = typeof defaultSettings;
|
||||
|
||||
/**
|
||||
* @deprecated use plugin-log-viewer, core-navigation-tree settings instead
|
||||
*/
|
||||
@injectable()
|
||||
export class CoreSettingsService {
|
||||
readonly settings: PluginSettings<CoreSettings>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService) {
|
||||
this.settings = this.pluginManagerService.getDeprecatedPluginSettings('core', defaultSettings);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
// Services
|
||||
export * from './CoreSettingsService';
|
||||
export * from './AppScreen/AppScreenService';
|
||||
export * from './AppScreen/AppScreenBootstrap';
|
||||
|
||||
|
||||
@@ -10,12 +10,11 @@ import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
import { AppLocaleService } from './AppLocaleService';
|
||||
import { AppScreenBootstrap } from './AppScreen/AppScreenBootstrap';
|
||||
import { AppScreenService } from './AppScreen/AppScreenService';
|
||||
import { CoreSettingsService } from './CoreSettingsService';
|
||||
|
||||
export const coreAppManifest: PluginManifest = {
|
||||
info: {
|
||||
name: 'Core App',
|
||||
},
|
||||
|
||||
providers: [AppScreenService, AppScreenBootstrap, CoreSettingsService, AppLocaleService],
|
||||
providers: [AppScreenService, AppScreenBootstrap, AppLocaleService],
|
||||
};
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
{
|
||||
"path": "../core-notifications/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-projects/tsconfig.json"
|
||||
},
|
||||
@@ -43,10 +40,10 @@
|
||||
"path": "../core-ui/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-version/tsconfig.json"
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
"path": "../core-version/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": [
|
||||
|
||||
@@ -5,21 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { createSettingsGroup, SettingsData } from '@cloudbeaver/core-settings';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
|
||||
export const AUTH_SETTINGS_GROUP = createSettingsGroup('settings_authentication');
|
||||
|
||||
export const settings: SettingsData = {
|
||||
scopeType: 'core',
|
||||
scope: 'authentication',
|
||||
settingsData: [
|
||||
// TODO: it's administrator settings
|
||||
// {
|
||||
// key: 'disableAnonymousAccess',
|
||||
// type: FormFieldType.Checkbox,
|
||||
// name: 'settings_authentication_disable_anonymous_access_name',
|
||||
// description: 'settings_authentication_disable_anonymous_access_description',
|
||||
// groupId: AUTH_SETTINGS_GROUP.id,
|
||||
// },
|
||||
],
|
||||
};
|
||||
export const AUTH_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_authentication_auth_settings_group');
|
||||
|
||||
@@ -6,25 +6,34 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
import { SettingsManagerService } from '@cloudbeaver/core-settings';
|
||||
import { PluginManagerService, PluginSettings, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { AUTH_SETTINGS_GROUP, settings } from './AUTH_SETTINGS_GROUP';
|
||||
const settingsSchema = schema.object({
|
||||
disableAnonymousAccess: schema.coerce.boolean().default(false),
|
||||
});
|
||||
|
||||
const defaultSettings = {
|
||||
disableAnonymousAccess: false,
|
||||
};
|
||||
|
||||
export type AuthSettings = typeof defaultSettings;
|
||||
export type AuthSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class AuthSettingsService {
|
||||
readonly settings: PluginSettings<AuthSettings>;
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService, settingsManagerService: SettingsManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('authentication', 'core', defaultSettings);
|
||||
constructor(private readonly pluginManagerService: PluginManagerService, private readonly settingsManagerService: SettingsManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('authentication', 'core', settingsSchema);
|
||||
|
||||
settingsManagerService.addGroup(AUTH_SETTINGS_GROUP);
|
||||
settingsManagerService.addSettings(settings.scopeType, settings.scope, settings.settingsData);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
// {
|
||||
// key: 'disableAnonymousAccess',
|
||||
// type: ESettingsValueType.Checkbox,
|
||||
// name: 'settings_authentication_disable_anonymous_access_name',
|
||||
// description: 'settings_authentication_disable_anonymous_access_description',
|
||||
// group: AUTH_SETTINGS_GROUP,
|
||||
// },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
|
||||
import { SettingsService } from '@cloudbeaver/core-settings';
|
||||
import { TempMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { UserInfoResource } from './UserInfoResource';
|
||||
@@ -18,7 +18,7 @@ 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 autoSaveService: LocalStorageSaveService) {
|
||||
constructor(private readonly userInfoResource: UserInfoResource, private readonly settingsService: SettingsService) {
|
||||
this.userData = new Map();
|
||||
|
||||
makeObservable<this, 'userData'>(this, {
|
||||
@@ -27,7 +27,7 @@ export class UserDataService {
|
||||
|
||||
this.tempData = new TempMap(this.userData);
|
||||
|
||||
this.autoSaveService.withAutoSave('user_data', this.userData, () => new Map());
|
||||
this.settingsService.registerSettings('user_data', this.userData, () => new Map());
|
||||
}
|
||||
|
||||
getUserData<T extends Record<any, any>>(key: string, defaultValue: () => T, validate?: (data: T) => boolean): T {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default [
|
||||
['settings_authentication', 'Authentication'],
|
||||
['core_authentication_auth_settings_group', 'Authentication'],
|
||||
['settings_authentication_disable_anonymous_access_name', 'Disable anonymous access'],
|
||||
['settings_authentication_disable_anonymous_access_description', 'Disable anonymous access function'],
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default [
|
||||
['settings_authentication', 'Authentication'],
|
||||
['core_authentication_auth_settings_group', 'Authentication'],
|
||||
['settings_authentication_disable_anonymous_access_name', 'Disable anonymous access'],
|
||||
['settings_authentication_disable_anonymous_access_description', 'Disable anonymous access function'],
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default [
|
||||
['settings_authentication', 'Аутентификация'],
|
||||
['core_authentication_auth_settings_group', 'Аутентификация'],
|
||||
['settings_authentication_disable_anonymous_access_name', 'Отключить анонимный доступ'],
|
||||
['settings_authentication_disable_anonymous_access_description', 'Отключить функцию анонимного доступа'],
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default [
|
||||
['settings_authentication', 'Authentication'],
|
||||
['core_authentication_auth_settings_group', 'Authentication'],
|
||||
['settings_authentication_disable_anonymous_access_name', 'Disable anonymous access'],
|
||||
['settings_authentication_disable_anonymous_access_description', 'Disable anonymous access function'],
|
||||
|
||||
|
||||
@@ -11,72 +11,72 @@
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-data-context/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-routing/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-routing/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-routing/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../tests-runner/tsconfig.json"
|
||||
}
|
||||
|
||||
@@ -17,4 +17,5 @@ export interface ILayoutSizeProps {
|
||||
fill?: boolean;
|
||||
noGrow?: boolean;
|
||||
zeroBasis?: boolean;
|
||||
groupGap?: boolean;
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
import type { ILayoutSizeProps } from './ILayoutSizeProps';
|
||||
|
||||
export function filterLayoutFakeProps<T extends ILayoutSizeProps>(props: T): Omit<T, keyof ILayoutSizeProps> {
|
||||
const { noWrap, keepSize, tiny, small, medium, large, maximum, fill, noGrow, zeroBasis, ...rest } = props;
|
||||
const { noWrap, keepSize, tiny, small, medium, large, maximum, fill, noGrow, zeroBasis, groupGap, ...rest } = props;
|
||||
|
||||
return rest;
|
||||
}
|
||||
|
||||
export function getLayoutProps<T extends ILayoutSizeProps>(props: T): ILayoutSizeProps {
|
||||
const { noWrap, keepSize, tiny, small, medium, large, maximum, fill, noGrow, zeroBasis } = props;
|
||||
return { noWrap, keepSize, tiny, small, medium, large, maximum, fill, noGrow, zeroBasis };
|
||||
const { noWrap, keepSize, tiny, small, medium, large, maximum, fill, noGrow, zeroBasis, groupGap } = props;
|
||||
return { noWrap, keepSize, tiny, small, medium, large, maximum, fill, noGrow, zeroBasis, groupGap };
|
||||
}
|
||||
|
||||
@@ -75,3 +75,15 @@
|
||||
.container > .zeroBasis {
|
||||
flex-basis: 0%;
|
||||
}
|
||||
|
||||
.container.gap > .groupGap + .groupGap {
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
.container.gap.compact > .groupGap + .groupGap {
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
.container.gap.dense > .groupGap + .groupGap {
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import { observer } from 'mobx-react-lite';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { filterLayoutFakeProps } from '../../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../../Containers/ILayoutSizeProps';
|
||||
import { isControlPresented } from '../isControlPresented';
|
||||
import { CheckboxMarkup, CheckboxMod } from './CheckboxMarkup';
|
||||
import { CheckboxOnChangeEvent, useCheckboxState } from './useCheckboxState';
|
||||
@@ -27,13 +25,12 @@ export interface CheckboxBaseProps {
|
||||
export type CheckboxInputProps = Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'type' | 'value' | 'defaultValue' | 'checked' | 'defaultChecked' | 'style'
|
||||
> &
|
||||
ILayoutSizeProps & {
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
defaultChecked?: boolean;
|
||||
label?: string;
|
||||
};
|
||||
> & {
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
defaultChecked?: boolean;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export interface ICheckboxControlledProps extends CheckboxInputProps {
|
||||
state?: never;
|
||||
@@ -50,9 +47,9 @@ export interface ICheckboxObjectProps<TKey extends string> extends CheckboxInput
|
||||
name: TKey;
|
||||
}
|
||||
|
||||
export interface CheckboxType {
|
||||
(props: CheckboxBaseProps & ICheckboxControlledProps): React.ReactElement<any, any> | null;
|
||||
<TKey extends string>(props: CheckboxBaseProps & ICheckboxObjectProps<TKey>): React.ReactElement<any, any> | null;
|
||||
export interface CheckboxType<P = NonNullable<unknown>> {
|
||||
(props: CheckboxBaseProps & ICheckboxControlledProps & P): React.ReactElement<any, any> | null;
|
||||
<TKey extends string>(props: CheckboxBaseProps & ICheckboxObjectProps<TKey> & P): React.ReactElement<any, any> | null;
|
||||
}
|
||||
|
||||
export const Checkbox: CheckboxType = observer(function Checkbox({
|
||||
@@ -72,7 +69,6 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
|
||||
onChange,
|
||||
...rest
|
||||
}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>)) {
|
||||
rest = filterLayoutFakeProps(rest);
|
||||
const checkboxState = useCheckboxState({
|
||||
value,
|
||||
defaultValue,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { filterLayoutFakeProps, getLayoutProps } from '../../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../../Containers/ILayoutSizeProps';
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import { Field } from '../Field';
|
||||
@@ -16,13 +17,13 @@ import { isControlPresented } from '../isControlPresented';
|
||||
import { Checkbox, CheckboxBaseProps, CheckboxType, ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox';
|
||||
import fieldCheckboxStyles from './FieldCheckbox.m.css';
|
||||
|
||||
export const FieldCheckbox: CheckboxType = observer(function FieldCheckbox({
|
||||
export const FieldCheckbox: CheckboxType<ILayoutSizeProps> = observer(function FieldCheckbox({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>)) {
|
||||
}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>) & ILayoutSizeProps) {
|
||||
const layoutProps = getLayoutProps(rest);
|
||||
const checkboxProps = filterLayoutFakeProps(rest);
|
||||
const checkboxProps = filterLayoutFakeProps(rest) as CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>);
|
||||
const styles = useS(fieldCheckboxStyles);
|
||||
|
||||
if (checkboxProps.autoHide && !isControlPresented(checkboxProps.name, checkboxProps.state)) {
|
||||
@@ -31,7 +32,7 @@ export const FieldCheckbox: CheckboxType = observer(function FieldCheckbox({
|
||||
|
||||
return (
|
||||
<Field {...layoutProps} className={s(styles, { field: true }, className)}>
|
||||
<Checkbox {...(checkboxProps as CheckboxBaseProps & ICheckboxControlledProps)} className={s(styles, { checkbox: true })} />
|
||||
<Checkbox {...(checkboxProps as any)} className={s(styles, { checkbox: true })} />
|
||||
{children && (
|
||||
<FieldLabel htmlFor={checkboxProps.id || checkboxProps.name} title={checkboxProps.title} className={s(styles, { fieldLabel: true })}>
|
||||
{children}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { filterLayoutFakeProps } from '../../Containers/filterLayoutFakeProps';
|
||||
import type { ILayoutSizeProps } from '../../Containers/ILayoutSizeProps';
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import { Field } from '../Field';
|
||||
@@ -32,8 +33,8 @@ interface IBaseProps {
|
||||
}
|
||||
|
||||
interface SwitchType {
|
||||
(props: IBaseProps & ICheckboxControlledProps): React.ReactElement<any, any> | null;
|
||||
<TKey extends string>(props: IBaseProps & ICheckboxObjectProps<TKey>): React.ReactElement<any, any> | null;
|
||||
(props: IBaseProps & ICheckboxControlledProps & ILayoutSizeProps): React.ReactElement<any, any> | null;
|
||||
<TKey extends string>(props: IBaseProps & ICheckboxObjectProps<TKey> & ILayoutSizeProps): React.ReactElement<any, any> | null;
|
||||
}
|
||||
|
||||
export const Switch: SwitchType = observer(function Switch({
|
||||
@@ -53,7 +54,7 @@ export const Switch: SwitchType = observer(function Switch({
|
||||
disabled,
|
||||
onChange,
|
||||
...rest
|
||||
}: IBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>)) {
|
||||
}: IBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>) & ILayoutSizeProps) {
|
||||
const checkboxState = useCheckboxState({
|
||||
value,
|
||||
defaultValue,
|
||||
|
||||
@@ -62,7 +62,9 @@ interface ComboboxType {
|
||||
<TValue, TKey extends keyof TState, TState>(props: ObjectProps<TValue, TKey, TState>): JSX.Element;
|
||||
}
|
||||
|
||||
{/* TODO rewrite whole component to select attribute instead of input type text so it has an okay form validation */}
|
||||
{
|
||||
/* TODO rewrite whole component to select attribute instead of input type text so it has an okay form validation */
|
||||
}
|
||||
export const Combobox: ComboboxType = observer(function Combobox({
|
||||
value: controlledValue,
|
||||
defaultValue,
|
||||
@@ -264,7 +266,7 @@ export const Combobox: ComboboxType = observer(function Combobox({
|
||||
</FieldLabel>
|
||||
)}
|
||||
<div className={s(styles, { inputBox: true })}>
|
||||
<input className={s(styles, { validationInput: true })} value={inputValue} required={rest.required} />
|
||||
<input className={s(styles, { validationInput: true })} value={inputValue} required={rest.required} readOnly />
|
||||
{(icon || loading) && (
|
||||
<div className={s(styles, { inputIcon: true })}>
|
||||
{loading ? (
|
||||
|
||||
@@ -48,4 +48,3 @@
|
||||
.input {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,38 @@ import { useContext, useEffect, useRef } from 'react';
|
||||
|
||||
import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
|
||||
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
import { useExecutor } from '../useExecutor';
|
||||
import { FormContext } from './FormContext';
|
||||
|
||||
export function useCustomInputValidation<T = void>(validation: (value: T) => string | null): React.RefObject<HTMLInputElement> {
|
||||
const context = useContext(FormContext);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const translate = useTranslate();
|
||||
|
||||
function validate(element: HTMLInputElement): boolean {
|
||||
let value: T = undefined as unknown as T;
|
||||
|
||||
if (element instanceof HTMLInputElement) {
|
||||
value = element.value as unknown as T;
|
||||
}
|
||||
|
||||
const valid = element.validity.valid;
|
||||
const result = validation(value);
|
||||
|
||||
try {
|
||||
if (typeof result === 'string') {
|
||||
element.setCustomValidity(result || translate('core_blocks_custom_input_validation_error'));
|
||||
return false;
|
||||
}
|
||||
element.setCustomValidity('');
|
||||
return true;
|
||||
} finally {
|
||||
if (valid !== element.validity.valid) {
|
||||
element.reportValidity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useExecutor({
|
||||
executor: context?.onValidate,
|
||||
@@ -24,19 +50,8 @@ export function useCustomInputValidation<T = void>(validation: (value: T) => str
|
||||
return;
|
||||
}
|
||||
|
||||
let value: T = undefined as unknown as T;
|
||||
|
||||
if (inputRef.current instanceof HTMLInputElement) {
|
||||
value = inputRef.current.value as unknown as T;
|
||||
}
|
||||
|
||||
const result = validation(value);
|
||||
|
||||
if (typeof result === 'string') {
|
||||
inputRef.current.setCustomValidity(result);
|
||||
if (!validate(inputRef.current)) {
|
||||
ExecutorInterrupter.interrupt(context);
|
||||
} else {
|
||||
inputRef.current?.setCustomValidity('');
|
||||
}
|
||||
},
|
||||
],
|
||||
@@ -48,14 +63,26 @@ export function useCustomInputValidation<T = void>(validation: (value: T) => str
|
||||
return;
|
||||
}
|
||||
|
||||
function resetValidationMessage() {
|
||||
element?.setCustomValidity('');
|
||||
function handleInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.validity.valid === false) {
|
||||
validate(target);
|
||||
}
|
||||
}
|
||||
|
||||
element.addEventListener('input', resetValidationMessage);
|
||||
function handleBlur(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.validity.valid === true) {
|
||||
validate(target);
|
||||
}
|
||||
}
|
||||
|
||||
element.addEventListener('input', handleInput);
|
||||
element.addEventListener('blur', handleBlur);
|
||||
|
||||
return () => {
|
||||
element?.removeEventListener('input', resetValidationMessage);
|
||||
element?.removeEventListener('input', handleInput);
|
||||
element?.removeEventListener('blur', handleBlur);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -5,26 +5,41 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
import { type ButtonHTMLAttributes, useState } from 'react';
|
||||
|
||||
import { IconOrImage } from '../IconOrImage';
|
||||
import { Loader } from '../Loader/Loader';
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import { useStateDelay } from '../useStateDelay';
|
||||
import style from './ToolsAction.m.css';
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<any> {
|
||||
interface Props extends Omit<ButtonHTMLAttributes<any>, 'onClick'> {
|
||||
icon?: string;
|
||||
viewBox?: string;
|
||||
loading?: boolean;
|
||||
className?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => Promise<any> | any;
|
||||
}
|
||||
|
||||
export const ToolsAction: React.FC<Props> = function ToolsAction({ icon, viewBox, loading, children, className, ...rest }) {
|
||||
export const ToolsAction: React.FC<Props> = function ToolsAction({ icon, viewBox, disabled, loading, children, className, onClick, ...rest }) {
|
||||
const styles = useS(style);
|
||||
const [loadingState, setLoadingState] = useState(false);
|
||||
|
||||
async function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
|
||||
try {
|
||||
setLoadingState(true);
|
||||
await onClick?.(event);
|
||||
} finally {
|
||||
setLoadingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
loading = useStateDelay(loading || loadingState, 300);
|
||||
disabled = disabled || loadingState;
|
||||
|
||||
return (
|
||||
<button type="button" {...rest} className={s(styles, { button: true }, className)}>
|
||||
<button type="button" disabled={disabled} onClick={handleClick} {...rest} className={s(styles, { button: true }, className)}>
|
||||
{loading && <Loader className={s(styles, { loader: true })} small />}
|
||||
{!loading && icon && <IconOrImage className={s(styles, { iconOrImage: true })} icon={icon} viewBox={viewBox} />}
|
||||
{children && <div className={s(styles, { buttonLabel: true })}>{children}</div>}
|
||||
|
||||
@@ -9,4 +9,5 @@ export default [
|
||||
['combobox_no_results_placeholder', 'No results'],
|
||||
['core_blocks_exception_message_error_title', 'Unable to display content'],
|
||||
['core_blocks_exception_message_error_message', 'Something went wrong.'],
|
||||
['core_blocks_custom_input_validation_error', 'Field value is not valid'],
|
||||
];
|
||||
|
||||
@@ -5,4 +5,5 @@ export default [
|
||||
['core_blocks_properties_table_item_reset', 'Reset to default'],
|
||||
['core_blocks_object_property_info_form_empty_placeholder', 'Properties empty'],
|
||||
['core_blocks_properties_table_item_remove', 'Remove property'],
|
||||
['core_blocks_custom_input_validation_error', 'Field value is not valid'],
|
||||
];
|
||||
|
||||
@@ -7,4 +7,5 @@ export default [
|
||||
['core_blocks_object_property_info_form_empty_placeholder', 'Параметры отсутствуют'],
|
||||
['core_blocks_properties_table_item_remove', 'Удалить параметр'],
|
||||
['combobox_no_results_placeholder', 'Нет совпадений'],
|
||||
['core_blocks_custom_input_validation_error', 'Значение не валидно'],
|
||||
];
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
{
|
||||
"path": "../core-authentication/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
@@ -18,33 +21,15 @@
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
@@ -54,9 +39,18 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
@@ -66,6 +60,12 @@
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-theming/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../tests-runner/tsconfig.json"
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-server-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-session-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-settings-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-version": "~0.1.0",
|
||||
|
||||
@@ -26,7 +26,10 @@ import { resourceManagerManifest } from '@cloudbeaver/core-resource-manager';
|
||||
import { coreRootManifest } from '@cloudbeaver/core-root';
|
||||
import { coreRoutingManifest } from '@cloudbeaver/core-routing';
|
||||
import { coreSDKManifest } from '@cloudbeaver/core-sdk';
|
||||
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 { coreThemingManifest } from '@cloudbeaver/core-theming';
|
||||
import { coreUIManifest } from '@cloudbeaver/core-ui';
|
||||
import { coreVersionManifest } from '@cloudbeaver/core-version';
|
||||
@@ -46,6 +49,9 @@ export const coreManifests: PluginManifest[] = [
|
||||
coreRoutingManifest, // important, should be first because the router starts in load phase first after all plugins register phase
|
||||
coreThemingManifest,
|
||||
coreLocalizationManifest,
|
||||
coreSettingsLocalizationManifest,
|
||||
coreSessionLocalization,
|
||||
coreServerLocalization,
|
||||
coreBlocksManifest,
|
||||
coreSettingsManifest,
|
||||
coreEventsManifest,
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
{
|
||||
"path": "../core-blocks/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser-cookies/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-connections/tsconfig.json"
|
||||
},
|
||||
@@ -58,10 +58,10 @@
|
||||
"path": "../core-projects/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
"path": "../core-resource-manager/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource-manager/tsconfig.json"
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
@@ -72,6 +72,15 @@
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-server-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-session-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
@@ -82,10 +91,10 @@
|
||||
"path": "../core-ui/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-version/tsconfig.json"
|
||||
"path": "../core-version-update/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-version-update/tsconfig.json"
|
||||
"path": "../core-version/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-view/tsconfig.json"
|
||||
|
||||
@@ -11,9 +11,6 @@ import { injectable } from '@cloudbeaver/core-di';
|
||||
@injectable()
|
||||
export class CookiesService {
|
||||
get cookiesEnabled() {
|
||||
if (this.browserSettingsService.settings.isValueDefault('cookies.disabled')) {
|
||||
return !this.browserSettingsService.deprecatedSettings.getValue('disabled');
|
||||
}
|
||||
return !this.browserSettingsService.settings.getValue('cookies.disabled');
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0"
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@cloudbeaver/core-browser": "~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/tests-runner": "~0.1.0",
|
||||
"@testing-library/jest-dom": "^6.1.3",
|
||||
"@types/jest": "^29.5.10",
|
||||
|
||||
+2
-6
@@ -5,10 +5,6 @@
|
||||
* 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';
|
||||
import { BROWSER_SETTINGS_GROUP } from './BROWSER_SETTINGS_GROUP';
|
||||
|
||||
import type { SettingsGroupType } from './SettingsManagerService';
|
||||
|
||||
export function createSettingsGroup(name: string): SettingsGroupType {
|
||||
return { name, id: uuid() };
|
||||
}
|
||||
export const BROWSER_COOKIES_SETTINGS_GROUP = BROWSER_SETTINGS_GROUP.createSubGroup('core_browser_settings_cookies_settings_group');
|
||||
+2
-5
@@ -5,9 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { createAction } from '@cloudbeaver/core-view';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
|
||||
export const ACTION_OPEN_APP_SETTINGS = createAction('open-app-settings', {
|
||||
label: 'settings_panel',
|
||||
tooltip: 'settings_panel',
|
||||
});
|
||||
export const BROWSER_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_browser_settings_browser_settings_group');
|
||||
@@ -7,17 +7,33 @@
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { coreBrowserManifest } from '@cloudbeaver/core-browser';
|
||||
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 { 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 { createApp } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import { BrowserSettingsService, CookiesSettings, DeprecatedCookiesSettings } from './BrowserSettingsService';
|
||||
import { BrowserSettingsService, CookiesSettings } from './BrowserSettingsService';
|
||||
import { coreBrowserSettingsManifest } from './manifest';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const app = createApp();
|
||||
const app = createApp(
|
||||
coreBrowserManifest,
|
||||
coreBrowserSettingsManifest,
|
||||
corePluginManifest,
|
||||
coreProductManifest,
|
||||
coreRootManifest,
|
||||
coreSDKManifest,
|
||||
coreSettingsManifest,
|
||||
coreLocalizationManifest,
|
||||
);
|
||||
|
||||
const server = mockGraphQL(...mockAppInit(endpoint));
|
||||
|
||||
@@ -30,7 +46,7 @@ const equalConfigA = {
|
||||
core: {
|
||||
cookies: {
|
||||
disabled: testValueA,
|
||||
} as DeprecatedCookiesSettings,
|
||||
},
|
||||
browser: {
|
||||
'cookies.disabled': testValueA,
|
||||
} as CookiesSettings,
|
||||
@@ -41,16 +57,11 @@ const equalConfigB = {
|
||||
core: {
|
||||
cookies: {
|
||||
disabled: testValueB,
|
||||
} as DeprecatedCookiesSettings,
|
||||
browser: {
|
||||
'cookies.disabled': testValueB,
|
||||
} as CookiesSettings,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: fails because of circular dependency:
|
||||
// BrowserSettingsService -> ... -> LocalStorageSaveService -> core-browser/IndexedDB
|
||||
test.skip('New settings equal deprecated settings A', async () => {
|
||||
test('New settings override deprecated settings', async () => {
|
||||
const settings = app.injector.getServiceByClass(BrowserSettingsService);
|
||||
const config = app.injector.getServiceByClass(ServerConfigResource);
|
||||
|
||||
@@ -59,10 +70,9 @@ test.skip('New settings equal deprecated settings A', async () => {
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('cookies.disabled')).toBe(testValueA);
|
||||
expect(settings.deprecatedSettings.getValue('disabled')).toBe(testValueA);
|
||||
});
|
||||
|
||||
test.skip('New settings equal deprecated settings B', async () => {
|
||||
test('New settings fall back to deprecated settings', async () => {
|
||||
const settings = app.injector.getServiceByClass(BrowserSettingsService);
|
||||
const config = app.injector.getServiceByClass(ServerConfigResource);
|
||||
|
||||
@@ -71,5 +81,4 @@ test.skip('New settings equal deprecated settings B', async () => {
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('cookies.disabled')).toBe(testValueB);
|
||||
expect(settings.deprecatedSettings.getValue('disabled')).toBe(testValueB);
|
||||
});
|
||||
|
||||
@@ -5,31 +5,45 @@
|
||||
* 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 { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
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';
|
||||
|
||||
const defaultSettings = {
|
||||
'cookies.disabled': false,
|
||||
};
|
||||
const settingsSchema = schema.object({
|
||||
'cookies.disabled': schema.coerce.boolean().default(false),
|
||||
});
|
||||
|
||||
/** @deprecated don't use */
|
||||
const deprecatedDefaultSettings = {
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export type CookiesSettings = typeof defaultSettings;
|
||||
|
||||
/** @deprecated don't use */
|
||||
export type DeprecatedCookiesSettings = typeof deprecatedDefaultSettings;
|
||||
export type CookiesSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class BrowserSettingsService {
|
||||
readonly settings: PluginSettings<CookiesSettings>;
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
readonly deprecatedSettings: PluginSettings<DeprecatedCookiesSettings>;
|
||||
export class BrowserSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('browser', 'core', defaultSettings);
|
||||
this.deprecatedSettings = this.pluginManagerService.createSettings('cookies', 'core', deprecatedDefaultSettings);
|
||||
constructor(
|
||||
private readonly pluginManagerService: PluginManagerService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly serverSettingsService: ServerSettingsService,
|
||||
private readonly serverSettingsResolverService: ServerSettingsResolverService,
|
||||
) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('browser', 'core', settingsSchema);
|
||||
|
||||
this.serverSettingsResolverService.addResolver(
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
createSettingsAliasResolver(this.serverSettingsService, this.settings, 'core'),
|
||||
);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
// {
|
||||
// group: BROWSER_COOKIES_SETTINGS_GROUP,
|
||||
// key: 'cookies.disabled',
|
||||
// name: 'Disable',
|
||||
// type: ESettingsValueType.Checkbox,
|
||||
// },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
|
||||
@injectable()
|
||||
export class LocaleService extends Bootstrap {
|
||||
constructor(private readonly localizationService: LocalizationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.localizationService.addProvider(this.provider.bind(this));
|
||||
}
|
||||
|
||||
load(): void | Promise<void> {}
|
||||
|
||||
private async provider(locale: string) {
|
||||
switch (locale) {
|
||||
case 'ru':
|
||||
return (await import('./locales/ru')).default;
|
||||
case 'it':
|
||||
return (await import('./locales/it')).default;
|
||||
case 'zh':
|
||||
return (await import('./locales/zh')).default;
|
||||
default:
|
||||
return (await import('./locales/en')).default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default [
|
||||
['core_browser_settings_cookies_settings_group', 'Cookies'],
|
||||
['core_browser_settings_browser_settings_group', 'Browser'],
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
export default [
|
||||
['core_browser_settings_cookies_settings_group', 'Cookies'],
|
||||
['core_browser_settings_browser_settings_group', 'Browser'],
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
export default [
|
||||
['core_browser_settings_cookies_settings_group', 'Cookies'],
|
||||
['core_browser_settings_browser_settings_group', 'Браузер'],
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
export default [
|
||||
['core_browser_settings_cookies_settings_group', 'Cookies'],
|
||||
['core_browser_settings_browser_settings_group', 'Browser'],
|
||||
];
|
||||
@@ -8,11 +8,12 @@
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { BrowserSettingsService } from './BrowserSettingsService';
|
||||
import { LocaleService } from './LocaleService';
|
||||
|
||||
export const coreBrowserSettingsManifest: PluginManifest = {
|
||||
info: {
|
||||
name: 'Core Browser Settings',
|
||||
},
|
||||
|
||||
providers: [BrowserSettingsService],
|
||||
providers: [BrowserSettingsService, LocaleService],
|
||||
};
|
||||
|
||||
@@ -11,15 +11,42 @@
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../tests-runner/tsconfig.json"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"dexie": "^3.2.4",
|
||||
"mobx": "^6.12.0",
|
||||
"workbox-core": "~7.x.x",
|
||||
"workbox-precaching": "~7.x.x",
|
||||
"workbox-window": "~7.x.x"
|
||||
|
||||
+3
-1
@@ -7,10 +7,12 @@
|
||||
*/
|
||||
import { entries, IReactionDisposer, keys, observable, ObservableMap, reaction, remove, runInAction, set, toJS } from 'mobx';
|
||||
|
||||
import { IndexedDB, IndexedDBService, IndexedDBTable } from '@cloudbeaver/core-browser';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
import { IndexedDB, type IndexedDBTable } from './IndexedDB/IndexedDB';
|
||||
import { IndexedDBService } from './IndexedDB/IndexedDBService';
|
||||
|
||||
interface ILocalStorageElement<T extends Record<any, any> | Map<any, any>> {
|
||||
storeId: string;
|
||||
store: T;
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './IndexedDB/IndexedDBService';
|
||||
export * from './IndexedDB/IndexedDB';
|
||||
export * from './LocalStorageSaveService';
|
||||
export * from './manifest';
|
||||
export * from './selectFiles';
|
||||
export * from './ServiceWorkerService';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { IndexedDBService } from './IndexedDB/IndexedDBService';
|
||||
import { LocalStorageSaveService } from './LocalStorageSaveService';
|
||||
import { ServiceWorkerBootstrap } from './ServiceWorkerBootstrap';
|
||||
import { ServiceWorkerService } from './ServiceWorkerService';
|
||||
|
||||
@@ -16,5 +17,5 @@ export const coreBrowserManifest: PluginManifest = {
|
||||
name: 'Core Browser',
|
||||
},
|
||||
|
||||
providers: [ServiceWorkerBootstrap, ServiceWorkerService, IndexedDBService],
|
||||
providers: [ServiceWorkerBootstrap, ServiceWorkerService, IndexedDBService, LocalStorageSaveService],
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ for (const dependency of dependencies) {
|
||||
typescriptConfig.references.push({
|
||||
path: upath.relative(currentDir, dependencyPath),
|
||||
});
|
||||
typescriptConfig.references.sort((a, b) => a.path.localeCompare(b.path));
|
||||
|
||||
// const relativePath = relative(tsRootPath, dependencyPath);
|
||||
|
||||
|
||||
@@ -92,24 +92,28 @@ const newAllDependencies = [...newDependencies, ...devDependencies].sort(sortDep
|
||||
|
||||
logUnmetAndExtraDependencies('dependencies', newDependencies, currentPackage.dependencies, newAllDependencies);
|
||||
|
||||
currentPackage.dependencies = newDependencies.reduce(
|
||||
(acc, dep) => ({
|
||||
...acc,
|
||||
[dep]: getVersion(dep, acc?.[dep]),
|
||||
}),
|
||||
currentPackage.dependencies,
|
||||
currentPackage.dependencies = sortObjectKeys(
|
||||
newDependencies.reduce(
|
||||
(acc, dep) => ({
|
||||
...acc,
|
||||
[dep]: getVersion(dep, acc?.[dep]),
|
||||
}),
|
||||
currentPackage.dependencies,
|
||||
),
|
||||
);
|
||||
|
||||
const newDevDependencies = [...devDependencies].sort(sortDependencies);
|
||||
|
||||
logUnmetAndExtraDependencies('dev dependencies', newDevDependencies, currentPackage.devDependencies, newAllDependencies);
|
||||
|
||||
currentPackage.devDependencies = [...devDependencies].sort(sortDependencies).reduce(
|
||||
(acc, dep) => ({
|
||||
...acc,
|
||||
[dep]: getVersion(dep, acc?.[dep]),
|
||||
}),
|
||||
currentPackage.devDependencies,
|
||||
currentPackage.devDependencies = sortObjectKeys(
|
||||
[...devDependencies].sort(sortDependencies).reduce(
|
||||
(acc, dep) => ({
|
||||
...acc,
|
||||
[dep]: getVersion(dep, acc?.[dep]),
|
||||
}),
|
||||
currentPackage.devDependencies,
|
||||
),
|
||||
);
|
||||
|
||||
if (isSuccess) {
|
||||
@@ -178,3 +182,12 @@ function logUnmetAndExtraDependencies(key, newDependencies, current, allDependen
|
||||
isSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
function sortObjectKeys(object) {
|
||||
return Object.keys(object)
|
||||
.sort()
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = object[key];
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
@@ -45,9 +45,18 @@ module.exports = (env, argv) => {
|
||||
index,
|
||||
sso,
|
||||
},
|
||||
watchOptions: {
|
||||
aggregateTimeout: 3000,
|
||||
ignored: ['**/node_modules', '**/packages/*/src/**/*.{ts,tsx}'],
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
moduleIds: 'named',
|
||||
|
||||
// improve performance
|
||||
removeAvailableModules: false,
|
||||
removeEmptyChunks: false,
|
||||
splitChunks: false,
|
||||
},
|
||||
devServer: {
|
||||
allowedHosts: 'all',
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
"css-loader": "~6.8.1",
|
||||
"declaration-bundler-webpack-plugin": "~1.0.3",
|
||||
"extra-watch-webpack-plugin": "~1.0.3",
|
||||
"fork-ts-checker-webpack-plugin": "~8.0.0",
|
||||
"html-loader": "~4.2.0",
|
||||
"html-replace-webpack-plugin": "~2.6.0",
|
||||
"html-webpack-plugin": "~5.5.3",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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 { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
|
||||
export const CONNECTIONS_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_connections_connections_settings_group');
|
||||
@@ -5,20 +5,34 @@
|
||||
* 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 { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
import { Dependency, injectable } from '@cloudbeaver/core-di';
|
||||
import { PluginManagerService, PluginSettings, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
const defaultSettings = {
|
||||
disabled: false,
|
||||
};
|
||||
const settingsSchema = schema.object({
|
||||
disabled: schema.coerce.boolean().default(false),
|
||||
});
|
||||
|
||||
export type ConnectionsSettings = typeof defaultSettings;
|
||||
export type ConnectionsSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class ConnectionsSettingsService {
|
||||
readonly settings: PluginSettings<ConnectionsSettings>;
|
||||
export class ConnectionsSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('connections', 'core', defaultSettings);
|
||||
constructor(private readonly pluginManagerService: PluginManagerService, private readonly settingsManagerService: SettingsManagerService) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('connections', 'core', settingsSchema);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
// {
|
||||
// group: CONNECTIONS_SETTINGS_GROUP,
|
||||
// key: 'disabled',
|
||||
// name: 'Disable',
|
||||
// type: ESettingsValueType.Checkbox,
|
||||
// },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export * from './ConnectionFolderResource';
|
||||
export * from './ConnectionDialectResource';
|
||||
export * from './ConnectionInfoEventHandler';
|
||||
export * from './ConnectionInfoResource';
|
||||
export * from './CONNECTIONS_SETTINGS_GROUP';
|
||||
export * from './EConnectionFeature';
|
||||
export * from './ConnectionsSettingsService';
|
||||
export * from './ContainerResource';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default [
|
||||
['core_connections_connections_settings_group', 'Connections'],
|
||||
['connections_administration_item', 'Connection Templates'],
|
||||
['connections_administration_connection_create', 'Add Database Template'],
|
||||
['connections_administration_search_database_tip', "Type your hosts here, e.g. 'localhost host1.myhost.com 192.168.0.1' and press Enter"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default [
|
||||
['core_connections_connections_settings_group', 'Connections'],
|
||||
['connections_administration_item', 'Connection Templates'],
|
||||
['connections_administration_connection_create', 'Add Database Template'],
|
||||
['connections_administration_search_database_tip', "Digita gli hosts qui, p.e. 'localhost host1.myhost.com 192.168.0.1' e premi Invio"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default [
|
||||
['core_connections_connections_settings_group', 'Подключения'],
|
||||
['connections_administration_item', 'Шаблоны подключений'],
|
||||
['connections_administration_connection_create', 'Создание шаблона подключения'],
|
||||
['connections_administration_search_database_tip', "Укажите ваши хосты, например 'localhost host1.myhost.com 192.168.0.1' и нажмите Enter"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default [
|
||||
['core_connections_connections_settings_group', 'Connections'],
|
||||
['connections_administration_item', 'Connection Templates'],
|
||||
['connections_administration_connection_create', 'Add Database Template'],
|
||||
['connections_administration_search_database_tip', '在此处输入您的主机,例如“localhost host1.myhost.com 192.168.0.1”然后按 Enter'],
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.10",
|
||||
"@types/react": "^18.2.42",
|
||||
"typescript": "^5.3.2",
|
||||
"@types/jest": "^29.5.10"
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,20 @@
|
||||
"@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-utils": "~0.1.0",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@cloudbeaver/core-browser": "~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/tests-runner": "~0.1.0",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@types/jest": "^29.5.10",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { coreBrowserManifest } from '@cloudbeaver/core-browser';
|
||||
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';
|
||||
@@ -15,41 +17,65 @@ import { mockAppInit } from '@cloudbeaver/core-root/dist/__custom_mocks__/mockAp
|
||||
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 { createApp } from '@cloudbeaver/tests-runner';
|
||||
|
||||
import { EventsSettings, EventsSettingsService } from './EventsSettingsService';
|
||||
import { coreEventsManifest } from './manifest';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const app = createApp(coreEventsManifest, corePluginManifest, coreProductManifest, coreRootManifest, coreSDKManifest);
|
||||
const app = createApp(
|
||||
coreEventsManifest,
|
||||
corePluginManifest,
|
||||
coreSettingsManifest,
|
||||
coreLocalizationManifest,
|
||||
coreBrowserManifest,
|
||||
coreProductManifest,
|
||||
coreRootManifest,
|
||||
coreSDKManifest,
|
||||
);
|
||||
|
||||
const server = mockGraphQL(...mockAppInit(endpoint));
|
||||
|
||||
beforeAll(() => app.init());
|
||||
|
||||
const equalConfig = {
|
||||
const oldConfig = {
|
||||
core_events: {
|
||||
notificationsPool: 1,
|
||||
maxPersistentAllow: 2,
|
||||
} as EventsSettings,
|
||||
};
|
||||
|
||||
const overrideConfig = {
|
||||
...oldConfig,
|
||||
plugin: {
|
||||
notifications: {
|
||||
notificationsPool: 1,
|
||||
maxPersistentAllow: 2,
|
||||
notificationsPool: 3,
|
||||
maxPersistentAllow: 4,
|
||||
} as EventsSettings,
|
||||
},
|
||||
};
|
||||
|
||||
test('New settings equal deprecated settings', async () => {
|
||||
test('New settings override deprecated settings', async () => {
|
||||
const settings = app.injector.getServiceByClass(EventsSettingsService);
|
||||
const config = app.injector.getServiceByClass(ServerConfigResource);
|
||||
|
||||
server.use(endpoint.query('serverConfig', mockServerConfig(equalConfig)));
|
||||
server.use(endpoint.query('serverConfig', mockServerConfig(overrideConfig)));
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('notificationsPool')).toBe(3);
|
||||
expect(settings.settings.getValue('maxPersistentAllow')).toBe(4);
|
||||
});
|
||||
|
||||
test('Deprecated settings accessible with new settings', async () => {
|
||||
const settings = app.injector.getServiceByClass(EventsSettingsService);
|
||||
const config = app.injector.getServiceByClass(ServerConfigResource);
|
||||
|
||||
server.use(endpoint.query('serverConfig', mockServerConfig(oldConfig)));
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('notificationsPool')).toBe(1);
|
||||
expect(settings.settings.getValue('maxPersistentAllow')).toBe(2);
|
||||
expect(settings.deprecatedSettings.getValue('notificationsPool')).toBe(1);
|
||||
expect(settings.deprecatedSettings.getValue('maxPersistentAllow')).toBe(2);
|
||||
});
|
||||
|
||||
@@ -5,24 +5,52 @@
|
||||
* 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 { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
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';
|
||||
|
||||
const defaultSettings = {
|
||||
notificationsPool: 20,
|
||||
maxPersistentAllow: 5,
|
||||
};
|
||||
const settingsSchema = schema.object({
|
||||
notificationsPool: schema.coerce.number().default(20),
|
||||
maxPersistentAllow: schema.coerce.number().default(5),
|
||||
});
|
||||
|
||||
export type EventsSettings = typeof defaultSettings;
|
||||
export type EventsSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class EventsSettingsService {
|
||||
readonly settings: PluginSettings<EventsSettings>;
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
readonly deprecatedSettings: PluginSettings<EventsSettings>;
|
||||
export class EventsSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('notifications', 'plugin', defaultSettings);
|
||||
this.deprecatedSettings = this.pluginManagerService.getDeprecatedPluginSettings('core_events', defaultSettings);
|
||||
constructor(
|
||||
private readonly pluginManagerService: PluginManagerService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly serverSettingsService: ServerSettingsService,
|
||||
private readonly serverSettingsResolverService: ServerSettingsResolverService,
|
||||
) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('notifications', 'plugin', settingsSchema);
|
||||
|
||||
this.serverSettingsResolverService.addResolver(
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
createSettingsAliasResolver(this.serverSettingsService, this.settings, 'core_events'),
|
||||
);
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
// {
|
||||
// group: NOTIFICATIONS_SETTINGS_GROUP,
|
||||
// key: 'maxPersistentAllow',
|
||||
// name: 'Max persistent notifications count',
|
||||
// type: ESettingsValueType.Input,
|
||||
// },
|
||||
// {
|
||||
// group: NOTIFICATIONS_SETTINGS_GROUP,
|
||||
// key: 'notificationsPool',
|
||||
// name: 'Max notifications count',
|
||||
// type: ESettingsValueType.Input,
|
||||
// },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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 { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
|
||||
export const NOTIFICATIONS_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_events_notifications_settings_group');
|
||||
@@ -52,9 +52,7 @@ export class NotificationService {
|
||||
if (options.persistent) {
|
||||
const persistentNotifications = this.notificationList.values.filter(value => value.persistent);
|
||||
|
||||
const maxPersistentAllow = this.settings.settings.isValueDefault('maxPersistentAllow')
|
||||
? this.settings.deprecatedSettings.getValue('maxPersistentAllow')
|
||||
: this.settings.settings.getValue('maxPersistentAllow');
|
||||
const maxPersistentAllow = this.settings.settings.getValue('maxPersistentAllow');
|
||||
|
||||
if (persistentNotifications.length >= maxPersistentAllow) {
|
||||
throw new Error(`You cannot create more than ${maxPersistentAllow} persistent notification`);
|
||||
@@ -107,9 +105,7 @@ export class NotificationService {
|
||||
|
||||
const filteredNotificationList = this.notificationList.values.filter(notification => !notification.persistent);
|
||||
|
||||
const notificationsPool = this.settings.settings.isValueDefault('notificationsPool')
|
||||
? this.settings.deprecatedSettings.getValue('notificationsPool')
|
||||
: this.settings.settings.getValue('notificationsPool');
|
||||
const notificationsPool = this.settings.settings.getValue('notificationsPool');
|
||||
|
||||
if (filteredNotificationList.length > notificationsPool) {
|
||||
let i = 0;
|
||||
|
||||
@@ -6,21 +6,21 @@
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
@@ -30,9 +30,21 @@
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../tests-runner/tsconfig.json"
|
||||
}
|
||||
|
||||
@@ -19,26 +19,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~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",
|
||||
"mobx": "^6.12.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@cloudbeaver/core-browser": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~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/tests-runner": "~0.1.0",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@types/jest": "^29.5.10",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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 { ILocale } from './ILocale';
|
||||
|
||||
export const DEFAULT_LOCALE: ILocale = { isoCode: 'en', displayName: 'English' };
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface ILocale {
|
||||
isoCode: string;
|
||||
displayName: string;
|
||||
}
|
||||
@@ -5,93 +5,83 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
import { action, makeObservable, observable, reaction } from 'mobx';
|
||||
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
import { ServerConfigResource, SessionResource } from '@cloudbeaver/core-root';
|
||||
import type { ServerLanguage } from '@cloudbeaver/core-sdk';
|
||||
import { SettingsService } from '@cloudbeaver/core-settings';
|
||||
|
||||
import { DEFAULT_LOCALE } from './DEFAULT_LOCALE';
|
||||
import type { ILocale } from './ILocale';
|
||||
import type { ILocaleProvider } from './ILocaleProvider';
|
||||
import type { TLocalizationToken } from './TLocalizationToken';
|
||||
|
||||
export type ServerLanguageShort = Pick<ServerLanguage, 'isoCode' | 'nativeName'>;
|
||||
|
||||
const DEFAULT_LOCALE_NAME = 'en';
|
||||
const LANG_SETTINGS_KEY = 'langSettings';
|
||||
|
||||
export interface ILocalizationSettings {
|
||||
defaultLanguage: string;
|
||||
}
|
||||
|
||||
interface ISettings {
|
||||
language: string;
|
||||
language: string | null;
|
||||
}
|
||||
|
||||
export const defaultThemeSettings: ILocalizationSettings = {
|
||||
defaultLanguage: DEFAULT_LOCALE_NAME,
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class LocalizationService extends Bootstrap {
|
||||
get currentLanguage(): string {
|
||||
return this.settings.language;
|
||||
}
|
||||
const lang = this.settings.language;
|
||||
|
||||
get defaultLanguage(): string {
|
||||
if (this.pluginSettings.isValueDefault('defaultLanguage')) {
|
||||
return this.deprecatedPluginSettings.getValue('defaultLanguage');
|
||||
if (lang !== null && this.isLanguageSupported(lang)) {
|
||||
return lang;
|
||||
}
|
||||
|
||||
return this.pluginSettings.getValue('defaultLanguage');
|
||||
if (this.isLanguageSupported(this.defaultLanguage)) {
|
||||
return this.defaultLanguage;
|
||||
}
|
||||
|
||||
return this.supportedLanguages[0].isoCode;
|
||||
}
|
||||
|
||||
settings: ISettings;
|
||||
readonly pluginSettings: PluginSettings<ILocalizationSettings>;
|
||||
/** @deprecated Use settings instead, will be removed in 23.0.0 */
|
||||
readonly deprecatedPluginSettings: PluginSettings<ILocalizationSettings>;
|
||||
supportedLanguages: ILocale[];
|
||||
|
||||
readonly onChange: ISyncExecutor<string>;
|
||||
// observable.shallow - don't treat locales as observables
|
||||
private defaultLanguage: string;
|
||||
private readonly localeMap: Map<string, Map<string, string>> = new Map();
|
||||
|
||||
private readonly localeProviders: ILocaleProvider[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly sessionResource: SessionResource,
|
||||
private readonly pluginManagerService: PluginManagerService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly settingsService: SettingsService,
|
||||
) {
|
||||
constructor(private readonly settingsService: SettingsService) {
|
||||
super();
|
||||
|
||||
this.settings = getDefaultLocalizationSettings();
|
||||
this.supportedLanguages = [DEFAULT_LOCALE];
|
||||
this.defaultLanguage = DEFAULT_LOCALE.isoCode;
|
||||
this.onChange = new SyncExecutor();
|
||||
this.pluginSettings = this.pluginManagerService.createSettings('localization', 'core', defaultThemeSettings);
|
||||
this.deprecatedPluginSettings = this.pluginManagerService.createSettings('user', 'core', defaultThemeSettings);
|
||||
|
||||
sessionResource.onDataUpdate.addHandler(this.syncLanguage.bind(this));
|
||||
this.onChange.addHandler(key => {
|
||||
this.sessionResource.setDefaultLocale(key);
|
||||
this.sessionResource.changeLanguage(key);
|
||||
});
|
||||
|
||||
makeObservable<LocalizationService, 'localeMap' | 'setCurrentLocale'>(this, {
|
||||
makeObservable<LocalizationService, 'localeMap' | 'setCurrentLocale' | 'supportedLanguages' | 'defaultLanguage'>(this, {
|
||||
defaultLanguage: observable,
|
||||
supportedLanguages: observable,
|
||||
settings: observable,
|
||||
localeMap: observable.shallow,
|
||||
defaultLanguage: computed,
|
||||
localeMap: observable.shallow, // observable.shallow - don't treat locales as observables
|
||||
setCurrentLocale: action,
|
||||
});
|
||||
}
|
||||
|
||||
isLanguageSupported(lang: string): boolean {
|
||||
return this.supportedLanguages.some(language => language.isoCode === lang);
|
||||
}
|
||||
|
||||
addProvider(provider: ILocaleProvider): void {
|
||||
this.localeProviders.push(provider);
|
||||
}
|
||||
|
||||
setSupportedLanguages(locales: ILocale[]) {
|
||||
this.supportedLanguages = locales;
|
||||
if (this.supportedLanguages.length === 0) {
|
||||
this.supportedLanguages = [DEFAULT_LOCALE];
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultLanguage(lang: string) {
|
||||
this.defaultLanguage = lang;
|
||||
}
|
||||
|
||||
readonly translate = <T extends TLocalizationToken | undefined>(token: T, fallback?: T, args: Record<string | number, any> = {}): T => {
|
||||
if (token === undefined) {
|
||||
return undefined as T;
|
||||
@@ -100,7 +90,7 @@ export class LocalizationService extends Bootstrap {
|
||||
let translation = this.localeMap.get(this.currentLanguage)?.get(token as TLocalizationToken);
|
||||
|
||||
if (!translation) {
|
||||
translation = this.localeMap.get(DEFAULT_LOCALE_NAME)?.get(token as TLocalizationToken);
|
||||
translation = this.localeMap.get(this.defaultLanguage)?.get(token as TLocalizationToken);
|
||||
}
|
||||
|
||||
if (typeof translation === 'string') {
|
||||
@@ -121,17 +111,31 @@ export class LocalizationService extends Bootstrap {
|
||||
};
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.settingsService.registerSettings(LANG_SETTINGS_KEY, this.settings, getDefaultLocalizationSettings); // load user state locale
|
||||
this.setSupportedLanguages([
|
||||
{
|
||||
isoCode: 'en',
|
||||
displayName: 'English',
|
||||
},
|
||||
{
|
||||
isoCode: 'ru',
|
||||
displayName: 'Русский',
|
||||
},
|
||||
{
|
||||
isoCode: 'it',
|
||||
displayName: 'Italiano',
|
||||
},
|
||||
{
|
||||
isoCode: 'zh',
|
||||
displayName: '中文',
|
||||
},
|
||||
]);
|
||||
this.addProvider(this.coreProvider.bind(this));
|
||||
this.sessionResource.setDefaultLocale(this.currentLanguage);
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
await this.serverConfigResource.load();
|
||||
this.setCurrentLocale(this.defaultLanguage); // set default app locale
|
||||
this.settingsService.registerSettings(LANG_SETTINGS_KEY, this.settings, getDefaultLocalizationSettings); // load user state locale
|
||||
this.sessionResource.setDefaultLocale(this.currentLanguage);
|
||||
await this.loadLocaleAsync(DEFAULT_LOCALE_NAME);
|
||||
await this.loadLocaleAsync(this.currentLanguage);
|
||||
await this.loadLocaleAsync(this.defaultLanguage);
|
||||
await this.autoLoadCurrentLanguage();
|
||||
}
|
||||
|
||||
async changeLocaleAsync(key: string): Promise<void> {
|
||||
@@ -142,14 +146,6 @@ export class LocalizationService extends Bootstrap {
|
||||
this.onChange.execute(this.currentLanguage);
|
||||
}
|
||||
|
||||
private async syncLanguage() {
|
||||
const session = this.sessionResource.data;
|
||||
|
||||
if (session) {
|
||||
await this.setLocale(session.locale);
|
||||
}
|
||||
}
|
||||
|
||||
private async coreProvider(locale: string) {
|
||||
switch (locale) {
|
||||
case 'ru':
|
||||
@@ -163,19 +159,12 @@ export class LocalizationService extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private setCurrentLocale(lang: string) {
|
||||
private setCurrentLocale(lang: string | null) {
|
||||
this.settings.language = lang;
|
||||
}
|
||||
|
||||
async setLocale(key: string) {
|
||||
const config = await this.serverConfigResource.load();
|
||||
|
||||
if (!config) {
|
||||
throw new Error("Can't get server settings");
|
||||
}
|
||||
|
||||
if (!config.supportedLanguages.some(lang => lang.isoCode === key)) {
|
||||
this.setCurrentLocale(config!.supportedLanguages[0]!.isoCode);
|
||||
if (!this.isLanguageSupported(key)) {
|
||||
throw new Error(`Language '${key}' is not supported`);
|
||||
}
|
||||
|
||||
@@ -187,23 +176,44 @@ export class LocalizationService extends Bootstrap {
|
||||
if (this.localeMap.has(localeKey)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const locale = new Map<string, string>();
|
||||
const locale = new Map<string, string>();
|
||||
|
||||
for (const provider of this.localeProviders) {
|
||||
for (const [key, value] of await provider(localeKey)) {
|
||||
locale.set(key, value);
|
||||
}
|
||||
for (const provider of this.localeProviders) {
|
||||
for (const [key, value] of await provider(localeKey)) {
|
||||
locale.set(key, value);
|
||||
}
|
||||
this.localeMap.set(localeKey, locale);
|
||||
} catch (error: any) {
|
||||
this.notificationService.logException(error, 'Locale is not found', '', true);
|
||||
}
|
||||
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: DEFAULT_LOCALE_NAME,
|
||||
language: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './manifest';
|
||||
export * from './LocalizationService';
|
||||
export * from './Locale';
|
||||
export * from './TLocalizationToken';
|
||||
export * from './DEFAULT_LOCALE';
|
||||
|
||||
@@ -9,47 +9,11 @@
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../tests-runner/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": [
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
"update-ts-references": "rimraf --glob dist && typescript-resolve-references"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-app": "~0.1.0",
|
||||
"@cloudbeaver/core-authentication": "~0.1.0",
|
||||
"@cloudbeaver/core-data-context": "~0.1.0",
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@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",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
|
||||
@injectable()
|
||||
export class LocaleService extends Bootstrap {
|
||||
constructor(private readonly localizationService: LocalizationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.localizationService.addProvider(this.provider.bind(this));
|
||||
}
|
||||
|
||||
private async provider(locale: string) {
|
||||
switch (locale) {
|
||||
case 'ru':
|
||||
return (await import('./locales/ru')).default;
|
||||
case 'it':
|
||||
return (await import('./locales/it')).default;
|
||||
case 'zh':
|
||||
return (await import('./locales/zh')).default;
|
||||
default:
|
||||
return (await import('./locales/en')).default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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 { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
|
||||
export const NAVIGATION_TREE_SETTINGS_GROUP = ROOT_SETTINGS_GROUP.createSubGroup('core_navigation_tree_navigation_tree_settings_group');
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { coreAppManifest, CoreSettingsService } from '@cloudbeaver/core-app';
|
||||
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';
|
||||
@@ -56,38 +56,51 @@ const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpo
|
||||
|
||||
beforeAll(() => app.init());
|
||||
|
||||
const equalConfig = {
|
||||
core: {
|
||||
app: {
|
||||
navigationTree: {
|
||||
childrenLimit: 1,
|
||||
},
|
||||
metadata: {
|
||||
editing: false,
|
||||
deleting: false,
|
||||
},
|
||||
},
|
||||
'navigation-tree': {
|
||||
const deprecatedSettings = {
|
||||
'core.app': {
|
||||
navigationTree: {
|
||||
childrenLimit: 1,
|
||||
},
|
||||
metadata: {
|
||||
editing: false,
|
||||
deleting: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newSettings = {
|
||||
...deprecatedSettings,
|
||||
core: {
|
||||
'navigation-tree': {
|
||||
childrenLimit: 2,
|
||||
editing: true,
|
||||
deleting: true,
|
||||
} as NavTreeSettings,
|
||||
},
|
||||
};
|
||||
|
||||
test('New settings equal deprecated settings', async () => {
|
||||
test('New settings override deprecated', async () => {
|
||||
const settings = app.injector.getServiceByClass(NavTreeSettingsService);
|
||||
const coreSettings = app.injector.getServiceByClass(CoreSettingsService);
|
||||
const config = app.injector.getServiceByClass(ServerConfigResource);
|
||||
|
||||
server.use(endpoint.query('serverConfig', mockServerConfig(equalConfig)));
|
||||
server.use(endpoint.query('serverConfig', mockServerConfig(newSettings)));
|
||||
|
||||
await config.refresh();
|
||||
|
||||
expect(settings.settings.getValue('childrenLimit')).toBe(2);
|
||||
expect(settings.settings.getValue('editing')).toBe(true);
|
||||
expect(settings.settings.getValue('deleting')).toBe(true);
|
||||
});
|
||||
|
||||
test('Deprecated settings are used if new settings are not defined', async () => {
|
||||
const settings = app.injector.getServiceByClass(NavTreeSettingsService);
|
||||
const config = app.injector.getServiceByClass(ServerConfigResource);
|
||||
|
||||
server.use(endpoint.query('serverConfig', mockServerConfig(deprecatedSettings)));
|
||||
|
||||
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(coreSettings.settings.getValue('app.navigationTree.childrenLimit')).toBe(1);
|
||||
expect(coreSettings.settings.getValue('app.metadata.editing')).toBe(false);
|
||||
expect(coreSettings.settings.getValue('app.metadata.deleting')).toBe(false);
|
||||
});
|
||||
|
||||
@@ -5,22 +5,60 @@
|
||||
* 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 { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
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';
|
||||
|
||||
const defaultSettings = {
|
||||
childrenLimit: 100,
|
||||
editing: true,
|
||||
deleting: true,
|
||||
};
|
||||
const settingsSchema = schema.object({
|
||||
childrenLimit: schema.coerce.number().default(100),
|
||||
editing: schema.coerce.boolean().default(true),
|
||||
deleting: schema.coerce.boolean().default(true),
|
||||
});
|
||||
|
||||
export type NavTreeSettings = typeof defaultSettings;
|
||||
export type NavTreeSettings = schema.infer<typeof settingsSchema>;
|
||||
|
||||
@injectable()
|
||||
export class NavTreeSettingsService {
|
||||
readonly settings: PluginSettings<NavTreeSettings>;
|
||||
export class NavTreeSettingsService extends Dependency {
|
||||
readonly settings: PluginSettings<typeof settingsSchema>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService) {
|
||||
this.settings = this.pluginManagerService.createSettings('navigation-tree', 'core', defaultSettings);
|
||||
constructor(
|
||||
private readonly pluginManagerService: PluginManagerService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly serverSettingsService: ServerSettingsService,
|
||||
private readonly serverSettingsResolverService: ServerSettingsResolverService,
|
||||
) {
|
||||
super();
|
||||
this.settings = this.pluginManagerService.createSettings('navigation-tree', 'core', settingsSchema);
|
||||
this.serverSettingsResolverService.addResolver(
|
||||
/** @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'),
|
||||
);
|
||||
|
||||
this.registerSettings();
|
||||
}
|
||||
|
||||
private registerSettings() {
|
||||
this.settingsManagerService.registerSettings(this.settings, () => [
|
||||
// {
|
||||
// group: NAVIGATION_TREE_SETTINGS_GROUP,
|
||||
// key: 'childrenLimit',
|
||||
// name: 'Children limit',
|
||||
// type: ESettingsValueType.Input,
|
||||
// },
|
||||
// {
|
||||
// group: NAVIGATION_TREE_SETTINGS_GROUP,
|
||||
// key: 'editing',
|
||||
// name: 'Editing',
|
||||
// type: ESettingsValueType.Checkbox,
|
||||
// },
|
||||
// {
|
||||
// group: NAVIGATION_TREE_SETTINGS_GROUP,
|
||||
// key: 'deleting',
|
||||
// name: 'Deleting',
|
||||
// type: ESettingsValueType.Checkbox,
|
||||
// },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
|
||||
|
||||
import { CoreSettingsService } from '@cloudbeaver/core-app';
|
||||
import { AppAuthService, UserInfoResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { Executor, ExecutorInterrupter, IExecutionContext, IExecutor } from '@cloudbeaver/core-executor';
|
||||
@@ -66,15 +65,12 @@ export class NavTreeResource extends CachedMapResource<string, string[], Record<
|
||||
readonly onNodeMove: IExecutor<INavNodeMoveData>;
|
||||
|
||||
get childrenLimit(): number {
|
||||
return this.navTreeSettingsService.settings.isValueDefault('childrenLimit')
|
||||
? this.coreSettingsService.settings.getValue('app.navigationTree.childrenLimit')
|
||||
: this.navTreeSettingsService.settings.getValue('childrenLimit');
|
||||
return this.navTreeSettingsService.settings.getValue('childrenLimit');
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly navNodeInfoResource: NavNodeInfoResource,
|
||||
private readonly coreSettingsService: CoreSettingsService,
|
||||
private readonly navTreeSettingsService: NavTreeSettingsService,
|
||||
private readonly sessionDataResource: SessionDataResource,
|
||||
private readonly userInfoResource: UserInfoResource,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './manifest';
|
||||
export * from './NAVIGATION_TREE_SETTINGS_GROUP';
|
||||
export * from './NavTreeSettingsService';
|
||||
|
||||
export * from './NodesManager/extensions/IObjectNavNodeProvider';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default [['core_navigation_tree_navigation_tree_settings_group', 'Navigation Tree']];
|
||||
@@ -0,0 +1 @@
|
||||
export default [['core_navigation_tree_navigation_tree_settings_group', 'Navigation Tree']];
|
||||
@@ -0,0 +1 @@
|
||||
export default [['core_navigation_tree_navigation_tree_settings_group', 'Дерево навигации']];
|
||||
@@ -0,0 +1 @@
|
||||
export default [['core_navigation_tree_navigation_tree_settings_group', 'Navigation Tree']];
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { LocaleService } from './LocaleService';
|
||||
import { NavTreeSettingsService } from './NavTreeSettingsService';
|
||||
import { DBObjectResource } from './NodesManager/DBObjectResource';
|
||||
import { NavNodeInfoResource } from './NodesManager/NavNodeInfoResource';
|
||||
@@ -19,5 +20,13 @@ export const coreNavigationTree: PluginManifest = {
|
||||
name: 'Core Navigation Tree',
|
||||
},
|
||||
|
||||
providers: [NavTreeSettingsService, NavNodeManagerService, DBObjectResource, NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService],
|
||||
providers: [
|
||||
NavTreeSettingsService,
|
||||
NavNodeManagerService,
|
||||
DBObjectResource,
|
||||
NavNodeInfoResource,
|
||||
NavTreeResource,
|
||||
ProjectsNavNodeService,
|
||||
LocaleService,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -12,12 +12,21 @@
|
||||
{
|
||||
"path": "../core-authentication/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-authentication/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-data-context/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-di/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-executor/tsconfig.json"
|
||||
},
|
||||
@@ -25,37 +34,7 @@
|
||||
"path": "../core-extensions/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-projects/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-ui/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-app/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-authentication/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-browser/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-events/tsconfig.json"
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-localization/tsconfig.json"
|
||||
@@ -63,12 +42,24 @@
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-plugin/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-product/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-projects/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-projects/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-resource/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-root/tsconfig.json"
|
||||
},
|
||||
@@ -78,6 +69,9 @@
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-sdk/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-settings/tsconfig.json"
|
||||
},
|
||||
@@ -87,6 +81,12 @@
|
||||
{
|
||||
"path": "../core-ui/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-ui/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-utils/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../core-view/tsconfig.json"
|
||||
},
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0"
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
|
||||
@injectable()
|
||||
export class LocaleService extends Bootstrap {
|
||||
constructor(private readonly localizationService: LocalizationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.localizationService.addProvider(this.provider.bind(this));
|
||||
}
|
||||
|
||||
private async provider(locale: string) {
|
||||
switch (locale) {
|
||||
case 'ru':
|
||||
return (await import('./locales/ru')).default;
|
||||
case 'it':
|
||||
return (await import('./locales/it')).default;
|
||||
case 'zh':
|
||||
return (await import('./locales/zh')).default;
|
||||
default:
|
||||
return (await import('./locales/en')).default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,35 +6,33 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ProductManagerService } from '@cloudbeaver/core-product';
|
||||
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 {
|
||||
store: Map<string, PluginSettings<any>>;
|
||||
constructor(private readonly productManagerService: ProductManagerService) {
|
||||
readonly store: Map<string, PluginSettings>;
|
||||
constructor(private readonly productSettingsService: ProductSettingsService) {
|
||||
this.store = new Map();
|
||||
}
|
||||
|
||||
createSettings<T>(scope: string, scopeType: SettingsScopeType, defaults: T) {
|
||||
createSettings<TSchema extends schema.SomeZodObject = schema.AnyZodObject>(scope: string, scopeType: SettingsScopeType, schema: TSchema) {
|
||||
const key = scopeType + '.' + scope;
|
||||
const settings = new PluginSettings(this.productManagerService.settings, key, defaults);
|
||||
const settings = new PluginSettings(this.productSettingsService, key, schema);
|
||||
|
||||
this.store.set(key, settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
getSettings(scope: string, scopeType: SettingsScopeType): PluginSettings<any> | undefined {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Please use createSettings instead
|
||||
* @deprecated Please use createSettings instead, will be removed in 23.0.0
|
||||
*/
|
||||
getDeprecatedPluginSettings<T>(scope: string, defaults: T) {
|
||||
return new PluginSettings(this.productManagerService.settings, scope, defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,44 +5,62 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { ISettingsSource, SettingsResolverSource } from '@cloudbeaver/core-settings';
|
||||
import type { ISettingsSource } from '@cloudbeaver/core-settings';
|
||||
import type { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
export class PluginSettings<T> extends SettingsResolverSource implements ISettingsSource {
|
||||
constructor(private readonly source: ISettingsSource, private readonly scope: string, private readonly defaults: T) {
|
||||
super();
|
||||
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 T>(key: TKey): boolean {
|
||||
has<TKey extends keyof schema.infer<TSchema>>(key: TKey): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isValueDefault<TKey extends keyof T>(key: TKey): boolean {
|
||||
return !super.has(key) && !this.source.has(this.scopedKey(key));
|
||||
isValueDefault<TKey extends keyof schema.infer<TSchema>>(key: TKey): boolean {
|
||||
return !this.source.has(this.scopedKey(key));
|
||||
}
|
||||
|
||||
getValue<TKey extends keyof T>(key: TKey): T[TKey] {
|
||||
if (super.has(key)) {
|
||||
return super.getValue(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))) {
|
||||
return this.source.getValue(this.scopedKey(key));
|
||||
value = this.source.getValue(this.scopedKey(key));
|
||||
}
|
||||
|
||||
return this.defaults[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 T>(key: TKey, value: T[TKey]): void {
|
||||
super.setValue(key, value);
|
||||
this.source.setValue(this.scopedKey(key), value);
|
||||
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)}`;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
super.clear();
|
||||
this.source.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export enum ESettingsValueType {
|
||||
Checkbox,
|
||||
Select,
|
||||
Textarea,
|
||||
Input,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 { TLocalizationToken } from '@cloudbeaver/core-localization';
|
||||
import type { schema } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ESettingsValueType } from './ESettingsValueType';
|
||||
import type { SettingsGroup } from './SettingsGroup';
|
||||
|
||||
export interface ISettingOptions {
|
||||
id: string;
|
||||
name: TLocalizationToken;
|
||||
}
|
||||
|
||||
export interface ISettingDescription<T = object> {
|
||||
key: keyof T;
|
||||
|
||||
type: ESettingsValueType;
|
||||
|
||||
group: SettingsGroup;
|
||||
name: TLocalizationToken;
|
||||
description?: TLocalizationToken;
|
||||
options?: ISettingOptions[];
|
||||
}
|
||||
|
||||
export interface ISettingDescriptionWithScope<T = Record<string, any>> extends ISettingDescription<T> {
|
||||
scope: string;
|
||||
schema: schema.AnyZodObject;
|
||||
}
|
||||
|
||||
export type SettingsDescriptionGetter<T> = () => ISettingDescription<T>[];
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user