Browse Source

Merge pull request #3051 from nocodb/fix/gui-v2-misc

fix(gui-v2): Misc issues
pull/3093/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
242db97fa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      packages/nc-gui-v2/components.d.ts
  2. 20
      packages/nc-gui-v2/components/dashboard/settings/AppStore.vue
  3. 16
      packages/nc-gui-v2/components/dashboard/settings/Metadata.vue
  4. 14
      packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue
  5. 30
      packages/nc-gui-v2/components/dashboard/settings/app-store/AppInstall.vue
  6. 17
      packages/nc-gui-v2/components/dlg/AirtableImport.vue
  7. 43
      packages/nc-gui-v2/components/dlg/QuickImport.vue
  8. 15
      packages/nc-gui-v2/components/dlg/TableRename.vue
  9. 13
      packages/nc-gui-v2/components/smartsheet-column/FormulaOptions.vue
  10. 23
      packages/nc-gui-v2/components/smartsheet-header/Menu.vue
  11. 28
      packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue
  12. 16
      packages/nc-gui-v2/components/smartsheet-toolbar/SharedViewList.vue
  13. 24
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue
  14. 18
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/LockMenu.vue
  15. 28
      packages/nc-gui-v2/components/tabs/auth/ApiTokenManagement.vue
  16. 40
      packages/nc-gui-v2/components/tabs/auth/UserManagement.vue
  17. 39
      packages/nc-gui-v2/components/tabs/auth/user-management/ShareBase.vue
  18. 20
      packages/nc-gui-v2/components/tabs/auth/user-management/UsersModal.vue
  19. 316
      packages/nc-gui-v2/components/template/Editor.vue
  20. 20
      packages/nc-gui-v2/components/template/utils.ts
  21. 21
      packages/nc-gui-v2/components/webhook/Editor.vue
  22. 17
      packages/nc-gui-v2/components/webhook/List.vue
  23. 12
      packages/nc-gui-v2/components/webhook/Test.vue
  24. 24
      packages/nc-gui-v2/composables/useColumnCreateStore.ts
  25. 18
      packages/nc-gui-v2/composables/useTable.ts
  26. 4
      packages/nc-gui-v2/composables/useViewData.ts
  27. 4
      packages/nc-gui-v2/composables/useViews.ts
  28. 15
      packages/nc-gui-v2/package-lock.json
  29. 3
      packages/nc-gui-v2/package.json
  30. 11
      packages/nc-gui-v2/pages/index/index.vue
  31. 17
      packages/nc-gui-v2/pages/project/index/[id].vue
  32. 18
      packages/nc-gui-v2/pages/project/index/create-external.vue
  33. 8
      packages/nc-gui-v2/pages/project/index/create.vue
  34. 10
      packages/nc-gui-v2/pages/projects/index.vue
  35. 8
      packages/nc-gui-v2/plugins/toastr.ts
  36. 1
      packages/nc-gui-v2/utils/index.ts
  37. 4
      packages/nc-gui-v2/utils/parsers/ExcelTemplateAdapter.ts

10
packages/nc-gui-v2/components.d.ts vendored

@ -24,7 +24,6 @@ declare module '@vue/runtime-core' {
AEmpty: typeof import('ant-design-vue/es')['Empty'] AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form'] AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem'] AFormItem: typeof import('ant-design-vue/es')['FormItem']
AImage: typeof import('ant-design-vue/es')['Image']
AInput: typeof import('ant-design-vue/es')['Input'] AInput: typeof import('ant-design-vue/es')['Input']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
@ -42,7 +41,6 @@ declare module '@vue/runtime-core' {
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup'] AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination'] APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
ARadio: typeof import('ant-design-vue/es')['Radio'] ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARate: typeof import('ant-design-vue/es')['Rate'] ARate: typeof import('ant-design-vue/es')['Rate']
@ -68,20 +66,16 @@ declare module '@vue/runtime-core' {
CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default'] CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default'] IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default']
IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default'] IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default']
MaterialSymbolsArrowBackRounded: typeof import('~icons/material-symbols/arrow-back-rounded')['default']
MaterialSymbolsArrowForwardRounded: typeof import('~icons/material-symbols/arrow-forward-rounded')['default']
MaterialSymbolsAttachFile: typeof import('~icons/material-symbols/attach-file')['default'] MaterialSymbolsAttachFile: typeof import('~icons/material-symbols/attach-file')['default']
MaterialSymbolsChevronLeftRounded: typeof import('~icons/material-symbols/chevron-left-rounded')['default'] MaterialSymbolsChevronLeftRounded: typeof import('~icons/material-symbols/chevron-left-rounded')['default']
MaterialSymbolsChevronRightRounded: typeof import('~icons/material-symbols/chevron-right-rounded')['default'] MaterialSymbolsChevronRightRounded: typeof import('~icons/material-symbols/chevron-right-rounded')['default']
MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default'] MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default']
MaterialSymbolsMenu: typeof import('~icons/material-symbols/menu')['default']
MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default'] MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default']
MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default'] MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default']
MdiAccountGroup: typeof import('~icons/mdi/account-group')['default'] MdiAccountGroup: typeof import('~icons/mdi/account-group')['default']
MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default'] MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default']
MdiApi: typeof import('~icons/mdi/api')['default'] MdiApi: typeof import('~icons/mdi/api')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default'] MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowExpandIcon: typeof import('~icons/mdi/arrow-expand-icon')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default'] MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiAt: typeof import('~icons/mdi/at')['default'] MdiAt: typeof import('~icons/mdi/at')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default'] MdiCalculator: typeof import('~icons/mdi/calculator')['default']
@ -94,10 +88,8 @@ declare module '@vue/runtime-core' {
MdiCloseThick: typeof import('~icons/mdi/close-thick')['default'] MdiCloseThick: typeof import('~icons/mdi/close-thick')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default'] MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiContentSave: typeof import('~icons/mdi/content-save')['default'] MdiContentSave: typeof import('~icons/mdi/content-save')['default']
MdiDatabase: typeof import('~icons/mdi/database')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default'] MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default'] MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default'] MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiDownload: typeof import('~icons/mdi/download')['default'] MdiDownload: typeof import('~icons/mdi/download')['default']
MdiDrag: typeof import('~icons/mdi/drag')['default'] MdiDrag: typeof import('~icons/mdi/drag')['default']
@ -123,11 +115,9 @@ declare module '@vue/runtime-core' {
MdiNotebookCheckOutline: typeof import('~icons/mdi/notebook-check-outline')['default'] MdiNotebookCheckOutline: typeof import('~icons/mdi/notebook-check-outline')['default']
MdiNumeric: typeof import('~icons/mdi/numeric')['default'] MdiNumeric: typeof import('~icons/mdi/numeric')['default']
MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default'] MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default']
MdiOperator: typeof import('~icons/mdi/operator')['default']
MdiPlus: typeof import('~icons/mdi/plus')['default'] MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default'] MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default']
MdiReload: typeof import('~icons/mdi/reload')['default'] MdiReload: typeof import('~icons/mdi/reload')['default']
MdiSearch: typeof import('~icons/mdi/search')['default']
MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default'] MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default'] MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiStar: typeof import('~icons/mdi/star')['default'] MdiStar: typeof import('~icons/mdi/star')['default']

20
packages/nc-gui-v2/components/dashboard/settings/AppStore.vue

@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import AppInstall from './app-store/AppInstall.vue' import AppInstall from './app-store/AppInstall.vue'
import MdiEditIcon from '~icons/ic/round-edit' import MdiEditIcon from '~icons/ic/round-edit'
import MdiCloseCircleIcon from '~icons/mdi/close-circle-outline' import MdiCloseCircleIcon from '~icons/mdi/close-circle-outline'
import MdiPlusIcon from '~icons/mdi/plus' import MdiPlusIcon from '~icons/mdi/plus'
import { extractSdkResponseErrorMsg } from '~/utils'
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const toast = useToast()
let apps = $ref<null | Array<any>>(null) let apps = $ref<null | Array<any>>(null)
let showPluginUninstallModal = $ref(false) let showPluginUninstallModal = $ref(false)
@ -22,9 +22,10 @@ const fetchPluginApps = async () => {
tags: p.tags ? p.tags.split(',') : [], tags: p.tags ? p.tags.split(',') : [],
parsedInput: p.input && JSON.parse(p.input), parsedInput: p.input && JSON.parse(p.input),
})) }))
} catch (e) { } catch (e: any) {
console.error(e) notification.error({
toast.error('Something went wrong') message: await extractSdkResponseErrorMsg(e),
})
} }
} }
@ -34,12 +35,15 @@ const resetPlugin = async () => {
input: undefined, input: undefined,
active: false, active: false,
}) })
toast.success('Plugin uninstalled successfully') notification.success({
message: 'Plugin uninstalled successfully',
})
showPluginUninstallModal = false showPluginUninstallModal = false
await fetchPluginApps() await fetchPluginApps()
} catch (e: any) { } catch (e: any) {
console.log(e) notification.error({
toast.error(e.message) message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:appstore:reset', { app: pluginApp.title }) $e('a:appstore:reset', { app: pluginApp.title })

16
packages/nc-gui-v2/components/dashboard/settings/Metadata.vue

@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { h, useNuxtApp, useProject } from '#imports' import { h, useNuxtApp, useProject } from '#imports'
import MdiReload from '~icons/mdi/reload' import MdiReload from '~icons/mdi/reload'
import MdiDatabaseSync from '~icons/mdi/database-sync' import MdiDatabaseSync from '~icons/mdi/database-sync'
import { extractSdkResponseErrorMsg } from '~/utils'
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const { project } = useProject() const { project } = useProject()
const toast = useToast()
let isLoading = $ref(false) let isLoading = $ref(false)
let isDifferent = $ref(false) let isDifferent = $ref(false)
@ -38,14 +38,14 @@ async function syncMetaDiff() {
isLoading = true isLoading = true
await $api.project.metaDiffSync(project.value.id) await $api.project.metaDiffSync(project.value.id)
toast.info(`Table metadata recreated successfully`) notification.info({
message: 'Table metadata recreated successfully',
})
await loadMetaDiff() await loadMetaDiff()
} catch (e: any) { } catch (e: any) {
if (e.response?.status === 402) { notification.error({
toast.info(e.message) message: await extractSdkResponseErrorMsg(e),
} else { })
toast.error(e.message)
}
} finally { } finally {
isLoading = false isLoading = false
} }

14
packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue

@ -1,14 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { viewIcons } from '~/utils' import { extractSdkResponseErrorMsg, viewIcons } from '~/utils'
import { computed, h, useNuxtApp, useProject } from '#imports' import { computed, h, useNuxtApp, useProject } from '#imports'
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { project } = useProject() const { project } = useProject()
const toast = useToast()
const roles = $ref<string[]>(['editor', 'commenter', 'viewer']) const roles = $ref<string[]>(['editor', 'commenter', 'viewer'])
let isLoading = $ref(false) let isLoading = $ref(false)
@ -49,9 +47,13 @@ async function saveUIAcl() {
project.value.id, project.value.id,
tables.filter((t) => t.edited), tables.filter((t) => t.edited),
) )
toast.success('Updated UI ACL for tables successfully') notification.success({
message: 'Updated UI ACL for tables successfully',
})
} catch (e: any) { } catch (e: any) {
toast.error(e?.message) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:proj-meta:ui-acl') $e('a:proj-meta:ui-acl')
} }

