mirror of
https://github.com/nocodb/nocodb.git
synced 2026-04-30 05:36:56 +00:00
feat: team & settings modal data sources tab revised
Signed-off-by: mertmit <mertmit99@gmail.com>
This commit is contained in:
@@ -70,96 +70,97 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="showPluginInstallModal"
|
||||
:class="{ active: showPluginInstallModal }"
|
||||
:closable="false"
|
||||
centered
|
||||
min-height="300"
|
||||
:footer="null"
|
||||
wrap-class-name="nc-modal-plugin-install"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<LazyDashboardSettingsAppInstall
|
||||
v-if="pluginApp && showPluginInstallModal"
|
||||
:id="pluginApp.id"
|
||||
@close="showPluginInstallModal = false"
|
||||
@saved="saved()"
|
||||
/>
|
||||
</a-modal>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="showPluginUninstallModal"
|
||||
:class="{ active: showPluginUninstallModal }"
|
||||
:closable="false"
|
||||
width="24rem"
|
||||
centered
|
||||
:footer="null"
|
||||
wrap-class-name="nc-modal-plugin-uninstall"
|
||||
>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex flex-row justify-center mt-2 text-center w-full text-base">
|
||||
{{ `Click on confirm to reset ${pluginApp && pluginApp.title}` }}
|
||||
</div>
|
||||
<div class="flex mt-6 justify-center space-x-2">
|
||||
<a-button @click="showPluginUninstallModal = false"> {{ $t('general.cancel') }} </a-button>
|
||||
<a-button type="primary" danger @click="resetPlugin"> {{ $t('general.confirm') }} </a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-2 gap-y-4 mt-4">
|
||||
<a-card
|
||||
v-for="(app, i) in apps"
|
||||
:key="i"
|
||||
:class="`relative flex overflow-x-hidden app-item-card !shadow-sm rounded-md w-full nc-app-store-card-${app.title}`"
|
||||
:body-style="{ width: '100%' }"
|
||||
<div>
|
||||
<a-modal
|
||||
v-model:visible="showPluginInstallModal"
|
||||
:class="{ active: showPluginInstallModal }"
|
||||
:closable="false"
|
||||
centered
|
||||
min-height="300"
|
||||
:footer="null"
|
||||
wrap-class-name="nc-modal-plugin-install"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<div class="install-btn flex flex-row justify-end space-x-1">
|
||||
<a-button v-if="app.parsedInput" size="small" type="primary" @click="showInstallPluginModal(app)">
|
||||
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit">
|
||||
<IcRoundEdit class="pr-0.5" :height="12" />
|
||||
Edit
|
||||
</div>
|
||||
</a-button>
|
||||
<LazyDashboardSettingsAppInstall
|
||||
v-if="pluginApp && showPluginInstallModal"
|
||||
:id="pluginApp.id"
|
||||
@close="showPluginInstallModal = false"
|
||||
@saved="saved()"
|
||||
/>
|
||||
</a-modal>
|
||||
|
||||
<a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)">
|
||||
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset">
|
||||
<MdiCloseCircleOutline />
|
||||
<div class="flex ml-0.5">Reset</div>
|
||||
</div>
|
||||
</a-button>
|
||||
|
||||
<a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)">
|
||||
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install">
|
||||
<MdiPlus />
|
||||
Install
|
||||
</div>
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row space-x-2 items-center justify-start w-full">
|
||||
<div class="flex w-20 pl-3">
|
||||
<img
|
||||
v-if="app.title !== 'SMTP'"
|
||||
class="avatar"
|
||||
alt="logo"
|
||||
:style="{
|
||||
backgroundColor: app.title === 'SES' ? '#242f3e' : '',
|
||||
}"
|
||||
:src="app.logo"
|
||||
/>
|
||||
|
||||
<div v-else />
|
||||
<a-modal
|
||||
v-model:visible="showPluginUninstallModal"
|
||||
:closable="false"
|
||||
width="24rem"
|
||||
centered
|
||||
:footer="null"
|
||||
wrap-class-name="nc-modal-plugin-uninstall"
|
||||
>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex flex-row justify-center mt-2 text-center w-full text-base">
|
||||
{{ `Click on confirm to reset ${pluginApp && pluginApp.title}` }}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-1 w-3/5 pl-3">
|
||||
<a-typography-title :level="5">{{ app.title }}</a-typography-title>
|
||||
|
||||
{{ app.description }}
|
||||
<div class="flex mt-6 justify-center space-x-2">
|
||||
<a-button @click="showPluginUninstallModal = false"> {{ $t('general.cancel') }} </a-button>
|
||||
<a-button type="primary" danger @click="resetPlugin"> {{ $t('general.confirm') }} </a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-modal>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-2 gap-y-4 mt-4">
|
||||
<a-card
|
||||
v-for="(app, i) in apps"
|
||||
:key="i"
|
||||
:class="`relative flex overflow-x-hidden app-item-card !shadow-sm rounded-md w-full nc-app-store-card-${app.title}`"
|
||||
:body-style="{ width: '100%' }"
|
||||
>
|
||||
<div class="install-btn flex flex-row justify-end space-x-1">
|
||||
<a-button v-if="app.parsedInput" size="small" type="primary" @click="showInstallPluginModal(app)">
|
||||
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit">
|
||||
<IcRoundEdit class="pr-0.5" :height="12" />
|
||||
Edit
|
||||
</div>
|
||||
</a-button>
|
||||
|
||||
<a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)">
|
||||
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset">
|
||||
<MdiCloseCircleOutline />
|
||||
<div class="flex ml-0.5">Reset</div>
|
||||
</div>
|
||||
</a-button>
|
||||
|
||||
<a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)">
|
||||
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install">
|
||||
<MdiPlus />
|
||||
Install
|
||||
</div>
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row space-x-2 items-center justify-start w-full">
|
||||
<div class="flex w-20 pl-3">
|
||||
<img
|
||||
v-if="app.title !== 'SMTP'"
|
||||
class="avatar"
|
||||
alt="logo"
|
||||
:style="{
|
||||
backgroundColor: app.title === 'SES' ? '#242f3e' : '',
|
||||
}"
|
||||
:src="app.logo"
|
||||
/>
|
||||
|
||||
<div v-else />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-1 w-3/5 pl-3">
|
||||
<a-typography-title :level="5">{{ app.title }}</a-typography-title>
|
||||
|
||||
{{ app.description }}
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,69 +2,90 @@
|
||||
import { Empty } from 'ant-design-vue'
|
||||
import type { BaseType } from 'nocodb-sdk'
|
||||
import CreateBase from './data-sources/CreateBase.vue'
|
||||
import Metadata from './Metadata.vue'
|
||||
import UIAcl from './UIAcl.vue'
|
||||
import Erd from './Erd.vue'
|
||||
import { DataSourcesSubTab } from '~/lib'
|
||||
import { useNuxtApp, useProject } from '#imports'
|
||||
|
||||
interface Props {
|
||||
state: string
|
||||
reload: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emits = defineEmits(['update:state', 'update:reload'])
|
||||
|
||||
const vModel = useVModel(props, 'state', emits)
|
||||
const vReload = useVModel(props, 'reload', emits)
|
||||
|
||||
const { $api } = useNuxtApp()
|
||||
const { project } = useProject()
|
||||
|
||||
let isLoading = $ref(false)
|
||||
let sources = $ref<BaseType[]>([])
|
||||
const newSourceTab = $ref(false)
|
||||
let activeBaseId = $ref('')
|
||||
let metadiffbases = $ref<string[]>([])
|
||||
|
||||
async function loadBases() {
|
||||
try {
|
||||
if (!project.value?.id) return
|
||||
|
||||
isLoading = true
|
||||
vReload.value = true
|
||||
const baseList = await $api.base.list(project.value?.id)
|
||||
if (baseList.bases.list && baseList.bases.list.length) {
|
||||
sources = baseList.bases.list
|
||||
}
|
||||
loadMetaDiff()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
isLoading = false
|
||||
vReload.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMetaDiff() {
|
||||
try {
|
||||
if (!project.value?.id) return
|
||||
|
||||
metadiffbases = []
|
||||
|
||||
const metadiff = await $api.project.metaDiffGet(project.value?.id)
|
||||
for (const model of metadiff) {
|
||||
if (model.detectedChanges?.length > 0) {
|
||||
metadiffbases.push(model.base_id)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const baseAction = (baseId: string, action: string) => {
|
||||
activeBaseId = baseId
|
||||
vModel.value = action
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (sources.length === 0) {
|
||||
await loadBases()
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.reload,
|
||||
async (reload) => {
|
||||
if (reload) {
|
||||
await loadBases()
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex flex-row justify-end items-center w-full mb-4">
|
||||
<a-button class="self-start nc-btn-new-datasource" @click="newSourceTab = !newSourceTab">
|
||||
<div v-if="newSourceTab" class="flex items-center gap-2 text-gray-600 font-light">
|
||||
<MdiClose class="text-lg group-hover:text-accent" />
|
||||
Cancel
|
||||
</div>
|
||||
<div v-else class="flex items-center gap-2 text-gray-600 font-light">
|
||||
<MdiDatabaseOutline class="text-lg group-hover:text-accent" />
|
||||
New
|
||||
</div>
|
||||
</a-button>
|
||||
<!-- Reload -->
|
||||
<a-button
|
||||
v-if="!newSourceTab"
|
||||
v-e="['a:proj-meta:meta-data:reload']"
|
||||
class="self-start nc-btn-metasync-reload"
|
||||
@click="loadBases"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-gray-600 font-light">
|
||||
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
|
||||
{{ $t('general.reload') }}
|
||||
</div>
|
||||
</a-button>
|
||||
</div>
|
||||
<div v-if="newSourceTab" class="max-h-600px overflow-y-auto">
|
||||
<CreateBase />
|
||||
</div>
|
||||
<div v-else class="max-h-600px overflow-y-auto">
|
||||
<div v-if="props.state === ''" class="max-h-600px overflow-y-auto">
|
||||
<a-table
|
||||
class="w-full"
|
||||
size="small"
|
||||
@@ -75,27 +96,68 @@ onMounted(async () => {
|
||||
"
|
||||
:data-source="sources ?? []"
|
||||
:pagination="false"
|
||||
:loading="isLoading"
|
||||
:loading="vReload"
|
||||
bordered
|
||||
>
|
||||
<template #emptyText> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" /> </template>
|
||||
<a-table-column key="type" title="Type" data-index="type" :width="180">
|
||||
<template #default="{ text }">{{ text }}</template>
|
||||
</a-table-column>
|
||||
<a-table-column key="alias" title="Name" data-index="alias">
|
||||
<template #default="{ text, record }">{{ record.is_meta ? 'BASE' : text }}</template>
|
||||
<template #default="{ text, record }">
|
||||
{{ record.is_meta ? 'BASE' : text }} <span class="text-gray-400 text-xs">({{ record.type }})</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column key="action" :title="$t('labels.actions')" :width="180">
|
||||
<template #default="{ record }">
|
||||
<div class="flex items-center gap-2">
|
||||
<MdiEditOutline v-e="['c:base:edit:rename']" class="nc-action-btn" />
|
||||
|
||||
<MdiDeleteOutline class="nc-action-btn" />
|
||||
<a-tooltip>
|
||||
<template #title>Sync Metadata {{ metadiffbases.includes(record.id) ? '(Out of sync)' : '' }}</template>
|
||||
<MdiDatabaseSync
|
||||
class="nc-action-btn cursor-pointer outline-0"
|
||||
:class="metadiffbases.includes(record.id) ? 'text-primary' : ''"
|
||||
@click="baseAction(record.id, DataSourcesSubTab.Metadata)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>UI ACL</template>
|
||||
<MdiDatabaseLockOutline
|
||||
class="nc-action-btn cursor-pointer outline-0"
|
||||
@click="baseAction(record.id, DataSourcesSubTab.UIAcl)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>ERD</template>
|
||||
<MdiGraphOutline
|
||||
class="nc-action-btn cursor-pointer outline-0"
|
||||
@click="baseAction(record.id, DataSourcesSubTab.ERD)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>Edit</template>
|
||||
<MdiEditOutline
|
||||
class="nc-action-btn cursor-pointer outline-0"
|
||||
@click="baseAction(record.id, DataSourcesSubTab.Edit)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>Delete</template>
|
||||
<MdiDeleteOutline class="nc-action-btn cursor-pointer outline-0" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
</div>
|
||||
<div v-else-if="props.state === DataSourcesSubTab.New" class="max-h-600px overflow-y-auto">
|
||||
<CreateBase />
|
||||
</div>
|
||||
<div v-else-if="props.state === DataSourcesSubTab.Metadata" class="max-h-600px overflow-y-auto">
|
||||
<Metadata :base-id="activeBaseId" />
|
||||
</div>
|
||||
<div v-else-if="props.state === DataSourcesSubTab.UIAcl" class="max-h-600px overflow-y-auto">
|
||||
<UIAcl :base-id="activeBaseId" />
|
||||
</div>
|
||||
<div v-else-if="props.state === DataSourcesSubTab.ERD" class="max-h-600px overflow-y-auto">
|
||||
<Erd :base-id="activeBaseId" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
baseId: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full !p-0 h-70vh">
|
||||
<ErdView />
|
||||
<ErdView :base-id="props.baseId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { Empty, extractSdkResponseErrorMsg, h, message, useI18n, useNuxtApp, useProject } from '#imports'
|
||||
|
||||
interface Props {
|
||||
baseId: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { $api } = useNuxtApp()
|
||||
|
||||
const { project, loadTables } = useProject()
|
||||
@@ -19,7 +25,7 @@ async function loadMetaDiff() {
|
||||
|
||||
isLoading = true
|
||||
isDifferent = false
|
||||
metadiff = await $api.project.metaDiffGet(project.value?.id)
|
||||
metadiff = await $api.base.metaDiffGet(project.value?.id, props.baseId)
|
||||
for (const model of metadiff) {
|
||||
if (model.detectedChanges?.length > 0) {
|
||||
model.syncState = model.detectedChanges.map((el: any) => el?.msg).join(', ')
|
||||
@@ -38,7 +44,7 @@ async function syncMetaDiff() {
|
||||
if (!project.value?.id || !isDifferent) return
|
||||
|
||||
isLoading = true
|
||||
await $api.project.metaDiffSync(project.value.id)
|
||||
await $api.base.metaDiffSync(project.value.id, props.baseId)
|
||||
// Table metadata recreated successfully
|
||||
message.info(t('msg.info.metaDataRecreated'))
|
||||
await loadTables()
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
import AppStore from './AppStore.vue'
|
||||
import DataSources from './DataSources.vue'
|
||||
import { resolveComponent, useI18n, useNuxtApp, useUIPermission, useVModel, watch } from '#imports'
|
||||
import Misc from './Misc.vue'
|
||||
import { useNuxtApp } from '#app'
|
||||
import { useI18n, useUIPermission, useVModel, watch } from '#imports'
|
||||
import StoreFrontOutline from '~icons/mdi/storefront-outline'
|
||||
import TeamFillIcon from '~icons/ri/team-fill'
|
||||
import MultipleTableIcon from '~icons/mdi/table-multiple'
|
||||
import NotebookOutline from '~icons/mdi/notebook-outline'
|
||||
import FolderCog from '~icons/mdi/folder-cog'
|
||||
import { DataSourcesSubTab } from '~~/lib'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
openKey?: string
|
||||
dataSourcesState?: string
|
||||
}
|
||||
|
||||
interface SubTabGroup {
|
||||
@@ -41,6 +47,9 @@ const { t } = useI18n()
|
||||
|
||||
const { $e } = useNuxtApp()
|
||||
|
||||
const dataSourcesState = ref(props.dataSourcesState)
|
||||
const dataSourcesReload = ref(false)
|
||||
|
||||
const tabsInfo: TabGroup = {
|
||||
teamAndAuth: {
|
||||
title: t('title.teamAndAuth'),
|
||||
@@ -69,60 +78,33 @@ const tabsInfo: TabGroup = {
|
||||
$e('c:settings:team-auth')
|
||||
},
|
||||
},
|
||||
...(isUIAllowed('appStore')
|
||||
? {
|
||||
appStore: {
|
||||
// App Store
|
||||
title: t('title.appStore'),
|
||||
icon: StoreFrontOutline,
|
||||
subTabs: {
|
||||
new: {
|
||||
title: 'Apps',
|
||||
body: resolveComponent('DashboardSettingsAppStore'),
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
$e('c:settings:appstore')
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
projMetaData: {
|
||||
// Project Metadata
|
||||
title: t('title.projMeta'),
|
||||
appStore: {
|
||||
// App Store
|
||||
title: t('title.appStore'),
|
||||
icon: StoreFrontOutline,
|
||||
subTabs: {
|
||||
new: {
|
||||
title: 'Apps',
|
||||
body: AppStore,
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
$e('c:settings:appstore')
|
||||
},
|
||||
},
|
||||
dataSources: {
|
||||
// Data Sources
|
||||
title: 'Data Sources',
|
||||
icon: MultipleTableIcon,
|
||||
subTabs: {
|
||||
dataSources: {
|
||||
title: 'Data Sources',
|
||||
body: DataSources,
|
||||
},
|
||||
metaData: {
|
||||
// Metadata
|
||||
title: t('title.metadata'),
|
||||
body: resolveComponent('DashboardSettingsMetadata'),
|
||||
},
|
||||
acl: {
|
||||
// UI Access Control
|
||||
title: t('title.uiACL'),
|
||||
body: resolveComponent('DashboardSettingsUIAcl'),
|
||||
onClick: () => {
|
||||
$e('c:table:ui-acl')
|
||||
},
|
||||
},
|
||||
erd: {
|
||||
title: t('title.erdView'),
|
||||
body: resolveComponent('DashboardSettingsErd'),
|
||||
onClick: () => {
|
||||
$e('c:settings:erd')
|
||||
},
|
||||
},
|
||||
misc: {
|
||||
title: t('general.misc'),
|
||||
body: resolveComponent('DashboardSettingsMisc'),
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
$e('c:settings:proj-metadata')
|
||||
dataSourcesState.value = ''
|
||||
$e('c:settings:data-sources')
|
||||
},
|
||||
},
|
||||
audit: {
|
||||
@@ -140,6 +122,21 @@ const tabsInfo: TabGroup = {
|
||||
$e('c:settings:audit')
|
||||
},
|
||||
},
|
||||
projectSettings: {
|
||||
// Project Settings
|
||||
title: 'Project Settings',
|
||||
icon: FolderCog,
|
||||
subTabs: {
|
||||
misc: {
|
||||
// Misc
|
||||
title: 'Misc',
|
||||
body: Misc,
|
||||
},
|
||||
},
|
||||
onClick: () => {
|
||||
$e('c:settings:project-settings')
|
||||
},
|
||||
},
|
||||
}
|
||||
const firstKeyOfObject = (obj: object) => Object.keys(obj)[0]
|
||||
|
||||
@@ -163,6 +160,15 @@ watch(
|
||||
selectedTabKeys = [Object.keys(tabsInfo).find((key) => key === nextOpenKey) || firstKeyOfObject(tabsInfo)]
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
dataSourcesState.value = props.dataSourcesState || ''
|
||||
selectedTabKeys = [Object.keys(tabsInfo).find((key) => key === props.openKey) || firstKeyOfObject(tabsInfo)]
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -215,7 +221,12 @@ watch(
|
||||
|
||||
<!-- Sub Tabs -->
|
||||
<a-layout-content class="h-auto px-4 scrollbar-thumb-gray-500">
|
||||
<a-menu v-model:selectedKeys="selectedSubTabKeys" :open-keys="[]" mode="horizontal">
|
||||
<a-menu
|
||||
v-if="selectedTabKeys[0] !== 'dataSources'"
|
||||
v-model:selectedKeys="selectedSubTabKeys"
|
||||
:open-keys="[]"
|
||||
mode="horizontal"
|
||||
>
|
||||
<a-menu-item
|
||||
v-for="(tab, key) of selectedTab.subTabs"
|
||||
:key="key"
|
||||
@@ -225,8 +236,52 @@ 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="dataSourcesState !== ''" @click="dataSourcesState = ''">
|
||||
<a class="!no-underline">Data Sources</a>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item v-else @click="dataSourcesState = ''">Data Sources</a-breadcrumb-item>
|
||||
<a-breadcrumb-item v-if="dataSourcesState !== ''">{{ dataSourcesState }}</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
<div v-if="dataSourcesState === ''" class="flex flex-row justify-end items-center w-full">
|
||||
<a-button class="self-start nc-btn-new-datasource" @click="dataSourcesState = DataSourcesSubTab.New">
|
||||
<div v-if="dataSourcesState === ''" class="flex items-center gap-2 text-gray-600 font-light">
|
||||
<MdiDatabaseOutline class="text-lg group-hover:text-accent" />
|
||||
New
|
||||
</div>
|
||||
</a-button>
|
||||
<!-- Reload -->
|
||||
<a-button
|
||||
v-e="['a:proj-meta:data-sources:reload']"
|
||||
class="self-start nc-btn-metasync-reload"
|
||||
@click="dataSourcesReload = true"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-gray-600 font-light">
|
||||
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': dataSourcesReload }" />
|
||||
{{ $t('general.reload') }}
|
||||
</div>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider style="margin: 10px 0" />
|
||||
</div>
|
||||
|
||||
<component :is="selectedSubTab?.body" class="px-2 py-6" :data-testid="`nc-settings-subtab-${selectedSubTab.title}`" />
|
||||
<component
|
||||
:is="selectedSubTab?.body"
|
||||
v-if="selectedSubTabKeys[0] === 'dataSources'"
|
||||
v-model:state="dataSourcesState"
|
||||
v-model:reload="dataSourcesReload"
|
||||
class="px-2 pb-2"
|
||||
:data-testid="`nc-settings-subtab-${selectedSubTab.title}`"
|
||||
/>
|
||||
<component
|
||||
:is="selectedSubTab?.body"
|
||||
v-else
|
||||
class="px-2 py-6"
|
||||
:data-testid="`nc-settings-subtab-${selectedSubTab.title}`"
|
||||
/>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-modal>
|
||||
|
||||
@@ -13,6 +13,12 @@ import {
|
||||
viewIcons,
|
||||
} from '#imports'
|
||||
|
||||
interface Props {
|
||||
baseId: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { $api, $e } = useNuxtApp()
|
||||
@@ -32,8 +38,9 @@ const searchInput = $ref('')
|
||||
const filteredTables = computed(() =>
|
||||
tables.filter(
|
||||
(el) =>
|
||||
(typeof el?._ptn === 'string' && el._ptn.toLowerCase().includes(searchInput.toLowerCase())) ||
|
||||
(typeof el?.title === 'string' && el.title.toLowerCase().includes(searchInput.toLowerCase())),
|
||||
el?.base_id === props.baseId &&
|
||||
((typeof el?._ptn === 'string' && el._ptn.toLowerCase().includes(searchInput.toLowerCase())) ||
|
||||
(typeof el?.title === 'string' && el.title.toLowerCase().includes(searchInput.toLowerCase()))),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user