[Core] [Source] [Metadata] Refactor metadata structure

This commit is contained in:
qianmoQ 2024-04-18 15:51:58 +08:00
parent f3242cddf4
commit dec9f83dae
10 changed files with 223 additions and 135 deletions

View File

@ -576,7 +576,9 @@ public class SourceServiceImpl
databaseTableCache.put(key, this.tableHandler.findSimpleAllByDatabase(item));
});
// Delete invalid data that no longer exists
List<DatabaseEntity> deleteEntities = origin.stream().filter(node -> entities.stream().noneMatch(item -> node.getName().equals(item.getName()))).collect(Collectors.toList());
List<DatabaseEntity> deleteEntities = origin.stream()
.filter(node -> entities.stream().noneMatch(item -> node.getName().equals(item.getName())))
.collect(Collectors.toList());
log.info("Removed database size [ {} ] from source [ {} ]", deleteEntities.size(), entity.getName());
databaseHandler.deleteAll(deleteEntities);
databaseRemovedCount.addAndGet(deleteEntities.size());

View File

@ -216,7 +216,7 @@ const createAdminRouter = (router: any) => {
component: () => import('@/views/pages/admin/source/SourceHome.vue')
},
{
path: 'source/manager/:source',
path: 'source/:source',
component: MetadataContainer,
meta: {
title: 'common.source',
@ -224,12 +224,30 @@ const createAdminRouter = (router: any) => {
},
children: [
{
path: 'info/:table',
path: 'd/:database/',
meta: {
title: 'common.source',
isRoot: false
},
component: () => import('@/views/pages/admin/source/SourceManagerInfo.vue')
component: () => import('@/views/pages/admin/source/SourceDatabase.vue')
},
{
path: 'd/:database/t/info/:table',
meta: {
title: 'common.source',
isRoot: false,
type: 'info'
},
component: () => import('@/views/pages/admin/source/SourceTableInfo.vue')
},
{
path: 'd/:database/t/structure/:table',
meta: {
title: 'common.source',
isRoot: false,
type: 'structure'
},
component: () => import('@/views/pages/admin/source/SourceTableStructure.vue')
}
]
},

View File

@ -16,12 +16,12 @@ class TableService
/**
* Retrieves all data from the database by the specified ID.
*
* @param {number} id - The ID of the database.
* @param {number} code - The code of the database.
* @return {Promise<ResponseModel>} A promise that resolves to a ResponseModel object.
*/
getAllByDatabase(id: number): Promise<ResponseModel>
getAllByDatabase(code: string): Promise<ResponseModel>
{
return new HttpUtils().post(`${ DEFAULT_PATH }/database/${ id }`)
return new HttpUtils().post(`${ DEFAULT_PATH }/database/${ code }`)
}
/**

View File

@ -3,13 +3,10 @@
<div class="hidden space-y-6 w-full md:block">
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-6 lg:space-y-0">
<aside class="-mx-4 w-[200px]">
<MetadataSidebar :code="code" @change="handlerChange"/>
<MetadataSidebar/>
</aside>
<div class="flex-1">
<Card v-if="!dataInfo" :body-class="'p-8'" :hidden-title="true">
<Alert :description="$t('source.tip.notSelectedNode')"/>
</Card>
<MetadataContent v-else/>
<MetadataContent/>
</div>
</div>
</div>
@ -22,33 +19,19 @@ import MetadataSidebar from '@/views/layouts/metadata/components/MetadataSidebar
import MetadataContent from '@/views/layouts/metadata/components/MetadataContent.vue'
import { StructureModel } from '@/model/structure.ts'
import router from '@/router'
import Card from '@/views/ui/card'
import Alert from '@/views/ui/alert'
export default defineComponent({
name: 'MetadataContainer',
components: {
MetadataContent,
MetadataSidebar,
Card,
Alert
},
data()
{
return {
code: null as string | null,
dataInfo: null as StructureModel | null
}
},
created()
{
this.code = this.$route.params?.source as string
MetadataSidebar
},
methods: {
handlerChange(node: StructureModel)
{
this.dataInfo = node
router.push(`/admin/source/manager/${ this.code }/info/${ node.code }`)
console.log(node)
router.push(`/admin/source/manager/${ this.code }/d/${ node.code }`)
}
}
})

View File

@ -1,15 +1,19 @@
<template>
<Tabs v-model="selectTab" :default-value="selectTab" class="w-full">
<Tabs v-model="selectTab as string" :default-value="selectTab as string" class="w-full">
<Card :title-class="'p-0'" :body-class="'p-0'">
<template #title>
<TabsList>
<TabsTrigger value="info" class="cursor-pointer">
<Info :size="18" class="mr-2"/>
{{ $t('source.common.info') }}
<TabsTrigger value="info" class="cursor-pointer" @click="handlerChange">
<div class="flex space-x-2">
<Info :size="18"/>
<span>{{ $t('source.common.info') }}</span>
</div>
</TabsTrigger>
<TabsTrigger value="structure" class="cursor-pointer">
<LayoutPanelTop :size="18" class="mr-2"/>
{{ $t('source.common.structure') }}
<TabsTrigger value="structure" class="cursor-pointer" @click="handlerChange">
<div class="flex space-x-2">
<LayoutPanelTop :size="18"/>
<span>{{ $t('source.common.structure') }}</span>
</div>
</TabsTrigger>
<TabsTrigger value="data" class="cursor-pointer">
<Table :size="18" class="mr-2"/>
@ -25,7 +29,7 @@
</TabsTrigger>
</TabsList>
</template>
<TabsContent :value="selectTab">
<TabsContent :value="selectTab as string">
<div class="h-[695px]">
<RouterView/>
</div>
@ -35,7 +39,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent, watch } from 'vue'
import Card from '@/views/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Info, LayoutPanelTop, SatelliteDish, Table, Wind } from 'lucide-vue-next'
@ -50,7 +54,44 @@ export default defineComponent({
data()
{
return {
selectTab: 'info'
selectTab: null as string | null,
originalSource: null as string | null,
originalDatabase: null as string | null,
originalTable: null as string | null
}
},
created()
{
this.handlerInitialize()
this.watchChange()
},
methods: {
handlerInitialize()
{
const source = this.$route.params?.source as string
const database = this.$route.params?.database as string
const table = this.$route.params?.table as string
const type = this.$route.meta.type as string
this.originalSource = source
this.originalDatabase = database
this.originalTable = table
this.selectTab = type
},
handlerChange()
{
if (this.selectTab === 'info') {
this.$router.push(`/admin/source/${ this.originalSource }/d/${ this.originalDatabase }/t/info/${ this.originalTable }`)
}
if (this.selectTab === 'structure') {
this.$router.push(`/admin/source/${ this.originalSource }/d/${ this.originalDatabase }/t/structure/${ this.originalTable }`)
}
},
watchChange()
{
watch(
() => this.$route?.params.table,
() => this.handlerInitialize()
)
}
}
})

View File

@ -1,7 +1,7 @@
<template>
<Card :title-class="'p-0'" :body-class="'p-0'">
<template #title>
<Select v-model="selectDatabase" @update:modelValue="handlerChangeDatabase">
<Select v-model="selectDatabase" :default-value="originalDatabase ? originalDatabase : selectDatabase" @update:modelValue="handlerChangeDatabase">
<SelectTrigger class="border-0 w-[200px]">
<SelectValue :placeholder="$t('source.tip.selectDatabase')"/>
</SelectTrigger>
@ -123,6 +123,7 @@ import ColumnChange from '@/views/pages/admin/source/components/ColumnChange.vue
import TableTruncate from '@/views/pages/admin/source/components/TableTruncate.vue'
import TableDrop from '@/views/pages/admin/source/components/TableDrop.vue'
import TableCreate from '@/views/pages/admin/source/components/TableCreate.vue'
import { ToastUtils } from '@/utils/toast.ts'
export default defineComponent({
name: 'MetadataSidebar',
@ -146,11 +147,6 @@ export default defineComponent({
DropdownMenuSubTrigger,
DropdownMenuTrigger
},
props: {
code: {
type: String
}
},
computed: {
StructureEnum()
{
@ -162,6 +158,9 @@ export default defineComponent({
return {
loading: false,
selectDatabase: undefined,
originalSource: null as string | null,
originalDatabase: null as string | null,
originalTable: null as string | null,
selectNode: null as StructureModel | null,
databaseArray: Array<StructureModel>(),
dataTreeArray: Array<StructureModel>(),
@ -182,19 +181,31 @@ export default defineComponent({
methods: {
handlerInitialize()
{
if (this.code) {
const source = this.$route.params?.source as string
const database = this.$route.params?.database as string
if (source) {
this.originalSource = source
this.loading = true
DatabaseService.getAllBySource(this.code as string)
DatabaseService.getAllBySource(source)
.then(response => {
if (response.status) {
response.data.forEach((item: { name: null; catalog: null; code: null }) => {
const structure: StructureModel = {
title: item.name,
catalog: item.catalog,
code: item.code
}
this.databaseArray.push(structure)
})
response.data
.forEach((item: { name: null; catalog: null; code: undefined }) => {
const structure: StructureModel = {
title: item.name,
catalog: item.catalog,
code: item.code
}
this.databaseArray.push(structure)
})
if (database) {
this.originalDatabase = database
this.selectDatabase = database as any
this.handlerChangeDatabase()
}
}
else {
ToastUtils.error(response.message)
}
})
.finally(() => this.loading = false)
@ -204,50 +215,67 @@ export default defineComponent({
{
this.loading = true
this.dataTreeArray = []
TableService.getAllByDatabase(this.selectDatabase as string)
TableService.getAllByDatabase(this.selectDatabase as any)
.then(response => {
if (response.status) {
response.data.forEach((item: {
name: null;
title: null;
catalog: null;
code: null;
type: null;
engine: null;
comment: null;
database: { name: null, id: string };
}) => {
const structure: StructureModel = {
title: item.name,
database: item.database.name,
databaseId: item.database.id,
catalog: item.catalog,
code: item.code,
type: item.type,
level: StructureEnum.TABLE,
engine: item.engine,
comment: item.comment,
origin: item,
loading: false,
contextmenu: true,
children: [] as StructureModel[],
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'table',
style: { marginRight: '6px' }
}),
this.resolveTableComponent(h, data)
])
])
}
}
this.dataTreeArray.push(structure)
})
response.data
.forEach((item: {
name: null;
title: null;
catalog: null;
code: undefined;
type: null;
engine: null;
comment: null;
database: { name: null, id: string };
}) => {
const structure: StructureModel = {
title: item.name,
database: item.database.name,
databaseId: item.database.id,
catalog: item.catalog,
code: item.code,
type: item.type,
level: StructureEnum.TABLE,
engine: item.engine,
comment: item.comment,
origin: item,
loading: false,
contextmenu: true,
children: [] as StructureModel[],
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'table',
style: { marginRight: '6px' }
}),
this.resolveTableComponent(h, data)
])
])
}
}
this.dataTreeArray.push(structure)
})
}
else {
ToastUtils.error(response.message)
}
})
.finally(() => {
this.loading = false
const table = this.$route.params?.table as string
if (table) {
const node = this.dataTreeArray.find(item => item.code === table)
if (node) {
node.selected = true
this.handlerSelectNode([node])
}
}
else {
this.$router.push(`/admin/source/${ this.originalSource }/d/${ this.selectDatabase }`)
}
})
.finally(() => this.loading = false)
},
handlerSelectNode(node: Array<StructureModel>)
{
@ -265,7 +293,7 @@ export default defineComponent({
return
}
this.selectNode = currentNode
this.$emit('change', this.selectNode)
this.$router.push(`/admin/source/${ this.originalSource }/d/${ this.selectDatabase }/t/info/${ currentNode.code }`)
},
handlerLoadChildData(item: StructureModel, callback: any)
{
@ -274,14 +302,14 @@ export default defineComponent({
callback(dataChildArray)
return
}
ColumnService.getAllByTable(item.code)
ColumnService.getAllByTable(item.code as string)
.then(response => {
if (response.status) {
response.data.forEach((item: {
name: null;
title: null;
catalog: null;
code: null;
code: undefined;
type: null;
dataType: null;
extra: null;

View File

@ -0,0 +1,19 @@
<template>
<Card :body-class="'p-8'" :hidden-title="true">
<Alert :description="$t('source.tip.notSelectedNode')"/>
</Card>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Card from '@/views/ui/card'
import Alert from '@/views/ui/alert'
export default defineComponent({
name: 'SourceDatabase',
components: {
Card,
Alert
}
})
</script>

View File

@ -42,7 +42,7 @@
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuItem :disabled="(loginUserId !== row.user.id) || !row.available" class="cursor-pointer">
<RouterLink :to="`/admin/source/manager/${row?.code}`" target="_blank" class="flex items-center">
<RouterLink :to="`/admin/source/${row?.code}`" target="_blank" class="flex items-center">
<Cog class="mr-2 h-4 w-4"/>
<span>{{ $t('source.common.manager') }}</span>
</RouterLink>

View File

@ -130,7 +130,7 @@ import { Textarea } from '@/components/ui/textarea'
import { toNumber } from 'lodash'
export default defineComponent({
name: 'SourceManagerInfo',
name: 'SourceTableInfo',
components: {
Textarea,
Input,
@ -146,6 +146,7 @@ export default defineComponent({
created()
{
this.handlerInitialize()
this.watchChange()
},
data()
{
@ -158,22 +159,20 @@ export default defineComponent({
methods: {
handlerInitialize()
{
watch(
() => this.$route?.params.table,
() => {
const code = this.$route?.params.table as string
if (code) {
this.loading = true
TableService.getByCode(code)
.then(response => {
if (response.status) {
this.dataInfo = response.data
}
})
.finally(() => this.loading = false)
}
}
)
const code = this.$route?.params.table as string
if (code) {
this.loading = true
TableService.getByCode(code)
.then(response => {
if (response.status) {
this.dataInfo = response.data
}
else {
ToastUtils.error(response.message)
}
})
.finally(() => this.loading = false)
}
},
handlerApply()
{
@ -195,6 +194,13 @@ export default defineComponent({
})
.finally(() => this.submitting = false)
}
},
watchChange()
{
watch(
() => this.$route?.params.table,
() => this.handlerInitialize()
)
}
}
})

View File

@ -8,27 +8,19 @@
<script lang="ts">
import { defineComponent, watch } from 'vue'
import { StructureModel } from '@/model/structure'
import { useI18n } from 'vue-i18n'
import { createHeaders } from '@/views/pages/admin/source/components/TableUtils'
import { createHeaders } from '@/views/pages/admin/source/components/TableUtils.ts'
import TableCommon from '@/views/components/table/TableCommon.vue'
import ColumnService from '@/services/column'
import { cloneDeep, toNumber } from 'lodash'
import { TableModel } from '@/model/table'
import { ColumnModel } from '@/model/column'
import ColumnService from '@/services/column.ts'
import { ColumnModel } from '@/model/column.ts'
import Switch from '@/views/ui/switch'
export default defineComponent({
name: 'TableStructure',
name: 'SourceTableStructure',
components: {
TableCommon,
Switch
},
props: {
info: {
type: Object as () => StructureModel | null
}
},
setup()
{
const headers = createHeaders(useI18n())
@ -41,22 +33,21 @@ export default defineComponent({
{
return {
loading: false,
dataInfo: null as TableModel | null,
data: Array<ColumnModel>
}
},
created()
{
this.handlerInitialize()
this.watchId()
this.watchChange()
},
methods: {
handlerInitialize()
{
if (this.info) {
this.dataInfo = cloneDeep(this.info.origin)
const code = this.$route?.params.table as string
if (code) {
this.loading = true
ColumnService.getAllByTable(toNumber(this.dataInfo?.id))
ColumnService.getAllByTable(code)
.then(response => {
if (response.status) {
this.data = response.data
@ -65,10 +56,10 @@ export default defineComponent({
.finally(() => this.loading = false)
}
},
watchId()
watchChange()
{
watch(
() => this.info,
() => this.$route?.params.table,
() => {
this.handlerInitialize()
}