SYNC: Merge pull request #19 from dbgate/feature/sfill

This commit is contained in:
Jan Prochazka
2025-12-17 12:26:43 +01:00
committed by Diflow
parent 66d1143ca0
commit ca4667ff1e
16 changed files with 843 additions and 146 deletions

46
packages/api/env/sfill/.env vendored Normal file
View File

@@ -0,0 +1,46 @@
DEVMODE=1
DEVWEB=1
STORAGE_SERVER=localhost
STORAGE_USER=root
STORAGE_PASSWORD=Pwd2020Db
STORAGE_PORT=3306
STORAGE_DATABASE=dbgate-filled
STORAGE_ENGINE=mysql@dbgate-plugin-mysql
CONNECTIONS=mysql,postgres,mongo,redis
LABEL_mysql=MySql
SERVER_mysql=dbgatedckstage1.sprinx.cz
USER_mysql=root
PASSWORD_mysql=Pwd2020Db
PORT_mysql=3306
ENGINE_mysql=mysql@dbgate-plugin-mysql
LABEL_postgres=Postgres
SERVER_postgres=dbgatedckstage1.sprinx.cz
USER_postgres=postgres
PASSWORD_postgres=Pwd2020Db
PORT_postgres=5432
ENGINE_postgres=postgres@dbgate-plugin-postgres
LABEL_mongo=Mongo
SERVER_mongo=dbgatedckstage1.sprinx.cz
USER_mongo=root
PASSWORD_mongo=Pwd2020Db
PORT_mongo=27017
ENGINE_mongo=mongo@dbgate-plugin-mongo
LABEL_redis=Redis
SERVER_redis=dbgatedckstage1.sprinx.cz
ENGINE_redis=redis@dbgate-plugin-redis
PORT_redis=6379
ROLE_test1_CONNECTIONS=mysql
ROLE_test1_PERMISSIONS=widgets/*
ROLE_test1_DATABASES_db1_CONNECTION=mysql
ROLE_test1_DATABASES_db1_PERMISSION=run_script
ROLE_test1_DATABASES_db1_DATABASES=db1
ROLE_test1_DATABASES_db2_CONNECTION=redis
ROLE_test1_DATABASES_db2_PERMISSION=run_script
ROLE_test1_DATABASES_db2_DATABASES=db2

View File

@@ -75,6 +75,7 @@
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
"start:storage": "env-cmd -f env/storage/.env node src/index.js --listen-api",
"start:sfill": "env-cmd -f env/sfill/.env node src/index.js --listen-api",
"start:storage:built": "env-cmd -f env/storage/.env cross-env DEVMODE= BUILTWEBMODE=1 node dist/bundle.js --listen-api",
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
"start:azure": "env-cmd -f env/azure/.env node src/index.js --listen-api",

View File

@@ -23,6 +23,7 @@ const pipeForkLogs = require('../utility/pipeForkLogs');
const requireEngineDriver = require('../utility/requireEngineDriver');
const { getAuthProviderById } = require('../auth/authProvider');
const { startTokenChecking } = require('../utility/authProxy');
const { extractConnectionsFromEnv } = require('../utility/envtools');
const logger = getLogger('connections');
@@ -61,55 +62,7 @@ function getDatabaseFileLabel(databaseFile) {
function getPortalCollections() {
if (process.env.CONNECTIONS) {
const connections = _.compact(process.env.CONNECTIONS.split(',')).map(id => ({
_id: id,
engine: process.env[`ENGINE_${id}`],
server: process.env[`SERVER_${id}`],
user: process.env[`USER_${id}`],
password: process.env[`PASSWORD_${id}`],
passwordMode: process.env[`PASSWORD_MODE_${id}`],
port: process.env[`PORT_${id}`],
databaseUrl: process.env[`URL_${id}`],
useDatabaseUrl: !!process.env[`URL_${id}`],
databaseFile: process.env[`FILE_${id}`]?.replace(
'%%E2E_TEST_DATA_DIRECTORY%%',
path.join(path.dirname(path.dirname(__dirname)), 'e2e-tests', 'tmpdata')
),
socketPath: process.env[`SOCKET_PATH_${id}`],
serviceName: process.env[`SERVICE_NAME_${id}`],
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
defaultDatabase:
process.env[`DATABASE_${id}`] ||
(process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null),
singleDatabase: !!process.env[`DATABASE_${id}`] || !!process.env[`FILE_${id}`],
displayName: process.env[`LABEL_${id}`],
isReadOnly: process.env[`READONLY_${id}`],
databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
allowedDatabases: process.env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
parent: process.env[`PARENT_${id}`] || undefined,
useSeparateSchemas: !!process.env[`USE_SEPARATE_SCHEMAS_${id}`],
localDataCenter: process.env[`LOCAL_DATA_CENTER_${id}`],
// SSH tunnel
useSshTunnel: process.env[`USE_SSH_${id}`],
sshHost: process.env[`SSH_HOST_${id}`],
sshPort: process.env[`SSH_PORT_${id}`],
sshMode: process.env[`SSH_MODE_${id}`],
sshLogin: process.env[`SSH_LOGIN_${id}`],
sshPassword: process.env[`SSH_PASSWORD_${id}`],
sshKeyfile: process.env[`SSH_KEY_FILE_${id}`],
sshKeyfilePassword: process.env[`SSH_KEY_FILE_PASSWORD_${id}`],
// SSL
useSsl: process.env[`USE_SSL_${id}`],
sslCaFile: process.env[`SSL_CA_FILE_${id}`],
sslCertFile: process.env[`SSL_CERT_FILE_${id}`],
sslCertFilePassword: process.env[`SSL_CERT_FILE_PASSWORD_${id}`],
sslKeyFile: process.env[`SSL_KEY_FILE_${id}`],
sslRejectUnauthorized: process.env[`SSL_REJECT_UNAUTHORIZED_${id}`],
trustServerCertificate: process.env[`SSL_TRUST_CERTIFICATE_${id}`],
}));
const connections = extractConnectionsFromEnv(process.env);
for (const conn of connections) {
for (const prop in process.env) {
@@ -229,6 +182,15 @@ module.exports = {
);
}
await this.checkUnsavedConnectionsLimit();
if (process.env.STORAGE_DATABASE && process.env.CONNECTIONS) {
const storage = require('./storage');
try {
await storage.fillStorageConnectionsFromEnv();
} catch (err) {
logger.error(extractErrorLogData(err), 'DBGM-00268 Error filling storage connections from env');
}
}
},
list_meta: true,

View File

@@ -65,6 +65,8 @@ async function copyStream(input, output, options) {
});
}
} catch (err) {
logger.error(extractErrorLogData(err, { progressName }), 'DBGM-00157 Import/export job failed');
process.send({
msgtype: 'copyStreamError',
copyStreamError: {
@@ -82,8 +84,6 @@ async function copyStream(input, output, options) {
errorMessage: extractErrorMessage(err),
});
}
logger.error(extractErrorLogData(err, { progressName }), 'DBGM-00157 Import/export job failed');
// throw err;
}
}

View File

@@ -686,9 +686,34 @@ module.exports = {
"columnName": "connectionDefinition",
"dataType": "text",
"notNull": false
},
{
"pureName": "connections",
"columnName": "import_source_id",
"dataType": "int",
"notNull": false
},
{
"pureName": "connections",
"columnName": "id_original",
"dataType": "varchar(250)",
"notNull": false
}
],
"foreignKeys": [
{
"constraintType": "foreignKey",
"constraintName": "FK_connections_import_source_id",
"pureName": "connections",
"refTableName": "import_sources",
"columns": [
{
"columnName": "import_source_id",
"refColumnName": "id"
}
]
}
],
"foreignKeys": [],
"primaryKey": {
"pureName": "connections",
"constraintType": "primaryKey",
@@ -790,6 +815,41 @@ module.exports = {
}
]
},
{
"pureName": "import_sources",
"columns": [
{
"pureName": "import_sources",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "import_sources",
"columnName": "name",
"dataType": "varchar(250)",
"notNull": true
}
],
"foreignKeys": [],
"primaryKey": {
"pureName": "import_sources",
"constraintType": "primaryKey",
"constraintName": "PK_import_sources",
"columns": [
{
"columnName": "id"
}
]
},
"preloadedRows": [
{
"id": -1,
"name": "env"
}
]
},
{
"pureName": "roles",
"columns": [
@@ -805,9 +865,34 @@ module.exports = {
"columnName": "name",
"dataType": "varchar(250)",
"notNull": false
},
{
"pureName": "roles",
"columnName": "import_source_id",
"dataType": "int",
"notNull": false
},
{
"pureName": "roles",
"columnName": "id_original",
"dataType": "varchar(250)",
"notNull": false
}
],
"foreignKeys": [
{
"constraintType": "foreignKey",
"constraintName": "FK_roles_import_source_id",
"pureName": "roles",
"refTableName": "import_sources",
"columns": [
{
"columnName": "import_source_id",
"refColumnName": "id"
}
]
}
],
"foreignKeys": [],
"primaryKey": {
"pureName": "roles",
"constraintType": "primaryKey",
@@ -854,6 +939,12 @@ module.exports = {
"columnName": "connection_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "role_connections",
"columnName": "import_source_id",
"dataType": "int",
"notNull": false
}
],
"foreignKeys": [
@@ -882,6 +973,18 @@ module.exports = {
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_connections_import_source_id",
"pureName": "role_connections",
"refTableName": "import_sources",
"columns": [
{
"columnName": "import_source_id",
"refColumnName": "id"
}
]
}
],
"primaryKey": {
@@ -934,6 +1037,18 @@ module.exports = {
"columnName": "database_permission_role_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "role_databases",
"columnName": "import_source_id",
"dataType": "int",
"notNull": false
},
{
"pureName": "role_databases",
"columnName": "id_original",
"dataType": "varchar(250)",
"notNull": false
}
],
"foreignKeys": [
@@ -974,6 +1089,18 @@ module.exports = {
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_databases_import_source_id",
"pureName": "role_databases",
"refTableName": "import_sources",
"columns": [
{
"columnName": "import_source_id",
"refColumnName": "id"
}
]
}
],
"primaryKey": {
@@ -1087,6 +1214,12 @@ module.exports = {
"columnName": "permission",
"dataType": "varchar(250)",
"notNull": true
},
{
"pureName": "role_permissions",
"columnName": "import_source_id",
"dataType": "int",
"notNull": false
}
],
"foreignKeys": [
@@ -1102,6 +1235,18 @@ module.exports = {
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_permissions_import_source_id",
"pureName": "role_permissions",
"refTableName": "import_sources",
"columns": [
{
"columnName": "import_source_id",
"refColumnName": "id"
}
]
}
],
"primaryKey": {
@@ -1184,6 +1329,18 @@ module.exports = {
"columnName": "table_permission_scope_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "role_tables",
"columnName": "import_source_id",
"dataType": "int",
"notNull": false
},
{
"pureName": "role_tables",
"columnName": "id_original",
"dataType": "varchar(250)",
"notNull": false
}
],
"foreignKeys": [
@@ -1236,6 +1393,18 @@ module.exports = {
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_tables_import_source_id",
"pureName": "role_tables",
"refTableName": "import_sources",
"columns": [
{
"columnName": "import_source_id",
"refColumnName": "id"
}
]
}
],
"primaryKey": {

View File

@@ -0,0 +1,445 @@
const path = require('path');
const _ = require('lodash');
const { safeJsonParse, getDatabaseFileLabel } = require('dbgate-tools');
const crypto = require('crypto');
function extractConnectionsFromEnv(env) {
if (!env?.CONNECTIONS) {
return null;
}
const connections = _.compact(env.CONNECTIONS.split(',')).map(id => ({
_id: id,
engine: env[`ENGINE_${id}`],
server: env[`SERVER_${id}`],
user: env[`USER_${id}`],
password: env[`PASSWORD_${id}`],
passwordMode: env[`PASSWORD_MODE_${id}`],
port: env[`PORT_${id}`],
databaseUrl: env[`URL_${id}`],
useDatabaseUrl: !!env[`URL_${id}`],
databaseFile: env[`FILE_${id}`]?.replace(
'%%E2E_TEST_DATA_DIRECTORY%%',
path.join(path.dirname(path.dirname(__dirname)), 'e2e-tests', 'tmpdata')
),
socketPath: env[`SOCKET_PATH_${id}`],
serviceName: env[`SERVICE_NAME_${id}`],
authType: env[`AUTH_TYPE_${id}`] || (env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
defaultDatabase: env[`DATABASE_${id}`] || (env[`FILE_${id}`] ? getDatabaseFileLabel(env[`FILE_${id}`]) : null),
singleDatabase: !!env[`DATABASE_${id}`] || !!env[`FILE_${id}`],
displayName: env[`LABEL_${id}`],
isReadOnly: env[`READONLY_${id}`],
databases: env[`DBCONFIG_${id}`] ? safeJsonParse(env[`DBCONFIG_${id}`]) : null,
allowedDatabases: env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
allowedDatabasesRegex: env[`ALLOWED_DATABASES_REGEX_${id}`],
parent: env[`PARENT_${id}`] || undefined,
useSeparateSchemas: !!env[`USE_SEPARATE_SCHEMAS_${id}`],
localDataCenter: env[`LOCAL_DATA_CENTER_${id}`],
// SSH tunnel
useSshTunnel: env[`USE_SSH_${id}`],
sshHost: env[`SSH_HOST_${id}`],
sshPort: env[`SSH_PORT_${id}`],
sshMode: env[`SSH_MODE_${id}`],
sshLogin: env[`SSH_LOGIN_${id}`],
sshPassword: env[`SSH_PASSWORD_${id}`],
sshKeyfile: env[`SSH_KEY_FILE_${id}`],
sshKeyfilePassword: env[`SSH_KEY_FILE_PASSWORD_${id}`],
// SSL
useSsl: env[`USE_SSL_${id}`],
sslCaFile: env[`SSL_CA_FILE_${id}`],
sslCertFile: env[`SSL_CERT_FILE_${id}`],
sslCertFilePassword: env[`SSL_CERT_FILE_PASSWORD_${id}`],
sslKeyFile: env[`SSL_KEY_FILE_${id}`],
sslRejectUnauthorized: env[`SSL_REJECT_UNAUTHORIZED_${id}`],
trustServerCertificate: env[`SSL_TRUST_CERTIFICATE_${id}`],
}));
return connections;
}
function extractImportEntitiesFromEnv(env) {
const portalConnections = extractConnectionsFromEnv(env) || [];
const connections = portalConnections.map((conn, index) => ({
...conn,
id_original: conn._id,
import_source_id: -1,
conid: crypto.randomUUID(),
_id: undefined,
id: index + 1, // autoincrement id
}));
const connectionEnvIdToDbId = {};
for (const conn of connections) {
connectionEnvIdToDbId[conn.id_original] = conn.id;
}
const connectionsRegex = /^ROLE_(.+)_CONNECTIONS$/;
const permissionsRegex = /^ROLE_(.+)_PERMISSIONS$/;
const dbConnectionRegex = /^ROLE_(.+)_DATABASES_(.+)_CONNECTION$/;
const dbDatabasesRegex = /^ROLE_(.+)_DATABASES_(.+)_DATABASES$/;
const dbDatabasesRegexRegex = /^ROLE_(.+)_DATABASES_(.+)_DATABASES_REGEX$/;
const dbPermissionRegex = /^ROLE_(.+)_DATABASES_(.+)_PERMISSION$/;
const tableConnectionRegex = /^ROLE_(.+)_TABLES_(.+)_CONNECTION$/;
const tableDatabasesRegex = /^ROLE_(.+)_TABLES_(.+)_DATABASES$/;
const tableDatabasesRegexRegex = /^ROLE_(.+)_TABLES_(.+)_DATABASES_REGEX$/;
const tableSchemasRegex = /^ROLE_(.+)_TABLES_(.+)_SCHEMAS$/;
const tableSchemasRegexRegex = /^ROLE_(.+)_TABLES_(.+)_SCHEMAS_REGEX$/;
const tableTablesRegex = /^ROLE_(.+)_TABLES_(.+)_TABLES$/;
const tableTablesRegexRegex = /^ROLE_(.+)_TABLES_(.+)_TABLES_REGEX$/;
const tablePermissionRegex = /^ROLE_(.+)_TABLES_(.+)_PERMISSION$/;
const tableScopeRegex = /^ROLE_(.+)_TABLES_(.+)_SCOPE$/;
const roles = [];
const role_connections = [];
const role_permissions = [];
const role_databases = [];
const role_tables = [];
// Permission name to ID mappings
const databasePermissionMap = {
view: -1,
read_content: -2,
write_data: -3,
run_script: -4,
deny: -5,
};
const tablePermissionMap = {
read: -1,
update_only: -2,
create_update_delete: -3,
run_script: -4,
deny: -5,
};
const tableScopeMap = {
all_objects: -1,
tables: -2,
views: -3,
tables_views_collections: -4,
procedures: -5,
functions: -6,
triggers: -7,
sql_objects: -8,
collections: -9,
};
// Collect database and table permissions data
const databasePermissions = {};
const tablePermissions = {};
// First pass: collect all database and table permission data
for (const key in env) {
const dbConnMatch = key.match(dbConnectionRegex);
const dbDatabasesMatch = key.match(dbDatabasesRegex);
const dbDatabasesRegexMatch = key.match(dbDatabasesRegexRegex);
const dbPermMatch = key.match(dbPermissionRegex);
const tableConnMatch = key.match(tableConnectionRegex);
const tableDatabasesMatch = key.match(tableDatabasesRegex);
const tableDatabasesRegexMatch = key.match(tableDatabasesRegexRegex);
const tableSchemasMatch = key.match(tableSchemasRegex);
const tableSchemasRegexMatch = key.match(tableSchemasRegexRegex);
const tableTablesMatch = key.match(tableTablesRegex);
const tableTablesRegexMatch = key.match(tableTablesRegexRegex);
const tablePermMatch = key.match(tablePermissionRegex);
const tableScopeMatch = key.match(tableScopeRegex);
// Database permissions
if (dbConnMatch) {
const [, roleName, permId] = dbConnMatch;
if (!databasePermissions[roleName]) databasePermissions[roleName] = {};
if (!databasePermissions[roleName][permId]) databasePermissions[roleName][permId] = {};
databasePermissions[roleName][permId].connection = env[key];
}
if (dbDatabasesMatch) {
const [, roleName, permId] = dbDatabasesMatch;
if (!databasePermissions[roleName]) databasePermissions[roleName] = {};
if (!databasePermissions[roleName][permId]) databasePermissions[roleName][permId] = {};
databasePermissions[roleName][permId].databases = env[key]?.replace(/\|/g, '\n');
}
if (dbDatabasesRegexMatch) {
const [, roleName, permId] = dbDatabasesRegexMatch;
if (!databasePermissions[roleName]) databasePermissions[roleName] = {};
if (!databasePermissions[roleName][permId]) databasePermissions[roleName][permId] = {};
databasePermissions[roleName][permId].databasesRegex = env[key];
}
if (dbPermMatch) {
const [, roleName, permId] = dbPermMatch;
if (!databasePermissions[roleName]) databasePermissions[roleName] = {};
if (!databasePermissions[roleName][permId]) databasePermissions[roleName][permId] = {};
databasePermissions[roleName][permId].permission = env[key];
}
// Table permissions
if (tableConnMatch) {
const [, roleName, permId] = tableConnMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].connection = env[key];
}
if (tableDatabasesMatch) {
const [, roleName, permId] = tableDatabasesMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].databases = env[key]?.replace(/\|/g, '\n');
}
if (tableDatabasesRegexMatch) {
const [, roleName, permId] = tableDatabasesRegexMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].databasesRegex = env[key];
}
if (tableSchemasMatch) {
const [, roleName, permId] = tableSchemasMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].schemas = env[key];
}
if (tableSchemasRegexMatch) {
const [, roleName, permId] = tableSchemasRegexMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].schemasRegex = env[key];
}
if (tableTablesMatch) {
const [, roleName, permId] = tableTablesMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].tables = env[key]?.replace(/\|/g, '\n');
}
if (tableTablesRegexMatch) {
const [, roleName, permId] = tableTablesRegexMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].tablesRegex = env[key];
}
if (tablePermMatch) {
const [, roleName, permId] = tablePermMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].permission = env[key];
}
if (tableScopeMatch) {
const [, roleName, permId] = tableScopeMatch;
if (!tablePermissions[roleName]) tablePermissions[roleName] = {};
if (!tablePermissions[roleName][permId]) tablePermissions[roleName][permId] = {};
tablePermissions[roleName][permId].scope = env[key];
}
}
// Second pass: process roles, connections, and permissions
for (const key in env) {
const connMatch = key.match(connectionsRegex);
const permMatch = key.match(permissionsRegex);
if (connMatch) {
const roleName = connMatch[1];
let role = roles.find(r => r.name === roleName);
if (!role) {
role = {
id: roles.length + 1,
name: roleName,
import_source_id: -1,
};
roles.push(role);
}
const connIds = env[key]
.split(',')
.map(id => id.trim())
.filter(id => id.length > 0);
for (const connId of connIds) {
const dbId = connectionEnvIdToDbId[connId];
if (dbId) {
role_connections.push({
role_id: role.id,
connection_id: dbId,
import_source_id: -1,
});
}
}
}
if (permMatch) {
const roleName = permMatch[1];
let role = roles.find(r => r.name === roleName);
if (!role) {
role = {
id: roles.length + 1,
name: roleName,
import_source_id: -1,
};
roles.push(role);
}
const permissions = env[key]
.split(',')
.map(p => p.trim())
.filter(p => p.length > 0);
for (const permission of permissions) {
role_permissions.push({
role_id: role.id,
permission,
import_source_id: -1,
});
}
}
}
// Process database permissions
for (const roleName in databasePermissions) {
let role = roles.find(r => r.name === roleName);
if (!role) {
role = {
id: roles.length + 1,
name: roleName,
import_source_id: -1,
};
roles.push(role);
}
for (const permId in databasePermissions[roleName]) {
const perm = databasePermissions[roleName][permId];
if (perm.connection && perm.permission) {
const dbId = connectionEnvIdToDbId[perm.connection];
const permissionId = databasePermissionMap[perm.permission];
if (dbId && permissionId) {
role_databases.push({
role_id: role.id,
connection_id: dbId,
database_names_list: perm.databases || null,
database_names_regex: perm.databasesRegex || null,
database_permission_role_id: permissionId,
id_original: permId,
import_source_id: -1,
});
}
}
}
}
// Process table permissions
for (const roleName in tablePermissions) {
let role = roles.find(r => r.name === roleName);
if (!role) {
role = {
id: roles.length + 1,
name: roleName,
import_source_id: -1,
};
roles.push(role);
}
for (const permId in tablePermissions[roleName]) {
const perm = tablePermissions[roleName][permId];
if (perm.connection && perm.permission) {
const dbId = connectionEnvIdToDbId[perm.connection];
const permissionId = tablePermissionMap[perm.permission];
const scopeId = tableScopeMap[perm.scope || 'all_objects'];
if (dbId && permissionId && scopeId) {
role_tables.push({
role_id: role.id,
connection_id: dbId,
database_names_list: perm.databases || null,
database_names_regex: perm.databasesRegex || null,
schema_names_list: perm.schemas || null,
schema_names_regex: perm.schemasRegex || null,
table_names_list: perm.tables || null,
table_names_regex: perm.tablesRegex || null,
table_permission_role_id: permissionId,
table_permission_scope_id: scopeId,
id_original: permId,
import_source_id: -1,
});
}
}
}
}
if (connections.length == 0 && roles.length == 0) {
return null;
}
return {
connections,
roles,
role_connections,
role_permissions,
role_databases,
role_tables,
};
}
function createStorageFromEnvReplicatorItems(importEntities) {
return [
{
name: 'connections',
findExisting: true,
createNew: true,
updateExisting: true,
matchColumns: ['id_original', 'import_source_id'],
deleteMissing: true,
deleteRestrictionColumns: ['import_source_id'],
skipUpdateColumns: ['conid'],
jsonArray: importEntities.connections,
},
{
name: 'roles',
findExisting: true,
createNew: true,
updateExisting: true,
matchColumns: ['name', 'import_source_id'],
deleteMissing: true,
deleteRestrictionColumns: ['import_source_id'],
jsonArray: importEntities.roles,
},
{
name: 'role_connections',
findExisting: true,
createNew: true,
updateExisting: false,
deleteMissing: true,
matchColumns: ['role_id', 'connection_id', 'import_source_id'],
jsonArray: importEntities.role_connections,
deleteRestrictionColumns: ['import_source_id'],
},
{
name: 'role_permissions',
findExisting: true,
createNew: true,
updateExisting: false,
deleteMissing: true,
matchColumns: ['role_id', 'permission', 'import_source_id'],
jsonArray: importEntities.role_permissions,
deleteRestrictionColumns: ['import_source_id'],
},
{
name: 'role_databases',
findExisting: true,
createNew: true,
updateExisting: true,
deleteMissing: true,
matchColumns: ['role_id', 'id_original', 'import_source_id'],
jsonArray: importEntities.role_databases,
deleteRestrictionColumns: ['import_source_id'],
},
{
name: 'role_tables',
findExisting: true,
createNew: true,
updateExisting: true,
deleteMissing: true,
matchColumns: ['role_id', 'id_original', 'import_source_id'],
jsonArray: importEntities.role_tables,
deleteRestrictionColumns: ['import_source_id'],
},
];
}
module.exports = {
extractConnectionsFromEnv,
extractImportEntitiesFromEnv,
createStorageFromEnvReplicatorItems,
};

View File

@@ -11,6 +11,7 @@
export let arg;
export let namePrefix;
export let isReadOnly = false;
$: name = `${namePrefix}${arg.name}`;
@@ -24,7 +25,7 @@
defaultValue={arg.default}
focused={arg.focused}
placeholder={arg.placeholder}
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
disabled={isReadOnly || (arg.disabledFn ? arg.disabledFn($values) : arg.disabled)}
/>
{:else if arg.type == 'stringlist'}
<FormStringList
@@ -32,6 +33,7 @@
addButtonLabel={_tval(arg.addButtonLabel)}
{name}
placeholder={arg.placeholder}
isReadOnly={isReadOnly || (arg.disabledFn ? arg.disabledFn($values) : arg.disabled)}
/>
{:else if arg.type == 'number'}
<FormTextField
@@ -41,14 +43,14 @@
defaultValue={arg.default}
focused={arg.focused}
placeholder={arg.placeholder}
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
disabled={isReadOnly || (arg.disabledFn ? arg.disabledFn($values) : arg.disabled)}
/>
{:else if arg.type == 'checkbox'}
<FormCheckboxField
label={_tval(arg.label)}
{name}
defaultValue={arg.default}
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
disabled={isReadOnly || (arg.disabledFn ? arg.disabledFn($values) : arg.disabled)}
/>
{:else if arg.type == 'select'}
<FormSelectField
@@ -59,7 +61,7 @@
options={arg.options.map(opt =>
_.isString(opt) ? { label: opt, value: opt } : { label: opt.name, value: opt.value }
)}
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
disabled={isReadOnly || (arg.disabledFn ? arg.disabledFn($values) : arg.disabled)}
/>
{:else if arg.type == 'dropdowntext'}
<FormDropDownTextField
@@ -72,6 +74,6 @@
onClick: () => setFieldValue(name, _.isString(opt) ? opt : opt.value),
}));
}}
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
disabled={isReadOnly || (arg.disabledFn ? arg.disabledFn($values) : arg.disabled)}
/>
{/if}

View File

@@ -3,10 +3,11 @@
export let namePrefix = '';
export let args: any[];
export let isReadOnly = false;
</script>
<div>
{#each args as arg (arg.name)}
<FormArgument {arg} {namePrefix} />
<FormArgument {arg} {namePrefix} {isReadOnly} />
{/each}
</div>

View File

@@ -12,6 +12,7 @@
export let addButtonLabel;
export let placeholder;
export let templateProps;
export let isReadOnly = false;
const { template, values, setFieldValue } = getFormContext();
@@ -20,7 +21,7 @@
<svelte:component this={template} type="text" {label} {...templateProps}>
{#each stringList as value, index}
<div class='input-line-flex'>
<div class="input-line-flex">
<TextField
{value}
{placeholder}
@@ -28,12 +29,14 @@
const newValues = stringList.map((v, i) => (i === index ? e.target['value'] : v));
setFieldValue(name, newValues);
}}
disabled={isReadOnly}
/>
<InlineButton
on:click={() => {
setFieldValue(name, [...stringList.slice(0, index), ...stringList.slice(index + 1)]);
}}
disabled={isReadOnly}
>
<FontIcon icon="icon delete" />
</InlineButton>
@@ -45,11 +48,12 @@
on:click={() => {
setFieldValue(name, [...stringList, '']);
}}
disabled={isReadOnly}
/>
</svelte:component>
<style>
.input-line-flex {
display: flex;
}
</style>
.input-line-flex {
display: flex;
}
</style>

View File

@@ -6,6 +6,8 @@
import FormArgumentList from '../forms/FormArgumentList.svelte';
import { _t } from '../translations';
export let isFormReadOnly;
const { values } = getFormContext();
$: engine = $values.engine;
@@ -17,9 +19,18 @@
$: advancedFields = driver?.getAdvancedConnectionFields ? driver?.getAdvancedConnectionFields() : null;
</script>
<FormTextAreaField label={_t('connection.allowedDatabases', { defaultMessage: 'Allowed databases, one per line' })} name="allowedDatabases" disabled={isConnected} rows={8} />
<FormTextField label={_t('connection.allowedDatabasesRegex', { defaultMessage: 'Allowed databases regular expression' })} name="allowedDatabasesRegex" disabled={isConnected} />
<FormTextAreaField
label={_t('connection.allowedDatabases', { defaultMessage: 'Allowed databases, one per line' })}
name="allowedDatabases"
disabled={isConnected || isFormReadOnly}
rows={8}
/>
<FormTextField
label={_t('connection.allowedDatabasesRegex', { defaultMessage: 'Allowed databases regular expression' })}
name="allowedDatabasesRegex"
disabled={isConnected || isFormReadOnly}
/>
{#if advancedFields}
<FormArgumentList args={advancedFields} />
<FormArgumentList args={advancedFields} isReadOnly={isFormReadOnly} />
{/if}

View File

@@ -23,6 +23,7 @@
export let getDatabaseList;
export let currentConnection;
export let isFormReadOnly;
const { values, setFieldValue } = getFormContext();
const electron = getElectron();
@@ -90,10 +91,10 @@
label={_t('connection.type', { defaultMessage: 'Connection type' })}
name="engine"
isNative
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_connectionType"
options={[
{ label: _t('connection.selectType', { defaultMessage: '(select connection type)' })},
{ label: _t('connection.selectType', { defaultMessage: '(select connection type)' }) },
..._.sortBy(
$extensions.drivers
// .filter(driver => !driver.isElectronOnly || electron)
@@ -113,7 +114,7 @@
data-testid="ConnectionDriverFields_authType"
name="authType"
isNative
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
defaultValue={driver?.defaultAuthTypeName}
options={$authTypes.map(auth => ({
value: auth.name,
@@ -127,16 +128,18 @@
<FormClusterNodesField
label={_t('connection.clusterNodes', { defaultMessage: 'Cluster nodes' })}
name="clusterNodes"
disabled={isConnected || disabledFields.includes('clusterNodes')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('clusterNodes')}
data-testid="ConnectionDriverFields_clusterNodes"
/>
{/if}
{#if driver?.showConnectionField('autoDetectNatMap', $values, showConnectionFieldArgs)}
<FormCheckboxField
label={_t('connection.autoDetectNatMap', { defaultMessage: 'Auto detect NAT map (use for Redis Cluster in Docker network)' })}
label={_t('connection.autoDetectNatMap', {
defaultMessage: 'Auto detect NAT map (use for Redis Cluster in Docker network)',
})}
name="autoDetectNatMap"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_autoDetectNatMap"
/>
{/if}
@@ -146,13 +149,13 @@
<FormElectronFileSelector
label={_t('connection.databaseFile', { defaultMessage: 'Database file' })}
name="databaseFile"
disabled={isConnected || disabledFields.includes('databaseFile')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('databaseFile')}
/>
{:else}
<FormTextField
label={_t('connection.databaseFilePath', { defaultMessage: 'Database file (path on server)' })}
name="databaseFile"
disabled={isConnected || disabledFields.includes('databaseFile')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('databaseFile')}
/>
{/if}
{/if}
@@ -160,11 +163,15 @@
{#if driver?.showConnectionField('useDatabaseUrl', $values, showConnectionFieldArgs)}
<div class="radio">
<FormRadioGroupField
disabled={isConnected || disabledFields.includes('useDatabaseUrl')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('useDatabaseUrl')}
name="useDatabaseUrl"
matchValueToOption={(value, option) => !!option.value == !!value}
options={[
{ label: _t('connection.fillDetails', { defaultMessage: 'Fill database connection details' }), value: '', default: true },
{
label: _t('connection.fillDetails', { defaultMessage: 'Fill database connection details' }),
value: '',
default: true,
},
{ label: _t('connection.useUrl', { defaultMessage: 'Use database URL' }), value: '1' },
]}
/>
@@ -177,7 +184,7 @@
name="databaseUrl"
data-testid="ConnectionDriverFields_databaseUrl"
placeholder={driver?.databaseUrlPlaceholder}
disabled={isConnected || disabledFields.includes('databaseUrl')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('databaseUrl')}
/>
{/if}
@@ -187,7 +194,7 @@
name="localDataCenter"
data-testid="ConnectionDriverFields_localDataCenter"
placeholder={driver?.defaultLocalDataCenter}
disabled={isConnected || disabledFields.includes('localDataCenter')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('localDataCenter')}
/>
{/if}
@@ -196,7 +203,7 @@
label={_t('connection.authToken', { defaultMessage: 'Auth token' })}
name="authToken"
data-testid="ConnectionDriverFields_authToken"
disabled={isConnected || disabledFields.includes('authToken')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('authToken')}
/>
{/if}
@@ -207,7 +214,7 @@
data-testid="ConnectionDriverFields_authType"
name="authType"
isNative
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
defaultValue={driver?.defaultAuthTypeName}
options={$authTypes.map(auth => ({
value: auth.name,
@@ -219,9 +226,9 @@
{#if driver?.showConnectionField('endpoint', $values, showConnectionFieldArgs)}
<FormTextField
label='Endpoint'
label="Endpoint"
name="endpoint"
disabled={isConnected || disabledFields.includes('endpoint')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('endpoint')}
data-testid="ConnectionDriverFields_endpoint"
/>
{/if}
@@ -230,7 +237,7 @@
<FormTextField
label={_t('connection.endpointKey', { defaultMessage: 'Key' })}
name="endpointKey"
disabled={isConnected || disabledFields.includes('endpointKey')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('endpointKey')}
data-testid="ConnectionDriverFields_endpointKey"
/>
{/if}
@@ -239,7 +246,7 @@
<FormTextField
label={_t('connection.clientLibraryPath', { defaultMessage: 'Client library path' })}
name="clientLibraryPath"
disabled={isConnected || disabledFields.includes('clientLibraryPath')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('clientLibraryPath')}
data-testid="ConnectionDriverFields_clientLibraryPath"
/>
{/if}
@@ -250,7 +257,7 @@
<FormTextField
label={_t('connection.server', { defaultMessage: 'Server' })}
name="server"
disabled={isConnected || disabledFields.includes('server')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('server')}
templateProps={{ noMargin: true }}
data-testid="ConnectionDriverFields_server"
/>
@@ -260,7 +267,7 @@
<FormTextField
label="Port"
name="port"
disabled={isConnected || disabledFields.includes('port')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('port')}
templateProps={{ noMargin: true }}
placeholder={driver?.defaultPort}
data-testid="ConnectionDriverFields_port"
@@ -271,7 +278,9 @@
{#if getCurrentConfig().isDocker}
<div class="row">
<FontIcon icon="img warn" padRight />
{ _t('connection.dockerWarning', { defaultMessage: 'Under docker, localhost and 127.0.0.1 will not work, use dockerhost instead' }) }
{_t('connection.dockerWarning', {
defaultMessage: 'Under docker, localhost and 127.0.0.1 will not work, use dockerhost instead',
})}
</div>
{/if}
{/if}
@@ -280,9 +289,11 @@
<div class="row">
<div class="col-9 mr-1">
<FormTextField
label={$values.serviceNameType == 'sid' ? 'SID' : _t('connection.serviceName', { defaultMessage: 'Service name' })}
label={$values.serviceNameType == 'sid'
? 'SID'
: _t('connection.serviceName', { defaultMessage: 'Service name' })}
name="serviceName"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
templateProps={{ noMargin: true }}
data-testid="ConnectionDriverFields_serviceName"
/>
@@ -293,7 +304,7 @@
isNative
name="serviceNameType"
defaultValue="serviceName"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
templateProps={{ noMargin: true }}
options={[
{ value: 'serviceName', label: _t('connection.serviceName', { defaultMessage: 'Service name' }) },
@@ -309,7 +320,7 @@
<FormTextField
label={_t('connection.socketPath', { defaultMessage: 'Socket path' })}
name="socketPath"
disabled={isConnected || disabledFields.includes('socketPath')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('socketPath')}
placeholder={driver?.defaultSocketPath}
data-testid="ConnectionDriverFields_scoketPath"
/>
@@ -322,7 +333,7 @@
<FormTextField
label={_t('connection.user', { defaultMessage: 'User' })}
name="user"
disabled={isConnected || disabledFields.includes('user')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('user')}
templateProps={{ noMargin: true }}
data-testid="ConnectionDriverFields_user"
/>
@@ -333,7 +344,7 @@
<FormPasswordField
label={_t('connection.password', { defaultMessage: 'Password' })}
name="password"
disabled={isConnected || disabledFields.includes('password')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('password')}
templateProps={{ noMargin: true }}
data-testid="ConnectionDriverFields_password"
/>
@@ -345,7 +356,7 @@
<FormTextField
label={_t('connection.user', { defaultMessage: 'User' })}
name="user"
disabled={isConnected || disabledFields.includes('user')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('user')}
data-testid="ConnectionDriverFields_user"
/>
{/if}
@@ -353,7 +364,7 @@
<FormPasswordField
label={_t('connection.password', { defaultMessage: 'Password' })}
name="password"
disabled={isConnected || disabledFields.includes('password')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('password')}
data-testid="ConnectionDriverFields_password"
/>
{/if}
@@ -380,7 +391,7 @@
<FormTextField
label={_t('connection.accessKeyId', { defaultMessage: 'Access Key ID' })}
name="accessKeyId"
disabled={isConnected || disabledFields.includes('accessKeyId')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('accessKeyId')}
templateProps={{ noMargin: true }}
data-testid="ConnectionDriverFields_accesKeyId"
/>
@@ -391,7 +402,7 @@
<FormPasswordField
label={_t('connection.secretAccessKey', { defaultMessage: 'Secret access key' })}
name="secretAccessKey"
disabled={isConnected || disabledFields.includes('secretAccessKey')}
disabled={isConnected || isFormReadOnly || disabledFields.includes('secretAccessKey')}
templateProps={{ noMargin: true }}
data-testid="ConnectionDriverFields_secretAccessKey"
/>
@@ -405,12 +416,15 @@
isNative
name="passwordMode"
defaultValue="saveEncrypted"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
options={[
{ value: 'saveEncrypted', label: _t('connection.saveEncrypted', { defaultMessage: 'Save and encrypt' }) },
{ value: 'saveRaw', label: _t('connection.saveRaw', { defaultMessage: 'Save raw (UNSAFE!!)' }) },
{ value: 'askPassword', label: _t('connection.askPassword', { defaultMessage: "Don't save, ask for password" }) },
{ value: 'askUser', label: _t('connection.askUser', { defaultMessage: "Don't save, ask for login and password" }) },
{
value: 'askUser',
label: _t('connection.askUser', { defaultMessage: "Don't save, ask for login and password" }),
},
]}
data-testid="ConnectionDriverFields_passwordMode"
/>
@@ -420,7 +434,7 @@
<FormTextField
label={_t('connection.keySeparator', { defaultMessage: 'Key separator' })}
name="treeKeySeparator"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
placeholder=":"
data-testid="ConnectionDriverFields_treeKeySeparator"
/>
@@ -430,7 +444,7 @@
<FormTextField
label={_t('connection.windowsDomain', { defaultMessage: 'Domain (specify to use NTLM authentication)' })}
name="windowsDomain"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_windowsDomain"
/>
{/if}
@@ -439,7 +453,7 @@
<FormCheckboxField
label={_t('connection.isReadOnly', { defaultMessage: 'Is read only' })}
name="isReadOnly"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_isReadOnly"
/>
{/if}
@@ -448,7 +462,7 @@
<FormCheckboxField
label={_t('connection.trustServerCertificate', { defaultMessage: 'Trust server certificate' })}
name="trustServerCertificate"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_trustServerCertificate"
/>
{/if}
@@ -457,33 +471,42 @@
<FormDropDownTextField
label={_t('connection.defaultDatabase', { defaultMessage: 'Default database' })}
name="defaultDatabase"
disabled={isConnected}
disabled={isConnected || isFormReadOnly || disabledFields.includes('defaultDatabase')}
data-testid="ConnectionDriverFields_defaultDatabase"
asyncMenu={createDatabasesMenu}
placeholder={_t('common.notSelectedOptional', { defaultMessage : "(not selected - optional)"})}
placeholder={_t('common.notSelectedOptional', { defaultMessage: '(not selected - optional)' })}
/>
{/if}
{#if defaultDatabase && driver?.showConnectionField('singleDatabase', $values, showConnectionFieldArgs)}
<FormCheckboxField
label={_t('connection.singleDatabase', { defaultMessage: 'Use only database {defaultDatabase}', values: { defaultDatabase } })}
label={_t('connection.singleDatabase', {
defaultMessage: 'Use only database {defaultDatabase}',
values: { defaultDatabase },
})}
name="singleDatabase"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_singleDatabase"
/>
{/if}
{#if driver?.showConnectionField('useSeparateSchemas', $values, showConnectionFieldArgs)}
<FormCheckboxField
label={_t('connection.useSeparateSchemas', { defaultMessage: 'Use schemas separately (use this if you have many large schemas)' })}
label={_t('connection.useSeparateSchemas', {
defaultMessage: 'Use schemas separately (use this if you have many large schemas)',
})}
name="useSeparateSchemas"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_useSeparateSchemas"
/>
{/if}
{#if driver?.showConnectionField('connectionDefinition', $values, showConnectionFieldArgs)}
<FormFileInputField disabled={isConnected} label={_t('connection.connectionDefinition', { defaultMessage: 'Service account key JSON' })} name="connectionDefinition" />
<FormFileInputField
disabled={isConnected || isFormReadOnly}
label={_t('connection.connectionDefinition', { defaultMessage: 'Service account key JSON' })}
name="connectionDefinition"
/>
{/if}
{#if driver}
@@ -493,7 +516,7 @@
label={_t('connection.displayName', { defaultMessage: 'Display name' })}
name="displayName"
templateProps={{ noMargin: true }}
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_displayName"
placeholder={getConnectionLabel(currentConnection)}
/>
@@ -505,7 +528,7 @@
name="connectionColor"
emptyLabel="(not selected)"
templateProps={{ noMargin: true }}
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionDriverFields_connectionColor"
/>
</div>

View File

@@ -14,6 +14,8 @@
import { extensions, openedConnections, openedSingleDatabaseConnections } from '../stores';
import { _t } from '../translations';
export let isFormReadOnly;
const { values, setFieldValue } = getFormContext();
const electron = getElectron();
@@ -30,9 +32,9 @@
</script>
<FormCheckboxField
label={_t('connection.sshTunnel.use', {defaultMessage: "Use SSH tunnel"})}
label={_t('connection.sshTunnel.use', { defaultMessage: 'Use SSH tunnel' })}
name="useSshTunnel"
disabled={isConnected}
disabled={isConnected || isFormReadOnly}
data-testid="ConnectionSshTunnelFields_useSshTunnel"
/>
@@ -41,7 +43,7 @@
<FormTextField
label="Host"
name="sshHost"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshHost"
/>
@@ -50,23 +52,30 @@
<FormTextField
label="Port"
name="sshPort"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
templateProps={{ noMargin: true }}
placeholder="22"
data-testid="ConnectionSshTunnelFields_sshPort"
/>
</div>
</div>
<FormTextField label="Bastion host (Jump host)" name="sshBastionHost" disabled={isConnected || !useSshTunnel} />
<FormTextField
label="Bastion host (Jump host)"
name="sshBastionHost"
disabled={isConnected || !useSshTunnel || isFormReadOnly}
/>
<FormSelectField
label={_t('connection.sshTunnel.authentication', {defaultMessage: "SSH Authentication"})}
label={_t('connection.sshTunnel.authentication', { defaultMessage: 'SSH Authentication' })}
name="sshMode"
isNative
defaultSelectValue="userPassword"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
options={[
{ value: 'userPassword', label: _t('connection.sshTunnel.authMethod.userPassword', {defaultMessage: "Username & password"}) },
{
value: 'userPassword',
label: _t('connection.sshTunnel.authMethod.userPassword', { defaultMessage: 'Username & password' }),
},
{ value: 'agent', label: 'SSH agent' },
{ value: 'keyFile', label: 'Key file' },
]}
@@ -77,7 +86,7 @@
<FormTextField
label="Login"
name="sshLogin"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
data-testid="ConnectionSshTunnelFields_sshLogin"
/>
{/if}
@@ -88,16 +97,16 @@
<FormTextField
label="Login"
name="sshLogin"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshLogin"
/>
</div>
<div class="col-6">
<FormPasswordField
label={_t('connection.password', {defaultMessage: 'Password'})}
label={_t('connection.password', { defaultMessage: 'Password' })}
name="sshPassword"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshPassword"
/>
@@ -110,18 +119,18 @@
<div class="col-6 mr-1">
{#if electron}
<FormElectronFileSelector
label={_t('connection.sshTunnel.privateKeyFile', {defaultMessage: "Private key file"})}
label={_t('connection.sshTunnel.privateKeyFile', { defaultMessage: 'Private key file' })}
name="sshKeyfile"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
templateProps={{ noMargin: true }}
defaultFileName={$platformInfo?.defaultKeyfile}
data-testid="ConnectionSshTunnelFields_sshKeyfile"
/>
{:else}
<FormTextField
label={_t('connection.sshTunnel.privateKeyFilePath', {defaultMessage: "Private key file (path on server)"})}
label={_t('connection.sshTunnel.privateKeyFilePath', { defaultMessage: 'Private key file (path on server)' })}
name="sshKeyfile"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
templateProps={{ noMargin: true }}
placeholder={$platformInfo?.defaultKeyfile}
data-testid="ConnectionSshTunnelFields_sshKeyfile"
@@ -130,9 +139,9 @@
</div>
<div class="col-6">
<FormPasswordField
label={_t('connection.sshTunnel.keyFilePassphrase', {defaultMessage: "Key file passphrase"})}
label={_t('connection.sshTunnel.keyFilePassphrase', { defaultMessage: 'Key file passphrase' })}
name="sshKeyfilePassword"
disabled={isConnected || !useSshTunnel}
disabled={isConnected || !useSshTunnel || isFormReadOnly}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshKeyfilePassword"
/>
@@ -143,9 +152,10 @@
{#if useSshTunnel && $values.sshMode == 'agent'}
<div class="ml-3 mb-3">
{#if $platformInfo && $platformInfo.sshAuthSock}
<FontIcon icon="img ok" /> {_t('connection.sshTunnel.agentFound', {defaultMessage: "SSH Agent found"})}
<FontIcon icon="img ok" /> {_t('connection.sshTunnel.agentFound', { defaultMessage: 'SSH Agent found' })}
{:else}
<FontIcon icon="img error" /> {_t('connection.sshTunnel.agentNotFound', {defaultMessage: "SSH Agent not found"})}
<FontIcon icon="img error" />
{_t('connection.sshTunnel.agentNotFound', { defaultMessage: 'SSH Agent not found' })}
{/if}
</div>
{/if}

View File

@@ -9,6 +9,8 @@
import { openedConnections, openedSingleDatabaseConnections } from '../stores';
import { _t } from '../translations';
export let isFormReadOnly;
const { values, setFieldValue } = getFormContext();
const electron = getElectron();
@@ -16,21 +18,35 @@
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
</script>
<FormCheckboxField label={_t('connection.ssl.use', {defaultMessage: "Use SSL"})} name="useSsl" disabled={isConnected} />
<FormElectronFileSelector label={_t('connection.ssl.caCert', {defaultMessage: "CA Cert (optional)"})} name="sslCaFile" disabled={isConnected || !useSsl || !electron} />
<FormCheckboxField
label={_t('connection.ssl.use', { defaultMessage: 'Use SSL' })}
name="useSsl"
disabled={isConnected || isFormReadOnly}
/>
<FormElectronFileSelector
label={_t('connection.ssl.certificate', {defaultMessage: "Certificate (optional)"})}
label={_t('connection.ssl.caCert', { defaultMessage: 'CA Cert (optional)' })}
name="sslCaFile"
disabled={isConnected || !useSsl || !electron || isFormReadOnly}
/>
<FormElectronFileSelector
label={_t('connection.ssl.certificate', { defaultMessage: 'Certificate (optional)' })}
name="sslCertFile"
disabled={isConnected || !useSsl || !electron}
disabled={isConnected || !useSsl || !electron || isFormReadOnly}
/>
<FormPasswordField
label={_t('connection.ssl.certificateKeyFilePassword', {defaultMessage: "Certificate key file password (optional)"})}
label={_t('connection.ssl.certificateKeyFilePassword', {
defaultMessage: 'Certificate key file password (optional)',
})}
name="sslCertFilePassword"
disabled={isConnected || !useSsl || !electron}
disabled={isConnected || !useSsl || !electron || isFormReadOnly}
/>
<FormElectronFileSelector
label={_t('connection.ssl.keyFile', {defaultMessage: "Key file (optional)"})}
label={_t('connection.ssl.keyFile', { defaultMessage: 'Key file (optional)' })}
name="sslKeyFile"
disabled={isConnected || !useSsl || !electron}
disabled={isConnected || !useSsl || !electron || isFormReadOnly}
/>
<FormCheckboxField
label={_t('connection.ssl.rejectUnauthorized', { defaultMessage: 'Reject unauthorized' })}
name="sslRejectUnauthorized"
disabled={isConnected || !useSsl || isFormReadOnly}
/>
<FormCheckboxField label={_t('connection.ssl.rejectUnauthorized', {defaultMessage: "Reject unauthorized"})} name="sslRejectUnauthorized" disabled={isConnected || !useSsl} />

View File

@@ -59,6 +59,8 @@
}
);
$: isFormReadOnly = !!$values.import_source_id;
// $: console.log('ConnectionTab.$values', $values);
// $: console.log('ConnectionTab.driver', driver);
@@ -302,22 +304,25 @@
{
label: _t('common.general', { defaultMessage: 'General' }),
component: ConnectionDriverFields,
props: { getDatabaseList, currentConnection },
props: { getDatabaseList, currentConnection, isFormReadOnly },
testid: 'ConnectionTab_tabGeneral',
},
driver?.showConnectionTab('sshTunnel', $values) && {
label: 'SSH Tunnel',
component: ConnectionSshTunnelFields,
props: { isFormReadOnly },
testid: 'ConnectionTab_tabSshTunnel',
},
driver?.showConnectionTab('ssl', $values) && {
label: 'SSL',
component: ConnectionSslFields,
props: { isFormReadOnly },
testid: 'ConnectionTab_tabSsl',
},
{
label: _t('common.advanced', { defaultMessage: 'Advanced' }),
component: ConnectionAdvancedDriverFields,
props: { isFormReadOnly },
testid: 'ConnectionTab_tabAdvanced',
},
]}
@@ -383,7 +388,8 @@
{/if}
{#if isTesting}
<div>
<FontIcon icon="icon loading" /> {_t('common.testingConnection', { defaultMessage: 'Testing connection' })}
<FontIcon icon="icon loading" />
{_t('common.testingConnection', { defaultMessage: 'Testing connection' })}
</div>
{/if}
</div>

View File

@@ -124,7 +124,7 @@ export function __t(key: string, options: TranslateOptions): DefferedTranslation
};
}
export function _tval(x: string | DefferedTranslationResult): string {
export function _tval(x: any | DefferedTranslationResult): string {
if (typeof x === 'string') return x;
if (typeof x?._transKey === 'string') {
return _t(x._transKey, x._transOptions);
@@ -132,7 +132,7 @@ export function _tval(x: string | DefferedTranslationResult): string {
if (typeof x?._transCallback === 'function') {
return x._transCallback();
}
return '';
return x?.toString() || '';
}
export function isDefferedTranslationResult(x: string | DefferedTranslationResult): x is DefferedTranslationResult {