30
packages/nc-gui-v2/components/dashboard/settings/app-store/AppInstall.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import type { PluginType } from 'nocodb-sdk' import type { PluginType } from 'nocodb-sdk'
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline' import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
import CloseIcon from '~icons/material-symbols/close-rounded' import CloseIcon from '~icons/material-symbols/close-rounded'
@ -25,8 +25,6 @@ enum Action {
Test = 'test', Test = 'test',
} }
const toast = useToast()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const formRef = ref() const formRef = ref()
@ -55,10 +53,13 @@ const saveSettings = async () => {
}) })
emits('saved') emits('saved')
toast.success(plugin?.formDetails.msgOnInstall || 'Plugin settings saved successfully') notification.success({
} catch (_e: any) { message: plugin?.formDetails.msgOnInstall || 'Plugin settings saved successfully',
const e = await extractSdkResponseErrorMsg(_e) })
toast.error(e.message) } catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally { } finally {
loadingAction = null loadingAction = null
} }
@ -76,13 +77,18 @@ const testSettings = async () => {
}) })
if (res) { if (res) {
toast.success('Successfully tested plugin settings') notification.success({
message: 'Successfully tested plugin settings',
})
} else { } else {
toast.info('Invalid credentials') notification.info({
message: 'Invalid credentials',
})
} }
} catch (_e: any) { } catch (e: any) {
const e = await extractSdkResponseErrorMsg(_e) notification.error({
toast.error(e.message) message: await extractSdkResponseErrorMsg(e),
})
} finally { } finally {
loadingAction = null loadingAction = null
} }

17
packages/nc-gui-v2/components/dlg/AirtableImport.vue

@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import io from 'socket.io-client' import io from 'socket.io-client'
import type { Socket } from 'socket.io-client' import type { Socket } from 'socket.io-client'
import { Form } from 'ant-design-vue' import { Form, notification } from 'ant-design-vue'
import type { Card as AntCard } from 'ant-design-vue' import type { Card as AntCard } from 'ant-design-vue'
import { useToast } from 'vue-toastification' import { extractSdkResponseErrorMsg, fieldRequiredValidator } from '~/utils'
import { fieldRequiredValidator } from '~/utils/validation'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiCloseCircleOutlineIcon from '~icons/mdi/close-circle-outline' import MdiCloseCircleOutlineIcon from '~icons/mdi/close-circle-outline'
import MdiCurrencyUsdIcon from '~icons/mdi/currency-usd' import MdiCurrencyUsdIcon from '~icons/mdi/currency-usd'
import MdiLoadingIcon from '~icons/mdi/loading' import MdiLoadingIcon from '~icons/mdi/loading'
@ -23,8 +21,6 @@ const baseURL = 'http://localhost:8080' // this.$axios.defaults.baseURL
const { $state } = useNuxtApp() const { $state } = useNuxtApp()
const toast = useToast()
const { project, loadTables } = useProject() const { project, loadTables } = useProject()
const showGoToDashboardButton = ref(false) const showGoToDashboardButton = ref(false)
@ -107,7 +103,9 @@ async function createOrUpdate() {
syncSource.value = data syncSource.value = data
} }
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }
@ -158,8 +156,9 @@ async function sync() {
}, },
}) })
} catch (e: any) { } catch (e: any) {
console.log(e) notification.error({
toast.error(e) message: await extractSdkResponseErrorMsg(e),
})
} }
} }

