From 36c089ed066fde784fc3b4d80532f689dc4a24ca Mon Sep 17 00:00:00 2001 From: Alexander Skoblikov Date: Thu, 8 Aug 2024 22:12:50 +0300 Subject: [PATCH 1/7] CB-5375 auto migration to postgres DB for default CB EE configuration (#2809) * CB-5375 auto migration to postgres DB for default CB EE configuration * CB-5375 fix test * CB-5375 move legacy db * CB-5474 async qm data migration * CB-5375 comment unused field --------- Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../src/io/cloudbeaver/DBWebException.java | 1 + .../cloudbeaver/model/app/WebApplication.java | 4 ++ .../schema/service.core.graphqls | 1 + .../DBWebExceptionServerNotInitialized.java | 27 +++++++++++ .../src/io/cloudbeaver/WebAction.java | 2 + .../io/cloudbeaver/server/CBApplication.java | 28 ++++++++++- .../CBServerConfigurationController.java | 9 ++-- .../service/WebServiceBindingBase.java | 7 ++- .../service/core/DBWServiceCore.java | 2 +- .../META-INF/MANIFEST.MF | 2 +- .../EmbeddedSecurityControllerFactory.java | 47 ++++++++++++++----- .../service/security/db/CBDatabase.java | 20 ++++++-- .../security/db/WebDatabaseConfig.java | 7 +-- .../test/platform/SQLQueryTranslatorTest.java | 6 ++- 14 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWebExceptionServerNotInitialized.java diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWebException.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWebException.java index e0e278a13f..f4cc8e50ea 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWebException.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWebException.java @@ -38,6 +38,7 @@ public class DBWebException extends DBException implements GraphQLError { public static final String ERROR_CODE_SESSION_EXPIRED = "sessionExpired"; public static final String ERROR_CODE_ACCESS_DENIED = "accessDenied"; + public static final String ERROR_CODE_SERVER_NOT_INITIALIZED = "serverNotInitialized"; public static final String ERROR_CODE_LICENSE_DENIED = "licenseRequired"; public static final String ERROR_CODE_IDENT_REQUIRED = "identRequired"; public static final String ERROR_CODE_AUTH_REQUIRED = "authRequired"; diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java index 6e14a4e705..6e5a2ccf49 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java @@ -42,6 +42,10 @@ public interface WebApplication extends DBPApplication { boolean isConfigurationMode(); + default boolean isInitializationMode() { + return false; + } + WebAppConfiguration getAppConfiguration(); WebServerConfiguration getServerConfiguration(); diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 622b05fa23..7bdc0106e7 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -148,6 +148,7 @@ type ServerConfig { localHostAddress: String configurationMode: Boolean! + # initializationMode: Boolean! @since(version: "24.1.5") developmentMode: Boolean! redirectOnFederatedAuth: Boolean! distributed: Boolean! diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWebExceptionServerNotInitialized.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWebExceptionServerNotInitialized.java new file mode 100644 index 0000000000..610d7c414e --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWebExceptionServerNotInitialized.java @@ -0,0 +1,27 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver; + +/** + * "Access denied" exception + */ +public class DBWebExceptionServerNotInitialized extends DBWebException { + + public DBWebExceptionServerNotInitialized(String message) { + super(message, ERROR_CODE_SERVER_NOT_INITIALIZED); + } +} diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java index b0aa55ea64..873864f142 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebAction.java @@ -33,4 +33,6 @@ boolean authRequired() default true; + boolean initializationRequired() default true; + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index ff1d0d6017..32cc1ac346 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -69,6 +69,7 @@ import java.security.Policy; import java.security.ProtectionDomain; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; /** * This class controls all aspects of the application's execution @@ -108,6 +109,8 @@ public static CBApplication getInstance() { private WebSessionManager sessionManager; + private final Map initActions = new ConcurrentHashMap<>(); + public CBApplication() { this.homeDirectory = new File(initHomeFolder()); } @@ -552,7 +555,8 @@ public synchronized void finishConfiguration( reloadConfiguration(credentialsProvider); } - public synchronized void reloadConfiguration(@Nullable SMCredentialsProvider credentialsProvider) throws DBException { + public synchronized void reloadConfiguration(@Nullable SMCredentialsProvider credentialsProvider) + throws DBException { // Re-load runtime configuration try { Path runtimeAppConfigPath = getServerConfigurationController().getRuntimeAppConfigPath(); @@ -717,7 +721,10 @@ public Class getPlatformUIClass() { return CBPlatformUI.class; } - public void saveProductConfiguration(SMCredentialsProvider credentialsProvider, Map productConfiguration) throws DBException { + public void saveProductConfiguration( + SMCredentialsProvider credentialsProvider, + Map productConfiguration + ) throws DBException { getServerConfigurationController().saveProductConfiguration(productConfiguration); flushConfiguration(credentialsProvider); sendConfigChangedEvent(credentialsProvider); @@ -750,4 +757,21 @@ private void refreshDisabledDriversConfig() { public boolean isEnvironmentVariablesAccessible() { return getAppConfiguration().isSystemVariablesResolvingEnabled(); } + + @Override + public boolean isInitializationMode() { + return !initActions.isEmpty(); + } + + public void addInitAction(@NotNull String actionId, @NotNull String description) { + initActions.put(actionId, description); + } + + public void removeInitAction(@NotNull String actionId) { + initActions.remove(actionId); + } + + public Map getInitActions() { + return Map.copyOf(initActions); + } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java index 40d925de13..b4233baf3f 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBServerConfigurationController.java @@ -330,7 +330,7 @@ protected GsonBuilder getGsonBuilder() { .registerTypeAdapter(PasswordPolicyConfiguration.class, smPasswordPoliceConfigCreator); } - protected void saveRuntimeConfig(SMCredentialsProvider credentialsProvider) throws DBException { + public synchronized void saveRuntimeConfig(SMCredentialsProvider credentialsProvider) throws DBException { saveRuntimeConfig( serverConfiguration, appConfiguration, @@ -338,7 +338,7 @@ protected void saveRuntimeConfig(SMCredentialsProvider credentialsProvider) thro ); } - protected void saveRuntimeConfig( + protected synchronized void saveRuntimeConfig( @NotNull CBServerConfig serverConfig, @NotNull CBAppConfig appConfig, SMCredentialsProvider credentialsProvider @@ -350,7 +350,7 @@ protected void saveRuntimeConfig( writeRuntimeConfig(getRuntimeAppConfigPath(), configurationProperties); } - private void writeRuntimeConfig(Path runtimeConfigPath, Map configurationProperties) + private synchronized void writeRuntimeConfig(Path runtimeConfigPath, Map configurationProperties) throws DBException { if (Files.exists(runtimeConfigPath)) { ContentUtils.makeFileBackup(runtimeConfigPath); @@ -370,7 +370,8 @@ private void writeRuntimeConfig(Path runtimeConfigPath, Map conf } - public void updateServerUrl(@NotNull SMCredentialsProvider credentialsProvider, @Nullable String newPublicUrl) throws DBException { + public synchronized void updateServerUrl(@NotNull SMCredentialsProvider credentialsProvider, + @Nullable String newPublicUrl) throws DBException { getServerConfiguration().setServerURL(newPublicUrl); } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java index ac61c1f9c0..7a85a39e97 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java @@ -250,6 +250,12 @@ private void checkServicePermissions(Method method, WebActionSet actionSet) thro } private void checkActionPermissions(@NotNull Method method, @NotNull WebAction webAction) throws DBWebException { + CBApplication application = CBApplication.getInstance(); + if (application.isInitializationMode() && webAction.initializationRequired()) { + String message = "Server initialization in progress: " + + String.join(",", application.getInitActions().values()) + ".\nDo not restart the server."; + throw new DBWebExceptionServerNotInitialized(message); + } String[] reqPermissions = webAction.requirePermissions(); if (reqPermissions.length == 0 && !webAction.authRequired()) { return; @@ -258,7 +264,6 @@ private void checkActionPermissions(@NotNull Method method, @NotNull WebAction w if (session == null) { throw new DBWebExceptionAccessDenied("No open session - anonymous access restricted"); } - CBApplication application = CBApplication.getInstance(); if (!application.isConfigurationMode()) { if (webAction.authRequired() && !session.isAuthorizedInSecurityManager()) { log.debug("Anonymous access to " + method.getName() + " restricted"); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java index cabced5886..48f78fbe7a 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java @@ -38,7 +38,7 @@ */ public interface DBWServiceCore extends DBWService { - @WebAction(authRequired = false) + @WebAction(authRequired = false, initializationRequired = false) WebServerConfig getServerConfig() throws DBWebException; @WebAction(authRequired = false) diff --git a/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF index 7dd72ecd6c..a7d81b179c 100644 --- a/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.service.security/META-INF/MANIFEST.MF @@ -13,7 +13,7 @@ Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport, org.jkiss.dbeaver.model.sql, org.jkiss.dbeaver.model.sql.jdbc, org.jkiss.dbeaver.registry;visibility:=reexport, - org.jkiss.bundle.apache.dbcp, + org.jkiss.bundle.apache.dbcp;visibility:=reexport, io.cloudbeaver.model Export-Package: io.cloudbeaver.auth.provider.local, io.cloudbeaver.auth.provider.rp, diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java index 6b5992918c..6c7b581b13 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/EmbeddedSecurityControllerFactory.java @@ -16,10 +16,12 @@ */ package io.cloudbeaver.service.security; +import io.cloudbeaver.auth.NoAuthCredentialsProvider; import io.cloudbeaver.model.app.WebAuthApplication; import io.cloudbeaver.service.security.db.CBDatabase; import io.cloudbeaver.service.security.db.WebDatabaseConfig; import io.cloudbeaver.service.security.internal.ClearAuthAttemptInfoJob; +import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; @@ -36,7 +38,7 @@ public static CBDatabase getDbInstance() { /** * Create new security controller instance with custom configuration */ - public CBEmbeddedSecurityController createSecurityService( + public CBEmbeddedSecurityController createSecurityService( T application, WebDatabaseConfig databaseConfig, SMCredentialsProvider credentialsProvider, @@ -45,32 +47,53 @@ public CBEmbeddedSecurityController createSecurityService( if (DB_INSTANCE == null) { synchronized (EmbeddedSecurityControllerFactory.class) { if (DB_INSTANCE == null) { - DB_INSTANCE = new CBDatabase(application, databaseConfig); + DB_INSTANCE = createAndInitDatabaseInstance( + application, + databaseConfig, + smConfig + ); } } - var securityController = createEmbeddedSecurityController( - application, DB_INSTANCE, credentialsProvider, smConfig - ); - //FIXME circular dependency - DB_INSTANCE.setAdminSecurityController(securityController); - DB_INSTANCE.initialize(); + if (application.isLicenseRequired()) { // delete expired auth info job in enterprise products - new ClearAuthAttemptInfoJob(securityController).schedule(); + new ClearAuthAttemptInfoJob(createEmbeddedSecurityController( + application, DB_INSTANCE, new NoAuthCredentialsProvider(), smConfig + )).schedule(); } - return securityController; } return createEmbeddedSecurityController( application, DB_INSTANCE, credentialsProvider, smConfig ); } - protected CBEmbeddedSecurityController createEmbeddedSecurityController( + protected @NotNull CBDatabase createAndInitDatabaseInstance( + @NotNull T application, + @NotNull WebDatabaseConfig databaseConfig, + @NotNull SMControllerConfiguration smConfig + ) throws DBException { + var database = new CBDatabase(application, databaseConfig); + var securityController = createEmbeddedSecurityController( + application, database, new NoAuthCredentialsProvider(), smConfig + ); + //FIXME circular dependency + database.setAdminSecurityController(securityController); + try { + database.initialize(); + } catch (DBException e) { + database.shutdown(); + throw e; + } + + return database; + } + + protected CBEmbeddedSecurityController createEmbeddedSecurityController( T application, CBDatabase database, SMCredentialsProvider credentialsProvider, SMControllerConfiguration smConfig ) { - return new CBEmbeddedSecurityController(application, database, credentialsProvider, smConfig); + return new CBEmbeddedSecurityController(application, database, credentialsProvider, smConfig); } } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java index ea1a858221..0c373781e4 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/CBDatabase.java @@ -475,7 +475,7 @@ public void close() throws SQLException { // Persistence - private void validateInstancePersistentState(Connection connection) throws IOException, SQLException, DBException { + protected void validateInstancePersistentState(Connection connection) throws IOException, SQLException, DBException { try (JDBCTransaction txn = new JDBCTransaction(connection)) { checkInstanceRecord(connection); var defaultTeamId = application.getAppConfiguration().getDefaultUserTeam(); @@ -587,10 +587,24 @@ public SQLDialect getDialect() { } public static boolean isDefaultH2Configuration(WebDatabaseConfig databaseConfiguration) { - var v1DefaultUrl = "jdbc:h2:/opt/cloudbeaver/workspace/.data/" + V1_DB_NAME; - var v2DefaultUrl = "jdbc:h2:/opt/cloudbeaver/workspace/.data/" + V2_DB_NAME; + var workspace = WebAppUtils.getWebApplication().getWorkspaceDirectory(); + var v1Path = workspace.resolve(".data").resolve(V1_DB_NAME); + var v2Path = workspace.resolve(".data").resolve(V2_DB_NAME); + var v1DefaultUrl = "jdbc:h2:" + v1Path; + var v2DefaultUrl = "jdbc:h2:" + v2Path; return v1DefaultUrl.equals(databaseConfiguration.getUrl()) || v2DefaultUrl.equals(databaseConfiguration.getUrl()); } + protected WebDatabaseConfig getDatabaseConfiguration() { + return databaseConfiguration; + } + + protected WebApplication getApplication() { + return application; + } + + protected SMAdminController getAdminSecurityController() { + return adminSecurityController; + } } diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/WebDatabaseConfig.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/WebDatabaseConfig.java index fb7243462a..27bf86f04f 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/WebDatabaseConfig.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/db/WebDatabaseConfig.java @@ -63,6 +63,7 @@ public String getUser() { return user; } + @Override public String getPassword() { return password; @@ -85,15 +86,15 @@ public String getSchema() { return schema; } - void setPassword(String password) { + public void setPassword(String password) { this.password = password; } - void setSchema(String schema) { + public void setSchema(String schema) { this.schema = schema; } - void setUser(String user) { + public void setUser(String user) { this.user = user; } } diff --git a/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/SQLQueryTranslatorTest.java b/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/SQLQueryTranslatorTest.java index 3c06bbce97..416aaa8656 100644 --- a/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/SQLQueryTranslatorTest.java +++ b/server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/SQLQueryTranslatorTest.java @@ -162,8 +162,10 @@ public void createTableWithAutoincrement() throws DBException { Map expectedSqlByDialect = new HashMap<>(); expectedSqlByDialect.put(new H2SQLDialect(), basicSql); expectedSqlByDialect.put(new PostgreDialect(), "CREATE SEQUENCE CB_TEST_TYPES_AUTOINC_COLUMN;\n" + - "CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN BIGINT NOT NULL DEFAULT NEXTVAL('CB_TEST_TYPES_AUTOINC_COLUMN'));" + - "\n"); + "CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN BIGINT NOT NULL DEFAULT NEXTVAL" + + "('CB_TEST_TYPES_AUTOINC_COLUMN'));\n" + + "ALTER SEQUENCE CB_TEST_TYPES_AUTOINC_COLUMN OWNED BY CB_TEST_TYPES.AUTOINC_COLUMN;\n" + ); expectedSqlByDialect.put(new MySQLDialect(), basicSql); expectedSqlByDialect.put( From 8d14e42797a0869f032d0e314317e3ed48f9d6b9 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Fri, 9 Aug 2024 13:30:32 +0200 Subject: [PATCH 2/7] CB-4068 adds driver libraries resource (#2826) Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> --- webapp/packages/core-connections/src/DBDriverResource.ts | 1 - .../core-sdk/src/queries/connections/driverLibraries.gql | 9 +++++++++ .../core-sdk/src/queries/connections/driverList.gql | 1 - .../core-sdk/src/queries/fragments/DatabaseDriver.gql | 6 ------ 4 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 webapp/packages/core-sdk/src/queries/connections/driverLibraries.gql diff --git a/webapp/packages/core-connections/src/DBDriverResource.ts b/webapp/packages/core-connections/src/DBDriverResource.ts index 351a63f5f2..68af6b6ee8 100644 --- a/webapp/packages/core-connections/src/DBDriverResource.ts +++ b/webapp/packages/core-connections/src/DBDriverResource.ts @@ -109,7 +109,6 @@ export class DBDriverResource extends CachedMapResource Date: Mon, 12 Aug 2024 13:12:18 +0200 Subject: [PATCH 3/7] Cb 5378 dm ability to use custom ssl certificate (#2808) * CB-5378 add getDomainFromUrl util function * CB-5378 add uplodable prop to textarea --------- Co-authored-by: naumov Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> --- .../src/FormControls/Textarea.module.css | 4 ++ .../core-blocks/src/FormControls/Textarea.tsx | 43 ++++++++++++++++--- .../core-utils/src/getDomainFromUrl.ts | 17 ++++++++ webapp/packages/core-utils/src/index.ts | 1 + 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 webapp/packages/core-utils/src/getDomainFromUrl.ts diff --git a/webapp/packages/core-blocks/src/FormControls/Textarea.module.css b/webapp/packages/core-blocks/src/FormControls/Textarea.module.css index 715c8b9d85..81e048286c 100644 --- a/webapp/packages/core-blocks/src/FormControls/Textarea.module.css +++ b/webapp/packages/core-blocks/src/FormControls/Textarea.module.css @@ -30,3 +30,7 @@ display: none; } } + +.uploadButton { + margin-top: 4px; +} diff --git a/webapp/packages/core-blocks/src/FormControls/Textarea.tsx b/webapp/packages/core-blocks/src/FormControls/Textarea.tsx index fe46613168..0e93bf3b7d 100644 --- a/webapp/packages/core-blocks/src/FormControls/Textarea.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Textarea.tsx @@ -8,9 +8,14 @@ import { observer } from 'mobx-react-lite'; import { useCallback, useContext, useLayoutEffect, useRef } from 'react'; +import { getTextFileReadingProcess } from '@cloudbeaver/core-utils'; + +import { Button } from '../Button'; import { filterLayoutFakeProps, getLayoutProps } from '../Containers/filterLayoutFakeProps'; import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; +import { useTranslate } from '../localization/useTranslate'; import { s } from '../s'; +import { UploadArea } from '../UploadArea'; import { useS } from '../useS'; import { Field } from './Field'; import { FieldDescription } from './FieldDescription'; @@ -24,6 +29,7 @@ type BaseProps = Omit, 'onChan labelTooltip?: string; embedded?: boolean; cursorInitiallyAtEnd?: boolean; + uploadable?: boolean; }; type ControlledProps = BaseProps & { @@ -56,9 +62,11 @@ export const Textarea: TextareaType = observer(function Textarea({ labelTooltip, embedded, cursorInitiallyAtEnd, + uploadable, onChange = () => {}, ...rest }: ControlledProps | ObjectProps) { + const translate = useTranslate(); const textareaRef = useRef(null); const layoutProps = getLayoutProps(rest); rest = filterLayoutFakeProps(rest); @@ -66,15 +74,15 @@ export const Textarea: TextareaType = observer(function Textarea({ const context = useContext(FormContext); const handleChange = useCallback( - (event: React.ChangeEvent) => { + (value: string) => { if (state) { - state[name] = event.target.value; + state[name] = value; } if (onChange) { - onChange(event.target.value, name); + onChange(value, name); } if (context) { - context.change(event.target.value, name); + context.change(value, name); } }, [state, name, onChange], @@ -102,9 +110,34 @@ export const Textarea: TextareaType = observer(function Textarea({ value={value ?? ''} name={name} data-embedded={embedded} - onChange={handleChange} + onChange={event => handleChange(event.target.value)} /> {description && {description}} + {uploadable && ( + { + const file = event.target.files?.[0]; + + if (!file) { + throw new Error('File is not found'); + } + + const process = getTextFileReadingProcess(file); + const value = await process.promise; + + if (value) { + handleChange(value); + } + }} + > + + + )} ); }); diff --git a/webapp/packages/core-utils/src/getDomainFromUrl.ts b/webapp/packages/core-utils/src/getDomainFromUrl.ts new file mode 100644 index 0000000000..dc0e6350d8 --- /dev/null +++ b/webapp/packages/core-utils/src/getDomainFromUrl.ts @@ -0,0 +1,17 @@ +/* + * 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 function getDomainFromUrl(url: string): string { + try { + const urlObject = new URL(url); + return urlObject.hostname; + } catch (e) { + console.error('Invalid URL:', e); + return ''; + } +} diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index 8f1c2d4534..a821ad96d9 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -87,3 +87,4 @@ export * from './toSafeHtmlString'; export * from './getProgressPercent'; export * from './types/UndefinedToNull'; export * from './bindFunctions'; +export * from './getDomainFromUrl'; From de00a60aadcfb3d1b7ffc961c7d91409d6bf8a5b Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:14:27 +0200 Subject: [PATCH 4/7] CB-4511 clear secure props if save password is disabled (#2835) Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../src/io/cloudbeaver/utils/WebDataSourceUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java index 6c402c6eef..aba7767b06 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java @@ -97,6 +97,10 @@ public static void updateHandlerConfig(DBWHandlerConfiguration handlerConfig, We private static void setSecureProperties(DBWHandlerConfiguration handlerConfig, WebNetworkHandlerConfigInput cfgInput, boolean ignoreNulls) { var secureProperties = cfgInput.getSecureProperties(); if (secureProperties == null) { + if (!handlerConfig.isSavePassword()) { + // clear all secure properties from handler config + handlerConfig.setSecureProperties(Map.of()); + } return; } for (var pr : secureProperties.entrySet()) { From 2d45b29a07c872e2938449d96a72b07ae541bda7 Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:14:52 +0200 Subject: [PATCH 5/7] CB-5025 update cloudbeaver features fix for ai (#2823) Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> --- .../src/io/cloudbeaver/service/DBWServiceInitializer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/DBWServiceInitializer.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/DBWServiceInitializer.java index cafe4bf624..23bf4529fc 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/DBWServiceInitializer.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/DBWServiceInitializer.java @@ -16,14 +16,14 @@ */ package io.cloudbeaver.service; +import io.cloudbeaver.server.CBApplication; import org.jkiss.dbeaver.DBException; -import org.jkiss.dbeaver.model.app.DBPApplication; /** * Web service implementation */ public interface DBWServiceInitializer extends DBWServiceBinding { - void initializeService(DBPApplication application) throws DBException; + void initializeService(CBApplication application) throws DBException; } From 92546849f8283ae5ca36d2982c63530eff0b9b8b Mon Sep 17 00:00:00 2001 From: DenisSinelnikov <142215442+DenisSinelnikov@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:16:56 +0400 Subject: [PATCH 6/7] =?UTF-8?q?CB-5123.=20Added=20backend=20part,=20added?= =?UTF-8?q?=20new=20parameter=20licenseStatus=20to=20ser=E2=80=A6=20(#2831?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CB-5123. Added backend part, added new parameter licenseStatus to server config gql * CB-5123 feat: display license status information * CB-5123 fix: after review * CB-5123. Added version --------- Co-authored-by: Aleksei Potsetsuev Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../schema/service.core.graphqls | 1 + .../io/cloudbeaver/model/WebServerConfig.java | 5 + .../AdministrationScreenService.ts | 2 +- .../src/PasswordPolicyService.ts | 15 ++- .../core-blocks/src/usePasswordValidation.ts | 3 + .../core-events/src/NotificationService.ts | 53 +++++---- .../core-projects/src/ProjectInfoResource.ts | 6 +- .../src/DefaultNavigatorSettingsResource.ts | 103 +++++++++++++++++ .../core-root/src/PasswordPolicyResource.ts | 31 +++++ .../core-root/src/PermissionsService.ts | 25 ++++- .../core-root/src/ProductInfoResource.ts | 31 +++++ .../packages/core-root/src/QuotasService.ts | 9 +- .../core-root/src/ServerConfigResource.ts | 106 ++---------------- .../src/ServerLicenseStatusResource.ts | 39 +++++++ .../src/ServerResourceQuotasResource.ts | 31 +++++ webapp/packages/core-root/src/index.ts | 5 + webapp/packages/core-root/src/manifest.ts | 5 + .../ServerConfig/DefaultNavigatorSettings.gql | 5 + .../fragments/ServerConfig/PasswordPolicy.gql | 8 ++ .../fragments/ServerConfig/ProductInfo.gql | 12 ++ .../fragments/ServerConfig/ResourceQuotas.gql | 3 + .../fragments/ServerConfig/ServerConfig.gql | 35 ++++++ .../ServerConfig/ServerLicenseStatus.gql | 5 + .../session/getDefaultNavigatorSettings.gql | 5 + .../src/queries/session/getPasswordPolicy.gql | 5 + .../src/queries/session/getProductInfo.gql | 5 + .../session/getServerLicenseStatus.gql | 5 + .../session/getServerResourceQuotas.gql | 5 + .../src/queries/session/serverConfig.gql | 60 +--------- .../core-version/src/VersionResource.ts | 8 +- .../ServerConfigurationPage.tsx | 5 +- .../ServerConfigurationService.ts | 22 +++- ...verConfigurationAuthenticationBootstrap.ts | 4 +- .../ResultSet/ResultSetDataContentAction.ts | 17 ++- .../plugin-product/src/ProductBootstrap.ts | 6 +- .../plugin-product/src/ProductInfoDialog.tsx | 6 +- .../plugin-top-app-bar/src/TopNavBar/Logo.tsx | 8 +- 37 files changed, 487 insertions(+), 212 deletions(-) create mode 100644 webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts create mode 100644 webapp/packages/core-root/src/PasswordPolicyResource.ts create mode 100644 webapp/packages/core-root/src/ProductInfoResource.ts create mode 100644 webapp/packages/core-root/src/ServerLicenseStatusResource.ts create mode 100644 webapp/packages/core-root/src/ServerResourceQuotasResource.ts create mode 100644 webapp/packages/core-sdk/src/queries/fragments/ServerConfig/DefaultNavigatorSettings.gql create mode 100644 webapp/packages/core-sdk/src/queries/fragments/ServerConfig/PasswordPolicy.gql create mode 100644 webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ProductInfo.gql create mode 100644 webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ResourceQuotas.gql create mode 100644 webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql create mode 100644 webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerLicenseStatus.gql create mode 100644 webapp/packages/core-sdk/src/queries/session/getDefaultNavigatorSettings.gql create mode 100644 webapp/packages/core-sdk/src/queries/session/getPasswordPolicy.gql create mode 100644 webapp/packages/core-sdk/src/queries/session/getProductInfo.gql create mode 100644 webapp/packages/core-sdk/src/queries/session/getServerLicenseStatus.gql create mode 100644 webapp/packages/core-sdk/src/queries/session/getServerResourceQuotas.gql diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 7bdc0106e7..e89cd4bbfa 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -143,6 +143,7 @@ type ServerConfig { licenseRequired: Boolean! licenseValid: Boolean! + licenseStatus: String @since(version: "24.1.5") sessionExpireTime: Int! localHostAddress: String diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java index d4f09a9ce6..826331eea4 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java @@ -121,6 +121,11 @@ public boolean isLicenseValid() { return application.isLicenseValid(); } + @Property + public String getLicenseStatus() { + return application.getLicenseStatus(); + } + @Property public boolean isConfigurationMode() { return application.isConfigurationMode(); diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts index b0e247f2cc..9c8ff331f1 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts @@ -50,7 +50,7 @@ export class AdministrationScreenService { } get publicDisabled(): boolean { - return this.serverConfigResource.publicDisabled; + return this.permissionsService.publicDisabled; } readonly ensurePermissions: IExecutor; diff --git a/webapp/packages/core-authentication/src/PasswordPolicyService.ts b/webapp/packages/core-authentication/src/PasswordPolicyService.ts index 480e11bcec..12efcaf6a3 100644 --- a/webapp/packages/core-authentication/src/PasswordPolicyService.ts +++ b/webapp/packages/core-authentication/src/PasswordPolicyService.ts @@ -9,7 +9,7 @@ import { computed, makeObservable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { LocalizationService } from '@cloudbeaver/core-localization'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { PasswordPolicyResource } from '@cloudbeaver/core-root'; import type { PasswordPolicyConfig } from '@cloudbeaver/core-sdk'; const DEFAULT_PASSWORD_POLICY: PasswordPolicyConfig = { @@ -25,15 +25,15 @@ type ValidationResult = { isValid: true; errorMessage: null } | { isValid: false export class PasswordPolicyService { get config(): PasswordPolicyConfig { return { - minLength: this.serverConfigResource.data?.passwordPolicyConfiguration?.minLength || DEFAULT_PASSWORD_POLICY.minLength, - minNumberCount: this.serverConfigResource.data?.passwordPolicyConfiguration?.minNumberCount || DEFAULT_PASSWORD_POLICY.minNumberCount, - minSymbolCount: this.serverConfigResource.data?.passwordPolicyConfiguration?.minSymbolCount || DEFAULT_PASSWORD_POLICY.minSymbolCount, - requireMixedCase: this.serverConfigResource.data?.passwordPolicyConfiguration?.requireMixedCase || DEFAULT_PASSWORD_POLICY.requireMixedCase, + minLength: this.passwordPolicyResource.data?.minLength || DEFAULT_PASSWORD_POLICY.minLength, + minNumberCount: this.passwordPolicyResource.data?.minNumberCount || DEFAULT_PASSWORD_POLICY.minNumberCount, + minSymbolCount: this.passwordPolicyResource.data?.minSymbolCount || DEFAULT_PASSWORD_POLICY.minSymbolCount, + requireMixedCase: this.passwordPolicyResource.data?.requireMixedCase || DEFAULT_PASSWORD_POLICY.requireMixedCase, }; } constructor( - private readonly serverConfigResource: ServerConfigResource, + private readonly passwordPolicyResource: PasswordPolicyResource, private readonly localizationService: LocalizationService, ) { makeObservable(this, { @@ -41,6 +41,9 @@ export class PasswordPolicyService { }); } + /** + * PasswordPolicyResource should be loaded before calling this method + */ validatePassword(password: string): ValidationResult { const trimmedPassword = password.trim(); diff --git a/webapp/packages/core-blocks/src/usePasswordValidation.ts b/webapp/packages/core-blocks/src/usePasswordValidation.ts index 1dc464a524..33d7b8903f 100644 --- a/webapp/packages/core-blocks/src/usePasswordValidation.ts +++ b/webapp/packages/core-blocks/src/usePasswordValidation.ts @@ -7,10 +7,13 @@ */ import { PasswordPolicyService } from '@cloudbeaver/core-authentication'; import { useService } from '@cloudbeaver/core-di'; +import { PasswordPolicyResource } from '@cloudbeaver/core-root'; import { useCustomInputValidation } from './FormControls/useCustomInputValidation'; +import { useResource } from './ResourcesHooks/useResource'; export function usePasswordValidation() { + useResource(usePasswordValidation, PasswordPolicyResource, undefined); const passwordPolicyService = useService(PasswordPolicyService); const ref = useCustomInputValidation(value => { diff --git a/webapp/packages/core-events/src/NotificationService.ts b/webapp/packages/core-events/src/NotificationService.ts index 410f51900f..95e49be844 100644 --- a/webapp/packages/core-events/src/NotificationService.ts +++ b/webapp/packages/core-events/src/NotificationService.ts @@ -80,26 +80,41 @@ export class NotificationService { const id = this.notificationNextId++; - const notification: INotification = { - id, - uuid: options.uuid, - title: options.title, - message: options.message, - details: options.details, - isSilent: !!options.isSilent, - customComponent: options.customComponent, - extraProps: options.extraProps || ({} as TProps), - autoClose: options.autoClose, - persistent: options.persistent, - state: observable({ deleteDelay: 0 }), - timestamp: options.timestamp || Date.now(), - type, - close: delayDeleting => { - this.close(id, delayDeleting); - options.onClose?.(delayDeleting); + const notification: INotification = observable( + { + id, + uuid: options.uuid, + title: options.title, + message: options.message, + details: options.details, + isSilent: !!options.isSilent, + customComponent: options.customComponent, + extraProps: options.extraProps || ({} as TProps), + autoClose: options.autoClose, + persistent: options.persistent, + state: observable({ deleteDelay: 0 }), + timestamp: options.timestamp || Date.now(), + type, + close: delayDeleting => { + this.close(id, delayDeleting); + options.onClose?.(delayDeleting); + }, + showDetails: this.showDetails.bind(this, id), + }, + { + title: observable.ref, + message: observable.ref, + details: observable.ref, + persistent: observable.ref, + isSilent: observable.ref, + customComponent: observable.ref, + extraProps: observable.ref, + autoClose: observable.ref, + type: observable.ref, + timestamp: observable.ref, + showDetails: observable.ref, }, - showDetails: this.showDetails.bind(this, id), - }; + ); this.notificationList.addValue(notification); diff --git a/webapp/packages/core-projects/src/ProjectInfoResource.ts b/webapp/packages/core-projects/src/ProjectInfoResource.ts index c38642c6d7..4e2d1ac296 100644 --- a/webapp/packages/core-projects/src/ProjectInfoResource.ts +++ b/webapp/packages/core-projects/src/ProjectInfoResource.ts @@ -8,7 +8,7 @@ import { AppAuthService, UserInfoResource } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; import { CachedMapAllKey, CachedMapResource, resourceKeyList } from '@cloudbeaver/core-resource'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { PermissionsService } from '@cloudbeaver/core-root'; import { GraphQLService, RmResourceType, ProjectInfo as SchemaProjectInfo } from '@cloudbeaver/core-sdk'; import { createResourceOfType } from './createResourceOfType'; @@ -21,7 +21,7 @@ export class ProjectInfoResource extends CachedMapResource constructor( private readonly graphQLService: GraphQLService, private readonly userInfoResource: UserInfoResource, - serverConfigResource: ServerConfigResource, + permissionsService: PermissionsService, appAuthService: AppAuthService, ) { super(() => new Map(), []); @@ -32,7 +32,7 @@ export class ProjectInfoResource extends CachedMapResource () => CachedMapAllKey, ); appAuthService.requireAuthentication(this); - serverConfigResource.requirePublic(this); + permissionsService.requirePublic(this); this.userInfoResource.onUserChange.addPostHandler(() => { this.clear(); }); diff --git a/webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts b/webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts new file mode 100644 index 0000000000..5d4312d815 --- /dev/null +++ b/webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts @@ -0,0 +1,103 @@ +/* + * 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 { action, makeObservable, observable } from 'mobx'; + +import { injectable } from '@cloudbeaver/core-di'; +import { CachedDataResource } from '@cloudbeaver/core-resource'; +import { DefaultNavigatorSettingsFragment, GraphQLService, NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; + +import { isNavigatorViewSettingsEqual } from './ConnectionNavigatorViewSettings'; +import { ServerConfigResource } from './ServerConfigResource'; + +export type DefaultNavigatorSettings = DefaultNavigatorSettingsFragment['defaultNavigatorSettings']; + +@injectable() +export class DefaultNavigatorSettingsResource extends CachedDataResource { + update: NavigatorSettingsInput; + + constructor( + private readonly graphQLService: GraphQLService, + serverConfigResource: ServerConfigResource, + ) { + super(() => null, undefined, []); + this.sync(serverConfigResource); + + this.update = getDefaultNavigatorSettings(); + + makeObservable(this, { + update: observable, + unlinkUpdate: action, + syncUpdateData: action, + }); + } + + isChanged(): boolean { + if (!this.data) { + return false; + } + + return !isNavigatorViewSettingsEqual(this.data, this.update); + } + + setDataUpdate(update: NavigatorSettingsInput): void { + this.update = update; + } + + resetUpdate(): void { + if (this.data) { + this.syncUpdateData(this.data); + } + } + + unlinkUpdate(): void { + if (this.data) { + Object.assign(this.update, this.data); + } else { + this.update = getDefaultNavigatorSettings(); + } + } + + async save(): Promise { + await this.performUpdate( + undefined, + undefined, + async () => { + await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: this.update }); + + this.setData(await this.loader()); + + this.onDataOutdated.execute(); + }, + () => !this.isChanged(), + ); + } + + protected async loader(): Promise { + const { defaultNavigatorSettings } = await this.graphQLService.sdk.getDefaultNavigatorSettings(); + + this.syncUpdateData(defaultNavigatorSettings.defaultNavigatorSettings); + + return defaultNavigatorSettings.defaultNavigatorSettings; + } + + private syncUpdateData(defaultNavigatorSettings: DefaultNavigatorSettings) { + Object.assign(this.update, defaultNavigatorSettings); + } +} + +function getDefaultNavigatorSettings(): NavigatorSettingsInput { + return { + hideFolders: false, + hideSchemas: false, + hideVirtualModel: false, + mergeEntities: false, + showOnlyEntities: false, + showSystemObjects: false, + showUtilityObjects: false, + }; +} diff --git a/webapp/packages/core-root/src/PasswordPolicyResource.ts b/webapp/packages/core-root/src/PasswordPolicyResource.ts new file mode 100644 index 0000000000..d4d0c9fb8c --- /dev/null +++ b/webapp/packages/core-root/src/PasswordPolicyResource.ts @@ -0,0 +1,31 @@ +/* + * 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 { CachedDataResource } from '@cloudbeaver/core-resource'; +import { GraphQLService, PasswordPolicyFragment } from '@cloudbeaver/core-sdk'; + +import { ServerConfigResource } from './ServerConfigResource'; + +export type PasswordPolicy = PasswordPolicyFragment['passwordPolicyConfiguration']; + +@injectable() +export class PasswordPolicyResource extends CachedDataResource { + constructor( + private readonly graphQLService: GraphQLService, + serverConfigResource: ServerConfigResource, + ) { + super(() => null, undefined, []); + this.sync(serverConfigResource); + } + + protected async loader(): Promise { + const { passwordPolicy } = await this.graphQLService.sdk.getPasswordPolicy(); + + return passwordPolicy.passwordPolicyConfiguration; + } +} diff --git a/webapp/packages/core-root/src/PermissionsService.ts b/webapp/packages/core-root/src/PermissionsService.ts index 06dd86b598..a264ae1dae 100644 --- a/webapp/packages/core-root/src/PermissionsService.ts +++ b/webapp/packages/core-root/src/PermissionsService.ts @@ -6,14 +6,37 @@ * you may not use this file except in compliance with the License. */ import { injectable } from '@cloudbeaver/core-di'; +import { ExecutorInterrupter } from '@cloudbeaver/core-executor'; +import { CachedResource } from '@cloudbeaver/core-resource'; +import { ServerConfigResource } from './ServerConfigResource'; +import { ServerLicenseStatusResource } from './ServerLicenseStatusResource'; import { SessionPermissionsResource } from './SessionPermissionsResource'; export enum EPermission {} @injectable() export class PermissionsService { - constructor(private readonly permissions: SessionPermissionsResource) {} + get publicDisabled(): boolean { + return ( + this.serverConfigResource.configurationMode || + (this.serverLicenseStatusResource?.licenseRequired && !this.serverLicenseStatusResource.licenseValid) + ); + } + + constructor( + private readonly permissions: SessionPermissionsResource, + private readonly serverConfigResource: ServerConfigResource, + private readonly serverLicenseStatusResource: ServerLicenseStatusResource, + ) {} + + requirePublic(resource: CachedResource, map?: (param: void) => T): this { + resource.preloadResource(this.serverLicenseStatusResource, () => {}).before(ExecutorInterrupter.interrupter(() => this.publicDisabled)); + + this.serverLicenseStatusResource.outdateResource(resource, map as any); + + return this; + } has(id: string): boolean { return this.permissions.has(id); diff --git a/webapp/packages/core-root/src/ProductInfoResource.ts b/webapp/packages/core-root/src/ProductInfoResource.ts new file mode 100644 index 0000000000..fc25a1ea64 --- /dev/null +++ b/webapp/packages/core-root/src/ProductInfoResource.ts @@ -0,0 +1,31 @@ +/* + * 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 { CachedDataResource } from '@cloudbeaver/core-resource'; +import { GraphQLService, ProductInfoFragment } from '@cloudbeaver/core-sdk'; + +import { ServerConfigResource } from './ServerConfigResource'; + +export type ProductInfo = ProductInfoFragment['productInfo']; + +@injectable() +export class ProductInfoResource extends CachedDataResource { + constructor( + private readonly graphQLService: GraphQLService, + serverConfigResource: ServerConfigResource, + ) { + super(() => null, undefined, []); + this.sync(serverConfigResource); + } + + protected async loader(): Promise { + const { productInfo } = await this.graphQLService.sdk.getProductInfo(); + + return productInfo.productInfo; + } +} diff --git a/webapp/packages/core-root/src/QuotasService.ts b/webapp/packages/core-root/src/QuotasService.ts index 81ba0d3ba3..d52cb5f63a 100644 --- a/webapp/packages/core-root/src/QuotasService.ts +++ b/webapp/packages/core-root/src/QuotasService.ts @@ -7,7 +7,7 @@ */ import { injectable } from '@cloudbeaver/core-di'; -import { ServerConfigResource } from './ServerConfigResource'; +import { ServerResourceQuotasResource } from './ServerResourceQuotasResource'; interface IQuotas { dataExportFileSizeLimit: number; @@ -46,10 +46,13 @@ export class QuotasService { }; } - constructor(private readonly serverConfigResource: ServerConfigResource) {} + constructor(private readonly serverResourceQuotasResource: ServerResourceQuotasResource) {} + /** + * Quotas should be manually loaded from ServerResourceQuotasResource before using this method + */ getQuota(key: QuotaKey) { - const serverQuota = this.serverConfigResource.data?.resourceQuotas[key]; + const serverQuota = this.serverResourceQuotasResource.data?.resourceQuotas[key]; if (isNumber(serverQuota)) { return serverQuota; diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index b8ab5c11b3..9227081242 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -8,24 +8,21 @@ import { action, makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; -import { ExecutorInterrupter } from '@cloudbeaver/core-executor'; -import { CachedDataResource, type CachedResource } from '@cloudbeaver/core-resource'; -import { GraphQLService, NavigatorSettingsInput, ServerConfig as SDKServerConfig, ServerConfigInput } from '@cloudbeaver/core-sdk'; +import { CachedDataResource } from '@cloudbeaver/core-resource'; +import { GraphQLService, ServerConfigFragment, ServerConfigInput } from '@cloudbeaver/core-sdk'; import { isArraysEqual } from '@cloudbeaver/core-utils'; -import { isNavigatorViewSettingsEqual } from './ConnectionNavigatorViewSettings'; import { DataSynchronizationQueue } from './DataSynchronization/DataSynchronizationQueue'; import { DataSynchronizationService } from './DataSynchronization/DataSynchronizationService'; import { ServerConfigEventHandler } from './ServerConfigEventHandler'; export const FEATURE_GIT_ID = 'git'; -export type ServerConfig = Omit; +export type ServerConfig = ServerConfigFragment; @injectable() export class ServerConfigResource extends CachedDataResource { update: ServerConfigInput; - navigatorSettingsUpdate: NavigatorSettingsInput; private readonly syncQueue: DataSynchronizationQueue; @@ -42,19 +39,9 @@ export class ServerConfigResource extends CachedDataResource(this, { update: observable, - navigatorSettingsUpdate: observable, unlinkUpdate: action, syncUpdateData: action, }); @@ -69,14 +56,6 @@ export class ServerConfigResource extends CachedDataResource(resource: CachedResource, map?: (param: void) => T): this { - resource.preloadResource(this, () => {}).before(ExecutorInterrupter.interrupter(() => this.publicDisabled)); - - this.outdateResource(resource, map as any); - - return this; - } - get redirectOnFederatedAuth(): boolean { return this.data?.redirectOnFederatedAuth ?? false; } @@ -93,26 +72,10 @@ export class ServerConfigResource extends CachedDataResource { + async save(): Promise { await this.performUpdate( undefined, undefined, async () => { - if (this.isNavigatorSettingsChanged()) { - await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: this.navigatorSettingsUpdate }); - - if (this.data) { - this.data.defaultNavigatorSettings = { ...this.navigatorSettingsUpdate }; - } else { - this.setData(await this.loader()); - } - } - - if (this.isChanged() && !skipConfigUpdate) { - await this.graphQLService.sdk.configureServer({ - configuration: this.update, - }); - this.setData(await this.loader()); - } - + await this.graphQLService.sdk.configureServer({ + configuration: this.update, + }); + this.setData(await this.loader()); this.onDataOutdated.execute(); }, - () => !this.isNavigatorSettingsChanged() && (!this.isChanged() || skipConfigUpdate), + () => !this.isChanged(), ); } @@ -289,7 +205,7 @@ export class ServerConfigResource extends CachedDataResource { + constructor( + private readonly graphQLService: GraphQLService, + serverConfigResource: ServerConfigResource, + ) { + super(() => null, undefined, []); + this.sync(serverConfigResource); + } + + get licenseRequired(): boolean { + return this.data?.licenseRequired ?? false; + } + + get licenseValid(): boolean { + return this.data?.licenseValid ?? false; + } + + protected async loader(): Promise { + const { licenseStatus } = await this.graphQLService.sdk.getServerLicenseStatus(); + + return licenseStatus; + } +} diff --git a/webapp/packages/core-root/src/ServerResourceQuotasResource.ts b/webapp/packages/core-root/src/ServerResourceQuotasResource.ts new file mode 100644 index 0000000000..1af57e41f0 --- /dev/null +++ b/webapp/packages/core-root/src/ServerResourceQuotasResource.ts @@ -0,0 +1,31 @@ +/* + * 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 { CachedDataResource } from '@cloudbeaver/core-resource'; +import { GraphQLService, ResourceQuotasFragment } from '@cloudbeaver/core-sdk'; + +import { ServerConfigResource } from './ServerConfigResource'; + +export type ServerResourceQuotas = ResourceQuotasFragment['resourceQuotas']; + +@injectable() +export class ServerResourceQuotasResource extends CachedDataResource { + constructor( + private readonly graphQLService: GraphQLService, + serverConfigResource: ServerConfigResource, + ) { + super(() => null, undefined, []); + this.sync(serverConfigResource); + } + + protected async loader(): Promise { + const { resourceQuotas } = await this.graphQLService.sdk.getServerResourceQuotas(); + + return resourceQuotas.resourceQuotas; + } +} diff --git a/webapp/packages/core-root/src/index.ts b/webapp/packages/core-root/src/index.ts index 5531ad75e1..386e0627e4 100644 --- a/webapp/packages/core-root/src/index.ts +++ b/webapp/packages/core-root/src/index.ts @@ -12,11 +12,14 @@ export * from './DataSynchronization/SynchronizationMessage'; export * from './ServerEventEmitter/IServerEventEmitter'; export * from './ServerEventEmitter/TopicEventHandler'; export * from './ConnectionNavigatorViewSettings'; +export * from './DefaultNavigatorSettingsResource'; export * from './FeaturesResource'; export * from './NetworkError'; export * from './NetworkStateService'; +export * from './PasswordPolicyResource'; export * from './SessionPermissionsResource'; export * from './PermissionsService'; +export * from './ProductInfoResource'; export * from './ServerConfigResource'; export * from './SessionEventSource'; export * from './QuotasService'; @@ -31,5 +34,7 @@ export * from './SessionExpireService'; export * from './SessionExpireEventService'; export * from './SessionActivityService'; export * from './ServerNodeService'; +export * from './ServerResourceQuotasResource'; export * from './WindowEventsService'; +export * from './ServerLicenseStatusResource'; export * from './manifest'; diff --git a/webapp/packages/core-root/src/manifest.ts b/webapp/packages/core-root/src/manifest.ts index 96d3d7e560..9a3e3b6dbe 100644 --- a/webapp/packages/core-root/src/manifest.ts +++ b/webapp/packages/core-root/src/manifest.ts @@ -36,5 +36,10 @@ export const coreRootManifest: PluginManifest = { () => import('./Settings/ServerSettingsResource').then(m => m.ServerSettingsResource), () => import('./Settings/ServerSettingsManagerService').then(m => m.ServerSettingsManagerService), () => import('./RootBootstrap').then(m => m.RootBootstrap), + () => import('./ServerLicenseStatusResource').then(m => m.ServerLicenseStatusResource), + () => import('./PasswordPolicyResource').then(m => m.PasswordPolicyResource), + () => import('./ProductInfoResource').then(m => m.ProductInfoResource), + () => import('./DefaultNavigatorSettingsResource').then(m => m.DefaultNavigatorSettingsResource), + () => import('./ServerResourceQuotasResource').then(m => m.ServerResourceQuotasResource), ], }; diff --git a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/DefaultNavigatorSettings.gql b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/DefaultNavigatorSettings.gql new file mode 100644 index 0000000000..8278665cab --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/DefaultNavigatorSettings.gql @@ -0,0 +1,5 @@ +fragment DefaultNavigatorSettings on ServerConfig { + defaultNavigatorSettings { + ...AllNavigatorSettings + } +} diff --git a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/PasswordPolicy.gql b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/PasswordPolicy.gql new file mode 100644 index 0000000000..72883c56d3 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/PasswordPolicy.gql @@ -0,0 +1,8 @@ +fragment PasswordPolicy on ServerConfig { + passwordPolicyConfiguration { + minLength + minNumberCount + minSymbolCount + requireMixedCase + } +} diff --git a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ProductInfo.gql b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ProductInfo.gql new file mode 100644 index 0000000000..2ce93b8765 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ProductInfo.gql @@ -0,0 +1,12 @@ +fragment ProductInfo on ServerConfig { + productInfo { + id + version + latestVersionInfo + name + description + buildTime + releaseTime + licenseInfo + } +} diff --git a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ResourceQuotas.gql b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ResourceQuotas.gql new file mode 100644 index 0000000000..7d92dc727f --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ResourceQuotas.gql @@ -0,0 +1,3 @@ +fragment ResourceQuotas on ServerConfig { + resourceQuotas +} diff --git a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql new file mode 100644 index 0000000000..1f184e12af --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerConfig.gql @@ -0,0 +1,35 @@ +fragment ServerConfig on ServerConfig { + name + version + workspaceId + serverURL + rootURI + containerId + defaultAuthRole + defaultUserTeam + productConfiguration + supportsCustomConnections + sessionExpireTime + + anonymousAccessEnabled + + adminCredentialsSaveEnabled + publicCredentialsSaveEnabled + + resourceManagerEnabled + + configurationMode + developmentMode + redirectOnFederatedAuth + distributed + + enabledFeatures + disabledBetaFeatures + enabledAuthProviders + supportedLanguages { + isoCode + displayName + nativeName + } + disabledDrivers +} diff --git a/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerLicenseStatus.gql b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerLicenseStatus.gql new file mode 100644 index 0000000000..c6172b834d --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/ServerConfig/ServerLicenseStatus.gql @@ -0,0 +1,5 @@ +fragment ServerLicenseStatus on ServerConfig { + licenseRequired + licenseValid + licenseStatus +} diff --git a/webapp/packages/core-sdk/src/queries/session/getDefaultNavigatorSettings.gql b/webapp/packages/core-sdk/src/queries/session/getDefaultNavigatorSettings.gql new file mode 100644 index 0000000000..b514f9f4dd --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/session/getDefaultNavigatorSettings.gql @@ -0,0 +1,5 @@ +query getDefaultNavigatorSettings { + defaultNavigatorSettings: serverConfig { + ...DefaultNavigatorSettings + } +} diff --git a/webapp/packages/core-sdk/src/queries/session/getPasswordPolicy.gql b/webapp/packages/core-sdk/src/queries/session/getPasswordPolicy.gql new file mode 100644 index 0000000000..57e1cba85b --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/session/getPasswordPolicy.gql @@ -0,0 +1,5 @@ +query getPasswordPolicy { + passwordPolicy: serverConfig { + ...PasswordPolicy + } +} diff --git a/webapp/packages/core-sdk/src/queries/session/getProductInfo.gql b/webapp/packages/core-sdk/src/queries/session/getProductInfo.gql new file mode 100644 index 0000000000..0ccbeb9643 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/session/getProductInfo.gql @@ -0,0 +1,5 @@ +query getProductInfo { + productInfo: serverConfig { + ...ProductInfo + } +} diff --git a/webapp/packages/core-sdk/src/queries/session/getServerLicenseStatus.gql b/webapp/packages/core-sdk/src/queries/session/getServerLicenseStatus.gql new file mode 100644 index 0000000000..b8c9546386 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/session/getServerLicenseStatus.gql @@ -0,0 +1,5 @@ +query getServerLicenseStatus { + licenseStatus: serverConfig { + ...ServerLicenseStatus + } +} diff --git a/webapp/packages/core-sdk/src/queries/session/getServerResourceQuotas.gql b/webapp/packages/core-sdk/src/queries/session/getServerResourceQuotas.gql new file mode 100644 index 0000000000..26b8b66e85 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/session/getServerResourceQuotas.gql @@ -0,0 +1,5 @@ +query getServerResourceQuotas { + resourceQuotas: serverConfig { + ...ResourceQuotas + } +} diff --git a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql index 6cb48088e0..5b065603bb 100644 --- a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql +++ b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql @@ -1,63 +1,5 @@ query serverConfig { serverConfig { - name - version - workspaceId - serverURL - rootURI - containerId - defaultAuthRole - defaultUserTeam - productConfiguration - supportsCustomConnections - supportsConnectionBrowser - supportsWorkspaces - sessionExpireTime - - anonymousAccessEnabled - - adminCredentialsSaveEnabled - publicCredentialsSaveEnabled - - resourceManagerEnabled - - licenseRequired - licenseValid - - configurationMode - developmentMode - redirectOnFederatedAuth - distributed - - enabledFeatures - disabledBetaFeatures - enabledAuthProviders - supportedLanguages { - isoCode - displayName - nativeName - } - productConfiguration - defaultNavigatorSettings { - ...AllNavigatorSettings - } - resourceQuotas - disabledDrivers - productInfo { - id - version - latestVersionInfo - name - description - buildTime - releaseTime - licenseInfo - } - passwordPolicyConfiguration { - minLength - minNumberCount - minSymbolCount - requireMixedCase - } + ...ServerConfig } } diff --git a/webapp/packages/core-version/src/VersionResource.ts b/webapp/packages/core-version/src/VersionResource.ts index c9f8c5b1f5..e4dae493e8 100644 --- a/webapp/packages/core-version/src/VersionResource.ts +++ b/webapp/packages/core-version/src/VersionResource.ts @@ -9,7 +9,7 @@ import { computed, makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { CachedMapResource, resourceKeyList } from '@cloudbeaver/core-resource'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { ProductInfoResource } from '@cloudbeaver/core-root'; export interface IVersion { number: string; @@ -30,11 +30,11 @@ export class VersionResource extends CachedMapResource { return this.values.find(v => v.number === this.latestVersionNumber); } - constructor(private readonly serverConfigResource: ServerConfigResource) { + constructor(private readonly productInfoResource: ProductInfoResource) { super(); this.latestVersionNumber = null; - this.preloadResource(this.serverConfigResource, () => {}); + this.preloadResource(this.productInfoResource, () => {}); makeObservable(this, { latestVersionNumber: observable.ref, @@ -43,7 +43,7 @@ export class VersionResource extends CachedMapResource { } protected async loader(): Promise> { - const versionLink = this.serverConfigResource.data?.productInfo.latestVersionInfo; + const versionLink = this.productInfoResource.data?.latestVersionInfo; if (!versionLink) { return this.data; } diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx index 0d9554fbae..f1312a74ce 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx @@ -28,7 +28,7 @@ import { } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { DefaultNavigatorSettingsResource, ServerConfigResource } from '@cloudbeaver/core-root'; import { ServerConfigurationConfigurationForm } from './Form/ServerConfigurationConfigurationForm'; import { ServerConfigurationFeaturesForm } from './Form/ServerConfigurationFeaturesForm'; @@ -45,9 +45,10 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser const [focusedRef, state] = useFocus({ focusFirstChild: true }); const service = useService(ServerConfigurationService); const serverConfigResource = useService(ServerConfigResource); + const defaultNavigatorSettingsResource = useService(DefaultNavigatorSettingsResource); const commonDialogService = useService(CommonDialogService); const configurationWizardService = useService(ConfigurationWizardService); - const changed = serverConfigResource.isChanged() || serverConfigResource.isNavigatorSettingsChanged(); + const changed = serverConfigResource.isChanged() || defaultNavigatorSettingsResource.isChanged(); useFormValidator(service.validationTask, state.reference); function handleChange() { diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index 401562aef8..1f4f89bf32 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -13,7 +13,7 @@ import { DEFAULT_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { ENotificationType, INotification, NotificationService } from '@cloudbeaver/core-events'; import { Executor, ExecutorInterrupter, IExecutor, IExecutorHandler } from '@cloudbeaver/core-executor'; -import { ServerConfigResource, SessionDataResource } from '@cloudbeaver/core-root'; +import { DefaultNavigatorSettingsResource, ProductInfoResource, ServerConfigResource, SessionDataResource } from '@cloudbeaver/core-root'; import { ADMINISTRATION_SERVER_CONFIGURATION_ITEM } from './ADMINISTRATION_SERVER_CONFIGURATION_ITEM'; import type { IServerConfigurationPageState } from './IServerConfigurationPageState'; @@ -53,8 +53,10 @@ export class ServerConfigurationService { constructor( private readonly administrationScreenService: AdministrationScreenService, private readonly serverConfigResource: ServerConfigResource, + private readonly defaultNavigatorSettingsResource: DefaultNavigatorSettingsResource, private readonly notificationService: NotificationService, private readonly sessionDataResource: SessionDataResource, + private readonly productInfoResource: ProductInfoResource, ) { this.done = false; this.loading = true; @@ -119,11 +121,13 @@ export class ServerConfigurationService { reset = true; this.stateLinked = true; await this.serverConfigResource.load(); + await this.defaultNavigatorSettingsResource.load(); this.serverConfigResource.setDataUpdate(this.state.serverConfig); - this.serverConfigResource.setNavigatorSettingsUpdate(this.state.navigatorConfig); + this.defaultNavigatorSettingsResource.setDataUpdate(this.state.navigatorConfig); if (reset) { + this.defaultNavigatorSettingsResource.resetUpdate(); this.serverConfigResource.resetUpdate(); } } @@ -156,12 +160,14 @@ export class ServerConfigurationService { try { const config = await this.serverConfigResource.load(); + const productInfo = await this.productInfoResource.load(); + const defaultNavigatorSettings = await this.defaultNavigatorSettingsResource.load(); if (!config) { return; } - data.state.serverConfig.serverName = config.name || config.productInfo.name; + data.state.serverConfig.serverName = config.name || productInfo?.name; data.state.serverConfig.serverURL = config.serverURL; if (this.administrationScreenService.isConfigurationMode && !config.distributed) { @@ -175,7 +181,7 @@ export class ServerConfigurationService { data.state.serverConfig.customConnectionsEnabled = config.supportsCustomConnections; data.state.serverConfig.disabledDrivers = [...config.disabledDrivers]; - Object.assign(data.state.navigatorConfig, config.defaultNavigatorSettings); + Object.assign(data.state.navigatorConfig, defaultNavigatorSettings); } catch (exception: any) { ExecutorInterrupter.interrupt(contexts); this.notificationService.logException(exception, "Can't load server configuration"); @@ -198,7 +204,10 @@ export class ServerConfigurationService { } try { - await this.serverConfigResource.save(data.configurationWizard); + await this.defaultNavigatorSettingsResource.save(); + if (!data.configurationWizard) { + await this.serverConfigResource.save(); + } if (data.configurationWizard && data.finish) { await this.serverConfigResource.finishConfiguration(); @@ -252,7 +261,7 @@ export class ServerConfigurationService { private showUnsavedNotification(close: boolean) { if ( - (!this.serverConfigResource.isChanged() && !this.serverConfigResource.isNavigatorSettingsChanged()) || + (!this.serverConfigResource.isChanged() && !this.defaultNavigatorSettingsResource.isChanged()) || this.administrationScreenService.activeScreen?.item === ADMINISTRATION_SERVER_CONFIGURATION_ITEM ) { this.unSaveNotification?.close(true); @@ -292,6 +301,7 @@ export class ServerConfigurationService { } this.unSaveNotification?.close(true); + this.defaultNavigatorSettingsResource.unlinkUpdate(); this.serverConfigResource.unlinkUpdate(); this.stateLinked = false; } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts index 7df18decf2..2a6b7d4d25 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts @@ -11,7 +11,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor'; import { CachedMapAllKey } from '@cloudbeaver/core-resource'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { PasswordPolicyResource, ServerConfigResource } from '@cloudbeaver/core-root'; import { ILoadConfigData, IServerConfigSaveData, @@ -28,6 +28,7 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { private readonly serverConfigResource: ServerConfigResource, private readonly notificationService: NotificationService, private readonly passwordPolicyService: PasswordPolicyService, + private readonly passwordPolicyResource: PasswordPolicyResource, ) { super(); } @@ -85,6 +86,7 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { return validation.invalidate(); } + await this.passwordPolicyResource.load(); const passwordValidation = this.passwordPolicyService.validatePassword(data.state.serverConfig.adminPassword); if (!passwordValidation.isValid) { validation.error(passwordValidation.errorMessage); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts index 78ed677a46..c54ec2210e 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.ts @@ -7,7 +7,7 @@ */ import { makeObservable, observable } from 'mobx'; -import { QuotasService } from '@cloudbeaver/core-root'; +import { QuotasService, ServerResourceQuotasResource } from '@cloudbeaver/core-root'; import { GraphQLService, ResultDataFormat } from '@cloudbeaver/core-sdk'; import { bytesToSize, download, downloadFromURL, GlobalConstants, isNotNullDefined } from '@cloudbeaver/core-utils'; @@ -34,17 +34,31 @@ interface ICacheEntry { @databaseDataAction() export class ResultSetDataContentAction extends DatabaseDataAction implements IResultSetDataContentAction { static dataFormat = [ResultDataFormat.Resultset]; + private subscriptionDispose?: () => void; constructor( source: IDatabaseDataSource, private readonly data: ResultSetDataAction, private readonly format: ResultSetFormatAction, private readonly graphQLService: GraphQLService, + private readonly serverResourceQuotasResource: ServerResourceQuotasResource, private readonly quotasService: QuotasService, private readonly cache: ResultSetCacheAction, ) { super(source); + function loadQuotas() { + setTimeout(() => serverResourceQuotasResource.load(), 0); + } + + this.serverResourceQuotasResource.onDataOutdated.addHandler(loadQuotas); + + loadQuotas(); + + this.subscriptionDispose = () => { + this.serverResourceQuotasResource.onDataOutdated.removeHandler(loadQuotas); + }; + makeObservable(this, { cache: observable, }); @@ -168,6 +182,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction import('./ProductInfoDialog' @injectable() export class ProductBootstrap extends Bootstrap { constructor( - private readonly serverConfigResource: ServerConfigResource, + private readonly productInfoResource: ProductInfoResource, private readonly commonDialogService: CommonDialogService, private readonly menuService: MenuService, ) { @@ -27,7 +27,7 @@ export class ProductBootstrap extends Bootstrap { register(): void { this.menuService.addCreator({ menus: [TOP_NAV_BAR_SETTINGS_MENU], - isApplicable: () => !!this.serverConfigResource.data?.productInfo, + isApplicable: () => !!this.productInfoResource.data, getItems: (context, items) => [ ...items, new MenuBaseItem( diff --git a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx index 3032463ae2..524ad1d30b 100644 --- a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx +++ b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx @@ -25,7 +25,7 @@ import { } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import type { DialogComponentProps } from '@cloudbeaver/core-dialogs'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { ProductInfoResource } from '@cloudbeaver/core-root'; import { ThemeService } from '@cloudbeaver/core-theming'; import { useAppVersion } from '@cloudbeaver/core-version'; import { WebsiteLinks } from '@cloudbeaver/core-website'; @@ -34,12 +34,12 @@ import ProductInfoDialogStyles from './ProductInfoDialog.module.css'; export const ProductInfoDialog = observer>(function ProductInfoDialog(props) { const translate = useTranslate(); - const serverConfigResource = useService(ServerConfigResource); + const serverConfigResource = useService(ProductInfoResource); const themeService = useService(ThemeService); const version = useAppVersion(); - const productInfo = serverConfigResource.data?.productInfo; + const productInfo = serverConfigResource.data; const logoIcon = themeService.themeId === 'light' ? '/icons/product-logo_light.svg' : '/icons/product-logo_dark.svg'; const styles = useS(ProductInfoDialogStyles); diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/Logo.tsx b/webapp/packages/plugin-top-app-bar/src/TopNavBar/Logo.tsx index d0a2c38770..a8f4d0edd9 100644 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/Logo.tsx +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/Logo.tsx @@ -7,20 +7,20 @@ */ import { observer } from 'mobx-react-lite'; -import { AppLogo } from '@cloudbeaver/core-blocks'; +import { AppLogo, useResource } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { ProductInfoResource } from '@cloudbeaver/core-root'; import { ScreenService } from '@cloudbeaver/core-routing'; import { useAppVersion } from '@cloudbeaver/core-version'; export const Logo = observer(function Logo() { - const serverConfigResource = useService(ServerConfigResource); + const productInfoResource = useResource(Logo, ProductInfoResource, undefined); const screenService = useService(ScreenService); const { backendVersion, frontendVersion } = useAppVersion(true); const isSameVersion = backendVersion === frontendVersion; - const productName = serverConfigResource.data?.productInfo.name || 'CloudBeaver'; + const productName = productInfoResource.data?.name || 'CloudBeaver'; const backendVersionTitle = `${productName} ver. ${backendVersion}`; const commonVersionTitle = `${productName} ver. ${frontendVersion}(${backendVersion})`; From 95046f076f31d341ec9f99af95c0ac51c685c5b8 Mon Sep 17 00:00:00 2001 From: alex <48489896+devnaumov@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:55:05 +0200 Subject: [PATCH 7/7] CB-5451 adjust regex (#2824) * CB-5451 adjust regex * CB-5451 do not validate when deleting rm resource --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> Co-authored-by: DenisSinelnikov <142215442+DenisSinelnikov@users.noreply.github.com> Co-authored-by: Ainur --- .../model/rm/local/LocalResourceController.java | 1 - .../core-connections/src/ConnectionFolderResource.ts | 2 +- webapp/packages/core-connections/src/locales/en.ts | 2 +- webapp/packages/core-connections/src/locales/fr.ts | 7 +++++-- webapp/packages/core-connections/src/locales/it.ts | 2 +- webapp/packages/core-connections/src/locales/ru.ts | 2 +- webapp/packages/core-connections/src/locales/zh.ts | 2 +- .../plugin-resource-manager-scripts/src/locales/en.ts | 9 ++++++++- .../plugin-resource-manager-scripts/src/locales/fr.ts | 2 +- .../plugin-resource-manager-scripts/src/locales/it.ts | 9 ++++++++- .../plugin-resource-manager-scripts/src/locales/ru.ts | 9 ++++++++- .../plugin-resource-manager-scripts/src/locales/zh.ts | 9 ++++++++- .../plugin-resource-manager/src/RESOURCE_NAME_REGEX.ts | 2 +- 13 files changed, 44 insertions(+), 14 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 5daf0a8d8e..ff261363a1 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -685,7 +685,6 @@ public void deleteResource(@NotNull String projectId, @NotNull String resourcePa if (log.isDebugEnabled()) { log.debug("Removing resource from '" + resourcePath + "' in project '" + projectId + "'" + (recursive ? " recursive" : "")); } - validateResourcePath(resourcePath); Path targetPath = getTargetPath(projectId, resourcePath); doFileWriteOperation(projectId, targetPath, () -> { if (!Files.exists(targetPath)) { diff --git a/webapp/packages/core-connections/src/ConnectionFolderResource.ts b/webapp/packages/core-connections/src/ConnectionFolderResource.ts index 63bbab8571..9b3b5df782 100644 --- a/webapp/packages/core-connections/src/ConnectionFolderResource.ts +++ b/webapp/packages/core-connections/src/ConnectionFolderResource.ts @@ -31,7 +31,7 @@ export interface IConnectionFolderParam { folderId: string; } -export const CONNECTION_FOLDER_NAME_VALIDATION = /^(?!\.)[^\\/:\\"]+$/u; +export const CONNECTION_FOLDER_NAME_VALIDATION = /^(?!\.)[^\\/:\\"'<>|?*]+$/u; export const ConnectionFolderProjectKey = resourceKeyAliasFactory('@connection-folder/project', (projectId: string) => ({ projectId })); diff --git a/webapp/packages/core-connections/src/locales/en.ts b/webapp/packages/core-connections/src/locales/en.ts index b0731a2c43..ec5ed27da8 100644 --- a/webapp/packages/core-connections/src/locales/en.ts +++ b/webapp/packages/core-connections/src/locales/en.ts @@ -54,7 +54,7 @@ export default [ ['connections_connection_edit_search_hosts', 'Host names'], ['connections_connection_address', 'Address'], ['connections_connection_folder', 'Folder'], - ['connections_connection_folder_validation', "Folder's name may not contain the following symbols / : \" \\ and can't start with a dot"], + ['connections_connection_folder_validation', "Folder's name may not contain the following symbols / : \" \\ ' <> | ? * and can't start with a dot"], ['connections_connection_name', 'Connection name'], ['connections_connection_access_user_or_team_name', 'User or Team name'], ['connections_connection_access_filter_placeholder', 'Search for user or team name'], diff --git a/webapp/packages/core-connections/src/locales/fr.ts b/webapp/packages/core-connections/src/locales/fr.ts index 9ec17f2af7..7cb705fa07 100644 --- a/webapp/packages/core-connections/src/locales/fr.ts +++ b/webapp/packages/core-connections/src/locales/fr.ts @@ -59,12 +59,15 @@ export default [ ['connections_connection_folder', 'Dossier'], [ 'connections_connection_folder_validation', - 'Le nom du dossier ne peut pas contenir les symboles suivants : / : " \\ et ne peut pas commencer par un point', + 'Le nom du dossier ne peut pas contenir les symboles suivants / : " \\ \' <> | ? * et ne peut pas commencer par un point', ], ['connections_connection_name', 'Nom de la connexion'], ['connections_connection_access_user_or_team_name', "Nom de l'utilisateur ou de l'équipe"], ['connections_connection_access_filter_placeholder', "Rechercher un nom d'utilisateur ou d'équipe"], - ['connections_connection_access_admin_info', "Les administrateurs voient toutes les connexions à l'exception des connexions privées des autres utilisateurs."], + [ + 'connections_connection_access_admin_info', + "Les administrateurs voient toutes les connexions à l'exception des connexions privées des autres utilisateurs.", + ], ['connections_connection_description', 'Description'], ['connections_connection_project', 'Projet'], ['connections_connection_driver', 'Pilote'], diff --git a/webapp/packages/core-connections/src/locales/it.ts b/webapp/packages/core-connections/src/locales/it.ts index 906713a88f..1d2dd96482 100644 --- a/webapp/packages/core-connections/src/locales/it.ts +++ b/webapp/packages/core-connections/src/locales/it.ts @@ -51,7 +51,7 @@ export default [ ['connections_connection_edit_search_hosts', 'Host names'], ['connections_connection_address', 'Indirizzo'], ['connections_connection_folder', 'Folder'], - ['connections_connection_folder_validation', "Folder's name may not contain the following symbols / : \" \\ and can't start with a dot"], + ['connections_connection_folder_validation', "Folder's name may not contain the following symbols / : \" \\ ' <> | ? * and can't start with a dot"], ['connections_connection_name', 'Nome della connessione'], ['connections_connection_access_revoke', 'Revoca'], ['connections_connection_access_grant', 'Permetti'], diff --git a/webapp/packages/core-connections/src/locales/ru.ts b/webapp/packages/core-connections/src/locales/ru.ts index f93384e221..76ad21656b 100644 --- a/webapp/packages/core-connections/src/locales/ru.ts +++ b/webapp/packages/core-connections/src/locales/ru.ts @@ -58,7 +58,7 @@ export default [ ['connections_connection_edit_search_hosts', 'Названия хостов'], ['connections_connection_address', 'Адрес'], ['connections_connection_folder', 'Папка'], - ['connections_connection_folder_validation', 'Имя папки не может содержать следующие символы / : " \\ и не может начинаться с точки'], + ['connections_connection_folder_validation', 'Имя папки не может содержать следующие символы / : " \\ \' <> | ? * и не может начинаться с точки'], ['connections_connection_name', 'Название подключения'], ['connections_connection_access_user_or_team_name', 'Имя пользователя или команды'], ['connections_connection_access_filter_placeholder', 'Поиск по имени пользователя или команде'], diff --git a/webapp/packages/core-connections/src/locales/zh.ts b/webapp/packages/core-connections/src/locales/zh.ts index a97d32b363..2b0e0840e6 100644 --- a/webapp/packages/core-connections/src/locales/zh.ts +++ b/webapp/packages/core-connections/src/locales/zh.ts @@ -52,7 +52,7 @@ export default [ ['connections_connection_edit_search_hosts', '主机名称'], ['connections_connection_address', '地址'], ['connections_connection_folder', 'Folder'], - ['connections_connection_folder_validation', "Folder's name may not contain the following symbols / : \" \\ and can't start with a dot"], + ['connections_connection_folder_validation', "Folder's name may not contain the following symbols / : \" \\ ' <> | ? * and can't start with a dot"], ['connections_connection_name', '连接名称'], ['connections_connection_access_admin_info', 'Administrators see all connections except private connections of other users.'], ['connections_connection_description', '描述'], diff --git a/webapp/packages/plugin-resource-manager-scripts/src/locales/en.ts b/webapp/packages/plugin-resource-manager-scripts/src/locales/en.ts index 765755d93e..53daab31a8 100644 --- a/webapp/packages/plugin-resource-manager-scripts/src/locales/en.ts +++ b/webapp/packages/plugin-resource-manager-scripts/src/locales/en.ts @@ -1,3 +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. + */ export default [ ['plugin_resource_manager_scripts_action_enable_label', 'Show scripts'], ['plugin_resource_manager_scripts_title', 'Scripts'], @@ -5,7 +12,7 @@ export default [ ['plugin_resource_manager_scripts_save_script', 'Save script'], [ 'plugin_resource_manager_scripts_script_name_invalid_characters_message', - "Script's name may not contain the following symbols / : \" \\ and can't start with a dot", + "Script's name may not contain the following symbols / : \" \\ ' <> | ? * and can't start with a dot", ], [ 'plugin_resource_manager_scripts_save_script_project_restriction_descripion', diff --git a/webapp/packages/plugin-resource-manager-scripts/src/locales/fr.ts b/webapp/packages/plugin-resource-manager-scripts/src/locales/fr.ts index e576064868..b7c4cfaa50 100644 --- a/webapp/packages/plugin-resource-manager-scripts/src/locales/fr.ts +++ b/webapp/packages/plugin-resource-manager-scripts/src/locales/fr.ts @@ -12,7 +12,7 @@ export default [ ['plugin_resource_manager_scripts_save_script', 'Enregistrer le script'], [ 'plugin_resource_manager_scripts_script_name_invalid_characters_message', - 'Le nom du script ne peut pas contenir les symboles suivants : / \\ \\ et ne peut pas commencer par un point', + 'Le nom du script ne peut pas contenir les symboles suivants / : " \\ \' <> | ? * et ne peut pas commencer par un point', ], [ 'plugin_resource_manager_scripts_save_script_project_restriction_description', diff --git a/webapp/packages/plugin-resource-manager-scripts/src/locales/it.ts b/webapp/packages/plugin-resource-manager-scripts/src/locales/it.ts index 765755d93e..53daab31a8 100644 --- a/webapp/packages/plugin-resource-manager-scripts/src/locales/it.ts +++ b/webapp/packages/plugin-resource-manager-scripts/src/locales/it.ts @@ -1,3 +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. + */ export default [ ['plugin_resource_manager_scripts_action_enable_label', 'Show scripts'], ['plugin_resource_manager_scripts_title', 'Scripts'], @@ -5,7 +12,7 @@ export default [ ['plugin_resource_manager_scripts_save_script', 'Save script'], [ 'plugin_resource_manager_scripts_script_name_invalid_characters_message', - "Script's name may not contain the following symbols / : \" \\ and can't start with a dot", + "Script's name may not contain the following symbols / : \" \\ ' <> | ? * and can't start with a dot", ], [ 'plugin_resource_manager_scripts_save_script_project_restriction_descripion', diff --git a/webapp/packages/plugin-resource-manager-scripts/src/locales/ru.ts b/webapp/packages/plugin-resource-manager-scripts/src/locales/ru.ts index 4d1a324482..1c72effe44 100644 --- a/webapp/packages/plugin-resource-manager-scripts/src/locales/ru.ts +++ b/webapp/packages/plugin-resource-manager-scripts/src/locales/ru.ts @@ -1,3 +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. + */ export default [ ['plugin_resource_manager_scripts_action_enable_label', 'Показывать скрипты'], ['plugin_resource_manager_scripts_title', 'Скрипты'], @@ -5,7 +12,7 @@ export default [ ['plugin_resource_manager_scripts_save_script', 'Сохранить скрипт'], [ 'plugin_resource_manager_scripts_script_name_invalid_characters_message', - 'Имя скрипта не может содержать следующие символы / : " \\ и не может начинаться с точки', + 'Имя скрипта не может содержать следующие символы / : " \\ \' <> | ? * и не может начинаться с точки', ], ['plugin_resource_manager_scripts_save_script_project_restriction_descripion', 'Проект скрипта не может отличаться от проекта подключения'], ]; diff --git a/webapp/packages/plugin-resource-manager-scripts/src/locales/zh.ts b/webapp/packages/plugin-resource-manager-scripts/src/locales/zh.ts index 90cee7bf47..ca607880f0 100644 --- a/webapp/packages/plugin-resource-manager-scripts/src/locales/zh.ts +++ b/webapp/packages/plugin-resource-manager-scripts/src/locales/zh.ts @@ -1,3 +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. + */ export default [ ['plugin_resource_manager_scripts_action_enable_label', 'Show scripts'], ['plugin_resource_manager_scripts_title', 'Scripts'], @@ -5,7 +12,7 @@ export default [ ['plugin_resource_manager_scripts_save_script', '保存脚本'], [ 'plugin_resource_manager_scripts_script_name_invalid_characters_message', - "Script's name may not contain the following symbols / : \" \\ and can't start with a dot", + "Script's name may not contain the following symbols / : \" \\ ' <> | ? * and can't start with a dot", ], [ 'plugin_resource_manager_scripts_save_script_project_restriction_descripion', diff --git a/webapp/packages/plugin-resource-manager/src/RESOURCE_NAME_REGEX.ts b/webapp/packages/plugin-resource-manager/src/RESOURCE_NAME_REGEX.ts index 51f7216273..523a0b43dc 100644 --- a/webapp/packages/plugin-resource-manager/src/RESOURCE_NAME_REGEX.ts +++ b/webapp/packages/plugin-resource-manager/src/RESOURCE_NAME_REGEX.ts @@ -6,4 +6,4 @@ * you may not use this file except in compliance with the License. */ -export const RESOURCE_NAME_REGEX = /^(?!\.)[^\\/:\\"]+$/u; +export const RESOURCE_NAME_REGEX = /^(?!\.)[^\\/:\\"'<>|?*]+$/u;