mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-3303-edit-driver-settings (#1607)
* CB-3305 api for driver editing * CB-3305 gql api for driver provider list * CB-3303 feat: file uploading API * CB-3303 feat: support gql request in file upload API * CB-3303 code style fix * CB-3376 file uploading mechanism * CB-3303 feat: gql sdk extension API * CB-3303 feat: gql sdk extensions API rename * CB-3303 add driver mutations, update drivers resource * CB-3303 move driver gql functions to another service * CB-3305 web driver config fix * CB-3305 driver config use same fields as in driver info * CB-3437 add driver service to the codegen.yml * CB-3437 add driver providers * CB-3376 delete used driver library after restart server * CB-3376 driver library add fix * CB-3306 include driver libraries * CB-3303 return empty driver properties array if there is the error * CB-3303 fix conflicts * CB-3437 reuse defaults * CB-3303 feat: add sub-secondary color * CB-3303 fix: use sub-secondary color in tables * CB-3305 delete driver libraries fix * CB-3376 driver libraries save delete fix * CB-3437 naming * CB-3376 do not delete jar library file from the server * CB-3376 create file controller fix * CB-3305 move drivers api to ee * CB-3305 move drivers api to ee * CB-3376 file controller use same path * CB-3305 move driver provider list to ee * CB-3303 move mutations to the EE --------- Co-authored-by: Aleksey Potsetsuev <wrouds@gmail.com> Co-authored-by: Tatiana Fokina <tatiana.fokina@dbeaver.com> Co-authored-by: naumov <iamemptyhuh@gmail.com> Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com> Co-authored-by: Alexander Skoblikov <aleksandr.skoblikov@dbeaver.com>
This commit is contained in:
co-authored by
Aleksey Potsetsuev
Tatiana Fokina
naumov
dariamarutkina
Alexander Skoblikov
parent
9d83c67ae8
commit
a219593e1d
+7
@@ -28,6 +28,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.app.DBPPlatform;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
@@ -73,6 +74,12 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
|
||||
throw new IllegalStateException("Resource controller is not supported by " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DBFileController createFileController(@NotNull SMCredentialsProvider credentialsProvider) {
|
||||
throw new IllegalStateException("File controller is not supported by " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Path getDefaultWorkingFolder() {
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.cloudbeaver.WebProjectImpl;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.app.DBPApplication;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
@@ -63,6 +64,8 @@ public interface WebApplication extends DBPApplication {
|
||||
RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider,
|
||||
@NotNull DBPWorkspace workspace) throws DBException;
|
||||
|
||||
DBFileController createFileController(@NotNull SMCredentialsProvider credentialsProvider);
|
||||
|
||||
String getServerURL();
|
||||
|
||||
default String getServicesURI() {
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
|
||||
import org.jkiss.dbeaver.model.access.DBACredentialsProvider;
|
||||
@@ -225,6 +226,10 @@ public class WebSession extends BaseWebSession
|
||||
return userContext.getRmController();
|
||||
}
|
||||
|
||||
public synchronized DBFileController getFileController() {
|
||||
return userContext.getFileController();
|
||||
}
|
||||
|
||||
public synchronized void refreshUserData() {
|
||||
refreshSessionAuth();
|
||||
|
||||
|
||||
+8
@@ -23,6 +23,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
|
||||
import org.jkiss.dbeaver.model.auth.SMAuthStatus;
|
||||
@@ -63,6 +64,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
private SMAdminController adminSecurityController;
|
||||
private DBSSecretController secretController;
|
||||
private RMController rmController;
|
||||
private DBFileController fileController;
|
||||
private Set<String> accessibleProjectIds = new HashSet<>();
|
||||
|
||||
public WebUserContext(WebApplication application, DBPWorkspace workspace) throws DBException {
|
||||
@@ -70,6 +72,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
this.workspace = workspace;
|
||||
this.securityController = application.createSecurityController(this);
|
||||
this.rmController = application.createResourceController(this, workspace);
|
||||
this.fileController = application.createFileController(this);
|
||||
setUserPermissions(getDefaultPermissions());
|
||||
}
|
||||
|
||||
@@ -111,6 +114,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
this.adminSecurityController = application.getAdminSecurityController(this);
|
||||
this.secretController = application.getSecretController(this);
|
||||
this.rmController = application.createResourceController(this, workspace);
|
||||
this.fileController = application.createFileController(this);
|
||||
if (isSessionChanged) {
|
||||
this.smSessionId = smAuthPermissions.getSessionId();
|
||||
setUser(smAuthPermissions.getUserId() == null ? null : new WebUser(securityController.getCurrentUser()));
|
||||
@@ -226,6 +230,10 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
return rmController;
|
||||
}
|
||||
|
||||
public DBFileController getFileController() {
|
||||
return fileController;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<String> getAccessibleProjectIds() {
|
||||
return accessibleProjectIds;
|
||||
|
||||
@@ -235,6 +235,14 @@ type DriverInfo {
|
||||
applicableNetworkHandlers: [ID]!
|
||||
|
||||
configurationTypes: [DriverConfigurationType]!
|
||||
|
||||
driverLibraries: [DriverLibraryInfo!]!
|
||||
}
|
||||
|
||||
type DriverLibraryInfo {
|
||||
id: ID!
|
||||
name: String!
|
||||
icon: String!
|
||||
}
|
||||
|
||||
enum ResultDataFormat {
|
||||
@@ -574,6 +582,5 @@ extend type Mutation {
|
||||
asyncTaskCancel(id: String!): Boolean
|
||||
|
||||
asyncTaskInfo(id: String!, removeOnFinish: Boolean!): AsyncTaskInfo!
|
||||
|
||||
}
|
||||
|
||||
|
||||
+14
-7
@@ -21,11 +21,9 @@ import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.ConfigurationUtils;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
import org.jkiss.dbeaver.model.connection.DBPAuthModelDescriptor;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
|
||||
import org.jkiss.dbeaver.model.connection.*;
|
||||
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
@@ -41,15 +39,16 @@ import java.util.Map;
|
||||
/**
|
||||
* Web driver configuration
|
||||
*/
|
||||
public class WebDatabaseDriverConfig {
|
||||
public class WebDatabaseDriverInfo {
|
||||
|
||||
private static final Log log = Log.getLog(WebDatabaseDriverInfo.class);
|
||||
public static final String URL_SERVER_FIELD = "{server}";
|
||||
public static final String URL_DATABASE_FIELD = ".*(?:\\{(?:database|file|folder)}).*";
|
||||
private final WebSession webSession;
|
||||
private final DBPDriver driver;
|
||||
private String id;
|
||||
|
||||
public WebDatabaseDriverConfig(WebSession webSession, DBPDriver driver) {
|
||||
public WebDatabaseDriverInfo(WebSession webSession, DBPDriver driver) {
|
||||
this.webSession = webSession;
|
||||
this.driver = driver;
|
||||
this.id = driver.getFullId();
|
||||
@@ -191,7 +190,8 @@ public class WebDatabaseDriverConfig {
|
||||
return Arrays.stream(properties)
|
||||
.map(p -> new WebPropertyInfo(webSession, p, propertySource)).toArray(WebPropertyInfo[]::new);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error reading driver properties", e);
|
||||
log.error("Error reading driver properties", e);
|
||||
return new WebPropertyInfo[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,4 +259,11 @@ public class WebDatabaseDriverConfig {
|
||||
}
|
||||
return driver.getSampleURL().matches(URL_DATABASE_FIELD);
|
||||
}
|
||||
|
||||
@Property
|
||||
public WebDriverLibraryInfo[] getDriverLibraries() {
|
||||
return driver.getDriverLibraries().stream()
|
||||
.map(dbpDriverLibrary -> new WebDriverLibraryInfo(webSession, dbpDriverLibrary))
|
||||
.toArray(WebDriverLibraryInfo[]::new);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverLibrary;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
public class WebDriverLibraryInfo {
|
||||
|
||||
private final WebSession webSession;
|
||||
private final DBPDriverLibrary driverLibrary;
|
||||
|
||||
public WebDriverLibraryInfo(@NotNull WebSession webSession, @NotNull DBPDriverLibrary driverLibrary) {
|
||||
this.webSession = webSession;
|
||||
this.driverLibrary = driverLibrary;
|
||||
}
|
||||
|
||||
|
||||
@Property
|
||||
public String getId() {
|
||||
return driverLibrary.getId();
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getName() {
|
||||
return driverLibrary.getDisplayName();
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getIcon() {
|
||||
return WebServiceUtils.makeIconId(driverLibrary.getIcon());
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model.user;
|
||||
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDataSourceProviderDescriptor;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
public class WebDataSourceProviderInfo {
|
||||
|
||||
@NotNull
|
||||
private final DBPDataSourceProviderDescriptor providerDescriptor;
|
||||
|
||||
public WebDataSourceProviderInfo(@NotNull DBPDataSourceProviderDescriptor descriptor) {
|
||||
this.providerDescriptor = descriptor;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getId() {
|
||||
return providerDescriptor.getId();
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getName() {
|
||||
return providerDescriptor.getName();
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getIcon() {
|
||||
return WebServiceUtils.makeIconId(providerDescriptor.getIcon());
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,15 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
import org.jkiss.dbeaver.model.rm.RMController;
|
||||
import org.jkiss.dbeaver.model.security.SMAdminController;
|
||||
import org.jkiss.dbeaver.model.security.SMController;
|
||||
import org.jkiss.dbeaver.registry.BasePlatformImpl;
|
||||
import org.jkiss.dbeaver.registry.LocalFileController;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -70,6 +74,12 @@ public class CBApplicationCE extends CBApplication {
|
||||
return LocalResourceController.builder(credentialsProvider, workspace, this::getSecurityController).build();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DBFileController createFileController(@NotNull SMCredentialsProvider credentialsProvider) {
|
||||
return new LocalFileController(DBWorkbench.getPlatform().getWorkspace().getAbsolutePath().resolve(DBFileController.DATA_FOLDER));
|
||||
}
|
||||
|
||||
protected void shutdown() {
|
||||
try {
|
||||
if (securityController instanceof CBEmbeddedSecurityController) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package io.cloudbeaver.server;
|
||||
|
||||
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
|
||||
import io.cloudbeaver.server.jobs.SessionStateJob;
|
||||
import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
|
||||
import io.cloudbeaver.service.session.WebSessionManager;
|
||||
@@ -27,6 +28,7 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBFileController;
|
||||
import org.jkiss.dbeaver.model.app.DBACertificateStorage;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDataSourceProviderDescriptor;
|
||||
@@ -41,6 +43,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
|
||||
import org.jkiss.dbeaver.registry.BasePlatformImpl;
|
||||
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.runtime.SecurityProviderUtils;
|
||||
import org.jkiss.dbeaver.runtime.qm.QMLogFileWriter;
|
||||
import org.jkiss.dbeaver.runtime.qm.QMRegistryImpl;
|
||||
@@ -289,7 +292,7 @@ public class CBPlatform extends BasePlatformImpl {
|
||||
}
|
||||
boolean hasAllFiles = true, hasJars = false;
|
||||
for (DBPDriverLibrary lib : libraries) {
|
||||
if (!lib.isOptional() && lib.getType() != DBPDriverLibrary.FileType.license &&
|
||||
if (!DBWorkbench.isDistributed() && !lib.isOptional() && lib.getType() != DBPDriverLibrary.FileType.license &&
|
||||
(lib.getLocalFile() == null || !Files.exists(lib.getLocalFile())))
|
||||
{
|
||||
hasAllFiles = false;
|
||||
@@ -309,4 +312,9 @@ public class CBPlatform extends BasePlatformImpl {
|
||||
log.info("Available drivers: " + applicableDrivers.stream().map(DBPDriver::getFullName).collect(Collectors.joining(",")));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DBFileController createFileController() {
|
||||
return getApplication().createFileController(new NoAuthCredentialsProvider());
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -16,12 +16,10 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.core;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebAction;
|
||||
import io.cloudbeaver.WebObjectId;
|
||||
import io.cloudbeaver.WebProjectAction;
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.model.*;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.user.WebDataSourceProviderInfo;
|
||||
import io.cloudbeaver.service.DBWService;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -30,6 +28,8 @@ import org.jkiss.dbeaver.model.rm.RMConstants;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.Part;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -42,7 +42,7 @@ public interface DBWServiceCore extends DBWService {
|
||||
WebServerConfig getServerConfig() throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
List<WebDatabaseDriverConfig> getDriverList(@NotNull WebSession webSession, String driverId) throws DBWebException;
|
||||
List<WebDatabaseDriverInfo> getDriverList(@NotNull WebSession webSession, String driverId) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
List<WebDatabaseAuthModel> getAuthModels(@NotNull WebSession webSession);
|
||||
|
||||
-1
@@ -184,5 +184,4 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
|
||||
private WebConnectionConfig getConnectionConfig(DataFetchingEnvironment env) {
|
||||
return new WebConnectionConfig(env.getArgument("config"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -23,6 +23,7 @@ import io.cloudbeaver.WebProjectImpl;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.*;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.user.WebDataSourceProviderInfo;
|
||||
import io.cloudbeaver.registry.WebHandlerRegistry;
|
||||
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
@@ -81,11 +82,11 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebDatabaseDriverConfig> getDriverList(@NotNull WebSession webSession, String driverId) {
|
||||
List<WebDatabaseDriverConfig> result = new ArrayList<>();
|
||||
public List<WebDatabaseDriverInfo> getDriverList(@NotNull WebSession webSession, String driverId) {
|
||||
List<WebDatabaseDriverInfo> result = new ArrayList<>();
|
||||
for (DBPDriver driver : CBPlatform.getInstance().getApplicableDrivers()) {
|
||||
if (driverId == null || driverId.equals(driver.getFullId())) {
|
||||
result.add(new WebDatabaseDriverConfig(webSession, driver));
|
||||
result.add(new WebDatabaseDriverInfo(webSession, driver));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -10,16 +10,17 @@ import { createContext } from 'react';
|
||||
|
||||
import type { IExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
type ChangeHandler = (value: string | number | boolean | null | undefined, name: string | undefined) => void;
|
||||
export type FormChangeValues = string | number | boolean | FileList | null | undefined;
|
||||
export type FormChangeHandler = (value: FormChangeValues, name: string | undefined) => void;
|
||||
|
||||
export interface IChangeData {
|
||||
value: string | number | boolean | null | undefined;
|
||||
value: FormChangeValues;
|
||||
name: string | undefined;
|
||||
}
|
||||
|
||||
export interface IFormContext {
|
||||
changeExecutor: IExecutor<IChangeData>;
|
||||
change: ChangeHandler;
|
||||
change: FormChangeHandler;
|
||||
}
|
||||
|
||||
export const FormContext = createContext<IFormContext | null>(null);
|
||||
|
||||
+4
-2
@@ -67,9 +67,11 @@ interface Props<TState> extends ILayoutSizeProps {
|
||||
mapValue?: (value: string) => string;
|
||||
}
|
||||
|
||||
type InputFileType = <TState extends Record<string, any>>(props: Props<TState>) => React.ReactElement<any, any>;
|
||||
type InputFileTextContentType = <TState extends Record<string, any>>(
|
||||
props: Props<TState>
|
||||
) => React.ReactElement<any, any>;
|
||||
|
||||
export const InputFile: InputFileType = observer(function InputFile({
|
||||
export const InputFileTextContent: InputFileTextContentType = observer(function InputFileTextContent({
|
||||
name,
|
||||
state,
|
||||
accept,
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useContext, useEffect, useState } from 'react';
|
||||
import styled, { use, css } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { Button } from '../Button';
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import { useTranslate } from '../localization/useTranslate';
|
||||
import { Tag } from '../Tags/Tag';
|
||||
import { Tags } from '../Tags/Tags';
|
||||
import { UploadArea } from '../UploadArea';
|
||||
import { useCombinedHandler } from '../useCombinedHandler';
|
||||
import { useRefInherit } from '../useRefInherit';
|
||||
import { useStateDelay } from '../useStateDelay';
|
||||
import { useStyles } from '../useStyles';
|
||||
import { baseFormControlStyles, baseInvalidFormControlStyles, baseValidFormControlStyles } from './baseFormControlStyles';
|
||||
import { FormContext } from './FormContext';
|
||||
import { isControlPresented } from './isControlPresented';
|
||||
|
||||
const INPUT_FIELD_STYLES = css`
|
||||
field-label {
|
||||
display: block;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
}
|
||||
field-label:not(:empty) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
input-container {
|
||||
position: relative;
|
||||
}
|
||||
Tags {
|
||||
padding-top: 8px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'name' | 'value' | 'style'> & ILayoutSizeProps & {
|
||||
error?: boolean;
|
||||
loading?: boolean;
|
||||
description?: string;
|
||||
labelTooltip?: string;
|
||||
hideTags?: boolean;
|
||||
mod?: 'surface';
|
||||
ref?: React.Ref<HTMLInputElement>;
|
||||
style?: ComponentStyle;
|
||||
};
|
||||
|
||||
type ControlledProps = BaseProps & {
|
||||
name?: string;
|
||||
value?: FileList | null;
|
||||
onChange?: (value: FileList | null, name?: string) => any;
|
||||
state?: never;
|
||||
autoHide?: never;
|
||||
};
|
||||
|
||||
type ObjectProps<TKey extends keyof TState, TState> = BaseProps & {
|
||||
name: TKey;
|
||||
state: TState;
|
||||
onChange?: (value: TState[TKey], name: TKey) => any;
|
||||
autoHide?: boolean;
|
||||
value?: never;
|
||||
};
|
||||
|
||||
interface InputFilesType {
|
||||
(props: ControlledProps): React.ReactElement<any, any> | null;
|
||||
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): React.ReactElement<any, any> | null;
|
||||
}
|
||||
|
||||
export const InputFiles: InputFilesType = observer(forwardRef(function InputFiles({
|
||||
name,
|
||||
style,
|
||||
value: valueControlled,
|
||||
required,
|
||||
state,
|
||||
children,
|
||||
className,
|
||||
error,
|
||||
loading,
|
||||
description,
|
||||
labelTooltip,
|
||||
hideTags,
|
||||
mod,
|
||||
fill,
|
||||
small,
|
||||
medium,
|
||||
large,
|
||||
tiny,
|
||||
autoHide,
|
||||
onChange,
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>, refInherit: React.Ref<HTMLInputElement>) {
|
||||
const ref = useRefInherit<HTMLInputElement>(refInherit);
|
||||
const [innerState, setInnerState] = useState<FileList | null>(null);
|
||||
const translate = useTranslate();
|
||||
const styles = useStyles(
|
||||
baseFormControlStyles,
|
||||
error ? baseInvalidFormControlStyles : baseValidFormControlStyles,
|
||||
INPUT_FIELD_STYLES,
|
||||
style
|
||||
);
|
||||
const context = useContext(FormContext);
|
||||
loading = useStateDelay(loading ?? false, 300);
|
||||
|
||||
let value = valueControlled ?? innerState;
|
||||
|
||||
if (state && name !== undefined && name in state) {
|
||||
value = state[name];
|
||||
}
|
||||
|
||||
function setValue(value: FileList | null) {
|
||||
setInnerState(value);
|
||||
if (state) {
|
||||
state[name] = value;
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(value, name);
|
||||
}
|
||||
if (context) {
|
||||
context.change(value, name);
|
||||
}
|
||||
}
|
||||
|
||||
const removeFile = useCombinedHandler(function removeFile(index: number): void {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dt = new DataTransfer();
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const file = value[i];
|
||||
if (index !== i) {
|
||||
dt.items.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
setValue(dt.files.length === 0 ? null : dt.files);
|
||||
});
|
||||
|
||||
const handleChange = useCombinedHandler((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(event.target.files);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== innerState) {
|
||||
setInnerState(value);
|
||||
}
|
||||
});
|
||||
|
||||
if (autoHide && !isControlPresented(name, state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const files = Array.from(value ?? []);
|
||||
|
||||
return styled(styles)(
|
||||
<field className={className} {...use({ small, medium, large, tiny })}>
|
||||
<field-label title={labelTooltip || rest.title}>{children}{required && ' *'}</field-label>
|
||||
<input-container>
|
||||
<UploadArea
|
||||
ref={ref}
|
||||
{...rest}
|
||||
name={name}
|
||||
value={value}
|
||||
{...use({ mod })}
|
||||
required={required}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<Button
|
||||
icon='/icons/import.svg'
|
||||
tag='div'
|
||||
loading={loading}
|
||||
mod={['outlined']}
|
||||
>
|
||||
{translate(rest.multiple ? 'ui_upload_files' : 'ui_upload_file')}
|
||||
</Button>
|
||||
</UploadArea>
|
||||
{!hideTags && (
|
||||
<Tags>
|
||||
{files.map((file, i) => (
|
||||
<Tag key={file.name} id={i} label={file.name} onRemove={removeFile} />
|
||||
))}
|
||||
</Tags>
|
||||
)}
|
||||
</input-container>
|
||||
{description && (
|
||||
<field-description>
|
||||
{description}
|
||||
</field-description>
|
||||
)}
|
||||
</field>
|
||||
);
|
||||
}));
|
||||
@@ -11,7 +11,7 @@ import React, { forwardRef, useContext, useState } from 'react';
|
||||
import { Executor } from '@cloudbeaver/core-executor';
|
||||
|
||||
import { useObjectRef } from '../useObjectRef';
|
||||
import { FormContext, IChangeData, IFormContext } from './FormContext';
|
||||
import { FormChangeHandler, FormContext, IChangeData, IFormContext } from './FormContext';
|
||||
|
||||
export interface IFormStateInfo {
|
||||
edited: boolean;
|
||||
@@ -24,7 +24,7 @@ type FormDetailedProps = Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<H
|
||||
disabled?: boolean;
|
||||
disableEnterSubmit?: boolean;
|
||||
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => Promise<void> | void;
|
||||
onChange?: (value: string | number | boolean | null | undefined, name: string | undefined) => void;
|
||||
onChange?: FormChangeHandler;
|
||||
};
|
||||
|
||||
export const SubmittingForm = forwardRef<HTMLFormElement, FormDetailedProps>(function SubmittingForm(
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ import { FieldCheckbox } from '../../FormControls/Checkboxes/FieldCheckbox';
|
||||
import { Combobox } from '../../FormControls/Combobox';
|
||||
import { FormFieldDescription } from '../../FormControls/FormFieldDescription';
|
||||
import { InputField } from '../../FormControls/InputField';
|
||||
import { InputFile } from '../../FormControls/InputFile';
|
||||
import { InputFileTextContent } from '../../FormControls/InputFileTextContent';
|
||||
import { isControlPresented } from '../../FormControls/isControlPresented';
|
||||
import { Textarea } from '../../FormControls/Textarea';
|
||||
import { Link } from '../../Link';
|
||||
@@ -94,7 +94,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
|
||||
if (controltype === 'file' && state) {
|
||||
return (
|
||||
<InputFile
|
||||
<InputFileTextContent
|
||||
tooltip={property.description}
|
||||
labelTooltip={property.displayName || property.description}
|
||||
name={property.id!}
|
||||
@@ -105,7 +105,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
mapValue={removeMetadataFromBase64}
|
||||
>
|
||||
{property.displayName}
|
||||
</InputFile>
|
||||
</InputFileTextContent>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ tbody > tr {
|
||||
&:not([|noHover]):hover,
|
||||
&:not([|noHover])[|selected],
|
||||
&:not([|noHover])[|expanded] {
|
||||
background-color: var(--theme-secondary);
|
||||
background-color: var(--theme-sub-secondary);
|
||||
}
|
||||
|
||||
&[|disabled] {
|
||||
|
||||
@@ -11,8 +11,8 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import { IconOrImage } from '../IconOrImage';
|
||||
|
||||
export interface ITag {
|
||||
id: string;
|
||||
export interface ITag<T extends string | number = string> {
|
||||
id: T;
|
||||
label: string;
|
||||
icon?: string;
|
||||
}
|
||||
@@ -53,8 +53,10 @@ const style = css`
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
IconOrImage {
|
||||
@@ -63,12 +65,18 @@ const style = css`
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props extends ITag {
|
||||
onRemove: (id: string) => void;
|
||||
interface Props<T extends string | number> extends ITag<T> {
|
||||
onRemove: (id: T) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Tag = observer<Props>(function Tag({ id, label, icon, onRemove, className }) {
|
||||
export const Tag = observer(function Tag<T extends string | number>({
|
||||
id,
|
||||
label,
|
||||
icon,
|
||||
onRemove,
|
||||
className,
|
||||
}: Props<T>) {
|
||||
return styled(style)(
|
||||
<tag-container as='li' title={label} className={className}>
|
||||
{icon && (
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { forwardRef, useLayoutEffect } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
|
||||
import { useRefInherit } from './useRefInherit';
|
||||
|
||||
interface Props extends Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, 'value'> {
|
||||
value?: FileList | null;
|
||||
className?: string;
|
||||
/** Reset prev file path. This ensures that the onChange event will be triggered for the same file as well */
|
||||
reset?: boolean;
|
||||
@@ -19,6 +23,7 @@ interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLIn
|
||||
const styles = css`
|
||||
label {
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
|
||||
&[|disabled] {
|
||||
cursor: default;
|
||||
@@ -26,8 +31,15 @@ const styles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const UploadArea: React.FC<Props> = function UploadArea({ id, reset, className, children, ...rest }) {
|
||||
const _id = id ?? uuid();
|
||||
export const UploadArea = forwardRef<HTMLInputElement, Props>(function UploadArea({
|
||||
id = uuid(),
|
||||
value,
|
||||
reset,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}, refInherit) {
|
||||
const ref = useRefInherit<HTMLInputElement>(refInherit);
|
||||
|
||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (rest.onChange) {
|
||||
@@ -40,12 +52,18 @@ export const UploadArea: React.FC<Props> = function UploadArea({ id, reset, clas
|
||||
}
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current && value !== undefined) {
|
||||
ref.current.files = value;
|
||||
}
|
||||
});
|
||||
|
||||
return styled(styles)(
|
||||
<>
|
||||
<input {...rest} type='file' id={_id} hidden onChange={handleChange} />
|
||||
<label htmlFor={_id} {...use({ disabled: rest.disabled })} className={className} title={rest.title}>
|
||||
<input ref={ref} {...rest} type='file' id={id} hidden onChange={handleChange} />
|
||||
<label htmlFor={id} {...use({ disabled: rest.disabled })} className={className} title={rest.title}>
|
||||
{children}
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -137,7 +137,8 @@ export * from './FormControls/FormContext';
|
||||
export * from './FormControls/FormFieldDescription';
|
||||
export * from './FormControls/FormGroup';
|
||||
export * from './FormControls/InputField';
|
||||
export * from './FormControls/InputFile';
|
||||
export * from './FormControls/InputFiles';
|
||||
export * from './FormControls/InputFileTextContent';
|
||||
export * from './FormControls/Radio';
|
||||
export * from './FormControls/RadioGroup';
|
||||
export * from './FormControls/RadioGroupContext';
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { useObjectRef } from './useObjectRef';
|
||||
|
||||
interface IPrivateRef<T> {
|
||||
value: T | null;
|
||||
current: T | null;
|
||||
}
|
||||
|
||||
export function useRefInherit<T>(refInherit?: React.Ref<T>): React.RefObject<T> {
|
||||
return useObjectRef<IPrivateRef<T>>(() => ({
|
||||
value: null,
|
||||
get current(): T | null {
|
||||
return this.value;
|
||||
},
|
||||
set current(value: T | null) {
|
||||
this.value = value;
|
||||
if (typeof refInherit === 'function') {
|
||||
refInherit(value);
|
||||
} else if (refInherit && 'current' in refInherit) {
|
||||
//@ts-expect-error We know it, we want it
|
||||
refInherit.current = value;
|
||||
}
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -11,11 +11,16 @@ import { computed, makeObservable } from 'mobx';
|
||||
import { AppAuthService } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { GraphQLService, CachedMapResource, ResourceKey, ResourceKeyUtils, DatabaseDriverFragment, DriverListQueryVariables, CachedMapAllKey, resourceKeyList, isResourceKeyAlias, isResourceAlias } from '@cloudbeaver/core-sdk';
|
||||
import { GraphQLService, CachedMapResource, ResourceKey, ResourceKeyUtils, DatabaseDriverFragment, DriverListQueryVariables, CachedMapAllKey, resourceKeyList, isResourceAlias } from '@cloudbeaver/core-sdk';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
export type DBDriver = DatabaseDriverFragment;
|
||||
|
||||
export const NEW_DRIVER_SYMBOL = Symbol('new-driver');
|
||||
|
||||
export type NewDBDriver = DBDriver & { [NEW_DRIVER_SYMBOL]: boolean; timestamp: number };
|
||||
type DriverResourceIncludes = Omit<DriverListQueryVariables, 'driverId'>;
|
||||
|
||||
@injectable()
|
||||
export class DBDriverResource extends CachedMapResource<string, DBDriver, DriverListQueryVariables> {
|
||||
get enabledDrivers() {
|
||||
@@ -31,7 +36,7 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
) {
|
||||
super();
|
||||
appAuthService.requireAuthentication(this);
|
||||
this.sync(this.serverConfigResource, () => {}, () => CachedMapAllKey);
|
||||
this.sync(this.serverConfigResource, () => { }, () => CachedMapAllKey);
|
||||
|
||||
makeObservable(this, {
|
||||
enabledDrivers: computed<DBDriver[]>({
|
||||
@@ -60,9 +65,7 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
|
||||
const { drivers } = await this.graphQLService.sdk.driverList({
|
||||
driverId,
|
||||
includeDriverParameters: false,
|
||||
includeDriverProperties: false,
|
||||
includeProviderProperties: false,
|
||||
...this.getDefaultIncludes(),
|
||||
...this.getIncludesMap(driverId, (all ? this.defaultIncludes : includes)),
|
||||
});
|
||||
|
||||
@@ -83,12 +86,58 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async addDriverLibraries(driverId: string, files: FileList) {
|
||||
await this.graphQLService.sdk.uploadDriverLibrary(driverId, files);
|
||||
await this.refresh(driverId);
|
||||
}
|
||||
|
||||
protected dataSet(key: string, value: DBDriver): void {
|
||||
const oldDriver = this.dataGet(key);
|
||||
this.data.set(key, { ...oldDriver, ...value });
|
||||
}
|
||||
|
||||
async refreshAll(): Promise<Map<string, DBDriver>> {
|
||||
this.resetIncludes();
|
||||
await this.refresh(CachedMapAllKey);
|
||||
return this.data;
|
||||
}
|
||||
|
||||
cleanNewFlags(): void {
|
||||
for (const driver of this.data.values()) {
|
||||
(driver as NewDBDriver)[NEW_DRIVER_SYMBOL] = false;
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultIncludes(): DriverResourceIncludes {
|
||||
return {
|
||||
includeDriverLibraries: false,
|
||||
includeDriverParameters: false,
|
||||
includeDriverProperties: false,
|
||||
includeProviderProperties: false,
|
||||
};
|
||||
}
|
||||
|
||||
protected validateKey(key: string): boolean {
|
||||
return typeof key === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
function isNewDriver(driver: DBDriver | NewDBDriver): driver is NewDBDriver {
|
||||
return (driver as NewDBDriver)[NEW_DRIVER_SYMBOL];
|
||||
}
|
||||
|
||||
export function compareNewDrivers(a: DBDriver, b: DBDriver): number {
|
||||
if (isNewDriver(a) && isNewDriver(b)) {
|
||||
return b.timestamp - a.timestamp;
|
||||
}
|
||||
|
||||
if (isNewDriver(b)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (isNewDriver(a)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -36,4 +36,5 @@ export * from './NetworkHandlerResource';
|
||||
export * from './useConnectionInfo';
|
||||
export * from './useDBDriver';
|
||||
export * from './USER_NAME_PROPERTY_ID';
|
||||
|
||||
export { manifest as coreConnectionsManifest } from './manifest';
|
||||
|
||||
@@ -94,6 +94,7 @@ export default [
|
||||
['ui_no_file_chosen', 'No file chosen'],
|
||||
['ui_file_size_exceeds', 'Max file size exceeds. File size: {arg:size}. Max file size: {arg:maxSize}'],
|
||||
['ui_upload_file', 'Upload file'],
|
||||
['ui_upload_files', 'Upload files'],
|
||||
|
||||
['root_permission_denied', "You don't have permissions"],
|
||||
['root_permission_no_permission', "You don't have permission for this action"],
|
||||
|
||||
@@ -80,6 +80,7 @@ export default [
|
||||
['ui_no_file_chosen', 'No file chosen'],
|
||||
['ui_file_size_exceeds', 'Max file size exceeds. File size: {arg:size}. Max file size: {arg:maxSize}'],
|
||||
['ui_upload_file', 'Upload file'],
|
||||
['ui_upload_files', 'Upload files'],
|
||||
|
||||
['root_permission_denied', 'Non hai i permessi'],
|
||||
['app_root_session_expire_warning_title', 'La sessione sta per scadere'],
|
||||
|
||||
@@ -92,6 +92,7 @@ export default [
|
||||
['ui_no_file_chosen', 'Файл не выбран'],
|
||||
['ui_file_size_exceeds', 'Максимальный размер файла превышен. Размер файла: {arg:size}. Максимальный размер: {arg:maxSize}'],
|
||||
['ui_upload_file', 'Загрузить файл'],
|
||||
['ui_upload_files', 'Загрузить файлы'],
|
||||
|
||||
['root_permission_denied', 'Отказано в доступе'],
|
||||
['root_permission_no_permission', 'У вас нет разрешения на это действие'],
|
||||
|
||||
@@ -93,6 +93,7 @@ export default [
|
||||
['ui_no_file_chosen', 'No file chosen'],
|
||||
['ui_file_size_exceeds', 'Max file size exceeds. File size: {arg:size}. Max file size: {arg:maxSize}'],
|
||||
['ui_upload_file', 'Upload file'],
|
||||
['ui_upload_files', 'Upload files'],
|
||||
|
||||
['root_permission_denied', '您没有权限'],
|
||||
['root_permission_no_permission', '您没有权限执行此操作'],
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"graphql-request": "~5.1.0",
|
||||
"graphql": "~16.x.x"
|
||||
"graphql": "~16.x.x",
|
||||
"axios": "~1.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "~4.0.1",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import axios, { AxiosProgressEvent, AxiosResponse } from 'axios';
|
||||
import { GraphQLClient, ClientError, resolveRequestDocument } from 'graphql-request';
|
||||
import { parseRequestArgs } from 'graphql-request/dist/parseArgs';
|
||||
import type { RequestDocument, RequestOptions, Variables } from 'graphql-request/dist/types';
|
||||
@@ -15,6 +16,15 @@ import { GQLError } from './GQLError';
|
||||
import type { IResponseInterceptor } from './IResponseInterceptor';
|
||||
import { PlainGQLError } from './PlainGQLError';
|
||||
|
||||
export type UploadProgressEvent = AxiosProgressEvent;
|
||||
|
||||
type GqlResponse = (
|
||||
{ data: object; errors: undefined }[]
|
||||
| { data: object; errors: undefined }
|
||||
| { data: undefined; errors: object }
|
||||
| { data: undefined; errors: object[] }
|
||||
);
|
||||
|
||||
export class CustomGraphQLClient extends GraphQLClient {
|
||||
get blockReason(): Error | string | null {
|
||||
return this.requestsBlockedReason;
|
||||
@@ -24,6 +34,19 @@ export class CustomGraphQLClient extends GraphQLClient {
|
||||
private isRequestsBlocked = false;
|
||||
private requestsBlockedReason: Error | string | null = null;
|
||||
|
||||
async uploadFile<T = any, V extends Variables = Variables>(
|
||||
url: string,
|
||||
files: FileList,
|
||||
query?: string,
|
||||
variables?: V,
|
||||
onUploadProgress?: (event: UploadProgressEvent) => void
|
||||
): Promise<T> {
|
||||
return this.interceptors.reduce(
|
||||
(accumulator, interceptor) => interceptor(accumulator),
|
||||
this.overrideFileUpload<T, V>(url, files, query, variables, onUploadProgress)
|
||||
);
|
||||
}
|
||||
|
||||
registerInterceptor(interceptor: IResponseInterceptor): void {
|
||||
this.interceptors.push(interceptor);
|
||||
}
|
||||
@@ -87,6 +110,85 @@ export class CustomGraphQLClient extends GraphQLClient {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async overrideFileUpload<T, V extends Variables = Variables>(
|
||||
url: string,
|
||||
files: FileList,
|
||||
query?: string,
|
||||
variables?: V,
|
||||
onUploadProgress?: (event: UploadProgressEvent) => void
|
||||
): Promise<T> {
|
||||
this.blockRequestsReasonHandler();
|
||||
try {
|
||||
const { operationName } = resolveRequestDocument(query ?? '');
|
||||
// TODO: we don't support GQL response right now
|
||||
const response = await axios.postForm/*<GqlResponse>*/<T>(url, {
|
||||
operationName,
|
||||
query,
|
||||
variables: JSON.stringify(variables),
|
||||
'files[]': files,
|
||||
}, {
|
||||
onUploadProgress,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
// TODO: we don't support GQL response right now
|
||||
// TODO: seems here can be undefined
|
||||
// return this.parseGQLResponse(response, query ?? '', variables);
|
||||
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
if (isClientError(error)) {
|
||||
if (isObjectError(error)) {
|
||||
throw new GQLError(error);
|
||||
} else {
|
||||
throw new PlainGQLError(error);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private parseGQLResponse<T>(
|
||||
response: AxiosResponse<GqlResponse>,
|
||||
query: string,
|
||||
variables?: Variables,
|
||||
): T {
|
||||
const result = response.data;
|
||||
|
||||
const successfullyReceivedData = Array.isArray(result)
|
||||
? !result.some(({ data }) => !data)
|
||||
: Boolean(result.data);
|
||||
|
||||
const successfullyPassedErrorPolicy = Array.isArray(result)
|
||||
|| !result.errors
|
||||
|| (Array.isArray(result.errors) && !result.errors.length);
|
||||
|
||||
if (response.status === 200 && successfullyPassedErrorPolicy && successfullyReceivedData) {
|
||||
const data = result;
|
||||
const dataEnvelope = data;
|
||||
|
||||
// @ts-expect-error TODO
|
||||
return {
|
||||
...dataEnvelope,
|
||||
headers: response.headers,
|
||||
status: response.status,
|
||||
};
|
||||
} else {
|
||||
const errorResult
|
||||
= typeof result === 'string'
|
||||
? {
|
||||
error: result,
|
||||
}
|
||||
: result;
|
||||
throw new ClientError(
|
||||
// @ts-expect-error TODO
|
||||
{ ...errorResult, status: response.status, headers: response.headers },
|
||||
{ query, variables }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isClientError(obj: any): obj is ClientError {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { CustomGraphQLClient, UploadProgressEvent } from '../CustomGraphQLClient';
|
||||
|
||||
export interface IUploadDriverLibraryExtension {
|
||||
uploadDriverLibrary: (
|
||||
driverId: string,
|
||||
files: FileList,
|
||||
onUploadProgress?: (event: UploadProgressEvent) => void
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export function uploadDriverLibraryExtension(client: CustomGraphQLClient): IUploadDriverLibraryExtension {
|
||||
return {
|
||||
uploadDriverLibrary(
|
||||
driverId: string,
|
||||
files: FileList,
|
||||
onUploadProgress?: (event: UploadProgressEvent) => void
|
||||
): Promise<void> {
|
||||
|
||||
return client.uploadFile(
|
||||
GlobalConstants.absoluteServiceUrl('drivers', 'library'),
|
||||
files,
|
||||
undefined,
|
||||
{ driverId },
|
||||
onUploadProgress
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -10,19 +10,29 @@ import { injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
import { CustomGraphQLClient } from './CustomGraphQLClient';
|
||||
import { EnvironmentService } from './EnvironmentService';
|
||||
import { uploadDriverLibraryExtension } from './Extensions/uploadDriverLibraryExtension';
|
||||
import type { IResponseInterceptor } from './IResponseInterceptor';
|
||||
import { getSdk } from './sdk';
|
||||
|
||||
function extendedSDK(client: CustomGraphQLClient) {
|
||||
const sdk = getSdk(client);
|
||||
|
||||
return {
|
||||
...sdk,
|
||||
...uploadDriverLibraryExtension(client),
|
||||
};
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class GraphQLService {
|
||||
sdk: ReturnType<typeof getSdk>;
|
||||
sdk: ReturnType<typeof extendedSDK>;
|
||||
|
||||
readonly client: CustomGraphQLClient;
|
||||
|
||||
constructor(private readonly environmentService: EnvironmentService) {
|
||||
const gqlEndpoint = this.environmentService.gqlEndpoint;
|
||||
this.client = new CustomGraphQLClient(gqlEndpoint);
|
||||
this.sdk = getSdk(this.client as any);
|
||||
this.sdk = extendedSDK(this.client);
|
||||
}
|
||||
|
||||
registerInterceptor(interceptor: IResponseInterceptor): void {
|
||||
|
||||
@@ -367,7 +367,7 @@ export abstract class CachedResource<
|
||||
}
|
||||
|
||||
hasAlias(key: ResourceAlias<TKey, any>): boolean {
|
||||
return this.paramAliases.some(alias => alias.id === key.id);
|
||||
return this.paramAliases.some(alias => alias.id === key.id);
|
||||
}
|
||||
|
||||
isAlias<TOptions extends ResourceAliasOptions = any>(
|
||||
@@ -783,14 +783,15 @@ export abstract class CachedResource<
|
||||
return this.getKeyRef(key);
|
||||
}
|
||||
|
||||
protected transformToKey(param: ResourceKey<TKey>): TKey | ResourceKeyList<TKey> {
|
||||
//TODO must be protected
|
||||
transformToKey(param: ResourceKey<TKey>): TKey | ResourceKeyList<TKey> {
|
||||
let deep = 0;
|
||||
|
||||
while (deep < 10) {
|
||||
if (!this.validateResourceKey(param)) {
|
||||
let paramString = JSON.stringify(toJS(param));
|
||||
|
||||
if (isResourceKeyList(param)) {
|
||||
if (isResourceKeyList(param)) {
|
||||
paramString = param.toString();
|
||||
}
|
||||
console.warn(this.getActionPrefixedName(`wrong param "${paramString}"`));
|
||||
@@ -831,7 +832,7 @@ export abstract class CachedResource<
|
||||
if (!this.validateResourceKey(key)) {
|
||||
let paramString = JSON.stringify(toJS(key));
|
||||
|
||||
if (isResourceKeyList(key) || isResourceAlias(key)) {
|
||||
if (isResourceKeyList(key) || isResourceAlias(key)) {
|
||||
paramString = key.toString();
|
||||
}
|
||||
console.warn(this.getActionPrefixedName(`wrong param "${paramString}"`));
|
||||
@@ -946,18 +947,19 @@ export abstract class CachedResource<
|
||||
refresh: boolean
|
||||
): Promise<TData>;
|
||||
|
||||
protected async performUpdate<T>(
|
||||
//TODO must be protected
|
||||
async performUpdate<T>(
|
||||
key: ResourceKey<TKey>,
|
||||
include: TInclude | undefined,
|
||||
update: (key: ResourceKey<TKey>, context?: ReadonlyArray<string>) => Promise<T>,
|
||||
): Promise<T>;
|
||||
protected async performUpdate<T>(
|
||||
async performUpdate<T>(
|
||||
key: ResourceKey<TKey>,
|
||||
include: TInclude | undefined,
|
||||
update: (key: ResourceKey<TKey>, context?: ReadonlyArray<string>) => Promise<T>,
|
||||
exitCheck: (key: ResourceKey<TKey>, context?: ReadonlyArray<string>) => boolean
|
||||
): Promise<T | undefined>;
|
||||
protected async performUpdate<T>(
|
||||
async performUpdate<T>(
|
||||
key: ResourceKey<TKey>,
|
||||
include: TInclude | undefined,
|
||||
update: (key: ResourceKey<TKey>, context?: ReadonlyArray<string>) => Promise<T>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
query driverList($driverId: ID, $includeProviderProperties: Boolean!, $includeDriverProperties: Boolean!, $includeDriverParameters: Boolean!) {
|
||||
query driverList($driverId: ID, $includeProviderProperties: Boolean!, $includeDriverProperties: Boolean!, $includeDriverParameters: Boolean!, $includeDriverLibraries: Boolean!) {
|
||||
drivers: driverList(id: $driverId) {
|
||||
...DatabaseDriver
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ fragment DatabaseDriver on DriverInfo {
|
||||
requiresServerName
|
||||
anonymousAccess
|
||||
promotedScore
|
||||
providerId
|
||||
driverClassName
|
||||
custom
|
||||
|
||||
defaultAuthModel
|
||||
applicableAuthModels
|
||||
@@ -33,4 +36,10 @@ fragment DatabaseDriver on DriverInfo {
|
||||
validValues
|
||||
}
|
||||
driverParameters @include(if: $includeDriverParameters)
|
||||
|
||||
driverLibraries @include(if: $includeDriverLibraries) {
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
}
|
||||
@@ -453,6 +453,7 @@ export interface DriverInfo {
|
||||
description?: Maybe<Scalars['String']>;
|
||||
driverClassName?: Maybe<Scalars['String']>;
|
||||
driverInfoURL?: Maybe<Scalars['String']>;
|
||||
driverLibraries: Array<DriverLibraryInfo>;
|
||||
driverParameters: Scalars['Object'];
|
||||
driverProperties: Array<ObjectPropertyInfo>;
|
||||
driverPropertiesURL?: Maybe<Scalars['String']>;
|
||||
@@ -472,6 +473,12 @@ export interface DriverInfo {
|
||||
sampleURL?: Maybe<Scalars['String']>;
|
||||
}
|
||||
|
||||
export interface DriverLibraryInfo {
|
||||
icon: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
}
|
||||
|
||||
export interface LogEntry {
|
||||
message?: Maybe<Scalars['String']>;
|
||||
stackTrace?: Maybe<Scalars['String']>;
|
||||
@@ -2210,10 +2217,11 @@ export type DriverListQueryVariables = Exact<{
|
||||
includeProviderProperties: Scalars['Boolean'];
|
||||
includeDriverProperties: Scalars['Boolean'];
|
||||
includeDriverParameters: Scalars['Boolean'];
|
||||
includeDriverLibraries: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DriverListQuery = { drivers: Array<{ id: string, name?: string, icon?: string, description?: string, defaultPort?: string, defaultDatabase?: string, defaultServer?: string, defaultUser?: string, sampleURL?: string, embedded?: boolean, enabled: boolean, requiresServerName?: boolean, anonymousAccess?: boolean, promotedScore?: number, defaultAuthModel: string, applicableAuthModels: Array<string>, applicableNetworkHandlers: Array<string>, configurationTypes: Array<DriverConfigurationType>, driverParameters?: any, providerProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any>, length: ObjectPropertyLength, features: Array<string>, order: number, supportedConfigurationTypes?: Array<string> }>, driverProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any> }> }> };
|
||||
export type DriverListQuery = { drivers: Array<{ id: string, name?: string, icon?: string, description?: string, defaultPort?: string, defaultDatabase?: string, defaultServer?: string, defaultUser?: string, sampleURL?: string, embedded?: boolean, enabled: boolean, requiresServerName?: boolean, anonymousAccess?: boolean, promotedScore?: number, providerId?: string, driverClassName?: string, custom?: boolean, defaultAuthModel: string, applicableAuthModels: Array<string>, applicableNetworkHandlers: Array<string>, configurationTypes: Array<DriverConfigurationType>, driverParameters?: any, providerProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any>, length: ObjectPropertyLength, features: Array<string>, order: number, supportedConfigurationTypes?: Array<string> }>, driverProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any> }>, driverLibraries?: Array<{ id: string, name: string, icon: string }> }> };
|
||||
|
||||
export type ExecutionContextCreateMutationVariables = Exact<{
|
||||
projectId: Scalars['ID'];
|
||||
@@ -2449,7 +2457,7 @@ export type ConnectionFolderInfoFragment = { id: string, projectId: string, desc
|
||||
|
||||
export type DatabaseConnectionFragment = { id: string, projectId: string, name: string, description?: string, driverId: string, template: boolean, connected: boolean, readOnly: boolean, saveCredentials: boolean, credentialsSaved?: boolean, sharedCredentials: boolean, folder?: string, nodePath?: string, configurationType?: DriverConfigurationType, useUrl?: boolean, host?: string, port?: string, serverName?: string, databaseName?: string, url?: string, properties?: any, providerProperties?: any, requiredAuth?: string, features: Array<string>, supportedDataFormats: Array<ResultDataFormat>, authNeeded?: boolean, authModel?: string, canViewSettings: boolean, canEdit: boolean, canDelete: boolean, origin?: { type: string, subType?: string, displayName: string, icon?: string, details?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any>, value?: any, length: ObjectPropertyLength, features: Array<string>, order: number }> }, authProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, value?: any, validValues?: Array<any>, defaultValue?: any, length: ObjectPropertyLength, features: Array<string>, order: number }>, networkHandlersConfig?: Array<{ id: string, enabled: boolean, authType: NetworkHandlerAuthType, userName?: string, password?: string, key?: string, savePassword: boolean, properties: any }>, navigatorSettings: { showSystemObjects: boolean, showUtilityObjects: boolean, showOnlyEntities: boolean, mergeEntities: boolean, hideFolders: boolean, hideSchemas: boolean, hideVirtualModel: boolean } };
|
||||
|
||||
export type DatabaseDriverFragment = { id: string, name?: string, icon?: string, description?: string, defaultPort?: string, defaultDatabase?: string, defaultServer?: string, defaultUser?: string, sampleURL?: string, embedded?: boolean, enabled: boolean, requiresServerName?: boolean, anonymousAccess?: boolean, promotedScore?: number, defaultAuthModel: string, applicableAuthModels: Array<string>, applicableNetworkHandlers: Array<string>, configurationTypes: Array<DriverConfigurationType>, driverParameters?: any, providerProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any>, length: ObjectPropertyLength, features: Array<string>, order: number, supportedConfigurationTypes?: Array<string> }>, driverProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any> }> };
|
||||
export type DatabaseDriverFragment = { id: string, name?: string, icon?: string, description?: string, defaultPort?: string, defaultDatabase?: string, defaultServer?: string, defaultUser?: string, sampleURL?: string, embedded?: boolean, enabled: boolean, requiresServerName?: boolean, anonymousAccess?: boolean, promotedScore?: number, providerId?: string, driverClassName?: string, custom?: boolean, defaultAuthModel: string, applicableAuthModels: Array<string>, applicableNetworkHandlers: Array<string>, configurationTypes: Array<DriverConfigurationType>, driverParameters?: any, providerProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any>, length: ObjectPropertyLength, features: Array<string>, order: number, supportedConfigurationTypes?: Array<string> }>, driverProperties?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any> }>, driverLibraries?: Array<{ id: string, name: string, icon: string }> };
|
||||
|
||||
export type DriverProviderPropertyInfoFragment = { id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array<any>, length: ObjectPropertyLength, features: Array<string>, order: number, supportedConfigurationTypes?: Array<string> };
|
||||
|
||||
@@ -3214,6 +3222,9 @@ export const DatabaseDriverFragmentDoc = `
|
||||
requiresServerName
|
||||
anonymousAccess
|
||||
promotedScore
|
||||
providerId
|
||||
driverClassName
|
||||
custom
|
||||
defaultAuthModel
|
||||
applicableAuthModels
|
||||
applicableNetworkHandlers
|
||||
@@ -3231,6 +3242,11 @@ export const DatabaseDriverFragmentDoc = `
|
||||
validValues
|
||||
}
|
||||
driverParameters @include(if: $includeDriverParameters)
|
||||
driverLibraries @include(if: $includeDriverLibraries) {
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
}
|
||||
${DriverProviderPropertyInfoFragmentDoc}`;
|
||||
export const ExecutionContextInfoFragmentDoc = `
|
||||
@@ -3764,7 +3780,7 @@ export const DeleteConnectionFolderDocument = `
|
||||
}
|
||||
`;
|
||||
export const DriverListDocument = `
|
||||
query driverList($driverId: ID, $includeProviderProperties: Boolean!, $includeDriverProperties: Boolean!, $includeDriverParameters: Boolean!) {
|
||||
query driverList($driverId: ID, $includeProviderProperties: Boolean!, $includeDriverProperties: Boolean!, $includeDriverParameters: Boolean!, $includeDriverLibraries: Boolean!) {
|
||||
drivers: driverList(id: $driverId) {
|
||||
...DatabaseDriver
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ $mdc-theme-primary: #2a7cb4;
|
||||
$mdc-theme-on-primary: #ffffff;
|
||||
$mdc-theme-secondary: hsl(240deg, 10%, 15%);
|
||||
$mdc-theme-on-secondary: #cbcbcb;
|
||||
|
||||
$mdc-theme-sub-secondary: hsl(240deg, 10%, 20%);
|
||||
$mdc-theme-on-sub-secondary: #cbcbcb;
|
||||
|
||||
$mdc-theme-surface: hsl(240deg, 10%, 18%);
|
||||
$mdc-theme-on-surface: #ffffff;
|
||||
$mdc-theme-background: hsl(240deg, 10%, 26%);
|
||||
@@ -38,6 +42,7 @@ $mdc-theme-text-colors: (
|
||||
dark: (
|
||||
primary: white,
|
||||
secondary: rgba(white, 0.7),
|
||||
sub-secondary: rgba(white, 0.7),
|
||||
hint: rgba(white, 0.45),
|
||||
disabled: rgba(white, 0.45),
|
||||
icon: rgba(white, 0.45),
|
||||
@@ -46,6 +51,7 @@ $mdc-theme-text-colors: (
|
||||
light: (
|
||||
primary: rgba(black, 0.87),
|
||||
secondary: rgba(black, 0.45),
|
||||
sub-secondary: rgba(white, 0.7),
|
||||
hint: rgba(black, 0.45),
|
||||
disabled: rgba(black, 0.2),
|
||||
icon: rgba(black, 0.38),
|
||||
|
||||
@@ -10,6 +10,10 @@ $mdc-theme-primary: #2a7cb4; // main menu background
|
||||
$mdc-theme-on-primary: #ffffff; // main text color
|
||||
$mdc-theme-secondary: #f2f2f2;
|
||||
$mdc-theme-on-secondary: #6d6d6d;
|
||||
|
||||
$mdc-theme-sub-secondary: #f8f8f8;
|
||||
$mdc-theme-on-sub-secondary: #6d6d6d;
|
||||
|
||||
$mdc-theme-surface: #ffffff;
|
||||
$mdc-theme-on-surface: #353535;
|
||||
$mdc-theme-background: #dedede;
|
||||
@@ -37,6 +41,7 @@ $mdc-theme-text-colors: (
|
||||
light: (
|
||||
primary: white,
|
||||
secondary: rgba(white, 0.7),
|
||||
sub-secondary: rgba(white, 0.7),
|
||||
hint: rgba(white, 0.45),
|
||||
disabled: rgba(white, 0.45),
|
||||
icon: rgba(white, 0.45),
|
||||
@@ -45,6 +50,7 @@ $mdc-theme-text-colors: (
|
||||
dark: (
|
||||
primary: rgba(black, 0.87),
|
||||
secondary: rgba(black, 0.45),
|
||||
sub-secondary: rgba(black, 0.45),
|
||||
hint: rgba(black, 0.45),
|
||||
disabled: rgba(black, 0.2),
|
||||
icon: rgba(black, 0.38),
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
$mdc-theme-extra-property-values: (
|
||||
sub-secondary: $mdc-theme-sub-secondary,
|
||||
on-sub-secondary: $mdc-theme-on-sub-secondary,
|
||||
positive: $color-positive,
|
||||
on-positive: $color-on-positive,
|
||||
negative: $color-negative,
|
||||
|
||||
-2
@@ -15,8 +15,6 @@ import { Group, GroupTitle, BASE_CONTAINERS_STYLES, useResource, Combobox, Tags,
|
||||
import { DBDriverResource } from '@cloudbeaver/core-connections';
|
||||
import { CachedMapAllKey, resourceKeyList, ServerConfigInput } from '@cloudbeaver/core-sdk';
|
||||
|
||||
|
||||
|
||||
interface Props {
|
||||
serverConfig: ServerConfigInput;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ $deleted-color: rgba(255,51,0,0.3);
|
||||
}
|
||||
|
||||
.rdg-row:hover {
|
||||
@include stripes-background($mdc-theme-secondary);
|
||||
@include stripes-background($mdc-theme-sub-secondary);
|
||||
}
|
||||
|
||||
.rdg-cell {
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.rdg-row:hover {
|
||||
@include stripes-background($mdc-theme-secondary);
|
||||
@include stripes-background($mdc-theme-sub-secondary);
|
||||
}
|
||||
|
||||
.rdg-cell {
|
||||
|
||||
@@ -5147,6 +5147,15 @@ axios@^1.0.0:
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
axios@~1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.5.tgz#e07209b39a0d11848e3e341fa087acd71dadc542"
|
||||
integrity sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
axobject-query@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1"
|
||||
|
||||
Reference in New Issue
Block a user