43
packages/nc-gui-v2/components/dlg/QuickImport.vue

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { Form, notification } from 'ant-design-vue'
import { Form } from 'ant-design-vue'
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import type { UploadChangeParam } from 'ant-design-vue' import type { UploadChangeParam } from 'ant-design-vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -16,9 +15,10 @@ import { useProject } from '#imports'
interface Props { interface Props {
modelValue: boolean modelValue: boolean
importType: 'csv' | 'json' | 'excel' importType: 'csv' | 'json' | 'excel'
importOnly: boolean
} }
const { importType, ...rest } = defineProps<Props>() const { importType, importOnly, ...rest } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
@ -26,8 +26,6 @@ const { t } = useI18n()
const { tables } = useProject() const { tables } = useProject()
const toast = useToast()
const activeKey = ref('uploadTab') const activeKey = ref('uploadTab')
const jsonEditorRef = ref() const jsonEditorRef = ref()
@ -40,6 +38,8 @@ const templateData = ref()
const importData = ref() const importData = ref()
const importColumns = ref([])
const templateEditorModal = ref(false) const templateEditorModal = ref(false)
const useForm = Form.useForm const useForm = Form.useForm
@ -132,7 +132,9 @@ async function handlePreImport() {
await validate() await validate()
await parseAndExtractData(importState.url, '') await parseAndExtractData(importState.url, '')
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} else if (activeKey.value === 'jsonEditorTab') { } else if (activeKey.value === 'jsonEditorTab') {
await parseAndExtractData(JSON.stringify(importState.jsonEditor), '') await parseAndExtractData(JSON.stringify(importState.jsonEditor), '')
@ -145,7 +147,9 @@ async function handleImport() {
loading.value = true loading.value = true
await templateEditorRef.value.importTemplate() await templateEditorRef.value.importTemplate()
} catch (e: any) { } catch (e: any) {
return toast.error(await extractSdkResponseErrorMsg(e)) return notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally { } finally {
loading.value = false loading.value = false
} }
@ -156,9 +160,12 @@ async function parseAndExtractData(val: any, name: string) {
try { try {
templateData.value = null templateData.value = null
importData.value = null importData.value = null
importColumns.value = []
const templateGenerator: any = getAdapter(name, val) const templateGenerator: any = getAdapter(name, val)
if (!templateGenerator) { if (!templateGenerator) {
toast.error('Template Generator cannot be found!') notification.error({
message: 'Template Generator cannot be found!',
})
return return
} }
await templateGenerator.init() await templateGenerator.init()
@ -166,16 +173,21 @@ async function parseAndExtractData(val: any, name: string) {
templateData.value = templateGenerator.getTemplate() templateData.value = templateGenerator.getTemplate()
templateData.value.tables[0].table_name = populateUniqueTableName() templateData.value.tables[0].table_name = populateUniqueTableName()
importData.value = templateGenerator.getData() importData.value = templateGenerator.getData()
if (importOnly) importColumns.value = templateGenerator.getColumns()
templateEditorModal.value = true templateEditorModal.value = true
} catch (e: any) { } catch (e: any) {
console.log(e) console.log(e)
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }
function rejectDrop(fileList: any[]) { function rejectDrop(fileList: any[]) {
fileList.map((file) => { fileList.map((file) => {
return toast.error(`Failed to upload file ${file.name}`) return notification.error({
message: `Failed to upload file ${file.name}`,
})
}) })
} }
@ -192,9 +204,13 @@ function handleChange(info: UploadChangeParam) {
reader.readAsArrayBuffer(info.file.originFileObj) reader.readAsArrayBuffer(info.file.originFileObj)
} }
if (status === 'done') { if (status === 'done') {
toast.success(`Uploaded file ${info.file.name} successfully`) notification.success({
message: `Uploaded file ${info.file.name} successfully`,
})
} else if (status === 'error') { } else if (status === 'error') {
toast.error(`Failed to upload file ${info.file.name}`) notification.error({
message: `Failed to upload file ${info.file.name}`,
})
} }
} }
@ -265,7 +281,10 @@ function getAdapter(name: string, val: any) {
ref="templateEditorRef" ref="templateEditorRef"
:project-template="templateData" :project-template="templateData"
:import-data="importData" :import-data="importData"
:import-columns="importColumns"
:import-only="importOnly"
:quick-import-type="importType" :quick-import-type="importType"
:max-rows-to-parse="importState.parserConfig.maxRowsToParse"
@import="handleImport" @import="handleImport"
/> />
<a-tabs v-else v-model:activeKey="activeKey" hide-add type="editable-card" :tab-position="top"> <a-tabs v-else v-model:activeKey="activeKey" hide-add type="editable-card" :tab-position="top">

15
packages/nc-gui-v2/components/dlg/TableRename.vue

@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { watchEffect } from '@vue/runtime-core' import { watchEffect } from '@vue/runtime-core'
import { Form } from 'ant-design-vue' import { Form, notification } from 'ant-design-vue'
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { useProject, useTabs } from '#imports' import { useProject, useTabs } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg, validateTableName } from '~/utils'
import { validateTableName } from '~/utils/validation'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
interface Props { interface Props {
@ -16,7 +14,6 @@ interface Props {
const { modelValue = false, tableMeta } = defineProps<Props>() const { modelValue = false, tableMeta } = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'updated']) const emit = defineEmits(['update:modelValue', 'updated'])
const { $e, $api } = useNuxtApp() const { $e, $api } = useNuxtApp()
const toast = useToast()
const dialogShow = computed({ const dialogShow = computed({
get() { get() {
return modelValue return modelValue
@ -79,11 +76,15 @@ const renameTable = async () => {
dialogShow.value = false dialogShow.value = false
loadTables() loadTables()
updateTab({ id: tableMeta?.id }, { title: formState.title }) updateTab({ id: tableMeta?.id }, { title: formState.title })
toast.success('Table renamed successfully') notification.success({
message: 'Table renamed successfully',
})
$e('a:table:rename') $e('a:table:rename')
dialogShow.value = false dialogShow.value = false
} catch (e) { } catch (e) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
loading = false loading = false
} }

13
packages/nc-gui-v2/components/smartsheet-column/FormulaOptions.vue

@ -581,19 +581,6 @@ function scrollToSelectedOption() {
}) })
} }
function getFormulaTypeName(type: string) {
switch (type) {
case 'function':
return 'Function'
case 'op':
return 'Operator'
case 'column':
return 'Column'
default:
return ''
}
}
// set default value // set default value
formState.value.formula_raw = (column?.value?.colOptions as Record<string, any>)?.formula_raw || '' formState.value.formula_raw = (column?.value?.colOptions as Record<string, any>)?.formula_raw || ''

23
packages/nc-gui-v2/components/smartsheet-header/Menu.vue

@ -1,26 +1,28 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Modal } from 'ant-design-vue' import { Modal, notification } from 'ant-design-vue'
import { inject } from 'vue' import { inject } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useToast } from 'vue-toastification'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
import { useMetas } from '#imports' import { useMetas } from '#imports'
import { ColumnInj, MetaInj } from '~/context' import { ColumnInj, MetaInj } from '~/context'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils'
import MdiEditIcon from '~icons/mdi/pencil' import MdiEditIcon from '~icons/mdi/pencil'
import MdiStarIcon from '~icons/mdi/star' import MdiStarIcon from '~icons/mdi/star'
import MdiDeleteIcon from '~icons/mdi/delete-outline' import MdiDeleteIcon from '~icons/mdi/delete-outline'
import MdiMenuDownIcon from '~icons/mdi/menu-down' import MdiMenuDownIcon from '~icons/mdi/menu-down'
const { virtual = false } = defineProps<{ virtual?: boolean }>() const { virtual = false } = defineProps<{ virtual?: boolean }>()
const editColumnDropdown = ref(false) const editColumnDropdown = ref(false)
const column = inject(ColumnInj) const column = inject(ColumnInj)
const meta = inject(MetaInj) const meta = inject(MetaInj)
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const { t } = useI18n() const { t } = useI18n()
const toast = useToast()
const { getMeta } = useMetas() const { getMeta } = useMetas()
const deleteColumn = () => const deleteColumn = () =>
@ -34,7 +36,9 @@ const deleteColumn = () =>
await $api.dbTableColumn.delete(column?.value?.id as string) await $api.dbTableColumn.delete(column?.value?.id as string)
getMeta(meta?.value?.id as string, true) getMeta(meta?.value?.id as string, true)
} catch (e) { } catch (e) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
}, },
}) })
@ -43,10 +47,13 @@ const setAsPrimaryValue = async () => {
try { try {
await $api.dbTableColumn.primaryColumnSet(column?.value?.id as string) await $api.dbTableColumn.primaryColumnSet(column?.value?.id as string)
getMeta(meta?.value?.id as string, true) getMeta(meta?.value?.id as string, true)
toast.success('Successfully updated as primary column') notification.success({
message: 'Successfully updated as primary column',
})
} catch (e) { } catch (e) {
console.log(e) notification.error({
toast.error('Failed to update primary column') message: 'Failed to update primary column',
})
} }
} }

28
packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue

@ -2,23 +2,25 @@
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { computed } from 'vue' import { computed } from 'vue'
import { message } from 'ant-design-vue' import { message, notification } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
import { useSmartsheetStoreOrThrow } from '#imports' import { useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils'
import MdiOpenInNewIcon from '~icons/mdi/open-in-new' import MdiOpenInNewIcon from '~icons/mdi/open-in-new'
import MdiCopyIcon from '~icons/mdi/content-copy' import MdiCopyIcon from '~icons/mdi/content-copy'
const { view, $api } = useSmartsheetStoreOrThrow() const { view, $api } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard() const { copy } = useClipboard()
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const toast = useToast()
const { dashboardUrl } = useDashboard() const { dashboardUrl } = useDashboard()
let showShareModel = $ref(false) let showShareModel = $ref(false)
const passwordProtected = $ref(false) const passwordProtected = $ref(false)
const shared = ref() const shared = ref()
const allowCSVDownload = computed({ const allowCSVDownload = computed({
@ -67,10 +69,14 @@ async function saveAllowCSVDownload() {
await $api.dbViewShare.update(shared.value.id, { await $api.dbViewShare.update(shared.value.id, {
meta, meta,
} as any)
notification.success({
message: 'Successfully updated',
}) })
toast.success('Successfully updated')
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
if (allowCSVDownload?.value) { if (allowCSVDownload?.value) {
$e('a:view:share:enable-csv-download') $e('a:view:share:enable-csv-download')
@ -84,9 +90,13 @@ const saveShareLinkPassword = async () => {
await $api.dbViewShare.update(shared.value.id, { await $api.dbViewShare.update(shared.value.id, {
password: shared.value.password, password: shared.value.password,
}) })
toast.success('Successfully updated') notification.success({
} catch (e) { message: 'Successfully updated',
toast.error(await extractSdkResponseErrorMsg(e)) })
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:view:share:enable-pwd') $e('a:view:share:enable-pwd')

16
packages/nc-gui-v2/components/smartsheet-toolbar/SharedViewList.vue

@ -1,8 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { useToast } from 'vue-toastification' import { message, notification } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import { onMounted, useSmartsheetStoreOrThrow } from '#imports' import { onMounted, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiVisibilityOnIcon from '~icons/mdi/visibility' import MdiVisibilityOnIcon from '~icons/mdi/visibility'
@ -20,8 +19,9 @@ interface SharedViewType {
} }
const { $api, meta } = useSmartsheetStoreOrThrow() const { $api, meta } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard() const { copy } = useClipboard()
const toast = useToast()
const { dashboardUrl } = useDashboard() const { dashboardUrl } = useDashboard()
const sharedViewList = ref<SharedViewType[]>() const sharedViewList = ref<SharedViewType[]>()
@ -80,10 +80,14 @@ const copyLink = (view: SharedViewType) => {
const deleteLink = async (id: string) => { const deleteLink = async (id: string) => {
try { try {
await $api.dbViewShare.delete(id) await $api.dbViewShare.delete(id)
toast.success('Deleted shared view successfully') notification.success({
message: 'Deleted shared view successfully',
})
await loadSharedViewsList() await loadSharedViewsList()
} catch (e) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }
</script> </script>

24
packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue

@ -14,12 +14,18 @@ const { $e } = useNuxtApp()
const isView = ref(false) const isView = ref(false)
let showApiSnippet = $ref(false) let showApiSnippet = $ref(false)
const showWebhookDrawer = ref(false)
function onApiSnippet() { function onApiSnippet() {
// get API snippet // get API snippet
showApiSnippet = true showApiSnippet = true
$e('a:view:api-snippet') $e('a:view:api-snippet')
} }
function onWebhooks() {
showWebhookDrawer.value = true
}
function onOpenModal(type: ViewTypes, title = '') { function onOpenModal(type: ViewTypes, title = '') {
emits('openModal', { type, title }) emits('openModal', { type, title })
} }
@ -33,7 +39,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<template #title> <template #title>
{{ $t('msg.info.onlyCreator') }} {{ $t('msg.info.onlyCreator') }}
</template> </template>
<MdiShieldLockOutline class="text-pink-500" /> <mdi-shield-lock-outline class="text-pink-500" />
</a-tooltip> </a-tooltip>
</h3> </h3>
@ -50,7 +56,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<MdiPlus class="group-hover:text-primary" /> <mdi-plus class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
@ -68,7 +74,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<MdiPlus class="group-hover:text-primary" /> <mdi-plus class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
@ -86,7 +92,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<MdiPlus class="group-hover:text-primary" /> <mdi-plus class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
@ -97,14 +103,14 @@ function onOpenModal(type: ViewTypes, title = '') {
class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded !bg-primary text-white transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease" class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded !bg-primary text-white transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease"
@click="onApiSnippet" @click="onApiSnippet"
> >
<MdiXml />Get API Snippet <mdi-xml />Get API Snippet
</button> </button>
<button <button
class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded border transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease" class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded border transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease"
@click="onApiSnippet" @click="onWebhooks"
> >
<MdiHook />{{ $t('objects.webhooks') }} <mdi-hook />{{ $t('objects.webhooks') }}
</button> </button>
</div> </div>
@ -135,10 +141,12 @@ function onOpenModal(type: ViewTypes, title = '') {
class="group flex items-center gap-2 w-full mx-3 px-4 py-2 rounded-l !bg-primary text-white transform translate-x-4 hover:(translate-x-0 shadow-lg !opacity-100) transition duration-150 ease" class="group flex items-center gap-2 w-full mx-3 px-4 py-2 rounded-l !bg-primary text-white transform translate-x-4 hover:(translate-x-0 shadow-lg !opacity-100) transition duration-150 ease"
@click.stop @click.stop
> >
<MdiCardsHeart class="text-red-500" /> <mdi-cards-heart class="text-red-500" />
{{ $t('activity.sponsorUs') }} {{ $t('activity.sponsorUs') }}
</a> </a>
</template> </template>
</general-flipping-card> </general-flipping-card>
<WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
</a-menu> </a-menu>
</template> </template>

18
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/LockMenu.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { computed, useSmartsheetStoreOrThrow } from '#imports' import { computed, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
import MdiLockOutlineIcon from '~icons/mdi/lock-outline' import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
@ -13,14 +13,16 @@ enum LockType {
} }
const { view, $api } = useSmartsheetStoreOrThrow() const { view, $api } = useSmartsheetStoreOrThrow()
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const toast = useToast()
function changeLockType(type: LockType) { async function changeLockType(type: LockType) {
$e('a:grid:lockmenu', { lockType: type }) $e('a:grid:lockmenu', { lockType: type })
if (type === 'personal') { if (type === 'personal') {
return toast.info('Coming soon', { timeout: 3000 }) return notification.info({
message: 'Coming soon',
})
} }
try { try {
;(view.value as any).lock_type = type ;(view.value as any).lock_type = type
@ -28,9 +30,13 @@ function changeLockType(type: LockType) {
lock_type: type, lock_type: type,
}) })
toast.success(`Successfully Switched to ${type} view`, { timeout: 3000 }) notification.success({
message: `Successfully Switched to ${type} view`,
})
} catch (e: any) { } catch (e: any) {
toast.error(extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }

28
packages/nc-gui-v2/components/tabs/auth/ApiTokenManagement.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ApiTokenType } from 'nocodb-sdk' import type { ApiTokenType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import KebabIcon from '~icons/ic/baseline-more-vert' import KebabIcon from '~icons/ic/baseline-more-vert'
import MdiPlusIcon from '~icons/mdi/plus' import MdiPlusIcon from '~icons/mdi/plus'
@ -10,9 +10,7 @@ import VisibilityOpenIcon from '~icons/material-symbols/visibility'
import VisibilityCloseIcon from '~icons/material-symbols/visibility-off' import VisibilityCloseIcon from '~icons/material-symbols/visibility-off'
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline' import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
import MdiContentCopyIcon from '~icons/mdi/content-copy' import MdiContentCopyIcon from '~icons/mdi/content-copy'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils'
const toast = useToast()
interface ApiToken extends ApiTokenType { interface ApiToken extends ApiTokenType {
show?: boolean show?: boolean
@ -42,7 +40,9 @@ const copyToken = (token: string | undefined) => {
if (!token) return if (!token) return
copy(token) copy(token)
toast.info('Copied to clipboard') notification.info({
message: 'Copied to clipboard',
})
$e('c:api-token:copy') $e('c:api-token:copy')
} }
@ -53,12 +53,15 @@ const generateToken = async () => {
await $api.apiToken.create(project.id, selectedTokenData) await $api.apiToken.create(project.id, selectedTokenData)
showNewTokenModal = false showNewTokenModal = false
toast.success('Token generated successfully') notification.success({
message: 'Token generated successfullyd',
})
selectedTokenData = {} selectedTokenData = {}
await loadApiTokens() await loadApiTokens()
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:api-token:generate') $e('a:api-token:generate')
@ -70,12 +73,15 @@ const deleteToken = async () => {
await $api.apiToken.delete(project.id, selectedTokenData.token) await $api.apiToken.delete(project.id, selectedTokenData.token)
toast.success('Token deleted successfully') notification.success({
message: 'Token deleted successfully',
})
await loadApiTokens() await loadApiTokens()
showDeleteTokenModal = false showDeleteTokenModal = false
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:api-token:delete') $e('a:api-token:delete')

40
packages/nc-gui-v2/components/tabs/auth/UserManagement.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useClipboard, watchDebounced } from '@vueuse/core' import { useClipboard, watchDebounced } from '@vueuse/core'
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import UsersModal from './user-management/UsersModal.vue' import UsersModal from './user-management/UsersModal.vue'
import FeedbackForm from './user-management/FeedbackForm.vue' import FeedbackForm from './user-management/FeedbackForm.vue'
import KebabIcon from '~icons/ic/baseline-more-vert' import KebabIcon from '~icons/ic/baseline-more-vert'
@ -17,7 +17,6 @@ import MdiContentCopyIcon from '~icons/mdi/content-copy'
import MdiEmailSendIcon from '~icons/mdi/email-arrow-right-outline' import MdiEmailSendIcon from '~icons/mdi/email-arrow-right-outline'
import RolesIcon from '~icons/mdi/drama-masks' import RolesIcon from '~icons/mdi/drama-masks'
import type { User } from '~/lib/types' import type { User } from '~/lib/types'
const toast = useToast()
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { project } = useProject() const { project } = useProject()
@ -53,8 +52,9 @@ const loadUsers = async (page = currentPage, limit = currentLimit) => {
totalRows = response.users.pageInfo.totalRows ?? 0 totalRows = response.users.pageInfo.totalRows ?? 0
users = response.users.list as User[] users = response.users.list as User[]
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
} }
@ -63,11 +63,14 @@ const inviteUser = async (user: User) => {
if (!project.value?.id) return if (!project.value?.id) return
await $api.auth.projectUserAdd(project.value.id, user) await $api.auth.projectUserAdd(project.value.id, user)
toast.success('Successfully added user to project') notification.success({
message: 'Successfully added user to project',
})
await loadUsers() await loadUsers()
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:user:add') $e('a:user:add')
@ -78,13 +81,17 @@ const deleteUser = async () => {
if (!project.value?.id || !selectedUser?.id) return if (!project.value?.id || !selectedUser?.id) return
await $api.auth.projectUserRemove(project.value.id, selectedUser.id) await $api.auth.projectUserRemove(project.value.id, selectedUser.id)
toast.success('Successfully deleted user from project') notification.success({
message: 'Successfully deleted user from project',
})
await loadUsers() await loadUsers()
showUserDeleteModal = false showUserDeleteModal = false
} catch (e: any) { } catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
showUserDeleteModal = false showUserDeleteModal = false
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
} }
$e('a:user:delete') $e('a:user:delete')
@ -110,11 +117,14 @@ const resendInvite = async (user: User) => {
try { try {
await $api.auth.projectUserResendInvite(project.value.id, user.id) await $api.auth.projectUserResendInvite(project.value.id, user.id)
toast.success('Invite email sent successfully') notification.success({
message: 'Invite email sent successfully',
})
await loadUsers() await loadUsers()
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:user:resend-invite') $e('a:user:resend-invite')
@ -126,7 +136,9 @@ const copyInviteUrl = (user: User) => {
const getInviteUrl = (token: string) => `${dashboardUrl}/user/authentication/signup/${token}` const getInviteUrl = (token: string) => `${dashboardUrl}/user/authentication/signup/${token}`
copy(getInviteUrl(user.invite_token)) copy(getInviteUrl(user.invite_token))
toast.success('Invite url copied to clipboard') notification.success({
message: 'Invite url copied to clipboard',
})
} }
onMounted(async () => { onMounted(async () => {

39
packages/nc-gui-v2/components/tabs/auth/user-management/ShareBase.vue

@ -1,11 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { onMounted, useClipboard, useNuxtApp, useProject } from '#imports' import { onMounted, useClipboard, useNuxtApp, useProject } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
const toast = useToast()
const { dashboardUrl } = $(useDashboard())
interface ShareBase { interface ShareBase {
uuid?: string uuid?: string
url?: string url?: string
@ -17,6 +14,8 @@ enum ShareBaseRole {
Viewer = 'viewer', Viewer = 'viewer',
} }
const { dashboardUrl } = $(useDashboard())
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
let base = $ref<null | ShareBase>(null) let base = $ref<null | ShareBase>(null)
@ -40,9 +39,9 @@ const loadBase = async () => {
role: res.roles, role: res.roles,
} }
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
message: await extractSdkResponseErrorMsg(e),
toast.error(await extractSdkResponseErrorMsg(e)) })
} }
} }
@ -57,9 +56,9 @@ const createShareBase = async (role = ShareBaseRole.Viewer) => {
base = res ?? {} base = res ?? {}
base!.role = role base!.role = role
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
message: await extractSdkResponseErrorMsg(e),
toast.error(await extractSdkResponseErrorMsg(e)) })
} }
$e('a:shared-base:enable', { role }) $e('a:shared-base:enable', { role })
@ -72,8 +71,9 @@ const disableSharedBase = async () => {
await $api.project.sharedBaseDisable(project.value.id) await $api.project.sharedBaseDisable(project.value.id)
base = null base = null
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:shared-base:disable') $e('a:shared-base:disable')
@ -91,9 +91,9 @@ const recreate = async () => {
base = { ...newBase, role: base?.role } base = { ...newBase, role: base?.role }
} catch (e: any) { } catch (e: any) {
console.error(e) notification.error({
message: await extractSdkResponseErrorMsg(e),
toast.error(await extractSdkResponseErrorMsg(e)) })
} }
$e('a:shared-base:recreate') $e('a:shared-base:recreate')
@ -104,7 +104,9 @@ const copyUrl = async () => {
await copy(url) await copy(url)
toast.success('Copied shareable base url to clipboard!') notification.success({
message: 'Copied shareable base url to clipboard!',
})
$e('c:shared-base:copy-url') $e('c:shared-base:copy-url')
} }
@ -127,7 +129,10 @@ frameborder="0"
width="100%" width="100%"
height="700" height="700"
style="background: transparent; border: 1px solid #ddd"></iframe>`) style="background: transparent; border: 1px solid #ddd"></iframe>`)
toast.success('Copied embeddable html code!')
notification.success({
message: 'Copied embeddable html code!',
})
$e('c:shared-base:copy-embed-frame') $e('c:shared-base:copy-embed-frame')
} }

20
packages/nc-gui-v2/components/tabs/auth/user-management/UsersModal.vue

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { Form, notification } from 'ant-design-vue'
import { Form } from 'ant-design-vue'
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import ShareBase from './ShareBase.vue' import ShareBase from './ShareBase.vue'
import SendIcon from '~icons/material-symbols/send-outline' import SendIcon from '~icons/material-symbols/send-outline'
@ -9,9 +8,7 @@ import MidAccountIcon from '~icons/mdi/account-outline'
import ContentCopyIcon from '~icons/mdi/content-copy' import ContentCopyIcon from '~icons/mdi/content-copy'
import type { User } from '~/lib/types' import type { User } from '~/lib/types'
import { ProjectRole } from '~/lib/enums' import { ProjectRole } from '~/lib/enums'
import { projectRoleTagColors, projectRoles } from '~/utils/userUtils' import { extractSdkResponseErrorMsg, isEmail, projectRoleTagColors, projectRoles } from '~/utils'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { isEmail } from '~/utils/validation'
interface Props { interface Props {
show: boolean show: boolean
@ -26,7 +23,6 @@ interface Users {
const { show, selectedUser } = defineProps<Props>() const { show, selectedUser } = defineProps<Props>()
const emit = defineEmits(['closed', 'reload']) const emit = defineEmits(['closed', 'reload'])
const toast = useToast()
const { project } = useProject() const { project } = useProject()
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
@ -93,10 +89,14 @@ const saveUser = async () => {
}) })
usersData.invitationToken = res.invite_token usersData.invitationToken = res.invite_token
} }
toast.success('Successfully updated the user details') notification.success({
message: 'Successfully updated the user details',
})
} catch (e: any) { } catch (e: any) {
console.error(e) console.error(e)
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }
@ -108,7 +108,9 @@ const copyUrl = async () => {
if (!inviteUrl) return if (!inviteUrl) return
copy(inviteUrl) copy(inviteUrl)
toast.success('Copied shareable base url to clipboard!') notification.success({
message: 'Copied shareable base url to clipboard!',
})
$e('c:shared-base:copy-url') $e('c:shared-base:copy-url')
} }

316
packages/nc-gui-v2/components/template/Editor.vue

@ -1,24 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification'
import type { ColumnType, TableType } from 'nocodb-sdk' import type { ColumnType, TableType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { Form } from 'ant-design-vue' import { Form, notification } from 'ant-design-vue'
import { tableColumns } from './utils' import { srcDestMappingColumns, tableColumns } from './utils'
import { computed, onMounted } from '#imports' import { computed, onMounted } from '#imports'
import MdiTableIcon from '~icons/mdi/table' import { extractSdkResponseErrorMsg, fieldRequiredValidator, getUIDTIcon } from '~/utils'
import MdiStringIcon from '~icons/mdi/alpha-a' import { MetaInj, ReloadViewDataHookInj } from '~/context'
import MdiLongTextIcon from '~icons/mdi/text'
import MdiNumericIcon from '~icons/mdi/numeric'
import MdiPlusIcon from '~icons/mdi/plus'
import MdiKeyStarIcon from '~icons/mdi/key-star'
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { fieldRequiredValidator } from '~/utils/validation'
interface Props { interface Props {
quickImportType: 'csv' | 'excel' | 'json' quickImportType: 'csv' | 'excel' | 'json'
projectTemplate: Record<string, any> projectTemplate: Record<string, any>
importData: any[] importData: Record<string, any>[]
importColumns: any[]
importOnly: boolean
maxRowsToParse: number
} }
interface Option { interface Option {
@ -26,10 +21,16 @@ interface Option {
value: string value: string
} }
const { quickImportType, projectTemplate, importData } = defineProps<Props>() const { quickImportType, projectTemplate, importData, importColumns, importOnly, maxRowsToParse } = defineProps<Props>()
const emit = defineEmits(['import']) const emit = defineEmits(['import'])
const meta = inject(MetaInj)
const columns = computed(() => meta?.value?.columns || [])
const reloadHook = inject(ReloadViewDataHookInj)!
const useForm = Form.useForm const useForm = Form.useForm
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
@ -67,8 +68,6 @@ const data = reactive<{ title: string | null; name: string; tables: TableType[]
tables: [], tables: [],
}) })
const toast = useToast()
const { addTab } = useTabs() const { addTab } = useTabs()
const { sqlUi, project, loadTables } = useProject() const { sqlUi, project, loadTables } = useProject()
@ -97,6 +96,8 @@ const validators = computed(() =>
}, {}), }, {}),
) )
const srcDestMapping = ref<Record<string, any>[]>([])
const { validate, validateInfos } = useForm(data, validators) const { validate, validateInfos } = useForm(data, validators)
function filterOption(input: string, option: Option) { function filterOption(input: string, option: Option) {
@ -174,7 +175,184 @@ function remapColNames(batchData: any[], columns: ColumnType[]) {
) )
} }
function missingRequiredColumnsValidation() {
const missingRequiredColumns = columns.value.filter(
(c: Record<string, any>) =>
(c.pk ? !c.ai && !c.cdf : !c.cdf && c.rqd) && !srcDestMapping.value.some((r) => r.destCn === c.title),
)
if (missingRequiredColumns.length) {
notification.error({
message: `Following columns are required : ${missingRequiredColumns.map((c) => c.title).join(', ')}`,
duration: 3,
})
return false
}
return true
}
function atLeastOneEnabledValidation() {
if (srcDestMapping.value.filter((v) => v.enabled === true).length === 0) {
notification.error({
message: 'At least one column has to be selected',
duration: 3,
})
return false
}
return true
}
function fieldsValidation(record: Record<string, any>) {
// if it is not selected, then pass validation
if (!record.enabled) {
return true
}
const tableName = meta?.value.title || ''
if (!record.destCn) {
notification.error({
message: `Cannot find the destination column for ${record.srcCn}`,
duration: 3,
})
return false
}
if (srcDestMapping.value.filter((v) => v.destCn === record.destCn).length > 1) {
notification.error({
message: 'Duplicate mapping found, please remove one of the mapping',
duration: 3,
})
return false
}
const v = columns.value.find((c) => c.title === record.destCn) as Record<string, any>
// check if the input contains null value for a required column
if (v.pk ? !v.ai && !v.cdf : !v.cdf && v.rqd) {
if (
importData[tableName]
.slice(0, maxRowsToParse)
.some((r: Record<string, any>) => r[record.srcCn] === null || r[record.srcCn] === undefined || r[record.srcCn] === '')
) {
notification.error({
message: 'null value violates not-null constraint',
duration: 3,
})
}
}
switch (v.uidt) {
case UITypes.Number:
if (
importData[tableName]
.slice(0, maxRowsToParse)
.some(
(r: Record<string, any>) => r[record.sourceCn] !== null && r[record.srcCn] !== undefined && isNaN(+r[record.srcCn]),
)
) {
notification.error({
message: 'Source data contains some invalid numbers',
duration: 3,
})
return false
}
break
case UITypes.Checkbox:
if (
importData[tableName].slice(0, maxRowsToParse).some((r: Record<string, any>) => {
if (r[record.srcCn] !== null && r[record.srcCn] !== undefined) {
let input = r[record.srcCn]
if (typeof input === 'string') {
input = input.replace(/["']/g, '').toLowerCase().trim()
return !(
input === 'false' ||
input === 'no' ||
input === 'n' ||
input === '0' ||
input === 'true' ||
input === 'yes' ||
input === 'y' ||
input === '1'
)
}
return input !== 1 && input !== 0 && input !== true && input !== false
}
return false
})
) {
notification.error({
message: 'Source data contains some invalid boolean values',
duration: 3,
})
return false
}
break
}
return true
}
async function importTemplate() { async function importTemplate() {
if (importOnly) {
// validate required columns
if (!missingRequiredColumnsValidation()) return
// validate at least one column needs to be selected
if (!atLeastOneEnabledValidation()) return
try {
isImporting.value = true
const tableName = meta?.value.title as string
const data = importData[tableName]
const projectName = project.value.title as string
const total = data.length
for (let i = 0, progress = 0; i < total; i += maxRowsToParse) {
const batchData = data.slice(i, i + maxRowsToParse).map((row: Record<string, any>) =>
srcDestMapping.value.reduce((res: Record<string, any>, col: Record<string, any>) => {
if (col.enabled && col.destCn) {
const v = columns.value.find((c: Record<string, any>) => c.title === col.destCn) as Record<string, any>
let input = row[col.srcCn]
// parse potential boolean values
if (v.uidt === UITypes.Checkbox) {
input = input.replace(/["']/g, '').toLowerCase().trim()
if (input === 'false' || input === 'no' || input === 'n') {
input = '0'
} else if (input === 'true' || input === 'yes' || input === 'y') {
input = '1'
}
} else if (v.uidt === UITypes.Number) {
if (input === '') {
input = null
}
} else if (v.uidt === UITypes.SingleSelect || v.uidt === UITypes.MultiSelect) {
if (input === '') {
input = null
}
}
res[col.destCn] = input
}
return res
}, {}),
)
await $api.dbTableRow.bulkCreate('noco', projectName, tableName, batchData)
importingTip.value = `Importing data to ${projectName}: ${progress}/${total} records`
progress += batchData.length
}
// reload table
reloadHook.trigger()
notification.success({
message: 'Successfully imported table data',
duration: 3,
})
} catch (e: any) {
notification.error({
message: e.message,
duration: 3,
})
} finally {
isImporting.value = false
}
} else {
// check if form is valid // check if form is valid
try { try {
await validate() await validate()
@ -235,7 +413,7 @@ async function importTemplate() {
if (importData) { if (importData) {
let total = 0 let total = 0
let progress = 0 let progress = 0
const offset = 500 const offset = maxRowsToParse
const projectName = project.value.title as string const projectName = project.value.title as string
await Promise.all( await Promise.all(
data.tables.map((table: Record<string, any>) => data.tables.map((table: Record<string, any>) =>
@ -261,13 +439,23 @@ async function importTemplate() {
type: 'table', type: 'table',
}) })
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally { } finally {
isImporting.value = false isImporting.value = false
} }
}
} }
const isValid = computed(() => { const isValid = computed(() => {
if (importOnly) {
for (const record of srcDestMapping.value) {
if (!fieldsValidation(record)) {
return false
}
}
} else {
for (const [_, o] of Object.entries(validateInfos)) { for (const [_, o] of Object.entries(validateInfos)) {
if (o?.validateStatus) { if (o?.validateStatus) {
if (o.validateStatus === 'error') { if (o.validateStatus === 'error') {
@ -275,18 +463,92 @@ const isValid = computed(() => {
} }
} }
} }
}
return true return true
}) })
function mapDefaultColumns() {
srcDestMapping.value = []
for (const col of importColumns[0]) {
const o = { srcCn: col.column_name, destCn: '', enabled: true }
if (columns.value) {
const tableColumn = columns.value.find((c: Record<string, any>) => c.title === col.column_name)
if (tableColumn) {
o.destCn = tableColumn.title as string
} else {
o.enabled = false
}
}
srcDestMapping.value.push(o)
}
}
defineExpose({ defineExpose({
importTemplate, importTemplate,
isValid, isValid,
}) })
onMounted(() => {
if (importOnly) {
mapDefaultColumns()
}
})
</script> </script>
<template> <template>
<a-spin :spinning="isImporting" :tip="importingTip" size="large"> <a-spin :spinning="isImporting" :tip="importingTip" size="large">
<a-card> <a-card v-if="importOnly">
<a-form :model="data" name="import-only">
<p v-if="data.tables && quickImportType === 'excel'" class="text-center">
{{ data.tables.length }} sheet{{ data.tables.length > 1 ? 's' : '' }}
available for import
</p>
</a-form>
<a-collapse v-if="data.tables && data.tables.length" v-model:activeKey="expansionPanel" class="template-collapse" accordion>
<a-collapse-panel v-for="(table, tableIdx) in data.tables" :key="tableIdx">
<template #header>
<span class="font-weight-bold text-lg flex items-center gap-2">
<mdi-table class="text-primary" />
{{ table.ref_table_name }}
</span>
</template>
<a-table
v-if="srcDestMapping"
class="template-form"
row-class-name="template-form-row"
:data-source="srcDestMapping"
:columns="srcDestMappingColumns"
:pagination="false"
>
<template #headerCell="{ column }">
<span v-if="column.key === 'source_column' || column.key === 'destination_column'">
{{ column.name }}
</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'source_column'">
<span>{{ record.srcCn }}</span>
</template>
<template v-else-if="column.key === 'destination_column'">
<a-select v-model:value="record.destCn" class="w-52" show-search :filter-option="filterOption">
<a-select-option v-for="(col, i) of columns" :key="i" :value="col.title">
<div class="flex items-center">
<component :is="getUIDTIcon(col.uidt)" />
<span class="ml-2">{{ col.title }}</span>
</div>
</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'action'">
<a-checkbox v-model:checked="record.enabled" />
</template>
</template>
</a-table>
</a-collapse-panel>
</a-collapse>
</a-card>
<a-card v-else>
<a-form :model="data" name="template-editor-form" @keydown.enter="emit('import')"> <a-form :model="data" name="template-editor-form" @keydown.enter="emit('import')">
<p v-if="data.tables && quickImportType === 'excel'" class="text-center"> <p v-if="data.tables && quickImportType === 'excel'" class="text-center">
{{ data.tables.length }} sheet{{ data.tables.length > 1 ? 's' : '' }} {{ data.tables.length }} sheet{{ data.tables.length > 1 ? 's' : '' }}
@ -312,7 +574,7 @@ defineExpose({
/> />
</a-form-item> </a-form-item>
<span v-else class="font-weight-bold text-lg flex items-center gap-2" @click="setEditableTn(tableIdx, true)"> <span v-else class="font-weight-bold text-lg flex items-center gap-2" @click="setEditableTn(tableIdx, true)">
<MdiTableIcon class="text-primary" /> <mdi-table class="text-primary" />
{{ table.table_name }} {{ table.table_name }}
</span> </span>
</template> </template>
@ -322,7 +584,7 @@ defineExpose({
<!-- TODO: i18n --> <!-- TODO: i18n -->
<span>Delete Table</span> <span>Delete Table</span>
</template> </template>
<MdiDeleteOutlineIcon v-if="data.tables.length > 1" class="text-lg mr-8" @click.stop="deleteTable(tableIdx)" /> <mdi-delete-outline v-if="data.tables.length > 1" class="text-lg mr-8" @click.stop="deleteTable(tableIdx)" />
</a-tooltip> </a-tooltip>
</template> </template>
@ -390,7 +652,7 @@ defineExpose({
<span>Primary Value</span> <span>Primary Value</span>
</template> </template>
<div class="flex items-center float-right mr-4"> <div class="flex items-center float-right mr-4">
<MdiKeyStarIcon class="text-lg" /> <mdi-key-star class="text-lg" />
</div> </div>
</a-tooltip> </a-tooltip>
<a-tooltip v-else> <a-tooltip v-else>
@ -401,7 +663,7 @@ defineExpose({
<a-button type="text" @click="deleteTableColumn(tableIdx, record.key)"> <a-button type="text" @click="deleteTableColumn(tableIdx, record.key)">
<div class="flex items-center"> <div class="flex items-center">
<MdiDeleteOutlineIcon class="text-lg" /> <mdi-delete-outline class="text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -417,7 +679,7 @@ defineExpose({
<a-button @click="addNewColumnRow(table, 'Number')"> <a-button @click="addNewColumnRow(table, 'Number')">
<div class="flex items-center"> <div class="flex items-center">
<MdiNumericIcon class="text-lg" /> <mdi-numeric class="text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -429,7 +691,7 @@ defineExpose({
</template> </template>
<a-button @click="addNewColumnRow(table, 'SingleLineText')"> <a-button @click="addNewColumnRow(table, 'SingleLineText')">
<div class="flex items-center"> <div class="flex items-center">
<MdiStringIcon class="text-lg" /> <mdi-alpha-a class="text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -441,7 +703,7 @@ defineExpose({
</template> </template>
<a-button @click="addNewColumnRow(table, 'LongText')"> <a-button @click="addNewColumnRow(table, 'LongText')">
<div class="flex items-center"> <div class="flex items-center">
<MdiLongTextIcon class="text-lg" /> <mdi-text class="text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -453,7 +715,7 @@ defineExpose({
</template> </template>
<a-button @click="addNewColumnRow(table, 'SingleLineText')"> <a-button @click="addNewColumnRow(table, 'SingleLineText')">
<div class="flex items-center"> <div class="flex items-center">
<MdiPlusIcon class="text-lg" /> <mdi-plus class="text-lg" />
Column Column
</div> </div>
</a-button> </a-button>

20
packages/nc-gui-v2/components/template/utils.ts

@ -23,3 +23,23 @@ export const tableColumns: (Omit<ColumnGroupType<any>, 'children'> & { dataIndex
align: 'right', align: 'right',
}, },
] ]
export const srcDestMappingColumns: (Omit<ColumnGroupType<any>, 'children'> & { dataIndex?: string; name: string })[] = [
{
name: 'Source column',
dataIndex: 'source_column',
key: 'source_column',
width: 400,
},
{
name: 'Destination column',
dataIndex: 'destination_column',
key: 'destination_column',
width: 400,
},
{
name: 'Action',
key: 'action',
align: 'right',
},
]

21
packages/nc-gui-v2/components/webhook/Editor.vue

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Form } from 'ant-design-vue' import { Form, notification } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { MetaInj } from '~/context' import { MetaInj } from '~/context'
import { extractSdkResponseErrorMsg, fieldRequiredValidator } from '~/utils' import { extractSdkResponseErrorMsg, fieldRequiredValidator } from '~/utils'
import { inject, reactive, useApi, useNuxtApp } from '#imports' import { inject, reactive, useApi, useNuxtApp } from '#imports'
@ -16,8 +15,6 @@ const { $e } = useNuxtApp()
const { api, isLoading: loading } = useApi() const { api, isLoading: loading } = useApi()
const toast = useToast()
const meta = inject(MetaInj) const meta = inject(MetaInj)
const useForm = Form.useForm const useForm = Form.useForm
@ -305,7 +302,9 @@ async function loadPluginList() {
hook.eventOperation = `${hook.event} ${hook.operation}` hook.eventOperation = `${hook.event} ${hook.operation}`
} }
} catch (e: any) { } catch (e: any) {
toast.error(extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }
@ -314,7 +313,9 @@ async function saveHooks() {
try { try {
await validate() await validate()
} catch (_: any) { } catch (_: any) {
toast.error('Invalid Form') notification.error({
message: 'Invalid Form',
})
loading.value = false loading.value = false
@ -352,9 +353,13 @@ async function saveHooks() {
// }); // });
// } // }
toast.success('Webhook details updated successfully') notification.success({
message: 'Webhook details updated successfully',
})
} catch (e: any) { } catch (e: any) {
toast.error(extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally { } finally {
loading.value = false loading.value = false
} }

17
packages/nc-gui-v2/components/webhook/List.vue

@ -1,14 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { MetaInj } from '~/context' import { MetaInj } from '~/context'
import { inject, onMounted, ref, useNuxtApp } from '#imports' import { inject, onMounted, ref, useNuxtApp } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
const emit = defineEmits(['edit']) const emit = defineEmits(['edit'])
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const toast = useToast()
const hooks = ref<Record<string, any>[]>([]) const hooks = ref<Record<string, any>[]>([])
const meta = inject(MetaInj) const meta = inject(MetaInj)
@ -21,7 +20,9 @@ async function loadHooksList() {
return hook return hook
}) })
} catch (e: any) { } catch (e: any) {
toast.error(e.message) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }
@ -33,12 +34,16 @@ async function deleteHook(item: Record<string, any>, index: number) {
} else { } else {
hooks.value.splice(index, 1) hooks.value.splice(index, 1)
} }
toast.success('Hook deleted successfully') notification.success({
message: 'Hook deleted successfully',
})
if (!hooks.value.length) { if (!hooks.value.length) {
hooks.value = [] hooks.value = []
} }
} catch (e: any) { } catch (e: any) {
toast.error(e.message) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
$e('a:webhook:delete') $e('a:webhook:delete')

12
packages/nc-gui-v2/components/webhook/Test.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from '@vue/runtime-core' import { onMounted } from '@vue/runtime-core'
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { MetaInj } from '~/context' import { MetaInj } from '~/context'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
@ -12,8 +12,6 @@ const { hook } = defineProps<Props>()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const toast = useToast()
const meta = inject(MetaInj) const meta = inject(MetaInj)
const sampleData = ref({ const sampleData = ref({
@ -41,9 +39,13 @@ async function testWebhook() {
payload: sampleData.value, payload: sampleData.value,
}) })
toast.success('Webhook tested successfully') notification.success({
message: 'Webhook tested successfully',
})
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }

24
packages/nc-gui-v2/composables/useColumnCreateStore.ts

@ -1,9 +1,8 @@
import { createInjectionState } from '@vueuse/core' import { createInjectionState } from '@vueuse/core'
import { Form } from 'ant-design-vue' import { Form, notification } from 'ant-design-vue'
import type { ColumnType, TableType } from 'nocodb-sdk' import type { ColumnType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { useToast } from 'vue-toastification'
import { useColumn } from './useColumn' import { useColumn } from './useColumn'
import { computed } from '#imports' import { computed } from '#imports'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
@ -25,7 +24,6 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
const { sqlUi } = useProject() const { sqlUi } = useProject()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const { getMeta } = useMetas() const { getMeta } = useMetas()
const toast = useToast()
const idType = null const idType = null
@ -178,12 +176,21 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
try { try {
console.log(formState, validators) console.log(formState, validators)
if (!(await validate())) return if (!(await validate())) return
} catch (e) {
notification.error({
message: 'Form validation failed',
})
return
}
try {
formState.value.table_name = meta.value.table_name formState.value.table_name = meta.value.table_name
// formState.value.title = formState.value.column_name // formState.value.title = formState.value.column_name
if (column?.value) { if (column?.value) {
await $api.dbTableColumn.update(column?.value?.id as string, formState.value) await $api.dbTableColumn.update(column?.value?.id as string, formState.value)
toast.success('Column updated') notification.success({
message: 'Column updated',
})
} else { } else {
// todo : set additional meta for auto generated string id // todo : set additional meta for auto generated string id
if (formState.value.uidt === UITypes.ID) { if (formState.value.uidt === UITypes.ID) {
@ -201,12 +208,15 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
getMeta(formState.value.childId, true).then(() => {}) getMeta(formState.value.childId, true).then(() => {})
} }
toast.success('Column created') notification.success({
message: 'Column created',
})
} }
onSuccess?.() onSuccess?.()
} catch (e: any) { } catch (e: any) {
const error = await extractSdkResponseErrorMsg(e) notification.error({
if (error) toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
} }

18
packages/nc-gui-v2/composables/useTable.ts

@ -1,7 +1,6 @@
import { Modal } from 'ant-design-vue' import { Modal, notification } from 'ant-design-vue'
import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk' import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { useProject } from './useProject' import { useProject } from './useProject'
import { TabType } from '~/composables/useTabs' import { TabType } from '~/composables/useTabs'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
@ -15,7 +14,6 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
}) })
const { $e, $api } = useNuxtApp() const { $e, $api } = useNuxtApp()
const toast = useToast()
const { getMeta, removeMeta } = useMetas() const { getMeta, removeMeta } = useMetas()
const { loadTables } = useProject() const { loadTables } = useProject()
const { closeTab } = useTabs() const { closeTab } = useTabs()
@ -78,13 +76,13 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
return `${i + 1}. ${c.title} is a LinkToAnotherRecord of ${(refMeta && refMeta.title) || c.title}` return `${i + 1}. ${c.title} is a LinkToAnotherRecord of ${(refMeta && refMeta.title) || c.title}`
}), }),
) )
toast.info( notification.info({
h('div', { message: h('div', {
innerHTML: `<div style="padding:10px 4px">Unable to delete tables because of the following. innerHTML: `<div style="padding:10px 4px">Unable to delete tables because of the following.
<br><br>${refColMsgs.join('<br>')}<br><br> <br><br>${refColMsgs.join('<br>')}<br><br>
Delete them & try again</div>`, Delete them & try again</div>`,
}), }),
) })
return return
} }
@ -99,10 +97,14 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
await loadTables() await loadTables()
removeMeta(table.id as string) removeMeta(table.id as string)
toast.info(`Deleted table ${table.title} successfully`) notification.info({
message: `Deleted table ${table.title} successfully`,
})
$e('a:table:delete') $e('a:table:delete')
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
}, },
}) })

