Browse Source

feat: ui improvements for multiple source

Signed-off-by: mertmit <mertmit99@gmail.com>
pull/3573/head
mertmit 2 years ago
parent
commit
d61a0874a9
  1. 6
      packages/nc-gui/components.d.ts
  2. 43
      packages/nc-gui/components/dashboard/TreeView.vue
  3. 53
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  4. 58
      packages/nc-gui/components/dashboard/settings/Modal.vue
  5. 11
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  6. 13
      packages/nc-gui/components/general/AddBaseButton.vue
  7. 28
      packages/nc-gui/components/general/BaseLogo.vue
  8. 2
      packages/nc-gui/nuxt.config.ts
  9. 38
      packages/nc-gui/package-lock.json
  10. 2
      packages/nc-gui/package.json
  11. 14
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue

6
packages/nc-gui/components.d.ts vendored

@ -86,6 +86,8 @@ declare module '@vue/runtime-core' {
IcTwotoneWidthFull: typeof import('~icons/ic/twotone-width-full')['default']
IcTwotoneWidthNormal: typeof import('~icons/ic/twotone-width-normal')['default']
LogosGoogleGmail: typeof import('~icons/logos/google-gmail')['default']
LogosMysql: typeof import('~icons/logos/mysql')['default']
LogosPostgresql: typeof import('~icons/logos/postgresql')['default']
LogosRedditIcon: typeof import('~icons/logos/reddit-icon')['default']
LogosSwagger: typeof import('~icons/logos/swagger')['default']
MaterialSymbolsAccountTreeRounded: typeof import('~icons/material-symbols/account-tree-rounded')['default']
@ -145,6 +147,7 @@ declare module '@vue/runtime-core' {
MdiContentSave: typeof import('~icons/mdi/content-save')['default']
MdiContentSaveEdit: typeof import('~icons/mdi/content-save-edit')['default']
MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default']
MdiDatabaseAlert: typeof import('~icons/mdi/database-alert')['default']
MdiDatabaseLockOutline: typeof import('~icons/mdi/database-lock-outline')['default']
MdiDatabaseOutline: typeof import('~icons/mdi/database-outline')['default']
MdiDatabasePlusOutline: typeof import('~icons/mdi/database-plus-outline')['default']
@ -240,7 +243,10 @@ declare module '@vue/runtime-core' {
MiCircleWarning: typeof import('~icons/mi/circle-warning')['default']
PhCloudLightningDuotone: typeof import('~icons/ph/cloud-lightning-duotone')['default']
PhFileCsv: typeof import('~icons/ph/file-csv')['default']
RiTeamFill: typeof import('~icons/ri/team-fill')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SimpleIconsMicrosoftsqlserver: typeof import('~icons/simple-icons/microsoftsqlserver')['default']
VscodeIconsFileTypeSqlite: typeof import('~icons/vscode-icons/file-type-sqlite')['default']
}
}

43
packages/nc-gui/components/dashboard/TreeView.vue

@ -5,6 +5,7 @@ import Sortable from 'sortablejs'
import GithubButton from 'vue-github-button'
import type { VNodeRef } from '#imports'
import {
ClientType,
Empty,
TabType,
computed,
@ -21,7 +22,7 @@ import {
useTabs,
useToggle,
useUIPermission,
watchEffect,
watchEffect
} from '#imports'
import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large'
@ -42,6 +43,8 @@ const route = useRoute()
const [searchActive, toggleSearchActive] = useToggle()
const toggleDialog = inject(ToggleDialogInj, () => {})
let key = $ref(0)
const activeKey = ref([])
@ -360,6 +363,35 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
{{ $t('labels.requestDataSource') }}
</a>
</a-menu-item>
<a-menu-divider class="my-0" />
<a-menu-item-group title="Connect to new datasource" class="!px-0 !mx-0">
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MYSQL)">
<div class="color-transition nc-project-menu-item group">
<LogosMysql class="group-hover:text-accent" />
MySQL
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.PG)">
<div class="color-transition nc-project-menu-item group">
<LogosPostgresql class="group-hover:text-accent" />
Postgres
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SQLITE)">
<div class="color-transition nc-project-menu-item group">
<VscodeIconsFileTypeSqlite class="group-hover:text-accent" />
SQLite
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MSSQL)">
<div class="color-transition nc-project-menu-item group">
<SimpleIconsMicrosoftsqlserver class="group-hover:text-accent" />
MSSQL
</div>
</a-menu-item>
</a-menu-item-group>
</a-menu>
</template>
</a-dropdown>
@ -439,10 +471,9 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<a-collapse v-else v-model:activeKey="activeKey" expand-icon-position="right" :bordered="false" accordion ghost>
<a-collapse-panel :key="index">
<template #header>
<div v-if="index !== '0'" class="flex items-center gap-2">
<MdiDatabaseOutline />
<div v-if="index !== '0'" class="flex items-center gap-2 text-gray-500 font-weightd">
<GeneralBaseLogo :base-type="base.type" />
{{ base.alias || '' }}
({{ base.type || '' }})
</div>
</template>
<div
@ -650,10 +681,6 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
class="color-transition py-1.5 px-2 text-primary font-bold cursor-pointer select-none hover:text-accent"
/>
<LazyGeneralShareBaseButton
class="color-transition py-1.5 px-2 text-primary font-bold cursor-pointer select-none hover:text-accent"
/>
<LazyGeneralHelpAndSupport class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent" />
<GeneralJoinCloud class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent" />

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

