Browse Source

Merge pull request #3538 from nocodb/develop

pull/3539/head
github-actions[bot] 2 years ago committed by GitHub
parent
commit
320f70d446
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      crowdin.yml
  2. 24
      packages/nc-gui/components/general/ReleaseInfo.vue
  3. 5
      packages/nc-gui/composables/useApi/index.ts
  4. 3
      packages/nc-gui/composables/useGlobal/state.ts
  5. 1
      packages/nc-gui/lib/constants.ts
  6. 2
      packages/nc-gui/pages/index/index/create-external.vue
  7. 29
      packages/nocodb/src/__tests__/noco/NcConfigFactory.test.ts
  8. 77
      packages/nocodb/src/lib/utils/NcConfigFactory.ts

3
crowdin.yml

@ -0,0 +1,3 @@
files:
- source: /packages/nc-gui/lang/en.json
translation: /packages/nc-gui/lang/%two_letters_code%.json

24
packages/nc-gui/components/general/ReleaseInfo.vue

@ -6,13 +6,19 @@ const { $api } = useNuxtApp()
const { currentVersion, latestRelease, hiddenRelease } = useGlobal() const { currentVersion, latestRelease, hiddenRelease } = useGlobal()
const releaseAlert = computed( const releaseAlert = computed({
() => get() {
return (
currentVersion.value && currentVersion.value &&
latestRelease.value && latestRelease.value &&
currentVersion.value !== latestRelease.value && currentVersion.value !== latestRelease.value &&
latestRelease.value !== hiddenRelease.value, latestRelease.value !== hiddenRelease.value
) )
},
set(val) {
hiddenRelease.value = val ? null : latestRelease.value
},
})
async function fetchReleaseInfo() { async function fetchReleaseInfo() {
try { try {
@ -35,7 +41,7 @@ onMounted(async () => await fetchReleaseInfo())
<template> <template>
<div v-if="releaseAlert" class="flex items-center"> <div v-if="releaseAlert" class="flex items-center">
<a-dropdown :trigger="['click']" placement="bottom"> <a-dropdown :trigger="['click']" placement="bottom">
<a-button class="bg-primary border-none"> <a-button class="!bg-primary !border-none">
<div class="flex gap-1 items-center text-white"> <div class="flex gap-1 items-center text-white">
<span class="text-sm font-weight-medium">{{ $t('activity.upgrade.available') }}</span> <span class="text-sm font-weight-medium">{{ $t('activity.upgrade.available') }}</span>
<mdi-menu-down /> <mdi-menu-down />
@ -43,21 +49,21 @@ onMounted(async () => await fetchReleaseInfo())
</a-button> </a-button>
<template #overlay> <template #overlay>
<div class="mt-1 bg-white shadow-lg !border"> <div class="mt-1 bg-white shadow-lg !border">
<nuxt-link class="text-primary" to="https://github.com/nocodb/nocodb/releases" target="_blank"> <nuxt-link class="!text-primary !no-underline" to="https://github.com/nocodb/nocodb/releases" target="_blank">
<div class="nc-menu-item"> <div class="nc-menu-item">
<mdi-script-text-outline /> <mdi-script-text-outline />
{{ latestRelease }} {{ $t('activity.upgrade.releaseNote') }} {{ latestRelease }} {{ $t('activity.upgrade.releaseNote') }}
</div> </div>
</nuxt-link> </nuxt-link>
<nuxt-link class="text-primary" to="https://docs.nocodb.com/getting-started/upgrading" target="_blank"> <nuxt-link class="!text-primary !no-underline" to="https://docs.nocodb.com/getting-started/upgrading" target="_blank">
<div class="nc-menu-item"> <div class="nc-menu-item">
<mdi-rocket-launch-outline /> <mdi-rocket-launch-outline />
<!-- How to upgrade? --> <!-- How to upgrade? -->
{{ $t('activity.upgrade.howTo') }} {{ $t('activity.upgrade.howTo') }}
</div> </div>
</nuxt-link> </nuxt-link>
<a-divider class="m-0" /> <a-divider class="!m-0" />
<div class="nc-menu-item" @click="latestRelease = null"> <div class="nc-menu-item" @click="releaseAlert = false">
<mdi-close /> <mdi-close />
<!-- Hide menu --> <!-- Hide menu -->
{{ $t('general.hideMenu') }} {{ $t('general.hideMenu') }}

5
packages/nc-gui/composables/useApi/index.ts

@ -3,14 +3,15 @@ import { Api } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { CreateApiOptions, UseApiProps, UseApiReturn } from './types' import type { CreateApiOptions, UseApiProps, UseApiReturn } from './types'
import { addAxiosInterceptors } from './interceptors' import { addAxiosInterceptors } from './interceptors'
import { BASE_URL } from '~/lib'
import { createEventHook, ref, unref, useCounter, useGlobal, useNuxtApp } from '#imports' import { createEventHook, ref, unref, useCounter, useGlobal, useNuxtApp } from '#imports'
export function createApiInstance<SecurityDataType = any>(options: CreateApiOptions = {}): Api<SecurityDataType> { export function createApiInstance<SecurityDataType = any>({ baseURL = BASE_URL }: CreateApiOptions = {}): Api<SecurityDataType> {
const { appInfo } = $(useGlobal()) const { appInfo } = $(useGlobal())
return addAxiosInterceptors( return addAxiosInterceptors(
new Api<SecurityDataType>({ new Api<SecurityDataType>({
baseURL: options.baseURL ?? appInfo.ncSiteUrl, baseURL: baseURL ?? appInfo.ncSiteUrl,
}), }),
) )
} }

3
packages/nc-gui/composables/useGlobal/state.ts

@ -2,6 +2,7 @@ import { usePreferredLanguages, useStorage } from '@vueuse/core'
import { useJwt } from '@vueuse/integrations/useJwt' import { useJwt } from '@vueuse/integrations/useJwt'
import type { JwtPayload } from 'jwt-decode' import type { JwtPayload } from 'jwt-decode'
import type { AppInfo, State, StoredState } from './types' import type { AppInfo, State, StoredState } from './types'
import { BASE_URL } from '~/lib'
import { computed, ref, toRefs, useCounter, useNuxtApp, useTimestamp } from '#imports' import { computed, ref, toRefs, useCounter, useNuxtApp, useTimestamp } from '#imports'
import type { User } from '~/lib' import type { User } from '~/lib'
@ -77,7 +78,7 @@ export function useGlobalState(storageKey = 'nocodb-gui-v2'): State {
}) })
const appInfo = ref<AppInfo>({ const appInfo = ref<AppInfo>({
ncSiteUrl: process.env.NC_BACKEND_URL || (process.env.NODE_ENV === 'production' ? '..' : 'http://localhost:8080'), ncSiteUrl: BASE_URL,
authType: 'jwt', authType: 'jwt',
connectToExternalDB: false, connectToExternalDB: false,
defaultLimit: 0, defaultLimit: 0,

1
packages/nc-gui/lib/constants.ts

@ -1,3 +1,4 @@
export const NOCO = 'noco' export const NOCO = 'noco'
export const USER_PROJECT_ROLES = 'user_project_roles' export const USER_PROJECT_ROLES = 'user_project_roles'
export const SYSTEM_COLUMNS = ['id', 'title', 'created_at', 'updated_at'] export const SYSTEM_COLUMNS = ['id', 'title', 'created_at', 'updated_at']
export const BASE_URL = process.env.NC_BACKEND_URL || (process.env.NODE_ENV === 'production' ? '..' : 'http://localhost:8080')

2
packages/nc-gui/pages/index/index/create-external.vue

@ -181,7 +181,7 @@ function getConnectionConfig() {
if ('ssl' in connection && connection.ssl) { if ('ssl' in connection && connection.ssl) {
if ( if (
formState.sslUse === SSLUsage.No || formState.sslUse === SSLUsage.No ||
(typeof connection.ssl === 'object' && Object.values(connection.ssl).every((v) => !v)) (typeof connection.ssl === 'object' && Object.values(connection.ssl).every((v) => v === null || v === undefined))
) { ) {
delete connection.ssl delete connection.ssl
} }

29
packages/nocodb/src/__tests__/noco/NcConfigFactory.test.ts

@ -38,6 +38,35 @@ describe('Config Factory Tests', () => {
expect(dbConfig).to.deep.equal(rest); expect(dbConfig).to.deep.equal(rest);
done(); done();
}); });
it('Generate config from DATABASE_URL', function (done) {
// postgres url
const ncDbUrl = NcConfigFactory.extractXcUrlFromJdbc(
'postgres://username:password@host:5432/db'
);
expect(ncDbUrl).to.be.equal('pg://host:5432?u=username&p=password&d=db&');
// postgres url without port
const ncDbUrl1 = NcConfigFactory.extractXcUrlFromJdbc(
'postgres://username:password@host/db'
);
expect(ncDbUrl1).to.be.equal('pg://host:5432?u=username&p=password&d=db&');
// mysql url
const ncDbUrl2 = NcConfigFactory.extractXcUrlFromJdbc(
'jdbc:mysql://localhost/sample_db'
);
expect(ncDbUrl2).to.be.equal('mysql2://localhost:3306?d=sample_db&');
// mariadb url
const ncDbUrl3 = NcConfigFactory.extractXcUrlFromJdbc(
'jdbc:mariadb://localhost/sample_db'
);
expect(ncDbUrl3).to.be.equal('mysql2://localhost:3306?d=sample_db&');
done();
});
it('Connection string with nested property', function (done) { it('Connection string with nested property', function (done) {
const dbConfig = NcConfigFactory.metaUrlToDbConfig( const dbConfig = NcConfigFactory.metaUrlToDbConfig(
`pg://localhost:5432?u=postgres&p=xgene&d=abcde&pool.min=1&pool.max=2&ssl.rejectUnauthorized=false` `pg://localhost:5432?u=postgres&p=xgene&d=abcde&pool.min=1&pool.max=2&ssl.rejectUnauthorized=false`

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

@ -20,6 +20,7 @@ const {
const driverClientMapping = { const driverClientMapping = {
mysql: 'mysql2', mysql: 'mysql2',
mariadb: 'mysql2',
postgres: 'pg', postgres: 'pg',
postgresql: 'pg', postgresql: 'pg',
sqlite: 'sqlite3', sqlite: 'sqlite3',
@ -59,23 +60,23 @@ const knownQueryParams = [
}, },
{ {
parameter: 'keyFilePath', parameter: 'keyFilePath',
aliases: [] aliases: [],
}, },
{ {
parameter: 'certFilePath', parameter: 'certFilePath',
aliases: [] aliases: [],
}, },
{ {
parameter: 'caFilePath', parameter: 'caFilePath',
aliases: [] aliases: [],
}, },
{ {
parameter: 'ssl', parameter: 'ssl',
aliases: [] aliases: [],
}, },
{ {
parameter: 'options', parameter: 'options',
aliases: ['opt', 'opts'] aliases: ['opt', 'opts'],
}, },
]; ];
@ -195,9 +196,11 @@ export default class NcConfigFactory implements NcConfig {
}, },
} as any; } as any;
} else { } else {
const parsedQuery = {} const parsedQuery = {};
for (const [key, value] of url.searchParams.entries()) { for (const [key, value] of url.searchParams.entries()) {
const fnd = knownQueryParams.find((param) => param.parameter === key || param.aliases.includes(key)) const fnd = knownQueryParams.find(
(param) => param.parameter === key || param.aliases.includes(key)
);
if (fnd) { if (fnd) {
parsedQuery[fnd.parameter] = value; parsedQuery[fnd.parameter] = value;
} else { } else {
@ -209,7 +212,8 @@ export default class NcConfigFactory implements NcConfig {
client: url.protocol.replace(':', ''), client: url.protocol.replace(':', ''),
connection: { connection: {
...defaultConnectionConfig, ...defaultConnectionConfig,
...parsedQuery ...parsedQuery,
host: url.hostname,
}, },
// pool: { // pool: {
// min: 1, // min: 1,
@ -303,9 +307,11 @@ export default class NcConfigFactory implements NcConfig {
: {}), : {}),
}; };
} else { } else {
const parsedQuery = {} const parsedQuery = {};
for (const [key, value] of url.searchParams.entries()) { for (const [key, value] of url.searchParams.entries()) {
const fnd = knownQueryParams.find((param) => param.parameter === key || param.aliases.includes(key)) const fnd = knownQueryParams.find(
(param) => param.parameter === key || param.aliases.includes(key)
);
if (fnd) { if (fnd) {
parsedQuery[fnd.parameter] = value; parsedQuery[fnd.parameter] = value;
} else { } else {
@ -317,7 +323,8 @@ export default class NcConfigFactory implements NcConfig {
client: url.protocol.replace(':', ''), client: url.protocol.replace(':', ''),
connection: { connection: {
...defaultConnectionConfig, ...defaultConnectionConfig,
...parsedQuery ...parsedQuery,
host: url.hostname,
}, },
acquireConnectionTimeout: 600000, acquireConnectionTimeout: 600000,
...(url.searchParams.has('search_path') ...(url.searchParams.has('search_path')
@ -636,7 +643,7 @@ export default class NcConfigFactory implements NcConfig {
} }
} }
public static extractXcUrlFromJdbc(url: string, rtConfig: boolean = false) { public static extractXcUrlFromJdbc(url: string, rtConfig = false) {
// drop the jdbc prefix // drop the jdbc prefix
if (url.startsWith('jdbc:')) { if (url.startsWith('jdbc:')) {
url = url.substring(5); url = url.substring(5);
@ -644,9 +651,19 @@ export default class NcConfigFactory implements NcConfig {
const config = parseDbUrl(url); const config = parseDbUrl(url);
const parsedConfig: { driver?: string, host?: string, port?: string, database?: string, user?:string, password?: string, ssl?: string } = {} const parsedConfig: {
driver?: string;
host?: string;
port?: string;
database?: string;
user?: string;
password?: string;
ssl?: string;
} = {};
for (const [key, value] of Object.entries(config)) { for (const [key, value] of Object.entries(config)) {
const fnd = knownQueryParams.find((param) => param.parameter === key || param.aliases.includes(key)) const fnd = knownQueryParams.find(
(param) => param.parameter === key || param.aliases.includes(key)
);
if (fnd) { if (fnd) {
parsedConfig[fnd.parameter] = value; parsedConfig[fnd.parameter] = value;
} else { } else {
@ -654,28 +671,42 @@ export default class NcConfigFactory implements NcConfig {
} }
} }
if (!parsedConfig?.port) parsedConfig.port = defaultClientPortMapping[driverClientMapping[parsedConfig.driver] || parsedConfig.driver]; if (!parsedConfig?.port)
parsedConfig.port =
defaultClientPortMapping[
driverClientMapping[parsedConfig.driver] || parsedConfig.driver
];
if (rtConfig) { if (rtConfig) {
const { driver, ...connectionConfig } = parsedConfig; const { driver, ...connectionConfig } = parsedConfig;
const client = driverClientMapping[driver] || driver; const client = driverClientMapping[driver] || driver;
const avoidSSL = ['localhost', '127.0.0.1', 'host.docker.internal', '172.17. 0.1'] const avoidSSL = [
'localhost',
'127.0.0.1',
'host.docker.internal',
'172.17.0.1',
];
if (client === 'pg' && !connectionConfig?.ssl && !avoidSSL.includes(connectionConfig.host)) { if (
client === 'pg' &&
!connectionConfig?.ssl &&
!avoidSSL.includes(connectionConfig.host)
) {
connectionConfig.ssl = 'true'; connectionConfig.ssl = 'true';
} }
return { return {
client: client, client: client,
connection: { connection: {
...connectionConfig ...connectionConfig,
} },
} as any; } as any;
} }
const { driver, host, port, database, user, password, ...extra } = parsedConfig; const { driver, host, port, database, user, password, ...extra } =
parsedConfig;
const extraParams = []; const extraParams = [];
@ -683,7 +714,11 @@ export default class NcConfigFactory implements NcConfig {
extraParams.push(`${key}=${value}`); extraParams.push(`${key}=${value}`);
} }
const res = `${driverClientMapping[driver] || driver}://${host}${port ? `:${port}` : ''}?${user ? `u=${user}&` : ''}${password ? `p=${password}&` : ''}${database ? `d=${database}&` : ''}${extraParams.join('&')}`; const res = `${driverClientMapping[driver] || driver}://${host}${
port ? `:${port}` : ''
}?${user ? `u=${user}&` : ''}${password ? `p=${password}&` : ''}${
database ? `d=${database}&` : ''
}${extraParams.join('&')}`;
return res; return res;
} }

Loading…
Cancel
Save