[Core] Add dataset list
@ -14,15 +14,21 @@
|
|||||||
"@vee-validate/zod": "^4.12.6",
|
"@vee-validate/zod": "^4.12.6",
|
||||||
"@visactor/vchart": "^1.10.0",
|
"@visactor/vchart": "^1.10.0",
|
||||||
"@visactor/vtable": "^0.21.2",
|
"@visactor/vtable": "^0.21.2",
|
||||||
|
"@vue-flow/background": "^1.3.0",
|
||||||
|
"@vue-flow/controls": "^1.1.1",
|
||||||
|
"@vue-flow/core": "^1.33.4",
|
||||||
|
"@vue-flow/node-resizer": "^1.3.6",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
"echarts": "^5.5.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-vue-next": "^0.356.0",
|
"lucide-vue-next": "^0.356.0",
|
||||||
"radix-vue": "^1.5.2",
|
"radix-vue": "^1.5.2",
|
||||||
"tailwind-merge": "^2.2.1",
|
"tailwind-merge": "^2.2.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
"vaul-vue": "^0.1.0",
|
"vaul-vue": "^0.1.0",
|
||||||
"vee-validate": "^4.12.6",
|
"vee-validate": "^4.12.6",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
BIN
core/datacap-ui/public/static/images/executor/Seatunnel.png
Normal file
After Width: | Height: | Size: 218 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Alioss.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Cassandra.png
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
core/datacap-ui/public/static/images/plugin/CeresDB.png
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
core/datacap-ui/public/static/images/plugin/ClickHouse.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
core/datacap-ui/public/static/images/plugin/CrateDB.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
core/datacap-ui/public/static/images/plugin/DB2.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Dameng.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Doris.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Dremio.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Druid.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
core/datacap-ui/public/static/images/plugin/DuckDB.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
core/datacap-ui/public/static/images/plugin/ElasticSearch.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
core/datacap-ui/public/static/images/plugin/GreptimeDB.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
core/datacap-ui/public/static/images/plugin/H2.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Hdfs.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Hive.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Hologres.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Ignite.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Impala.png
Normal file
After Width: | Height: | Size: 245 KiB |
BIN
core/datacap-ui/public/static/images/plugin/IoTDB.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Kafka.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Kylin.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Kyuubi.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
core/datacap-ui/public/static/images/plugin/MatrixOne.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
core/datacap-ui/public/static/images/plugin/MonetDB.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
core/datacap-ui/public/static/images/plugin/MongoDB.png
Normal file
After Width: | Height: | Size: 194 KiB |
BIN
core/datacap-ui/public/static/images/plugin/MySQL.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Neo4j.png
Normal file
After Width: | Height: | Size: 192 KiB |
BIN
core/datacap-ui/public/static/images/plugin/OceanBase.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Oracle.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
core/datacap-ui/public/static/images/plugin/ParadeDB.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Phoenix.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Pinot.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
core/datacap-ui/public/static/images/plugin/PostgreSQL.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Presto.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
core/datacap-ui/public/static/images/plugin/QuestDB.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Redis.png
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
core/datacap-ui/public/static/images/plugin/ScyllaDB.png
Normal file
After Width: | Height: | Size: 189 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Snowflake.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
core/datacap-ui/public/static/images/plugin/SqlServer.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
core/datacap-ui/public/static/images/plugin/StarRocks.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
core/datacap-ui/public/static/images/plugin/TDengine.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Timescale.png
Normal file
After Width: | Height: | Size: 200 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Trino.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
core/datacap-ui/public/static/images/plugin/YDB.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
core/datacap-ui/public/static/images/plugin/Zookeeper.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
13
core/datacap-ui/src/components/ui/hover-card/HoverCard.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { HoverCardRoot, type HoverCardRootProps, useForwardProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<HoverCardRootProps>()
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(props)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<HoverCardRoot v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</HoverCardRoot>
|
||||||
|
</template>
|
@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import {
|
||||||
|
HoverCardContent,
|
||||||
|
type HoverCardContentProps,
|
||||||
|
HoverCardPortal,
|
||||||
|
useForwardProps,
|
||||||
|
} from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<HoverCardContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||||
|
{
|
||||||
|
sideOffset: 4,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<HoverCardPortal>
|
||||||
|
<HoverCardContent
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCardPortal>
|
||||||
|
</template>
|
@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { HoverCardTrigger, type HoverCardTriggerProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<HoverCardTriggerProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<HoverCardTrigger v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</HoverCardTrigger>
|
||||||
|
</template>
|
3
core/datacap-ui/src/components/ui/hover-card/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as HoverCard } from './HoverCard.vue'
|
||||||
|
export { default as HoverCardTrigger } from './HoverCardTrigger.vue'
|
||||||
|
export { default as HoverCardContent } from './HoverCardContent.vue'
|
@ -44,4 +44,6 @@ export default {
|
|||||||
invalidParam: 'If the parameter is invalid, check whether the parameter is correct',
|
invalidParam: 'If the parameter is invalid, check whether the parameter is correct',
|
||||||
role: 'Role',
|
role: 'Role',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
scheduler: 'Scheduler',
|
||||||
|
executor: 'Executor',
|
||||||
}
|
}
|
@ -1,7 +1,8 @@
|
|||||||
export default {
|
export default {
|
||||||
common: {
|
common: {
|
||||||
list: 'Dashboard List',
|
list: 'Dashboard List',
|
||||||
delete: 'Delete Dashboard'
|
delete: 'Delete Dashboard',
|
||||||
|
modify: 'Modify Dashboard'
|
||||||
},
|
},
|
||||||
tip: {
|
tip: {
|
||||||
deleteTip1: 'You are deleting a dashboard. This action permanently deletes the dashboard. Please be sure to confirm your actions before proceeding. ',
|
deleteTip1: 'You are deleting a dashboard. This action permanently deletes the dashboard. Please be sure to confirm your actions before proceeding. ',
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
export default {
|
export default {
|
||||||
common: {
|
common: {
|
||||||
|
list: 'Dataset List',
|
||||||
notSpecifiedTitle: 'Not Specified Title',
|
notSpecifiedTitle: 'Not Specified Title',
|
||||||
adhocDndTip: 'Drag the indicator dimension on the left to the corresponding position to query and render the data'
|
adhocDndTip: 'Drag the indicator dimension on the left to the corresponding position to query and render the data',
|
||||||
|
syncMode: 'Sync Mode',
|
||||||
|
syncModeManual: 'Manual',
|
||||||
|
syncModeTiming: 'Timing synchronization',
|
||||||
|
syncModeOutSync: 'Out Sync',
|
||||||
|
totalRows: 'Total Rows',
|
||||||
|
totalSize: 'Total Size',
|
||||||
|
complete: 'Complete',
|
||||||
|
failed: 'Failed',
|
||||||
|
stateOfStart: 'Start',
|
||||||
|
stateOfMetadata: 'Metadata State',
|
||||||
|
stateOfMetadataStarted: 'Metadata Started',
|
||||||
|
stateOfCreateTable: 'Create Table State'
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,5 +43,7 @@ export default {
|
|||||||
noData: '没有数据可以展示',
|
noData: '没有数据可以展示',
|
||||||
invalidParam: '参数无效,请检查参数是否填写正确',
|
invalidParam: '参数无效,请检查参数是否填写正确',
|
||||||
role: '权限',
|
role: '权限',
|
||||||
cancel: '取消'
|
cancel: '取消',
|
||||||
|
scheduler: '调度器',
|
||||||
|
executor: '执行器',
|
||||||
}
|
}
|
@ -1,7 +1,8 @@
|
|||||||
export default {
|
export default {
|
||||||
common: {
|
common: {
|
||||||
list: '仪表盘列表',
|
list: '仪表盘列表',
|
||||||
delete: '删除仪表盘'
|
delete: '删除仪表盘',
|
||||||
|
modify: '修改仪表盘'
|
||||||
},
|
},
|
||||||
tip: {
|
tip: {
|
||||||
deleteTip1: '您正在删除仪表板。此操作将永久删除仪表板。在继续操作之前,请务必确认您的操作。',
|
deleteTip1: '您正在删除仪表板。此操作将永久删除仪表板。在继续操作之前,请务必确认您的操作。',
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
export default {
|
export default {
|
||||||
common: {
|
common: {
|
||||||
|
list: '数据集列表',
|
||||||
notSpecifiedTitle: '未指定标题',
|
notSpecifiedTitle: '未指定标题',
|
||||||
adhocDndTip: '拖拽左侧指标|维度到相应位置即可查询并渲染数据'
|
adhocDndTip: '拖拽左侧指标|维度到相应位置即可查询并渲染数据',
|
||||||
|
syncMode: '同步模式',
|
||||||
|
syncModeManual: '手动',
|
||||||
|
syncModeTiming: '定时同步',
|
||||||
|
syncModeOutSync: '不同步',
|
||||||
|
totalRows: '总行数',
|
||||||
|
totalSize: '总大小',
|
||||||
|
complete: '完成',
|
||||||
|
failed: '失败',
|
||||||
|
stateOfStarted: '已启动',
|
||||||
|
stateOfMetadata: '元数据状态',
|
||||||
|
stateOfMetadataStarted: '元数据已启动',
|
||||||
|
stateOfCreateTable: '创建表状态',
|
||||||
}
|
}
|
||||||
}
|
}
|
9
core/datacap-ui/src/model/execute.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface ExecuteModel
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
content: string
|
||||||
|
env?: object
|
||||||
|
format?: string
|
||||||
|
limit?: number
|
||||||
|
mode?: string
|
||||||
|
}
|
@ -99,6 +99,24 @@ const createAdminRouter = (router: any) => {
|
|||||||
isRoot: false
|
isRoot: false
|
||||||
},
|
},
|
||||||
component: () => import('@/views/pages/admin/dashboard/DashboardHome.vue')
|
component: () => import('@/views/pages/admin/dashboard/DashboardHome.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'info',
|
||||||
|
path: 'dashboard/info/:id',
|
||||||
|
meta: {
|
||||||
|
title: 'common.dashboard',
|
||||||
|
isRoot: false
|
||||||
|
},
|
||||||
|
component: () => import('@/views/pages/admin/dashboard/DashboardInfo.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dataset',
|
||||||
|
path: 'dataset',
|
||||||
|
meta: {
|
||||||
|
title: 'common.dataset',
|
||||||
|
isRoot: false
|
||||||
|
},
|
||||||
|
component: () => import('@/views/pages/admin/dataset/DatasetHome.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
29
core/datacap-ui/src/services/execute.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { ExecuteModel } from '@/model/execute';
|
||||||
|
import { BaseService } from '@/services/base'
|
||||||
|
import { ResponseModel } from '@/model/response'
|
||||||
|
import { HttpUtils } from '@/utils/http'
|
||||||
|
|
||||||
|
const DEFAULT_PATH = '/api/v1/execute'
|
||||||
|
|
||||||
|
export class ExecuteService
|
||||||
|
extends BaseService
|
||||||
|
{
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
super(DEFAULT_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given configuration and returns a Promise that resolves to a ResponseModel.
|
||||||
|
*
|
||||||
|
* @param {ExecuteModel} configure - the configuration to be executed
|
||||||
|
* @param {any} cancelToken - a token to cancel the request
|
||||||
|
* @return {Promise<ResponseModel>} a Promise that resolves to a ResponseModel
|
||||||
|
*/
|
||||||
|
execute(configure: ExecuteModel, cancelToken: any): Promise<ResponseModel>
|
||||||
|
{
|
||||||
|
return new HttpUtils().post(`${DEFAULT_PATH}`, JSON.stringify(configure), cancelToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ExecuteService()
|
14
core/datacap-ui/src/services/report.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { BaseService } from '@/services/base'
|
||||||
|
|
||||||
|
const DEFAULT_PATH = '/api/v1/report'
|
||||||
|
|
||||||
|
export class ReportService
|
||||||
|
extends BaseService
|
||||||
|
{
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
super(DEFAULT_PATH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ReportService()
|
17
core/datacap-ui/src/views/components/echarts/DataUtils.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Returns an array of unique values extracted from the specified key in each object within the provided array.
|
||||||
|
*
|
||||||
|
* @param {string} key - The key to extract values from each object in the array.
|
||||||
|
* @param {any[]} columns - The array of objects to extract values from.
|
||||||
|
* @return {any[]} An array containing unique values extracted from the specified key in each object.
|
||||||
|
*/
|
||||||
|
export function getValueByKey(key: string, columns: []): any[]
|
||||||
|
{
|
||||||
|
const container: any[] = []
|
||||||
|
columns.forEach(column => {
|
||||||
|
if (container.indexOf(column[key]) === -1) {
|
||||||
|
container.push(column[key])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return container
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import { EchartsType } from '@/views/components/echarts/EchartsType'
|
||||||
|
|
||||||
|
export class EchartsConfigure
|
||||||
|
{
|
||||||
|
headers: [] | undefined
|
||||||
|
types: [] | undefined
|
||||||
|
columns: [] | undefined
|
||||||
|
type: EchartsType = EchartsType.LINE
|
||||||
|
}
|
250
core/datacap-ui/src/views/components/echarts/EchartsEditor.vue
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Modal v-model="visible"
|
||||||
|
placement="right"
|
||||||
|
:title="$t('common.visualization')"
|
||||||
|
:mask-closable="false"
|
||||||
|
:width="'90%'"
|
||||||
|
:transfer="false">
|
||||||
|
<Form :inline="true">
|
||||||
|
<FormItem :label="$t('common.name')"
|
||||||
|
:label-width="80">
|
||||||
|
<Input v-model="formState.name"/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem :label="$t('common.realtime')"
|
||||||
|
:label-width="80">
|
||||||
|
<Switch v-model="formState.realtime"/>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
<Layout v-if="configure">
|
||||||
|
<Layout>
|
||||||
|
<Content>
|
||||||
|
<EchartsPreview :key="referKey"
|
||||||
|
:height="'500px'"
|
||||||
|
:configure="chartOptions">
|
||||||
|
</EchartsPreview>
|
||||||
|
</Content>
|
||||||
|
<Sider style="background-color: #FFFFFF;"
|
||||||
|
hide-trigger>
|
||||||
|
<Form label-position="left"
|
||||||
|
:label-width="50">
|
||||||
|
<Card padding="0"
|
||||||
|
dis-hover
|
||||||
|
:bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<RadioGroup v-model="chartType"
|
||||||
|
type="button"
|
||||||
|
@on-change="handlerChangeValue('Type')">
|
||||||
|
<Space>
|
||||||
|
<Radio label="line">
|
||||||
|
<FontAwesomeIcon icon="chart-line">
|
||||||
|
</FontAwesomeIcon>
|
||||||
|
</Radio>
|
||||||
|
<Radio label="bar">
|
||||||
|
<FontAwesomeIcon icon="chart-bar">
|
||||||
|
</FontAwesomeIcon>
|
||||||
|
</Radio>
|
||||||
|
</Space>
|
||||||
|
</RadioGroup>
|
||||||
|
</template>
|
||||||
|
<Collapse v-if="chartType"
|
||||||
|
v-model="collapseValue"
|
||||||
|
accordion>
|
||||||
|
<Panel name="xAxis">
|
||||||
|
{{ $t('common.xAxis') }}
|
||||||
|
<template #content>
|
||||||
|
<FormItem :label="$t('common.column')">
|
||||||
|
<Select v-model="defaultConfigure.xAxis" @on-change="handlerChangeValue('xAxis')">
|
||||||
|
<Option v-for="value of configure.headers" :value="value" v-bind:key="value">{{ value }}</Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem :label="$t('common.type')">
|
||||||
|
<RadioGroup v-model="chartOptions.xAxis.type" type="button" size="small" @on-change="handlerChangeValue">
|
||||||
|
<Radio label="value">{{ $t('common.column') }}</Radio>
|
||||||
|
<Radio label="category">{{ $t('common.tag') }}</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormItem>
|
||||||
|
</template>
|
||||||
|
</Panel>
|
||||||
|
<Panel v-if="chartOptions.xAxis && !chartOptions.yAxis.disabled" disabled name="yAxis">
|
||||||
|
{{ $t('common.yAxis') }}
|
||||||
|
<template #content>
|
||||||
|
<FormItem label="Value">
|
||||||
|
<Select v-model="defaultConfigure.yAxis">
|
||||||
|
<Option v-for="value of configure.headers" :value="value" v-bind:key="value">{{ value }}</Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Type">
|
||||||
|
<RadioGroup v-model="chartOptions.yAxis.type" @on-change="handlerChangeValue">
|
||||||
|
<Radio label="value"></Radio>
|
||||||
|
<Radio label="category"></Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormItem>
|
||||||
|
</template>
|
||||||
|
</Panel>
|
||||||
|
<Panel v-if="chartOptions.xAxis" name="Series">
|
||||||
|
{{ $t('common.data') }}
|
||||||
|
<template #content>
|
||||||
|
<FormItem :label="$t('common.column')">
|
||||||
|
<Select v-model="defaultConfigure.series" @on-change="handlerChangeValue('Series')">
|
||||||
|
<Option v-for="value of configure.headers" :value="value" v-bind:key="value">{{ value }}</Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
</template>
|
||||||
|
</Panel>
|
||||||
|
</Collapse>
|
||||||
|
</Card>
|
||||||
|
</Form>
|
||||||
|
</Sider>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
<Result v-else
|
||||||
|
type="warning">
|
||||||
|
<template #desc>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
</template>
|
||||||
|
</Result>
|
||||||
|
<template #footer>
|
||||||
|
<Button type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
@click="handlerPublish">
|
||||||
|
{{ $t('common.publish') }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {ChartConfigure} from "@/components/editor/echarts/configure/ChartConfigure";
|
||||||
|
import {getValueByKey} from "./DataUtils";
|
||||||
|
import {getTimestamp} from "@/common/DateCommon";
|
||||||
|
import {SeriesConfigure} from "@/components/editor/echarts/configure/SeriesConfigure";
|
||||||
|
import {isEmpty} from "lodash";
|
||||||
|
import {EchartsConfigure} from "@/components/editor/echarts/EchartsConfigure";
|
||||||
|
import EchartsPreview from "@/components/editor/echarts/EchartsPreview.vue";
|
||||||
|
import {AxisConfigure} from "@/components/editor/echarts/configure/AxisConfigure";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/vue-fontawesome";
|
||||||
|
import ReportService from "@/services/admin/ReportService";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'EchartsEditor',
|
||||||
|
components: {FontAwesomeIcon, EchartsPreview},
|
||||||
|
props: {
|
||||||
|
isVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false
|
||||||
|
},
|
||||||
|
configure: {
|
||||||
|
type: EchartsConfigure,
|
||||||
|
default: () => null
|
||||||
|
},
|
||||||
|
sourceId: {
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
collapseValue: 'xAxis',
|
||||||
|
referKey: 0,
|
||||||
|
defaultConfigure: {
|
||||||
|
xAxis: '',
|
||||||
|
yAxis: '',
|
||||||
|
series: ''
|
||||||
|
},
|
||||||
|
chartOptions: null as ChartConfigure,
|
||||||
|
chartType: null,
|
||||||
|
formState: {name: null, realtime: null, type: 'QUERY', configure: null, source: {id: null}, query: null},
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created()
|
||||||
|
{
|
||||||
|
this.handlerInitialize();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlerInitialize()
|
||||||
|
{
|
||||||
|
this.chartOptions = new ChartConfigure();
|
||||||
|
},
|
||||||
|
handlerChangeValue(type: string)
|
||||||
|
{
|
||||||
|
this.referKey = getTimestamp();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'xAxis':
|
||||||
|
this.chartOptions.xAxis = new AxisConfigure();
|
||||||
|
this.chartOptions.xAxis.data = getValueByKey(this.defaultConfigure.xAxis, this.configure.columns);
|
||||||
|
this.chartOptions.xAxis.meta.column = this.defaultConfigure.xAxis;
|
||||||
|
this.chartOptions.yAxis = new AxisConfigure();
|
||||||
|
this.chartOptions.yAxis.type = 'value';
|
||||||
|
this.chartOptions.yAxis.data = getValueByKey(this.defaultConfigure.yAxis, this.configure.columns);
|
||||||
|
this.chartOptions.yAxis.disabled = true;
|
||||||
|
this.chartOptions.yAxis.meta.column = this.defaultConfigure.yAxis;
|
||||||
|
break;
|
||||||
|
case 'Series': {
|
||||||
|
const series: SeriesConfigure = new SeriesConfigure();
|
||||||
|
series.data = getValueByKey(this.defaultConfigure.series, this.configure.columns);
|
||||||
|
series.type = this.chartType;
|
||||||
|
series.meta.column = this.defaultConfigure.series;
|
||||||
|
this.chartOptions.series = [];
|
||||||
|
this.chartOptions.series.push(series);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'Type': {
|
||||||
|
if (this.chartOptions.series) {
|
||||||
|
this.chartOptions.series[0].type = this.chartType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.handlerSetDefaultValue();
|
||||||
|
},
|
||||||
|
handlerSetDefaultValue()
|
||||||
|
{
|
||||||
|
if (isEmpty(this.defaultConfigure.xAxis)) {
|
||||||
|
this.chartOptions.xAxis.data = null;
|
||||||
|
}
|
||||||
|
if (isEmpty(this.defaultConfigure.yAxis)) {
|
||||||
|
this.chartOptions.yAxis.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handlerPublish()
|
||||||
|
{
|
||||||
|
this.loading = true;
|
||||||
|
this.formState.configure = JSON.stringify(this.chartOptions);
|
||||||
|
this.formState.source.id = this.sourceId;
|
||||||
|
this.formState.query = this.query;
|
||||||
|
ReportService.saveOrUpdate(this.formState)
|
||||||
|
.then(response => {
|
||||||
|
if (response.status) {
|
||||||
|
this.$Message.success(this.$t('report.publishSuccess').replace('REPLACE_NAME', this.formState.name));
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => this.loading = false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
visible: {
|
||||||
|
get(): boolean
|
||||||
|
{
|
||||||
|
return this.isVisible;
|
||||||
|
},
|
||||||
|
set(value: boolean)
|
||||||
|
{
|
||||||
|
this.$emit('close', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.ivu-drawer-body {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
107
core/datacap-ui/src/views/components/echarts/EchartsPreview.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CircularLoading v-if="loading" :show="loading"/>
|
||||||
|
<div :style="{width: width, height: height, padding: '0'}" :id="key"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
|
||||||
|
import { ChartConfigure } from './configure/ChartConfigure'
|
||||||
|
import ReportService from '@/services/report'
|
||||||
|
import { getValueByKey } from './DataUtils'
|
||||||
|
import { SeriesConfigure } from './configure/SeriesConfigure'
|
||||||
|
import { ExecuteModel } from '@/model/execute'
|
||||||
|
import ExecuteService from '@/services/execute'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'EchartsPreview',
|
||||||
|
components: {CircularLoading},
|
||||||
|
props: {
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: () => '100%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: () => '300px'
|
||||||
|
},
|
||||||
|
configure: {
|
||||||
|
type: Object as () => ChartConfigure
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: Number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
width: 'handlerInitialize',
|
||||||
|
height: 'handlerInitialize'
|
||||||
|
},
|
||||||
|
created()
|
||||||
|
{
|
||||||
|
this.handlerInitialize()
|
||||||
|
},
|
||||||
|
data()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
key: null as string | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlerInitialize()
|
||||||
|
{
|
||||||
|
this.key = uuidv4()
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
if (this.key) {
|
||||||
|
const echartsContainer = document.getElementById(this.key)
|
||||||
|
const echartsChart = echarts.init(echartsContainer)
|
||||||
|
echartsChart.resize()
|
||||||
|
if (this.id) {
|
||||||
|
this.loading = true
|
||||||
|
ReportService.getById(this.id)
|
||||||
|
.then(response => {
|
||||||
|
if (response.status && response.data.realtime) {
|
||||||
|
if (response.data.source) {
|
||||||
|
const queryConfigure: ExecuteModel = {
|
||||||
|
name: response.data.source.id,
|
||||||
|
content: response.data.query,
|
||||||
|
format: 'JSON',
|
||||||
|
mode: 'REPORT'
|
||||||
|
}
|
||||||
|
ExecuteService.execute(queryConfigure, null)
|
||||||
|
.then(response => {
|
||||||
|
const configure: any = this.configure as ChartConfigure;
|
||||||
|
configure.xAxis.data = getValueByKey(configure.xAxis.meta.column, response.data.columns)
|
||||||
|
configure.yAxis.data = getValueByKey(configure.yAxis.meta.column, response.data.columns)
|
||||||
|
configure.series.forEach((item: SeriesConfigure) => {
|
||||||
|
const series: SeriesConfigure = item as SeriesConfigure
|
||||||
|
series.data = getValueByKey(series.meta.column, response.data.columns)
|
||||||
|
})
|
||||||
|
echartsChart.setOption(configure)
|
||||||
|
})
|
||||||
|
.finally(() => this.loading = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echartsChart.setOption(this.configure)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => this.loading = false)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echartsChart.setOption(this.configure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,5 @@
|
|||||||
|
export enum EchartsType
|
||||||
|
{
|
||||||
|
LINE = ('line'),
|
||||||
|
BAR = ('bar')
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
export class AxisConfigure
|
||||||
|
{
|
||||||
|
type = 'category'
|
||||||
|
data: any[] = []
|
||||||
|
disabled = false
|
||||||
|
meta = {column: null}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { SeriesConfigure } from '@/views/components/echarts/configure/SeriesConfigure'
|
||||||
|
import { AxisConfigure } from '@/views/components/echarts/configure/AxisConfigure'
|
||||||
|
import { TooltipConfigure } from '@/views/components/echarts/configure/TooltipConfigure'
|
||||||
|
|
||||||
|
export class ChartConfigure
|
||||||
|
{
|
||||||
|
xAxis: AxisConfigure | undefined
|
||||||
|
yAxis: AxisConfigure | undefined
|
||||||
|
series: Array<SeriesConfigure> | undefined
|
||||||
|
tooltip: TooltipConfigure = new TooltipConfigure()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function toOptions(options: ChartConfigure): any {
|
||||||
|
return JSON.stringify(options)
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { EchartsType } from '@/views/components/echarts/EchartsType'
|
||||||
|
|
||||||
|
export class SeriesConfigure
|
||||||
|
{
|
||||||
|
data: any[] = []
|
||||||
|
type: EchartsType = EchartsType.LINE
|
||||||
|
smooth = true
|
||||||
|
meta = {
|
||||||
|
column: ''
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export class TooltipConfigure {
|
||||||
|
trigger = 'axis'
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
class CommonUtils
|
||||||
|
{
|
||||||
|
getXAxis(type = 'value', items: any[])
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type: type,
|
||||||
|
data: items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CommonUtils()
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<AlertDialog :open="visible" :default-open="visible">
|
<AlertDialog :open="visible" :default-open="visible">
|
||||||
<AlertDialogContent>
|
<AlertDialogContent v-if="data">
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
{{ $t('dashboard.common.delete') + ' [ ' + data?.name + ' ]' }}
|
{{ $t('dashboard.common.delete') + ' [ ' + data.name + ' ]' }}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
<Alert variant="destructive" class="mt-3">
|
<Alert variant="destructive" class="mt-3">
|
||||||
@ -14,7 +14,7 @@
|
|||||||
{{ $t('dashboard.tip.deleteTip2') }}
|
{{ $t('dashboard.tip.deleteTip2') }}
|
||||||
</Alert>
|
</Alert>
|
||||||
<Alert class="mt-3">
|
<Alert class="mt-3">
|
||||||
{{ $t('dashboard.tip.deleteTip3').replace('$NAME', data?.name) }}
|
{{ $t('dashboard.tip.deleteTip3').replace('$NAME', data.name) }}
|
||||||
<Input v-model="inputValue" class="mt-3"/>
|
<Input v-model="inputValue" class="mt-3"/>
|
||||||
</Alert>
|
</Alert>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<Button variant="outline" @click="handlerCancel">
|
<Button variant="outline" @click="handlerCancel">
|
||||||
{{ $t('common.cancel') }}
|
{{ $t('common.cancel') }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button :disabled="inputValue !== data?.name || loading" @click="handlerDelete">
|
<Button :disabled="inputValue !== data.name || loading" @click="handlerDelete">
|
||||||
<Loader2 v-if="loading" class="w-full justify-center animate-spin"/>
|
<Loader2 v-if="loading" class="w-full justify-center animate-spin"/>
|
||||||
{{ $t('dashboard.common.delete') }}
|
{{ $t('dashboard.common.delete') }}
|
||||||
</Button>
|
</Button>
|
||||||
@ -58,7 +58,8 @@ export default defineComponent({
|
|||||||
default: () => false
|
default: () => false
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
type: Object as () => DashboardModel
|
type: Object as () => DashboardModel | null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -77,7 +78,7 @@ export default defineComponent({
|
|||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
inputValue: null
|
inputValue: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -19,6 +19,12 @@
|
|||||||
<Cog :size="20"/>
|
<Cog :size="20"/>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<RouterLink :to="`/admin/dashboard/info/${item.id}`" target="_blank" class="flex items-center">
|
||||||
|
<Pencil :size="15" class="mr-1"/>
|
||||||
|
{{ $t('dashboard.common.modify') }}
|
||||||
|
</RouterLink>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem @click="handlerDelete(true, item)">
|
<DropdownMenuItem @click="handlerDelete(true, item)">
|
||||||
<Trash :size="15" class="mr-1"/>
|
<Trash :size="15" class="mr-1"/>
|
||||||
{{ $t('dashboard.common.delete') }}
|
{{ $t('dashboard.common.delete') }}
|
||||||
@ -43,20 +49,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Cog, Loader2, Trash } from 'lucide-vue-next';
|
import { Cog, Loader2, Pencil, Trash } from 'lucide-vue-next'
|
||||||
import DashboardService from '@/services/dashboard'
|
import DashboardService from '@/services/dashboard'
|
||||||
import { FilterModel } from '@/model/filter'
|
import { FilterModel } from '@/model/filter'
|
||||||
import { PaginationModel, PaginationRequest } from '@/model/pagination'
|
import { PaginationModel, PaginationRequest } from '@/model/pagination'
|
||||||
import { DashboardModel } from '@/model/dashboard'
|
import { DashboardModel } from '@/model/dashboard'
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||||
import DashboardDelete from '@/views/pages/admin/dashboard/DashboardDelete.vue';
|
import DashboardDelete from '@/views/pages/admin/dashboard/DashboardDelete.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DashboardHome',
|
name: 'DashboardHome',
|
||||||
components: {
|
components: {
|
||||||
DashboardDelete,
|
DashboardDelete,
|
||||||
DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuContent, DropdownMenuTrigger, DropdownMenu,
|
DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuContent, DropdownMenuTrigger, DropdownMenu,
|
||||||
Loader2, Cog, Trash,
|
Loader2, Cog, Trash, Pencil,
|
||||||
CardContent, CardHeader, CardTitle, Card
|
CardContent, CardHeader, CardTitle, Card
|
||||||
},
|
},
|
||||||
setup()
|
setup()
|
||||||
@ -94,7 +100,7 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
.finally(() => this.loading = false)
|
.finally(() => this.loading = false)
|
||||||
},
|
},
|
||||||
handlerDelete(opened: boolean, data: any)
|
handlerDelete(opened: boolean, data: DashboardModel | null)
|
||||||
{
|
{
|
||||||
this.deleteVisible = opened
|
this.deleteVisible = opened
|
||||||
this.dataInfo = data
|
this.dataInfo = data
|
||||||
|
@ -1,12 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
<CircularLoading v-if="loading" :show="loading" class="mt-20"></CircularLoading>
|
||||||
|
<div v-else>
|
||||||
|
<DashboardEditor :elements="nodes" :source-configure="sourceConfigure"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
|
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
|
||||||
|
import DashboardService from '@/services/dashboard'
|
||||||
|
import { ToastUtils } from '@/utils/toast'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { DashboardModel } from '@/model/dashboard'
|
||||||
|
import DashboardEditor from '@/views/pages/admin/dashboard/components/DashboardEditor.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DashboardInfo'
|
name: 'DashboardInfo',
|
||||||
|
components: {DashboardEditor, CircularLoading},
|
||||||
|
data()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
saving: false,
|
||||||
|
configure: null as DashboardModel | null,
|
||||||
|
nodes: [],
|
||||||
|
sourceConfigure: null as DashboardModel | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created()
|
||||||
|
{
|
||||||
|
this.handlerInitialize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlerInitialize()
|
||||||
|
{
|
||||||
|
this.loading = true
|
||||||
|
const router = useRouter()
|
||||||
|
const params = router.currentRoute.value.params
|
||||||
|
DashboardService.getById(params['id'] as unknown as number)
|
||||||
|
.then(response => {
|
||||||
|
if (response.status) {
|
||||||
|
const configure = JSON.parse(response.data.configure)
|
||||||
|
configure.nodes?.forEach((node: any) => {
|
||||||
|
this.nodes.push({id: node.id, type: node.type, label: node.label, position: node.position, data: node.data})
|
||||||
|
})
|
||||||
|
this.sourceConfigure = response.data
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ToastUtils.error(response.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => this.loading = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CircularLoading v-if="loading" :show="loading"/>
|
||||||
|
<Card v-else class="mt-3" v-for="node in data">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{{ node.name }}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div :draggable="true" @dragstart="onDragStart($event, node)">
|
||||||
|
<EchartsPreview :key="node.id" :height="'200px'" :id="node.id" :configure="JSON.parse(node.configure)"/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import '../style.css'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import ReportService from '@/services/report'
|
||||||
|
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
|
||||||
|
import { FilterModel } from '@/model/filter'
|
||||||
|
import EchartsPreview from '@/views/components/echarts/EchartsPreview.vue'
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DashboardChart',
|
||||||
|
components: {
|
||||||
|
CardContent, CardHeader, CardTitle, Card,
|
||||||
|
EchartsPreview,
|
||||||
|
CircularLoading
|
||||||
|
},
|
||||||
|
setup()
|
||||||
|
{
|
||||||
|
const onDragStart = (event: { dataTransfer: { setData: (arg0: string, arg1: any) => void; effectAllowed: string; }; }, node: any) => {
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.setData('application/vueflow', JSON.stringify(node))
|
||||||
|
event.dataTransfer.effectAllowed = 'move'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
onDragStart
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created()
|
||||||
|
{
|
||||||
|
this.loading = true
|
||||||
|
const filter: FilterModel = new FilterModel()
|
||||||
|
ReportService.getAll(filter)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status) {
|
||||||
|
this.data = response.data.content
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => this.loading = false)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full h-full" :style="{width: width + 'px', height: height + 'px'}">
|
||||||
|
<VueFlow :default-viewport="{ zoom: 1.5 }" :min-zoom="0.2" :max-zoom="4" @dragover="onDragOver">
|
||||||
|
<template #node-resizable="{ data }">
|
||||||
|
<DashboardNode :configure="JSON.parse(data.configure)" :id="data.id"/>
|
||||||
|
</template>
|
||||||
|
<Controls/>
|
||||||
|
<Background/>
|
||||||
|
<Panel position="top-right">
|
||||||
|
<Space>
|
||||||
|
<Tooltip :content="$t('pipeline.resetTransform')">
|
||||||
|
<Button type="primary"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
@click="resetTransform">
|
||||||
|
<FontAwesomeIcon icon="rotate"/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip :content="$t('common.save')">
|
||||||
|
<Button type="primary"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
@click="saveConfigure(configure, true)">
|
||||||
|
<FontAwesomeIcon icon="save"/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
</Panel>
|
||||||
|
</VueFlow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, nextTick, ref, watch } from 'vue'
|
||||||
|
import { Panel, useVueFlow, VueFlow } from '@vue-flow/core'
|
||||||
|
import { Controls } from '@vue-flow/controls'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { Background } from '@vue-flow/background'
|
||||||
|
import DashboardNode from '@/views/pages/admin/dashboard/components/DashboardNode.vue'
|
||||||
|
import DashboardChart from '@/views/pages/admin/dashboard/components/DashboardChart.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DashboardEditor',
|
||||||
|
components: {
|
||||||
|
DashboardNode, DashboardChart,
|
||||||
|
Background, VueFlow, Controls, Panel
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
elements: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ref([])
|
||||||
|
},
|
||||||
|
sourceConfigure: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, {emit})
|
||||||
|
{
|
||||||
|
const configureVisible = ref(false);
|
||||||
|
const configure = ref({
|
||||||
|
id: props.sourceConfigure ? props.sourceConfigure.id : null,
|
||||||
|
name: props.sourceConfigure ? props.sourceConfigure.name : null,
|
||||||
|
configure: null,
|
||||||
|
version: 'V1',
|
||||||
|
reports: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const {findNode, onConnect, addEdges, addNodes, project, vueFlowRef, setTransform, toObject} = useVueFlow({nodes: []})
|
||||||
|
|
||||||
|
props.elements.forEach((item) => {
|
||||||
|
const newNode = {id: item.id, position: item.position, label: item.label, type: 'resizable', data: item.data}
|
||||||
|
addNodes([newNode])
|
||||||
|
})
|
||||||
|
|
||||||
|
onConnect((params: any) => addEdges(params))
|
||||||
|
|
||||||
|
const onDrop = (event: { dataTransfer: { getData: (arg0: string) => any; }; clientX: number; clientY: number; }) => {
|
||||||
|
const data = JSON.parse(event.dataTransfer?.getData('application/vueflow'));
|
||||||
|
const {left, top} = vueFlowRef.value.getBoundingClientRect();
|
||||||
|
const position = project({x: event.clientX - left, y: event.clientY - top});
|
||||||
|
const newNode = {id: `${uuidv4()}`, position, label: `${data.name}`, type: 'resizable', data: data}
|
||||||
|
addNodes([newNode])
|
||||||
|
nextTick(() => {
|
||||||
|
const node = findNode(newNode.id)
|
||||||
|
const stop = watch(
|
||||||
|
() => node.dimensions,
|
||||||
|
(dimensions) => {
|
||||||
|
if (dimensions.width > 0 && dimensions.height > 0) {
|
||||||
|
node.position = {x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2}
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{deep: true, flush: 'post'}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragOver = (event: { preventDefault: () => void; dataTransfer: { dropEffect: string; }; }) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.dropEffect = 'move'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetTransform = () => {
|
||||||
|
return setTransform({x: 0, y: 0, zoom: 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveConfigure = (configure: any, opened: boolean) => {
|
||||||
|
configureVisible.value = opened;
|
||||||
|
if (!opened) {
|
||||||
|
const obj = toObject()
|
||||||
|
configure.configure = JSON.stringify(obj);
|
||||||
|
obj.nodes.forEach((item: { data: { id: any } }) => {
|
||||||
|
configure.reports.push({id: item.data.id})
|
||||||
|
})
|
||||||
|
emit('onCommit', configure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = window.innerHeight - 120
|
||||||
|
const width = window.innerWidth - 35
|
||||||
|
|
||||||
|
return {
|
||||||
|
onDrop,
|
||||||
|
onDragOver,
|
||||||
|
resetTransform,
|
||||||
|
saveConfigure,
|
||||||
|
configure,
|
||||||
|
configureVisible,
|
||||||
|
height,
|
||||||
|
width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NodeResizer min-width="100" min-height="30"/>
|
||||||
|
<EchartsPreview :width="'250px'" :height="'200px'" :id="id" :configure="configure"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { NodeResizer } from '@vue-flow/node-resizer'
|
||||||
|
import EchartsPreview from '@/views/components/echarts/EchartsPreview.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DashboardNode',
|
||||||
|
components: {NodeResizer, EchartsPreview},
|
||||||
|
props: {
|
||||||
|
configure: {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: Number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -50,10 +50,12 @@ import { defineComponent } from 'vue'
|
|||||||
import { GridItem, GridLayout } from 'vue3-grid-layout-next'
|
import { GridItem, GridLayout } from 'vue3-grid-layout-next'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import VisualView from '@/views/components/visual/VisualView.vue'
|
import VisualView from '@/views/components/visual/VisualView.vue'
|
||||||
|
import EchartsPreview from '@/views/components/echarts/EchartsPreview.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DashboardView',
|
name: 'DashboardView',
|
||||||
components: {
|
components: {
|
||||||
|
EchartsPreview,
|
||||||
VisualView,
|
VisualView,
|
||||||
CardContent, CardHeader, CardTitle, Card,
|
CardContent, CardHeader, CardTitle, Card,
|
||||||
GridItem, GridLayout
|
GridItem, GridLayout
|
||||||
|
19
core/datacap-ui/src/views/pages/admin/dashboard/style.css
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
@import '@vue-flow/core/dist/style.css';
|
||||||
|
@import '@vue-flow/core/dist/theme-default.css';
|
||||||
|
@import '@vue-flow/controls/dist/style.css';
|
||||||
|
@import '@vue-flow/node-resizer/dist/style.css';
|
||||||
|
|
||||||
|
.dndflow {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dndflow .nodes > * {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: grab;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
118
core/datacap-ui/src/views/pages/admin/dataset/DatasetHome.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<Card>
|
||||||
|
<CardHeader class="border-b p-4">
|
||||||
|
<CardTitle>{{ $t('dataset.common.list') }}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<TableCommon :loading="loading" :columns="headers" :data="data" :pagination="pagination" @changePage="handlerChangePage">
|
||||||
|
<template #source="{row}">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger as-child>
|
||||||
|
<Avatar size="sm">
|
||||||
|
<AvatarImage :src="'/static/images/plugin/' + row?.source.type + '.png'" :alt="row?.source.type"/>
|
||||||
|
<AvatarFallback>{{ row?.source.type }}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent align="center">{{ row?.source.type }}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</template>
|
||||||
|
<template #syncMode="{ row }">
|
||||||
|
<Badge v-if="row?.syncMode === 'MANUAL'">{{ $t('dataset.common.syncModeManual') }}</Badge>
|
||||||
|
<Badge v-else-if="row?.syncMode === 'TIMING'">{{ $t('dataset.common.syncModeTiming') }}</Badge>
|
||||||
|
<Badge v-else-if="row?.syncMode === 'OUT_SYNC'">{{ $t('dataset.common.syncModeOutSync') }}</Badge>
|
||||||
|
</template>
|
||||||
|
<template #state="{ row }">
|
||||||
|
<HoverCard>
|
||||||
|
<HoverCardTrigger>{{ getState(row?.state) }}</HoverCardTrigger>
|
||||||
|
<HoverCardContent>
|
||||||
|
<DatasetState class="mt-[25px]" :states="row?.state"/>
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
|
</template>
|
||||||
|
</TableCommon>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import TableCommon from '@/views/components/table/TableCommon.vue'
|
||||||
|
import { FilterModel } from '@/model/filter'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { PaginationModel, PaginationRequest } from '@/model/pagination'
|
||||||
|
import { createHeaders } from './DatasetUtils'
|
||||||
|
import DatasetService from '@/services/dataset'
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import DatasetState from '@/views/pages/admin/dataset/components/DatasetState.vue'
|
||||||
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DatasetHome',
|
||||||
|
components: {
|
||||||
|
HoverCardContent, HoverCardTrigger, HoverCard,
|
||||||
|
DatasetState,
|
||||||
|
Badge,
|
||||||
|
AvatarFallback, AvatarImage, Avatar,
|
||||||
|
TooltipTrigger, TooltipProvider, TooltipContent, Tooltip,
|
||||||
|
TableCommon,
|
||||||
|
CardContent, CardHeader, CardTitle, Card
|
||||||
|
},
|
||||||
|
setup()
|
||||||
|
{
|
||||||
|
const filter: FilterModel = new FilterModel()
|
||||||
|
const headers = createHeaders(useI18n())
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter,
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
pagination: {} as PaginationModel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created()
|
||||||
|
{
|
||||||
|
this.handlerInitialize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlerInitialize()
|
||||||
|
{
|
||||||
|
this.loading = true
|
||||||
|
DatasetService.getAll(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()
|
||||||
|
},
|
||||||
|
getState(state: Array<any> | null): string | null
|
||||||
|
{
|
||||||
|
if (state && state.length > 0) {
|
||||||
|
return state[state.length - 1]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Generates headers for a table based on i18n translations.
|
||||||
|
*
|
||||||
|
* @param {any} i18n - the internationalization object for translations
|
||||||
|
* @return {Array} an array of header objects for the table
|
||||||
|
*/
|
||||||
|
const createHeaders = (i18n: any) => {
|
||||||
|
return [
|
||||||
|
{key: 'id', hidden: true, header: i18n.t('common.id'), width: 80},
|
||||||
|
{key: 'name', hidden: true, header: i18n.t('common.name'), width: 100},
|
||||||
|
{key: 'description', hidden: true, header: i18n.t('common.description'), width: 200},
|
||||||
|
{key: 'source', hidden: true, header: i18n.t('common.source'), slot: 'source', width: 100},
|
||||||
|
{key: 'syncMode', hidden: true, header: i18n.t('dataset.common.syncMode'), slot: 'syncMode', width: 80},
|
||||||
|
{key: 'scheduler', hidden: true, header: i18n.t('common.scheduler'), width: 80},
|
||||||
|
{key: 'executor', hidden: true, header: i18n.t('common.executor'), width: 80},
|
||||||
|
{key: 'state', hidden: true, header: i18n.t('common.state'), slot: 'state', width: 80, class: 'text-center'},
|
||||||
|
{key: 'totalRows', hidden: true, header: i18n.t('dataset.common.totalRows')},
|
||||||
|
{key: 'totalSize', hidden: true, header: i18n.t('dataset.common.totalSize')},
|
||||||
|
{key: 'createTime', hidden: true, header: i18n.t('common.createTime')},
|
||||||
|
{key: 'updateTime', hidden: true, header: i18n.t('common.updateTime')},
|
||||||
|
{key: 'action', hidden: true, header: i18n.t('common.action'), slot: 'action', width: 80, class: 'text-right'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createHeaders
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<div v-for="state in states" :key="state">
|
||||||
|
<Alert class="flex items-center" v-if="state === 'START'">
|
||||||
|
<AlertTitle class="flex-grow">
|
||||||
|
{{ $t('dataset.common.stateOfStarted') }}
|
||||||
|
</AlertTitle>
|
||||||
|
<AlertDescription class="ml-4">
|
||||||
|
{{ $t('dataset.complete') }}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
<Alert class="flex items-center mt-2" v-else-if="state.startsWith('METADATA_')">
|
||||||
|
<AlertTitle class="flex-grow">
|
||||||
|
{{ $t('dataset.common.stateOfMetadata') }}
|
||||||
|
</AlertTitle>
|
||||||
|
<AlertDescription class="ml-4">
|
||||||
|
<span v-if="state.endsWith('SUCCESS')" class="content">
|
||||||
|
{{ $t('dataset.common.complete') }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="state.endsWith('FAILED')" class="content">
|
||||||
|
{{ $t('dataset.common.failed') }}
|
||||||
|
</span>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
<Alert class="flex items-center mt-2" v-else-if="state.startsWith('TABLE_')">
|
||||||
|
<AlertTitle class="flex-grow">
|
||||||
|
{{ $t('dataset.common.stateOfCreateTable') }}
|
||||||
|
</AlertTitle>
|
||||||
|
<AlertDescription class="ml-4">
|
||||||
|
<span v-if="state.endsWith('SUCCESS')" class="content">
|
||||||
|
{{ $t('dataset.common.complete') }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="state.endsWith('FAILED')" class="content">
|
||||||
|
{{ $t('dataset.common.failed') }}
|
||||||
|
</span>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { Alert, AlertTitle } from '@/components/ui/alert'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DatasetState',
|
||||||
|
components: {AlertTitle, Alert},
|
||||||
|
props: {
|
||||||
|
states: {
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|