@ -6,7 +6,7 @@ import EditBase from './data-sources/EditBase.vue'
import Metadata from './Metadata.vue'
import UIAcl from './UIAcl.vue'
import Erd from './Erd.vue'
import { DataSourcesSubTab } from '~/lib'
import { ClientType, DataSourcesSubTab } from '~/lib'
import { useNuxtApp, useProject } from '#imports'
interface Props {
@ -18,7 +18,7 @@ const props = defineProps<Props>()
const emits = defineEmits(['update:state', 'update:reload'])
const vModel = useVModel(props, 'state', emits)
const vState = useVModel(props, 'state', emits)
const vReload = useVModel(props, 'reload', emits)
const { $api, $e } = useNuxtApp()
@ -27,6 +27,7 @@ const { project, loadProject } = useProject()
let sources = $ref<BaseType[]>([])
let activeBaseId = $ref('')
let metadiffbases = $ref<string[]>([])
let clientType = $ref<ClientType>(ClientType.MYSQL)
async function loadBases() {
try {
@ -64,7 +65,7 @@ async function loadMetaDiff() {
const baseAction = (baseId: string, action: string) => {
activeBaseId = baseId
vModel.value = action
vState.value = action
}
const deleteBase = (base: BaseType) => {
@ -105,12 +106,37 @@ watch(
}
},
)
watch(
vState,
(newState) => {
switch (newState) {
case ClientType.MYSQL:
clientType = ClientType.MYSQL
vState.value = DataSourcesSubTab.New
break
case ClientType.PG:
clientType = ClientType.PG
vState.value = DataSourcesSubTab.New
break
case ClientType.SQLITE:
clientType = ClientType.SQLITE
vState.value = DataSourcesSubTab.New
break
case ClientType.MSSQL:
clientType = ClientType.MSSQL
vState.value = DataSourcesSubTab.New
break
}
},
{ immediate: true },
)
</script>
<template>
<div class="flex flex-row w-full">
<div class="flex flex-col w-full">
<div v-if="props.state === ''" class="max-h-600px overflow-y-auto">
<div v-if="vState === ''" class="max-h-600px overflow-y-auto">
<a-table
class="w-full"
size="small"
@ -127,7 +153,10 @@ watch(
<template #emptyText> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" /> </template>
<a-table-column key="alias" title="Name" data-index="alias">
<template #default="{ text, record }">
{{ record.is_meta ? 'BASE' : text }} <span class="text-gray-400 text-xs">({{ record.type }})</span>
<div class="flex items-center gap-1">
<GeneralBaseLogo :base-type="record.type" />
{{ record.is_meta ? 'BASE' : text }} <span class="text-gray-400 text-xs">({{ record.type }})</span>
</div>
</template>
</a-table-column>
<a-table-column key="action" :title="$t('labels.actions')" :width="180">
@ -140,7 +169,7 @@ watch(
<div class="flex items-center gap-2 text-gray-600 font-light">
<a-tooltip v-if="metadiffbases.includes(record.id)">
<template #title>Out of sync</template>
<MdiDatabaseSync class="text-lg group-hover:text-accent text-primary" />
<MdiDatabaseAlert class="text-lg group-hover:text-accent text-primary" />
</a-tooltip>
<MdiDatabaseSync v-else class="text-lg group-hover:text-accent" />
Sync Metadata
@ -179,19 +208,19 @@ watch(
</a-table-column>
</a-table>
</div>
<div v-else-if="props.state === DataSourcesSubTab.New" class="max-h-600px overflow-y-auto">
<CreateBase @base-created="loadBases" />
<div v-else-if="vState === DataSourcesSubTab.New" class="max-h-600px overflow-y-auto">
<CreateBase :connection-type="clientType" @base-created="loadBases" />
</div>
<div v-else-if="props.state === DataSourcesSubTab.Metadata" class="max-h-600px overflow-y-auto">
<div v-else-if="vState === 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">
<div v-else-if="vState === 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">
<div v-else-if="vState === DataSourcesSubTab.ERD" class="max-h-600px overflow-y-auto">
<Erd :base-id="activeBaseId" />
</div>
<div v-else-if="props.state === DataSourcesSubTab.Edit" class="max-h-600px overflow-y-auto">
<div v-else-if="vState === DataSourcesSubTab.Edit" class="max-h-600px overflow-y-auto">
<EditBase :base-id="activeBaseId" @base-updated="loadBases" />
</div>
</div>

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

@ -14,8 +14,8 @@ import { DataSourcesSubTab } from '~~/lib'
interface Props {
modelValue: boolean
openKey?: string
dataSourcesState?: string
openKey: string
dataSourcesState: string
}
interface SubTabGroup {
@ -37,17 +37,20 @@ interface TabGroup {
const props = defineProps<Props>()
const emits = defineEmits(['update:modelValue'])
const emits = defineEmits(['update:modelValue', 'update:openKey', 'update:dataSourcesState'])
const vModel = useVModel(props, 'modelValue', emits)
const vOpenKey = useVModel(props, 'openKey', emits)
const vDataState = useVModel(props, 'dataSourcesState', emits)
const { isUIAllowed } = useUIPermission()
const { t } = useI18n()
const { $e } = useNuxtApp()
const dataSourcesState = ref(props.dataSourcesState)
const dataSourcesReload = ref(false)
const tabsInfo: TabGroup = {
@ -103,7 +106,7 @@ const tabsInfo: TabGroup = {
},
},
onClick: () => {
dataSourcesState.value = ''
vDataState.value = ''
$e('c:settings:data-sources')
},
},
@ -141,7 +144,13 @@ const tabsInfo: TabGroup = {
const firstKeyOfObject = (obj: object) => Object.keys(obj)[0]
// Array of keys of tabs which are selected. In our case will be only one.
let selectedTabKeys = $ref<string[]>([firstKeyOfObject(tabsInfo)])
const selectedTabKeys = $computed<string[]>({
get: () => [Object.keys(tabsInfo).find((key) => key === vOpenKey.value) || firstKeyOfObject(tabsInfo)],
set: (value) => {
vOpenKey.value = value[0]
},
})
const selectedTab = $computed(() => tabsInfo[selectedTabKeys[0]])
let selectedSubTabKeys = $ref<string[]>([firstKeyOfObject(selectedTab.subTabs)])
@ -153,29 +162,6 @@ watch(
selectedSubTabKeys = [firstKeyOfObject(tabsInfo[newTabKey].subTabs)]
},
)
watch(
() => props.openKey,
(nextOpenKey) => {
selectedTabKeys = [Object.keys(tabsInfo).find((key) => key === nextOpenKey) || firstKeyOfObject(tabsInfo)]
},
)
watch(
() => props.dataSourcesState,
(nextState) => {
dataSourcesState.value = nextState || ''
},
)
watch(
() => props.modelValue,
() => {
dataSourcesState.value = props.dataSourcesState || ''
selectedTabKeys = [Object.keys(tabsInfo).find((key) => key === props.openKey) || firstKeyOfObject(tabsInfo)]
},
{ immediate: true },
)
</script>
<template>
@ -246,15 +232,15 @@ watch(
<div v-else>
<div class="flex items-center">
<a-breadcrumb class="w-full cursor-pointer">
<a-breadcrumb-item v-if="dataSourcesState !== ''" @click="dataSourcesState = ''">
<a-breadcrumb-item v-if="vDataState !== ''" @click="vDataState = ''">
<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-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="dataSourcesState === ''" class="flex flex-row justify-end items-center w-full gap-1">
<a-button class="self-start nc-btn-new-datasource" @click="dataSourcesState = DataSourcesSubTab.New">
<div v-if="dataSourcesState === ''" class="flex items-center gap-2 text-primary font-light">
<div v-if="vDataState === ''" class="flex flex-row justify-end items-center w-full gap-1">
<a-button class="self-start nc-btn-new-datasource" @click="vDataState = DataSourcesSubTab.New">
<div v-if="vDataState === ''" class="flex items-center gap-2 text-primary font-light">
<MdiDatabasePlusOutline class="text-lg group-hover:text-accent" />
New
</div>
@ -278,7 +264,7 @@ watch(
<component
:is="selectedSubTab?.body"
v-if="selectedSubTabKeys[0] === 'dataSources'"
v-model:state="dataSourcesState"
v-model:state="vDataState"
v-model:reload="dataSourcesReload"
class="px-2 pb-2"
:data-testid="`nc-settings-subtab-${selectedSubTab.title}`"

11
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -25,6 +25,8 @@ import { ClientType } from '~/lib'
import { DefaultConnection, SQLiteConnection } from '~/utils'
import type { ProjectCreateForm } from '~/utils'
const { connectionType } = defineProps<{ connectionType: ClientType }>()
const emit = defineEmits(['baseCreated'])
const { project, loadProject } = useProject()
@ -331,6 +333,15 @@ onMounted(async () => {
}, 500)
})
})
watch(
() => connectionType,
(v) => {
formState.dataSource.client = v
onClientChange()
},
{ immediate: true },
)
</script>
<template>

13
packages/nc-gui/components/general/AddBaseButton.vue

@ -1,20 +1,21 @@
<script setup lang="ts">
import { useUIPermission } from '#imports'
const { isUIAllowed } = useUIPermission()
const { t } = useI18n()
const toggleDialog = inject(ToggleDialogInj, () => {})
</script>
<template>
<div
v-if="isUIAllowed('settings')"
class="flex items-center w-full pl-3 hover:(text-primary bg-primary bg-opacity-5)"
@click="toggleDialog(true, 'dataSources', 'New')"
@click="toggleDialog(true)"
>
<div v-if="isUIAllowed('newBase')">
<div>
<div class="flex items-center space-x-1">
<MdiDatabasePlusOutline class="mr-1 nc-new-base" />
<div>Data Sources</div>
<RiTeamFill class="mr-1 nc-new-base" />
<div>{{ t('title.teamAndSettings') }}</div>
</div>
</div>
</div>

28
packages/nc-gui/components/general/BaseLogo.vue

@ -0,0 +1,28 @@
<script setup lang="ts">
import LogosMysql from '~icons/logos/mysql'
import LogosPostgresql from '~icons/logos/postgresql'
import VscodeIconsFileTypeSqlite from '~icons/vscode-icons/file-type-sqlite'
import SimpleIconsMicrosoftsqlserver from '~icons/simple-icons/microsoftsqlserver'
import MdiDatabaseOutline from '~icons/mdi/database-outline'
const { baseType } = defineProps<{ baseType?: string }>()
const baseIcon = computed(() => {
switch (baseType) {
case ClientType.MYSQL:
return LogosMysql
case ClientType.PG:
return LogosPostgresql
case ClientType.SQLITE:
return VscodeIconsFileTypeSqlite
case ClientType.MSSQL:
return SimpleIconsMicrosoftsqlserver
default:
return MdiDatabaseOutline
}
})
</script>
<template>
<component :is="baseIcon" />
</template>

2
packages/nc-gui/nuxt.config.ts

@ -129,6 +129,8 @@ export default defineNuxtConfig({
'ph',
'ri',
'system-uicons',
'vscode-icons',
'simple-icons',
],
}),
],

38
packages/nc-gui/package-lock.json generated

@ -57,7 +57,9 @@
"@iconify-json/mi": "^1.1.2",
"@iconify-json/ph": "^1.1.2",
"@iconify-json/ri": "^1.1.3",
"@iconify-json/simple-icons": "^1.1.29",
"@iconify-json/system-uicons": "^1.1.4",
"@iconify-json/vscode-icons": "^1.1.14",
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0",
@ -1175,6 +1177,15 @@
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/simple-icons": {
"version": "1.1.29",
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.1.29.tgz",
"integrity": "sha512-uP4fKlNoh9IuVTf1e1bl0TWZs+IDE19zCpKLNy+bKmJL5F9zOPwiIMqSsg4KRPXSdoZ2x7lTtt9BrO/O/3Phyg==",
"dev": true,
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/system-uicons": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@iconify-json/system-uicons/-/system-uicons-1.1.4.tgz",
@ -1184,6 +1195,15 @@
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/vscode-icons": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/@iconify-json/vscode-icons/-/vscode-icons-1.1.14.tgz",
"integrity": "sha512-we1er6h6WNDuV/dNSey/f4RDea5DILfDPCM7QN32EkjINr3ggAh7JhfTuQPP9A5lUQkjq5Xu2BpLyiz+E0Yd8w==",
"dev": true,
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify/types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-1.1.0.tgz",
@ -18414,6 +18434,15 @@
"@iconify/types": "*"
}
},
"@iconify-json/simple-icons": {
"version": "1.1.29",
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.1.29.tgz",
"integrity": "sha512-uP4fKlNoh9IuVTf1e1bl0TWZs+IDE19zCpKLNy+bKmJL5F9zOPwiIMqSsg4KRPXSdoZ2x7lTtt9BrO/O/3Phyg==",
"dev": true,
"requires": {
"@iconify/types": "*"
}
},
"@iconify-json/system-uicons": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@iconify-json/system-uicons/-/system-uicons-1.1.4.tgz",
@ -18423,6 +18452,15 @@
"@iconify/types": "*"
}
},
"@iconify-json/vscode-icons": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/@iconify-json/vscode-icons/-/vscode-icons-1.1.14.tgz",
"integrity": "sha512-we1er6h6WNDuV/dNSey/f4RDea5DILfDPCM7QN32EkjINr3ggAh7JhfTuQPP9A5lUQkjq5Xu2BpLyiz+E0Yd8w==",
"dev": true,
"requires": {
"@iconify/types": "*"
}
},
"@iconify/types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-1.1.0.tgz",