4
packages/nc-gui-v2/composables/useViewData.ts

@ -89,7 +89,7 @@ export function useViewData(
for (const row of formattedData.value) { for (const row of formattedData.value) {
const id = extractPkFromRow(row.row, meta?.value?.columns as ColumnType[]) const id = extractPkFromRow(row.row, meta?.value?.columns as ColumnType[])
row.rowMeta.commentCount = aggCommentCount.value?.find((c) => c.row_id === id)?.count || 0 row.rowMeta.commentCount = aggCommentCount.value?.find((c: Record<string, any>) => c.row_id === id)?.count || 0
} }
} }
@ -262,7 +262,7 @@ export function useViewData(
let row = formattedData.value.length let row = formattedData.value.length
while (row--) { while (row--) {
try { try {
const { row: rowObj, rowMeta } = formattedData.value[row] const { row: rowObj, rowMeta } = formattedData.value[row] as Record<string, any>
if (!rowMeta.selected) { if (!rowMeta.selected) {
continue continue
} }

4
packages/nc-gui-v2/composables/useViews.ts

@ -3,7 +3,7 @@ import type { MaybeRef } from '@vueuse/core'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
export function useViews(meta: MaybeRef<TableType | undefined>) { export function useViews(meta: MaybeRef<TableType | undefined>) {
let views = $ref<ViewType[]>([]) const views = $ref<ViewType[]>([])
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const loadViews = async () => { const loadViews = async () => {
@ -12,7 +12,7 @@ export function useViews(meta: MaybeRef<TableType | undefined>) {
if (_meta && _meta.id) { if (_meta && _meta.id) {
const response = (await $api.dbView.list(_meta.id)).list const response = (await $api.dbView.list(_meta.id)).list
if (response) { if (response) {
views = response.sort((a, b) => a.order! - b.order!) // views = response.sort((a, b) => a.order! - b.order!)
} }
} }
} }

15
packages/nc-gui-v2/package-lock.json generated

@ -24,7 +24,6 @@
"util": "^0.12.4", "util": "^0.12.4",
"vue-dompurify-html": "^3.0.0", "vue-dompurify-html": "^3.0.0",
"vue-i18n": "^9.1.10", "vue-i18n": "^9.1.10",
"vue-toastification": "^2.0.0-rc.5",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"vuetify": "^3.0.0-alpha.13", "vuetify": "^3.0.0-alpha.13",
"xlsx": "^0.17.3" "xlsx": "^0.17.3"
@ -14629,14 +14628,6 @@
"vue": "^3.2.0" "vue": "^3.2.0"
} }
}, },
"node_modules/vue-toastification": {
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz",
"integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==",
"peerDependencies": {
"vue": "^3.0.2"
}
},
"node_modules/vue-types": { "node_modules/vue-types": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",
@ -26117,12 +26108,6 @@
"@vue/devtools-api": "^6.0.0" "@vue/devtools-api": "^6.0.0"
} }
}, },
"vue-toastification": {
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz",
"integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==",
"requires": {}
},
"vue-types": { "vue-types": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",

3
packages/nc-gui-v2/package.json

@ -26,11 +26,10 @@
"socket.io-client": "^4.5.1", "socket.io-client": "^4.5.1",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"vue-dompurify-html": "^3.0.0",
"url": "^0.11.0", "url": "^0.11.0",
"util": "^0.12.4", "util": "^0.12.4",
"vue-dompurify-html": "^3.0.0",
"vue-i18n": "^9.1.10", "vue-i18n": "^9.1.10",
"vue-toastification": "^2.0.0-rc.5",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"vuetify": "^3.0.0-alpha.13", "vuetify": "^3.0.0-alpha.13",
"xlsx": "^0.17.3" "xlsx": "^0.17.3"

11
packages/nc-gui-v2/pages/index/index.vue

@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Modal } from 'ant-design-vue' import { Modal, notification } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk' import type { ProjectType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { navigateTo } from '#app' import { navigateTo } from '#app'
import { computed, onMounted, ref, useApi, useNuxtApp, useSidebar } from '#imports' import { computed, onMounted, ref, useApi, useNuxtApp, useSidebar } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
@ -18,8 +17,6 @@ const { api, isLoading } = useApi()
useSidebar({ hasSidebar: true, isOpen: true }) useSidebar({ hasSidebar: true, isOpen: true })
const toast = useToast()
const filterQuery = ref('') const filterQuery = ref('')
const projects = ref<ProjectType[]>() const projects = ref<ProjectType[]>()
@ -45,10 +42,12 @@ const deleteProject = (project: ProjectType) => {
async onOk() { async onOk() {
try { try {
$e('c:project:delete') $e('c:project:delete')
await $api.project.delete(project.id as string) await api.project.delete(project.id as string)
return projects.value?.splice(projects.value.indexOf(project), 1) return projects.value?.splice(projects.value.indexOf(project), 1)
} catch (e: any) { } catch (e: any) {
return toast.error(await extractSdkResponseErrorMsg(e)) return notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
}, },
}) })

