blob: 47f199b2cf55cdb4383c457a361f019613585aa1 [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 #parent><folder-outlined /></template>
<template #leaf><block-outlined /></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 {}
}
}
},
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
this.treeVerticalData = this.treeData
if (this.treeViewData.length > 0) {
this.oldTreeViewData = this.treeViewData
this.rootKey = this.treeViewData[0].key
}
}
},
treeSelected: {
deep: true,
handler () {
if (Object.keys(this.treeSelected).length === 0) {
return
}
if (Object.keys(this.resource).length > 0) {
this.selectedTreeKey = this.resource.key
this.$emit('change-resource', this.resource)
// 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.onSelectResource()
resolve()
})
})
},
onSelectResource () {
if (this.treeStore.selected) {
this.selectedTreeKey = this.treeStore.selected
this.defaultSelected = [this.selectedTreeKey]
const resource = this.treeVerticalData.filter(item => item.id === this.selectedTreeKey)
if (resource.length > 0) {
this.resource = resource[0]
this.$emit('change-resource', this.resource)
} else {
const resourceIdx = this.treeVerticalData.findIndex(item => item.id === this.resource.id)
const parentIndex = this.treeVerticalData.findIndex(item => item.id === this.resource.parentdomainid)
if (resourceIdx !== -1) {
this.resource = this.treeVerticalData[resourceIdx]
} else if (parentIndex !== 1) {
this.resource = this.treeVerticalData[parentIndex]
} else {
this.resource = this.treeVerticalData[0]
}
this.selectedTreeKey = this.resource.key
this.defaultSelected = [this.selectedTreeKey]
this.$emit('change-resource', this.resource)
}
}
},
onSelect (selectedKeys, event) {
if (!event.selected) {
setTimeout(() => { event.node.$refs.selectHandle.click() })
return
}
// check item tree selected, set selectedTreeKey
if (selectedKeys && selectedKeys[0]) {
this.selectedTreeKey = selectedKeys[0]
}
this.defaultSelected = []
this.defaultSelected.push(this.selectedTreeKey)
const treeStore = this.treeStore
treeStore.expands = this.arrExpand
treeStore.selected = this.selectedTreeKey
this.$emit('change-tree-store', this.treeStore)
this.getDetailResource(this.selectedTreeKey)
},
onExpand (treeExpand) {
const treeStore = this.treeStore
this.arrExpand = treeExpand
treeStore.isExpand = true
treeStore.expands = this.arrExpand
treeStore.selected = this.selectedTreeKey
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
},
getDetailResource (selectedKey) {
// 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)
// check json response is empty
if (!jsonResponse || jsonResponse.length === 0) {
this.resource = []
} else {
this.resource = jsonResponse[0]
this.resource = this.createResourceData(this.resource)
// set all value of resource tree data
this.treeVerticalData.filter((item, index) => {
if (item.id === this.resource.id) {
this.treeVerticalData[index] = this.resource
}
})
}
// emit change resource to parent
this.$emit('change-resource', this.resource)
}).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
resource.slots = {
icon: 'parent'
}
if (!resource.haschild) {
resource.isLeaf = true
resource.slots = {
icon: 'leaf'
}
}
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>
.list-tree-view {
overflow-y: hidden;
}
: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) li span.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;
}
:deep(.ant-tree) li .ant-tree-node-content-wrapper {
padding-left: 0;
margin-left: 3px;
}
</style>