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:
Ainur
2023-05-17 13:42:55 +03:00
committed by GitHub
co-authored by Aleksey Potsetsuev Tatiana Fokina naumov dariamarutkina Alexander Skoblikov
parent 9d83c67ae8
commit a219593e1d
44 changed files with 738 additions and 65 deletions
@@ -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();
@@ -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!
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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);
@@ -184,5 +184,4 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
private WebConnectionConfig getConnectionConfig(DataFetchingEnvironment env) {
return new WebConnectionConfig(env.getArgument("config"));
}
}
@@ -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);
@@ -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(
@@ -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] {
+14 -6
View File
@@ -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 && (
+24 -6
View File
@@ -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>
</>
);
};
});
+2 -1
View File
@@ -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', '您没有权限执行此操作'],
+2 -1
View File
@@ -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
);
},
};
}
+12 -2
View File
@@ -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
}
}
+19 -3
View File
@@ -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,
@@ -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 {
@@ -35,7 +35,7 @@
}
.rdg-row:hover {
@include stripes-background($mdc-theme-secondary);
@include stripes-background($mdc-theme-sub-secondary);
}
.rdg-cell {
+9
View File
@@ -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"