[Core] [Refactor] [UI] [Source] Add history

This commit is contained in:
qianmoQ 2024-04-07 11:18:19 +08:00
parent d8a6a0fb8b
commit 77fc3c8de5
7 changed files with 196 additions and 94 deletions

View File

@ -18,6 +18,7 @@ export default {
create: 'Create Source', create: 'Create Source',
delete: 'Delete Source [ $NAME ]', delete: 'Delete Source [ $NAME ]',
syncMetadata: 'Sync Metadata', syncMetadata: 'Sync Metadata',
syncHistory: 'Sync History',
}, },
tip: { tip: {
selectSource: 'Please select a source', selectSource: 'Please select a source',

View File

@ -18,6 +18,7 @@ export default {
create: '创建数据源', create: '创建数据源',
delete: '删除数据源 [ $NAME ]', delete: '删除数据源 [ $NAME ]',
syncMetadata: '同步元数据', syncMetadata: '同步元数据',
syncHistory: '同步历史',
}, },
tip: { tip: {
selectSource: '请选择数据源', selectSource: '请选择数据源',

View File

@ -3,6 +3,7 @@ import { BaseService } from '@/services/base'
import { HttpUtils } from '@/utils/http' import { HttpUtils } from '@/utils/http'
import { SourceModel } from '@/model/source.ts' import { SourceModel } from '@/model/source.ts'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { FilterModel } from '@/model/filter.ts'
const DEFAULT_PATH_V1 = '/api/v1/source' const DEFAULT_PATH_V1 = '/api/v1/source'
const DEFAULT_PATH_V2 = '/api/v2/source' const DEFAULT_PATH_V2 = '/api/v2/source'
@ -50,6 +51,11 @@ class SourceService
{ {
return new HttpUtils().put(`${ DEFAULT_PATH_V2 }/syncMetadata/${ id }`) return new HttpUtils().put(`${ DEFAULT_PATH_V2 }/syncMetadata/${ id }`)
} }
getHistory(id: number, configure: FilterModel): Promise<ResponseModel>
{
return new HttpUtils().post(`${ DEFAULT_PATH_V2 }/getHistory/${ id }`, configure)
}
} }
export default new SourceService() export default new SourceService()

View File

@ -0,0 +1,157 @@
<template>
<Dialog :is-visible="visible" :title="$t('source.common.syncHistory')" :width="'60%'">
<TableCommon :loading="loading" :columns="headers" :data="data" :pagination="pagination" @changePage="handlerChangePage">
<template #elapsed="{row}">
{{ (getTime(row.updateTime) - getTime(row.createTime)) / 1000 }}
</template>
<template #state="{row}">
<Badge :style="{backgroundColor: Common.getColor(row?.state)}">
<HoverCard v-if="row?.state === 'FAILURE'">
<HoverCardTrigger as-child>
<Button variant="link">
{{ getStateText(row?.state) }}
</Button>
</HoverCardTrigger>
<HoverCardContent class="w-full">
{{ row?.message }}
</HoverCardContent>
</HoverCard>
<span v-else>{{ getStateText(row?.state) }}</span>
</Badge>
</template>
<template #result="{row}">
<HoverCard>
<HoverCardTrigger as-child>
<Eye class="cursor-pointer"/>
</HoverCardTrigger>
<HoverCardContent class="w-full">
<MdPreview :modelValue="toMarkdown(row.info)" style="padding: 0"/>
</HoverCardContent>
</HoverCard>
</template>
</TableCommon>
<template #footer>
<div class="space-x-5">
<Button variant="outline" size="sm" @click="handlerCancel">
{{ $t('common.cancel') }}
</Button>
</div>
</template>
</Dialog>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Dialog from '@/views/ui/dialog'
import { SourceModel } from '@/model/source'
import SourceService from '@/services/source'
import Button from '@/views/ui/button'
import { FilterModel } from '@/model/filter'
import { useI18n } from 'vue-i18n'
import { createHistoryHeaders } from '@/views/pages/admin/source/SourceUtils'
import { PaginationModel, PaginationRequest } from '@/model/pagination'
import TableCommon from '@/views/components/table/TableCommon.vue'
import { Badge } from '@/components/ui/badge'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import Common from '@/utils/common'
import { Eye } from 'lucide-vue-next'
import { MdPreview } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
export default defineComponent({
name: 'SourceHistory',
components: {
Badge,
Button,
Dialog,
TableCommon,
HoverCard, HoverCardContent, HoverCardTrigger,
Eye,
MdPreview
},
computed: {
visible: {
get(): boolean
{
return this.isVisible
},
set(value: boolean)
{
this.$emit('close', value)
}
},
Common()
{
return Common
}
},
props: {
isVisible: {
type: Boolean
},
info: {
type: Object as () => SourceModel | null
}
},
setup()
{
const i18n = useI18n()
const filter: FilterModel = new FilterModel()
const headers = createHistoryHeaders(i18n)
return {
filter,
headers,
i18n
}
},
data()
{
return {
loading: false,
data: [],
pagination: {} as PaginationModel
}
},
created()
{
this.handlerInitialize()
},
methods: {
handlerInitialize()
{
this.loading = true
SourceService.getHistory(this.info?.id as number, this.filter)
.then((response) => {
if (response.status) {
this.data = response.data.content
this.pagination = PaginationRequest.of(response.data)
}
})
.finally(() => this.loading = false)
},
handlerChangePage(value: PaginationModel)
{
this.filter.page = value.currentPage
this.filter.size = value.pageSize
this.handlerInitialize()
},
handlerCancel()
{
this.visible = false
},
getTime(time: any)
{
return time ? new Date(time).getTime() : 0
},
getStateText(origin: string): string
{
return Common.getText(this.i18n, origin)
},
toMarkdown(content: string)
{
return '```json\n' + JSON.stringify(content, null, 4) + '\n```'
}
}
})
</script>

View File

@ -45,6 +45,10 @@
<Trash class="mr-2 h-4 w-4"/> <Trash class="mr-2 h-4 w-4"/>
<span>{{ $t('common.deleteData') }}</span> <span>{{ $t('common.deleteData') }}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem :disabled="(loginUserId !== row.user.id)" class="cursor-pointer" @click="handlerHistory(true, row)">
<History class="mr-2 h-4 w-4"/>
{{ $t('source.common.syncHistory') }}
</DropdownMenuItem>
<DropdownMenuItem :disabled="(loginUserId !== row.user.id) || !row.available" class="cursor-pointer" @click="handlerSyncMetadata(true, row)"> <DropdownMenuItem :disabled="(loginUserId !== row.user.id) || !row.available" class="cursor-pointer" @click="handlerSyncMetadata(true, row)">
<RefreshCcwDot class="mr-2 h-4 w-4"/> <RefreshCcwDot class="mr-2 h-4 w-4"/>
{{ $t('source.common.syncMetadata') }} {{ $t('source.common.syncMetadata') }}
@ -59,6 +63,7 @@
<SourceInfo v-if="dataInfoVisible" :is-visible="dataInfoVisible" :info="dataInfo" @close="handlerInfo(false, null)"/> <SourceInfo v-if="dataInfoVisible" :is-visible="dataInfoVisible" :info="dataInfo" @close="handlerInfo(false, null)"/>
<SourceDelete v-if="dataDeleteVisible" :is-visible="dataDeleteVisible" :info="dataInfo" @close="handlerDelete(false, null)"/> <SourceDelete v-if="dataDeleteVisible" :is-visible="dataDeleteVisible" :info="dataInfo" @close="handlerDelete(false, null)"/>
<SourceMetadata v-if="dataSyncMetadataVisible" :is-visible="dataSyncMetadataVisible" :info="dataInfo" @close="handlerSyncMetadata(false, null)"/> <SourceMetadata v-if="dataSyncMetadataVisible" :is-visible="dataSyncMetadataVisible" :info="dataInfo" @close="handlerSyncMetadata(false, null)"/>
<SourceHistory v-if="dataHistoryVisible" :is-visible="dataHistoryVisible" :info="dataInfo" @close="handlerHistory(false, null)"/>
</div> </div>
</template> </template>
@ -66,7 +71,7 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import Card from '@/views/ui/card' import Card from '@/views/ui/card'
import Button from '@/views/ui/button' import Button from '@/views/ui/button'
import { CirclePlay, CircleX, Cog, Pencil, Plus, RefreshCcwDot, Trash } from 'lucide-vue-next' import { CirclePlay, CircleX, Cog, History, Pencil, Plus, RefreshCcwDot, Trash } from 'lucide-vue-next'
import TableCommon from '@/views/components/table/TableCommon.vue' import TableCommon from '@/views/components/table/TableCommon.vue'
import { FilterModel } from '@/model/filter' import { FilterModel } from '@/model/filter'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -91,10 +96,12 @@ import {
} from '@/components/ui/dropdown-menu' } from '@/components/ui/dropdown-menu'
import SourceDelete from '@/views/pages/admin/source/SourceDelete.vue' import SourceDelete from '@/views/pages/admin/source/SourceDelete.vue'
import SourceMetadata from '@/views/pages/admin/source/SourceMetadata.vue' import SourceMetadata from '@/views/pages/admin/source/SourceMetadata.vue'
import SourceHistory from '@/views/pages/admin/source/SourceHistory.vue'
export default defineComponent({ export default defineComponent({
name: 'SourceHome', name: 'SourceHome',
components: { components: {
SourceHistory,
SourceMetadata, SourceMetadata,
SourceDelete, SourceDelete,
DropdownMenuItem, DropdownMenuGroup, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuContent, DropdownMenuTrigger, DropdownMenu, DropdownMenuItem, DropdownMenuGroup, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuContent, DropdownMenuTrigger, DropdownMenu,
@ -104,7 +111,7 @@ export default defineComponent({
Switch, Switch,
Avatar, Avatar,
TableCommon, TableCommon,
Pencil, CircleX, CirclePlay, Cog, Trash, Plus, RefreshCcwDot, Pencil, CircleX, CirclePlay, Cog, Trash, Plus, RefreshCcwDot, History,
Button, Button,
Card Card
}, },
@ -129,7 +136,8 @@ export default defineComponent({
dataInfoVisible: false, dataInfoVisible: false,
dataInfo: null as SourceModel | null, dataInfo: null as SourceModel | null,
dataDeleteVisible: false, dataDeleteVisible: false,
dataSyncMetadataVisible: false dataSyncMetadataVisible: false,
dataHistoryVisible: false
} }
}, },
created() created()
@ -175,6 +183,11 @@ export default defineComponent({
{ {
this.dataSyncMetadataVisible = opened this.dataSyncMetadataVisible = opened
this.dataInfo = value this.dataInfo = value
},
handlerHistory(opened: boolean, value: null | SourceModel)
{
this.dataHistoryVisible = opened
this.dataInfo = value
} }
} }
}) })