17
packages/nc-gui-v2/pages/project/index/[id].vue

@ -3,10 +3,9 @@ import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue' import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk' import type { ProjectType } from 'nocodb-sdk'
import { ref } from 'vue' import { ref } from 'vue'
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { navigateTo, useRoute } from '#app' import { navigateTo, useRoute } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg, projectTitleValidator } from '~/utils'
import { projectTitleValidator } from '~/utils/validation'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline' import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
import { nextTick, reactive, useSidebar } from '#imports' import { nextTick, reactive, useSidebar } from '#imports'
@ -14,8 +13,6 @@ const { api } = useApi()
useSidebar({ hasSidebar: false }) useSidebar({ hasSidebar: false })
const toast = useToast()
const route = useRoute() const route = useRoute()
const nameValidationRules = [ const nameValidationRules = [
@ -32,10 +29,12 @@ const formState = reactive({
const getProject = async () => { const getProject = async () => {
try { try {
const result: ProjectType = await $api.project.read(route.params.id as string) const result: ProjectType = await api.project.read(route.params.id as string)
formState.title = result.title as string formState.title = result.title as string
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }
@ -45,7 +44,9 @@ const renameProject = async () => {
navigateTo(`/nc/${route.params.id}`) navigateTo(`/nc/${route.params.id}`)
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }

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

@ -1,8 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from '@vue/runtime-core' import { onMounted } from '@vue/runtime-core'
import { Form, Modal } from 'ant-design-vue' import { Form, Modal, notification } from 'ant-design-vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useToast } from 'vue-toastification'
import { computed, ref, useSidebar, watch } from '#imports' import { computed, ref, useSidebar, watch } from '#imports'
import { navigateTo, useNuxtApp } from '#app' import { navigateTo, useNuxtApp } from '#app'
import { ClientType } from '~/lib' import { ClientType } from '~/lib'
@ -27,8 +26,6 @@ const { $api, $e } = useNuxtApp()
useSidebar({ hasSidebar: false }) useSidebar({ hasSidebar: false })
const toast = useToast()
const { t } = useI18n() const { t } = useI18n()
const formState = $ref<ProjectCreateForm>({ const formState = $ref<ProjectCreateForm>({
@ -145,8 +142,9 @@ const createProject = async () => {
$e('a:project:create:extdb') $e('a:project:create:extdb')
await navigateTo(`/nc/${result.id}`) await navigateTo(`/nc/${result.id}`)
} catch (e: any) { } catch (e: any) {
// todo: toast notification.error({
toast.error(await extractSdkResponseErrorMsg(e)) message: await extractSdkResponseErrorMsg(e),
})
} }
loading.value = false loading.value = false
} }
@ -187,12 +185,16 @@ const testConnection = async () => {
}) })
} else { } else {
testSuccess.value = false testSuccess.value = false
toast.error(`${t('msg.error.dbConnectionFailed')} ${result.message}`) notification.error({
message: `${t('msg.error.dbConnectionFailed')} ${result.message}`,
})
} }
} }
} catch (e: any) { } catch (e: any) {
testSuccess.value = false testSuccess.value = false
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }

