Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6708ee747a | ||
|
|
288b5dc9e4 | ||
|
|
5def270426 | ||
|
|
15f9ea7911 | ||
|
|
840f4f7b48 |
@@ -25,11 +25,6 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### 25.2.4 2025-11-03
|
||||
- A confirmation prompt has been added for potentially dangerous queries in the SQL Editor to help prevent unintended execution. You can customize this behavior in Preferences > SQL Editor;
|
||||
- The value panel in the Data Editor has been redesigned to provide users with more space for viewing and editing data. Tab buttons for different data type visualizations are now aligned on a single level, creating a cleaner layout;
|
||||
- Permission configuration for connections was simplified, providing a clearer appearance and improved performance.
|
||||
|
||||
### 25.2.3 2025-10-20
|
||||
- Added visualization for column reordering in the Data Editor;
|
||||
- Fixed the critical issue with duplicating the execution of queries executed by a shortcut in the SQL Editor;
|
||||
|
||||
@@ -74,8 +74,4 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
|
||||
*/
|
||||
default void postAuthentication() {}
|
||||
|
||||
@Override
|
||||
default boolean supportsOpeningSessionAsChild() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,13 +276,21 @@ public class CBAppConfig extends BaseWebAppConfiguration implements ServletAuthC
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public String[] getEnabledAuthProviders() {
|
||||
if (enabledAuthProviders == null) {
|
||||
// No config - enable all providers (+backward compatibility)
|
||||
return WebAuthProviderRegistry.getInstance().getAuthProviders()
|
||||
.stream().map(WebAuthProviderDescriptor::getId).toArray(String[]::new);
|
||||
}
|
||||
return enabledAuthProviders;
|
||||
//all enabled
|
||||
return WebAuthProviderRegistry.getInstance().getAuthProviders()
|
||||
.stream()
|
||||
.map(WebAuthProviderDescriptor::getId)
|
||||
.toArray(String[]::new);
|
||||
// if (enabledAuthProviders == null) {
|
||||
// // No config - enable all providers (+backward compatibility)
|
||||
// return WebAuthProviderRegistry.getInstance().getAuthProviders()
|
||||
// .stream()
|
||||
// .map(WebAuthProviderDescriptor::getId)
|
||||
// .toArray(String[]::new);
|
||||
// }
|
||||
// return enabledAuthProviders;
|
||||
}
|
||||
|
||||
public void setEnabledAuthProviders(String[] enabledAuthProviders) {
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.impl.preferences.AbstractUserPreferenceStore;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@@ -86,13 +85,4 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
|
||||
public void save() throws IOException {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
public boolean getUserPreferenceBoolean(@NotNull String name, boolean fallbackValue) {
|
||||
String value = CommonUtils.toString(userPreferences.get(name));
|
||||
if (value.isEmpty()) {
|
||||
return fallbackValue;
|
||||
} else {
|
||||
return toBoolean(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package io.cloudbeaver.registry;
|
||||
import io.cloudbeaver.auth.CBAuthConstants;
|
||||
import io.cloudbeaver.auth.SMAuthProviderFederated;
|
||||
import io.cloudbeaver.auth.SMSignOutLinkProvider;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
@@ -69,6 +68,10 @@ public class WebAuthProviderConfiguration {
|
||||
return config.isDisabled();
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return config.isDefault();
|
||||
}
|
||||
|
||||
public String getIconURL() {
|
||||
return config.getIconURL();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jkiss.dbeaver.model.impl.AbstractDescriptor;
|
||||
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.security.SMAuthCredentialsProfile;
|
||||
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
|
||||
import org.jkiss.dbeaver.model.security.SMAuthProviderDescriptor;
|
||||
import org.jkiss.dbeaver.model.security.SMSubjectType;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
@@ -61,6 +62,8 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
private final String[] requiredFeatures;
|
||||
private final boolean isRequired;
|
||||
private String[] types;
|
||||
@Nullable
|
||||
private final SMAuthProviderCustomConfiguration defaultConfiguration;
|
||||
|
||||
public WebAuthProviderDescriptor(IConfigurationElement cfg) {
|
||||
super(cfg);
|
||||
@@ -98,6 +101,26 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
|
||||
String typesAttr = cfg.getAttribute(WebRegistryConstant.ATTR_CATEGORIES);
|
||||
this.types = CommonUtils.isEmpty(typesAttr) ? new String[0] : typesAttr.split(",");
|
||||
IConfigurationElement[] defaultConfigurations = cfg.getChildren(WebRegistryConstant.TAG_DEFAULT_CONFIGURATION);
|
||||
if (defaultConfigurations != null && defaultConfigurations.length > 0) {
|
||||
if (defaultConfigurations.length > 1) {
|
||||
throw new IllegalStateException("Multiple default configuration elements defined for auth provider " + getId());
|
||||
}
|
||||
IConfigurationElement defaultConfiguration = defaultConfigurations[0];
|
||||
|
||||
this.defaultConfiguration = SMAuthProviderCustomConfiguration.builder()
|
||||
.id(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_ID))
|
||||
.provider(getId())
|
||||
.displayName(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_LABEL))
|
||||
.disabled(CommonUtils.toBoolean(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_DISABLED)))
|
||||
.iconURL(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_ICON))
|
||||
.description(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_DESCRIPTION))
|
||||
.parameters(new HashMap<>())
|
||||
.isDefault(CommonUtils.toBoolean(defaultConfiguration.getAttribute(WebRegistryConstant.ATTR_IS_DEFAULT)))
|
||||
.build();
|
||||
} else {
|
||||
this.defaultConfiguration = null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -239,4 +262,9 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SMAuthProviderCustomConfiguration getDefaultConfiguration() {
|
||||
return defaultConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,12 @@
|
||||
package io.cloudbeaver.registry;
|
||||
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPConditionalProperty;
|
||||
import org.jkiss.dbeaver.model.impl.LocalizedPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.osgi.framework.Bundle;
|
||||
|
||||
public class WebAuthProviderProperty extends LocalizedPropertyDescriptor implements DBPConditionalProperty {
|
||||
|
||||
private static final String WEB_AUTH_PROPERTY_PREFIX = "prop.auth.model.";
|
||||
|
||||
private final String[] requiredFeatures;
|
||||
@Nullable
|
||||
private final String type;
|
||||
@@ -36,7 +30,6 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
|
||||
private final String authProviderId;
|
||||
private final String hideExpr;
|
||||
private final String readOnlyExpr;
|
||||
private final Bundle bundle;
|
||||
|
||||
public WebAuthProviderProperty(String category, IConfigurationElement config, String authProviderId) {
|
||||
super(category, config);
|
||||
@@ -46,7 +39,6 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
|
||||
this.type = config.getAttribute("type");
|
||||
this.hideExpr = config.getAttribute("hideExpr");
|
||||
this.readOnlyExpr = config.getAttribute("readOnlyExpr");
|
||||
this.bundle = extractBundle(config);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -71,34 +63,12 @@ public class WebAuthProviderProperty extends LocalizedPropertyDescriptor impleme
|
||||
return readOnlyExpr;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getLocalizedName(@NotNull String locale) {
|
||||
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getLocalizedDescription(@NotNull String locale) {
|
||||
return RuntimeUtils.getBundleLocalization(bundle, locale).getString(getPropertyId() + "." + ATTR_DESCRIPTION);
|
||||
}
|
||||
|
||||
private String getPropertyId() {
|
||||
public String getPropertyId() {
|
||||
if (authProviderId != null) {
|
||||
return WEB_AUTH_PROPERTY_PREFIX + authProviderId + "." + this.getId();
|
||||
return "prop.auth.model." + authProviderId + "." + this.getId();
|
||||
} else {
|
||||
return WEB_AUTH_PROPERTY_PREFIX + this.getId();
|
||||
return "prop.auth.model." + this.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Bundle extractBundle(@NotNull IConfigurationElement config) {
|
||||
final Bundle bundle;
|
||||
String bundleName = config.getContributor().getName();
|
||||
bundle = Platform.getBundle(bundleName);
|
||||
if (bundle == null) {
|
||||
throw new IllegalStateException("Bundle '" + bundleName + "' not found");
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@ import org.eclipse.core.runtime.IExtensionRegistry;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration;
|
||||
import org.jkiss.dbeaver.registry.RegistryConstants;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WebAuthProviderRegistry {
|
||||
|
||||
@@ -120,4 +122,11 @@ public class WebAuthProviderRegistry {
|
||||
return authProviders.get(id);
|
||||
}
|
||||
|
||||
public Set<SMAuthProviderCustomConfiguration> getMandatoryConfigurations() {
|
||||
return authProviders.values()
|
||||
.stream()
|
||||
.map(WebAuthProviderDescriptor::getDefaultConfiguration)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,11 @@ public class WebRegistryConstant {
|
||||
public static final String ATTR_REQUIRED_FEATURES = "requiredFeatures";
|
||||
public static final String ATTR_CATEGORIES = "categories";
|
||||
public static final String ATTR_SERVICE_PROVIDER = "serviceProvider";
|
||||
public static final String ATTR_DISABLED = "disabled";
|
||||
public static final String ATTR_IS_DEFAULT = "isDefault";
|
||||
|
||||
public static final String TAG_CONFIGURATION = "configuration";
|
||||
public static final String TAG_CREDENTIALS = "credentials";
|
||||
public static final String TAG_META_PARAMETERS = "metaParameters";
|
||||
public static final String TAG_DEFAULT_CONFIGURATION = "defaultConfiguration";
|
||||
}
|
||||
|
||||
@@ -47,5 +47,5 @@ public class WebSQLConstants {
|
||||
|
||||
public static final String TASK_CONFIRMATION_ATTR_PREFIX = "session_task_confirmation_";
|
||||
public static final String SKIP_TASK_CONFIRMATIONS_ATTR = "skip_session_task_confirmations";
|
||||
public static final int TASK_CONFIRMATION_TIMEOUT_SECONDS = 60;
|
||||
public static final int TASK_CONFIRMATION_TIMEOUT_SECONDS = 30;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.cloudbeaver.model.config.CBAppConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.model.config.PasswordPolicyConfiguration;
|
||||
import io.cloudbeaver.model.config.SMControllerConfiguration;
|
||||
import io.cloudbeaver.registry.WebAuthProviderRegistry;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -223,6 +224,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
appConfiguration.getAuthCustomConfigurations().stream()
|
||||
)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
Set<SMAuthProviderCustomConfiguration> mandatoryConfigurations = WebAuthProviderRegistry.getInstance().getMandatoryConfigurations();
|
||||
mergedAuthProviders.addAll(mandatoryConfigurations);
|
||||
appConfiguration.setAuthProvidersConfigurations(mergedAuthProviders);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ public class CBStaticServlet extends DefaultServlet {
|
||||
}
|
||||
CBAppConfig appConfig = application.getAppConfiguration();
|
||||
String[] authProviders = appConfig.getEnabledAuthProviders();
|
||||
//fixme there is no single auth provider
|
||||
if (authProviders.length == 1) {
|
||||
String authProviderId = authProviders[0];
|
||||
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance().getAuthProvider(authProviderId);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a field or method as sensitive, so its value won't be logged.
|
||||
*/
|
||||
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WebParameterSecure {
|
||||
}
|
||||
@@ -246,7 +246,14 @@ public class GraphQLEndpoint extends HttpServlet {
|
||||
if (operationName != null) {
|
||||
contextBuilder.operationName(operationName);
|
||||
}
|
||||
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
|
||||
String userId = GraphQLLoggerUtil.getUserId(request);
|
||||
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, variables);
|
||||
if (operationName != null) {
|
||||
log.debug("API > " + operationName + loggerMessage);
|
||||
} else if (DEBUG) {
|
||||
log.debug("API > " + query + loggerMessage);
|
||||
}
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
ExecutionInput executionInput = contextBuilder.build();
|
||||
ExecutionResult executionResult = null;
|
||||
|
||||
@@ -16,27 +16,21 @@
|
||||
*/
|
||||
package io.cloudbeaver.server.graphql;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.cloudbeaver.WebParameterSecure;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import io.cloudbeaver.server.WebApplication;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class GraphQLLoggerUtil {
|
||||
|
||||
private static final Log log = Log.getLog(GraphQLLoggerUtil.class);
|
||||
public static final String LOG_API_GRAPHQL_DEBUG_PARAMETER = "log.api.graphql.debug";
|
||||
public static final Gson GSON = new GsonBuilder().create();
|
||||
public static final String MASK_STRING = "****";
|
||||
private static final Set<String> PROHIBITED_VARIABLES =
|
||||
Set.of("password", "config", "parameters", "settings", "licenseText", "credentials", "username");
|
||||
|
||||
public static String getUserId(HttpServletRequest request) {
|
||||
WebSession session = getWebSession(request);
|
||||
@@ -69,67 +63,43 @@ public class GraphQLLoggerUtil {
|
||||
.findWebSession(request);
|
||||
}
|
||||
|
||||
public static String buildLoggerMessage(String sessionId, String userId, Method method, Object[] args) {
|
||||
StringBuilder sb = new StringBuilder(64)
|
||||
.append(" [user: ").append(userId)
|
||||
.append(", sessionId: ").append(sessionId)
|
||||
.append("]");
|
||||
public static String buildLoggerMessage(String sessionId, String userId, Map<String, Object> variables) {
|
||||
StringBuilder loggerMessage = new StringBuilder(" [user: ").append(userId)
|
||||
.append(", sessionId: ").append(sessionId).append("]");
|
||||
|
||||
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)) {
|
||||
sb.append('(');
|
||||
String text = maskArgsToString(method, args);
|
||||
if (CommonUtils.isNotEmpty(text)) {
|
||||
sb.append(text);
|
||||
if (WebAppUtils.getWebPlatform().getPreferenceStore().getBoolean(LOG_API_GRAPHQL_DEBUG_PARAMETER)
|
||||
&& variables != null
|
||||
) {
|
||||
loggerMessage.append(" [variables] ");
|
||||
String parsedVariables = parseVarialbes(variables);
|
||||
if (CommonUtils.isNotEmpty(parsedVariables)) {
|
||||
loggerMessage.append(parseVarialbes(variables));
|
||||
}
|
||||
sb.append(')');
|
||||
}
|
||||
return sb.toString();
|
||||
return loggerMessage.toString();
|
||||
}
|
||||
|
||||
public static String maskArgsToString(Method method, Object[] args) {
|
||||
Parameter[] params = method.getParameters();
|
||||
if (params.length == 0 || args == null || args.length == 0) {
|
||||
return "";
|
||||
}
|
||||
private static String parseVarialbes(Map<String, Object> map) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
int limit = Math.min(args.length, params.length);
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
for (int i = 0; i < limit; i++) {
|
||||
Object value = args[i];
|
||||
if (value instanceof WebSession) {
|
||||
//we already log sessionId
|
||||
continue;
|
||||
}
|
||||
if (params[i].isAnnotationPresent(WebParameterSecure.class)) {
|
||||
joiner.add(MASK_STRING);
|
||||
continue;
|
||||
}
|
||||
if (value instanceof String sv && CommonUtils.isEmpty(sv)) {
|
||||
boolean isProhibited = PROHIBITED_VARIABLES.stream()
|
||||
.anyMatch(prohibitedKey -> key.toLowerCase().contains(prohibitedKey.toLowerCase()));
|
||||
|
||||
if (isProhibited) {
|
||||
result.append(key).append(": ").append("******** ");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value != null && !isSimple(value.getClass())) {
|
||||
String stringValue;
|
||||
try {
|
||||
stringValue = GSON.toJson(value);
|
||||
} catch (Exception e) {
|
||||
stringValue = value.toString();
|
||||
}
|
||||
joiner.add(stringValue);
|
||||
if (value instanceof Map) {
|
||||
result.append(parseVarialbes((Map<String, Object>) value));
|
||||
} else {
|
||||
joiner.add(String.valueOf(value));
|
||||
result.append(key).append(": ").append(value).append(" ");
|
||||
}
|
||||
}
|
||||
return joiner.toString();
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
private static boolean isSimple(Class<?> cls) {
|
||||
return cls.isPrimitive()
|
||||
|| Number.class.isAssignableFrom(cls)
|
||||
|| CharSequence.class.isAssignableFrom(cls)
|
||||
|| Boolean.class.equals(cls)
|
||||
|| Enum.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package io.cloudbeaver.service;
|
||||
|
||||
import graphql.GraphQLContext;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import graphql.schema.idl.SchemaParser;
|
||||
import graphql.schema.idl.TypeDefinitionRegistry;
|
||||
@@ -28,11 +27,9 @@ import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.session.WebSessionProvider;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
|
||||
import io.cloudbeaver.server.graphql.GraphQLLoggerUtil;
|
||||
import io.cloudbeaver.service.security.SMUtils;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -164,7 +161,7 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static WebConnectionInfo getWebConnection(@NotNull WebSession session, @Nullable String projectId, @NotNull String connectionId) throws DBWebException {
|
||||
public static WebConnectionInfo getWebConnection(@NotNull WebSession session, @NotNull String projectId, @NotNull String connectionId) throws DBWebException {
|
||||
return WebDataSourceUtils.getWebConnectionInfo(session, projectId, connectionId);
|
||||
}
|
||||
|
||||
@@ -343,27 +340,16 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
|
||||
}
|
||||
}
|
||||
}
|
||||
// Perform any checks before action call
|
||||
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
|
||||
|
||||
GraphQLContext graphQlContext = this.env.getGraphQlContext();
|
||||
HttpServletRequest request = graphQlContext.get("request");
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
String sessionId = GraphQLLoggerUtil.getSmSessionId(request);
|
||||
String userId = GraphQLLoggerUtil.getUserId(request);
|
||||
String loggerMessage = GraphQLLoggerUtil.buildLoggerMessage(sessionId, userId, method, args);
|
||||
}
|
||||
|
||||
log.debug("API > " + method.getName() + loggerMessage);
|
||||
|
||||
setLogContext(method, args);
|
||||
}
|
||||
|
||||
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
|
||||
Log.setContext(null);
|
||||
}
|
||||
// Perform any checks before action call
|
||||
protected void beforeWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
|
||||
setLogContext(method, args);
|
||||
}
|
||||
|
||||
protected void afterWebActionCall(WebAction webAction, Method method, Object[] args) throws DBException {
|
||||
Log.setContext(null);
|
||||
}
|
||||
|
||||
protected void setLogContext(Method method, @Nullable Object[] args) {
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.core;
|
||||
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebAction;
|
||||
import io.cloudbeaver.WebObjectId;
|
||||
import io.cloudbeaver.WebProjectAction;
|
||||
import io.cloudbeaver.model.*;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.DBWService;
|
||||
@@ -116,7 +119,7 @@ public interface DBWServiceCore extends DBWService {
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable String projectId,
|
||||
@NotNull String connectionId,
|
||||
@WebParameterSecure @Nullable Map<String, Object> authProperties,
|
||||
@Nullable Map<String, Object> authProperties,
|
||||
@Nullable List<WebNetworkHandlerConfigInput> networkCredentials,
|
||||
boolean saveCredentials,
|
||||
boolean sharedCredentials,
|
||||
@@ -127,7 +130,7 @@ public interface DBWServiceCore extends DBWService {
|
||||
WebConnectionInfo createConnection(
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable @WebObjectId String projectId,
|
||||
@WebParameterSecure @NotNull Map<String, Object> connectionConfig
|
||||
@NotNull Map<String, Object> connectionConfig
|
||||
) throws DBWebException;
|
||||
|
||||
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
|
||||
|
||||
@@ -20,7 +20,6 @@ import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.WebAsyncTaskInfo;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.session.WebSessionPreferenceStore;
|
||||
import io.cloudbeaver.model.session.WebSessionProvider;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import io.cloudbeaver.server.jobs.SqlOutputLogReaderJob;
|
||||
@@ -63,6 +62,7 @@ import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -231,7 +231,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
if (useEvents) {
|
||||
boolean isConfirmed = confirmQueryIfNeeded(mainQuery.getScriptElements(), asyncTask, isGenerated);
|
||||
if (!isConfirmed) {
|
||||
throw new DBWebException("Query execution was cancelled by user");
|
||||
throw new DBWebException("Query execution cancelled by user");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +292,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
sqlOutputLogReaderJob.join();
|
||||
}
|
||||
fillQueryResults(contextInfo, dataContainer, dbStat, hasResultSet, executeInfo, webDataFilter, dataFilter, dataFormat);
|
||||
} catch (DBException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,12 +352,14 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
results.setResultSet(resultSet);
|
||||
|
||||
executeInfo.setResults(new WebSQLQueryResults[]{results});
|
||||
setResultFilterText(session.getDataSource(), executeInfo, dataFilter);
|
||||
setResultFilterText(dataContainer, session.getDataSource(), executeInfo, dataFilter);
|
||||
executeInfo.setFullQuery(statistics.getQueryText());
|
||||
if (resultSet != null && resultSet.getRowsWithMetaData() != null && resultSet.getResultsInfo() != null) {
|
||||
resultSet.getResultsInfo().setQueryText(statistics.getQueryText());
|
||||
executeInfo.setStatusMessage(resultSet.getRowsWithMetaData().size() + " row(s) fetched");
|
||||
}
|
||||
} catch (DBException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -395,7 +399,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
for (var rowIdentifier : rowIdentifierList) {
|
||||
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
|
||||
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
|
||||
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, keyReceiver);
|
||||
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, keyReceiver);
|
||||
|
||||
DBCExecutionContext executionContext = getExecutionContext(dataManipulator);
|
||||
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) {
|
||||
@@ -496,7 +500,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
@NotNull Set<WebSQLQueryResultSetRow> newResultSetRows,
|
||||
@Nullable WebDataFormat dataFormat,
|
||||
@NotNull DBRProgressMonitor monitor)
|
||||
throws DBException {
|
||||
throws DBCException {
|
||||
try (DBCSession session = getExecutionContext().openSession(
|
||||
monitor,
|
||||
DBCExecutionPurpose.UTIL,
|
||||
@@ -572,8 +576,9 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
@NotNull String resultsId,
|
||||
@Nullable List<WebSQLResultsRow> updatedRows,
|
||||
@Nullable List<WebSQLResultsRow> deletedRows,
|
||||
@Nullable List<WebSQLResultsRow> addedRows
|
||||
) throws DBException {
|
||||
@Nullable List<WebSQLResultsRow> addedRows,
|
||||
@Nullable WebDataFormat dataFormat) throws DBException
|
||||
{
|
||||
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();
|
||||
|
||||
|
||||
@@ -589,7 +594,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
for (var rowIdentifier : rowIdentifierList) {
|
||||
DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
|
||||
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, resultBatches, null);
|
||||
monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, null);
|
||||
|
||||
List<DBEPersistAction> actions = new ArrayList<>();
|
||||
|
||||
@@ -615,6 +620,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
@Nullable List<WebSQLResultsRow> updatedRows,
|
||||
@Nullable List<WebSQLResultsRow> deletedRows,
|
||||
@Nullable List<WebSQLResultsRow> addedRows,
|
||||
@Nullable WebDataFormat dataFormat,
|
||||
@NotNull Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches,
|
||||
@Nullable DBDDataReceiver keyReceiver)
|
||||
throws DBException
|
||||
@@ -874,6 +880,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
@NotNull
|
||||
public WebSQLExecutionPlan explainExecutionPlan(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String sql,
|
||||
@NotNull Map<String, Object> configuration) throws DBWebException {
|
||||
|
||||
@@ -898,6 +905,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
DBCQueryPlannerConfiguration planConfig = new DBCQueryPlannerConfiguration();
|
||||
planConfig.getParameters().putAll(configuration);
|
||||
dbcPlan[0] = planner.planQueryExecution(session, sql, planConfig);
|
||||
} catch (DBException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
});
|
||||
} catch (DBException e) {
|
||||
@@ -937,14 +946,16 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
private void readCellDataValue(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLResultsInfo resultsInfo,
|
||||
@NotNull WebSQLResultsRow row,
|
||||
@Nullable WebSQLResultsRow row,
|
||||
@NotNull WebSQLCellValueReceiver dataReceiver
|
||||
) throws DBException {
|
||||
DBSDataContainer dataContainer = resultsInfo.getDataContainer();
|
||||
DBCExecutionContext executionContext = getExecutionContext(dataContainer);
|
||||
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Generate data update batches")) {
|
||||
DBDDataFilter dataFilter = new DBDDataFilter();
|
||||
addKeyAttributes(resultsInfo, row, dataContainer, session, dataFilter);
|
||||
if (!resultsInfo.isSingleRow()) {
|
||||
addKeyAttributes(resultsInfo, row, dataContainer, session, dataFilter);
|
||||
}
|
||||
WebExecutionSource executionSource = new WebExecutionSource(dataContainer, executionContext, this);
|
||||
dataContainer.readData(
|
||||
executionSource, session, dataReceiver, dataFilter,
|
||||
@@ -954,16 +965,12 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
|
||||
private void addKeyAttributes(
|
||||
@NotNull WebSQLResultsInfo resultsInfo,
|
||||
@NotNull WebSQLResultsRow row,
|
||||
@Nullable WebSQLResultsRow row,
|
||||
@NotNull DBSDataContainer dataContainer,
|
||||
@NotNull DBCSession session,
|
||||
@NotNull DBDDataFilter dataFilter
|
||||
) throws DBException {
|
||||
DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier();
|
||||
if (rowIdentifier == null || rowIdentifier.isIncomplete()) {
|
||||
return;
|
||||
}
|
||||
DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]);
|
||||
DBDAttributeBinding[] keyAttributes = resultsInfo.getDefaultRowIdentifier().getAttributes().toArray(new DBDAttributeBinding[0]);
|
||||
Object[] rowValues = new Object[keyAttributes.length];
|
||||
List<DBDAttributeConstraint> constraints = new ArrayList<>();
|
||||
for (int i = 0; i < keyAttributes.length; i++) {
|
||||
@@ -1070,6 +1077,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) {
|
||||
readResultSet(dbStat.getSession(), resultSet, webDataFilter, dataReceiver);
|
||||
results.setResultSet(dataReceiver.getResultSet());
|
||||
dataReceiver.getResultSet().getResultsInfo().setQueryText(resultSet.getSourceStatement().getQueryString());
|
||||
}
|
||||
}
|
||||
resultList.add(results);
|
||||
@@ -1089,15 +1097,11 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
}
|
||||
executeInfo.setResults(resultList.toArray(new WebSQLQueryResults[0]));
|
||||
|
||||
setResultFilterText(dbStat.getSession().getDataSource(), executeInfo, dataFilter);
|
||||
setResultFilterText(dataContainer, dbStat.getSession().getDataSource(), executeInfo, dataFilter);
|
||||
executeInfo.setFullQuery(dbStat.getQueryString());
|
||||
}
|
||||
|
||||
private void setResultFilterText(
|
||||
@NotNull DBPDataSource dataSource,
|
||||
@NotNull WebSQLExecuteInfo executeInfo,
|
||||
@NotNull DBDDataFilter filter
|
||||
) throws DBException {
|
||||
private void setResultFilterText(@NotNull DBSDataContainer dataContainer, @NotNull DBPDataSource dataSource, @NotNull WebSQLExecuteInfo executeInfo, @NotNull DBDDataFilter filter) throws DBException {
|
||||
if (!filter.getConstraints().isEmpty() || !CommonUtils.isEmpty(filter.getWhere())) {
|
||||
StringBuilder where = new StringBuilder();
|
||||
SQLUtils.appendConditionString(
|
||||
@@ -1110,13 +1114,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private void readResultSet(
|
||||
@NotNull DBCSession session,
|
||||
@NotNull DBCResultSet dbResult,
|
||||
@NotNull WebSQLDataFilter filter,
|
||||
@NotNull WebSQLQueryDataReceiver dataReceiver
|
||||
) throws DBException {
|
||||
DBDDataReceiver.startFetchWorkflow(dataReceiver, session, dbResult, filter.getOffset(), filter.getLimit());
|
||||
private void readResultSet(@NotNull DBCSession session, @NotNull DBCResultSet dbResult, @NotNull WebSQLDataFilter filter, @NotNull WebSQLQueryDataReceiver dataReceiver) throws DBCException {
|
||||
dataReceiver.fetchStart(session, dbResult, filter.getOffset(), filter.getLimit());
|
||||
int rowCount = 0;
|
||||
while (dbResult.nextRow()) {
|
||||
if (rowCount > filter.getLimit()) {
|
||||
@@ -1126,6 +1125,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
dataReceiver.fetchRow(session, dbResult);
|
||||
rowCount++;
|
||||
}
|
||||
dataReceiver.fetchEnd(session, dbResult);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1163,6 +1163,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
if (keyValue == null) {
|
||||
continue;
|
||||
}
|
||||
boolean updated = false;
|
||||
if (!CommonUtils.isEmpty(keyAttribute.getName())) {
|
||||
DBDAttributeBinding binding = DBUtils.findObject(resultsAttributes, keyAttribute.getName());
|
||||
if (binding != null) {
|
||||
@@ -1233,7 +1234,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
|
||||
private Object setCellRowValue(Object cellRow, WebSession webSession, DBCSession dbcSession, DBDAttributeBinding allAttributes, boolean withoutExecution)
|
||||
throws DBException {
|
||||
if (cellRow instanceof Map<?, ?> variables) {
|
||||
if (cellRow instanceof Map<?, ?>) {
|
||||
Map<String, Object> variables = (Map<String, Object>) cellRow;
|
||||
if (variables.get(FILE_ID) != null) {
|
||||
Path path = WebAppUtils.getWebPlatform()
|
||||
.getTempFolder(webSession.getProgressMonitor(), TEMP_FILE_FOLDER)
|
||||
@@ -1251,6 +1253,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
return convertInputCellValue(dbcSession, allAttributes, cellRow, withoutExecution);
|
||||
}
|
||||
|
||||
// TODO: Refactor to unify with desktop when confirmation settings will be added
|
||||
private boolean confirmQueryIfNeeded(
|
||||
@NotNull List<SQLScriptElement> scriptElements,
|
||||
@NotNull WebAsyncTaskInfo asyncTask,
|
||||
@@ -1278,28 +1281,25 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
.map(SQLScriptElement::getText)
|
||||
.collect(Collectors.joining("\n\n"));
|
||||
} else {
|
||||
WebSessionPreferenceStore store = webSession.getUserPreferenceStore();
|
||||
boolean confirmDangerousQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DANGER_SQL_KEY, true);
|
||||
boolean confirmDropQueries = store.getUserPreferenceBoolean(ConfirmationConstants.CONFIRM_DROP_SQL_KEY, true);
|
||||
for (SQLScriptElement scriptElement : scriptElements) {
|
||||
if (scriptElement instanceof SQLQuery sqlQuery) {
|
||||
if (confirmDangerousQueries && sqlQuery.isDeleteUpdateDangerous()) {
|
||||
if (sqlQuery.isDeleteUpdateDangerous()) {
|
||||
hasDangerousUpdates = true;
|
||||
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
|
||||
.getConfirmation(ConfirmationConstants.CONFIRM_DANGER_SQL_ID);
|
||||
.getConfirmation(ConfirmationConstants.CONFIRM_DANGER_SQL);
|
||||
title = descriptor.getLocalizedTitle(webSession.getLocale());
|
||||
var entityMetadata = sqlQuery.getEntityMetadata(false);
|
||||
message = MessageFormat.format(
|
||||
descriptor.getLocalizedMessage(webSession.getLocale()),
|
||||
sqlQuery.getType().name(),
|
||||
entityMetadata != null ? entityMetadata.getEntityName() : "multiple tables"
|
||||
entityMetadata != null ? entityMetadata.getEntityName() : null
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (confirmDropQueries && sqlQuery.isDropTableDangerous()) {
|
||||
if (sqlQuery.isDropTableDangerous()) {
|
||||
hasDropStatement = true;
|
||||
ConfirmationDescriptor descriptor = ConfirmationRegistry.getInstance()
|
||||
.getConfirmation(ConfirmationConstants.CONFIRM_DROP_SQL_ID);
|
||||
.getConfirmation(ConfirmationConstants.CONFIRM_DROP_SQL);
|
||||
title = descriptor.getLocalizedTitle(webSession.getLocale());
|
||||
message = descriptor.getLocalizedMessage(webSession.getLocale());
|
||||
break;
|
||||
|
||||
@@ -104,7 +104,7 @@ public class WebSQLQueryDataContainer implements DBSDataContainer, DBPContextPro
|
||||
long maxRows,
|
||||
long flags,
|
||||
int fetchSize
|
||||
) throws DBException {
|
||||
) throws DBCException {
|
||||
return queryDataContainer.readData(source, session, dataReceiver, dataFilter, firstRow, maxRows, flags, fetchSize);
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
|
||||
webResultSet.setReadOnlyInfo(contextInfo.getProcessor().getExecutionContext());
|
||||
|
||||
WebSQLResultsInfo resultsInfo = contextInfo.saveResult(dataContainer, trace, bindings, rows.size() == 1);
|
||||
resultsInfo.setQueryText(resultSet.getSourceStatement().getQueryString());
|
||||
webResultSet.setResultsInfo(resultsInfo);
|
||||
|
||||
boolean isSingleEntity = DBExecUtils.detectSingleSourceTable(bindings) != null;
|
||||
@@ -233,6 +232,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
rows.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public WebSQLDialectInfo getDialectInfo(@NotNull WebConnectionInfo connectionInfo) {
|
||||
public WebSQLDialectInfo getDialectInfo(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
|
||||
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
|
||||
SQLDialect dialect = getSqlDialectFromConnection(dataSourceContainer);
|
||||
return new WebSQLDialectInfo(dataSourceContainer.getDataSource(), dialect);
|
||||
@@ -400,16 +400,21 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
DBExecUtils.tryExecuteRecover(
|
||||
monitor,
|
||||
contextInfo.getProcessor().getConnection().getDataSource(),
|
||||
monitor1 ->
|
||||
result[0] = contextInfo.getProcessor().updateResultsDataBatch(
|
||||
monitor1, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat)
|
||||
monitor1 -> {
|
||||
try {
|
||||
result[0] = contextInfo.getProcessor().updateResultsDataBatch(
|
||||
monitor1, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat);
|
||||
} catch (Exception e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowableFunction<T, R> {
|
||||
R apply(T obj) throws DBException;
|
||||
R apply(T obj) throws Exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -432,6 +437,9 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@NotNull Integer lobColumnIndex,
|
||||
@NotNull WebSQLResultsRow row
|
||||
) throws DBWebException {
|
||||
if (row == null) {
|
||||
throw new DBWebException("Results row is not found");
|
||||
}
|
||||
WebSQLProcessor processor = contextInfo.getProcessor();
|
||||
ThrowableFunction<DBRProgressMonitor, String> function = monitor -> processor.readStringValue(
|
||||
monitor, contextInfo, resultsId, lobColumnIndex, row);
|
||||
@@ -449,7 +457,13 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
DBExecUtils.tryExecuteRecover(
|
||||
processor.getWebSession().getProgressMonitor(),
|
||||
processor.getConnection().getDataSource(),
|
||||
monitor -> result.append(function.apply(monitor))
|
||||
monitor -> {
|
||||
try {
|
||||
result.append(function.apply(monitor));
|
||||
} catch (Exception e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
return result.toString();
|
||||
} catch (DBException e) {
|
||||
@@ -462,8 +476,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
try {
|
||||
return contextInfo.getProcessor().generateResultsDataUpdateScript(
|
||||
contextInfo.getProcessor().getWebSession().getProgressMonitor(),
|
||||
contextInfo, resultsId, updatedRows, deletedRows, addedRows
|
||||
);
|
||||
contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error genering update script", e);
|
||||
}
|
||||
@@ -503,11 +516,10 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@NotNull String nodePath,
|
||||
@Nullable String resultId,
|
||||
@Nullable WebSQLDataFilter filter,
|
||||
@Nullable WebDataFormat dataFormat
|
||||
) {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Nullable WebDataFormat dataFormat) throws DBWebException {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
|
||||
try {
|
||||
monitor.beginTask("Read data", 1);
|
||||
monitor.subTask("Extra data from " + nodePath);
|
||||
@@ -521,8 +533,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
dataContainer,
|
||||
resultId,
|
||||
filter != null ? filter : new WebSQLDataFilter(),
|
||||
dataFormat
|
||||
);
|
||||
dataFormat);
|
||||
this.result = executeResults.getStatusMessage();
|
||||
this.extendedResults = executeResults;
|
||||
} catch (Throwable e) {
|
||||
@@ -553,25 +564,24 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@Override
|
||||
public WebSQLExecuteInfo asyncGetQueryResults(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
|
||||
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
|
||||
return (WebSQLExecuteInfo) taskStatus.getExtendedResult();
|
||||
if (taskStatus != null) {
|
||||
return (WebSQLExecuteInfo) taskStatus.getExtendedResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Explain plan
|
||||
|
||||
@Override
|
||||
public WebAsyncTaskInfo asyncSqlExplainExecutionPlan(
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String sql,
|
||||
@NotNull Map<String, Object> configuration
|
||||
) {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
public WebAsyncTaskInfo asyncSqlExplainExecutionPlan(@NotNull WebSQLContextInfo contextInfo, @NotNull String sql, @NotNull Map<String, Object> configuration) throws DBException {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
|
||||
try {
|
||||
monitor.beginTask("Explain execution plan", 1);
|
||||
monitor.subTask("Explain query [" + sql + "] execution plan");
|
||||
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, sql, configuration);
|
||||
WebSQLExecutionPlan executeResults = contextInfo.getProcessor().explainExecutionPlan(monitor, contextInfo, sql, configuration);
|
||||
this.result = "Execution plan explain has been scheduled";
|
||||
this.extendedResults = executeResults;
|
||||
} catch (Throwable e) {
|
||||
@@ -587,11 +597,14 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@Override
|
||||
public WebSQLExecutionPlan asyncSqlExplainExecutionPlanResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
|
||||
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
|
||||
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
|
||||
if (taskStatus != null) {
|
||||
return (WebSQLExecutionPlan) taskStatus.getExtendedResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSQLScriptInfo parseSqlScript(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript) {
|
||||
public WebSQLScriptInfo parseSqlScript(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript) throws DBWebException {
|
||||
SQLDialect dialect = getSqlDialectFromConnection(connectionInfo.getDataSourceContainer());
|
||||
List<SQLScriptElement> queries = SQLScriptParser.parseScript(
|
||||
connectionInfo.getDataSource(),
|
||||
@@ -605,7 +618,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSQLQueryInfo parseSqlQuery(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript, int cursorPosition) {
|
||||
public WebSQLQueryInfo parseSqlQuery(@NotNull WebConnectionInfo connectionInfo, @NotNull String sqlScript, int cursorPosition) throws DBWebException {
|
||||
SQLDialect dialect = getSqlDialectFromConnection(connectionInfo.getDataSourceContainer());
|
||||
SQLScriptElement query = SQLScriptParser.parseQuery(
|
||||
connectionInfo.getDataSource(),
|
||||
@@ -648,20 +661,14 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAsyncTaskInfo getRowDataCount(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId) {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
public WebAsyncTaskInfo getRowDataCount(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId) throws DBWebException {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
|
||||
try {
|
||||
monitor.beginTask("Get row data count", 1);
|
||||
WebSQLResultsInfo results = contextInfo.getResults(resultsId);
|
||||
long rowCount = DBUtils.readRowCount(
|
||||
monitor,
|
||||
contextInfo.getProcessor().getExecutionContext(),
|
||||
results.getDataContainer(),
|
||||
null,
|
||||
this
|
||||
);
|
||||
long rowCount = DBUtils.readRowCount(monitor, contextInfo.getProcessor().getExecutionContext(), results.getDataContainer(), null, this);
|
||||
this.result = "Row data count completed";
|
||||
this.extendedResults = rowCount;
|
||||
} catch (Throwable e) {
|
||||
@@ -678,16 +685,19 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@Nullable
|
||||
public Long getRowDataCountResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
|
||||
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
|
||||
return (Long) taskStatus.getExtendedResult();
|
||||
if (taskStatus != null) {
|
||||
return (Long) taskStatus.getExtendedResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAsyncTaskInfo asyncSqlSetAutoCommit(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, boolean autoCommit) {
|
||||
public WebAsyncTaskInfo asyncSqlSetAutoCommit(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo, boolean autoCommit) throws DBWebException {
|
||||
return contextInfo.setAutoCommit(autoCommit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAsyncTaskInfo asyncSqlRollbackTransaction(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo) {
|
||||
public WebAsyncTaskInfo asyncSqlRollbackTransaction(@NotNull WebSession webSession, @NotNull WebSQLContextInfo contextInfo) throws DBWebException {
|
||||
return contextInfo.rollbackTransaction();
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ type AdminAuthProviderConfiguration {
|
||||
id: ID!
|
||||
displayName: String!
|
||||
disabled: Boolean!
|
||||
isDefault: Boolean!
|
||||
|
||||
iconURL: String
|
||||
description: String
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.admin;
|
||||
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.DBWFeatureSet;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebAction;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.registry.WebAuthProviderConfiguration;
|
||||
@@ -113,7 +116,7 @@ public interface DBWServiceAdmin extends DBWService {
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull String userID,
|
||||
@NotNull String providerId,
|
||||
@WebParameterSecure @NotNull Map<String, Object> credentials
|
||||
@NotNull Map<String, Object> credentials
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
@@ -153,7 +156,7 @@ public interface DBWServiceAdmin extends DBWService {
|
||||
boolean disabled,
|
||||
@Nullable String iconURL,
|
||||
@Nullable String description,
|
||||
@WebParameterSecure @Nullable Map<String, Object> parameters) throws DBWebException;
|
||||
@Nullable Map<String, Object> parameters) throws DBWebException;
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
boolean deleteAuthProviderConfiguration(@NotNull WebSession webSession, @NotNull String id) throws DBWebException;
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.admin.impl;
|
||||
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.DBWFeatureSet;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebProjectImpl;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.config.CBAppConfig;
|
||||
@@ -360,11 +363,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setUserCredentials(@NotNull WebSession webSession,
|
||||
@NotNull String userID,
|
||||
@NotNull String providerId,
|
||||
@WebParameterSecure @NotNull Map<String, Object> credentials
|
||||
) throws DBWebException {
|
||||
public boolean setUserCredentials(@NotNull WebSession webSession, @NotNull String userID, @NotNull String providerId, @NotNull Map<String, Object> credentials) throws DBWebException {
|
||||
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance().getAuthProvider(providerId);
|
||||
if (authProvider == null) {
|
||||
throw new DBWebException("Invalid auth provider '" + providerId + "'");
|
||||
|
||||
@@ -31,6 +31,7 @@ type AuthProviderConfiguration {
|
||||
displayName: String!
|
||||
disabled: Boolean!
|
||||
authRoleProvided: Boolean
|
||||
isDefault: Boolean
|
||||
|
||||
iconURL: String
|
||||
description: String
|
||||
|
||||
@@ -18,7 +18,6 @@ package io.cloudbeaver.service.auth;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebAction;
|
||||
import io.cloudbeaver.WebParameterSecure;
|
||||
import io.cloudbeaver.model.WebPropertyInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.DBWService;
|
||||
@@ -78,11 +77,7 @@ public interface DBWServiceAuth extends DBWService {
|
||||
WebAuthProviderInfo[] getAuthProviders(@NotNull HttpServletRequest request) throws DBWebException;
|
||||
|
||||
@WebAction()
|
||||
boolean changeLocalPassword(
|
||||
@NotNull WebSession webSession,
|
||||
@WebParameterSecure @NotNull String oldPassword,
|
||||
@WebParameterSecure @NotNull String newPassword
|
||||
) throws DBWebException;
|
||||
boolean changeLocalPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBWebException;
|
||||
|
||||
@WebAction(authRequired = false)
|
||||
WebPropertyInfo[] listUserProfileProperties(@NotNull WebSession webSession);
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.jkiss.dbeaver.model.security.SMController;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.Certificate;
|
||||
@@ -49,8 +48,6 @@ import javax.naming.Context;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.*;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
@@ -309,22 +306,18 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
|
||||
|
||||
@NotNull
|
||||
private String findUserNameFromDN(@NotNull String fullUserDN, @NotNull LdapSettings ldapSettings)
|
||||
throws DBException {
|
||||
try {
|
||||
LdapName ldapDN = new LdapName(fullUserDN);
|
||||
for (Rdn rdn : ldapDN.getRdns()) {
|
||||
if (rdn.getType().equalsIgnoreCase(ldapSettings.getUserIdentifierAttr())) {
|
||||
Object v = rdn.getValue();
|
||||
if (v instanceof byte[]) {
|
||||
return new String((byte[]) v, StandardCharsets.UTF_8);
|
||||
}
|
||||
return String.valueOf(v);
|
||||
}
|
||||
throws DBException {
|
||||
String userId = null;
|
||||
for (String dn : fullUserDN.split(",")) {
|
||||
if (dn.startsWith(ldapSettings.getUserIdentifierAttr() + "=")) {
|
||||
userId = dn.split("=")[1];
|
||||
break;
|
||||
}
|
||||
throw new DBException("Failed to determine userId from user DN: " + fullUserDN);
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Invalid user DN: " + fullUserDN, e);
|
||||
}
|
||||
if (userId == null) {
|
||||
throw new DBException("Failed to determine userId from user DN: " + fullUserDN);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -555,16 +548,14 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
|
||||
|
||||
context = initConnection(environment);
|
||||
|
||||
String searchFilter = "(member={0})";
|
||||
String searchFilter = "(member=" + fullDN + ")";
|
||||
SearchControls searchControls = new SearchControls();
|
||||
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
searchResults = context.search(ldapSettings.getBaseDN(), searchFilter, new Object[] {fullDN}, searchControls);
|
||||
|
||||
searchResults = context.search(ldapSettings.getBaseDN(), searchFilter, searchControls);
|
||||
while (searchResults.hasMore()) {
|
||||
try {
|
||||
SearchResult next = searchResults.next();
|
||||
//add full dn
|
||||
result.add(next.getNameInNamespace());
|
||||
//add relative dn to base dn
|
||||
result.add(next.getName());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed fetch user group. Skipping...", e);
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
<property id="password" label="User password" type="string" description="User password" encryption="hash" admin="true" user="true" required="true"/>
|
||||
</propertyGroup>
|
||||
</credentials>
|
||||
<defaultConfiguration id="local"
|
||||
label="local"
|
||||
disabled="false"
|
||||
iconURL=""
|
||||
description="configuration of local provider"
|
||||
isDefault="true"/>
|
||||
|
||||
</authProvider>
|
||||
<authProvider id="reverseProxy" label="Reverse proxy"
|
||||
|
||||
@@ -101,11 +101,6 @@ public class LocalAuthProvider implements SMAuthProvider<LocalAuthSession>, SMBr
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsOpeningSessionAsChild() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean changeUserPassword(@NotNull WebSession webSession, @NotNull String oldPassword, @NotNull String newPassword) throws DBException {
|
||||
String userName = webSession.getUser().getUserId();
|
||||
|
||||
|
||||
@@ -2472,13 +2472,6 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
if (autoAssign != null && CommonUtils.isNotEmpty(autoAssign.getAuthRoleAssignReason())) {
|
||||
log.info(activeUserId + " authenticated with role " + autoAssign.getAuthRole() + ", reason: " + autoAssign.getAuthRoleAssignReason());
|
||||
}
|
||||
} else {
|
||||
SMAuthProvider<?> authProviderInstance = authProvider.getInstance();
|
||||
if (!authProviderInstance.supportsOpeningSessionAsChild()) {
|
||||
var error = "Auth provider '" + authProviderId + "' doesn't support opening as a child session";
|
||||
updateAuthStatus(authId, SMAuthStatus.ERROR, dbStoredUserData, error, null);
|
||||
return SMAuthInfo.error(authId, error, isMainAuthSession, null, authInfo.getAppSessionId());
|
||||
}
|
||||
}
|
||||
dbStoredUserData.put(
|
||||
authConfiguration,
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>3.25.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>13.2.1.jre11</version>
|
||||
<version>12.8.0.jre11</version>
|
||||
</dependency>
|
||||
<!--
|
||||
<dependency>
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { memo, use, useMemo } from 'react';
|
||||
import { Cell, type CellRendererProps } from 'react-data-grid';
|
||||
import { DataGridCellContext, type IDataGridCellRenderer } from './DataGridCellContext.js';
|
||||
@@ -85,17 +77,19 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
|
||||
|
||||
const renderDefaultCell = useMemo<IDataGridCellRenderer>(
|
||||
() =>
|
||||
({ onClick, onDoubleClick, onContextMenu, ...rest }) => (
|
||||
<Cell
|
||||
{...props}
|
||||
title={tooltip}
|
||||
onCellClick={(_, event) => onClick?.(event)}
|
||||
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
|
||||
onCellContextMenu={(_, event) => onContextMenu?.(event)}
|
||||
{...rest}
|
||||
isCellSelected={props.isCellSelected || rest.isFocused || false}
|
||||
/>
|
||||
),
|
||||
({ onClick, onDoubleClick, onContextMenu, ...rest }) => {
|
||||
return (
|
||||
<Cell
|
||||
{...props}
|
||||
title={tooltip}
|
||||
onCellClick={(_, event) => onClick?.(event)}
|
||||
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
|
||||
onCellContextMenu={(_, event) => onContextMenu?.(event)}
|
||||
{...rest}
|
||||
isCellSelected={props.isCellSelected || rest.isFocused || false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[...Object.values(props), tooltip],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Row, type RenderRowProps } from 'react-data-grid';
|
||||
import { DataGridRowContext, type IDataGridRowRenderer } from './DataGridRowContext.js';
|
||||
import { memo, use, useEffect, useMemo } from 'react';
|
||||
import { DataGridRowContext } from './DataGridRowContext.js';
|
||||
import { memo, use, useEffect } from 'react';
|
||||
import { useGridReactiveValue } from './useGridReactiveValue.js';
|
||||
|
||||
export const BaseRow = memo(function BaseRow<TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) {
|
||||
@@ -20,18 +12,5 @@ export const BaseRow = memo(function BaseRow<TRow, TSummaryRow>(props: RenderRow
|
||||
rowContext?.onScrollToBottom?.();
|
||||
}
|
||||
}, [rowsCount]);
|
||||
|
||||
const renderDefaultRow = useMemo<IDataGridRowRenderer>(
|
||||
() => override => (
|
||||
<Row
|
||||
{...props}
|
||||
{...override}
|
||||
/>
|
||||
),
|
||||
[...Object.values(props)],
|
||||
);
|
||||
|
||||
const rowElement = useGridReactiveValue(rowContext?.rowElement, props.rowIdx, props, renderDefaultRow);
|
||||
|
||||
return rowElement ?? <Row {...props} />;
|
||||
return <Row {...props} />;
|
||||
}) as <TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) => React.ReactNode;
|
||||
|
||||
@@ -42,7 +42,6 @@ export interface DataGridCellKeyboardEvent extends React.KeyboardEvent<HTMLDivEl
|
||||
export interface DataGridProps extends IDataGridCellContext, IDataGridRowContext, IDataGridHeaderCellContext, React.PropsWithChildren {
|
||||
getRowHeight?: (rowIdx: number) => number;
|
||||
getRowId?: (rowIdx: number) => React.Key;
|
||||
getRowClass?: (rowIdx: number) => string | null;
|
||||
columnCount: IGridReactiveValue<number, []>;
|
||||
getColumnKey?: (colIdx: number) => string;
|
||||
onScroll?: (event: React.UIEvent<HTMLDivElement>) => void;
|
||||
@@ -79,11 +78,9 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
|
||||
getCellEditable,
|
||||
columnCount,
|
||||
getColumnKey,
|
||||
rowElement,
|
||||
rowCount,
|
||||
getRowId,
|
||||
getRowHeight,
|
||||
getRowClass,
|
||||
onHeaderReorder,
|
||||
onScroll,
|
||||
onScrollToBottom,
|
||||
@@ -184,7 +181,7 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
|
||||
|
||||
return (
|
||||
<HeaderDnDContext value={dndHeaderContext}>
|
||||
<DataGridRowContext value={{ rowElement, rowCount, onScrollToBottom }}>
|
||||
<DataGridRowContext value={{ rowCount, onScrollToBottom }}>
|
||||
<DataGridCellContext value={{ cell, cellText, cellElement, cellTooltip, onCellChange }}>
|
||||
<DataGridCellHeaderContext
|
||||
value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState, onHeaderKeyDown }}
|
||||
@@ -197,7 +194,6 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
|
||||
headerRowHeight={getHeaderHeight?.()}
|
||||
rowHeight={getRowHeight ? row => getRowHeight(row.idx) : undefined}
|
||||
rowKeyGetter={getRowId ? row => getRowId(row.idx) : undefined}
|
||||
rowClass={getRowClass ? row => getRowClass(row.idx) : undefined}
|
||||
columnWidths={columnWidths}
|
||||
renderers={{
|
||||
renderRow: rowRenderer,
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import type { IGridReactiveValue } from './IGridReactiveValue.js';
|
||||
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import type { IGridReactiveValue } from './IGridReactiveValue.js';
|
||||
|
||||
export interface IDataGridRowProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {}
|
||||
|
||||
export interface IDataGridRowRenderer {
|
||||
(propsOverride: Partial<IDataGridRowProps>): React.ReactNode;
|
||||
}
|
||||
|
||||
export interface IDataGridRowContext {
|
||||
rowElement?: IGridReactiveValue<React.ReactNode, [rowIdx: number, props: IDataGridRowProps, renderDefaultRow: IDataGridRowRenderer]>;
|
||||
rowCount: IGridReactiveValue<number, []>;
|
||||
onScrollToBottom?: () => void;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export * from './DataGrid.js';
|
||||
export * from './DataGridRowContext.js';
|
||||
export * from './DataGridCellContext.js';
|
||||
export * from './DataGridCellInnerContext.js';
|
||||
export * from './IGridReactiveValue.js';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# @dbeaver/react-translate
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "@dbeaver/react-translate",
|
||||
"type": "module",
|
||||
"sideEffects": [],
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
"./*": "./lib/*.js"
|
||||
},
|
||||
"scripts": {
|
||||
"clear": "rimraf lib"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"lib",
|
||||
"!lib/**/*.d.ts.map",
|
||||
"!lib/**/*.test.js",
|
||||
"!lib/**/*.test.d.ts",
|
||||
"!lib/**/*.test.d.ts.map",
|
||||
"!lib/**/*.test.js.map",
|
||||
"!lib/tests",
|
||||
"!.tsbuildinfo"
|
||||
],
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"devDependencies": {
|
||||
"@dbeaver/tsconfig": "workspace:^",
|
||||
"@types/react": "^19",
|
||||
"rimraf": "^6",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export type LocalizationToken = string;
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { memo } from 'react';
|
||||
import { useTranslate } from './useTranslate.js';
|
||||
|
||||
interface Props {
|
||||
children: string;
|
||||
args?: Record<string, any>;
|
||||
fallback?: string;
|
||||
}
|
||||
|
||||
export const Translate = memo<Props>(function Translate({ children, args, fallback }) {
|
||||
const t = useTranslate();
|
||||
return t(children, fallback, args);
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import type { LocalizationToken } from './LocalizationToken.js';
|
||||
|
||||
export type TranslateFn = <T extends LocalizationToken | undefined>(token: T, fallback?: T, args?: Record<string | number, any>) => T;
|
||||
export const TranslateContext = createContext<{ translate: TranslateFn }>({
|
||||
translate: (key, fallback) => fallback || key,
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export * from './LocalizationToken.js';
|
||||
export * from './useTranslate.js';
|
||||
export * from './Translate.js';
|
||||
export * from './TranslateContext.js';
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { use } from 'react';
|
||||
import { TranslateContext, type TranslateFn } from './TranslateContext.js';
|
||||
|
||||
export function useTranslate(): TranslateFn {
|
||||
const context = use(TranslateContext);
|
||||
return context.translate;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": "@dbeaver/tsconfig/tsconfig.webview.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"rootDir": "src",
|
||||
"rootDirs": [
|
||||
"src"
|
||||
],
|
||||
"outDir": "lib",
|
||||
"incremental": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.0708 13.9291L13.9286 28.0712L1.85742 16L15.9996 1.85786L28.0708 13.9291ZM25.4999 14.0002L15.9996 4.68629L6.50057 14.0002H25.4999Z" fill="#3387C5"/>
|
||||
<mask id="path-2-inside-1_6364_2329" fill="white">
|
||||
<path d="M14.9497 5.05025C13.637 3.7375 11.8565 3 10 3C8.14348 3 6.36301 3.7375 5.05025 5.05025C3.7375 6.36301 3 8.14349 3 10C3 11.8565 3.7375 13.637 5.05025 14.9497L10 10L14.9497 5.05025Z"/>
|
||||
</mask>
|
||||
<path d="M14.9497 5.05025C13.637 3.7375 11.8565 3 10 3C8.14348 3 6.36301 3.7375 5.05025 5.05025C3.7375 6.36301 3 8.14349 3 10C3 11.8565 3.7375 13.637 5.05025 14.9497L10 10L14.9497 5.05025Z" fill="#D9D9D9" stroke="#3387C5" stroke-width="4" mask="url(#path-2-inside-1_6364_2329)"/>
|
||||
<path d="M30 27C30 29 28 31 26 31C24 31 22 29 22 27C22 25 23.5 23.3333 26 20C28.5 23.3333 30 25 30 27Z" fill="#3387C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 916 B |
@@ -1,8 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.5601 10.0096L11.0045 20.565L1.99512 11.5557L12.5507 1.0002L21.5601 10.0096ZM19.6411 10.0627L12.55 3.11115L5.46023 10.0627H19.6411Z" fill="#3387C5"/>
|
||||
<mask id="path-2-inside-1_6364_2418" fill="white">
|
||||
<path d="M11.7667 3.38279C10.7869 2.40299 9.45798 1.85254 8.07231 1.85254C6.68665 1.85254 5.35773 2.40299 4.37792 3.38279C3.39811 4.3626 2.84766 5.6915 2.84766 7.07716C2.84766 8.46281 3.39811 9.79171 4.37792 10.7715L8.07231 7.07716L11.7667 3.38279Z"/>
|
||||
</mask>
|
||||
<path d="M11.7667 3.38279C10.7869 2.40299 9.45798 1.85254 8.07231 1.85254C6.68665 1.85254 5.35773 2.40299 4.37792 3.38279C3.39811 4.3626 2.84766 5.6915 2.84766 7.07716C2.84766 8.46281 3.39811 9.79171 4.37792 10.7715L8.07231 7.07716L11.7667 3.38279Z" fill="#D9D9D9" stroke="#3387C5" stroke-width="3" mask="url(#path-2-inside-1_6364_2418)"/>
|
||||
<path d="M22.9994 19.7656C22.9994 21.2584 21.5066 22.7511 20.0138 22.7511C18.5211 22.7511 17.0283 21.2584 17.0283 19.7656C17.0283 18.2729 18.1479 17.0289 20.0138 14.541C21.8798 17.0289 22.9994 18.2729 22.9994 19.7656Z" fill="#3387C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,8 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.0728 6.79956L7.27946 13.5932L1.48047 7.79395L8.27381 1.00034L14.0728 6.79956ZM5.39227 6.83477L9.23501 6.83408H12.8381L8.2745 2.36003L3.71085 6.83408L5.39227 6.83477Z" fill="#3387C5"/>
|
||||
<mask id="path-2-inside-1_6364_2433" fill="white">
|
||||
<path d="M7.77072 2.53377C7.1401 1.90312 6.28479 1.54883 5.39295 1.54883C4.50111 1.54883 3.6458 1.90312 3.01518 2.53377C2.38455 3.16442 2.03027 4.01977 2.03027 4.91164C2.03027 5.80351 2.38455 6.65886 3.01518 7.28951L5.39295 4.91164L7.77072 2.53377Z"/>
|
||||
</mask>
|
||||
<path d="M7.77072 2.53377C7.1401 1.90312 6.28479 1.54883 5.39295 1.54883C4.50111 1.54883 3.6458 1.90312 3.01518 2.53377C2.38455 3.16442 2.03027 4.01977 2.03027 4.91164C2.03027 5.80351 2.38455 6.65886 3.01518 7.28951L5.39295 4.91164L7.77072 2.53377Z" fill="#D9D9D9" stroke="#3387C5" stroke-width="2" mask="url(#path-2-inside-1_6364_2433)"/>
|
||||
<path d="M15.0003 13.0786C15.0003 14.0394 14.0395 15.0002 13.0788 15.0002C12.118 15.0002 11.1572 14.0394 11.1572 13.0786C11.1572 12.1178 11.8778 11.3172 13.0788 9.71582C14.2797 11.3172 15.0003 12.1178 15.0003 13.0786Z" fill="#3387C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- SORTING ICONS -->
|
||||
<symbol id="icon-sort-unknown" viewBox="0 0 32 32" fill="currentColor">
|
||||
<polygon points="4 10 14 10 9 4 4 10" />
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
<!-- CROSS ICON (ONLY ONE SIZE)-->
|
||||
<symbol id="icon-cross_m" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" fill-rule="evenodd"
|
||||
<path xmlns="http://www.w3.org/2000/svg" fill="currentColor" fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.31579 3.31579C2.89474 3.73684 2.89474 4.42105 3.31579 4.84211L10.4737 12L3.31579 19.1579C2.89474 19.5789 2.89474 20.2632 3.31579 20.6842C3.73684 21.1053 4.42105 21.1053 4.84211 20.6842L12 13.5263L19.1579 20.6842C19.5789 21.1053 20.2632 21.1053 20.6842 20.6842C21.1053 20.2632 21.1053 19.5789 20.6842 19.1579L13.5263 12L20.6842 4.84211C21.1053 4.42105 21.1053 3.73684 20.6842 3.31579C20.2632 2.89474 19.5789 2.89474 19.1579 3.31579L12 10.4737L4.84211 3.31579C4.42105 2.89474 3.73684 2.89474 3.31579 3.31579Z" />
|
||||
</symbol>
|
||||
@@ -298,78 +298,5 @@
|
||||
<rect x="5.35" y="8.32" width="1.01" height="7.07"
|
||||
transform="translate(-6.67 7.61) rotate(-45)" />
|
||||
</symbol>
|
||||
|
||||
<!-- ARROW ICONS END -->
|
||||
<symbol id="icon-no-color" viewBox="0 0 32 32" fill="none">
|
||||
<path
|
||||
d="M16 3.6709C17.2541 5.34249 18.3954 6.80132 19.4092 8.10742C20.6414 9.69492 21.6732 11.0484 22.5078 12.3213C24.1549 14.8333 25 16.9781 25 19.8184C24.9999 25.3957 20.5013 29 16 29C11.4987 29 7.0001 25.3957 7 19.8184C7 16.9781 7.84511 14.8333 9.49219 12.3213C10.3268 11.0484 11.3586 9.69492 12.5908 8.10742C13.6046 6.80132 14.7459 5.34249 16 3.6709Z"
|
||||
stroke="#3387C5" stroke-width="2" />
|
||||
<path d="M3 3L29 29" stroke="#3387C5" stroke-width="2" />
|
||||
</symbol>
|
||||
<symbol id="icon-no-color_m" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M12 3.27051C12.8606 4.4394 13.6453 5.46203 14.3428 6.37891C15.2052 7.5127 15.9265 8.47801 16.5098 9.38574C17.6602 11.1761 18.25 12.7038 18.25 14.7275C18.2499 18.7036 15.1113 21.25 12 21.25C8.88869 21.25 5.75014 18.7036 5.75 14.7275C5.75 12.7038 6.33976 11.1761 7.49023 9.38574C8.07355 8.47801 8.79477 7.5127 9.65723 6.37891C10.3547 5.46203 11.1394 4.4394 12 3.27051Z"
|
||||
stroke="#3387C5" stroke-width="1.5" />
|
||||
<path d="M2.87109 2.72266L21.6643 21.5159" stroke="#3387C5" stroke-width="1.5" />
|
||||
</symbol>
|
||||
<symbol id="icon-no-color_sm" viewBox="0 0 16 16" fill="none">
|
||||
<g clip-path="url(#clip0_6364_2471)">
|
||||
<path
|
||||
d="M8 1.83594C8.627 2.67162 9.19823 3.40073 9.70508 4.05371C10.3211 4.8473 10.8367 5.52384 11.2539 6.16016C12.0775 7.41621 12.5 8.48898 12.5 9.90918C12.5 12.6978 10.2506 14.5 8 14.5C5.74937 14.5 3.50005 12.6978 3.5 9.90918C3.5 8.48898 3.92249 7.41621 4.74609 6.16016C5.16334 5.52384 5.67894 4.8473 6.29492 4.05371C6.80177 3.40073 7.373 2.67162 8 1.83594Z"
|
||||
stroke="#3387C5" />
|
||||
<path d="M1 1L15 15" stroke="#3387C5" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_6364_2471">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</symbol>
|
||||
|
||||
<symbol id="icon-fill-color" viewBox="0 0 32 32" fill="currentColor">
|
||||
<path
|
||||
d="M28.0708 13.9291L13.9286 28.0712L1.85742 16L15.9996 1.85786L28.0708 13.9291ZM25.4999 14.0002L15.9996 4.68629L6.50057 14.0002H25.4999Z"
|
||||
fill="#3387C5" />
|
||||
<mask id="path-2-inside-1_6364_2329" fill="white">
|
||||
<path
|
||||
d="M14.9497 5.05025C13.637 3.7375 11.8565 3 10 3C8.14348 3 6.36301 3.7375 5.05025 5.05025C3.7375 6.36301 3 8.14349 3 10C3 11.8565 3.7375 13.637 5.05025 14.9497L10 10L14.9497 5.05025Z" />
|
||||
</mask>
|
||||
<path
|
||||
d="M14.9497 5.05025C13.637 3.7375 11.8565 3 10 3C8.14348 3 6.36301 3.7375 5.05025 5.05025C3.7375 6.36301 3 8.14349 3 10C3 11.8565 3.7375 13.637 5.05025 14.9497L10 10L14.9497 5.05025Z"
|
||||
fill="#D9D9D9" stroke="#3387C5" stroke-width="4" mask="url(#path-2-inside-1_6364_2329)" />
|
||||
<path
|
||||
d="M30 27C30 29 28 31 26 31C24 31 22 29 22 27C22 25 23.5 23.3333 26 20C28.5 23.3333 30 25 30 27Z"
|
||||
fill="#3387C5" />
|
||||
</symbol>
|
||||
<symbol id="icon-fill-color_m" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M21.5601 10.0096L11.0045 20.565L1.99512 11.5557L12.5507 1.0002L21.5601 10.0096ZM19.6411 10.0627L12.55 3.11115L5.46023 10.0627H19.6411Z"
|
||||
fill="#3387C5" />
|
||||
<mask id="path-2-inside-1_6364_2418" fill="white">
|
||||
<path
|
||||
d="M11.7667 3.38279C10.7869 2.40299 9.45798 1.85254 8.07231 1.85254C6.68665 1.85254 5.35773 2.40299 4.37792 3.38279C3.39811 4.3626 2.84766 5.6915 2.84766 7.07716C2.84766 8.46281 3.39811 9.79171 4.37792 10.7715L8.07231 7.07716L11.7667 3.38279Z" />
|
||||
</mask>
|
||||
<path
|
||||
d="M11.7667 3.38279C10.7869 2.40299 9.45798 1.85254 8.07231 1.85254C6.68665 1.85254 5.35773 2.40299 4.37792 3.38279C3.39811 4.3626 2.84766 5.6915 2.84766 7.07716C2.84766 8.46281 3.39811 9.79171 4.37792 10.7715L8.07231 7.07716L11.7667 3.38279Z"
|
||||
fill="#D9D9D9" stroke="#3387C5" stroke-width="3" mask="url(#path-2-inside-1_6364_2418)" />
|
||||
<path
|
||||
d="M22.9994 19.7656C22.9994 21.2584 21.5066 22.7511 20.0138 22.7511C18.5211 22.7511 17.0283 21.2584 17.0283 19.7656C17.0283 18.2729 18.1479 17.0289 20.0138 14.541C21.8798 17.0289 22.9994 18.2729 22.9994 19.7656Z"
|
||||
fill="#3387C5" />
|
||||
</symbol>
|
||||
<symbol id="icon-fill-color_sm" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path
|
||||
d="M14.0728 6.79956L7.27946 13.5932L1.48047 7.79395L8.27381 1.00034L14.0728 6.79956ZM5.39227 6.83477L9.23501 6.83408H12.8381L8.2745 2.36003L3.71085 6.83408L5.39227 6.83477Z"
|
||||
fill="#3387C5" />
|
||||
<mask id="path-2-inside-1_6364_2433" fill="white">
|
||||
<path
|
||||
d="M7.77072 2.53377C7.1401 1.90312 6.28479 1.54883 5.39295 1.54883C4.50111 1.54883 3.6458 1.90312 3.01518 2.53377C2.38455 3.16442 2.03027 4.01977 2.03027 4.91164C2.03027 5.80351 2.38455 6.65886 3.01518 7.28951L5.39295 4.91164L7.77072 2.53377Z" />
|
||||
</mask>
|
||||
<path
|
||||
d="M7.77072 2.53377C7.1401 1.90312 6.28479 1.54883 5.39295 1.54883C4.50111 1.54883 3.6458 1.90312 3.01518 2.53377C2.38455 3.16442 2.03027 4.01977 2.03027 4.91164C2.03027 5.80351 2.38455 6.65886 3.01518 7.28951L5.39295 4.91164L7.77072 2.53377Z"
|
||||
fill="#D9D9D9" stroke="#3387C5" stroke-width="2" mask="url(#path-2-inside-1_6364_2433)" />
|
||||
<path
|
||||
d="M15.0003 13.0786C15.0003 14.0394 14.0395 15.0002 13.0788 15.0002C12.118 15.0002 11.1572 14.0394 11.1572 13.0786C11.1572 12.1178 11.8778 11.3172 13.0788 9.71582C14.2797 11.3172 15.0003 12.1178 15.0003 13.0786Z"
|
||||
fill="#3387C5" />
|
||||
</symbol>
|
||||
<!-- COLOR PICKER ICONS -->
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 3.6709C17.2541 5.34249 18.3954 6.80132 19.4092 8.10742C20.6414 9.69492 21.6732 11.0484 22.5078 12.3213C24.1549 14.8333 25 16.9781 25 19.8184C24.9999 25.3957 20.5013 29 16 29C11.4987 29 7.0001 25.3957 7 19.8184C7 16.9781 7.84511 14.8333 9.49219 12.3213C10.3268 11.0484 11.3586 9.69492 12.5908 8.10742C13.6046 6.80132 14.7459 5.34249 16 3.6709Z" stroke="#3387C5" stroke-width="2"/>
|
||||
<path d="M3 3L29 29" stroke="#3387C5" stroke-width="2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 553 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 3.27051C12.8606 4.4394 13.6453 5.46203 14.3428 6.37891C15.2052 7.5127 15.9265 8.47801 16.5098 9.38574C17.6602 11.1761 18.25 12.7038 18.25 14.7275C18.2499 18.7036 15.1113 21.25 12 21.25C8.88869 21.25 5.75014 18.7036 5.75 14.7275C5.75 12.7038 6.33976 11.1761 7.49023 9.38574C8.07355 8.47801 8.79477 7.5127 9.65723 6.37891C10.3547 5.46203 11.1394 4.4394 12 3.27051Z" stroke="#3387C5" stroke-width="1.5"/>
|
||||
<path d="M2.87109 2.72266L21.6643 21.5159" stroke="#3387C5" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 599 B |
@@ -1,11 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_6364_2471)">
|
||||
<path d="M8 1.83594C8.627 2.67162 9.19823 3.40073 9.70508 4.05371C10.3211 4.8473 10.8367 5.52384 11.2539 6.16016C12.0775 7.41621 12.5 8.48898 12.5 9.90918C12.5 12.6978 10.2506 14.5 8 14.5C5.74937 14.5 3.50005 12.6978 3.5 9.90918C3.5 8.48898 3.92249 7.41621 4.74609 6.16016C5.16334 5.52384 5.67894 4.8473 6.29492 4.05371C6.80177 3.40073 7.373 2.67162 8 1.83594Z" stroke="#3387C5"/>
|
||||
<path d="M1 1L15 15" stroke="#3387C5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_6364_2471">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 670 B |
@@ -32,8 +32,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0",
|
||||
"@dbeaver/js-helpers": "workspace:*",
|
||||
"@dbeaver/react-translate": "workspace:^",
|
||||
"clsx": "^2",
|
||||
"react": "^19",
|
||||
"react-dom": "^19"
|
||||
|
||||
@@ -136,23 +136,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dbv-kit-button--ghost {
|
||||
--dbv-kit-btn-foreground: var(--dbv-kit-btn-ghost-foreground);
|
||||
--dbv-kit-btn-foreground-hover: var(--dbv-kit-btn-ghost-foreground-hover);
|
||||
--dbv-kit-btn-foreground-active: var(--dbv-kit-btn-ghost-foreground-active);
|
||||
|
||||
--dbv-kit-btn-background: var(--dbv-kit-btn-ghost-background);
|
||||
--dbv-kit-btn-background-hover: var(--dbv-kit-btn-ghost-background-hover);
|
||||
--dbv-kit-btn-background-active: var(--dbv-kit-btn-ghost-background-active);
|
||||
--dbv-kit-btn-border-color: var(--dbv-kit-btn-ghost-border-color);
|
||||
|
||||
--dbv-kit-btn-loader-color: var(--dbv-kit-btn-ghost-foreground);
|
||||
|
||||
&:disabled {
|
||||
color: var(--dbv-kit-control-disabled-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------- */
|
||||
/* Loader */
|
||||
|
||||
@@ -175,10 +158,6 @@
|
||||
/* ---------------------------- */
|
||||
/* Icon */
|
||||
|
||||
.dbv-kit-button:disabled .dbv-kit-button__icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.dbv-kit-button__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ButtonRoot, type ButtonRootProps } from './ButtonRoot.js';
|
||||
import './Button.css';
|
||||
|
||||
export interface ButtonProps extends ButtonRootProps {
|
||||
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
|
||||
variant?: 'primary' | 'secondary' | 'danger';
|
||||
size?: ControlSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
height: 100%;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
gap: var(--dbv-kit-btn-gap);
|
||||
}
|
||||
|
||||
@@ -27,15 +27,5 @@
|
||||
--dbv-kit-btn-danger-background-active: var(--tw-color-red-800);
|
||||
--dbv-kit-btn-danger-border-color: transparent;
|
||||
--dbv-kit-btn-danger-color-outline: var(--tw-color-red-300);
|
||||
|
||||
/* Ghost Button */
|
||||
--dbv-kit-btn-ghost-foreground: var(--dbv-kit-color-primary-600);
|
||||
--dbv-kit-btn-ghost-foreground-hover: var(--dbv-kit-color-primary-600);
|
||||
--dbv-kit-btn-ghost-foreground-active: var(--dbv-kit-color-primary-600);
|
||||
--dbv-kit-btn-ghost-background: transparent;
|
||||
--dbv-kit-btn-ghost-background-hover: var(--dbv-kit-color-primary-50);
|
||||
--dbv-kit-btn-ghost-background-active: var(--dbv-kit-color-primary-100);
|
||||
--dbv-kit-btn-ghost-border-color: transparent;
|
||||
--dbv-kit-btn-ghost-border-color-hover: var(--dbv-kit-color-primary-600);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { ColorIndicator } from './ColorIndicator.js';
|
||||
import { type UseColorPaletteResult } from './useColorPalette.js';
|
||||
import { memo } from 'react';
|
||||
import { useTranslate } from '@dbeaver/react-translate';
|
||||
import { Command } from '../Command.js';
|
||||
|
||||
interface ColorGridProps {
|
||||
colorPalette: UseColorPaletteResult;
|
||||
onColorSelect: (color: string) => void;
|
||||
}
|
||||
|
||||
export const ColorGrid: React.FC<ColorGridProps> = memo(function ColorGrid({ colorPalette, onColorSelect }) {
|
||||
const t = useTranslate();
|
||||
return (
|
||||
<div className="tw:flex tw:flex-col tw:gap-0.5" role="grid">
|
||||
{colorPalette.palette.map(row => (
|
||||
<div key={row[0]?.label ?? ''} role="row" className="tw:flex tw:gap-0.5">
|
||||
{row.map(color => {
|
||||
const isSelected = colorPalette.isSelected(color.rgb);
|
||||
const label = t('dbeaver_ui_kit_color_picker_color_label_' + color.label.replace(' ', '_'), color.label);
|
||||
|
||||
return (
|
||||
<Command
|
||||
key={color.label}
|
||||
render={<div />}
|
||||
role="gridcell"
|
||||
aria-selected={isSelected}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
className="tw:cursor-pointer tw:hover:scale-110 tw:transition-transform"
|
||||
onClick={() => onColorSelect(color.rgb)}
|
||||
>
|
||||
<ColorIndicator color={color.rgb} isSelected={isSelected} />
|
||||
</Command>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { ColorConvert } from '@dbeaver/js-helpers';
|
||||
import clsx from 'clsx';
|
||||
import { memo } from 'react';
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
isSelected?: boolean;
|
||||
}
|
||||
|
||||
export const ColorIndicator = memo(function ColorIndicator({ color, isSelected }: Props) {
|
||||
const isLight = ColorConvert(color).isLight();
|
||||
const selectedMark = isLight ? 'dbv-kit-color-picker__color--selected-light' : 'dbv-kit-color-picker__color--selected-dark';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'dbv_kit_color-picker__color tw:w-5 tw:h-5 tw:rounded-full tw:border tw:border-(--dbv-kit-color-picker-border-color)',
|
||||
isSelected && selectedMark,
|
||||
)}
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
@import './_base.css';
|
||||
|
||||
@layer base {
|
||||
.dbv-kit-color-picker {
|
||||
border: none !important;
|
||||
height: 0px !important;
|
||||
width: 0px !important;
|
||||
overflow: hidden !important;
|
||||
min-height: 0 !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 0 !important;
|
||||
max-height: 0 !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.dbv-kit-color-picker__color--selected-light {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNnB4IiBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0iIzAwMDAwMCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMSwgMCwgMCwgMSkiPjxwYXRoIGQ9Ik0wIDBoNDh2NDhIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTggMzIuMzRMOS42NiAyNGwtMi44MyAyLjgzTDE4IDM4bDI0LTI0LTIuODMtMi44M3oiLz48L2c+PC9zdmc+);
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.dbv-kit-color-picker__color--selected-dark {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNnB4IiBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0iI0ZGRkZGRiI+PHBhdGggZD0iTTAgMGg0OHY0OEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xOCAzMi4zNEw5LjY2IDI0bC0yLjgzIDIuODNMMTggMzhsMjQtMjQtMi44My0yLjgzeiIvPjwvc3ZnPg==);
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { componentProviderWrapper } from '../componentProviderWrapper.js';
|
||||
import { ColorPickerBase } from './ColorPickerBase.js';
|
||||
|
||||
export const ColorPicker = componentProviderWrapper('ColorPicker', ColorPickerBase);
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useDeferredValue, useId, useImperativeHandle, useRef, type ComponentPropsWithRef } from 'react';
|
||||
import './ColorPicker.css';
|
||||
import { Popover } from '../Popover/Popover.js';
|
||||
import { ColorGrid } from './ColorGrid.js';
|
||||
import { Button, ButtonIcon } from '../Button/Button.js';
|
||||
import { ColorIndicator } from './ColorIndicator.js';
|
||||
import { COLOR_PALETTE, type ColorInfo } from './colorPalette.js';
|
||||
import { useColorPalette } from './useColorPalette.js';
|
||||
import { IconButton } from '../IconButton/IconButton.js';
|
||||
import { useTranslate } from '@dbeaver/react-translate';
|
||||
import { Icon } from '../Icon/Icon.js';
|
||||
import { normalizeColorToHex } from './colorUtils.js';
|
||||
|
||||
export type ColorPickerProps = Omit<ComponentPropsWithRef<'input'>, 'size' | 'value' | 'onChange' | 'type'> & {
|
||||
value: string;
|
||||
variant?: 'background' | 'text';
|
||||
size?: 'small' | 'medium' | 'large' | 'xlarge';
|
||||
palette?: ColorInfo[][];
|
||||
onChange?: (value: string | null) => void;
|
||||
};
|
||||
|
||||
export const ColorPickerBase: React.FC<ColorPickerProps> = function ColorPicker({
|
||||
ref,
|
||||
value,
|
||||
variant,
|
||||
size,
|
||||
className,
|
||||
palette = COLOR_PALETTE,
|
||||
title,
|
||||
'aria-label': ariaLabel,
|
||||
style,
|
||||
onChange,
|
||||
...props
|
||||
}) {
|
||||
const t = useTranslate();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const id = useId();
|
||||
const deferredValue = useDeferredValue(value);
|
||||
const colorPalette = useColorPalette(deferredValue, palette);
|
||||
|
||||
useImperativeHandle(ref, () => inputRef.current as HTMLInputElement);
|
||||
|
||||
const handleColorSelect = useCallback((color: string) => {
|
||||
const input = inputRef.current;
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hexColor = normalizeColorToHex(color);
|
||||
const nativeValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
|
||||
nativeValueSetter?.call(input, hexColor);
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}, []);
|
||||
|
||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const selectedColor = event.target.value;
|
||||
onChange?.(selectedColor || null);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<Popover.PopoverDisclosure
|
||||
render={
|
||||
<IconButton
|
||||
aria-labelledby={id}
|
||||
aria-label={ariaLabel ?? t('dbeaver_ui_kit_color_picker_select_color', 'Select color')}
|
||||
title={title}
|
||||
size={size}
|
||||
className={className}
|
||||
style={style}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
{variant === 'text' ? 'A' : <Icon name="fill-color" />}
|
||||
<div className="tw:border-b-4 tw:absolute tw:bottom-0 tw:w-5/6" style={{ borderColor: deferredValue }} />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<Popover.PopoverContent className="tw:p-(--dbv-kit-color-picker-padding) tw:flex tw:flex-col tw:gap-2" modal>
|
||||
<Button type="button" size="small" variant="secondary" className="tw:w-full tw:shrink-0" onClick={() => onChange?.(null)}>
|
||||
<ButtonIcon placement="start">
|
||||
<Icon name="no-color" />
|
||||
</ButtonIcon>
|
||||
{t('dbeaver_ui_kit_color_picker_none', 'None')}
|
||||
</Button>
|
||||
<ColorGrid colorPalette={colorPalette} onColorSelect={handleColorSelect} />
|
||||
|
||||
<Button render={<label htmlFor={id} />} type="button" size="small" variant="secondary" className="tw:w-full tw:shrink-0">
|
||||
<ButtonIcon placement="start">
|
||||
<ColorIndicator color={deferredValue} isSelected={colorPalette.isCustomColor(deferredValue)} />
|
||||
</ButtonIcon>
|
||||
{t('dbeaver_ui_kit_color_picker_custom', 'Custom')}
|
||||
</Button>
|
||||
<input ref={inputRef} id={id} type="color" className="dbv-kit-color-picker" value={value} onChange={handleChange} {...props} />
|
||||
</Popover.PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--dbv-kit-color-picker-border-color: var(--dbv-kit-color-control-inactive);
|
||||
--dbv-kit-color-picker-padding: calc(var(--tw-spacing) * 2.5);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export interface ColorInfo {
|
||||
label: string;
|
||||
rgb: string;
|
||||
}
|
||||
|
||||
export const COLOR_PALETTE: ColorInfo[][] = [
|
||||
// Row 1: Grayscale
|
||||
[
|
||||
{ label: 'black', rgb: 'rgb(0, 0, 0)' },
|
||||
{ label: 'dark gray 4', rgb: 'rgb(67, 67, 67)' },
|
||||
{ label: 'dark gray 3', rgb: 'rgb(102, 102, 102)' },
|
||||
{ label: 'dark gray 2', rgb: 'rgb(153, 153, 153)' },
|
||||
{ label: 'dark gray 1', rgb: 'rgb(183, 183, 183)' },
|
||||
{ label: 'gray', rgb: 'rgb(204, 204, 204)' },
|
||||
{ label: 'light gray 1', rgb: 'rgb(217, 217, 217)' },
|
||||
{ label: 'light gray 2', rgb: 'rgb(239, 239, 239)' },
|
||||
{ label: 'light gray 3', rgb: 'rgb(243, 243, 243)' },
|
||||
{ label: 'white', rgb: 'rgb(255, 255, 255)' },
|
||||
],
|
||||
// Row 2: Primary colors
|
||||
[
|
||||
{ label: 'red berry', rgb: 'rgb(152, 0, 0)' },
|
||||
{ label: 'red', rgb: 'rgb(255, 0, 0)' },
|
||||
{ label: 'orange', rgb: 'rgb(255, 153, 0)' },
|
||||
{ label: 'yellow', rgb: 'rgb(255, 255, 0)' },
|
||||
{ label: 'green', rgb: 'rgb(0, 255, 0)' },
|
||||
{ label: 'cyan', rgb: 'rgb(0, 255, 255)' },
|
||||
{ label: 'cornflower blue', rgb: 'rgb(74, 134, 232)' },
|
||||
{ label: 'blue', rgb: 'rgb(0, 0, 255)' },
|
||||
{ label: 'purple', rgb: 'rgb(153, 0, 255)' },
|
||||
{ label: 'magenta', rgb: 'rgb(255, 0, 255)' },
|
||||
],
|
||||
// Row 3: Light 3
|
||||
[
|
||||
{ label: 'light red berry 3', rgb: 'rgb(230, 184, 175)' },
|
||||
{ label: 'light red 3', rgb: 'rgb(244, 204, 204)' },
|
||||
{ label: 'light orange 3', rgb: 'rgb(252, 229, 205)' },
|
||||
{ label: 'light yellow 3', rgb: 'rgb(255, 242, 204)' },
|
||||
{ label: 'light green 3', rgb: 'rgb(217, 234, 211)' },
|
||||
{ label: 'light cyan 3', rgb: 'rgb(208, 224, 227)' },
|
||||
{ label: 'light cornflower blue 3', rgb: 'rgb(201, 218, 248)' },
|
||||
{ label: 'light blue 3', rgb: 'rgb(207, 226, 243)' },
|
||||
{ label: 'light purple 3', rgb: 'rgb(217, 210, 233)' },
|
||||
{ label: 'light magenta 3', rgb: 'rgb(234, 209, 220)' },
|
||||
],
|
||||
// Row 4: Light 2
|
||||
[
|
||||
{ label: 'light red berry 2', rgb: 'rgb(221, 126, 107)' },
|
||||
{ label: 'light red 2', rgb: 'rgb(234, 153, 153)' },
|
||||
{ label: 'light orange 2', rgb: 'rgb(249, 203, 156)' },
|
||||
{ label: 'light yellow 2', rgb: 'rgb(255, 229, 153)' },
|
||||
{ label: 'light green 2', rgb: 'rgb(182, 215, 168)' },
|
||||
{ label: 'light cyan 2', rgb: 'rgb(162, 196, 201)' },
|
||||
{ label: 'light cornflower blue 2', rgb: 'rgb(164, 194, 244)' },
|
||||
{ label: 'light blue 2', rgb: 'rgb(159, 197, 232)' },
|
||||
{ label: 'light purple 2', rgb: 'rgb(180, 167, 214)' },
|
||||
{ label: 'light magenta 2', rgb: 'rgb(213, 166, 189)' },
|
||||
],
|
||||
// Row 5: Light 1
|
||||
[
|
||||
{ label: 'light red berry 1', rgb: 'rgb(204, 65, 37)' },
|
||||
{ label: 'light red 1', rgb: 'rgb(224, 102, 102)' },
|
||||
{ label: 'light orange 1', rgb: 'rgb(246, 178, 107)' },
|
||||
{ label: 'light yellow 1', rgb: 'rgb(255, 217, 102)' },
|
||||
{ label: 'light green 1', rgb: 'rgb(147, 196, 125)' },
|
||||
{ label: 'light cyan 1', rgb: 'rgb(118, 165, 175)' },
|
||||
{ label: 'light cornflower blue 1', rgb: 'rgb(109, 158, 235)' },
|
||||
{ label: 'light blue 1', rgb: 'rgb(111, 168, 220)' },
|
||||
{ label: 'light purple 1', rgb: 'rgb(142, 124, 195)' },
|
||||
{ label: 'light magenta 1', rgb: 'rgb(194, 123, 160)' },
|
||||
],
|
||||
// Row 6: Dark 1
|
||||
[
|
||||
{ label: 'dark red berry 1', rgb: 'rgb(166, 28, 0)' },
|
||||
{ label: 'dark red 1', rgb: 'rgb(204, 0, 0)' },
|
||||
{ label: 'dark orange 1', rgb: 'rgb(230, 145, 56)' },
|
||||
{ label: 'dark yellow 1', rgb: 'rgb(241, 194, 50)' },
|
||||
{ label: 'dark green 1', rgb: 'rgb(106, 168, 79)' },
|
||||
{ label: 'dark cyan 1', rgb: 'rgb(69, 129, 142)' },
|
||||
{ label: 'dark cornflower blue 1', rgb: 'rgb(60, 120, 216)' },
|
||||
{ label: 'dark blue 1', rgb: 'rgb(61, 133, 198)' },
|
||||
{ label: 'dark purple 1', rgb: 'rgb(103, 78, 167)' },
|
||||
{ label: 'dark magenta 1', rgb: 'rgb(166, 77, 121)' },
|
||||
],
|
||||
// Row 7: Dark 2
|
||||
[
|
||||
{ label: 'dark red berry 2', rgb: 'rgb(133, 32, 12)' },
|
||||
{ label: 'dark red 2', rgb: 'rgb(153, 0, 0)' },
|
||||
{ label: 'dark orange 2', rgb: 'rgb(180, 95, 6)' },
|
||||
{ label: 'dark yellow 2', rgb: 'rgb(191, 144, 0)' },
|
||||
{ label: 'dark green 2', rgb: 'rgb(56, 118, 29)' },
|
||||
{ label: 'dark cyan 2', rgb: 'rgb(19, 79, 92)' },
|
||||
{ label: 'dark cornflower blue 2', rgb: 'rgb(17, 85, 204)' },
|
||||
{ label: 'dark blue 2', rgb: 'rgb(11, 83, 148)' },
|
||||
{ label: 'dark purple 2', rgb: 'rgb(53, 28, 117)' },
|
||||
{ label: 'dark magenta 2', rgb: 'rgb(116, 27, 71)' },
|
||||
],
|
||||
// Row 8: Dark 3
|
||||
[
|
||||
{ label: 'dark red berry 3', rgb: 'rgb(91, 15, 0)' },
|
||||
{ label: 'dark red 3', rgb: 'rgb(102, 0, 0)' },
|
||||
{ label: 'dark orange 3', rgb: 'rgb(120, 63, 4)' },
|
||||
{ label: 'dark yellow 3', rgb: 'rgb(127, 96, 0)' },
|
||||
{ label: 'dark green 3', rgb: 'rgb(39, 78, 19)' },
|
||||
{ label: 'dark cyan 3', rgb: 'rgb(12, 52, 61)' },
|
||||
{ label: 'dark cornflower blue 3', rgb: 'rgb(28, 69, 135)' },
|
||||
{ label: 'dark blue 3', rgb: 'rgb(7, 55, 99)' },
|
||||
{ label: 'dark purple 3', rgb: 'rgb(32, 18, 77)' },
|
||||
{ label: 'dark magenta 3', rgb: 'rgb(76, 17, 48)' },
|
||||
],
|
||||
];
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { ColorConvert } from '@dbeaver/js-helpers';
|
||||
|
||||
export function normalizeColorToHex(color: string): string {
|
||||
return ColorConvert(color).hex();
|
||||
}
|
||||
|
||||
export function normalizeColorToRgb(color: string): string {
|
||||
return ColorConvert(color).rgb().string();
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { normalizeColorToHex } from './colorUtils.js';
|
||||
import { COLOR_PALETTE, type ColorInfo } from './colorPalette.js';
|
||||
|
||||
export interface UseColorPaletteResult {
|
||||
palette: ColorInfo[][];
|
||||
paletteColorsHex: Set<string>;
|
||||
isSelected: (targetColor: string) => boolean;
|
||||
isCustomColor: (color: string) => boolean;
|
||||
}
|
||||
|
||||
export function useColorPalette(value: string | undefined, palette: ColorInfo[][] = COLOR_PALETTE): UseColorPaletteResult {
|
||||
const normalized = value !== undefined ? normalizeColorToHex(value) : undefined;
|
||||
const paletteColorsHex = useMemo(() => new Set(palette.flat().map(color => normalizeColorToHex(color.rgb))), [palette]);
|
||||
|
||||
const isSelected = useMemo(
|
||||
() => (targetColor: string) => {
|
||||
const targetHex = normalizeColorToHex(targetColor);
|
||||
return normalized === targetHex;
|
||||
},
|
||||
[normalized],
|
||||
);
|
||||
|
||||
const isCustomColor = useMemo(
|
||||
() => (color: string) => {
|
||||
if (!color) {
|
||||
return false;
|
||||
}
|
||||
const normalized = normalizeColorToHex(color);
|
||||
return typeof normalized === 'string' && !paletteColorsHex.has(normalized);
|
||||
},
|
||||
[paletteColorsHex],
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
palette,
|
||||
paletteColorsHex,
|
||||
isSelected,
|
||||
isCustomColor,
|
||||
}),
|
||||
[palette, paletteColorsHex, isSelected, isCustomColor],
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export { Command } from '@ariakit/react';
|
||||
@@ -1,11 +1,3 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import type { ButtonBase, ButtonBaseIcon } from './Button/Button.js';
|
||||
import type { IconButtonBase } from './IconButton/IconButton.js';
|
||||
@@ -15,14 +7,12 @@ import type { InputBase } from './Input/Input.js';
|
||||
import type { RadioGroup } from './Radio/RadioGroup.js';
|
||||
import type { Select } from './Select/Select.js';
|
||||
import type { SelectField } from './Select/SelectField.js';
|
||||
import type { ColorPickerBase } from './ColorPicker/ColorPickerBase.js';
|
||||
|
||||
export interface IComponentProvider {
|
||||
Button?: typeof ButtonBase;
|
||||
ButtonIcon?: typeof ButtonBaseIcon;
|
||||
IconButton?: typeof IconButtonBase;
|
||||
Input?: typeof InputBase;
|
||||
ColorPicker?: typeof ColorPickerBase;
|
||||
Checkbox?: typeof CheckboxBase;
|
||||
Radio?: typeof Radio;
|
||||
RadioGroup?: typeof RadioGroup;
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
type IconNames =
|
||||
@@ -19,9 +11,7 @@ type IconNames =
|
||||
| 'cross'
|
||||
| 'sort-unknown'
|
||||
| 'arrow-up'
|
||||
| 'arrow-down'
|
||||
| 'no-color'
|
||||
| 'fill-color';
|
||||
| 'arrow-down';
|
||||
|
||||
export interface IconProps {
|
||||
name: IconNames;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible,
|
||||
&[data-focus-visible='true'] {
|
||||
&[data-focus-visible] {
|
||||
outline: var(--dbv-kit-icon-btn-outline-width) solid var(--dbv-kit-icon-btn-outline-color);
|
||||
outline-offset: var(--dbv-kit-icon-btn-outline-offset);
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
&:active,
|
||||
&[data-active='true'] {
|
||||
&[data-active] {
|
||||
color: var(--dbv-kit-icon-btn-foreground-active);
|
||||
background-color: var(--dbv-kit-icon-btn-background-active);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
@import './_base.css';
|
||||
|
||||
@layer base {
|
||||
.dbv-kit-popover__content {
|
||||
z-index: 1000;
|
||||
min-width: 100%;
|
||||
max-height: var(--dbv-kit-popover-height);
|
||||
overflow-y: auto;
|
||||
background-color: var(--dbv-kit-popover-content-background);
|
||||
color: var(--dbv-kit-popover-content-foreground);
|
||||
border-color: var(--dbv-kit-popover-border-color);
|
||||
border-radius: var(--dbv-kit-popover-border-radius);
|
||||
border-width: var(--dbv-kit-popover-border-width);
|
||||
border-style: var(--dbv-kit-popover-border-style);
|
||||
box-shadow: var(--dbv-kit-popover-content-shadow);
|
||||
outline: 0;
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
opacity: 0;
|
||||
transform-origin: top;
|
||||
transition-property: opacity, scale, translate;
|
||||
transition-timing-function: var(--tw-ease-in-out);
|
||||
transition-duration: 150ms;
|
||||
scale: 0.95;
|
||||
translate: 0 -0.5rem;
|
||||
|
||||
&[data-enter] {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
translate: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import './Popover.css';
|
||||
|
||||
export function Popover({ children, ...props }: Ariakit.PopoverProviderProps) {
|
||||
return <Ariakit.PopoverProvider {...props}>{children}</Ariakit.PopoverProvider>;
|
||||
}
|
||||
|
||||
export function PopoverDisclosure({ className, ...props }: Ariakit.PopoverDisclosureProps) {
|
||||
return <Ariakit.PopoverDisclosure className={clsx('dbv-kit-popover__disclosure', className)} {...props} />;
|
||||
}
|
||||
|
||||
export function PopoverArrow({ className, ...props }: Ariakit.PopoverArrowProps) {
|
||||
return <Ariakit.PopoverArrow className={clsx('dbv-kit-popover__arrow', className)} {...props} />;
|
||||
}
|
||||
|
||||
export function PopoverHeading({ className, ...props }: Ariakit.PopoverHeadingProps) {
|
||||
return <Ariakit.PopoverHeading className={clsx('dbv-kit-popover__heading', className)} {...props} />;
|
||||
}
|
||||
|
||||
export function PopoverDescription({ className, ...props }: Ariakit.PopoverDescriptionProps) {
|
||||
return <Ariakit.PopoverDescription className={clsx('dbv-kit-popover__description', className)} {...props} />;
|
||||
}
|
||||
|
||||
export function PopoverContent({ className, ...props }: Ariakit.PopoverProps) {
|
||||
return <Ariakit.Popover className={clsx('dbv-kit-popover__content', className)} {...props} />;
|
||||
}
|
||||
|
||||
Popover.PopoverDisclosure = PopoverDisclosure;
|
||||
Popover.PopoverArrow = PopoverArrow;
|
||||
Popover.PopoverHeading = PopoverHeading;
|
||||
Popover.PopoverDescription = PopoverDescription;
|
||||
Popover.PopoverContent = PopoverContent;
|
||||
@@ -1,14 +0,0 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--dbv-kit-popover-height: var(--popover-available-height);
|
||||
|
||||
--dbv-kit-popover-border-width: var(--dbv-kit-control-border-width);
|
||||
--dbv-kit-popover-border-color: var(--dbv-kit-color-control-inactive);
|
||||
--dbv-kit-popover-border-style: solid;
|
||||
--dbv-kit-popover-border-radius: var(--dbv-kit-control-border-radius);
|
||||
|
||||
--dbv-kit-popover-content-background: var(--dbv-kit-color-background);
|
||||
--dbv-kit-popover-content-foreground: var(--dbv-kit-color-foreground);
|
||||
--dbv-kit-popover-content-shadow: var(--tw-shadow-md);
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,6 @@ export interface SelectFieldProps<T, ItemType = SelectItem<T>> {
|
||||
/** Options array - can be SelectOption objects or arbitrary objects */
|
||||
items: ItemType[];
|
||||
|
||||
/**
|
||||
* Function to extract serialized value from items
|
||||
* Example: (key) => JSON.stringify(key)
|
||||
*/
|
||||
itemValueSerialized?: PropertyGetter<T, string>;
|
||||
|
||||
/**
|
||||
* Function to extract value from items
|
||||
* Example: (item) => item.id
|
||||
@@ -80,9 +74,9 @@ export interface SelectFieldProps<T, ItemType = SelectItem<T>> {
|
||||
|
||||
autoFocusItemsOnShow?: boolean;
|
||||
|
||||
'aria-labelledby'?: string;
|
||||
'aria-labelledby'?: string,
|
||||
|
||||
'aria-label'?: string;
|
||||
'aria-label'?: string
|
||||
|
||||
id?: string;
|
||||
}
|
||||
@@ -97,7 +91,6 @@ export function SelectField<T, ItemType extends {} = SelectItem<T>>({
|
||||
value,
|
||||
onChange,
|
||||
itemValue,
|
||||
itemValueSerialized,
|
||||
itemRender,
|
||||
itemDisabled,
|
||||
label,
|
||||
@@ -116,9 +109,6 @@ export function SelectField<T, ItemType extends {} = SelectItem<T>>({
|
||||
const getItemValue = (item: ItemType): T =>
|
||||
getValueByPath<ItemType, T>(item, itemValue, i => ('value' in i ? (i as unknown as SelectItem<T>).value : (i as unknown as T)));
|
||||
|
||||
const getItemValueSerialized = (item: ItemType): string =>
|
||||
getValueByPath<T, string>(getItemValue(item), itemValueSerialized, key => JSON.stringify(key));
|
||||
|
||||
const renderItem = (item: ItemType): React.ReactNode =>
|
||||
getValueByPath<ItemType, React.ReactNode>(item, itemRender, i => ('label' in i ? (i as unknown as SelectItem<T>).label : String(i)));
|
||||
|
||||
@@ -132,36 +122,25 @@ export function SelectField<T, ItemType extends {} = SelectItem<T>>({
|
||||
return firstEnabledItem ? getItemValue(firstEnabledItem) : undefined;
|
||||
});
|
||||
|
||||
const handleChange = (newValue: string | string[]) => {
|
||||
// TODO: add support for multi-select
|
||||
|
||||
const newItem = items.find(item => getItemValueSerialized(item) === newValue);
|
||||
if (!newItem) {
|
||||
return;
|
||||
}
|
||||
const newItemValue = getItemValue(newItem);
|
||||
setSelectedValue(newItemValue);
|
||||
onChange?.(newItemValue);
|
||||
const handleChange = (newValue: T) => {
|
||||
setSelectedValue(newValue);
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
const currentValue = value !== undefined ? value : selectedValue;
|
||||
let currentValueSerialized = undefined;
|
||||
|
||||
if (currentValue !== undefined) {
|
||||
currentValueSerialized = itemValueSerialized ? itemValueSerialized(currentValue) : JSON.stringify(currentValue);
|
||||
}
|
||||
const selectedItem = items.find(item => getItemValue(item) === currentValue);
|
||||
|
||||
const selectedItem = currentValue !== undefined ? items.find(item => getItemValueSerialized(item) === currentValueSerialized) : undefined;
|
||||
const displayValue = selectedRender ? selectedRender(currentValue, selectedItem) : selectedItem ? renderItem(selectedItem) : '';
|
||||
|
||||
return (
|
||||
<div className={clsx('dbv-kit-select-field', className)}>
|
||||
<SelectProvider value={currentValueSerialized} setValue={val => handleChange(val)} store={store}>
|
||||
<SelectProvider value={currentValue as any} setValue={val => handleChange(val as T)} store={store}>
|
||||
{label && <SelectLabel className={clsx(required && 'dbv-kit-select__label--required')}>{label}</SelectLabel>}
|
||||
|
||||
<Select id={id} name={name} disabled={disabled} required={required}>
|
||||
{displayValue}
|
||||
{arrowIcon ?? <Select.Arrow className="dbv-kit-select__arrow-icon" />}
|
||||
{arrowIcon ?? <Select.Arrow className='dbv-kit-select__arrow-icon' />}
|
||||
</Select>
|
||||
{description && <span className="dbv-kit-select__description">{description}</span>}
|
||||
|
||||
@@ -170,7 +149,11 @@ export function SelectField<T, ItemType extends {} = SelectItem<T>>({
|
||||
<div className="dbv-kit-select__empty">{noItemsPlaceholder}</div>
|
||||
) : (
|
||||
items.map(item => (
|
||||
<SelectItem key={getItemValueSerialized(item)} value={getItemValueSerialized(item)} disabled={isItemDisabled(item)}>
|
||||
<SelectItem
|
||||
key={String(getItemValue(item))}
|
||||
value={getItemValue(item) as any}
|
||||
disabled={isItemDisabled(item)}
|
||||
>
|
||||
{renderItem(item)}
|
||||
</SelectItem>
|
||||
))
|
||||
|
||||
@@ -24,8 +24,6 @@ export {
|
||||
export { Button, ButtonBase, type ButtonProps, ButtonIcon, type ButtonIconProps } from './Button/Button.js';
|
||||
export { IconButton, IconButtonBase, type IconButtonProps } from './IconButton/IconButton.js';
|
||||
export { Checkbox, CheckboxBase, type CheckboxProps } from './Checkbox/Checkbox.js';
|
||||
export { ColorPicker } from './ColorPicker/ColorPicker.js';
|
||||
export { ColorPickerBase, type ColorPickerProps } from './ColorPicker/ColorPickerBase.js';
|
||||
export { Input, InputBase, type InputProps } from './Input/Input.js';
|
||||
export {
|
||||
Select,
|
||||
|
||||
@@ -9,13 +9,5 @@
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../common-typescript/@dbeaver/js-helpers"
|
||||
},
|
||||
{
|
||||
"path": "../react-translate"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "^0",
|
||||
"color": "^5.0.2",
|
||||
"p-debounce": "^4.0.0",
|
||||
"p-memoize": "^8.0.0",
|
||||
"quick-lru": "^7.1.0"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export { default as ColorConvert } from 'color';
|
||||
@@ -6,7 +6,6 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export * from './ColorConvert.js';
|
||||
export * from './debouncePromise.js';
|
||||
export * from './isDefined.js';
|
||||
export * from './isNotNullDefined.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,12 @@ import {
|
||||
ResourceKeyUtils,
|
||||
} from '@cloudbeaver/core-resource';
|
||||
import { EAdminPermission, SessionPermissionsResource } from '@cloudbeaver/core-root';
|
||||
import { type AdminAuthProviderConfiguration, type GetAuthProviderConfigurationsQueryVariables, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import {
|
||||
type AdminAuthProviderConfiguration,
|
||||
type GetAuthProviderConfigurationsQueryVariables,
|
||||
GraphQLService,
|
||||
type SaveAuthProviderConfigurationQueryVariables,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { AuthProviderConfiguration } from './AuthProvidersResource.js';
|
||||
|
||||
@@ -39,7 +44,7 @@ export class AuthConfigurationsResource extends CachedMapResource<string, AuthCo
|
||||
permissionsResource.require(this, EAdminPermission.admin).outdateResource(this);
|
||||
}
|
||||
|
||||
async saveConfiguration(config: AuthConfiguration): Promise<AuthConfiguration> {
|
||||
async saveConfiguration(config: SaveAuthProviderConfigurationQueryVariables): Promise<AuthConfiguration> {
|
||||
await this.performUpdate(config.id, [], async () => {
|
||||
const response = await this.graphQLService.sdk.saveAuthProviderConfiguration(config);
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
"react-hotkeys-hook": "^5",
|
||||
"react-minisearch": "^7",
|
||||
"reakit": "^1",
|
||||
"reakit-system": "^0",
|
||||
"reakit-utils": "^0",
|
||||
"subscript": "^9",
|
||||
"tslib": "^2"
|
||||
|
||||
@@ -33,14 +33,6 @@
|
||||
--dbv-kit-btn-danger-background-hover: color-mix(in srgb, var(--theme-negative), white 10%);
|
||||
--dbv-kit-btn-danger-background-active: color-mix(in srgb, var(--theme-negative), white 25%);
|
||||
|
||||
--dbv-kit-btn-ghost-foreground: var(--theme-link-color);
|
||||
--dbv-kit-btn-ghost-foreground-hover: var(--theme-link-color);
|
||||
--dbv-kit-btn-ghost-foreground-active: var(--theme-link-color);
|
||||
--dbv-kit-btn-ghost-background: transparent;
|
||||
--dbv-kit-btn-ghost-background-hover: color-mix(in srgb, transparent, var(--theme-primary) 5%);
|
||||
--dbv-kit-btn-ghost-background-active: color-mix(in srgb, transparent, var(--theme-primary) 20%);
|
||||
--dbv-kit-btn-ghost-border-color: transparent;
|
||||
|
||||
min-width: 64px;
|
||||
|
||||
font-family: Roboto, sans-serif;
|
||||
|
||||
@@ -11,11 +11,9 @@ import style from './Cell.module.css';
|
||||
import { Container } from './Containers/Container.js';
|
||||
import { s } from './s.js';
|
||||
import { useS } from './useS.js';
|
||||
import { Clickable, type ReakitComponent } from './Clickable.js';
|
||||
import type { ClickableOptions } from 'reakit';
|
||||
|
||||
interface Props extends ClickableOptions {
|
||||
description?: React.ReactNode | string;
|
||||
interface Props {
|
||||
description?: React.ReactElement | string;
|
||||
before?: React.ReactElement;
|
||||
after?: React.ReactElement;
|
||||
ripple?: boolean;
|
||||
@@ -24,39 +22,27 @@ interface Props extends ClickableOptions {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Cell: ReakitComponent<'div', Props> = observer(function Cell({
|
||||
as: asElement,
|
||||
before,
|
||||
after,
|
||||
description,
|
||||
className,
|
||||
ripple = true,
|
||||
big,
|
||||
children,
|
||||
...rest
|
||||
}) {
|
||||
export const Cell = observer<Props>(function Cell({ before, after, description, className, ripple = true, big, children }) {
|
||||
const styles = useS(style);
|
||||
|
||||
return (
|
||||
<Clickable {...rest} as={asElement ?? 'div'} className={s(styles, { ripple, big }, className)}>
|
||||
<div className={s(styles, { ripple, big }, className)}>
|
||||
<Container className={s(styles, { main: true })} gap parent center dense>
|
||||
{before && (
|
||||
<Container className={s(styles, { before: true })} keepSize>
|
||||
{before}
|
||||
</Container>
|
||||
)}
|
||||
{(children || description) && (
|
||||
<Container className={s(styles, { info: true })} zeroBasis>
|
||||
{children}
|
||||
{description && <Container className={s(styles, { description: true })}>{description}</Container>}
|
||||
</Container>
|
||||
)}
|
||||
<Container className={s(styles, { info: true })} zeroBasis>
|
||||
{children}
|
||||
{description && <Container className={s(styles, { description: true })}>{description}</Container>}
|
||||
</Container>
|
||||
{after && (
|
||||
<Container className={s(styles, { after: true })} keepSize>
|
||||
{after}
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
</Clickable>
|
||||
</div>
|
||||
);
|
||||
}) as ReakitComponent<'div', Props>;
|
||||
});
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* 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 { Clickable as ReakitClickable } from 'reakit';
|
||||
export type { Component as ReakitComponent } from 'reakit-system';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { type ClickableOptions, Clickable as ReakitClickable } from 'reakit';
|
||||
|
||||
export const Clickable: typeof ReakitClickable = function Clickable(props: any) {
|
||||
const Component = ReakitClickable;
|
||||
import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './Menu/ReakitProxyComponent.js';
|
||||
|
||||
return <Component {...props} />;
|
||||
} as typeof ReakitClickable;
|
||||
export const Clickable: ReakitProxyComponent<'button', ClickableOptions> = observer<ReakitProxyComponentOptions<'button', ClickableOptions>>(
|
||||
function Clickable({ children, ...rest }) {
|
||||
const Component = ReakitClickable;
|
||||
|
||||
return <Component {...rest}>{children}</Component>;
|
||||
},
|
||||
) as ReakitProxyComponent<'button', ClickableOptions>;
|
||||
|
||||
@@ -31,7 +31,6 @@ export type SelectBaseProps<TKey, TValue> = Omit<
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
keySelector?: (item: TValue, index: number) => TKey;
|
||||
serializedKeySelector?: (key: TKey) => string;
|
||||
valueSelector?: (item: TValue) => string;
|
||||
titleSelector?: (item: TValue) => string | undefined;
|
||||
iconSelector?: (item: TValue) => string | React.ReactElement | undefined;
|
||||
@@ -78,7 +77,6 @@ export const Select: SelectType = observer(function Select({
|
||||
id,
|
||||
keySelector = v => v,
|
||||
valueSelector = v => v,
|
||||
serializedKeySelector,
|
||||
iconSelector,
|
||||
titleSelector,
|
||||
isDisabled,
|
||||
@@ -185,7 +183,6 @@ export const Select: SelectType = observer(function Select({
|
||||
id={inputId}
|
||||
aria-label={children || title}
|
||||
itemValue={itemValue}
|
||||
itemValueSerialized={serializedKeySelector}
|
||||
itemRender={itemRender}
|
||||
itemDisabled={itemDisabled}
|
||||
name={name}
|
||||
|
||||
@@ -36,7 +36,6 @@ export const WEBSITE_LINKS = {
|
||||
LATEST_COMMUNITY_VERSION_PAGE: 'https://dbeaver.com/product/cloudbeaver-ce-version.json',
|
||||
TEAM_ARCHIVE: 'https://dbeaver.com/downloads-team',
|
||||
CONTACT_PAGE: 'https://dbeaver.com/contact/',
|
||||
TECH_SUPPORT_PAGE: 'https://dbeaver.com/tickets/',
|
||||
|
||||
getTeamArchiveById(id: string): string {
|
||||
return `${WEBSITE_LINKS.TEAM_ARCHIVE}/${id}`;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { createSettingsLayer, ROOT_SETTINGS_LAYER, EditableSettingsSource } from '@cloudbeaver/core-settings';
|
||||
import { createSettingsLayer, ROOT_SETTINGS_LAYER, SettingsSource } from '@cloudbeaver/core-settings';
|
||||
|
||||
export const PRODUCT_SETTINGS_LAYER = createSettingsLayer(ROOT_SETTINGS_LAYER, 'product');
|
||||
|
||||
@injectable()
|
||||
export class ProductSettingsService extends EditableSettingsSource {
|
||||
export class ProductSettingsService extends SettingsSource {
|
||||
private readonly settings: Map<string, any>;
|
||||
|
||||
constructor() {
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
"@cloudbeaver/core-sdk": "workspace:*",
|
||||
"@cloudbeaver/core-settings": "workspace:*",
|
||||
"@cloudbeaver/core-utils": "workspace:*",
|
||||
"@dbeaver/js-helpers": "workspace:*",
|
||||
"mobx": "^6",
|
||||
"rxjs": "^7",
|
||||
"tslib": "^2"
|
||||
|
||||
@@ -6,26 +6,18 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { ROOT_SETTINGS_LAYER, SettingsManagerService, SettingsResolverService } from '@cloudbeaver/core-settings';
|
||||
import { SettingsManagerService, SettingsResolverService } from '@cloudbeaver/core-settings';
|
||||
|
||||
import { ServerSettingsManagerService } from './Settings/ServerSettingsManagerService.js';
|
||||
import { ServerDefaultSettingsService } from './Settings/ServerDefaultSettingsService.js';
|
||||
import { SERVER_SETTINGS_LAYER, ServerSettingsService } from './Settings/ServerSettingsService.js';
|
||||
|
||||
@injectable(() => [
|
||||
SettingsResolverService,
|
||||
ServerSettingsService,
|
||||
SettingsManagerService,
|
||||
ServerSettingsManagerService,
|
||||
ServerDefaultSettingsService,
|
||||
])
|
||||
@injectable(() => [SettingsResolverService, ServerSettingsService, SettingsManagerService, ServerSettingsManagerService])
|
||||
export class RootBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly settingsResolverService: SettingsResolverService,
|
||||
private readonly serverSettingsService: ServerSettingsService,
|
||||
private readonly settingsManagerService: SettingsManagerService,
|
||||
private readonly serverSettingsManagerService: ServerSettingsManagerService,
|
||||
private readonly serverDefaultSettingsService: ServerDefaultSettingsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -33,6 +25,5 @@ export class RootBootstrap extends Bootstrap {
|
||||
override register(): void {
|
||||
this.settingsManagerService.registerSettings(this.serverSettingsManagerService.getSettingsGetter(), this.serverSettingsManagerService.loaders);
|
||||
this.settingsResolverService.addResolver(SERVER_SETTINGS_LAYER, this.serverSettingsService);
|
||||
this.settingsResolverService.addResolver(ROOT_SETTINGS_LAYER, this.serverDefaultSettingsService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,17 +76,15 @@ export class SessionResource extends CachedDataResource<SessionState | null> {
|
||||
|
||||
async changeLanguage(locale: string): Promise<void> {
|
||||
await this.load();
|
||||
//TODO we check "!locale" because of the bug described in CB-6048, should be removed when fixed
|
||||
if (this.data?.locale === locale || !locale) {
|
||||
return;
|
||||
}
|
||||
await this.graphQLService.sdk.changeSessionLanguage({ locale });
|
||||
|
||||
this.performUpdate(undefined, [], async () => {
|
||||
if (this.data?.locale === locale) {
|
||||
return;
|
||||
}
|
||||
await this.graphQLService.sdk.changeSessionLanguage({ locale });
|
||||
|
||||
if (this.data) {
|
||||
this.data.locale = locale;
|
||||
}
|
||||
});
|
||||
if (this.data) {
|
||||
this.data.locale = locale;
|
||||
}
|
||||
|
||||
this.markOutdated();
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { action, makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SettingsSource } from '@cloudbeaver/core-settings';
|
||||
import { getObjectPropertyDefaultValue } from '@cloudbeaver/core-sdk';
|
||||
import { isNotNullDefined } from '@dbeaver/js-helpers';
|
||||
import { ServerSettingsResource } from './ServerSettingsResource.js';
|
||||
|
||||
@injectable(() => [ServerSettingsResource])
|
||||
export class ServerDefaultSettingsService extends SettingsSource {
|
||||
private readonly settings: Map<string, any>;
|
||||
|
||||
constructor(private readonly serverSettingsResource: ServerSettingsResource) {
|
||||
super();
|
||||
this.settings = new Map();
|
||||
|
||||
this.serverSettingsResource.onDataUpdate.addHandler(this.refreshDefaults.bind(this));
|
||||
|
||||
makeObservable<this, 'settings' | 'refreshDefaults'>(this, {
|
||||
refreshDefaults: action,
|
||||
settings: observable.shallow,
|
||||
});
|
||||
}
|
||||
|
||||
override has(key: string): boolean {
|
||||
return this.settings.has(key);
|
||||
}
|
||||
|
||||
getValue(key: string): any {
|
||||
return this.settings.get(key);
|
||||
}
|
||||
|
||||
protected getSnapshot(): any {
|
||||
return Object.fromEntries(this.settings);
|
||||
}
|
||||
|
||||
private refreshDefaults() {
|
||||
this.update(() => {
|
||||
this.settings.clear();
|
||||
|
||||
const settings = this.serverSettingsResource.data?.settings || [];
|
||||
|
||||
for (const property of settings) {
|
||||
const key = property.id;
|
||||
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const defaultValue = getObjectPropertyDefaultValue(property);
|
||||
|
||||
if (isNotNullDefined(defaultValue)) {
|
||||
this.settings.set(key, defaultValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { action, makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { PRODUCT_SETTINGS_LAYER } from '@cloudbeaver/core-product';
|
||||
import { createSettingsLayer, EditableSettingsSource } from '@cloudbeaver/core-settings';
|
||||
import { createSettingsLayer, SettingsSource } from '@cloudbeaver/core-settings';
|
||||
|
||||
import { EAdminPermission } from '../EAdminPermission.js';
|
||||
import { ServerConfigResource } from '../ServerConfigResource.js';
|
||||
@@ -18,7 +18,7 @@ import { SessionPermissionsResource } from '../SessionPermissionsResource.js';
|
||||
export const SERVER_SETTINGS_LAYER = createSettingsLayer(PRODUCT_SETTINGS_LAYER, 'server');
|
||||
|
||||
@injectable(() => [ServerConfigResource, SessionPermissionsResource])
|
||||
export class ServerSettingsService extends EditableSettingsSource {
|
||||
export class ServerSettingsService extends SettingsSource {
|
||||
private readonly settings: Map<string, any>;
|
||||
private lastConfig: any;
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import { DefaultNavigatorSettingsResource } from './DefaultNavigatorSettingsReso
|
||||
import { DataSynchronizationService } from './DataSynchronization/DataSynchronizationService.js';
|
||||
import { AsyncTaskInfoService } from './AsyncTask/AsyncTaskInfoService.js';
|
||||
import { AsyncTaskInfoEventHandler } from './AsyncTask/AsyncTaskInfoEventHandler.js';
|
||||
import { ServerDefaultSettingsService } from './Settings/ServerDefaultSettingsService.js';
|
||||
|
||||
export default ModuleRegistry.add({
|
||||
name: '@cloudbeaver/core-root',
|
||||
@@ -66,7 +65,6 @@ export default ModuleRegistry.add({
|
||||
.addSingleton(Dependency, proxy(ServerSettingsResource))
|
||||
.addSingleton(AsyncTaskInfoEventHandler)
|
||||
.addSingleton(ServerSettingsService)
|
||||
.addSingleton(ServerDefaultSettingsService)
|
||||
.addSingleton(WorkspaceConfigEventHandler)
|
||||
.addSingleton(WindowEventsService)
|
||||
.addSingleton(ServerSettingsResource)
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
{
|
||||
"path": "../../common-react/@dbeaver/react-tests"
|
||||
},
|
||||
{
|
||||
"path": "../../common-typescript/@dbeaver/js-helpers"
|
||||
},
|
||||
{
|
||||
"path": "../core-cli"
|
||||
},
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
query getAuthProviderConfigurations($providerId: ID) {
|
||||
configurations: listAuthProviderConfigurations(providerId: $providerId) {
|
||||
providerId
|
||||
id
|
||||
displayName
|
||||
disabled
|
||||
iconURL
|
||||
description
|
||||
parameters
|
||||
signInLink
|
||||
signOutLink
|
||||
redirectLink
|
||||
metadataLink
|
||||
acsLink
|
||||
entityIdLink
|
||||
...AdminAuthProviderConfigurationInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,6 @@ query saveAuthProviderConfiguration(
|
||||
description: $description
|
||||
parameters: $parameters
|
||||
) {
|
||||
providerId
|
||||
id
|
||||
displayName
|
||||
disabled
|
||||
iconURL
|
||||
description
|
||||
parameters
|
||||
signInLink
|
||||
signOutLink
|
||||
redirectLink
|
||||
metadataLink
|
||||
acsLink
|
||||
entityIdLink
|
||||
...AdminAuthProviderConfigurationInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
fragment AdminAuthProviderConfigurationInfo on AdminAuthProviderConfiguration {
|
||||
providerId
|
||||
id
|
||||
displayName
|
||||
disabled
|
||||
isDefault
|
||||
iconURL
|
||||
description
|
||||
parameters
|
||||
signInLink
|
||||
signOutLink
|
||||
redirectLink
|
||||
metadataLink
|
||||
acsLink
|
||||
entityIdLink
|
||||
}
|
||||
@@ -26,7 +26,6 @@
|
||||
"@cloudbeaver/core-di": "workspace:*",
|
||||
"@cloudbeaver/core-localization": "workspace:*",
|
||||
"@cloudbeaver/core-root": "workspace:*",
|
||||
"@cloudbeaver/core-settings": "workspace:*",
|
||||
"@cloudbeaver/core-settings-localization": "workspace:*",
|
||||
"tslib": "^2"
|
||||
},
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { SessionResource } from '@cloudbeaver/core-root';
|
||||
import { SettingsLocalizationService, type ILocalizationSettings } from '@cloudbeaver/core-settings-localization';
|
||||
import type { ISettingChangeData } from '@cloudbeaver/core-settings';
|
||||
|
||||
@injectable(() => [SessionResource, LocalizationService, SettingsLocalizationService])
|
||||
export class SessionLocalizationBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly sessionResource: SessionResource,
|
||||
private readonly localizationService: LocalizationService,
|
||||
private readonly settingsLocalizationService: SettingsLocalizationService,
|
||||
) {
|
||||
super();
|
||||
this.syncSessionLanguage = this.syncSessionLanguage.bind(this);
|
||||
this.syncSessionLanguageWithSettings = this.syncSessionLanguageWithSettings.bind(this);
|
||||
}
|
||||
|
||||
override register(): void | Promise<void> {
|
||||
this.sessionResource.onDataUpdate.addHandler(this.syncSessionLanguage.bind(this));
|
||||
this.settingsLocalizationService.settingsProvider.onChange.addHandler(this.syncSessionLanguageWithSettings);
|
||||
}
|
||||
|
||||
protected override dispose(): Promise<void> | void {
|
||||
this.sessionResource.onDataUpdate.removeHandler(this.syncSessionLanguage.bind(this));
|
||||
this.settingsLocalizationService.settingsProvider.onChange.removeHandler(this.syncSessionLanguageWithSettings);
|
||||
}
|
||||
|
||||
private syncSessionLanguageWithSettings(data: ISettingChangeData<keyof ILocalizationSettings>) {
|
||||
if (data.key === 'core.localization.language') {
|
||||
this.sessionResource.changeLanguage(data.value).catch(exception => {
|
||||
console.error(exception);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private syncSessionLanguage() {
|
||||
const session = this.sessionResource.data;
|
||||
|
||||
if (session && session.locale !== this.localizationService.currentLanguage) {
|
||||
this.sessionResource.changeLanguage(this.localizationService.currentLanguage).catch(exception => {
|
||||
console.error(exception);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||