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:
Ainur
2024-01-31 20:33:05 +03:00
committed by GitHub
co-authored by Alexey Evgenia Bezborodova
parent 11adc7d3d6
commit fdde4348a2
355 changed files with 4429 additions and 2183 deletions
@@ -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")
}
@@ -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;
@@ -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
@@ -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

@@ -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"
}
+2 -3
View File
@@ -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
View File
@@ -1,5 +1,4 @@
// Services
export * from './CoreSettingsService';
export * from './AppScreen/AppScreenService';
export * from './AppScreen/AppScreenBootstrap';
+1 -2
View File
@@ -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],
};
+2 -5
View File
@@ -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', 'Значение не валидно'],
];
+21 -21
View File
@@ -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,
+16 -7
View File
@@ -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",
@@ -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');
@@ -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"
@@ -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';
+2 -1
View File
@@ -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',
-1
View File
@@ -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'],
+2 -2
View File
@@ -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"
}
}
+4
View File
@@ -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;
+18 -6
View File
@@ -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"
},
+3 -1
View File
@@ -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