Edit Base
{
no-style
:label-col="{ span: 8 }"
>
-
-
-
-
-
-
- {{ client.text }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+ {{ client.text }}
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
- {{ $t('activity.useConnectionUrl') }}
-
-
-
-
-
-
- {{ $t('title.advancedParameters') }}
-
-
-
-
- {{ opt }}
-
-
-
-
-
-
-
-
- {{ $t('tooltip.clientCert') }}
-
-
-
- {{ $t('labels.clientCert') }}
-
-
-
-
-
-
- {{ $t('tooltip.clientKey') }}
-
-
- {{ $t('labels.clientKey') }}
-
-
-
-
-
-
- {{ $t('tooltip.clientCA') }}
-
-
- {{ $t('labels.serverCA') }}
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('activity.useConnectionUrl') }}
+
+
+
+
+
+
+ {{ $t('title.advancedParameters') }}
+
+
+
+
+ {{ opt }}
+
+
+
+
+
+
+
+
+ {{ $t('tooltip.clientCert') }}
+
+
+
+ {{ $t('labels.clientCert') }}
+
+
+
+
+
+
+ {{ $t('tooltip.clientKey') }}
+
+
+ {{ $t('labels.clientKey') }}
+
+
+
+
+
+
+ {{ $t('tooltip.clientCA') }}
+
+
+
+ {{ $t('labels.serverCA') }}
+
+
+
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {{ type }}
+
+
+
+
+
+ {{ type }}
+
+
+
+
+
+
+ {{ $t('activity.editConnJson') }}
-
-
-
-
-
-
-
- {{ type }}
-
-
-
-
-
- {{ type }}
-
-
-
-
-
-
- {{ $t('activity.editConnJson') }}
-
-
-
-
-
+
+
+
+
+
-
+
{{ $t('activity.testDbConn') }}
@@ -592,8 +600,8 @@ onMounted(async () => {
-
- Please make sure database you are trying to connect is valid! This operation can cause schema loss!!
+
+
Please make sure database you are trying to connect is valid! This operation can cause schema loss!!
@@ -647,7 +655,7 @@ onMounted(async () => {
:deep(.ant-input-affix-wrapper),
:deep(.ant-input),
:deep(.ant-select) {
- @apply !appearance-none border-1 border-solid rounded;
+ @apply !appearance-none border-solid rounded;
}
:deep(.ant-input-password) {
diff --git a/packages/nc-gui/components/dashboard/settings/data-sources/Info.vue b/packages/nc-gui/components/dashboard/settings/data-sources/Info.vue
new file mode 100644
index 0000000000..a154d08eb5
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/settings/data-sources/Info.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/nc-gui/components/dlg/AirtableImport.vue b/packages/nc-gui/components/dlg/AirtableImport.vue
index f6c3cc6ce3..73cb9905f9 100644
--- a/packages/nc-gui/components/dlg/AirtableImport.vue
+++ b/packages/nc-gui/components/dlg/AirtableImport.vue
@@ -25,14 +25,16 @@ const { modelValue, baseId } = defineProps<{
const emit = defineEmits(['update:modelValue'])
-const { appInfo } = $(useGlobal())
+const { appInfo } = useGlobal()
-const baseURL = appInfo.ncSiteUrl
+const baseURL = appInfo.value.ncSiteUrl
const { $state, $jobs } = useNuxtApp()
const projectStore = useProject()
+const { refreshCommandPalette } = useCommandPalette()
+
const { loadTables } = projectStore
const { project } = storeToRefs(projectStore)
@@ -88,6 +90,7 @@ const onStatus = async (status: JobStatus, data?: any) => {
showGoToDashboardButton.value = true
await loadTables()
pushProgress('Done!', status)
+ refreshCommandPalette()
// TODO: add tab of the first table
} else if (status === JobStatus.FAILED) {
pushProgress(data.error.message, status)
diff --git a/packages/nc-gui/components/dlg/BulkUpdate.vue b/packages/nc-gui/components/dlg/BulkUpdate.vue
index f5e27f2c79..a154d08eb5 100644
--- a/packages/nc-gui/components/dlg/BulkUpdate.vue
+++ b/packages/nc-gui/components/dlg/BulkUpdate.vue
@@ -1,508 +1,3 @@
-
-
-
-
-
-
-
-
-
-
- Select columns to Edit
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
diff --git a/packages/nc-gui/components/dlg/ProjectDelete.vue b/packages/nc-gui/components/dlg/ProjectDelete.vue
new file mode 100644
index 0000000000..a6fcd4612f
--- /dev/null
+++ b/packages/nc-gui/components/dlg/ProjectDelete.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+ {{ project.title }}
+
+
+
+
+
diff --git a/packages/nc-gui/components/dlg/ProjectDuplicate.vue b/packages/nc-gui/components/dlg/ProjectDuplicate.vue
index 4c2e7c530c..21fc1290db 100644
--- a/packages/nc-gui/components/dlg/ProjectDuplicate.vue
+++ b/packages/nc-gui/components/dlg/ProjectDuplicate.vue
@@ -1,4 +1,5 @@
-
-
- {{ $t('general.cancel') }}
-
- {{ $t('general.confirm') }}
-
-
-
-
-
{{ $t('general.duplicate') }}
-
-
Are you sure you want to duplicate the `{{ project.title }}` project?
+
+
+
{{ $t('general.duplicate') }}
+
+
Are you sure you want to duplicate the `{{ project.title }}` project?
{{ $t('title.advancedSettings') }}
@@ -83,7 +83,11 @@ const isEaster = ref(false)
Include webhooks
-
+
+ {{ $t('general.cancel') }}
+ {{ $t('general.confirm') }}
+
+
diff --git a/packages/nc-gui/components/dlg/QuickImport.vue b/packages/nc-gui/components/dlg/QuickImport.vue
index cd680e9e7c..4f47b476f2 100644
--- a/packages/nc-gui/components/dlg/QuickImport.vue
+++ b/packages/nc-gui/components/dlg/QuickImport.vue
@@ -2,11 +2,18 @@
import type { TableType } from 'nocodb-sdk'
import type { UploadChangeParam, UploadFile } from 'ant-design-vue'
import { Upload } from 'ant-design-vue'
+import { toRaw, unref } from '@vue/runtime-core'
+import type { ImportWorkerPayload, importFileList, streamImportFileList } from '#imports'
import {
+ BASE_FALLBACK_URL,
CSVTemplateAdapter,
ExcelTemplateAdapter,
ExcelUrlTemplateAdapter,
Form,
+ ImportSource,
+ ImportType,
+ ImportWorkerOperations,
+ ImportWorkerResponse,
JSONTemplateAdapter,
JSONUrlTemplateAdapter,
computed,
@@ -16,15 +23,20 @@ import {
importCsvUrlValidator,
importExcelUrlValidator,
importUrlValidator,
+ initWorker,
message,
reactive,
ref,
storeToRefs,
+ useGlobal,
useI18n,
useProject,
useVModel,
} from '#imports'
-import type { importFileList, streamImportFileList } from '~/lib'
+
+// import worker script according to the doc of Vite
+import importWorkerUrl from '~/workers/importWorker?worker&url'
+import { useNuxtApp } from '#app'
interface Props {
modelValue: boolean
@@ -37,8 +49,20 @@ const { importType, importDataOnly = false, baseId, ...rest } = defineProps
importType === 'json')
const isImportTypeCsv = computed(() => importType === 'csv')
@@ -119,6 +145,19 @@ const importMeta = computed(() => {
const dialogShow = useVModel(rest, 'modelValue', emit)
+// watch dialogShow to init or terminate worker
+if (isWorkerSupport) {
+ watch(
+ dialogShow,
+ (val) => {
+ if (val) {
+ importWorker = initWorker(importWorkerUrl)
+ } else importWorker?.terminate()
+ },
+ { immediate: true },
+ )
+}
+
const disablePreImportButton = computed(() => {
if (activeKey.value === 'uploadTab') {
return !(importState.fileList.length > 0)
@@ -150,7 +189,7 @@ async function handlePreImport() {
isParsingData.value = true
if (activeKey.value === 'uploadTab') {
- if (isImportTypeCsv.value) {
+ if (isImportTypeCsv.value || (isWorkerSupport && importWorker)) {
await parseAndExtractData(importState.fileList as streamImportFileList)
} else {
await parseAndExtractData((importState.fileList as importFileList)[0].data)
@@ -169,7 +208,7 @@ async function handlePreImport() {
async function handleImport() {
try {
- if (!templateGenerator) {
+ if (!templateGenerator && !importWorker) {
message.error(t('msg.error.templateGeneratorNotFound'))
return
}
@@ -185,45 +224,6 @@ async function handleImport() {
dialogShow.value = false
}
-// UploadFile[] for csv import (streaming)
-// ArrayBuffer for excel import
-// string for json import
-async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
- try {
- templateData.value = null
- importData.value = null
- importColumns.value = []
-
- templateGenerator = getAdapter(val)
-
- if (!templateGenerator) {
- message.error(t('msg.error.templateGeneratorNotFound'))
- return
- }
-
- await templateGenerator.init()
-
- await templateGenerator.parse()
-
- templateData.value = templateGenerator!.getTemplate()
- if (importDataOnly) importColumns.value = templateGenerator!.getColumns()
- else {
- // ensure the target table name not exist in current table list
- templateData.value.tables = templateData.value.tables.map((table: Record) => ({
- ...table,
- table_name: populateUniqueTableName(table.table_name),
- }))
- }
- importData.value = templateGenerator!.getData()
- templateEditorModal.value = true
- } catch (e: any) {
- message.error(await extractSdkResponseErrorMsg(e))
- } finally {
- isParsingData.value = false
- preImportLoading.value = false
- }
-}
-
function rejectDrop(fileList: UploadFile[]) {
fileList.map((file) => {
return message.error(`${t('msg.error.fileUploadFailed')} ${file.name}`)
@@ -233,7 +233,7 @@ function rejectDrop(fileList: UploadFile[]) {
function handleChange(info: UploadChangeParam) {
const status = info.file.status
if (status && status !== 'uploading' && status !== 'removed') {
- if (isImportTypeCsv.value) {
+ if (isImportTypeCsv.value || (isWorkerSupport && importWorker)) {
if (!importState.fileList.find((f) => f.uid === info.file.uid)) {
;(importState.fileList as streamImportFileList).push({
...info.file,
@@ -307,14 +307,14 @@ function getAdapter(val: any) {
case 'uploadTab':
return new ExcelTemplateAdapter(val, importState.parserConfig)
case 'urlTab':
- return new ExcelUrlTemplateAdapter(val, importState.parserConfig)
+ return new ExcelUrlTemplateAdapter(val, importState.parserConfig, $api)
}
} else if (isImportTypeJson.value) {
switch (activeKey.value) {
case 'uploadTab':
return new JSONTemplateAdapter(val, importState.parserConfig)
case 'urlTab':
- return new JSONUrlTemplateAdapter(val, importState.parserConfig)
+ return new JSONUrlTemplateAdapter(val, importState.parserConfig, $api)
case 'jsonEditorTab':
return new JSONTemplateAdapter(val, importState.parserConfig)
}
@@ -346,6 +346,188 @@ const beforeUpload = (file: UploadFile) => {
}
return !exceedLimit || Upload.LIST_IGNORE
}
+
+// UploadFile[] for csv import (streaming)
+// ArrayBuffer for excel import
+function extractImportWorkerPayload(value: UploadFile[] | ArrayBuffer | string) {
+ let payload: ImportWorkerPayload
+ if (isImportTypeCsv.value) {
+ switch (activeKey.value) {
+ case 'uploadTab':
+ payload = {
+ config: {
+ ...importState.parserConfig,
+ importFromURL: false,
+ },
+ value,
+ importType: ImportType.CSV,
+ importSource: ImportSource.FILE,
+ }
+ break
+ case 'urlTab':
+ payload = {
+ config: {
+ ...importState.parserConfig,
+ importFromURL: true,
+ },
+ value,
+ importType: ImportType.CSV,
+ importSource: ImportSource.FILE,
+ }
+ break
+ }
+ } else if (IsImportTypeExcel.value) {
+ switch (activeKey.value) {
+ case 'uploadTab':
+ payload = {
+ config: toRaw(importState.parserConfig),
+ value,
+ importType: ImportType.EXCEL,
+ importSource: ImportSource.FILE,
+ }
+ break
+ case 'urlTab':
+ payload = {
+ config: toRaw(importState.parserConfig),
+ value,
+ importType: ImportType.EXCEL,
+ importSource: ImportSource.URL,
+ }
+ break
+ }
+ } else if (isImportTypeJson.value) {
+ switch (activeKey.value) {
+ case 'uploadTab':
+ payload = {
+ config: toRaw(importState.parserConfig),
+ value,
+ importType: ImportType.JSON,
+ importSource: ImportSource.FILE,
+ }
+ break
+ case 'urlTab':
+ payload = {
+ config: toRaw(importState.parserConfig),
+ value,
+ importType: ImportType.JSON,
+ importSource: ImportSource.URL,
+ }
+ break
+ case 'jsonEditorTab':
+ payload = {
+ config: toRaw(importState.parserConfig),
+ value,
+ importType: ImportType.JSON,
+ importSource: ImportSource.STRING,
+ }
+ break
+ }
+ }
+ return payload
+}
+
+// string for json import
+async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
+ templateData.value = null
+ importData.value = null
+ importColumns.value = []
+ try {
+ // if the browser supports web worker, use it to parse the file and process the data
+ if (isWorkerSupport && importWorker) {
+ importWorker.postMessage([
+ ImportWorkerOperations.INIT_SDK,
+ {
+ baseURL: config.public.ncBackendUrl || appInfo.value.ncSiteUrl || BASE_FALLBACK_URL,
+ token: token.value,
+ },
+ ])
+
+ let value = toRaw(val)
+
+ // if array, iterate and unwrap proxy
+ if (Array.isArray(value)) value = value.map((v) => toRaw(v))
+
+ const payload = extractImportWorkerPayload(value)
+
+ importWorker.postMessage([
+ ImportWorkerOperations.SET_TABLES,
+ unref(tables).map((t) => ({
+ table_name: t.table_name,
+ title: t.title,
+ })),
+ ])
+ importWorker.postMessage([
+ ImportWorkerOperations.SET_CONFIG,
+ {
+ importDataOnly,
+ importColumns: !!importColumns.value,
+ importData: !!importData.value,
+ },
+ ])
+
+ const response: {
+ templateData: any
+ importColumns: any
+ importData: any
+ } = await new Promise((resolve, reject) => {
+ const handler = (e: MessageEvent) => {
+ const [type, payload] = e.data
+ switch (type) {
+ case ImportWorkerResponse.PROCESSED_DATA:
+ resolve(payload)
+ importWorker?.removeEventListener('message', handler, false)
+ break
+ case ImportWorkerResponse.PROGRESS:
+ progressMsg.value = payload
+ break
+ case ImportWorkerResponse.ERROR:
+ reject(payload)
+ importWorker?.removeEventListener('message', handler, false)
+ break
+ }
+ }
+ importWorker?.addEventListener('message', handler, false)
+
+ importWorker?.postMessage([ImportWorkerOperations.PROCESS, payload])
+ })
+ templateData.value = response.templateData
+ importColumns.value = response.importColumns
+ importData.value = response.importData
+ }
+ // otherwise, use the main thread to parse the file and process the data
+ else {
+ templateGenerator = getAdapter(val)
+
+ if (!templateGenerator) {
+ message.error(t('msg.error.templateGeneratorNotFound'))
+ return
+ }
+
+ await templateGenerator.init()
+
+ await templateGenerator.parse()
+
+ templateData.value = templateGenerator!.getTemplate()
+ if (importDataOnly) importColumns.value = templateGenerator!.getColumns()
+ else {
+ // ensure the target table name not exist in current table list
+ templateData.value.tables = templateData.value.tables.map((table: Record) => ({
+ ...table,
+ table_name: populateUniqueTableName(table.table_name),
+ }))
+ }
+ importData.value = templateGenerator!.getData()
+ }
+
+ templateEditorModal.value = true
+ } catch (e: any) {
+ console.log(e)
+ message.error(await extractSdkResponseErrorMsg(e))
+ } finally {
+ isParsingData.value = false
+ preImportLoading.value = false
+ }
+}
@@ -356,7 +538,7 @@ const beforeUpload = (file: UploadFile) => {
wrap-class-name="nc-modal-quick-import"
@keydown.esc="dialogShow = false"
>
-
+
{{ importMeta.header }}
@@ -371,6 +553,7 @@ const beforeUpload = (file: UploadFile) => {
:quick-import-type="importType"
:max-rows-to-parse="importState.parserConfig.maxRowsToParse"
:base-id="baseId"
+ :import-worker="importWorker"
class="nc-quick-import-template-editor"
@import="handleImport"
/>
@@ -475,16 +658,16 @@ const beforeUpload = (file: UploadFile) => {
- {{ $t('labels.importData') }}
+ {{ $t('labels.importData') }}