Browse Source

Merge pull request #8510 from nocodb/nc-refactor/ds

Nc refactor/ds
pull/8516/head
Pranav C 8 months ago committed by GitHub
parent
commit
c7cc1f92fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  2. 358
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  3. 93
      packages/nc-gui/components/dashboard/settings/Modal.vue
  4. 2
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  5. 53
      packages/nc-gui/components/dlg/ProjectAudit.vue
  6. 6
      packages/nc-gui/components/project/AllTables.vue
  7. 4
      packages/nc-gui/components/project/View.vue
  8. 2
      packages/nc-gui/lang/en.json
  9. 8
      packages/noco-docs/docs/100.data-sources/010.data-source-overview.md
  10. 29
      packages/noco-docs/docs/100.data-sources/020.connect-to-data-source.md
  11. 18
      packages/noco-docs/docs/100.data-sources/030.sync-with-data-source.md
  12. 69
      packages/noco-docs/docs/100.data-sources/040.actions-on-data-sources.md
  13. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-1.png
  14. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-2.png
  15. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-audit.png
  16. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-edit.png
  17. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-erd.png
  18. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-hide.png
  19. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-meta-sync-1.png
  20. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-meta-sync-2.png
  21. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-remove.png
  22. BIN
      packages/noco-docs/static/img/v2/data-source/data-source-uiacl.png
  23. BIN
      packages/noco-docs/static/img/v2/data-source/ds-connect-1.png
  24. BIN
      packages/noco-docs/static/img/v2/data-source/ds-connect-2.png
  25. BIN
      packages/noco-docs/static/img/v2/data-source/ds-connect-3.png
  26. BIN
      packages/noco-docs/static/img/v2/data-source/ds-connect-4.png
  27. 4
      tests/playwright/pages/Dashboard/ProjectView/Metadata.ts
  28. 3
      tests/playwright/pages/Dashboard/ProjectView/index.ts
  29. 30
      tests/playwright/pages/Dashboard/Settings/DataSources.ts
  30. 4
      tests/playwright/pages/Dashboard/TreeView.ts
  31. 6
      tests/playwright/tests/db/features/erd.spec.ts
  32. 13
      tests/playwright/tests/db/features/metaSync.spec.ts
  33. 6
      tests/playwright/tests/db/general/tableOperations.spec.ts

