Browse Source

Merge pull request #3306 from nocodb/feat/3210-additional-params

feat: additional parameters for external database connections
pull/3349/head
mertmit 2 years ago committed by GitHub
parent
commit
d748f0f28e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 229
      packages/nc-gui-v2/pages/index/index/create-external.vue
  2. 118
      packages/nc-gui-v2/utils/projectCreateUtils.ts
  3. 16
      packages/nc-gui-v2/utils/validation.ts
  4. 16
      packages/nocodb-sdk/src/lib/Api.ts
  5. 14
      packages/nocodb/src/lib/meta/api/utilApis.ts
  6. 132
      packages/nocodb/src/lib/utils/NcConfigFactory.ts
  7. 18
      scripts/sdk/swagger.json

229
packages/nc-gui-v2/pages/index/index/create-external.vue

@ -1,6 +1,9 @@
<script lang="ts" setup>
import { Form, Modal, message } from 'ant-design-vue'
import type { SelectHandler } from 'ant-design-vue/es/vc-select/Select'
import {
CertTypes,
SSLUsage,
clientTypes,
computed,
extractSdkResponseErrorMsg,
@ -14,7 +17,6 @@ import {
projectTitleValidator,
readFile,
ref,
sslUsage,
useApi,
useI18n,
useNuxtApp,
@ -22,12 +24,15 @@ import {
watch,
} from '#imports'
import { ClientType } from '~/lib'
import { DefaultConnection, SQLiteConnection } from '~/utils'
import type { ProjectCreateForm } from '~/utils'
const useForm = Form.useForm
const testSuccess = ref(false)
const form = ref<typeof Form>()
const { api, isLoading } = useApi()
const { $e } = useNuxtApp()
@ -36,14 +41,26 @@ useSidebar({ hasSidebar: false })
const { t } = useI18n()
const formState = $ref<ProjectCreateForm>({
let formState = $ref<ProjectCreateForm>({
title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) },
inflection: {
inflectionColumn: 'camelize',
inflectionTable: 'camelize',
},
sslUse: SSLUsage.No,
extraParameters: [],
})
const customFormState = ref<ProjectCreateForm>({
title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) },
inflection: {
inflectionColumn: 'camelize',
inflectionTable: 'camelize',
},
sslUse: 'No',
sslUse: SSLUsage.No,
extraParameters: [],
})
const validators = computed(() => {
@ -55,6 +72,7 @@ const validators = computed(() => {
},
projectTitleValidator,
],
'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator],
...(formState.dataSource.client === ClientType.SQLITE
? {
@ -77,51 +95,102 @@ const validators = computed(() => {
const { validate, validateInfos } = useForm(formState, validators)
const populateName = (v: string) => {
formState.dataSource.connection.database = `${v.trim()}_noco`
}
const onClientChange = () => {
formState.dataSource = { ...getDefaultConnectionConfig(formState.dataSource.client) }
populateName(formState.title)
}
const inflectionTypes = ['camelize', 'none']
const configEditDlg = ref(false)
const onSSLModeChange = ((mode: SSLUsage) => {
if (formState.dataSource.client !== ClientType.SQLITE) {
const connection = formState.dataSource.connection as DefaultConnection
switch (mode) {
case SSLUsage.No:
delete connection.ssl
break
case SSLUsage.Allowed:
connection.ssl = 'true'
break
default:
connection.ssl = {
ca: '',
cert: '',
key: '',
}
break
}
}
}) as SelectHandler
const updateSSLUse = () => {
if (formState.dataSource.client !== ClientType.SQLITE) {
const connection = formState.dataSource.connection as DefaultConnection
if (connection.ssl) {
if (typeof connection.ssl === 'string') {
formState.sslUse = SSLUsage.Allowed
} else {
formState.sslUse = SSLUsage.Preferred
}
} else {
formState.sslUse = SSLUsage.No
}
}
}
// populate database name based on title
watch(
() => formState.title,
(v) => (formState.dataSource.connection.database = `${v?.trim()}_noco`),
)
const addNewParam = () => {
formState.extraParameters.push({ key: '', value: '' })
}
// generate a random project title
formState.title = generateUniqueName()
const removeParam = (index: number) => {
formState.extraParameters.splice(index, 1)
}
const inflectionTypes = ['camelize', 'none']
const importURL = ref('')
const configEditDlg = ref(false)
const importURLDlg = ref(false)
const caFileInput = ref<HTMLInputElement>()
const keyFileInput = ref<HTMLInputElement>()
const certFileInput = ref<HTMLInputElement>()
const onFileSelect = (key: 'ca' | 'cert' | 'key', el: HTMLInputElement) => {
const onFileSelect = (key: CertTypes, el?: HTMLInputElement) => {
if (!el) return
readFile(el, (content) => {
if ('ssl' in formState.dataSource.connection && formState.dataSource.connection.ssl)
if ('ssl' in formState.dataSource.connection && typeof formState.dataSource.connection.ssl === 'object')
formState.dataSource.connection.ssl[key] = content ?? ''
})
}
const sslFilesRequired = computed<boolean>(() => {
return formState?.sslUse && formState.sslUse !== 'No'
})
const sslFilesRequired = computed(
() => !!formState.sslUse && formState.sslUse !== SSLUsage.No && formState.sslUse !== SSLUsage.Allowed,
)
function getConnectionConfig() {
const extraParameters = Object.fromEntries(new Map(formState.extraParameters.map((object) => [object.key, object.value])))
const connection = {
...formState.dataSource.connection,
...extraParameters,
}
if ('ssl' in connection && connection.ssl && (!sslFilesRequired || Object.values(connection.ssl).every((v) => !v))) {
delete connection.ssl
if ('ssl' in connection && connection.ssl) {
if (
formState.sslUse === SSLUsage.No ||
(typeof connection.ssl === 'object' && Object.values(connection.ssl).every((v) => !v))
) {
delete connection.ssl
}
}
return connection
}
const form = ref<any>()
const focusInvalidInput = () => {
form?.value?.$el.querySelector('.ant-form-item-explain-error')?.parentNode?.parentNode?.querySelector('input')?.focus()
form.value?.$el.querySelector('.ant-form-item-explain-error')?.parentNode?.parentNode?.querySelector('input')?.focus()
}
const createProject = async () => {
@ -209,6 +278,32 @@ const testConnection = async () => {
}
}
const handleImportURL = async () => {
if (!importURL.value || importURL.value === '') return
const connectionConfig = await api.utils.urlToConfig({ url: importURL.value })
if (connectionConfig) {
formState.dataSource.client = connectionConfig.client
formState.dataSource.connection = { ...connectionConfig.connection }
} else {
message.error('Invalid URL')
}
importURLDlg.value = false
updateSSLUse()
}
const handleEditJSON = () => {
customFormState.value = { ...formState }
configEditDlg.value = true
}
const handleOk = () => {
formState = { ...customFormState.value }
configEditDlg.value = false
updateSSLUse()
}
// reset test status on config change
watch(
() => formState.dataSource,
@ -216,8 +311,15 @@ watch(
{ deep: true },
)
// populate database name based on title
watch(
() => formState.title,
(v) => populateName(v),
)
// select and focus title field on load
onMounted(() => {
formState.title = generateUniqueName()
nextTick(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
@ -270,28 +372,34 @@ onMounted(() => {
:label="$t('labels.sqliteFile')"
v-bind="validateInfos['dataSource.connection.connection.filename']"
>
<a-input v-model:value="formState.dataSource.connection.connection.filename" />
<a-input v-model:value="(formState.dataSource.connection as SQLiteConnection).connection.filename" />
</a-form-item>
<template v-else>
<!-- Host Address -->
<a-form-item :label="$t('labels.hostAddress')" v-bind="validateInfos['dataSource.connection.host']">
<a-input v-model:value="formState.dataSource.connection.host" class="nc-extdb-host-address" />
<a-input v-model:value="(formState.dataSource.connection as DefaultConnection).host" class="nc-extdb-host-address" />
</a-form-item>
<!-- Port Number -->
<a-form-item :label="$t('labels.port')" v-bind="validateInfos['dataSource.connection.port']">
<a-input-number v-model:value="formState.dataSource.connection.port" class="!w-full nc-extdb-host-port" />
<a-input-number
v-model:value="(formState.dataSource.connection as DefaultConnection).port"
class="!w-full nc-extdb-host-port"
/>
</a-form-item>
<!-- Username -->
<a-form-item :label="$t('labels.username')" v-bind="validateInfos['dataSource.connection.user']">
<a-input v-model:value="formState.dataSource.connection.user" class="nc-extdb-host-user" />
<a-input v-model:value="(formState.dataSource.connection as DefaultConnection).user" class="nc-extdb-host-user" />
</a-form-item>
<!-- Password -->
<a-form-item :label="$t('labels.password')">
<a-input-password v-model:value="formState.dataSource.connection.password" class="nc-extdb-host-password" />
<a-input-password
v-model:value="(formState.dataSource.connection as DefaultConnection).password"
class="nc-extdb-host-password"
/>
</a-form-item>
<!-- Database -->
@ -314,11 +422,19 @@ onMounted(() => {
</a-form-item>
<a-collapse ghost expand-icon-position="right" class="!mt-6">
<a-collapse-panel key="1" :header="$t('title.advancedParameters')">
<a-collapse-panel key="1">
<template #header>
<div class="flex items-center gap-2">
<a-button type="default" class="nc-extdb-btn-import-url" @click.stop="importURLDlg = true">
Use Connection URL
</a-button>
<span>{{ $t('title.advancedParameters') }}</span>
</div>
</template>
<!-- todo: add in i18n -->
<a-form-item label="SSL mode">
<a-select v-model:value="formState.sslUse" @change="onClientChange">
<a-select-option v-for="opt in sslUsage" :key="opt" :value="opt">{{ opt }}</a-select-option>
<a-select v-model:value="formState.sslUse" @select="onSSLModeChange">
<a-select-option v-for="opt in Object.values(SSLUsage)" :key="opt" :value="opt">{{ opt }}</a-select-option>
</a-select>
</a-form-item>
@ -330,7 +446,7 @@ onMounted(() => {
<span>{{ $t('tooltip.clientCert') }}</span>
</template>
<a-button :disabled="!sslFilesRequired" class="shadow" @click="certFileInput.click()">
<a-button :disabled="!sslFilesRequired" class="shadow" @click="certFileInput?.click()">
{{ $t('labels.clientCert') }}
</a-button>
</a-tooltip>
@ -340,7 +456,7 @@ onMounted(() => {
<template #title>
<span>{{ $t('tooltip.clientKey') }}</span>
</template>
<a-button :disabled="!sslFilesRequired" class="shadow" @click="keyFileInput.click()">
<a-button :disabled="!sslFilesRequired" class="shadow" @click="keyFileInput?.click()">
{{ $t('labels.clientKey') }}
</a-button>
</a-tooltip>
@ -351,33 +467,56 @@ onMounted(() => {
<span>{{ $t('tooltip.clientCA') }}</span>
</template>
<a-button :disabled="!sslFilesRequired" class="shadow" @click="caFileInput.click()">
<a-button :disabled="!sslFilesRequired" class="shadow" @click="caFileInput?.click()">
{{ $t('labels.serverCA') }}
</a-button>
</a-tooltip>
</div>
</a-form-item>
<input ref="caFileInput" type="file" class="!hidden" @change="onFileSelect('ca', caFileInput)" />
<input ref="caFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.ca, caFileInput)" />
<input ref="certFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.cert, certFileInput)" />
<input ref="certFileInput" type="file" class="!hidden" @change="onFileSelect('cert', certFileInput)" />
<input ref="keyFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.key, keyFileInput)" />
<input ref="keyFileInput" type="file" class="!hidden" @change="onFileSelect('key', keyFileInput)" />
<a-divider />
<a-form-item class="mb-2" label="Extra connection parameters" v-bind="validateInfos.extraParameters">
<a-card>
<div v-for="(item, index) of formState.extraParameters" :key="index">
<div class="flex py-1 items-center gap-1">
<a-input v-model:value="item.key" />
<span>:</span>
<a-input v-model:value="item.value" />
<MdiClose :style="{ 'font-size': '1.5em', 'color': 'red' }" @click="removeParam(index)" />
</div>
</div>
<a-button type="dashed" class="w-full caption mt-2" @click="addNewParam">
<div class="flex items-center justify-center"><MdiPlus /></div>
</a-button>
</a-card>
</a-form-item>
<a-divider />
<a-form-item :label="$t('labels.inflection.tableName')">
<a-select v-model:value="formState.inflection.inflectionTable" @change="onClientChange">
<a-select v-model:value="formState.inflection.inflectionTable">
<a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('labels.inflection.columnName')">
<a-select v-model:value="formState.inflection.inflectionColumn" @change="onClientChange">
<a-select v-model:value="formState.inflection.inflectionColumn">
<a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option>
</a-select>
</a-form-item>
<div class="flex justify-end">
<a-button class="!shadow-md" @click="configEditDlg = true">
<a-button class="!shadow-md" @click="handleEditJSON()">
<!-- Edit connection JSON -->
{{ $t('activity.editConnJson') }}
</a-button>
@ -399,13 +538,13 @@ onMounted(() => {
</a-form-item>
</a-form>
<!-- todo: needs replacement
<v-dialog v-model="configEditDlg">
<a-card>
<MonacoEditor v-if="configEditDlg" v-model="formState" class="h-[400px] w-[600px]" />
</a-card>
</v-dialog>
-->
<a-modal v-model:visible="configEditDlg" :title="$t('activity.editConnJson')" width="600px" @ok="handleOk">
<MonacoEditor v-if="configEditDlg" v-model="customFormState" class="h-[400px] w-full" />
</a-modal>
<a-modal v-model:visible="importURLDlg" title="Use Connection URL" width="600px" @ok="handleImportURL">
<a-input v-model:value="importURL" />
</a-modal>
</div>
</template>

118
packages/nc-gui-v2/utils/projectCreateUtils.ts

@ -4,30 +4,33 @@ export interface ProjectCreateForm {
title: string
dataSource: {
client: ClientType
connection:
| {
host: string
database: string
user: string
password: string
port: number | string
ssl?: Record<string, string>
}
| {
client?: ClientType.SQLITE
database: string
connection?: {
filename?: string
}
useNullAsDefault?: boolean
}
connection: DefaultConnection | SQLiteConnection
searchPath?: string[]
}
inflection: {
inflectionColumn?: string
inflectionTable?: string
}
sslUse?: any
sslUse?: SSLUsage
extraParameters: { key: string; value: string }[]
}
export interface DefaultConnection {
host: string
database: string
user: string
password: string
port: number | string
ssl?: Record<CertTypes, string> | 'true'
}
export interface SQLiteConnection {
client: ClientType.SQLITE
database: string
connection: {
filename?: string
}
useNullAsDefault?: boolean
}
const defaultHost = 'localhost'
@ -66,18 +69,23 @@ export const clientTypes = [
]
const homeDir = ''
const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataSource']['connection']> = {
type ConnectionClientType =
| Exclude<ClientType, ClientType.SQLITE>
| 'tidb'
| 'yugabyte'
| 'citusdb'
| 'cockroachdb'
| 'oracledb'
| 'greenplum'
const sampleConnectionData: { [key in ConnectionClientType]: DefaultConnection } & { [ClientType.SQLITE]: SQLiteConnection } = {
[ClientType.PG]: {
host: defaultHost,
port: '5432',
user: 'postgres',
password: 'password',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
[ClientType.MYSQL]: {
host: defaultHost,
@ -85,11 +93,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'root',
password: 'password',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
[ClientType.VITESS]: {
host: defaultHost,
@ -97,11 +100,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'root',
password: 'password',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
[ClientType.MSSQL]: {
host: defaultHost,
@ -109,11 +107,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'sa',
password: 'Password123.',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
[ClientType.SQLITE]: {
client: ClientType.SQLITE,
@ -129,11 +122,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'root',
password: '',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
yugabyte: {
host: defaultHost,
@ -141,11 +129,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'postgres',
password: '',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
citusdb: {
host: defaultHost,
@ -153,11 +136,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'postgres',
password: '',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
cockroachdb: {
host: defaultHost,
@ -165,11 +143,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'postgres',
password: '',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
greenplum: {
host: defaultHost,
@ -177,11 +150,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'postgres',
password: '',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
oracledb: {
host: defaultHost,
@ -189,11 +157,6 @@ const sampleConnectionData: Record<ClientType | string, ProjectCreateForm['dataS
user: 'system',
password: 'Oracle18',
database: '_test',
ssl: {
ca: '',
key: '',
cert: '',
},
},
}
@ -209,4 +172,19 @@ export const getDefaultConnectionConfig = (client: ClientType): ProjectCreateFor
}
}
export const sslUsage = ['No', 'Preferred', 'Required', 'Required-CA', 'Required-IDENTITY']
enum SSLUsage {
No = 'No',
Allowed = 'Allowed',
Preferred = 'Preferred',
Required = 'Required',
RequiredWithCa = 'Required-CA',
RequiredWithIdentity = 'Required-Identity',
}
enum CertTypes {
ca = 'ca',
cert = 'cert',
key = 'key',
}
export { SSLUsage, CertTypes }

16
packages/nc-gui-v2/utils/validation.ts

@ -127,3 +127,19 @@ export const importExcelUrlValidator = {
})
},
}
export const extraParameterValidator = {
validator: (_: unknown, value: { key: string; value: string }[]) => {
return new Promise((resolve, reject) => {
for (const param of value) {
if (param.key === '') {
return reject(new Error('Parameter key cannot be empty'))
}
if (value.filter((el: any) => el.key === param.key).length !== 1) {
return reject(new Error('Duplicate parameter keys are not allowed'))
}
}
return resolve(true)
})
},
}

16
packages/nocodb-sdk/src/lib/Api.ts

@ -3210,6 +3210,22 @@ export class Api<
...params,
}),
/**
* No description
*
* @tags Utils
* @name UrlToConfig
* @request POST:/api/v1/url_to_config
*/
urlToConfig: (data: any, params: RequestParams = {}) =>
this.request<any, any>({
path: `/api/v1/url_to_config`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
}),
/**
* No description
*

14
packages/nocodb/src/lib/meta/api/utilApis.ts

@ -4,7 +4,7 @@ import { Request, Response } from 'express';
import { packageVersion } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import SqlMgrv2 from '../../db/sql-mgr/v2/SqlMgrv2';
import { defaultConnectionConfig } from '../../utils/NcConfigFactory';
import NcConfigFactory, { defaultConnectionConfig } from '../../utils/NcConfigFactory';
import User from '../../models/User';
import catchError from '../helpers/catchError';
import axios from 'axios';
@ -147,6 +147,17 @@ export async function axiosRequestMake(req: Request, res: Response) {
return await _axiosRequestMake(req, res);
}
export async function urlToDbConfig(req: Request, res: Response) {
const { url } = req.body;
try {
let connectionConfig;
connectionConfig = NcConfigFactory.extractXcUrlFromJdbc(url, true);
return res.json(connectionConfig);
} catch (error) {
return res.sendStatus(500)
}
}
export default (router) => {
router.post(
'/api/v1/db/meta/connection/test',
@ -157,4 +168,5 @@ export default (router) => {
router.get('/api/v1/version', catchError(versionInfo));
router.get('/api/v1/health', catchError(appHealth));
router.get('/api/v1/feedback_form', catchError(feedbackFormGet));
router.post('/api/v1/url_to_config', catchError(urlToDbConfig));
};

132
packages/nocodb/src/lib/utils/NcConfigFactory.ts

@ -21,6 +21,7 @@ const {
const driverClientMapping = {
mysql: 'mysql2',
postgres: 'pg',
postgresql: 'pg',
sqlite: 'sqlite3',
mssql: 'mssql',
};
@ -39,6 +40,45 @@ const defaultConnectionConfig: any = {
dateStrings: true,
};
const knownQueryParams = [
{
parameter: 'database',
aliases: ['d', 'db'],
},
{
parameter: 'password',
aliases: ['p'],
},
{
parameter: 'user',
aliases: ['u'],
},
{
parameter: 'title',
aliases: ['t'],
},
{
parameter: 'keyFilePath',
aliases: []
},
{
parameter: 'certFilePath',
aliases: []
},
{
parameter: 'caFilePath',
aliases: []
},
{
parameter: 'ssl',
aliases: []
},
{
parameter: 'options',
aliases: ['opt', 'opts']
},
];
export default class NcConfigFactory implements NcConfig {
public static make(): NcConfig {
this.jdbcToXcUrl();
@ -155,17 +195,21 @@ export default class NcConfigFactory implements NcConfig {
},
} as any;
} else {
const parsedQuery = {}
for (const [key, value] of url.searchParams.entries()) {
const fnd = knownQueryParams.find((param) => param.parameter === key || param.aliases.includes(key))
if (fnd) {
parsedQuery[fnd.parameter] = value;
} else {
parsedQuery[key] = value;
}
}
dbConfig = {
client: url.protocol.replace(':', ''),
connection: {
...defaultConnectionConfig,
database:
url.searchParams.get('d') || url.searchParams.get('database'),
host: url.hostname,
password:
url.searchParams.get('p') || url.searchParams.get('password'),
port: +url.port,
user: url.searchParams.get('u') || url.searchParams.get('user'),
...parsedQuery
},
// pool: {
// min: 1,
@ -259,17 +303,21 @@ export default class NcConfigFactory implements NcConfig {
: {}),
};
} else {
const parsedQuery = {}
for (const [key, value] of url.searchParams.entries()) {
const fnd = knownQueryParams.find((param) => param.parameter === key || param.aliases.includes(key))
if (fnd) {
parsedQuery[fnd.parameter] = value;
} else {
parsedQuery[key] = value;
}
}
dbConfig = {
client: url.protocol.replace(':', ''),
connection: {
...defaultConnectionConfig,
database:
url.searchParams.get('d') || url.searchParams.get('database'),
host: url.hostname,
password:
url.searchParams.get('p') || url.searchParams.get('password'),
port: +url.port,
user: url.searchParams.get('u') || url.searchParams.get('user'),
...parsedQuery
},
acquireConnectionTimeout: 600000,
...(url.searchParams.has('search_path')
@ -588,17 +636,55 @@ export default class NcConfigFactory implements NcConfig {
}
}
public static extractXcUrlFromJdbc(url: string) {
public static extractXcUrlFromJdbc(url: string, rtConfig: boolean = false) {
// drop the jdbc prefix
if (url.startsWith('jdbc:')) {
url = url.substring(5);
}
const config = parseDbUrl(url);
const port = config.port || defaultClientPortMapping[config.driver];
const res = `${driverClientMapping[config.driver] || config.driver}://${
config.host
}${port ? `:${port}` : ''}?p=${config.password}&u=${config.user}&d=${
config.database
}`;
if (config.search_path) {
return `${res}&search_path=${config.search_path}`;
const parsedConfig: { driver?: string, host?: string, port?: string, database?: string, user?:string, password?: string, ssl?: string } = {}
for (const [key, value] of Object.entries(config)) {
const fnd = knownQueryParams.find((param) => param.parameter === key || param.aliases.includes(key))
if (fnd) {
parsedConfig[fnd.parameter] = value;
} else {
parsedConfig[key] = value;
}
}
if (!parsedConfig?.port) parsedConfig.port = defaultClientPortMapping[driverClientMapping[parsedConfig.driver] || parsedConfig.driver];
if (rtConfig) {
const { driver, ...connectionConfig } = parsedConfig;
const client = driverClientMapping[driver] || driver;
const avoidSSL = ['localhost', '127.0.0.1', 'host.docker.internal', '172.17. 0.1']
if (client === 'pg' && !connectionConfig?.ssl && !avoidSSL.includes(connectionConfig.host)) {
connectionConfig.ssl = 'true';
}
return {
client: client,
connection: {
...connectionConfig
}
} as any;
}
const { driver, host, port, database, user, password, ...extra } = parsedConfig;
const extraParams = [];
for (const [key, value] of Object.entries(extra)) {
extraParams.push(`${key}=${value}`);
}
const res = `${driverClientMapping[driver] || driver}://${host}${port ? `:${port}` : ''}?${user ? `u=${user}&` : ''}${password ? `p=${password}&` : ''}${database ? `d=${database}&` : ''}${extraParams.join('&')}`;
return res;
}

18
scripts/sdk/swagger.json

@ -5204,6 +5204,24 @@
"description": ""
}
},
"/api/v1/url_to_config": {
"parameters": [],
"post": {
"summary": "",
"operationId": "utils-url-to-config",
"tags": [
"Utils"
],
"requestBody": {
"content": {
"application/json": {
"schema": {}
}
}
},
"description": ""
}
},
"/api/v1/db/meta/nocodb/info": {
"parameters": [],
"get": {

Loading…
Cancel
Save