blob: 4564e2bc826aec00dde3e853f61b9ddaa5607604 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<resource-layout>
<template #left>
<a-spin :spinning="loading">
<a-card :bordered="false">
<a-input-search
size="default"
:placeholder="$t('label.search')"
v-model:value="searchQuery"
@search="onSearch"
>
<template #prefix><search-outlined /></template>
</a-input-search>
<a-spin :spinning="loadingSearch">
<a-tree
showLine
v-if="treeViewData.length > 0"
class="list-tree-view"
:treeData="treeViewData"
:loadData="onLoadData"
:expandAction="false"
:showIcon="true"
:selectedKeys="defaultSelected"
:checkStrictly="true"
@select="onSelect"
@expand="onExpand"
:expandedKeys="arrExpand">
<template #icon="{data}">
<block-outlined v-if="data.isLeaf" />
</template>
</a-tree>
</a-spin>
</a-card>
</a-spin>
</template>
<template #right>
<a-spin :spinning="detailLoading">
<a-card
class="spin-content"
:bordered="true"
style="width:100%">
<a-tabs
style="width: 100%"
:animated="false"
:defaultActiveKey="tabs[0].name"
@change="onTabChange" >
<template v-for="tab in tabs" :tab="$t('label.' + tab.name)" :key="tab.name">
<a-tab-pane :tab="$t('label.' + tab.name)" :key="tab.name" v-if="checkShowTabDetail(tab)">
<keep-alive>
<component
v-if="tab.resourceType"
:is="tab.component"
:resource="resource"
:resourceType="tab.resourceType"
:loading="loading"
:tab="tabActive"
:bordered="false" />
<component
v-else
:is="tab.component"
:resource="resource"
:items="items"
:tab="tabActive"
:loading="loading"
:bordered="false" />
</keep-alive>
</a-tab-pane>
</template>
</a-tabs>
</a-card>
</a-spin>
</template>
</resource-layout>
</template>
<script>
import store from '@/store'
import { api } from '@/api'
import DetailsTab from '@/components/view/DetailsTab'
import ResourceView from '@/components/view/ResourceView'
import ResourceLayout from '@/layouts/ResourceLayout'
import eventBus from '@/config/eventBus'
export default {
name: 'TreeView',
components: {
ResourceLayout,
ResourceView
},
props: {
treeData: {
type: Array,
required: true
},
treeSelected: {
type: Object,
required: true
},
tabs: {
type: Array,
default () {
return [{
name: 'details',
component: DetailsTab
}]
}
},
loadedKeys: {
type: Array,
default () {
return []
}
},
loading: {
type: Boolean,
default: false
},
treeStore: {
type: Object,
default () {
return {}
}
},
treeDeletedKey: {
type: String,
default: null
}
},
provide: function () {
return {
parentFetchData: null,
parentToggleLoading: null
}
},
data () {
return {
detailLoading: false,
loadingSearch: false,
tabActive: 'details',
selectedTreeKey: '',
resource: {},
defaultSelected: [],
treeVerticalData: [],
treeViewData: [],
oldTreeViewData: [],
apiList: '',
apiChildren: '',
apiDetail: '',
metaName: '',
page: 1,
pageSize: 20,
items: [],
showSetting: false,
oldSearchQuery: '',
searchQuery: '',
arrExpand: [],
rootKey: ''
}
},
created: function () {
this.metaName = this.$route.meta.name
this.apiList = this.$route.meta.permission[0] ? this.$route.meta.permission[0] : ''
this.apiChildren = this.$route.meta.permission[1] ? this.$route.meta.permission[1] : ''
eventBus.on('refresh-domain-icon', () => {
this.getDetailResource(this.selectedTreeKey)
})
},
watch: {
loading () {
this.detailLoading = this.loading
this.treeViewData = []
this.arrExpand = []
if (!this.loading) {
this.treeViewData = this.treeData.slice()
this.treeVerticalData = this.treeData.slice()
if (this.treeViewData.length > 0) {
this.oldTreeViewData = this.treeViewData.slice()
this.rootKey = this.treeViewData[0].key
}
}
},
treeSelected: {
deep: true,
handler () {
if (Object.keys(this.treeSelected).length === 0) {
return
}
const resourceKeys = Object.keys(this.resource)
if (resourceKeys.length > 0) {
this.selectedTreeKey = this.resource.key
this.$emit('change-resource', this.resource)
if (resourceKeys.filter(x => x.endsWith('limit')).length === 0) {
setTimeout(() => { this.getDetailResource(this.resource.id) })
}
// set default expand
if (this.defaultSelected.length > 1) {
const arrSelected = this.defaultSelected
this.defaultSelected = []
this.defaultSelected.push(arrSelected[0])
}
return
}
this.resource = this.treeSelected
this.resource = this.createResourceData(this.resource)
this.selectedTreeKey = this.treeSelected.key
this.defaultSelected.push(this.selectedTreeKey)
// set default expand
if (this.defaultSelected.length > 1) {
const arrSelected = this.defaultSelected
this.defaultSelected = []
this.defaultSelected.push(arrSelected[0])
}
}
},
treeVerticalData: {
deep: true,
handler () {
if (!this.treeStore.isExpand) {
return
}
if (this.treeStore.expands && this.treeStore.expands.length > 0) {
for (const expandKey of this.treeStore.expands) {
if (this.arrExpand.includes(expandKey)) {
continue
}
const keyVisible = this.treeVerticalData.findIndex(item => item.key === expandKey)
if (keyVisible > -1) this.arrExpand.push(expandKey)
}
}
}
}
},
methods: {
onLoadData (treeNode) {
if (this.searchQuery !== '' && treeNode.eventKey !== this.rootKey) {
return new Promise(resolve => {
resolve()
})
}
const params = {
listAll: true,
id: treeNode.eventKey,
showicon: true
}
return new Promise(resolve => {
api(this.apiChildren, params).then(json => {
const dataResponse = this.getResponseJsonData(json)
const dataGenerate = this.generateTreeData(dataResponse)
treeNode.dataRef.children = dataGenerate
if (this.treeVerticalData.length === 0) {
this.treeVerticalData = this.treeViewData
}
this.treeViewData = [...this.treeViewData]
this.oldTreeViewData = this.treeViewData
for (let i = 0; i < dataGenerate.length; i++) {
const resource = this.treeVerticalData.filter(item => item.id === dataGenerate[i].id)
if (!resource || resource.length === 0) {
this.treeVerticalData.push(dataGenerate[i])
} else {
this.treeVerticalData.filter((item, index) => {
if (item.id === dataGenerate[i].id) {
// replace all value of tree data
Object.keys(dataGenerate[i]).forEach((value, idx) => {
this.treeVerticalData[index][value] = dataGenerate[i][value]
})
}
})
}
}
this.handleSelectResource(treeNode.eventKey)
resolve()
})
})
},
async handleSelectResource (treeKey) {
if (this.treeDeletedKey && this.treeDeletedKey === this.treeStore?.treeSelected?.key) {
const treeSelectedKey = this.treeStore.treeSelected.parentdomainid
if (treeSelectedKey === this.rootKey) {
this.resource = this.treeVerticalData[0]
this.selectedTreeKey = treeSelectedKey
this.defaultSelected = [treeSelectedKey]
this.$emit('change-resource', this.resource)
await this.setTreeStore(false, false, this.resource)
return
}
const resourceIndex = await this.treeVerticalData.findIndex(item => item.id === treeSelectedKey)
if (resourceIndex > -1) {
const resource = await this.getDetailResource(treeSelectedKey)
this.resource = await this.createResourceData(resource)
this.selectedTreeKey = treeSelectedKey
this.defaultSelected = [treeSelectedKey]
this.$emit('change-resource', this.resource)
await this.setTreeStore(false, false, this.resource)
return
}
}
const treeSelectedKey = this.treeStore.treeSelected.key
const resourceIndex = await this.treeVerticalData.findIndex(item => item.id === treeSelectedKey)
if (resourceIndex > -1) {
this.selectedTreeKey = treeSelectedKey
this.defaultSelected = [treeSelectedKey]
this.resource = this.treeStore.treeSelected
this.$emit('change-resource', this.resource)
}
},
async onSelect (selectedKeys, event) {
if (!event.selected) {
return
}
// check item tree selected, set selectedTreeKey
if (selectedKeys && selectedKeys[0]) {
this.selectedTreeKey = selectedKeys[0]
}
this.defaultSelected = []
this.defaultSelected.push(this.selectedTreeKey)
const resource = await this.getDetailResource(this.selectedTreeKey)
this.resource = await this.createResourceData(resource)
const index = this.treeVerticalData.findIndex(item => item.key === this.selectedTreeKey)
this.treeVerticalData[index] = this.resource
this.$emit('change-resource', this.resource)
await this.setTreeStore(false, false, this.resource)
},
onExpand (treeExpand) {
const treeStore = this.treeStore
this.arrExpand = treeExpand
treeStore.isExpand = true
treeStore.expands = this.arrExpand
treeStore.treeSelected = this.resource
this.$emit('change-tree-store', treeStore)
},
onSearch (value) {
if (this.searchQuery === '' && this.oldSearchQuery === '') {
return
}
this.searchQuery = value
this.newTreeData = this.treeViewData
this.treeVerticalData = this.newTreeData
// set parameter for the request
const params = {}
params.listall = true
// Check the search query to set params and variables using reset data
if (this.searchQuery !== '') {
this.oldSearchQuery = this.searchQuery
params.keyword = this.searchQuery
} else if (this.metaName === 'domain') {
this.oldSearchQuery = ''
params.id = this.$store.getters.userInfo.domainid
}
this.arrExpand = []
this.treeViewData = []
this.loadingSearch = true
this.$emit('change-tree-store', {})
api(this.apiList, params).then(json => {
const listDomains = this.getResponseJsonData(json)
this.treeVerticalData = this.treeVerticalData.concat(listDomains)
if (!listDomains || listDomains.length === 0) {
return
}
if (listDomains[0].id === this.rootKey) {
const rootDomain = this.generateTreeData(listDomains)
this.treeViewData = rootDomain
this.defaultSelected = []
this.defaultSelected.push(this.treeViewData[0].key)
this.resource = this.treeViewData[0]
this.$emit('change-resource', this.resource)
return
}
this.recursiveTreeData(listDomains)
if (this.treeViewData && this.treeViewData[0]) {
this.defaultSelected = []
this.defaultSelected.push(this.treeViewData[0].key)
this.resource = this.treeViewData[0]
this.$emit('change-resource', this.resource)
}
// check treeViewData, set to expand first children
if (this.treeViewData &&
this.treeViewData[0] &&
this.treeViewData[0].children &&
this.treeViewData[0].children.length > 0
) {
this.arrExpand.push(this.treeViewData[0].children[0].key)
}
}).finally(() => {
this.loadingSearch = false
})
},
onTabChange (key) {
this.tabActive = key
},
setTreeStore (arrExpand, isExpand, resource) {
const treeStore = this.treeStore
if (arrExpand) treeStore.expands = arrExpand
if (isExpand) treeStore.isExpand = true
if (resource) treeStore.treeSelected = resource
this.$emit('change-tree-store', treeStore)
},
getDetailResource (selectedKey) {
return new Promise(resolve => {
// set api name and parameter
const apiName = this.$route.meta.permission[0]
const params = {}
// set id to parameter
params.id = selectedKey
params.listAll = true
params.showicon = true
params.page = 1
params.pageSize = 1
this.detailLoading = true
api(apiName, params).then(json => {
const jsonResponse = this.getResponseJsonData(json)
resolve(jsonResponse[0])
}).catch(() => {
resolve()
}).finally(() => {
this.detailLoading = false
})
})
},
getResponseJsonData (json) {
let responseName
let objectName
let hasJobId = false
for (const key in json) {
if (key.includes('response')) {
for (const res in json[key]) {
if (res === 'jobid') {
hasJobId = true
break
}
}
responseName = key
break
}
}
for (const key in json[responseName]) {
if (key === 'count') {
continue
}
objectName = key
break
}
if (hasJobId) {
return {}
}
return json[responseName][objectName]
},
checkShowTabDetail (tab) {
if ('show' in tab) {
return tab.show(this.resource, this.$route, store.getters.userInfo)
}
// get permission from the route
const permission = tab.permission ? tab.permission[0] : ''
// check permission not exists
if (!permission || permission === '') {
return true
}
// Check the permissions to see the tab for a user
if (!Object.prototype.hasOwnProperty.call(store.getters.apis, permission)) {
return false
}
return true
},
generateTreeData (jsonData) {
if (!jsonData || jsonData.length === 0) {
return []
}
for (let i = 0; i < jsonData.length; i++) {
jsonData[i] = this.createResourceData(jsonData[i])
}
return jsonData
},
createResourceData (resource) {
if (!resource || Object.keys(resource) === 0) {
return {}
}
Object.keys(resource).forEach((value, idx) => {
if (resource[value] === 'Unlimited') {
resource.value = '-1'
}
})
resource.title = resource.name
resource.key = resource.id
if (resource?.icon) {
resource.resourceIcon = resource.icon
delete resource.icon
}
if (!resource.haschild) {
resource.isLeaf = true
}
return resource
},
recursiveTreeData (treeData) {
const maxLevel = Math.max.apply(Math, treeData.map((o) => { return o.level }))
const items = treeData.filter(item => item.level <= maxLevel)
this.treeViewData = this.getNestedChildren(items, 0, maxLevel)
this.oldTreeViewData = this.treeViewData
},
getNestedChildren (dataItems, level, maxLevel, id) {
if (level > maxLevel) {
return
}
let items = []
if (!id || id === '') {
items = dataItems.filter(item => item.level === level)
} else {
items = dataItems.filter(item => {
let parentKey = ''
const arrKeys = Object.keys(item)
for (let i = 0; i < arrKeys.length; i++) {
if (arrKeys[i].indexOf('parent') > -1 && arrKeys[i].indexOf('id') > -1) {
parentKey = arrKeys[i]
break
}
}
return parentKey ? item[parentKey] === id : item.level === level
})
}
if (items.length === 0) {
return this.getNestedChildren(dataItems, (level + 1), maxLevel)
}
for (let i = 0; i < items.length; i++) {
items[i] = this.createResourceData(items[i])
if (items[i].haschild) {
items[i].children = this.getNestedChildren(dataItems, (level + 1), maxLevel, items[i].key)
}
}
return items
}
}
}
</script>
<style lang="less" scoped>
:deep(.ant-tree).list-tree-view {
overflow-y: hidden;
margin-top: 10px;
}
:deep(.ant-tree).ant-tree-directory {
li.ant-tree-treenode-selected {
span.ant-tree-switcher {
color: rgba(0, 0, 0, 0.65);
}
span.ant-tree-node-content-wrapper.ant-tree-node-selected > span {
color: rgba(0, 0, 0, 0.65);
background-color: #bae7ff;
}
span.ant-tree-node-content-wrapper::before {
background: #ffffff;
}
}
.ant-tree-child-tree {
li.ant-tree-treenode-selected {
span.ant-tree-switcher {
color: rgba(0, 0, 0, 0.65);
}
span.ant-tree-node-content-wrapper::before {
background: #ffffff;
}
}
}
}
:deep(.ant-tree-show-line) .ant-tree-switcher.ant-tree-switcher-noop {
display: none
}
:deep(.ant-tree-node-content-wrapper-open) > span:first-child,
:deep(.ant-tree-node-content-wrapper-close) > span:first-child {
display: none;
}
:deep(.ant-tree-icon__customize) {
padding-right: 5px;
background: transparent;
}
:deep(.ant-tree) {
.ant-tree-treenode {
.ant-tree-node-content-wrapper {
margin-left: 0;
margin-bottom: 2px;
&:before {
border-left: 1px solid #d9d9d9;
content: " ";
height: 100%;
height: calc(100% - 15px);
left: 12px;
margin: 22px 0 0;
position: absolute;
width: 1px;
top: 0;
}
}
&.ant-tree-treenode-switcher-close, &.ant-tree-treenode-switcher-open, &.ant-tree-treenode-leaf-last {
.ant-tree-node-content-wrapper:before {
display: none;
}
}
}
}
:deep(.ant-tree-show-line) .ant-tree-indent-unit:before {
bottom: -2px;
top: -5px;
}
</style>