3
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -331,6 +331,7 @@ const deleteTable = () => {
<NcMenuItem
v-if="isUIAllowed('tableRename', { roles: baseRole })"
:data-testid="`sidebar-table-rename-${table.title}`"
class="nc-table-rename"
@click="openRenameTableDialog(table, base.sources[sourceIndex].id)"
>
<div v-e="['c:table:rename']" class="flex gap-2 items-center">
@ -358,7 +359,7 @@ const deleteTable = () => {
<NcMenuItem
v-if="isUIAllowed('tableDelete', { roles: baseRole })"
:data-testid="`sidebar-table-delete-${table.title}`"
class="!text-red-500 !hover:bg-red-50"
class="!text-red-500 !hover:bg-red-50 nc-table-delete"
@click="deleteTable"
>
<div v-e="['c:table:delete']" class="flex gap-2 items-center">

358
packages/nc-gui/components/dashboard/settings/DataSources.vue

@ -25,6 +25,8 @@ const { isDataSourceLimitReached } = storeToRefs(basesStore)
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
const { isUIAllowed } = useRoles()
const { projectPageTab } = storeToRefs(useConfigStore())
const { refreshCommandPalette } = useCommandPalette()
@ -253,67 +255,24 @@ const isNewBaseModalOpen = computed({
},
})
const isErdModalOpen = computed({
get: () => {
return [DataSourcesSubTab.ERD].includes(vState.value as any)
},
set: (val) => {
if (!val) {
vState.value = ''
}
},
})
const isMetaDataModal = computed({
get: () => {
return [DataSourcesSubTab.Metadata].includes(vState.value as any)
},
set: (val) => {
if (!val) {
vState.value = ''
}
},
})
const isUIAclModalOpen = computed({
get: () => {
return [DataSourcesSubTab.UIAcl].includes(vState.value as any)
},
set: (val) => {
if (!val) {
vState.value = ''
}
},
})
const isBaseAuditModalOpen = computed({
get: () => {
return [DataSourcesSubTab.Audit].includes(vState.value as any)
},
set: (val) => {
if (!val) {
vState.value = ''
}
},
})
const isEditBaseModalOpen = computed({
get: () => {
return [DataSourcesSubTab.Edit].includes(vState.value as any)
},
set: (val) => {
if (!val) {
vState.value = ''
}
},
})
const activeSource = ref<SourceType>(null)
const openedTab = ref('erd')
</script>
<template>
<div class="flex flex-row w-full h-full nc-data-sources-view">
<div class="flex flex-col w-full overflow-auto">
<div class="flex flex-row w-full justify-end mt-6.5 mb-2">
<div class="flex flex-col h-full">
<div class="px-4 py-2 flex justify-between">
<a-breadcrumb separator=">" class="w-full cursor-pointer font-weight-bold">
<a-breadcrumb-item @click="activeSource = null">
<a class="!no-underline">Data Sources</a>
</a-breadcrumb-item>
<a-breadcrumb-item v-if="activeSource">
<span class="capitalize">{{ activeSource.alias || 'Default Source' }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
<NcButton
v-if="!isDataSourceLimitReached"
v-if="!isDataSourceLimitReached && !activeSource && isUIAllowed('sourceCreate')"
size="large"
class="z-10 !px-2"
type="primary"
@ -325,6 +284,69 @@ const isEditBaseModalOpen = computed({
</div>
</NcButton>
</div>
<div data-testid="nc-settings-datasources" class="flex flex-row w-full nc-data-sources-view flex-grow min-h-0">
<template v-if="activeSource">
<NcTabs v-model:activeKey="openedTab" class="nc-source-tab w-full">
<a-tab-pane key="erd">
<template #tab>
<div class="tab" data-testid="nc-erd-tab">
<div>{{ $t('title.erdView') }}</div>
</div>
</template>
<div class="h-full pt-4">
<LazyDashboardSettingsErd class="h-full overflow-auto" :source-id="activeSource.id" :show-all-columns="false" />
</div>
</a-tab-pane>
<a-tab-pane v-if="sources && activeSource === sources[0]" key="audit">
<template #tab>
<div class="tab" data-testid="nc-audit-tab">
<div>{{ $t('title.auditLogs') }}</div>
</div>
</template>
<div class="p-4 h-full overflow-auto">
<LazyDashboardSettingsBaseAudit :source-id="activeSource.id" />
</div>
</a-tab-pane>
<a-tab-pane v-if="!activeSource.is_meta && !activeSource.is_local" key="audit">
<template #tab>
<div class="tab" data-testid="nc-connection-tab">
<div>{{ $t('labels.connectionDetails') }}</div>
</div>
</template>
<div class="p-6 mt-4 h-full overflow-auto">
<LazyDashboardSettingsDataSourcesEditBase
class="w-600px"
:source-id="activeSource.id"
@source-updated="loadBases(true)"
@close="activeSource = null"
/>
</div>
</a-tab-pane>
<a-tab-pane key="acl">
<template #tab>
<div class="tab" data-testid="nc-acl-tab">
<div>{{ $t('labels.uiAcl') }}</div>
</div>
</template>
<div class="pt-4 h-full overflow-auto">
<LazyDashboardSettingsUIAcl :source-id="activeSource.id" />
</div>
</a-tab-pane>
<a-tab-pane v-if="!activeSource.is_meta && !activeSource.is_local" key="meta-sync">
<template #tab>
<div class="tab" data-testid="nc-meta-sync-tab">
<div>{{ $t('labels.metaSync') }}</div>
</div>
</template>
<div class="pt-4 h-full overflow-auto">
<LazyDashboardSettingsMetadata :source-id="activeSource.id" @source-synced="loadBases(true)" />
</div>
</a-tab-pane>
</NcTabs>
</template>
<div v-else class="flex flex-col w-full overflow-auto mt-1">
<div
class="overflow-y-auto nc-scrollbar-md"
:style="{
@ -336,16 +358,15 @@ const isEditBaseModalOpen = computed({
<div class="ds-table-col ds-table-enabled cursor-pointer">{{ $t('general.visibility') }}</div>
<div class="ds-table-col ds-table-name">{{ $t('general.name') }}</div>
<div class="ds-table-col ds-table-type">{{ $t('general.type') }}</div>
<div class="ds-table-col ds-table-actions -ml-13">{{ $t('labels.actions') }}</div>
<div class="ds-table-col ds-table-crud"></div>
<div class="ds-table-col ds-table-actions">{{ $t('labels.actions') }}</div>
</div>
</div>
<div class="ds-table-body">
<Draggable :list="sources" item-key="id" handle=".ds-table-handle" @end="moveBase">
<template #header>
<div v-if="sources[0]" class="ds-table-row border-gray-200">
<div v-if="sources[0]" class="ds-table-row border-gray-200 cursor-pointer" @click="activeSource = sources[0]">
<div class="ds-table-col ds-table-enabled">
<div class="flex items-center gap-1">
<div class="flex items-center gap-1" @click.stop>
<div v-if="sources.length > 2" class="ds-table-handle" />
<a-tooltip>
<template #title>
@ -373,80 +394,12 @@ const isEditBaseModalOpen = computed({
</div>
<div class="ds-table-col ds-table-actions">
<div class="flex items-center gap-2">
<NcTooltip v-if="!sources[0].is_meta && !sources[0].is_local">
<template #title>
{{ $t('tooltip.metaSync') }}
</template>
<NcButton
class="nc-action-btn cursor-pointer outline-0"
type="text"
data-testid="nc-data-sources-view-meta-sync"
size="small"
@click="baseAction(sources[0].id, DataSourcesSubTab.Metadata)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="sync" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
{{ $t('title.relations') }}
</template>
<NcButton
size="small"
class="nc-action-btn cursor-pointer outline-0"
type="text"
data-testid="nc-data-sources-view-erd"
@click="baseAction(sources[0].id, DataSourcesSubTab.ERD)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="erd" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
{{ $t('labels.uiAcl') }}
</template>
<NcButton
size="small"
class="nc-action-btn cursor-pointer outline-0"
type="text"
data-testid="nc-data-sources-view-ui-acl"
@click="baseAction(sources[0].id, DataSourcesSubTab.UIAcl)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="acl" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
{{ $t('title.audit') }}
</template>
<NcButton
size="small"
class="nc-action-btn cursor-pointer outline-0"
type="text"
data-testid="nc-data-sources-view-audit"
@click="baseAction(sources[0].id, DataSourcesSubTab.Audit)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="book" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
</div>
</div>
<div class="ds-table-col ds-table-crud">
<NcButton
v-if="!sources[0].is_meta && !sources[0].is_local"
size="small"
class="nc-action-btn cursor-pointer outline-0 !w-8 !px-1 !rounded-lg"
type="text"
@click="baseAction(sources[0].id, DataSourcesSubTab.Edit)"
@click.stop="baseAction(sources[0].id, DataSourcesSubTab.Edit)"
>
<GeneralIcon icon="edit" class="text-gray-600" />
</NcButton>
@ -454,9 +407,9 @@ const isEditBaseModalOpen = computed({
</div>
</template>
<template #item="{ element: source, index }">
<div v-if="index !== 0" class="ds-table-row border-gray-200">
<div v-if="index !== 0" class="ds-table-row border-gray-200 cursor-pointer" @click="activeSource = source">
<div class="ds-table-col ds-table-enabled">
<div class="flex items-center gap-1">
<div class="flex items-center gap-1" @click.stop>
<GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" />
<a-tooltip>
<template #title>
@ -473,7 +426,7 @@ const isEditBaseModalOpen = computed({
</div>
</div>
<div class="ds-table-col ds-table-name font-medium w-full">
<div v-if="source.is_meta || source.is_local">-</div>
<div v-if="source.is_meta || source.is_local" class="h-8 w-1">-</div>
<span v-else class="truncate">
{{ source.is_meta || source.is_local ? $t('general.base') : source.alias }}
</span>
@ -485,91 +438,7 @@ const isEditBaseModalOpen = computed({
<span class="text-gray-700 capitalize">{{ source.type }}</span>
</div>
</div>
<div class="ds-table-col ds-table-actions">
<div class="flex items-center gap-2">
<NcTooltip>
<template #title>
{{ $t('title.relations') }}
</template>
<NcButton
size="small"
class="nc-action-btn cursor-pointer outline-0"
type="text"
data-testid="nc-data-sources-view-erd"
@click="baseAction(source.id, DataSourcesSubTab.ERD)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="erd" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
{{ $t('labels.uiAcl') }}
</template>
<NcButton
size="small"
type="text"
class="nc-action-btn cursor-pointer outline-0"
data-testid="nc-data-sources-view-ui-acl"
@click="baseAction(source.id, DataSourcesSubTab.UIAcl)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="acl" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
<NcTooltip v-if="!isEeUI">
<template #title>
{{ $t('title.audit') }}
</template>
<NcButton
size="small"
class="nc-action-btn cursor-pointer outline-0"
type="text"
data-testid="nc-data-sources-view-audit"
@click="baseAction(source.id, DataSourcesSubTab.Audit)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="book" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
{{ $t('tooltip.metaSync') }}
</template>
<NcButton
v-if="!source.is_meta && !source.is_local"
size="small"
type="text"
data-testid="nc-data-sources-view-meta-sync"
class="nc-action-btn cursor-pointer outline-0"
@click="baseAction(source.id, DataSourcesSubTab.Metadata)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="sync" class="group-hover:text-accent" />
</div>
</NcButton>
</NcTooltip>
</div>
</div>
<div class="ds-table-col ds-table-crud justify-end gap-x-1">
<NcTooltip>
<template #title>
{{ $t('general.edit') }}
</template>
<NcButton
v-if="!source.is_meta && !source.is_local"
size="small"
class="nc-action-btn cursor-pointer outline-0 !w-8 !px-1 !rounded-lg"
type="text"
@click="baseAction(source.id, DataSourcesSubTab.Edit)"
>
<GeneralIcon icon="edit" class="text-gray-600" />
</NcButton>
</NcTooltip>
<div class="ds-table-col justify-end gap-x-1 ds-table-actions">
<NcTooltip>
<template #title>
{{ $t('general.remove') }}
@ -579,7 +448,7 @@ const isEditBaseModalOpen = computed({
size="small"
class="nc-action-btn cursor-pointer outline-0 !w-8 !px-1 !rounded-lg"
type="text"
@click="openDeleteBase(source)"
@click.stop="openDeleteBase(source)"
>
<GeneralIcon icon="delete" class="text-red-500" />
</NcButton>
@ -595,35 +464,6 @@ const isEditBaseModalOpen = computed({
:connection-type="clientType"
@source-created="loadBases(true)"
/>
<GeneralModal v-model:visible="isErdModalOpen" size="large">
<div class="h-[80vh]">
<LazyDashboardSettingsErd :source-id="activeBaseId" :show-all-columns="false" />
</div>
</GeneralModal>
<GeneralModal v-model:visible="isMetaDataModal" size="medium">
<div class="p-6">
<LazyDashboardSettingsMetadata :source-id="activeBaseId" @source-synced="loadBases(true)" />
</div>
</GeneralModal>
<GeneralModal v-model:visible="isUIAclModalOpen" class="!w-[60rem]">
<div class="p-6">
<LazyDashboardSettingsUIAcl :source-id="activeBaseId" />
</div>
</GeneralModal>
<GeneralModal v-model:visible="isEditBaseModalOpen" closable :mask-closable="false" size="medium">
<div class="p-6">
<LazyDashboardSettingsDataSourcesEditBase
:source-id="activeBaseId"
@source-updated="loadBases(true)"
@close="isEditBaseModalOpen = false"
/>
</div>
</GeneralModal>
<GeneralModal v-model:visible="isBaseAuditModalOpen" class="!w-[70rem]">
<div class="p-6">
<LazyDashboardSettingsBaseAudit :source-id="activeBaseId" @close="isBaseAuditModalOpen = false" />
</div>
</GeneralModal>
<GeneralDeleteModal
v-model:visible="isDeleteBaseModalOpen"
:entity-name="$t('general.datasource')"
@ -644,9 +484,10 @@ const isEditBaseModalOpen = computed({
</GeneralDeleteModal>
</div>
</div>
</div>
</template>
<style>
<style scoped lang="scss">
.ds-table-head {
@apply flex items-center border-0 text-gray-500;
}
@ -656,7 +497,7 @@ const isEditBaseModalOpen = computed({
}
.ds-table-row {
@apply grid grid-cols-20 border-b border-gray-100 w-full h-full;
@apply grid grid-cols-18 border-b border-gray-100 w-full h-full;
}
.ds-table-col {
@ -676,11 +517,7 @@ const isEditBaseModalOpen = computed({
}
.ds-table-actions {
@apply col-span-5 flex w-full justify-end;
}
.ds-table-crud {
@apply col-span-2;
@apply col-span-5 flex w-full justify-center;
}
.ds-table-col:last-child {
@ -690,4 +527,15 @@ const isEditBaseModalOpen = computed({
.ds-table-handle {
@apply cursor-pointer justify-self-start mr-2 w-[16px];
}
.ds-table-body .ds-table-row:hover {
@apply bg-gray-50/60;
}
:deep(.ant-tabs-content),
:deep(.ant-tabs) {
@apply !h-full;
}
:deep(.ant-tabs-content-holder) {
@apply !min-h-0 !flex-shrink;
}
</style>

93
packages/nc-gui/components/dashboard/settings/Modal.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { FunctionalComponent, SVGAttributes } from 'vue'
import Misc from './Misc.vue'
import DataSources from '~/components/dashboard/settings/DataSources.vue'
interface Props {
modelValue?: boolean
@ -39,6 +40,8 @@ const vDataState = useVModel(props, 'dataSourcesState', emits)
const baseId = toRef(props, 'baseId')
const { isUIAllowed } = useRoles()
provide(ProjectIdInj, baseId)
const { $e } = useNuxtApp()
@ -77,21 +80,6 @@ const tabsInfo: TabGroup = {
// $e('c:settings:team-auth')
// },
// },
// dataSources: {
// // Data Sources
// title: 'Data Sources',
// icon: iconMap.datasource,
// subTabs: {
// dataSources: {
// title: 'Data Sources',
// body: DataSources,
// },
// },
// onClick: () => {
// vDataState.value = ''
// $e('c:settings:data-sources')
// },
// },
// audit: {
// // Audit
// title: t('title.audit'),
@ -123,6 +111,22 @@ const tabsInfo: TabGroup = {
$e('c:settings:base-settings')
},
},
dataSources: {
// Data Sources
title: 'Data Sources',
icon: iconMap.database,
subTabs: {
dataSources: {
title: 'Data Sources',
body: DataSources,
},
},
onClick: () => {
vDataState.value = ''
$e('c:settings:data-sources')
},
},
}
const firstKeyOfObject = (obj: object) => Object.keys(obj)[0]
@ -154,6 +158,7 @@ watch(
:footer="null"
width="max(90vw, 600px)"
:closable="false"
class="!top-50px !bottom-50px"
wrap-class-name="nc-modal-settings"
@cancel="emits('update:modelValue', false)"
>
@ -173,11 +178,16 @@ watch(
</a-button>
</div>
<a-layout class="mt-3 h-[75vh] overflow-y-auto flex">
<a-layout class="mt-3 overflow-y-auto flex">
<!-- Side tabs -->
<a-layout-sider>
<a-menu v-model:selected-keys="selectedTabKeys" class="tabs-menu h-full" :open-keys="[]">
<a-menu-item v-for="(tab, key) of tabsInfo" :key="key" class="active:(!ring-0) hover:(!bg-primary !bg-opacity-25)">
<template v-for="(tab, key) of tabsInfo" :key="key">
<a-menu-item
v-if="key !== 'dataSources' || isUIAllowed('sourceCreate')"
:key="key"
class="active:(!ring-0) hover:(!bg-primary !bg-opacity-25)"
>
<div class="flex items-center space-x-2" @click="tab.onClick">
<component :is="tab.icon" />
@ -186,11 +196,12 @@ watch(
</div>
</div>
</a-menu-item>
</template>
</a-menu>
</a-layout-sider>
<!-- Sub Tabs -->
<a-layout-content class="h-auto px-4 scrollbar-thumb-gray-500">
<a-layout-content class="h-auto h-80vh px-4 scrollbar-thumb-gray-500">
<a-menu
v-if="selectedTabKeys[0] !== 'dataSources'"
v-model:selectedKeys="selectedSubTabKeys"
@ -206,51 +217,19 @@ watch(
{{ tab.title }}
</a-menu-item>
</a-menu>
<div v-else>
<div class="flex items-center">
<a-breadcrumb class="w-full cursor-pointer">
<a-breadcrumb-item v-if="vDataState !== ''" @click="vDataState = ''">
<a class="!no-underline">Data Sources</a>
</a-breadcrumb-item>
<a-breadcrumb-item v-else @click="vDataState = ''">Data Sources</a-breadcrumb-item>
<a-breadcrumb-item v-if="vDataState !== ''">{{ vDataState }}</a-breadcrumb-item>
</a-breadcrumb>
<div v-if="vDataState === ''" class="flex flex-row justify-end items-center w-full gap-1">
<a-button
v-if="!isDataSourceLimitReached"
type="primary"
class="self-start !rounded-md nc-btn-new-datasource"
@click="vDataState = DataSourcesSubTab.New"
>
<div v-if="vDataState === ''" class="flex items-center gap-2 font-light">
<component :is="iconMap.plusCircle" class="group-hover:text-accent" />
New
</div>
</a-button>
<!-- Reload -->
<a-button
v-e="['a:proj-meta:data-sources:reload']"
type="text"
class="self-start !rounded-md nc-btn-metasync-reload"
@click="dataSourcesReload = true"
>
<div class="flex items-center gap-2 text-gray-600 font-light">
<component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': dataSourcesReload }" />
{{ $t('general.reload') }}
</div>
</a-button>
</div>
</div>
<a-divider style="margin: 10px 0" />
</div>
<div class="h-[600px]">
<div
class="overflow-auto"
:class="{
'h-full': selectedSubTabKeys[0] === 'dataSources',
}"
>
<component
:is="selectedSubTab?.body"
v-if="selectedSubTabKeys[0] === 'dataSources'"
v-model:state="vDataState"
v-model:reload="dataSourcesReload"
class="px-2 pb-2"
class="px-2 pb-2 h-full"
:data-testid="`nc-settings-subtab-${selectedSubTab.key}`"
:base-id="baseId"
/>

2
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -131,7 +131,7 @@ const toggleSelectAll = (role: Role) => {
<template>
<div class="flex flex-row w-full items-center justify-center">
<div class="flex flex-col w-[900px]">
<div class="flex flex-col">
<NcTooltip class="mb-4 first-letter:capital font-bold max-w-100 truncate" show-on-truncate-only>
<template #title>{{ base.title }}</template>
<span> UI ACL : {{ base.title }} </span>

53
packages/nc-gui/components/dlg/ProjectAudit.vue

@ -0,0 +1,53 @@
<script lang="ts" setup>
const props = defineProps<{
baseId: string
sourceId: string
modelValue: boolean
}>()
const emit = defineEmits(['update:modelValue'])
const isOpen = useVModel(props, 'modelValue', emit)
const activeSourceId = computed(() => props.sourceId)
const { openedProject: base } = storeToRefs(useBases())
const { baseTables } = storeToRefs(useTablesStore())
const { loadProjectTables } = useTablesStore()
const isLoading = ref(true)
const { getMeta } = useMetas()
const baseId = computed(() => props.baseId || base.value?.id)
onMounted(async () => {
if (baseId.value && baseTables.value.get(baseId.value)) {
return (isLoading.value = false)
}
try {
await loadProjectTables(baseId.value!)
await Promise.all(
baseTables.value.get(baseId.value!)!.map(async (table) => {
await getMeta(table.id!, false, false, baseId.value!)
}),
)
} catch (e) {
message.error('Failed to load tables/bases. Please try again later.')
console.error(e)
} finally {
isLoading.value = false
}
})
</script>
<template>
<GeneralModal v-model:visible="isOpen" size="large" class="!w-[70rem]">
<div class="p-6">
<DashboardSettingsBaseAudit v-if="!isLoading" :source-id="activeSourceId" :base-id="baseId" :show-all-columns="false" />
</div>
</GeneralModal>
</template>

6
packages/nc-gui/components/project/AllTables.vue

@ -105,7 +105,7 @@ const onCreateBaseClick = () => {
<GeneralIcon icon="download" />
<div class="label">{{ $t('activity.import') }} {{ $t('general.data') }}</div>
</div>
<component :is="isDataSourceLimitReached ? NcTooltip : 'div'" v-if="isUIAllowed('sourceCreate')">
<!-- <component :is="isDataSourceLimitReached ? NcTooltip : 'div'" v-if="isUIAllowed('sourceCreate')">
<template #title>
<div>
{{ $t('tooltip.reachedSourceLimit') }}
@ -124,7 +124,7 @@ const onCreateBaseClick = () => {
<GeneralIcon icon="dataSource" />
<div class="label">{{ $t('labels.connectDataSource') }}</div>
</div>
</component>
</component>-->
</div>
<div
v-if="base?.isLoading"
@ -191,7 +191,7 @@ const onCreateBaseClick = () => {
</div>
<ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" />
<LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" />
<!-- <LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" />-->
</div>
</template>

4
packages/nc-gui/components/project/View.vue

@ -162,7 +162,7 @@ watch(
</template>
<ProjectAccessSettings :base-id="currentBase?.id" />
</a-tab-pane>
<a-tab-pane v-if="isUIAllowed('sourceCreate')" key="data-source">
<!-- <a-tab-pane v-if="isUIAllowed('sourceCreate')" key="data-source">
<template #tab>
<div class="tab-title" data-testid="proj-view-tab__data-sources">
<GeneralIcon icon="database" />
@ -180,7 +180,7 @@ watch(
</div>
</template>
<DashboardSettingsDataSources v-model:state="baseSettingsState" />
</a-tab-pane>
</a-tab-pane>-->
</a-tabs>
</div>
</div>

2
packages/nc-gui/lang/en.json

@ -448,6 +448,8 @@
"noResultsMatchedYourSearch": "Your search did not yield any matching results"
},
"labels": {
"connectionDetails": "Connection Details",
"metaSync": "Meta Sync",
"today": "Today",
"workspace": "Workspace",
"txt": "TXT Record value",

8
packages/noco-docs/docs/100.data-sources/010.data-source-overview.md

@ -21,10 +21,12 @@ Currently only one external data source can be added per project.
## Accessing `Data Sources`
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Click on `Data Sources` tab
1. Access Base context menu by clicking on the `...` in the left sidebar against the base name
2. Click on `Settings` tab
3. In the popup modal, click on `Data Sources` tab
![data source](/img/v2/data-source/data-source.png)
![data source](/img/v2/data-source/data-source-1.png)
![data source](/img/v2/data-source/data-source-2.png)
Learn more about working with Data sources in the following sections:

29
packages/noco-docs/docs/100.data-sources/020.connect-to-data-source.md

@ -7,10 +7,11 @@ keywords: ['NocoDB data source', 'connect data source', 'external data source',
To connect to an external data source, follow the steps below:
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Select `Data Sources` tab
3. Click on `+ New Data Source` button
4. On the pop-up modal, provide the following details:
1. Access the Base context menu by clicking on the `...` in the left sidebar against the base name
2. Click on `Settings` tab
3. In the popup modal, click on `Data Sources` tab
4. Click on `+ New Data Source` button
5. On the input modal, provide the following details:
| Field Name | Description |
|---------------|--------------------------------------------------------------------------------------|
@ -23,7 +24,7 @@ To connect to an external data source, follow the steps below:
| Database | Name of the database to connect to |
| Schema name | Name of the schema to connect to |
4a. Optionally, if the connection required is TLS/MTLS for MITM protection, follow these additional steps below:
5a. Optionally, if the connection required is TLS/MTLS for MITM protection, follow these additional steps below:
- Click on `SSL & Advanced Parameters`
- Select `SSL Mode` and upload the client certificate, client key, and Root CA files by clicking on the file.
@ -33,16 +34,18 @@ To connect to an external data source, follow the steps below:
Example: In PostgreSQL when SSL mode set to "Required-Identity," if the server certificate's common name (cname) differs from the actual DNS/IP used for connection, the connection will fail.\
To resolve, add "servername" property with same cname value under the SSL section. Additional details are available at [knex configuration options](https://knexjs.org/guide/#configuration-options).
5. Click on `Test Database Connection` button to verify the connection
6. Wait for the connection to be verified.
- After connection is successful, `Submit` button will be enabled.
- Click on `Submit` button to save the data source.
6. Click on the `Test Database Connection` button to verify the connection
7. Wait for the connection to be verified.
- After test is successful, `Counnect to Data Source` button will be enabled.
- Click on `Connect to Data Source` button to save the data source.
![data source-1](/img/v2/data-source/data-source-connect-1.png)
![data source-2](/img/v2/data-source/data-source-connect-2.png)
![data source-3](/img/v2/data-source/data-source-connect-3.png)
![data source-1](/img/v2/data-source/ds-connect-1.png)
![data source-4](/img/v2/data-source/data-source-connect-4a.png)
![data source-2](/img/v2/data-source/ds-connect-2.png)
![data source-3](/img/v2/data-source/ds-connect-3.png)
![data source-4](/img/v2/data-source/ds-connect-4.png)

18
packages/noco-docs/docs/100.data-sources/030.sync-with-data-source.md

@ -5,17 +5,15 @@ tags: ['Data Sources', 'Sync', 'External', 'PG', 'MySQL']
keywords: ['NocoDB data source', 'connect data source', 'external data source', 'PG data source', 'MySQL data source']
---
Access `Data Sources` tab in the `Base Settings` to sync changes done in the external data source with NocoDB.
1. Select the data source that you wish to sync metadata for
2. Click on the `Meta Sync` button listed under `Actions` column for the data source that you wish to sync metadata for
3. Click on the `Reload` button to refresh Sync state (Optional)
4. Any changes to the metadata identified will be listed in the `Sync State` column
5. Click on `Sync Now` button to sync the metadata changes
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Select `Data Sources` tab
3. Click on `Sync Metadata` button listed under `Actions` column for the data source that you wish to sync metadata for
4. Click on `Reload` button to refresh Sync state (Optional)
5. Any changes to the metadata identified will be listed in the `Sync State` column
6. Click on `Sync Now` button to sync the metadata changes
![sync metadata](/img/v2/data-source/data-source-2.png)
![sync metadata](/img/v2/data-source/data-source-meta-sync.png)
![sync metadata](/img/v2/data-source/data-source-meta-sync-1.png)
![sync metadata](/img/v2/data-source/data-source-meta-sync-2.png)
After the sync is complete, you can see the updated state in the `Sync State` column.
Sync modal also marks `Tables metadata is in Sync` in the header.

69
packages/noco-docs/docs/100.data-sources/040.actions-on-data-sources.md

@ -6,24 +6,22 @@ keywords: ['NocoDB data source', 'UI ACL', 'Audit logs', 'Relations', 'Edit', 'U
---
## Edit external database configuration parameters
- Access `Data Sources` tab in the `Base Settings`
- Click on `Connection Details` tab
- Re-configure database credentials as required
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Click on `Data Sources` tab
3. Click on `Edit` icon listed under `Actions` column for the data source that you wish to access ERD (Relations view) for
Go to `Data Sources`, click ``Edit`` icon, you can re-configure database credentials.
Please make sure database configuration parameters are valid. Any incorrect parameters could lead to schema loss!
![relations](/img/v2/data-source/data-source-edit.png)
:::info
Please make sure database configuration parameters are valid. Any incorrect parameters could lead to schema loss!
:::
![edit db config](/img/v2/data-source/edit-base.png)
![edit-data-source](/img/v2/data-source/data-source-edit.png)
## Remove data source
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Click on `Data Sources` tab
3. Click on `Delete` icon listed under `Actions` column for the data source that you wish to Unlink
- Access `Data Sources` tab in the `Base Settings`
- Click on `Delete` icon listed under `Actions` column for the data source that you wish to remove
![datasource unlink](/img/v2/data-source/data-source-unlink.png)
![datasource unlink](/img/v2/data-source/data-source-remove.png)
:::note
Unlinking a data source will not delete the external data source. It will only remove the data source from the current project.
@ -32,52 +30,45 @@ Unlinking a data source will not delete the external data source. It will only r
## Data source visibility
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Click on `Data Sources` tab
3. Toggle radio button listed under `Visibility` column for the data source that you wish to hide/un-hide
- Access `Data Sources` tab in the `Base Settings`
- Toggle radio button listed under `Visibility` column for the data source that you wish to hide/un-hide
![datasource visibility](/img/v2/data-source/data-source-visibility.png)
![datasource visibility](/img/v2/data-source/data-source-hide.png)
## UI Access Control
:::info
UI Access Control is available only in Open Source version of NocoDB.
UI Access Control is available only in Open-Source version of NocoDB.
:::
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Click on `Data Sources` tab
3. Click on `UI ACL` button listed under `Actions` column for the data source that you wish to manage UI access control for
4. On the UI ACL modal, you can see the list of tables available in the data source as rows & roles available as columns. Toggle checkboxes to enable/disable access to tables for specific roles.
5. Click on `Save` button to save the changes
Access `Data Sources` tab in the `Base Settings` to manage UI access control for the data source.
1. Click on `UI ACL` button listed under `Actions` column for the data source that you wish to manage UI access control for
2. On the UI ACL modal, you can see the list of tables available in the data source as rows & roles available as columns. Toggle checkboxes to enable/disable access to tables for specific roles.
3. Click on `Save` button to save the changes
![ui acl](/img/v2/data-source/data-source-3.png)
![ui acl](/img/v2/data-source/ui-acl.png)
![ui acl](/img/v2/data-source/data-source-uiacl.png)
## Audit logs
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Click on `Data Sources` tab
3. Click on `Audit` button listed under `Actions` column for the data source that you wish to access Audit logs for
![audit](/img/v2/data-source/audit.png)
Access `Data Sources` tab in the `Base Settings` to access Audit logs for the data source.
- Click on `Default` datasource & then
- Access `Audit` tab to view the audit logs.
![audit logs](/img/v2/data-source/audit-logs.png)
![audit](/img/v2/data-source/data-source-audit.png)
:::info
Audit logs are not available for external data source connections.
:::
## Relations
Access `Data Sources` tab in the `Base Settings` to access Relations view for the data source.
- Select the data source that you wish to access ERD (Relations view) for
- Click on `ERD` tab
1. Access Base context menu by clicking on the `Base` name in the left sidebar
2. Click on `Data Sources` tab
3. Click on `Relations` button listed under `Actions` column for the data source that you wish to access ERD (Relations view) for
![relations](/img/v2/data-source/data-source-4.png)
![relations](https://github.com/nocodb/nocodb/assets/86527202/c3775d27-f75d-4263-8903-dd66427de4b4)
![relations](/img/v2/data-source/data-source-erd.png)
### Junction table names within Relations

BIN
packages/noco-docs/static/img/v2/data-source/data-source-1.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 128 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-audit.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-edit.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 278 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-erd.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-hide.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-meta-sync-1.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-meta-sync-2.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-remove.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
packages/noco-docs/static/img/v2/data-source/data-source-uiacl.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

BIN
packages/noco-docs/static/img/v2/data-source/ds-connect-1.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
packages/noco-docs/static/img/v2/data-source/ds-connect-2.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
packages/noco-docs/static/img/v2/data-source/ds-connect-3.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

BIN
packages/noco-docs/static/img/v2/data-source/ds-connect-4.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

4
tests/playwright/pages/Dashboard/ProjectView/Metadata.ts

@ -25,7 +25,9 @@ export class MetaDataPage extends BasePage {
await this.get().click();
await this.rootPage.keyboard.press('Escape');
await this.rootPage.keyboard.press('Escape');
await this.get().waitFor({ state: 'detached' });
await this.rootPage.waitForSelector('div.ant-modal-content', {
state: 'hidden',
});
}
async sync() {

3
tests/playwright/pages/Dashboard/ProjectView/index.ts

@ -54,12 +54,9 @@ export class ProjectViewPage extends BasePage {
expect(await this.tab_allTables.isVisible()).toBeTruthy();
if (role.toLowerCase() === 'creator' || role.toLowerCase() === 'owner') {
await this.tab_dataSources.waitFor({ state: 'visible' });
await this.tab_accessSettings.waitFor({ state: 'visible' });
expect(await this.tab_dataSources.isVisible()).toBeTruthy();
expect(await this.tab_accessSettings.isVisible()).toBeTruthy();
} else {
expect(await this.tab_dataSources.isVisible()).toBeFalsy();
expect(await this.tab_accessSettings.isVisible()).toBeFalsy();
}

30
tests/playwright/pages/Dashboard/Settings/DataSources.ts

@ -20,21 +20,35 @@ export class DataSourcesPage extends BasePage {
}
get() {
return this.settings.get().locator(`[data-testid="nc-settings-subtab-Data Sources"]`);
return this.settings.get().locator('[data-testid="nc-settings-datasources"]');
}
async openErd({ dataSourceName }: { dataSourceName: string }) {
await this.get().locator('.ds-table-row', { hasText: dataSourceName }).locator('button:has-text("ERD")').click();
async openErd({ rowIndex }: { rowIndex: number }) {
const row = this.get()
.locator('.ds-table-row')
.nth(rowIndex + 1);
await row.click();
await this.get().getByTestId('nc-erd-tab').click();
}
async openAudit({ rowIndex }: { rowIndex: number }) {
const row = this.get()
.locator('.ds-table-row')
.nth(rowIndex + 1);
await row.click();
await this.get().getByTestId('nc-audit-tab').click();
}
async openAcl({ dataSourceName = defaultBaseName }: { dataSourceName?: string } = {}) {
await this.get().locator('.ds-table-row', { hasText: dataSourceName }).locator('button:has-text("UI ACL")').click();
}
async openMetaSync({ dataSourceName = defaultBaseName }: { dataSourceName?: string } = {}) {
await this.get()
.locator('.ds-table-row', { hasText: dataSourceName })
.locator('button:has-text("Sync Metadata")')
.click();
async openMetaSync({ rowIndex }: { rowIndex: number }) {
// 0th offset for header
const row = this.get()
.locator('.ds-table-row')
.nth(rowIndex + 1);
await row.click();
await this.get().getByTestId('nc-meta-sync-tab').click();
}
}

4
tests/playwright/pages/Dashboard/TreeView.ts

@ -182,7 +182,7 @@ export class TreeViewPage extends BasePage {
await this.waitForTableOptions({ title });
await this.get().locator(`.nc-base-tree-tbl-${tableTitle}`).locator('.nc-tbl-context-menu').click();
await this.rootPage.locator('.ant-dropdown').locator('.nc-menu-item:has-text("Delete")').click();
await this.rootPage.locator('.ant-dropdown').locator('.nc-menu-item.nc-table-delete:has-text("Delete")').click();
await this.waitForResponse({
uiAction: async () => {
@ -205,7 +205,7 @@ export class TreeViewPage extends BasePage {
await this.waitForTableOptions({ title });
await this.get().locator(`.nc-base-tree-tbl-${tableTitle}`).locator('.nc-tbl-context-menu').click();
await this.rootPage.locator('.ant-dropdown').locator('.nc-menu-item:has-text("Rename")').click();
await this.rootPage.locator('.ant-dropdown').locator('.nc-table-rename.nc-menu-item:has-text("Rename")').click();
await this.dashboard.get().locator('[placeholder="Enter table name"]').fill(newTitle);
await this.dashboard.get().locator('button:has-text("Rename Table")').click();

6
tests/playwright/tests/db/features/erd.spec.ts

@ -45,8 +45,10 @@ test.describe('Erd', () => {
};
const openProjectErd = async () => {
await dashboard.baseView.tab_dataSources.click();
await dashboard.baseView.dataSources.openERD({ rowIndex: 0 });
await dashboard.treeView.baseSettings({ title: context.base.title });
await dashboard.settings.selectTab({ tab: 'dataSources' });
await dashboard.settings.dataSources.openErd({ rowIndex: 0 });
// await dashboard.baseView.dataSources.openERD({ rowIndex: 0 });
};
const openErdOfATable = async (tableName: string) => {

13
tests/playwright/tests/db/features/metaSync.spec.ts

@ -36,8 +36,12 @@ test.describe('Meta sync', () => {
test('Meta sync', async () => {
test.setTimeout(process.env.CI ? 100000 : 70000);
await dashboard.baseView.tab_dataSources.click();
await dashboard.baseView.dataSources.openMetaSync({ rowIndex: 0 });
// await dashboard.baseView.tab_dataSources.click();
// await dashboard.baseView.dataSources.openMetaSync({ rowIndex: 0 });
await dashboard.treeView.baseSettings({ title: context.base.title });
await dashboard.settings.selectTab({ tab: 'dataSources' });
await dashboard.settings.dataSources.openMetaSync({ rowIndex: 0 });
await dbExec(`CREATE TABLE table1 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`);
await dbExec(`CREATE TABLE table2 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`);
@ -247,8 +251,9 @@ test.describe('Meta sync', () => {
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (1,1,1,1,1), (2,2,2,2,2), (3,3,3,3,3), (4,4,4,4,4), (5,5,5,5,5), (6,6,6,6,6), (7,7,7,7,7), (8,8,8,8,8), (9,9,9,9,9);`
);
await dashboard.baseView.tab_dataSources.click();
await dashboard.baseView.dataSources.openMetaSync({ rowIndex: 0 });
await dashboard.treeView.baseSettings({ title: context.base.title });
await dashboard.settings.selectTab({ tab: 'dataSources' });
await dashboard.settings.dataSources.openMetaSync({ rowIndex: 0 });
await metaData.clickReload();
await metaData.sync();

6
tests/playwright/tests/db/general/tableOperations.spec.ts

@ -32,8 +32,10 @@ test.describe('Table Operations', () => {
// Audit logs in clickhouse; locally wont be accessible
await dashboard.treeView.openProject({ title: context.base.title, context });
await dashboard.baseView.tab_dataSources.click();
await dashboard.baseView.dataSources.openAudit({ rowIndex: 0 });
await dashboard.treeView.baseSettings({ title: context.base.title });
await dashboard.settings.selectTab({ tab: 'dataSources' });
await dashboard.settings.dataSources.openAudit({ rowIndex: 0 });
await audit.verifyRow({
index: 0,

Loading…
Cancel
Save