mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into CB-92-builder-package
# Conflicts: # webapp/package.json # webapp/packages/builder/configs/webpack/common.js # webapp/packages/builder/src/fix-gql.js # webapp/packages/core/package.json # webapp/packages/core/src/AppBootstrap.ts # webapp/packages/core/src/root/index.ts # webapp/packages/dbeaver/configs/webpack/common-exp.js # webapp/packages/dbeaver/package.json # webapp/yarn.lock
This commit is contained in:
@@ -60,7 +60,7 @@ type ServerConfig {
|
||||
supportsConnectionBrowser: Boolean
|
||||
supportsWorkspaces: Boolean
|
||||
|
||||
supportedLanguages: [ ServerLanguage ]!
|
||||
supportedLanguages: [ ServerLanguage! ]!
|
||||
services: [ WebServiceConfig ]
|
||||
productConfiguration: Object!
|
||||
}
|
||||
@@ -73,7 +73,7 @@ type SessionInfo {
|
||||
cacheExpired: Boolean!
|
||||
|
||||
serverMessages: [ ServerMessage ]
|
||||
connections: [ ConnectionInfo ]!
|
||||
connections: [ ConnectionInfo! ]!
|
||||
}
|
||||
|
||||
####################################################
|
||||
@@ -183,7 +183,7 @@ type ConnectionInfo {
|
||||
clientVersion: String
|
||||
|
||||
# Supported features (provided etc)
|
||||
features: [ String! ]
|
||||
features: [ String! ]!
|
||||
}
|
||||
|
||||
type LogEntry {
|
||||
@@ -199,30 +199,30 @@ type LogEntry {
|
||||
|
||||
extend type Query {
|
||||
# Returns server config
|
||||
serverConfig: ServerConfig
|
||||
serverConfig: ServerConfig!
|
||||
|
||||
# Returns session state ( initialize if not )
|
||||
sessionState: SessionInfo
|
||||
sessionState: SessionInfo!
|
||||
|
||||
# Session permissions
|
||||
sessionPermissions: [ID]!
|
||||
|
||||
# Get driver info
|
||||
driverList( id: ID ): [ DriverInfo! ]
|
||||
driverList( id: ID ): [ DriverInfo! ]!
|
||||
|
||||
# Get list of predefined data sources
|
||||
dataSourceList: [ DataSourceInfo! ]
|
||||
dataSourceList: [ DataSourceInfo! ]!
|
||||
|
||||
# Return connection state
|
||||
connectionState( id: ID! ): ConnectionInfo
|
||||
connectionState( id: ID! ): ConnectionInfo!
|
||||
|
||||
readSessionLog(maxEntries: Int, clearEntries: Boolean): [ LogEntry! ]
|
||||
readSessionLog(maxEntries: Int, clearEntries: Boolean): [ LogEntry! ]!
|
||||
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
# Initialize session
|
||||
openSession: SessionInfo
|
||||
openSession: SessionInfo!
|
||||
|
||||
# Destroy session
|
||||
closeSession: Boolean
|
||||
@@ -234,13 +234,13 @@ extend type Mutation {
|
||||
changeSessionLanguage(locale: String): Boolean
|
||||
|
||||
# Create new connection
|
||||
createConnection( config: ConnectionConfig! ): ConnectionInfo
|
||||
createConnection( config: ConnectionConfig! ): ConnectionInfo!
|
||||
|
||||
# Test connection configuration. Returns remote server version
|
||||
testConnection( config: ConnectionConfig! ): ConnectionInfo
|
||||
testConnection( config: ConnectionConfig! ): ConnectionInfo!
|
||||
|
||||
# Connect to database
|
||||
openConnection( config: ConnectionConfig! ): ConnectionInfo
|
||||
openConnection( config: ConnectionConfig! ): ConnectionInfo!
|
||||
|
||||
# Disconnect from database
|
||||
closeConnection( id: ID! ): Boolean!
|
||||
|
||||
@@ -29,7 +29,7 @@ type ObjectPropertyInfo {
|
||||
# Default property value
|
||||
defaultValue: Object
|
||||
# Supported features (system, hidden, inherited, foreign, expensive, etc)
|
||||
features: [ String! ]
|
||||
features: [ String! ]!
|
||||
}
|
||||
|
||||
input ObjectPropertyFilter {
|
||||
@@ -94,10 +94,8 @@ type NavigatorNodeInfo {
|
||||
}
|
||||
|
||||
type DatabaseStructContainers {
|
||||
|
||||
catalogList: [ DatabaseObjectInfo! ]!
|
||||
schemaList: [ DatabaseObjectInfo! ]!
|
||||
|
||||
}
|
||||
|
||||
####################################################
|
||||
@@ -111,9 +109,9 @@ extend type Query {
|
||||
parentPath: ID!,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
onlyFolders: Boolean): [ NavigatorNodeInfo! ]
|
||||
onlyFolders: Boolean): [ NavigatorNodeInfo! ]!
|
||||
|
||||
navNodeInfo( nodePath: ID! ): NavigatorNodeInfo
|
||||
navNodeInfo( nodePath: ID! ): NavigatorNodeInfo!
|
||||
|
||||
navRefreshNode( nodePath: ID! ): Boolean
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ type SQLExecuteInfo {
|
||||
|
||||
statusMessage: String
|
||||
duration: Int
|
||||
results: [ SQLQueryResults! ]
|
||||
results: [ SQLQueryResults! ]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
|
||||
+13
-1
@@ -17,10 +17,14 @@
|
||||
package io.cloudbeaver.model.user;
|
||||
|
||||
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
|
||||
import io.cloudbeaver.registry.WebAuthProviderPropertyDescriptor;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Web user.
|
||||
* WebAuthProviderInfo.
|
||||
*/
|
||||
public class WebAuthProviderInfo {
|
||||
|
||||
@@ -48,6 +52,14 @@ public class WebAuthProviderInfo {
|
||||
return descriptor.getDescription();
|
||||
}
|
||||
|
||||
public boolean isDefaultProvider() {
|
||||
return descriptor.getId().equals(CBPlatform.getInstance().getApplication().getAppConfiguration().getDefaultAuthProvider());
|
||||
}
|
||||
|
||||
public List<WebAuthProviderPropertyDescriptor> getCredentialParameters() {
|
||||
return descriptor.getCredentialParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getId();
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
package io.cloudbeaver.server;
|
||||
|
||||
import io.cloudbeaver.auth.provider.local.LocalAuthProvider;
|
||||
|
||||
/**
|
||||
* Application configuration
|
||||
*/
|
||||
@@ -30,6 +32,7 @@ public class CBAppConfig {
|
||||
private boolean supportsUserWorkspaces = false;
|
||||
private String[] enabledDrivers = new String[0];
|
||||
private String[] disabledDrivers = new String[0];
|
||||
private String defaultAuthProvider = LocalAuthProvider.PROVIDER_ID;
|
||||
|
||||
public boolean isAuthenticationEnabled() {
|
||||
return authenticationEnabled;
|
||||
@@ -70,4 +73,8 @@ public class CBAppConfig {
|
||||
public String[] getDisabledDrivers() {
|
||||
return disabledDrivers;
|
||||
}
|
||||
|
||||
public String getDefaultAuthProvider() {
|
||||
return defaultAuthProvider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ type AuthProviderInfo {
|
||||
icon: ID
|
||||
description: String
|
||||
|
||||
isDefault: Boolean
|
||||
defaultProvider: Boolean
|
||||
|
||||
#configurationParameters: [ObjectPropertyInfo]!
|
||||
|
||||
credentialParameters: [AuthCredentialInfo]!
|
||||
credentialParameters: [AuthCredentialInfo!]!
|
||||
}
|
||||
|
||||
type UserAuthInfo {
|
||||
@@ -46,12 +46,12 @@ type UserAuthInfo {
|
||||
|
||||
extend type Query {
|
||||
|
||||
authLogin(provider: ID!, credentials: Object!): UserAuthInfo
|
||||
authLogin(provider: ID!, credentials: Object!): UserAuthInfo!
|
||||
|
||||
authLogout: Boolean
|
||||
|
||||
sessionUser: UserAuthInfo
|
||||
|
||||
authProviders: [AuthProviderInfo]!
|
||||
authProviders: [AuthProviderInfo!]!
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWService;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebAction;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.user.WebAuthProviderInfo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -36,4 +37,7 @@ public interface DBWServiceAuth extends DBWService {
|
||||
|
||||
@WebAction(requirePermissions = {})
|
||||
WebAuthInfo sessionUser(WebSession webSession) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = {})
|
||||
WebAuthProviderInfo[] getAuthProviders(WebSession webSession);
|
||||
}
|
||||
|
||||
+1
-2
@@ -17,7 +17,6 @@
|
||||
package io.cloudbeaver.service.auth;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
import io.cloudbeaver.service.DBWBindingContext;
|
||||
import io.cloudbeaver.service.WebServiceBindingBase;
|
||||
import io.cloudbeaver.service.auth.impl.WebServiceAuthImpl;
|
||||
@@ -45,7 +44,7 @@ public class WebServiceBindingAuth extends WebServiceBindingBase<DBWServiceAuth>
|
||||
return true;
|
||||
})
|
||||
.dataFetcher("sessionUser", env -> getService(env).sessionUser(getWebSession(env)))
|
||||
.dataFetcher("authProviders", env -> WebServiceRegistry.getInstance().getAuthProviders())
|
||||
.dataFetcher("authProviders", env -> getService(env).getAuthProviders(getWebSession(env)))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
+8
@@ -21,6 +21,7 @@ import io.cloudbeaver.DBWAuthProviderExternal;
|
||||
import io.cloudbeaver.DBWSecurityController;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.user.WebAuthProviderInfo;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
import io.cloudbeaver.registry.WebAuthProviderDescriptor;
|
||||
import io.cloudbeaver.registry.WebServiceRegistry;
|
||||
@@ -126,4 +127,11 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
return webSession.getAttribute(ATTR_USER_AUTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthProviderInfo[] getAuthProviders(WebSession webSession) {
|
||||
return WebServiceRegistry.getInstance().getAuthProviders()
|
||||
.stream().map(WebAuthProviderInfo::new)
|
||||
.toArray(WebAuthProviderInfo[]::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ input DataTransferParameters {
|
||||
extend type Query {
|
||||
|
||||
# Available transfer processors
|
||||
dataTransferAvailableStreamProcessors: [ DataTransferProcessorInfo ]
|
||||
dataTransferAvailableStreamProcessors: [ DataTransferProcessorInfo! ]!
|
||||
|
||||
dataTransferExportDataFromContainer(
|
||||
connectionId: ID!,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<deps.output.dir>mysql</deps.output.dir>
|
||||
<deps.output.dir>mysql/mysql8</deps.output.dir>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
<setEntry value="org.jkiss.dbeaver.db.feature:default"/>
|
||||
<setEntry value="org.jkiss.dbeaver.runtime.feature:default"/>
|
||||
</setAttribute>
|
||||
<stringAttribute key="selected_target_plugins" value="com.github.jsqlparser@default:default,com.google.gson@default:default,com.google.guava*27.1.0.v20190517-1946@default:default,com.ibm.icu.base@default:default,com.jcraft.jsch@default:default,com.jcraft.jzlib@default:default,com.sun.jna*4.5.1.v20190425-1842@default:default,com.sun.jna.platform*4.5.1.v20190425-1842@default:default,javax.annotation@default:default,javax.inject@default:default,javax.servlet*3.1.0.v201410161800@default:default,javax.xml@default:default,net.sf.opencsv@default:default,org.apache.batik.constants@default:default,org.apache.batik.css@default:default,org.apache.batik.i18n@default:default,org.apache.batik.util@default:default,org.apache.commons.io@default:default,org.apache.commons.jexl@default:default,org.apache.commons.jxpath@default:default,org.apache.commons.logging@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.scr@1:true,org.apache.log4j@default:default,org.apache.lucene.analyzers-common*8.0.0.v20190404-1858@default:default,org.apache.lucene.analyzers-smartcn@default:default,org.apache.lucene.core*8.0.0.v20190404-1858@default:default,org.apache.xerces@default:default,org.apache.xml.resolver@default:default,org.apache.xml.serializer@default:default,org.apache.xmlgraphics@default:default,org.bouncycastle.bcpkix@default:default,org.bouncycastle.bcprov@default:default,org.eclipse.ant.core@default:default,org.eclipse.compare.core@default:default,org.eclipse.core.commands@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding.property@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.expressions@default:default,org.eclipse.core.filebuffers@default:default,org.eclipse.core.filesystem.win32.x86_64@default:false,org.eclipse.core.filesystem@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.net.win32.x86_64@default:false,org.eclipse.core.net@default:default,org.eclipse.core.resources.win32.x86_64@default:false,org.eclipse.core.resources@default:default,org.eclipse.core.runtime@default:true,org.eclipse.core.variables@default:default,org.eclipse.e4.core.commands@default:default,org.eclipse.e4.core.contexts@default:default,org.eclipse.e4.core.di.annotations@default:default,org.eclipse.e4.core.di.extensions.supplier@default:default,org.eclipse.e4.core.di.extensions@default:default,org.eclipse.e4.core.di@default:default,org.eclipse.e4.core.services@default:default,org.eclipse.e4.emf.xpath@default:default,org.eclipse.emf.common@default:default,org.eclipse.emf.ecore.change@default:default,org.eclipse.emf.ecore.edit@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.emf.edit@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.bidi@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.event@default:default,org.eclipse.equinox.http.jetty@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.p2.core@default:default,org.eclipse.equinox.p2.engine@default:default,org.eclipse.equinox.p2.metadata.repository@default:default,org.eclipse.equinox.p2.metadata@default:default,org.eclipse.equinox.p2.repository@default:default,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.security.win32.x86_64@default:false,org.eclipse.equinox.security@default:default,org.eclipse.jdt.annotation*2.2.300.v20190328-1431@default:default,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.ltk.core.refactoring@default:default,org.eclipse.osgi.compatibility.state@default:false,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.eclipse.team.core@default:default,org.eclipse.text@default:default,org.jkiss.bundle.apache.dbcp@default:default,org.jkiss.bundle.aws@default:default,org.jkiss.bundle.gis@default:default,org.jkiss.bundle.graphql.java@default:default,org.jkiss.bundle.sshj@default:default,org.objectweb.asm*7.1.0.v20190412-2143@default:default,org.slf4j.api*1.7.10.v20170428-1633@default:default,org.tukaani.xz@default:default,org.w3c.css.sac@default:default,org.w3c.dom.events@default:default,org.w3c.dom.smil@default:default,org.w3c.dom.svg@default:default,stax.api@default:default"/>
|
||||
<stringAttribute key="selected_workspace_plugins" value="com.dbeaver.db.sqlite.crypt@default:default,io.cloudbeaver.connect.provider.sample@default:default,io.cloudbeaver.resources.drivers.base@default:default,io.cloudbeaver.resources.drivers.ee@default:default,io.cloudbeaver.server@default:default,io.cloudbeaver.service.admin@default:default,io.cloudbeaver.service.auth.aws@default:default,io.cloudbeaver.service.auth@default:default,io.cloudbeaver.service.data.transfer@default:default,io.cloudbeaver.service.metadata@default:default,org.jkiss.dbeaver.cmp.simple@default:default,org.jkiss.dbeaver.data.gis@default:default,org.jkiss.dbeaver.data.transfer@default:default,org.jkiss.dbeaver.ext.clickhouse@default:default,org.jkiss.dbeaver.ext.db2.i@default:default,org.jkiss.dbeaver.ext.db2.zos@default:default,org.jkiss.dbeaver.ext.derby@default:default,org.jkiss.dbeaver.ext.firebird@default:default,org.jkiss.dbeaver.ext.generic@default:default,org.jkiss.dbeaver.ext.greenplum@default:default,org.jkiss.dbeaver.ext.h2@default:default,org.jkiss.dbeaver.ext.h2gis@default:default,org.jkiss.dbeaver.ext.hana@default:default,org.jkiss.dbeaver.ext.hive@default:default,org.jkiss.dbeaver.ext.hsqldb@default:default,org.jkiss.dbeaver.ext.informix@default:default,org.jkiss.dbeaver.ext.kognitio@default:default,org.jkiss.dbeaver.ext.mssql@default:default,org.jkiss.dbeaver.ext.mysql@default:default,org.jkiss.dbeaver.ext.netezza@default:default,org.jkiss.dbeaver.ext.opendistro@default:default,org.jkiss.dbeaver.ext.oracle@default:default,org.jkiss.dbeaver.ext.phoenix@default:default,org.jkiss.dbeaver.ext.postgresql@default:default,org.jkiss.dbeaver.ext.snowflake.core@default:default,org.jkiss.dbeaver.ext.sqlite@default:default,org.jkiss.dbeaver.ext.teradata@default:default,org.jkiss.dbeaver.ext.vertica@default:default,org.jkiss.dbeaver.model.sql@default:default,org.jkiss.dbeaver.model@default:default,org.jkiss.dbeaver.net.ssh.sshj@default:default,org.jkiss.dbeaver.net.ssh@default:default,org.jkiss.dbeaver.registry@default:default,org.jkiss.dbeaver.slf4j@default:false,org.jkiss.dbeaver.tasks.native@default:default,org.jkiss.utils@default:default,org.jkiss.wmi@default:default"/>
|
||||
<stringAttribute key="selected_target_plugins" value="com.github.jsqlparser@default:default,com.google.gson@default:default,com.google.guava*27.1.0.v20190517-1946@default:default,com.ibm.icu.base@default:default,com.jcraft.jsch@default:default,com.jcraft.jzlib@default:default,com.sun.jna*4.5.1.v20190425-1842@default:default,com.sun.jna.platform*4.5.1.v20190425-1842@default:default,javax.annotation@default:default,javax.inject@default:default,javax.servlet*3.1.0.v201410161800@default:default,javax.xml@default:default,net.sf.opencsv@default:default,org.apache.batik.constants@default:default,org.apache.batik.css@default:default,org.apache.batik.i18n@default:default,org.apache.batik.util@default:default,org.apache.commons.io@default:default,org.apache.commons.jexl@default:default,org.apache.commons.jxpath@default:default,org.apache.commons.logging@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.scr@1:true,org.apache.log4j@default:default,org.apache.xerces@default:default,org.apache.xml.resolver@default:default,org.apache.xml.serializer@default:default,org.apache.xmlgraphics@default:default,org.bouncycastle.bcpkix@default:default,org.bouncycastle.bcprov@default:default,org.eclipse.ant.core@default:default,org.eclipse.compare.core@default:default,org.eclipse.core.commands@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding.property@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.expressions@default:default,org.eclipse.core.filebuffers@default:default,org.eclipse.core.filesystem.win32.x86_64@default:false,org.eclipse.core.filesystem@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.net.win32.x86_64@default:false,org.eclipse.core.net@default:default,org.eclipse.core.resources.win32.x86_64@default:false,org.eclipse.core.resources@default:default,org.eclipse.core.runtime@default:true,org.eclipse.core.variables@default:default,org.eclipse.e4.core.commands@default:default,org.eclipse.e4.core.contexts@default:default,org.eclipse.e4.core.di.annotations@default:default,org.eclipse.e4.core.di.extensions.supplier@default:default,org.eclipse.e4.core.di.extensions@default:default,org.eclipse.e4.core.di@default:default,org.eclipse.e4.core.services@default:default,org.eclipse.e4.emf.xpath@default:default,org.eclipse.emf.common@default:default,org.eclipse.emf.ecore.change@default:default,org.eclipse.emf.ecore.edit@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.emf.edit@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.bidi@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.event@default:default,org.eclipse.equinox.http.jetty@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.p2.core@default:default,org.eclipse.equinox.p2.engine@default:default,org.eclipse.equinox.p2.metadata.repository@default:default,org.eclipse.equinox.p2.metadata@default:default,org.eclipse.equinox.p2.repository@default:default,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.security.win32.x86_64@default:false,org.eclipse.equinox.security@default:default,org.eclipse.jdt.annotation*2.2.300.v20190328-1431@default:default,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.osgi.compatibility.state@default:false,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.eclipse.team.core@default:default,org.eclipse.text@default:default,org.jkiss.bundle.apache.dbcp@default:default,org.jkiss.bundle.apache.poi@default:default,org.jkiss.bundle.aws@default:default,org.jkiss.bundle.gis@default:default,org.jkiss.bundle.google.cloud@default:default,org.jkiss.bundle.graphql.java@default:default,org.jkiss.bundle.influxdb@default:default,org.jkiss.bundle.jaxb@default:default,org.jkiss.bundle.jedis@default:default,org.jkiss.bundle.onelogin.saml@default:default,org.jkiss.bundle.sshj@default:default,org.objectweb.asm*7.1.0.v20190412-2143@default:default,org.slf4j.api*1.7.10.v20170428-1633@default:default,org.tukaani.xz@default:default,org.w3c.css.sac@default:default,org.w3c.dom.events@default:default,org.w3c.dom.smil@default:default,org.w3c.dom.svg@default:default,stax.api@default:default"/>
|
||||
<stringAttribute key="selected_workspace_plugins" value="com.dbeaver.db.sqlite.crypt@default:default,com.dbeaver.resources.clients.mysql.win32@default:default,com.dbeaver.resources.clients.postgresql.win32@default:default,com.dbeaver.resources.drivers.jdbc@default:default,io.cloudbeaver.connect.provider.sample@default:default,io.cloudbeaver.product.ce@default:default,io.cloudbeaver.resources.drivers.base@default:default,io.cloudbeaver.resources.drivers.ee@default:default,io.cloudbeaver.server@default:default,io.cloudbeaver.service.admin@default:default,io.cloudbeaver.service.auth.aws@default:default,io.cloudbeaver.service.auth@default:default,io.cloudbeaver.service.data.transfer@default:default,io.cloudbeaver.service.metadata@default:default,org.jkiss.dbeaver.cmp.simple@default:default,org.jkiss.dbeaver.data.gis@default:default,org.jkiss.dbeaver.data.office@default:default,org.jkiss.dbeaver.data.transfer@default:default,org.jkiss.dbeaver.ext.clickhouse@default:default,org.jkiss.dbeaver.ext.db2.i@default:default,org.jkiss.dbeaver.ext.db2.zos@default:default,org.jkiss.dbeaver.ext.derby@default:default,org.jkiss.dbeaver.ext.firebird@default:default,org.jkiss.dbeaver.ext.generic@default:default,org.jkiss.dbeaver.ext.greenplum@default:default,org.jkiss.dbeaver.ext.h2@default:default,org.jkiss.dbeaver.ext.h2gis@default:default,org.jkiss.dbeaver.ext.hana@default:default,org.jkiss.dbeaver.ext.hive@default:default,org.jkiss.dbeaver.ext.hsqldb@default:default,org.jkiss.dbeaver.ext.informix@default:default,org.jkiss.dbeaver.ext.kognitio@default:default,org.jkiss.dbeaver.ext.mssql@default:default,org.jkiss.dbeaver.ext.mysql@default:default,org.jkiss.dbeaver.ext.netezza@default:default,org.jkiss.dbeaver.ext.opendistro@default:default,org.jkiss.dbeaver.ext.oracle@default:default,org.jkiss.dbeaver.ext.phoenix@default:default,org.jkiss.dbeaver.ext.postgresql@default:default,org.jkiss.dbeaver.ext.snowflake.core@default:default,org.jkiss.dbeaver.ext.sqlite@default:default,org.jkiss.dbeaver.ext.teradata@default:default,org.jkiss.dbeaver.ext.vertica@default:default,org.jkiss.dbeaver.model.sql@default:default,org.jkiss.dbeaver.model@default:default,org.jkiss.dbeaver.net.ssh.sshj@default:default,org.jkiss.dbeaver.net.ssh@default:default,org.jkiss.dbeaver.registry@default:default,org.jkiss.dbeaver.slf4j@default:false,org.jkiss.dbeaver.tasks.native@default:default,org.jkiss.utils@default:default,org.jkiss.wmi@default:default"/>
|
||||
<booleanAttribute key="show_selected_only" value="false"/>
|
||||
<booleanAttribute key="tracing" value="false"/>
|
||||
<booleanAttribute key="useCustomFeatures" value="true"/>
|
||||
<booleanAttribute key="useCustomFeatures" value="false"/>
|
||||
<booleanAttribute key="useDefaultConfig" value="true"/>
|
||||
<booleanAttribute key="useDefaultConfigArea" value="true"/>
|
||||
<booleanAttribute key="useProduct" value="true"/>
|
||||
|
||||
+4
-5
@@ -5,15 +5,12 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
// 'plugin:prettier/recommended',
|
||||
// 'prettier/@typescript-eslint',
|
||||
],
|
||||
|
||||
plugins: [
|
||||
'import',
|
||||
'@typescript-eslint',
|
||||
'eslint-plugin-import-helpers',
|
||||
// 'prettier',
|
||||
'jest',
|
||||
],
|
||||
|
||||
@@ -37,7 +34,6 @@ module.exports = {
|
||||
},
|
||||
|
||||
rules: {
|
||||
// 'prettier/prettier': 'warn',
|
||||
'arrow-parens': ['error', 'as-needed', { 'requireForBlockBody': true }],
|
||||
'curly': ['error', 'all'],
|
||||
'class-methods-use-this': 'off',
|
||||
@@ -76,12 +72,15 @@ module.exports = {
|
||||
'no-restricted-globals': 'off',
|
||||
'no-useless-constructor': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'arrow-body-style': ['error', 'as-needed'],
|
||||
'function-call-argument-newline': ['error', 'consistent'],
|
||||
'max-len': [
|
||||
'error',
|
||||
{
|
||||
code: 120,
|
||||
ignoreTrailingComments: true,
|
||||
ignoreStrings: true
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true
|
||||
}
|
||||
],
|
||||
'lines-between-class-members': 'off',
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// we use prettier only for graphQL-codegen
|
||||
|
||||
module.exports = {
|
||||
useTabs: false,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'all',
|
||||
singleQuote: false,
|
||||
semi: true,
|
||||
}
|
||||
@@ -33,7 +33,6 @@
|
||||
"@typescript-eslint/parser": "^2.8.0",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-config-airbnb-base": "^14.0.0",
|
||||
"eslint-config-prettier": "^6.5.0",
|
||||
"eslint-import-resolver-typescript": "^2.0.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
|
||||
@@ -15,14 +15,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dbeaver/core": "^0.1.0",
|
||||
"@material/button": "^4.0.0",
|
||||
"@material/elevation": "^4.0.0",
|
||||
"@material/ripple": "^4.0.0",
|
||||
"@material/theme": "^4.0.0",
|
||||
"@material/typography": "^4.0.0",
|
||||
"graphql": "^14.5.8",
|
||||
"graphql-request": "^1.8.2",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.2.2",
|
||||
"react": "^16.13.1",
|
||||
|
||||
@@ -22,14 +22,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dbeaver/core": "^0.1.0",
|
||||
"@material/button": "^4.0.0",
|
||||
"@material/elevation": "^4.0.0",
|
||||
"@material/ripple": "^4.0.0",
|
||||
"@material/theme": "^4.0.0",
|
||||
"@material/typography": "^4.0.0",
|
||||
"graphql": "^14.5.8",
|
||||
"graphql-request": "^1.8.2",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.2.2",
|
||||
"react": "^16.13.1",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@dbeaver/authentication",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/types",
|
||||
"scripts": {
|
||||
"build-plugin": "npm run clean-dist && webpack -p --config=../../configs/webpack/build-plugin.js",
|
||||
"clean-dist": "rimraf dist/*",
|
||||
"rollup": "npm run clean-dist && rollup --config ../../configs/rollup.config.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dbeaver/core": "^0.1.0",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.2.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"reakit": "^1.0.0-beta.11",
|
||||
"reshadow": "0.0.1-alpha.75",
|
||||
"rxjs": "^6.5.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
import { SessionService } from '@dbeaver/core/root';
|
||||
import { GraphQLService, UserAuthInfo } from '@dbeaver/core/sdk';
|
||||
|
||||
import { AuthProviderService } from './AuthProviderService';
|
||||
|
||||
@injectable()
|
||||
export class AuthInfoService {
|
||||
@observable private user: UserAuthInfo | null = null;
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private notificationService: NotificationService,
|
||||
private authProviderService: AuthProviderService,
|
||||
private sessionService: SessionService,
|
||||
) { }
|
||||
|
||||
get userInfo() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
async login(provider: string, credentials: object): Promise<UserAuthInfo> {
|
||||
if (this.user) {
|
||||
throw new Error('User already logged in');
|
||||
}
|
||||
|
||||
const processedCredentials = this.authProviderService.processCredentials(provider, credentials);
|
||||
|
||||
const { user } = await this.graphQLService.gql.authLogin({
|
||||
provider,
|
||||
credentials: processedCredentials,
|
||||
});
|
||||
this.user = user;
|
||||
|
||||
await this.updateSession();
|
||||
return this.user;
|
||||
}
|
||||
|
||||
async logout() {
|
||||
if (this.user) {
|
||||
await this.graphQLService.gql.authLogout();
|
||||
this.user = null;
|
||||
await this.updateSession();
|
||||
}
|
||||
}
|
||||
|
||||
async updateAuthInfo(): Promise<UserAuthInfo | null> {
|
||||
try {
|
||||
const { user } = await this.graphQLService.gql.getSessionUser();
|
||||
this.user = user || null;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load session user');
|
||||
}
|
||||
|
||||
return this.user;
|
||||
}
|
||||
|
||||
private async updateSession() {
|
||||
await this.sessionService.update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { SettingsMenuService, TopNavService } from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { ServerService } from '@dbeaver/core/root';
|
||||
|
||||
import { AuthenticationService } from './AuthenticationService';
|
||||
import { AuthInfoService } from './AuthInfoService';
|
||||
import { AuthDialogService } from './Dialog/AuthDialogService';
|
||||
import { UserInfo } from './UserInfo';
|
||||
|
||||
@injectable()
|
||||
export class AuthMenuService {
|
||||
constructor(
|
||||
private serverService: ServerService,
|
||||
private authDialogService: AuthDialogService,
|
||||
private authInfoService: AuthInfoService,
|
||||
private settingsMenuService: SettingsMenuService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private topNavService: TopNavService,
|
||||
) { }
|
||||
|
||||
register() {
|
||||
this.settingsMenuService.addMenuItem(
|
||||
SettingsMenuService.settingsMenuToken,
|
||||
{
|
||||
id: 'login',
|
||||
order: 0,
|
||||
isHidden: () => !this.serverService.config.data?.authenticationEnabled || !!this.authInfoService.userInfo,
|
||||
title: 'authentication_login',
|
||||
onClick: () => this.authDialogService.showLoginForm(),
|
||||
}
|
||||
);
|
||||
|
||||
this.settingsMenuService.addMenuItem(
|
||||
SettingsMenuService.settingsMenuToken,
|
||||
{
|
||||
id: 'logout',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
isHidden: () => !this.serverService.config.data?.authenticationEnabled || !this.authInfoService.userInfo,
|
||||
title: 'authentication_logout',
|
||||
onClick: this.logout.bind(this),
|
||||
}
|
||||
);
|
||||
|
||||
this.topNavService.placeholder.add(UserInfo, 4);
|
||||
}
|
||||
|
||||
private async logout() {
|
||||
await this.authInfoService.logout();
|
||||
await this.authenticationService.auth();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { createHash } from 'crypto';
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import {
|
||||
GraphQLService, CachedResource, AuthProviderInfo
|
||||
} from '@dbeaver/core/sdk';
|
||||
|
||||
export type AuthProvider = Omit<AuthProviderInfo, 'configurationParameters'>
|
||||
|
||||
@injectable()
|
||||
export class AuthProviderService {
|
||||
readonly providers = new CachedResource([], this.refreshAsync.bind(this), data => !!data.length)
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) { }
|
||||
|
||||
processCredentials(providerId: string, credentials: any) {
|
||||
const provider = this.providers.data.find(provider => provider.id === providerId);
|
||||
|
||||
if (!provider) {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
const credentialsProcessed = { ...credentials };
|
||||
for (const parameter of provider.credentialParameters) {
|
||||
if (parameter.encryption === 'hash' && parameter.id in credentialsProcessed) {
|
||||
const md5Hash = createHash('md5')
|
||||
.update(credentialsProcessed[parameter.id])
|
||||
.digest('hex')
|
||||
.toUpperCase();
|
||||
credentialsProcessed[parameter.id] = md5Hash;
|
||||
}
|
||||
}
|
||||
|
||||
return credentialsProcessed;
|
||||
}
|
||||
|
||||
private async refreshAsync(data: AuthProvider[]): Promise<AuthProvider[]> {
|
||||
const { providers } = await this.graphQLService.gql.getAuthProviders();
|
||||
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { ServerService } from '@dbeaver/core/root';
|
||||
|
||||
import { AuthInfoService } from './AuthInfoService';
|
||||
import { AuthDialogService } from './Dialog/AuthDialogService';
|
||||
|
||||
@injectable()
|
||||
export class AuthenticationService {
|
||||
constructor(
|
||||
private serverService: ServerService,
|
||||
private authDialogService: AuthDialogService,
|
||||
private authInfoService: AuthInfoService,
|
||||
) { }
|
||||
|
||||
async auth() {
|
||||
const config = await this.serverService.config.load();
|
||||
if (!config) {
|
||||
throw new Error('Can\'t configure Authentication');
|
||||
}
|
||||
|
||||
if (!config.authenticationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userInfo = await this.authInfoService.updateAuthInfo();
|
||||
if (userInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.anonymousAccessEnabled) {
|
||||
await this.authDialogService.showLoginForm(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
|
||||
import { AuthenticationService } from './AuthenticationService';
|
||||
import { AuthMenuService } from './AuthMenuService';
|
||||
|
||||
@injectable()
|
||||
export class Bootstrap {
|
||||
constructor(
|
||||
private authenticationService: AuthenticationService,
|
||||
private authMenuService: AuthMenuService
|
||||
) { }
|
||||
|
||||
async bootstrap() {
|
||||
this.authMenuService.register();
|
||||
await this.authenticationService.auth();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import {
|
||||
SubmittingForm, ErrorMessage, TabsState, TabList, Tab, TabTitle
|
||||
} from '@dbeaver/core/blocks';
|
||||
import { useController } from '@dbeaver/core/di';
|
||||
import { DialogComponent, CommonDialogWrapper } from '@dbeaver/core/dialogs';
|
||||
import { useTranslate } from '@dbeaver/core/localization';
|
||||
import { composes, useStyles } from '@dbeaver/core/theming';
|
||||
|
||||
import { AuthDialogController } from './AuthDialogController';
|
||||
import { AuthDialogFooter } from './AuthDialogFooter';
|
||||
import { AuthProviderForm } from './AuthProviderForm/AuthProviderForm';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
Tab {
|
||||
composes: theme-ripple theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
ErrorMessage {
|
||||
composes: theme-background-secondary from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
custom-connection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
CommonDialogWrapper {
|
||||
min-height: 400px;
|
||||
min-width: 600px;
|
||||
}
|
||||
SubmittingForm {
|
||||
overflow: auto;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
SubmittingForm, AuthProviderForm {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
TabList {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
padding-left: 24px;
|
||||
}
|
||||
Tab {
|
||||
composes: theme-typography--body2 from global;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
|
||||
&:global([aria-selected=true]) {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
AuthProviderForm {
|
||||
flex-direction: column;
|
||||
padding: 18px 24px;
|
||||
}
|
||||
ErrorMessage {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const AuthDialog: DialogComponent<null, null> = observer(
|
||||
function AuthDialog(props) {
|
||||
const controller = useController(AuthDialogController, props.rejectDialog);
|
||||
const translate = useTranslate();
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<TabsState currentTabId={controller.provider?.id || null}>
|
||||
<CommonDialogWrapper
|
||||
title={translate('authentication_login_dialog_title')}
|
||||
noBodyPadding
|
||||
header={(
|
||||
<TabList>
|
||||
{controller.providers.map(provider => (
|
||||
<Tab
|
||||
key={provider.id}
|
||||
tabId={provider.id}
|
||||
onOpen={() => controller.selectProvider(provider.id)}
|
||||
>
|
||||
<TabTitle title={provider.label} />
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
)}
|
||||
footer={(
|
||||
<AuthDialogFooter
|
||||
isAuthenticating={controller.isAuthenticating}
|
||||
onLogin={controller.login}
|
||||
/>
|
||||
)}
|
||||
onReject={props.options?.persistent ? undefined : props.rejectDialog}
|
||||
>
|
||||
<SubmittingForm onSubmit={controller.login}>
|
||||
{controller.provider && (
|
||||
<AuthProviderForm
|
||||
provider={controller.provider}
|
||||
credentials={controller.credentials}
|
||||
authenticate={controller.isAuthenticating}
|
||||
/>
|
||||
)}
|
||||
{!controller.provider && <>Select available provider</>}
|
||||
</SubmittingForm>
|
||||
{controller.error.responseMessage && (
|
||||
<ErrorMessage
|
||||
text={controller.error.responseMessage}
|
||||
hasDetails={controller.error.hasDetails}
|
||||
onShowDetails={controller.showDetails}
|
||||
/>
|
||||
)}
|
||||
</CommonDialogWrapper>
|
||||
</TabsState>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { observable, computed } from 'mobx';
|
||||
|
||||
import { injectable, IInitializableController, IDestructibleController } from '@dbeaver/core/di';
|
||||
import { CommonDialogService } from '@dbeaver/core/dialogs';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
import { GQLErrorCatcher } from '@dbeaver/core/sdk';
|
||||
import { ErrorDetailsDialog } from '@dbeaver/core/src/app';
|
||||
|
||||
import { AuthInfoService } from '../AuthInfoService';
|
||||
import { AuthProviderService, AuthProvider } from '../AuthProviderService';
|
||||
|
||||
@injectable()
|
||||
export class AuthDialogController implements IInitializableController, IDestructibleController {
|
||||
@observable provider: AuthProvider | null = null
|
||||
@observable isAuthenticating = false;
|
||||
@observable credentials = {};
|
||||
|
||||
get isLoading() {
|
||||
return this.authProviderService.providers.isLoading();
|
||||
}
|
||||
|
||||
@computed get providers(): AuthProvider[] {
|
||||
return this.authProviderService
|
||||
.providers
|
||||
.data
|
||||
.concat()
|
||||
.sort(this.compareProviders);
|
||||
}
|
||||
|
||||
readonly error = new GQLErrorCatcher();
|
||||
private isDistructed = false;
|
||||
private close!: () => void;
|
||||
|
||||
constructor(
|
||||
private notificationService: NotificationService,
|
||||
private authProviderService: AuthProviderService,
|
||||
private authInfoService: AuthInfoService,
|
||||
private commonDialogService: CommonDialogService,
|
||||
) { }
|
||||
|
||||
init(onClose: () => void) {
|
||||
this.close = onClose;
|
||||
this.loadProviders();
|
||||
}
|
||||
|
||||
destruct(): void {
|
||||
this.isDistructed = true;
|
||||
}
|
||||
|
||||
login = async () => {
|
||||
if (!this.provider || this.isAuthenticating) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isAuthenticating = true;
|
||||
try {
|
||||
await this.authInfoService.login(this.provider.id, this.credentials);
|
||||
this.close();
|
||||
} catch (exception) {
|
||||
if (!this.error.catch(exception) || this.isDistructed) {
|
||||
this.notificationService.logException(exception, 'Login failed');
|
||||
}
|
||||
} finally {
|
||||
this.isAuthenticating = false;
|
||||
}
|
||||
}
|
||||
|
||||
selectProvider = (providerId: string) => {
|
||||
if (providerId === this.provider?.id) {
|
||||
return;
|
||||
}
|
||||
this.provider = this.authProviderService
|
||||
.providers.data.find(provider => provider.id === providerId) || null;
|
||||
this.credentials = {};
|
||||
}
|
||||
|
||||
showDetails = () => {
|
||||
if (this.error.exception) {
|
||||
this.commonDialogService.open(ErrorDetailsDialog, this.error.exception);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadProviders() {
|
||||
try {
|
||||
await this.authProviderService.providers.load();
|
||||
if (this.providers.length > 0) {
|
||||
this.provider = this.providers[0];
|
||||
}
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load auth providers');
|
||||
}
|
||||
}
|
||||
|
||||
private compareProviders = (providerA: AuthProvider, providerB: AuthProvider): number => {
|
||||
if (providerA.defaultProvider === providerB.defaultProvider)
|
||||
{
|
||||
return providerA.label.localeCompare(providerB.label);
|
||||
}
|
||||
|
||||
if (providerA.defaultProvider) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Button } from '@dbeaver/core/blocks';
|
||||
import { useTranslate } from '@dbeaver/core/localization';
|
||||
|
||||
const styles = css`
|
||||
controls {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`;
|
||||
|
||||
export type Props = {
|
||||
isAuthenticating: boolean;
|
||||
onLogin(): void;
|
||||
}
|
||||
|
||||
export const AuthDialogFooter = observer(
|
||||
function AuthDialogFooter({
|
||||
isAuthenticating,
|
||||
onLogin,
|
||||
}: Props) {
|
||||
const translate = useTranslate();
|
||||
|
||||
return styled(styles)(
|
||||
<controls as="div">
|
||||
<Button
|
||||
type="button"
|
||||
mod={['unelevated']}
|
||||
onClick={onLogin}
|
||||
disabled={isAuthenticating}
|
||||
>
|
||||
{translate('authentication_login')}
|
||||
</Button>
|
||||
</controls>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { CommonDialogService } from '@dbeaver/core/dialogs';
|
||||
|
||||
import { AuthDialog } from './AuthDialog';
|
||||
|
||||
@injectable()
|
||||
export class AuthDialogService {
|
||||
constructor(
|
||||
private commonDialogService: CommonDialogService,
|
||||
) { }
|
||||
|
||||
async showLoginForm(persistent = false) {
|
||||
await this.commonDialogService.open(AuthDialog, null, { persistent });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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';
|
||||
import { useCallback } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { InputField } from '@dbeaver/core/blocks';
|
||||
import { useStyles } from '@dbeaver/core/theming';
|
||||
|
||||
import { AuthProvider } from '../../AuthProviderService';
|
||||
import { formStyles } from './formStyles';
|
||||
|
||||
type Props = {
|
||||
provider: AuthProvider;
|
||||
credentials: any;
|
||||
authenticate: boolean;
|
||||
}
|
||||
|
||||
export const AuthProviderForm = observer(function AuthProviderForm({
|
||||
provider,
|
||||
credentials,
|
||||
authenticate,
|
||||
}: Props) {
|
||||
const handleChange = useCallback((key: string, value: string) => {
|
||||
credentials[key] = value;
|
||||
}, [credentials]);
|
||||
|
||||
return styled(useStyles(formStyles))(
|
||||
<login-form as='div'>
|
||||
{provider.credentialParameters.map(parameter => parameter.user && (
|
||||
<group as="div" key={parameter.id}>
|
||||
<InputField
|
||||
type={parameter.encryption === 'none' ? 'text' : 'password'}
|
||||
name={parameter.id}
|
||||
value={credentials[parameter.id]}
|
||||
onChange={value => handleChange(parameter.id, value)}
|
||||
disabled={authenticate}
|
||||
mod='surface'
|
||||
>
|
||||
{parameter.displayName}
|
||||
</InputField>
|
||||
</group>
|
||||
))}
|
||||
</login-form>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { css } from 'reshadow';
|
||||
|
||||
export const formStyles = css`
|
||||
login-form {
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
group {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
margin: 0 12px;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { IconOrImage } from '@dbeaver/core/blocks';
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
import { composes, useStyles } from '@dbeaver/core/theming';
|
||||
|
||||
import { AuthInfoService } from './AuthInfoService';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
user {
|
||||
composes: theme-ripple from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
user {
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
IconOrImage {
|
||||
display: block;
|
||||
width: 24px;
|
||||
}
|
||||
user-name {
|
||||
display: block;
|
||||
line-height: initial;
|
||||
margin-left: 8px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const UserInfo = observer(function UserInfo() {
|
||||
const authInfoService = useService(AuthInfoService);
|
||||
|
||||
if (!authInfoService.userInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<user as='div'>
|
||||
<user-icon as="div">
|
||||
<IconOrImage icon='user' viewBox='0 0 28 28' />
|
||||
</user-icon>
|
||||
<user-name as='div'>{authInfoService.userInfo.userId}</user-name>
|
||||
</user>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { manifest } from './manifest';
|
||||
|
||||
export default manifest;
|
||||
|
||||
export * from './AuthenticationService';
|
||||
export * from './AuthInfoService';
|
||||
export * from './AuthProviderService';
|
||||
export * from './Dialog/AuthDialogService';
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { IServiceInjector, PluginManifest } from '@dbeaver/core/di';
|
||||
|
||||
import { AuthenticationService } from './AuthenticationService';
|
||||
import { AuthInfoService } from './AuthInfoService';
|
||||
import { AuthMenuService } from './AuthMenuService';
|
||||
import { AuthProviderService } from './AuthProviderService';
|
||||
import { Bootstrap } from './Bootstrap';
|
||||
import { AuthDialogService } from './Dialog/AuthDialogService';
|
||||
|
||||
export const manifest: PluginManifest = {
|
||||
info: {
|
||||
name: 'Authentication',
|
||||
},
|
||||
|
||||
providers: [
|
||||
AuthenticationService,
|
||||
AuthInfoService,
|
||||
AuthProviderService,
|
||||
AuthDialogService,
|
||||
AuthMenuService,
|
||||
],
|
||||
|
||||
async initialize(services: IServiceInjector) {
|
||||
services
|
||||
.resolveServiceByClass(Bootstrap)
|
||||
.bootstrap();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"declarationDir": "dist/types",
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"outDir": "dist/js"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"@dbeaver/**"
|
||||
],
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"@dbeaver/**"
|
||||
]
|
||||
}
|
||||
@@ -17,14 +17,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dbeaver/core": "^0.1.0",
|
||||
"@material/button": "^4.0.0",
|
||||
"@material/elevation": "^4.0.0",
|
||||
"@material/ripple": "^4.0.0",
|
||||
"@material/theme": "^4.0.0",
|
||||
"@material/typography": "^4.0.0",
|
||||
"graphql": "^14.5.8",
|
||||
"graphql-request": "^1.8.2",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.2.2",
|
||||
"react": "^16.13.1",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { MainMenuService, ConnectionDialogsService } from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { CommonDialogService } from '@dbeaver/core/dialogs';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
import { PermissionsService, EPermission } from '@dbeaver/core/root';
|
||||
|
||||
import { BasicConnectionService } from './BasicConnectionService';
|
||||
import { ConnectionDialog } from './ConnectionDialog/ConnectionDialog';
|
||||
@@ -17,13 +18,14 @@ import { ConnectionDialog } from './ConnectionDialog/ConnectionDialog';
|
||||
@injectable()
|
||||
export class BasicConnectionPluginBootstrap {
|
||||
|
||||
private hasPreconfiguredConnection = false;
|
||||
|
||||
constructor(private connectionDialogsService: ConnectionDialogsService,
|
||||
private mainMenuService: MainMenuService,
|
||||
private basicConnectionService: BasicConnectionService,
|
||||
private notificationService: NotificationService,
|
||||
private commonDialogService: CommonDialogService) {
|
||||
constructor(
|
||||
private connectionDialogsService: ConnectionDialogsService,
|
||||
private mainMenuService: MainMenuService,
|
||||
private basicConnectionService: BasicConnectionService,
|
||||
private commonDialogService: CommonDialogService,
|
||||
private notificationService: NotificationService,
|
||||
private permissionsService: PermissionsService
|
||||
) {
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
@@ -33,9 +35,10 @@ export class BasicConnectionPluginBootstrap {
|
||||
{
|
||||
id: 'mainMenuConnect',
|
||||
order: 2,
|
||||
title: 'Preconfigured',
|
||||
title: 'basicConnection_main_menu_item',
|
||||
onClick: () => this.openConnectionsDialog(),
|
||||
isDisabled: () => !this.hasPreconfiguredConnection,
|
||||
isHidden: () => !this.permissionsService.has(EPermission.public),
|
||||
isDisabled: () => !this.basicConnectionService.dbSources.data.length,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -46,11 +49,9 @@ export class BasicConnectionPluginBootstrap {
|
||||
|
||||
private async loadDbSources() {
|
||||
try {
|
||||
const sources = await this.basicConnectionService.loadDBSourcesAsync();
|
||||
this.hasPreconfiguredConnection = !!sources.length;
|
||||
await this.basicConnectionService.dbSources.load();
|
||||
} catch (error) {
|
||||
this.notificationService.logException(error, 'DBSources loading failed');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,30 +6,35 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Connection, DBSource, ConnectionsManagerService } from '@dbeaver/core/app';
|
||||
import {
|
||||
Connection, DBSource, ConnectionsManagerService
|
||||
} from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { ConnectionConfig, GraphQLService } from '@dbeaver/core/sdk';
|
||||
import { PermissionsService, EPermission } from '@dbeaver/core/root';
|
||||
import { ConnectionConfig, GraphQLService, CachedResource } from '@dbeaver/core/sdk';
|
||||
|
||||
@injectable()
|
||||
export class BasicConnectionService {
|
||||
private dbSourcesCache: DBSource[] = [];
|
||||
readonly dbSources = new CachedResource([], this.loadDBSourcesAsync.bind(this), data => !!data.length)
|
||||
|
||||
constructor(private graphQLService: GraphQLService,
|
||||
private connectionsManagerService: ConnectionsManagerService) {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private permissionsService: PermissionsService
|
||||
) {
|
||||
this.permissionsService.onUpdate.subscribe(() => this.dbSources.refresh());
|
||||
}
|
||||
|
||||
public getDBSources(): DBSource[] {
|
||||
return this.dbSourcesCache;
|
||||
return this.dbSources.data;
|
||||
}
|
||||
|
||||
async loadDBSourcesAsync(): Promise<DBSource[]> {
|
||||
if (this.dbSourcesCache.length > 0) {
|
||||
return this.dbSourcesCache;
|
||||
private async loadDBSourcesAsync(data: DBSource[]): Promise<DBSource[]> {
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { dataSourceList } = await this.graphQLService.gql.dataSourceList();
|
||||
this.dbSourcesCache = dataSourceList;
|
||||
return this.dbSourcesCache;
|
||||
return dataSourceList;
|
||||
}
|
||||
|
||||
async openConnectionAsync(config: ConnectionConfig): Promise<Connection> {
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
|
||||
|
||||
private async loadDBSources() {
|
||||
try {
|
||||
await this.basicConnectionService.loadDBSourcesAsync();
|
||||
await this.basicConnectionService.dbSources.load();
|
||||
await this.connectionsManagerService.loadDriversAsync();
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load database sources');
|
||||
|
||||
@@ -22,10 +22,8 @@ import { DBSourceSelector } from './DBSourceSelector/DBSourceSelector';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 330px;
|
||||
min-height: 330px;
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
}
|
||||
SubmittingForm, center {
|
||||
display: flex;
|
||||
|
||||
+2
@@ -15,8 +15,10 @@ import { useTranslate } from '@dbeaver/core/localization';
|
||||
const styles = css`
|
||||
controls {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
fill {
|
||||
|
||||
@@ -50,10 +50,6 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), {
|
||||
"@dbeaver/core/product": path.resolve(__dirname, "../../../core/src/product"),
|
||||
"@dbeaver/core/plugin": path.resolve(__dirname, "../../../core/src/plugin"),
|
||||
|
||||
// rewrite imports for unnecessary work in `web/src/libs/sdk.ts`
|
||||
'graphql-tag': path.resolve(__dirname, '../../src/fix-gql.js'),
|
||||
graphql: path.resolve(__dirname, '../../src/fix-gql.js'),
|
||||
|
||||
react: 'preact/compat',
|
||||
react$: 'preact/compat',
|
||||
'react-dom': 'preact/compat',
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* rewrite imports for unnecessary work in `webapp/packages/core/src/libs/sdk.ts`
|
||||
*/
|
||||
module.exports = strings => strings[0]
|
||||
module.exports.print = v => v
|
||||
@@ -12,6 +12,7 @@ module.exports = {
|
||||
basicConnection_connectionDialog_connecting: 'Connecting...',
|
||||
basicConnection_connectionDialog_connect: 'Connect',
|
||||
basicConnection_connectionDialog_connecting_message: 'Connecting to database...',
|
||||
basicConnection_main_menu_item: 'Preconfigured',
|
||||
|
||||
customConnection_connectionType_custom: 'Parameters',
|
||||
customConnection_connectionType_url: 'URL',
|
||||
@@ -42,6 +43,10 @@ module.exports = {
|
||||
|
||||
administration_menu_enter: 'Administration',
|
||||
|
||||
authentication_login_dialog_title: 'Authentication',
|
||||
authentication_login: 'Login',
|
||||
authentication_logout: 'Logout',
|
||||
|
||||
ui_stepper_back: 'Back',
|
||||
ui_processing_loading: 'Loading...',
|
||||
ui_processing_cancel: 'Cancel',
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
basicConnection_connectionDialog_connecting: 'Подключение...',
|
||||
basicConnection_connectionDialog_connect: 'Подключиться',
|
||||
basicConnection_connectionDialog_connecting_message: 'Подключение к базе...',
|
||||
basicConnection_main_menu_item: 'Подключиться',
|
||||
|
||||
customConnection_connectionType_custom: 'Параметры',
|
||||
customConnection_connectionType_url: 'URL',
|
||||
@@ -41,6 +42,10 @@ module.exports = {
|
||||
|
||||
administration_menu_enter: 'Администрирование',
|
||||
|
||||
authentication_login_dialog_title: 'Авторизация',
|
||||
authentication_login: 'Войти',
|
||||
authentication_logout: 'Выйти',
|
||||
|
||||
ui_stepper_back: 'Назад',
|
||||
ui_processing_loading: 'Загрузка...',
|
||||
ui_processing_cancel: 'Отменить',
|
||||
|
||||
@@ -36,19 +36,15 @@
|
||||
.__Tab {
|
||||
composes: theme-typography--body2 from global;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
|
||||
height: 48px;
|
||||
padding: 0 24px 0 12px;
|
||||
border-top: solid 2px transparent;
|
||||
|
||||
&:global([aria-selected=true]) {
|
||||
&:global([aria-selected="true"]) {
|
||||
font-weight: 500;
|
||||
cursor: auto;
|
||||
border-top-color: $color-negative;
|
||||
@@ -58,52 +54,64 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:global([aria-selected=true])) {
|
||||
&:not(:global([aria-selected="true"])) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
tab-icon {
|
||||
height: 22px;
|
||||
padding-right: 8px;
|
||||
tab-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
|
||||
& StaticImage,
|
||||
& img {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
& placeholder {
|
||||
width: 22px;
|
||||
tab-icon {
|
||||
height: 22px;
|
||||
padding-left: 12px;
|
||||
|
||||
StaticImage,
|
||||
img {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
placeholder {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tab-title placeholder {
|
||||
width: 80px;
|
||||
height: 16px;
|
||||
}
|
||||
tab-title {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
tab-action:before {
|
||||
content: "";
|
||||
display: block;
|
||||
left: -2px;
|
||||
top: -2px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
tab-title placeholder {
|
||||
width: 80px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
tab-action {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
align-self: flex-start;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
|
||||
& Icon {
|
||||
tab-action:before {
|
||||
content: "";
|
||||
display: block;
|
||||
left: -4px;
|
||||
top: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tab-action {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
align-self: flex-start;
|
||||
padding: 4px;
|
||||
padding-left: 0;
|
||||
|
||||
Icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,21 +5,28 @@ schema:
|
||||
- ../../../server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls
|
||||
- ../../../server/bundles/io.cloudbeaver.service.metadata/schema/service.metadata.graphqls
|
||||
- ../../../server/bundles/io.cloudbeaver.service.data.transfer/schema/service.data.transfer.graphqls
|
||||
- ../../../server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls
|
||||
documents:
|
||||
- ./src/**/*.gql
|
||||
generates:
|
||||
src/sdk/sdk.ts:
|
||||
plugins:
|
||||
- add: '/* eslint-disable */'
|
||||
- add: '/* eslint-disable max-len */'
|
||||
- typescript
|
||||
- typescript-operations
|
||||
- typescript-graphql-request
|
||||
config:
|
||||
skipTypename: true
|
||||
documentMode : 'string'
|
||||
avoidOptionals:
|
||||
field: false
|
||||
inputValue: false
|
||||
object: false
|
||||
maybeValue: T
|
||||
scalars:
|
||||
Object: any
|
||||
JSON: "{ [key: string]: any }"
|
||||
DateTime: any
|
||||
hooks:
|
||||
afterAllFileWrite:
|
||||
- prettier --write --config ../../.prettierrc.js
|
||||
- eslint --fix
|
||||
|
||||
@@ -4,14 +4,6 @@ const path = require('path');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
|
||||
module.exports = (env, argv) => merge(commonConfig(env, argv), {
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
// rewrite imports for unnecessary work in `web/src/libs/sdk.ts`
|
||||
'graphql-tag': path.join(__dirname, '../../fix-gql.js'),
|
||||
graphql: path.join(__dirname, '../../fix-gql.js'),
|
||||
},
|
||||
},
|
||||
context: path.resolve(__dirname, '../../src'),
|
||||
plugins: [
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* rewrite imports for unnecessary work in `webapp/packages/core/src/libs/sdk.ts`
|
||||
*/
|
||||
module.exports = strings => strings[0]
|
||||
module.exports.print = v => v
|
||||
@@ -35,8 +35,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"go-split": "^1.3.2",
|
||||
"graphql": "^14.5.8",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.2.2",
|
||||
"react": "^16.13.1",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
import {
|
||||
ConnectionDialogsService,
|
||||
ConnectionsManagerService,
|
||||
NodesManagerService,
|
||||
NavigationTabsService, NavigationTreeContextMenuService, LogViewerMenuService, ConnectionSchemaManagerService,
|
||||
} from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
@@ -19,6 +17,9 @@ import { LocalizationService } from '@dbeaver/core/localization';
|
||||
import { PermissionsService } from '@dbeaver/core/root';
|
||||
import { ThemeService } from '@dbeaver/core/theming';
|
||||
|
||||
import { SessionExpireService } from './dialogs';
|
||||
import { LocalizationService } from './localization';
|
||||
|
||||
/**
|
||||
* AppBootstrap.init() will be executed between first and second phase of App initialization,
|
||||
* when application has registered all dependencies but plugins did'nt initialized.
|
||||
@@ -28,19 +29,17 @@ import { ThemeService } from '@dbeaver/core/theming';
|
||||
@injectable()
|
||||
export class AppBootstrap {
|
||||
|
||||
constructor(private exceptionsCatcherService: ExceptionsCatcherService,
|
||||
private localizationService: LocalizationService,
|
||||
private themeService: ThemeService,
|
||||
private connectionDialogService: ConnectionDialogsService,
|
||||
private connectionsManager: ConnectionsManagerService,
|
||||
private logViewerMenuService: LogViewerMenuService,
|
||||
private sessionExpireService: SessionExpireService,
|
||||
private navigationTreeContextMenuService: NavigationTreeContextMenuService,
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private navigationTabsService: NavigationTabsService,
|
||||
private connectionSchemaManagerService: ConnectionSchemaManagerService,
|
||||
private permissionsService: PermissionsService) {
|
||||
}
|
||||
constructor(
|
||||
private exceptionsCatcherService: ExceptionsCatcherService,
|
||||
private localizationService: LocalizationService,
|
||||
private themeService: ThemeService,
|
||||
private connectionDialogService: ConnectionDialogsService,
|
||||
private logViewerMenuService: LogViewerMenuService,
|
||||
private sessionExpireService: SessionExpireService,
|
||||
private navigationTreeContextMenuService: NavigationTreeContextMenuService,
|
||||
private navigationTabsService: NavigationTabsService,
|
||||
private connectionSchemaManagerService: ConnectionSchemaManagerService,
|
||||
) { }
|
||||
|
||||
async init() {
|
||||
this.exceptionsCatcherService.subscribe();
|
||||
@@ -48,16 +47,11 @@ export class AppBootstrap {
|
||||
|
||||
await this.localizationService.init();
|
||||
await this.themeService.init();
|
||||
await this.permissionsService.update();
|
||||
|
||||
this.connectionSchemaManagerService.registerCallbacks();
|
||||
this.navigationTreeContextMenuService.registerMenuItems();
|
||||
this.connectionDialogService.registerMenuItems();
|
||||
this.logViewerMenuService.registerMenuItems();
|
||||
|
||||
await this.connectionsManager.restoreConnections();
|
||||
|
||||
await this.nodesManagerService.updateRootChildren();
|
||||
}
|
||||
|
||||
async doAfterPluginsInit() {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { usePermission, EPermission } from '@dbeaver/core/root';
|
||||
|
||||
import { useChildren } from '../shared/useChildren';
|
||||
import { NavigationTreeNode } from './NavigationTreeNode/NavigationTreeNode';
|
||||
|
||||
@@ -38,6 +40,11 @@ const navigationTreeStyles = css`
|
||||
|
||||
export const NavigationTree = observer(function NavigationTree() {
|
||||
const nodeChildren = useChildren();
|
||||
const isEnabled = usePermission(EPermission.public);
|
||||
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nodeChildren || nodeChildren.children.length === 0) {
|
||||
return styled(navigationTreeStyles)(
|
||||
|
||||
+2
@@ -15,8 +15,10 @@ export const styles = css`
|
||||
}
|
||||
controls {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
Button {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 styled, { css } from 'reshadow';
|
||||
|
||||
const styles = css`
|
||||
fill {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export function Fill() {
|
||||
return styled(styles)(<fill as='div' />);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Icon } from '@dbeaver/core/blocks';
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
import { ServerService } from '@dbeaver/core/root';
|
||||
|
||||
declare const version: string; // declared in webpack DefinePlugin // todo move to enviroment?
|
||||
|
||||
const styles = css`
|
||||
logo {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 16px;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
Icon {
|
||||
height: 24px;
|
||||
width: auto;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Logo = observer(function Logo() {
|
||||
const serverService = useService(ServerService);
|
||||
const title = `Frontend: ${version}\nBackend: ${serverService.config.data?.version}`;
|
||||
|
||||
return styled(styles)(
|
||||
<logo as="div" title={title}>
|
||||
<Icon name="logo" viewBox="0 0 361 73" />
|
||||
</logo>
|
||||
);
|
||||
});
|
||||
@@ -25,7 +25,6 @@ export const settingsMenuStyles = css`
|
||||
}
|
||||
menu-panel-item {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
& menu-item-text {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -6,34 +6,21 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { Icon } from '@dbeaver/core/blocks';
|
||||
import { Placeholder } from '@dbeaver/core/blocks';
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
import { ServerService } from '@dbeaver/core/root';
|
||||
import { useStyles } from '@dbeaver/core/theming';
|
||||
|
||||
import { ConnectionSelector } from './ConnectionSchemaManager/ConnectionSelector/ConnectionSelector';
|
||||
import { MainMenu } from './MainMenu/MainMenu';
|
||||
import { SettingsMenu } from './SettingsMenu/SettingsMenu';
|
||||
import { TopNavService } from './TopNavBarService';
|
||||
import { topNavBarStyles } from './topNavBarStyles';
|
||||
|
||||
|
||||
declare const version: string; // declared in webpack DefinePlugin // todo move to enviroment?
|
||||
|
||||
export const TopNavBar = observer(function TopNavBar() {
|
||||
const serverService = useService(ServerService);
|
||||
const title = `Frontend: ${version}\nBackend: ${serverService.config.data?.version}`;
|
||||
export function TopNavBar() {
|
||||
const topNavBarService = useService(TopNavService);
|
||||
|
||||
return styled(useStyles(topNavBarStyles))(
|
||||
<header>
|
||||
<logo as="div" title={title}>
|
||||
<Icon name="logo" viewBox="0 0 361 73" />
|
||||
</logo>
|
||||
<MainMenu />
|
||||
<ConnectionSelector/>
|
||||
<SettingsMenu />
|
||||
<Placeholder container={topNavBarService.placeholder} />
|
||||
</header>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { PlaceholderContainer } from '@dbeaver/core/blocks';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
|
||||
import { ConnectionSelector } from './ConnectionSchemaManager/ConnectionSelector/ConnectionSelector';
|
||||
import { Fill } from './Fill';
|
||||
import { Logo } from './Logo';
|
||||
import { MainMenu } from './MainMenu/MainMenu';
|
||||
import { SettingsMenu } from './SettingsMenu/SettingsMenu';
|
||||
|
||||
@injectable()
|
||||
export class TopNavService {
|
||||
readonly placeholder = new PlaceholderContainer();
|
||||
|
||||
constructor() {
|
||||
this.placeholder.add(Logo, 0);
|
||||
this.placeholder.add(MainMenu, 1);
|
||||
this.placeholder.add(ConnectionSelector, 2);
|
||||
this.placeholder.add(Fill, 3);
|
||||
this.placeholder.add(SettingsMenu, 4);
|
||||
}
|
||||
}
|
||||
@@ -25,18 +25,5 @@ export const topNavBarStyles = composes(
|
||||
padding: 0 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
logo {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 16px;
|
||||
width: 250px;
|
||||
|
||||
& Icon {
|
||||
height: 24px;
|
||||
width: auto;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Services
|
||||
export * from './TopNavBar/MainMenu/MainMenuService';
|
||||
export * from './TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService';
|
||||
export * from './TopNavBar/SettingsMenu/SettingsMenuService';
|
||||
export * from './TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService';
|
||||
export * from './TopNavBar/TopNavBarService';
|
||||
export * from './shared/ConnectionsManager/ConnectionsManagerService';
|
||||
export * from './shared/ConnectionsManager/ConnectionDialogsService';
|
||||
export * from './shared/ConnectionsManager/extensions/IConnectionProvider';
|
||||
|
||||
+24
-6
@@ -65,9 +65,12 @@ export class ConnectionsManagerService {
|
||||
onOpenConnection = new Subject<Connection>();
|
||||
onCloseConnection = new Subject<string>();
|
||||
|
||||
constructor(private graphQLService: GraphQLService,
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private sessionService: SessionService) {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private sessionService: SessionService
|
||||
) {
|
||||
this.sessionService.onUpdate.subscribe(this.restoreConnections.bind(this));
|
||||
}
|
||||
|
||||
getDBDrivers(): Map<string, DBDriver> {
|
||||
@@ -114,9 +117,8 @@ export class ConnectionsManagerService {
|
||||
}
|
||||
|
||||
async closeConnectionAsync(id: string, skipNodesRefresh?: boolean): Promise<void> {
|
||||
await this.nodesManagerService.closeConnection(id);
|
||||
this.onCloseConnection.next(id);
|
||||
await this.graphQLService.gql.closeConnection({ id });
|
||||
await this.afterConnectionClose(id);
|
||||
this.connectionsMap.delete(id);
|
||||
|
||||
if (!skipNodesRefresh) {
|
||||
@@ -129,16 +131,29 @@ export class ConnectionsManagerService {
|
||||
return data.get(connectionId)!;
|
||||
}
|
||||
|
||||
async restoreConnections() {
|
||||
private async afterConnectionClose(id: string) {
|
||||
await this.nodesManagerService.closeConnection(id);
|
||||
this.onCloseConnection.next(id);
|
||||
}
|
||||
|
||||
private async restoreConnections() {
|
||||
const config = await this.sessionService.session.load();
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let connectionsToRemove = this.connections.concat();
|
||||
// TODO: connections must be string[]
|
||||
for (const connection of config.connections) {
|
||||
await this.restoreConnection(connection);
|
||||
connectionsToRemove = connectionsToRemove.filter(({ id }) => id !== connection.id);
|
||||
}
|
||||
|
||||
for (const connection of connectionsToRemove) {
|
||||
await this.afterConnectionClose(connection.id);
|
||||
this.connectionsMap.delete(connection.id);
|
||||
}
|
||||
|
||||
await this.nodesManagerService.updateRootChildren();
|
||||
}
|
||||
|
||||
@@ -187,6 +202,9 @@ export class ConnectionsManagerService {
|
||||
// TODO: Must be loaded based on connection id
|
||||
// const { connection } = await this.graphQLService.gql.connectionState({ id });
|
||||
|
||||
if (this.connectionsMap.has(connection.id)) {
|
||||
return;
|
||||
}
|
||||
this.connectionsMap.set(connection.id, connection);
|
||||
this.onOpenConnection.next(connection);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@ import { useStyles } from '@dbeaver/core/theming';
|
||||
const styles = css`
|
||||
controls {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
Button {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { PermissionsService, EPermission } from '@dbeaver/core/root';
|
||||
import { DatabaseObjectInfo, GraphQLService } from '@dbeaver/core/sdk';
|
||||
|
||||
import { INavigator } from '../Navigation/INavigator';
|
||||
@@ -51,7 +52,8 @@ export class NodesManagerService {
|
||||
private nodesStore = new NodesStore();
|
||||
|
||||
constructor(private graphQLService: GraphQLService,
|
||||
private navigationService: NavigationService) {
|
||||
private navigationService: NavigationService,
|
||||
private permissionsService: PermissionsService) {
|
||||
|
||||
this.navigator = this.navigationService.createNavigator<INodeNavigationData>(
|
||||
data => data.nodeId,
|
||||
@@ -134,7 +136,10 @@ export class NodesManagerService {
|
||||
}
|
||||
|
||||
async updateRootChildren() {
|
||||
return this.updateChildren(ROOT_NODE_PATH);
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
return;
|
||||
}
|
||||
this.updateChildren(ROOT_NODE_PATH);
|
||||
}
|
||||
|
||||
async updateChildrenDatabaseObjectInfo(nodeId: string) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { action, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
import { PermissionsService, EPermission } from '@dbeaver/core/root';
|
||||
import { GraphQLService } from '@dbeaver/core/sdk';
|
||||
import { CoreSettingsService } from '@dbeaver/core/settings';
|
||||
import { uuid } from '@dbeaver/core/utils';
|
||||
@@ -19,23 +20,29 @@ import { ILogEntry } from './ILogEntry';
|
||||
@injectable()
|
||||
export class LogViewerService {
|
||||
|
||||
@observable isActive = false;
|
||||
@observable _isActive = false;
|
||||
|
||||
get isActive() {
|
||||
return this._isActive;
|
||||
}
|
||||
|
||||
@observable private log: ILogEntry[] = [];
|
||||
private interval: any = null;
|
||||
|
||||
constructor(private graphQLService: GraphQLService,
|
||||
private coreSettingsService: CoreSettingsService,
|
||||
private notificationService: NotificationService) {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private coreSettingsService: CoreSettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private permissionsService: PermissionsService,
|
||||
) {
|
||||
this.permissionsService.onUpdate.subscribe(this.stopIfHasNoPermission.bind(this));
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.isActive) {
|
||||
if (this._isActive) {
|
||||
this.stopLog();
|
||||
this.isActive = false;
|
||||
} else {
|
||||
this.startLog();
|
||||
this.isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +51,13 @@ export class LogViewerService {
|
||||
}
|
||||
|
||||
async startLog() {
|
||||
if (this.interval) {
|
||||
if (this._isActive) {
|
||||
return;
|
||||
}
|
||||
if (!this.isLogViewerAvailable()) {
|
||||
throw new Error('Access denied');
|
||||
}
|
||||
this._isActive = true;
|
||||
await this.updateLog();
|
||||
const refreshInterval = this.coreSettingsService.settings.getValue('app.logViewer.refreshInterval');
|
||||
this.interval = setInterval(() => {
|
||||
@@ -59,6 +70,7 @@ export class LogViewerService {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
this._isActive = false;
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -66,24 +78,6 @@ export class LogViewerService {
|
||||
this.log = [];
|
||||
}
|
||||
|
||||
private async updateLog() {
|
||||
try {
|
||||
const newEntries = await this.loadLog();
|
||||
this.addNewEntries(newEntries);
|
||||
} catch (e) {
|
||||
this.notificationService.logException(e, 'Failed to load log');
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
addNewEntries(entries: ILogEntry[]) {
|
||||
this.log.unshift(...entries.reverse());
|
||||
const maxLogEntries = this.coreSettingsService.settings.getValue('app.logViewer.maxLogEntries');
|
||||
if (this.log.length > maxLogEntries) {
|
||||
this.log.splice(maxLogEntries, this.log.length - maxLogEntries);
|
||||
}
|
||||
}
|
||||
|
||||
async loadLog(): Promise<ILogEntry[]> {
|
||||
const maxLogEntries = this.coreSettingsService.settings.getValue('app.logViewer.maxLogEntries');
|
||||
const { log } = await this.graphQLService.gql.readSessionLog({
|
||||
@@ -96,4 +90,36 @@ export class LogViewerService {
|
||||
}));
|
||||
return entries;
|
||||
}
|
||||
|
||||
@action
|
||||
private addNewEntries(entries: ILogEntry[]) {
|
||||
this.log.unshift(...entries.reverse());
|
||||
const maxLogEntries = this.coreSettingsService.settings.getValue('app.logViewer.maxLogEntries');
|
||||
if (this.log.length > maxLogEntries) {
|
||||
this.log.splice(maxLogEntries, this.log.length - maxLogEntries);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateLog() {
|
||||
if (!this.isLogViewerAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newEntries = await this.loadLog();
|
||||
this.addNewEntries(newEntries);
|
||||
} catch (e) {
|
||||
this.notificationService.logException(e, 'Failed to load log');
|
||||
}
|
||||
}
|
||||
|
||||
private stopIfHasNoPermission() {
|
||||
if (this._isActive && !this.isLogViewerAvailable()) {
|
||||
this.stopLog();
|
||||
}
|
||||
}
|
||||
|
||||
private isLogViewerAvailable() {
|
||||
return this.permissionsService.has(EPermission.public);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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';
|
||||
|
||||
import { PlaceholderContainer } from './PlaceholderContainer';
|
||||
|
||||
type Props<T = unknown> = T extends unknown ? {
|
||||
container: PlaceholderContainer<T>;
|
||||
context?: T;
|
||||
} : {
|
||||
container: PlaceholderContainer<T>;
|
||||
context: T;
|
||||
}
|
||||
|
||||
export const Placeholder = observer(function Placeholder<T = unknown>({ container, context }: Props<T>) {
|
||||
return (
|
||||
<>
|
||||
{container.get().map(({ id, component: Component }) => <Component key={id} context={context!} />)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { observable } from 'mobx';
|
||||
|
||||
import { uuid } from '@dbeaver/core/utils';
|
||||
|
||||
export type PlaceholderProps<T = unknown> = {
|
||||
context: T;
|
||||
}
|
||||
|
||||
export type PlaceholderComponent<T = unknown> = React.FunctionComponent<PlaceholderProps<T>>
|
||||
|
||||
export type PlaceholderElement<T = unknown> = {
|
||||
id: string;
|
||||
component: PlaceholderComponent<T>;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export class PlaceholderContainer<T = unknown> {
|
||||
private placeholders = observable<PlaceholderElement<T>>([], { deep: false });
|
||||
|
||||
get() {
|
||||
return this.placeholders;
|
||||
}
|
||||
|
||||
add(component: PlaceholderComponent<T>, order?: number) {
|
||||
const placeholder: PlaceholderElement<T> = {
|
||||
id: uuid(),
|
||||
component,
|
||||
order,
|
||||
};
|
||||
|
||||
if (!order) {
|
||||
this.placeholders.push(placeholder);
|
||||
return;
|
||||
}
|
||||
|
||||
this.placeholders.splice(this.findPosition(order), 0, placeholder);
|
||||
}
|
||||
|
||||
private findPosition(order: number) {
|
||||
const position = this.placeholders
|
||||
.findIndex(placeholder => placeholder.order === undefined || order <= placeholder.order);
|
||||
|
||||
if (position === -1) {
|
||||
return this.placeholders.length;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
export interface IProperty {
|
||||
id: string;
|
||||
name: string;
|
||||
key: string;
|
||||
displayName?: string;
|
||||
defaultValue?: string;
|
||||
description?: string;
|
||||
validValues?: string[];
|
||||
|
||||
@@ -26,7 +26,7 @@ type PropertiesState = {
|
||||
type PropertiesTableProps = {
|
||||
properties: IProperty[];
|
||||
propertiesState?: PropertiesState;
|
||||
onNameChange?(id: string, name: string): void;
|
||||
onKeyChange?(id: string, name: string): void;
|
||||
onChange?(state: PropertiesState): void;
|
||||
onAdd?(): void;
|
||||
onRemove?(id: string): void;
|
||||
@@ -36,7 +36,7 @@ type PropertiesTableProps = {
|
||||
export const PropertiesTable = observer(function PropertiesTable({
|
||||
propertiesState,
|
||||
properties,
|
||||
onNameChange,
|
||||
onKeyChange,
|
||||
onChange,
|
||||
onAdd,
|
||||
onRemove,
|
||||
@@ -44,22 +44,22 @@ export const PropertiesTable = observer(function PropertiesTable({
|
||||
}: PropertiesTableProps) {
|
||||
const translate = useTranslate();
|
||||
const state = useLocalStore<PropertiesState>(() => (propertiesState || {}));
|
||||
const changeName = useCallback((id: string, name: string) => {
|
||||
const changeName = useCallback((id: string, key: string) => {
|
||||
const property = properties.find(property => property.id === id);
|
||||
|
||||
if (!property) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state[property.name] !== undefined) {
|
||||
state[name] = state[property.name];
|
||||
delete state[property.name];
|
||||
if (state[property.key] !== undefined) {
|
||||
state[key] = state[property.key];
|
||||
delete state[property.key];
|
||||
}
|
||||
|
||||
if (onNameChange) {
|
||||
onNameChange(property.name, name);
|
||||
if (onKeyChange) {
|
||||
onKeyChange(property.key, key);
|
||||
}
|
||||
property.name = name;
|
||||
property.key = key;
|
||||
}, [properties]);
|
||||
|
||||
const changeValue = useCallback((id: string, value: string) => {
|
||||
@@ -69,7 +69,7 @@ export const PropertiesTable = observer(function PropertiesTable({
|
||||
return;
|
||||
}
|
||||
|
||||
state[property.name] = value;
|
||||
state[property.key] = value;
|
||||
|
||||
if (onChange) {
|
||||
onChange(state);
|
||||
@@ -83,8 +83,8 @@ export const PropertiesTable = observer(function PropertiesTable({
|
||||
return;
|
||||
}
|
||||
|
||||
if (state[property.name] !== undefined) {
|
||||
delete state[property.name];
|
||||
if (state[property.key] !== undefined) {
|
||||
delete state[property.key];
|
||||
}
|
||||
|
||||
if (onRemove) {
|
||||
@@ -93,8 +93,8 @@ export const PropertiesTable = observer(function PropertiesTable({
|
||||
properties.splice(properties.indexOf(property), 1);
|
||||
}, [properties, onRemove]);
|
||||
|
||||
const isNameUnique = useCallback(
|
||||
(name: string) => properties.filter(property => property.name === name).length === 1,
|
||||
const isKeyUnique = useCallback(
|
||||
(key: string) => properties.filter(property => property.key === key).length === 1,
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -116,11 +116,11 @@ export const PropertiesTable = observer(function PropertiesTable({
|
||||
<PropertyItem
|
||||
key={property.id}
|
||||
property={property}
|
||||
value={state[property.name]}
|
||||
value={state[property.key]}
|
||||
onNameChange={changeName}
|
||||
onValueChange={changeValue}
|
||||
onRemove={removeProperty}
|
||||
error={!isNameUnique(property.name)}
|
||||
error={!isKeyUnique(property.key)}
|
||||
/>
|
||||
))}
|
||||
</properties-list>
|
||||
|
||||
@@ -80,7 +80,7 @@ const styles = composes(
|
||||
&[|edited] {
|
||||
font-weight: 600;
|
||||
}
|
||||
&:not(:focus):not([|focus]) {
|
||||
&:global([readonly]), &:not(:focus):not([|focus]) {
|
||||
background: transparent;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ const styles = composes(
|
||||
`
|
||||
);
|
||||
|
||||
type PropertyItemProps = {
|
||||
type Props = {
|
||||
property: IProperty;
|
||||
value?: string;
|
||||
onNameChange(staticId: string, newId: string): void;
|
||||
@@ -121,12 +121,12 @@ export const PropertyItem = observer(function PropertyItem({
|
||||
onValueChange,
|
||||
onRemove,
|
||||
error,
|
||||
}: PropertyItemProps) {
|
||||
const isEditable = property.name !== property.id;
|
||||
}: Props) {
|
||||
const isKeyEditable = !property.displayName;
|
||||
const edited = value !== undefined && value !== property.defaultValue;
|
||||
const [focus, setFocus] = useState(false);
|
||||
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||
const handleNameChange = useCallback((value: string) => onNameChange(property.id, value), [property]);
|
||||
const keyInputRef = useRef<HTMLInputElement>(null);
|
||||
const handleKeyChange = useCallback((key: string) => onNameChange(property.id, key), [property]);
|
||||
const handleValueChange = useCallback(
|
||||
(value: string) => onValueChange(property.id, value),
|
||||
[property]
|
||||
@@ -134,8 +134,8 @@ export const PropertyItem = observer(function PropertyItem({
|
||||
const handleRemove = useCallback(() => onRemove(property.id), [property]);
|
||||
|
||||
useEffect(() => {
|
||||
if (nameInputRef.current && isEditable) {
|
||||
nameInputRef.current.focus();
|
||||
if (keyInputRef.current && isKeyEditable) {
|
||||
keyInputRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -145,12 +145,12 @@ export const PropertyItem = observer(function PropertyItem({
|
||||
<ShadowInput
|
||||
type='text'
|
||||
name={property.id}
|
||||
onChange={handleNameChange}
|
||||
ref={nameInputRef}
|
||||
readOnly={!isEditable}
|
||||
onChange={handleKeyChange}
|
||||
ref={keyInputRef}
|
||||
readOnly={!isKeyEditable}
|
||||
autoComplete='none'
|
||||
>
|
||||
{property.name || property.id}
|
||||
{property.displayName || property.key}
|
||||
</ShadowInput>
|
||||
</property-name>
|
||||
<property-value as='div'>
|
||||
@@ -176,7 +176,7 @@ export const PropertyItem = observer(function PropertyItem({
|
||||
</DriverPropertyValueSelector>
|
||||
</property-select>
|
||||
)}
|
||||
{isEditable && (
|
||||
{isKeyEditable && (
|
||||
<property-remove as="div">
|
||||
<button type="button" onClick={handleRemove}><Icon name="reject" viewBox="0 0 11 11" /></button>
|
||||
</property-remove>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { TabsContext } from '../TabsContext';
|
||||
type TabProps = PropsWithChildren<{
|
||||
tabId: string;
|
||||
className?: string;
|
||||
onOpen: (tabId: string) => void;
|
||||
onOpen?: (tabId: string) => void;
|
||||
onClose?: (tabId: string) => void;
|
||||
}>
|
||||
|
||||
@@ -36,8 +36,10 @@ export function Tab({
|
||||
|
||||
const handleOpen = useCallback((e: React.MouseEvent<any>) => {
|
||||
e.preventDefault();
|
||||
onOpen(tabId);
|
||||
}, []);
|
||||
if (onOpen) {
|
||||
onOpen(tabId);
|
||||
}
|
||||
}, [onOpen]);
|
||||
|
||||
const handleClose = useCallback((e: React.MouseEvent<any>) => {
|
||||
e.preventDefault();
|
||||
@@ -54,12 +56,14 @@ export function Tab({
|
||||
className={className}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
{children}
|
||||
{onClose && (
|
||||
<tab-action as="div" onClick={handleClose}>
|
||||
<Icon name="cross-bold" viewBox="0 0 7 8"/>
|
||||
</tab-action>
|
||||
)}
|
||||
<tab-container as='div'>
|
||||
{children}
|
||||
{onClose && (
|
||||
<tab-action as="div" onClick={handleClose}>
|
||||
<Icon name="cross-bold" viewBox="0 0 7 8"/>
|
||||
</tab-action>
|
||||
)}
|
||||
</tab-container>
|
||||
</BaseTab>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { useContext } from 'react';
|
||||
import { TabList as BaseTabList, TabListOptions } from 'reakit/Tab';
|
||||
|
||||
import { TabsContext } from './TabsContext';
|
||||
|
||||
export function TabList(props: React.PropsWithChildren<TabListOptions>) {
|
||||
const state = useContext(TabsContext);
|
||||
|
||||
if (!state) {
|
||||
throw new Error('Tabs context was not provided');
|
||||
}
|
||||
|
||||
return <BaseTabList {...props} {...state} />;
|
||||
}
|
||||
@@ -6,15 +6,12 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
useMemo, PropsWithChildren, ReactNode, useEffect,
|
||||
} from 'react';
|
||||
import { useTabState } from 'reakit/Tab';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { useStyles, Style } from '@dbeaver/core/theming';
|
||||
|
||||
import { TabsContext } from '../TabsContext';
|
||||
import { TabsState } from '../TabsState';
|
||||
|
||||
type TabsBoxProps = PropsWithChildren<{
|
||||
currentTabId: string;
|
||||
@@ -30,16 +27,9 @@ export function TabsBox({
|
||||
className,
|
||||
style = [],
|
||||
}: TabsBoxProps) {
|
||||
const state = useTabState({
|
||||
selectedId: currentTabId,
|
||||
manual: true,
|
||||
});
|
||||
state.selectedId = currentTabId;
|
||||
useEffect(() => state.select(currentTabId), [currentTabId]); // hack currentId and selectedId not works
|
||||
const value = useMemo(() => state, Object.values(state));
|
||||
|
||||
return styled(useStyles(...style))(
|
||||
<TabsContext.Provider value={value}>
|
||||
<TabsState currentTabId={currentTabId}>
|
||||
<tabs-box as="div" className={className}>
|
||||
{tabs && (
|
||||
<tabs as="div">
|
||||
@@ -50,6 +40,6 @@ export function TabsBox({
|
||||
{children}
|
||||
</tab-panels>
|
||||
</tabs-box>
|
||||
</TabsContext.Provider>
|
||||
</TabsState>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { useEffect, useMemo } from 'react';
|
||||
import { useTabState } from 'reakit/Tab';
|
||||
|
||||
import { TabsContext } from './TabsContext';
|
||||
|
||||
type Props = React.PropsWithChildren<{
|
||||
selectedId?: string;
|
||||
currentTabId?: string | null;
|
||||
}>
|
||||
|
||||
export function TabsState({ selectedId, currentTabId, children }: Props) {
|
||||
const state = useTabState({
|
||||
selectedId: selectedId || currentTabId,
|
||||
manual: true,
|
||||
});
|
||||
if (currentTabId) {
|
||||
state.selectedId = currentTabId;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!currentTabId) {
|
||||
return;
|
||||
}
|
||||
state.select(currentTabId);
|
||||
}, [currentTabId]); // hack currentId and selectedId not works
|
||||
const value = useMemo(() => state, Object.values(state));
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={value}>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
export * from './Tab';
|
||||
export * from './TabPanel';
|
||||
export * from './TabsContext';
|
||||
export * from './TabsState';
|
||||
export * from './TabList';
|
||||
|
||||
export * from './ITab';
|
||||
|
||||
|
||||
@@ -18,3 +18,5 @@ export * from './SanitizedHTML/SanitizedHTML';
|
||||
export * from './ItemList';
|
||||
export * from './PropertiesTable';
|
||||
export * from './IconButton';
|
||||
export * from './Placeholder/Placeholder';
|
||||
export * from './Placeholder/PlaceholderContainer';
|
||||
|
||||
@@ -10,8 +10,13 @@ import { observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
|
||||
export type DialogOptions = {
|
||||
persistent?: boolean;
|
||||
}
|
||||
|
||||
export type DialogComponentProps<TPayload, TResult> = {
|
||||
payload: TPayload;
|
||||
options?: DialogOptions;
|
||||
resolveDialog(result: TResult | null): void;
|
||||
rejectDialog(): void; // the dialog was closed by cancel button or backdrop click
|
||||
}
|
||||
@@ -23,6 +28,7 @@ export type DialogComponent<TPayload, TResult> = React.ElementType<
|
||||
export interface DialogInternal {
|
||||
component: DialogComponent<any, any>;
|
||||
payload: any;
|
||||
options?: DialogOptions;
|
||||
resolve(result: any): void;
|
||||
}
|
||||
|
||||
@@ -32,14 +38,16 @@ export class CommonDialogService {
|
||||
|
||||
// note that if dialog is closed by user it will be resolved with null
|
||||
async open<TPayload, TResult>(
|
||||
dialog: DialogComponent<TPayload, TResult>,
|
||||
payload: TPayload
|
||||
component: DialogComponent<TPayload, TResult>,
|
||||
payload: TPayload,
|
||||
options?: DialogOptions,
|
||||
): Promise<TResult | null> {
|
||||
return new Promise<TResult>((resolve, reject) => {
|
||||
const dialogInternal: DialogInternal = {
|
||||
component: dialog,
|
||||
component,
|
||||
payload,
|
||||
resolve,
|
||||
options,
|
||||
};
|
||||
this.dialogs.push(dialogInternal);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { composes, useStyles } from '@dbeaver/core/theming';
|
||||
|
||||
export type CommonDialogWrapperProps = {
|
||||
title: string;
|
||||
onReject: () => any;
|
||||
onReject?: () => void;
|
||||
className?: string;
|
||||
noBodyPadding?: boolean;
|
||||
footer?: JSX.Element | boolean;
|
||||
@@ -39,6 +39,7 @@ const style = composes(
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
height: auto;
|
||||
min-width: 748px;
|
||||
max-height: 100%;
|
||||
border-radius: 0.25rem;
|
||||
@@ -54,6 +55,8 @@ const style = composes(
|
||||
}
|
||||
dialog-body {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 18px 24px;
|
||||
@@ -80,6 +83,7 @@ const style = composes(
|
||||
}
|
||||
footer {
|
||||
composes: theme-elevation-z10 from global;
|
||||
display: flex;
|
||||
z-index: 0;
|
||||
box-sizing: border-box;
|
||||
min-height: 72px;
|
||||
@@ -99,17 +103,19 @@ export function CommonDialogWrapper({
|
||||
}: CommonDialogWrapperProps) {
|
||||
|
||||
return styled(useStyles(style))(
|
||||
<dialog>
|
||||
<dialog className={className}>
|
||||
<header>
|
||||
<header-title as="div">
|
||||
<h1>{title}</h1>
|
||||
<reject as="div">
|
||||
<Icon name="cross" viewBox="0 0 16 16" onClick={onReject} />
|
||||
</reject>
|
||||
{onReject && (
|
||||
<reject as="div">
|
||||
<Icon name="cross" viewBox="0 0 16 16" onClick={onReject} />
|
||||
</reject>
|
||||
)}
|
||||
</header-title>
|
||||
{header}
|
||||
</header>
|
||||
<dialog-body as="div" className={className} {...use({ noPadding: noBodyPadding })}>{children}</dialog-body>
|
||||
<dialog-body as="div" {...use({ noPadding: noBodyPadding })}>{children}</dialog-body>
|
||||
<footer>
|
||||
{footer}
|
||||
</footer>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DialogBackdrop,
|
||||
useDialogState,
|
||||
} from 'reakit/Dialog';
|
||||
import { Portal } from 'reakit/Portal';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
@@ -63,18 +64,26 @@ function NestedDialog(props: NestedDialogType) {
|
||||
(result: any) => props.resolveDialog(props.dialog, result),
|
||||
[props.dialog, props.resolveDialog]
|
||||
);
|
||||
const backdropClickCallback = useCallback(() => {
|
||||
if (!props.dialog.options?.persistent) {
|
||||
handleReject();
|
||||
}
|
||||
}, [props.dialog.options?.persistent, handleReject]);
|
||||
|
||||
const DialogComponent = props.dialog.component;
|
||||
|
||||
// TODO: place Dialog inside CommonDialogWrapper, so we can pass aria-label
|
||||
return styled(styles)(
|
||||
<Dialog {...dialogState} aria-label="can't be provided" tabIndex={0}>
|
||||
<DialogBackdrop {...dialogState} onClick={handleReject} />
|
||||
<DialogComponent
|
||||
payload={props.dialog.payload}
|
||||
resolveDialog={handleResolve}
|
||||
rejectDialog={handleReject}
|
||||
/>
|
||||
</Dialog>
|
||||
<>
|
||||
<Portal><DialogBackdrop {...dialogState} onClick={backdropClickCallback} /></Portal>
|
||||
<Dialog {...dialogState} aria-label="can't be provided" tabIndex={0} >
|
||||
<DialogComponent
|
||||
payload={props.dialog.payload}
|
||||
options={props.dialog.options}
|
||||
resolveDialog={handleResolve}
|
||||
rejectDialog={handleReject}
|
||||
/>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,24 +11,23 @@ import { css } from 'reshadow';
|
||||
export const dialogStyles = css`
|
||||
DialogBackdrop {
|
||||
box-sizing: border-box;
|
||||
background-color: #0000007a;
|
||||
background-color: rgba(0,0,0,0.48);
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
Dialog {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
padding: 54px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
max-height: calc(100vh);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 999;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -26,9 +26,11 @@ const styles = css`
|
||||
}
|
||||
controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
MainMenuService, NavigationService, NavigationTreeService, NodesManagerService,
|
||||
ConnectionSchemaManagerService, NavigationTabsService,
|
||||
NavigationTreeContextMenuService,
|
||||
SettingsMenuService, LogViewerService, LogViewerMenuService,
|
||||
SettingsMenuService, LogViewerService, LogViewerMenuService, TopNavService,
|
||||
} from '@dbeaver/core/app';
|
||||
import { PluginManifest } from '@dbeaver/core/di';
|
||||
import { CommonDialogService, ContextMenuService, SessionExpireService } from '@dbeaver/core/dialogs';
|
||||
@@ -55,6 +55,7 @@ export const coreManifest: PluginManifest = {
|
||||
LogViewerMenuService,
|
||||
LogViewerService,
|
||||
MainMenuService,
|
||||
TopNavService,
|
||||
NavigationService,
|
||||
NavigationTabsService,
|
||||
NavigationTreeContextMenuService,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
query authLogin(
|
||||
$provider: ID!
|
||||
$credentials: Object!
|
||||
) {
|
||||
user: authLogin(provider: $provider, credentials: $credentials) {
|
||||
userId
|
||||
displayName
|
||||
authProvider
|
||||
loginTime
|
||||
message
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
query authLogout {
|
||||
authLogout
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
query getAuthProviders {
|
||||
providers: authProviders {
|
||||
id
|
||||
label
|
||||
icon
|
||||
description
|
||||
defaultProvider
|
||||
|
||||
#configurationParameters {
|
||||
# id
|
||||
# displayName
|
||||
# description
|
||||
# category
|
||||
# dataType
|
||||
# value
|
||||
# validValues
|
||||
# defaultValue
|
||||
# features
|
||||
#}
|
||||
|
||||
credentialParameters {
|
||||
id
|
||||
displayName
|
||||
description
|
||||
admin
|
||||
user
|
||||
possibleValues
|
||||
encryption
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
query getSessionUser {
|
||||
user: sessionUser {
|
||||
userId
|
||||
displayName
|
||||
authProvider
|
||||
loginTime
|
||||
message
|
||||
}
|
||||
}
|
||||
@@ -6,31 +6,54 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { GraphQLService, CachedResource } from '@dbeaver/core/sdk';
|
||||
|
||||
import { SessionService } from './SessionService';
|
||||
|
||||
type PermissionsMetadata = {
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
export enum EPermission {
|
||||
public = 'public'
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class PermissionsService {
|
||||
readonly onUpdate: Observable<unknown>;
|
||||
|
||||
private updateSubject: Subject<unknown>;
|
||||
private permissions = new CachedResource(
|
||||
new Map(),
|
||||
this.refreshAsync.bind(this),
|
||||
(_, { loaded }) => loaded
|
||||
);
|
||||
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) { }
|
||||
private sessionService: SessionService,
|
||||
) {
|
||||
this.updateSubject = new Subject();
|
||||
this.onUpdate = this.updateSubject.asObservable();
|
||||
this.sessionService.onUpdate.subscribe(this.update.bind(this));
|
||||
}
|
||||
|
||||
has(id: string): boolean {
|
||||
return this.permissions.data.has(id);
|
||||
}
|
||||
|
||||
async hasAsync(id: string): Promise<boolean> {
|
||||
const permissions = await this.permissions.load();
|
||||
return permissions.has(id);
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.permissions.refresh();
|
||||
this.updateSubject.next();
|
||||
}
|
||||
|
||||
private async refreshAsync(
|
||||
|
||||
@@ -33,8 +33,7 @@ export class ServerService {
|
||||
this.settings.clear();
|
||||
parseJSONFlat(
|
||||
serverConfig.productConfiguration,
|
||||
this.settings.setSelfValue.bind(this.settings),
|
||||
undefined
|
||||
this.settings.setSelfValue.bind(this.settings)
|
||||
);
|
||||
}
|
||||
return serverConfig;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import {
|
||||
ConnectionInfo, GraphQLService, SessionInfo, CachedResource,
|
||||
@@ -21,12 +23,23 @@ export type SessionState = Pick<SessionInfo, 'createTime' | 'cacheExpired' | 'la
|
||||
export class SessionService {
|
||||
readonly session = new CachedResource(undefined, this.refreshSessionStateAsync.bind(this), data => !!data);
|
||||
readonly settings = new SessionSettingsService('session_settings');
|
||||
readonly onUpdate: Observable<unknown>;
|
||||
|
||||
constructor(private graphQLService: GraphQLService) {}
|
||||
private updateSubject: Subject<unknown>;
|
||||
|
||||
constructor(private graphQLService: GraphQLService) {
|
||||
this.updateSubject = new Subject();
|
||||
this.onUpdate = this.updateSubject.asObservable();
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.session.refresh();
|
||||
}
|
||||
|
||||
private async refreshSessionStateAsync(data: SessionState | undefined): Promise<SessionState> {
|
||||
const { session } = await this.graphQLService.gql.openSession();
|
||||
|
||||
this.updateSubject.next();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './ServerService';
|
||||
export * from './PermissionsService';
|
||||
export * from '../settings/ServerSettingsService';
|
||||
export * from '../settings/SessionSettingsService';
|
||||
export * from './usePermission';
|
||||
|
||||
@@ -8,6 +8,8 @@ query serverConfig {
|
||||
supportsCustomConnections
|
||||
supportsConnectionBrowser
|
||||
supportsWorkspaces
|
||||
anonymousAccessEnabled
|
||||
authenticationEnabled
|
||||
supportedLanguages {
|
||||
isoCode
|
||||
displayName
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
|
||||
import { PermissionsService } from './PermissionsService';
|
||||
|
||||
export function usePermission(key: string) {
|
||||
const permissionsService = useService(PermissionsService);
|
||||
|
||||
return permissionsService.has(key);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { observable, action } from 'mobx';
|
||||
|
||||
import { GQLError } from './GQLError';
|
||||
|
||||
export class GQLErrorCatcher {
|
||||
@observable hasDetails = false
|
||||
@observable responseMessage: string | null = null
|
||||
@observable exception: GQLError | null = null;
|
||||
|
||||
@action catch(exception: any): boolean {
|
||||
if (exception instanceof GQLError) {
|
||||
this.responseMessage = exception.errorText;
|
||||
this.hasDetails = exception.hasDetails();
|
||||
this.exception = exception;
|
||||
return true;
|
||||
}
|
||||
this.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
@action clear() {
|
||||
this.hasDetails = false;
|
||||
this.responseMessage = null;
|
||||
this.exception = null;
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,12 @@ import { ServerError } from './sdk';
|
||||
export class ServerInternalError extends Error implements ServerError {
|
||||
readonly errorCode?: string;
|
||||
readonly stackTrace?: string;
|
||||
readonly causedBy?: ServerError;
|
||||
|
||||
constructor(error: ServerError) {
|
||||
super(error.message);
|
||||
this.errorCode = error.errorCode;
|
||||
this.stackTrace = error.stackTrace;
|
||||
this.causedBy = error.causedBy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from './GQLError';
|
||||
export * from './ServerInternalError';
|
||||
export * from './CachedResource';
|
||||
export * from './EServerErrorCode';
|
||||
export * from './GQLErrorCatcher';
|
||||
|
||||
+1382
-1916
File diff suppressed because it is too large
Load Diff
@@ -17,14 +17,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dbeaver/core": "^0.1.0",
|
||||
"@material/button": "^4.0.0",
|
||||
"@material/elevation": "^4.0.0",
|
||||
"@material/ripple": "^4.0.0",
|
||||
"@material/theme": "^4.0.0",
|
||||
"@material/typography": "^4.0.0",
|
||||
"graphql": "^14.5.8",
|
||||
"graphql-request": "^1.8.2",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.2.2",
|
||||
"react": "^16.13.1",
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ export const ConnectionForm = observer(function ConnectionForm({
|
||||
const translate = useTranslate();
|
||||
|
||||
return styled(useStyles(formStyles))(
|
||||
<>
|
||||
<connection-form as='div'>
|
||||
<connection-type as="div">
|
||||
<Radio
|
||||
name="type"
|
||||
@@ -74,6 +74,6 @@ export const ConnectionForm = observer(function ConnectionForm({
|
||||
</group>
|
||||
)}
|
||||
{!driver?.anonymousAccess && <CredentialsForm controller={controller}/>}
|
||||
</>
|
||||
</connection-form>
|
||||
);
|
||||
});
|
||||
|
||||
+4
@@ -9,6 +9,10 @@
|
||||
import { css } from 'reshadow';
|
||||
|
||||
export const formStyles = css`
|
||||
connection-form {
|
||||
flex-direction: column;
|
||||
padding: 18px 24px;
|
||||
}
|
||||
connection-type {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
+56
-74
@@ -7,13 +7,13 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import {
|
||||
useTabState, Tab as BaseTab, TabList, TabPanel as BaseTabPanel,
|
||||
} from 'reakit/Tab';
|
||||
import { useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { DBDriver } from '@dbeaver/core/app';
|
||||
import { SubmittingForm, ErrorMessage } from '@dbeaver/core/blocks';
|
||||
import {
|
||||
SubmittingForm, ErrorMessage, TabsState, TabList, Tab, TabTitle, TabPanel
|
||||
} from '@dbeaver/core/blocks';
|
||||
import { useController } from '@dbeaver/core/di';
|
||||
import { CommonDialogWrapper } from '@dbeaver/core/dialogs';
|
||||
import { useTranslate } from '@dbeaver/core/localization';
|
||||
@@ -24,10 +24,9 @@ import { ConnectionFormDialogController } from './ConnectionFormDialogController
|
||||
import { ConnectionFormDialogFooter } from './ConnectionFormDialogFooter';
|
||||
import { DriverProperties } from './DriverProperties/DriverProperties';
|
||||
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
BaseTab {
|
||||
Tab {
|
||||
composes: theme-ripple theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
ErrorMessage {
|
||||
@@ -41,10 +40,8 @@ const styles = composes(
|
||||
box-sizing: border-box;
|
||||
}
|
||||
CommonDialogWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 330px;
|
||||
min-height: 330px;
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
}
|
||||
SubmittingForm {
|
||||
overflow: auto;
|
||||
@@ -55,39 +52,20 @@ const styles = composes(
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
BaseTab, BaseTabPanel {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
TabList {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
padding-left: 24px;
|
||||
}
|
||||
BaseTab {
|
||||
Tab {
|
||||
composes: theme-typography--body2 from global;
|
||||
text-transform: uppercase;
|
||||
padding: 12px 16px;
|
||||
border-top: solid 2px transparent;
|
||||
height: 48px;
|
||||
font-weight: normal;
|
||||
|
||||
&:global([aria-selected='true']) {
|
||||
border-top-color: #fd1d48;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
&:global([aria-selected=true]) {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
&:not(:global([aria-selected='true'])) {
|
||||
cursor: pointer;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
BaseTabPanel::first-child {
|
||||
flex-direction: column;
|
||||
padding: 18px 24px;
|
||||
}
|
||||
ErrorMessage {
|
||||
position: sticky;
|
||||
@@ -113,50 +91,54 @@ export const ConnectionFormDialog = observer(
|
||||
}: ConnectionFormDialogProps) {
|
||||
const translate = useTranslate();
|
||||
const controller = useController(ConnectionFormDialogController, driver, onClose);
|
||||
const tab = useTabState({
|
||||
selectedId: 'options',
|
||||
});
|
||||
const [loadProperties, setLoadProperties] = useState(false);
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
title={title}
|
||||
noBodyPadding
|
||||
header={(
|
||||
<TabList {...tab} aria-label="My tabs">
|
||||
<BaseTab {...tab} type='button' stopId='options'>{translate('customConnection_options')}</BaseTab>
|
||||
<BaseTab {...tab} type='button' stopId='driver_properties'>{translate('customConnection_properties')}</BaseTab>
|
||||
</TabList>
|
||||
)}
|
||||
footer={(
|
||||
<ConnectionFormDialogFooter
|
||||
isConnecting={controller.isConnecting}
|
||||
onConnectionTest={controller.onTestConnection}
|
||||
onCreateConnection={controller.onCreateConnection}
|
||||
onBack={onBack}
|
||||
/>
|
||||
)}
|
||||
onReject={onClose}
|
||||
>
|
||||
<SubmittingForm onSubmit={controller.onCreateConnection}>
|
||||
<BaseTabPanel {...tab} stopId='options'>
|
||||
<ConnectionForm driver={driver} controller={controller} />
|
||||
</BaseTabPanel>
|
||||
<BaseTabPanel {...tab} stopId='driver_properties'>
|
||||
<DriverProperties
|
||||
driver={driver}
|
||||
state={controller.config.properties}
|
||||
isSelected={tab.selectedId === 'driver_properties'}
|
||||
<TabsState selectedId='options'>
|
||||
<CommonDialogWrapper
|
||||
title={title}
|
||||
noBodyPadding
|
||||
header={(
|
||||
<TabList>
|
||||
<Tab tabId='options' >
|
||||
<TabTitle title={translate('customConnection_options')} />
|
||||
</Tab>
|
||||
<Tab tabId='driver_properties' onOpen={() => setLoadProperties(true)} >
|
||||
<TabTitle title={translate('customConnection_properties')} />
|
||||
</Tab>
|
||||
</TabList>
|
||||
)}
|
||||
footer={(
|
||||
<ConnectionFormDialogFooter
|
||||
isConnecting={controller.isConnecting}
|
||||
onConnectionTest={controller.onTestConnection}
|
||||
onCreateConnection={controller.onCreateConnection}
|
||||
onBack={onBack}
|
||||
/>
|
||||
</BaseTabPanel>
|
||||
</SubmittingForm>
|
||||
{controller.responseMessage && (
|
||||
<ErrorMessage
|
||||
text={controller.responseMessage}
|
||||
hasDetails={controller.hasDetails}
|
||||
onShowDetails={controller.onShowDetails}
|
||||
/>
|
||||
)}
|
||||
</CommonDialogWrapper>
|
||||
)}
|
||||
onReject={onClose}
|
||||
>
|
||||
<SubmittingForm onSubmit={controller.onCreateConnection}>
|
||||
<TabPanel tabId='options'>
|
||||
<ConnectionForm driver={driver} controller={controller} />
|
||||
</TabPanel>
|
||||
<TabPanel tabId='driver_properties'>
|
||||
<DriverProperties
|
||||
driver={driver}
|
||||
state={controller.config.properties!}
|
||||
loadProperties={loadProperties}
|
||||
/>
|
||||
</TabPanel>
|
||||
</SubmittingForm>
|
||||
{controller.error.responseMessage && (
|
||||
<ErrorMessage
|
||||
text={controller.error.responseMessage}
|
||||
hasDetails={controller.error.hasDetails}
|
||||
onShowDetails={controller.onShowDetails}
|
||||
/>
|
||||
)}
|
||||
</CommonDialogWrapper>
|
||||
</TabsState>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
+7
-20
@@ -12,7 +12,7 @@ import { DBDriver, ErrorDetailsDialog } from '@dbeaver/core/app';
|
||||
import { injectable, IInitializableController, IDestructibleController } from '@dbeaver/core/di';
|
||||
import { CommonDialogService } from '@dbeaver/core/dialogs';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
import { ConnectionConfig, GQLError } from '@dbeaver/core/sdk';
|
||||
import { ConnectionConfig, GQLErrorCatcher } from '@dbeaver/core/sdk';
|
||||
|
||||
import { CustomConnectionService } from '../../CustomConnectionService';
|
||||
|
||||
@@ -39,10 +39,7 @@ implements IInitializableController, IDestructibleController {
|
||||
url: '',
|
||||
properties: {},
|
||||
}
|
||||
@observable hasDetails = false
|
||||
@observable responseMessage: string | null = null
|
||||
|
||||
private exception: GQLError | null = null;
|
||||
readonly error = new GQLErrorCatcher();
|
||||
private onClose!: () => void
|
||||
private isDistructed = false;
|
||||
|
||||
@@ -70,7 +67,7 @@ implements IInitializableController, IDestructibleController {
|
||||
|
||||
onTestConnection = async () => {
|
||||
this.isConnecting = true;
|
||||
this.clearError();
|
||||
this.error.clear();
|
||||
try {
|
||||
await this.customConnectionService.testConnectionAsync(this.getConnectionConfig());
|
||||
|
||||
@@ -84,7 +81,7 @@ implements IInitializableController, IDestructibleController {
|
||||
|
||||
onCreateConnection = async () => {
|
||||
this.isConnecting = true;
|
||||
this.clearError();
|
||||
this.error.clear();
|
||||
try {
|
||||
const connection = await this.customConnectionService.createConnectionAsync(this.getConnectionConfig());
|
||||
|
||||
@@ -98,8 +95,8 @@ implements IInitializableController, IDestructibleController {
|
||||
}
|
||||
|
||||
onShowDetails = () => {
|
||||
if (this.exception) {
|
||||
this.commonDialogService.open(ErrorDetailsDialog, this.exception);
|
||||
if (this.error.exception) {
|
||||
this.commonDialogService.open(ErrorDetailsDialog, this.error.exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,12 +141,6 @@ implements IInitializableController, IDestructibleController {
|
||||
this.config.properties = {};
|
||||
}
|
||||
|
||||
private clearError() {
|
||||
this.responseMessage = null;
|
||||
this.hasDetails = false;
|
||||
this.exception = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates connection name based on connection url
|
||||
* @param defaultName default connection name if url parsing failed
|
||||
@@ -175,11 +166,7 @@ implements IInitializableController, IDestructibleController {
|
||||
}
|
||||
|
||||
private showError(exception: Error, message: string) {
|
||||
if (exception instanceof GQLError && !this.isDistructed) {
|
||||
this.responseMessage = exception.errorText;
|
||||
this.hasDetails = exception.hasDetails();
|
||||
this.exception = exception;
|
||||
} else {
|
||||
if (!this.error.catch(exception) || this.isDistructed) {
|
||||
this.notificationService.logException(exception, message);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user