View File

@ -15,6 +15,19 @@ const createHeaders = (i18n: any) => {
] ]
} }
export { const createHistoryHeaders = (i18n: any) => {
createHeaders return [
{ key: 'id', hidden: true, header: i18n.t('common.id'), width: 80 },
{ key: 'name', hidden: true, header: i18n.t('common.name') },
{ key: 'createTime', hidden: true, header: i18n.t('common.createTime') },
{ key: 'updateTime', hidden: true, header: i18n.t('common.updateTime') },
{ key: 'elapsed', hidden: true, header: i18n.t('common.elapsed'), slot: 'elapsed', class: 'text-center' },
{ key: 'state', hidden: true, header: i18n.t('common.state'), slot: 'state', class: 'text-center' },
{ key: 'result', hidden: true, header: i18n.t('common.result'), slot: 'result' }
]
}
export {
createHeaders,
createHistoryHeaders
} }

View File

@ -1,89 +0,0 @@
<template>
<div>
<Modal v-model="visible"
:title="$t('common.syncMetadata') + ' [ ' + data.name + ' ]'"
:mask-closable="false"
@cancel="handlerCancel()">
<Alert type="warning"
show-icon>
{{ $t('source.manager.sourceSyncMetadataTip1') }}
</Alert>
<Alert type="error"
show-icon>
{{ $t('source.manager.sourceSyncMetadataTip2') }}
</Alert>
<p>{{ $t('source.manager.sourceSyncMetadataTip3').replace('REPLACE_NAME', data.name) }}</p>
<Input v-model="inputValue"/>
<template #footer>
<Button type="primary"
:disabled="inputValue !== data.name"
:loading="loading"
@click="handlerSubmit()">
<FontAwesomeIcon icon="rotate"/>
{{ $t('common.syncMetadata') }}
</Button>
</template>
</Modal>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
import SourceV2Service from "@/services/SourceV2Service";
export class DataItem
{
id: number;
name: string;
}
export default defineComponent({
name: "SourceMetadata",
props: {
isVisible: {
type: Boolean,
default: () => false
},
data: {
type: DataItem
}
},
data()
{
return {
loading: false,
inputValue: null
}
},
methods: {
handlerSubmit()
{
this.loading = true;
SourceV2Service
.syncMetadata(this.data.id)
.then((response) => {
if (response.status) {
this.$Message.success(`${this.$t('source.manager.sourceSyncMetadataTip4').replace('REPLACE_NAME', this.data.name)}`);
this.handlerCancel();
}
})
.finally(() => this.loading = false);
},
handlerCancel()
{
this.visible = false;
}
},
computed: {
visible: {
get(): boolean
{
return this.isVisible;
},
set(value: boolean)
{
this.$emit('close', value);
}
}
}
});
</script>