2
packages/nc-gui/package.json

@ -80,7 +80,9 @@
"@iconify-json/mi": "^1.1.2",
"@iconify-json/ph": "^1.1.2",
"@iconify-json/ri": "^1.1.3",
"@iconify-json/simple-icons": "^1.1.29",
"@iconify-json/system-uicons": "^1.1.4",
"@iconify-json/vscode-icons": "^1.1.14",
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0",

14
packages/nc-gui/pages/[projectType]/[projectId]/index.vue

@ -59,9 +59,9 @@ const { isOpen, toggle, toggleHasSidebar } = useSidebar('nc-left-sidebar', { has
const dialogOpen = ref(false)
const openDialogKey = ref<string>()
const openDialogKey = ref<string>('')
const dataSourcesState = ref<string>()
const dataSourcesState = ref<string>('')
const dropdownOpen = ref(false)
@ -77,8 +77,8 @@ const logout = () => {
function toggleDialog(value?: boolean, key?: string, dsState?: string) {
dialogOpen.value = value ?? !dialogOpen.value
openDialogKey.value = key
dataSourcesState.value = dsState
openDialogKey.value = key || ''
dataSourcesState.value = dsState || ''
}
provide(ToggleDialogInj, toggleDialog)
@ -569,7 +569,11 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
</template>
<div :key="$route.fullPath.split('?')[0]">
<LazyDashboardSettingsModal v-model="dialogOpen" :open-key="openDialogKey" :data-sources-state="dataSourcesState" />
<LazyDashboardSettingsModal
v-model:model-value="dialogOpen"
v-model:open-key="openDialogKey"
v-model:data-sources-state="dataSourcesState"
/>
<NuxtPage :page-key="$route.params.projectId" />

Loading…
Cancel
Save