8
packages/nc-gui-v2/pages/project/index/create.vue

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from '@vue/runtime-core' import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue' import type { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification' import { notification } from 'ant-design-vue'
import { nextTick, reactive, ref, useApi, useSidebar } from '#imports' import { nextTick, reactive, ref, useApi, useSidebar } from '#imports'
import { navigateTo, useNuxtApp } from '#app' import { navigateTo, useNuxtApp } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
@ -14,8 +14,6 @@ const { api, isLoading } = useApi()
useSidebar({ hasSidebar: false }) useSidebar({ hasSidebar: false })
const toast = useToast()
const nameValidationRules = [ const nameValidationRules = [
{ {
required: true, required: true,
@ -37,7 +35,9 @@ const createProject = async () => {
await navigateTo(`/nc/${result.id}`) await navigateTo(`/nc/${result.id}`)
} catch (e: any) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
} }

10
packages/nc-gui-v2/pages/projects/index.vue

@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Modal } from 'ant-design-vue' import { Modal, notification } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk' import type { ProjectType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { navigateTo } from '#app' import { navigateTo } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
import MaterialSymbolsFormatListBulletedRounded from '~icons/material-symbols/format-list-bulleted-rounded' import MaterialSymbolsFormatListBulletedRounded from '~icons/material-symbols/format-list-bulleted-rounded'
@ -33,7 +32,6 @@ const navDrawerOptions = [
const route = useRoute() const route = useRoute()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const toast = useToast()
const response = await $api.project.list({}) const response = await $api.project.list({})
const projects = $ref(response.list) const projects = $ref(response.list)
@ -50,8 +48,10 @@ const deleteProject = (project: ProjectType) => {
try { try {
await $api.project.delete(project.id as string) await $api.project.delete(project.id as string)
projects.splice(projects.indexOf(project), 1) projects.splice(projects.indexOf(project), 1)
} catch (e) { } catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e)) notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} }
}, },
}) })

8
packages/nc-gui-v2/plugins/toastr.ts

@ -1,8 +0,0 @@
import { defineNuxtPlugin } from 'nuxt/app'
import Toast from 'vue-toastification'
// Import the CSS or use your own!
import 'vue-toastification/dist/index.css'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Toast, {})
})

1
packages/nc-gui-v2/utils/index.ts

@ -16,3 +16,4 @@ export * from './validation'
export * from './viewUtils' export * from './viewUtils'
export * from './currencyUtils' export * from './currencyUtils'
export * from './dataUtils' export * from './dataUtils'
export * from './userUtils'

4
packages/nc-gui-v2/utils/parsers/ExcelTemplateAdapter.ts

@ -237,4 +237,8 @@ export default class ExcelTemplateAdapter extends TemplateGenerator {
getData() { getData() {
return this.data return this.data
} }
getColumns() {
return this.project.tables.map((t: Record<string, any>) => t.columns)
}
} }

Loading…
Cancel
Save