mirror of
https://github.com/apache/cloudstack.git
synced 2025-12-16 10:32:34 +01:00
domain: implement tree-view based domain list view (#53)
Fixes #27 Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
3a82cc535f
commit
c9a75e2f30
@ -27,25 +27,19 @@
|
||||
</span>
|
||||
<a-menu slot="overlay" class="user-menu-wrapper">
|
||||
<a-menu-item class="user-menu-item" key="0">
|
||||
<router-link :to="{ name: 'account' }">
|
||||
<router-link :to="{ path: '/accountuser/' + $store.getters.userInfo.id }">
|
||||
<a-icon class="user-menu-item-icon" type="user"/>
|
||||
<span class="user-menu-item-name">Account</span>
|
||||
<span class="user-menu-item-name">Profile</span>
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item class="user-menu-item" key="2" disabled>
|
||||
<router-link :to="{ name: 'account' }">
|
||||
<a-icon class="user-menu-item-icon" type="setting"/>
|
||||
<span class="user-menu-item-name">Settings</span>
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item class="user-menu-item" key="3" disabled>
|
||||
<a-menu-item class="user-menu-item" key="1" disabled>
|
||||
<a :href="docBase" target="_blank">
|
||||
<a-icon class="user-menu-item-icon" type="question-circle-o"></a-icon>
|
||||
<span class="user-menu-item-name">Help</span>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-divider/>
|
||||
<a-menu-item class="user-menu-item" key="4">
|
||||
<a-menu-item class="user-menu-item" key="2">
|
||||
<a href="javascript:;" @click="handleLogout">
|
||||
<a-icon class="user-menu-item-icon" type="logout"/>
|
||||
<span class="user-menu-item-name">Logout</span>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<a-card class="spin-content" :bordered="true" :title="title">
|
||||
<a-card class="spin-content" :bordered="bordered" :title="title">
|
||||
<div>
|
||||
<div class="resource-details">
|
||||
<div class="avatar">
|
||||
@ -466,6 +466,10 @@ export default {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
||||
@ -35,7 +35,8 @@
|
||||
<a-tab-pane
|
||||
v-for="tab in tabs"
|
||||
:tab="$t(tab.name)"
|
||||
:key="tab.name">
|
||||
:key="tab.name"
|
||||
v-if="'show' in tab ? tab.show(resource, $route) : true">
|
||||
<component :is="tab.component" :resource="resource" :loading="loading" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
590
ui/src/components/view/TreeView.vue
Normal file
590
ui/src/components/view/TreeView.vue
Normal file
@ -0,0 +1,590 @@
|
||||
// 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>
|
||||
<a-spin :spinning="loading" slot="left">
|
||||
<a-card :bordered="false">
|
||||
<a-input-search
|
||||
size="default"
|
||||
placeholder="Search"
|
||||
v-model="searchQuery"
|
||||
@search="onSearch"
|
||||
>
|
||||
<a-icon slot="prefix" type="search" />
|
||||
</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"
|
||||
:defaultSelectedKeys="defaultSelected"
|
||||
:checkStrictly="true"
|
||||
@select="onSelect"
|
||||
@expand="onExpand"
|
||||
:defaultExpandedKeys="arrExpand">
|
||||
<a-icon slot="parent" type="folder" />
|
||||
<a-icon slot="leaf" type="block" />
|
||||
</a-tree>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
<a-spin :spinning="detailLoading" slot="right">
|
||||
<a-card
|
||||
class="spin-content"
|
||||
:bordered="true"
|
||||
style="width:100%">
|
||||
<a-tabs
|
||||
style="width: 100%"
|
||||
:animated="false"
|
||||
:defaultActiveKey="tabs[0].name"
|
||||
@change="onTabChange" >
|
||||
<a-tab-pane
|
||||
v-for="tab in tabs"
|
||||
:tab="$t(tab.name)"
|
||||
:key="tab.name"
|
||||
v-if="checkShowTabDetail(tab.name)">
|
||||
<component
|
||||
:is="tab.component"
|
||||
:resource="resource"
|
||||
:items="items"
|
||||
:tab="tabActive"
|
||||
:loading="loading"
|
||||
:bordered="false" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</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'
|
||||
|
||||
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
|
||||
},
|
||||
actionData: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
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] : ''
|
||||
},
|
||||
watch: {
|
||||
loading () {
|
||||
this.detailLoading = this.loading
|
||||
},
|
||||
treeData () {
|
||||
if (this.oldTreeViewData.length === 0) {
|
||||
this.treeViewData = this.treeData
|
||||
this.treeVerticalData = this.treeData
|
||||
}
|
||||
|
||||
if (this.treeViewData.length > 0) {
|
||||
this.oldTreeViewData = this.treeViewData
|
||||
this.rootKey = this.treeViewData[0].key
|
||||
}
|
||||
},
|
||||
treeSelected () {
|
||||
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])
|
||||
}
|
||||
},
|
||||
actionData (newData, oldData) {
|
||||
if (!newData || newData.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.reloadTreeData(newData)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onLoadData (treeNode) {
|
||||
if (this.searchQuery !== '' && treeNode.eventKey !== this.rootKey) {
|
||||
return new Promise(resolve => {
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
const params = {
|
||||
listAll: true,
|
||||
id: treeNode.eventKey
|
||||
}
|
||||
|
||||
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.$set(this.treeVerticalData[index], value, dataGenerate[i][value])
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
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.getDetailResource(this.selectedTreeKey)
|
||||
},
|
||||
onExpand (treeExpand) {
|
||||
this.arrExpand = treeExpand
|
||||
},
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
},
|
||||
reloadTreeData (objData) {
|
||||
// data response from action
|
||||
let jsonResponse = this.getResponseJsonData(objData[0])
|
||||
jsonResponse = this.createResourceData(jsonResponse)
|
||||
|
||||
// resource for check create or edit
|
||||
const resource = this.treeVerticalData.filter(item => item.id === jsonResponse.id)
|
||||
|
||||
// when edit
|
||||
if (resource && resource[0]) {
|
||||
this.treeVerticalData.filter((item, index) => {
|
||||
if (item.id === jsonResponse.id) {
|
||||
// replace all value of tree data
|
||||
Object.keys(jsonResponse).forEach((value, idx) => {
|
||||
this.$set(this.treeVerticalData[index], value, jsonResponse[value])
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// when create
|
||||
let resourceExists = true
|
||||
|
||||
// check is searching data
|
||||
if (this.searchQuery !== '') {
|
||||
resourceExists = jsonResponse.title.indexOf(this.searchQuery) > -1
|
||||
}
|
||||
|
||||
// push new resource to tree data
|
||||
if (this.resource.haschild && resourceExists) {
|
||||
this.treeVerticalData.push(jsonResponse)
|
||||
}
|
||||
|
||||
// set resource is currently active as a parent
|
||||
this.treeVerticalData.filter((item, index) => {
|
||||
if (item.id === this.resource.id) {
|
||||
this.$set(this.treeVerticalData[index], 'isLeaf', false)
|
||||
this.$set(this.treeVerticalData[index], 'haschild', true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.recursiveTreeData(this.treeVerticalData)
|
||||
},
|
||||
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.page = 1
|
||||
params.pageSize = 1
|
||||
|
||||
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)
|
||||
})
|
||||
},
|
||||
getResponseJsonData (json) {
|
||||
let responseName
|
||||
let objectName
|
||||
for (const key in json) {
|
||||
if (key.includes('response')) {
|
||||
responseName = key
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in json[responseName]) {
|
||||
if (key === 'count') {
|
||||
continue
|
||||
}
|
||||
|
||||
objectName = key
|
||||
break
|
||||
}
|
||||
return json[responseName][objectName]
|
||||
},
|
||||
checkShowTabDetail (tabKey) {
|
||||
// get tab item from the route
|
||||
const itemTab = this.tabs.filter(item => item.name === tabKey)
|
||||
|
||||
// check tab item not exists
|
||||
if (!itemTab || !itemTab[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
// get permission from the route
|
||||
const permission = itemTab[0].permission ? itemTab[0].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') {
|
||||
this.$set(resource, value, '-1')
|
||||
}
|
||||
})
|
||||
this.$set(resource, 'title', resource.name)
|
||||
this.$set(resource, 'key', resource.id)
|
||||
resource.slots = {
|
||||
icon: 'parent'
|
||||
}
|
||||
|
||||
if (!resource.haschild) {
|
||||
this.$set(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 {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
background: #fff;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
/deep/.ant-tree li .ant-tree-node-content-wrapper {
|
||||
padding-left: 0;
|
||||
margin-left: 3px;
|
||||
}
|
||||
</style>
|
||||
@ -62,7 +62,9 @@ export function generateRouterMap (section) {
|
||||
columns: child.columns,
|
||||
details: child.details,
|
||||
related: child.related,
|
||||
actions: child.actions
|
||||
actions: child.actions,
|
||||
treeView: child.treeView ? child.treeView : false,
|
||||
tabs: child.treeView ? child.tabs : {}
|
||||
},
|
||||
component: component,
|
||||
hideChildrenInMenu: true,
|
||||
|
||||
@ -167,7 +167,7 @@ export default {
|
||||
name: 'domain',
|
||||
title: 'Domains',
|
||||
icon: 'block',
|
||||
permission: ['listDomains'],
|
||||
permission: ['listDomains', 'listDomainChildren'],
|
||||
resourceType: 'Domain',
|
||||
columns: ['name', 'state', 'path', 'parentdomainname', 'level'],
|
||||
details: ['name', 'id', 'path', 'parentdomainname', 'level', 'networkdomain', 'iptotal', 'vmtotal', 'volumetotal', 'vmlimit', 'iplimit', 'volumelimit', 'snapshotlimit', 'templatelimit', 'vpclimit', 'cpulimit', 'memorylimit', 'networklimit', 'primarystoragelimit', 'secondarystoragelimit'],
|
||||
@ -176,18 +176,37 @@ export default {
|
||||
title: 'Accounts',
|
||||
param: 'domainid'
|
||||
}],
|
||||
tabs: [
|
||||
{
|
||||
name: 'Domain',
|
||||
component: () => import('@/components/view/InfoCard.vue'),
|
||||
show: (record, route) => { return route.path === '/domain' }
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
component: () => import('@/components/view/DetailsTab.vue')
|
||||
}
|
||||
],
|
||||
treeView: true,
|
||||
actions: [
|
||||
{
|
||||
api: 'createDomain',
|
||||
icon: 'plus',
|
||||
label: 'label.add.domain',
|
||||
listView: true,
|
||||
args: ['parentdomainid', 'name', 'networkdomain', 'domainid']
|
||||
dataView: true,
|
||||
args: ['parentdomainid', 'name', 'networkdomain', 'domainid'],
|
||||
mapping: {
|
||||
parentdomainid: {
|
||||
value: (record) => { return record.id }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'updateDomain',
|
||||
icon: 'edit',
|
||||
label: 'label.action.edit.domain',
|
||||
listView: true,
|
||||
dataView: true,
|
||||
args: ['name', 'networkdomain']
|
||||
},
|
||||
@ -195,6 +214,7 @@ export default {
|
||||
api: 'updateResourceCount',
|
||||
icon: 'sync',
|
||||
label: 'label.action.update.resource.count',
|
||||
listView: true,
|
||||
dataView: true,
|
||||
args: ['domainid'],
|
||||
mapping: {
|
||||
@ -207,6 +227,7 @@ export default {
|
||||
api: 'deleteDomain',
|
||||
icon: 'delete',
|
||||
label: 'label.delete.domain',
|
||||
listView: true,
|
||||
dataView: true,
|
||||
show: (record) => { return record.level !== 0 },
|
||||
args: ['cleanup']
|
||||
|
||||
@ -45,8 +45,8 @@
|
||||
</template>
|
||||
<a-button
|
||||
v-if="action.api in $store.getters.apis &&
|
||||
((!dataView && (action.listView || action.groupAction && selectedRowKeys.length > 0)) ||
|
||||
(dataView && action.dataView && ('show' in action ? action.show(resource) : true)))"
|
||||
((!dataView && (action.listView || action.groupAction && selectedRowKeys.length > 0)) || (dataView && action.dataView)) &&
|
||||
('show' in action ? action.show(resource) : true)"
|
||||
:icon="action.icon"
|
||||
:type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
|
||||
shape="circle"
|
||||
@ -59,7 +59,7 @@
|
||||
style="width: unset"
|
||||
placeholder="Search"
|
||||
v-model="searchQuery"
|
||||
v-if="!dataView"
|
||||
v-if="!dataView && !treeView"
|
||||
@search="onSearch" />
|
||||
</span>
|
||||
</a-col>
|
||||
@ -191,14 +191,18 @@
|
||||
</a-modal>
|
||||
</div>
|
||||
|
||||
<div v-if="dataView">
|
||||
<resource-view :resource="resource" :loading="loading" :tabs="$route.meta.tabs" />
|
||||
<div v-if="dataView && !treeView">
|
||||
<resource-view
|
||||
:resource="resource"
|
||||
:loading="loading"
|
||||
:tabs="$route.meta.tabs" />
|
||||
</div>
|
||||
<div class="row-element" v-else>
|
||||
<list-view
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:items="items" />
|
||||
:items="items"
|
||||
v-if="!treeView" />
|
||||
<a-pagination
|
||||
class="row-element"
|
||||
size="small"
|
||||
@ -209,7 +213,16 @@
|
||||
:pageSizeOptions="['10', '20', '40', '80', '100']"
|
||||
@change="changePage"
|
||||
@showSizeChange="changePageSize"
|
||||
showSizeChanger />
|
||||
showSizeChanger
|
||||
v-if="!treeView" />
|
||||
<tree-view
|
||||
v-if="treeView"
|
||||
:treeData="treeData"
|
||||
:treeSelected="treeSelected"
|
||||
:loading="loading"
|
||||
:tabs="$route.meta.tabs"
|
||||
@change-resource="changeResource"
|
||||
:actionData="actionData"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -225,6 +238,7 @@ import ChartCard from '@/components/widgets/ChartCard'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import ListView from '@/components/view/ListView'
|
||||
import ResourceView from '@/components/view/ResourceView'
|
||||
import TreeView from '@/components/view/TreeView'
|
||||
import { genericCompare } from '@/utils/sort.js'
|
||||
|
||||
export default {
|
||||
@ -234,6 +248,7 @@ export default {
|
||||
ChartCard,
|
||||
ResourceView,
|
||||
ListView,
|
||||
TreeView,
|
||||
Status
|
||||
},
|
||||
mixins: [mixinDevice],
|
||||
@ -253,7 +268,11 @@ export default {
|
||||
currentAction: {},
|
||||
showAction: false,
|
||||
dataView: false,
|
||||
actions: []
|
||||
treeView: false,
|
||||
actions: [],
|
||||
treeData: [],
|
||||
treeSelected: {},
|
||||
actionData: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -291,6 +310,8 @@ export default {
|
||||
this.columns = []
|
||||
this.columnKeys = []
|
||||
this.items = []
|
||||
this.treeData = []
|
||||
this.treeSelected = {}
|
||||
var params = { listall: true }
|
||||
if (Object.keys(this.$route.query).length > 0) {
|
||||
Object.assign(params, this.$route.query)
|
||||
@ -302,9 +323,12 @@ export default {
|
||||
params.keyword = this.searchQuery
|
||||
}
|
||||
|
||||
this.treeView = this.$route && this.$route.meta && this.$route.meta.treeView
|
||||
|
||||
if (this.$route && this.$route.params && this.$route.params.id) {
|
||||
this.resource = {}
|
||||
this.dataView = true
|
||||
this.treeView = false
|
||||
} else {
|
||||
this.dataView = false
|
||||
}
|
||||
@ -358,8 +382,16 @@ export default {
|
||||
params.name = this.$route.params.id
|
||||
}
|
||||
}
|
||||
params.page = this.page
|
||||
params.pagesize = this.pageSize
|
||||
|
||||
if (!this.treeView) {
|
||||
params.page = this.page
|
||||
params.pagesize = this.pageSize
|
||||
} else {
|
||||
const domainId = this.$store.getters.userInfo.domainid
|
||||
params.id = domainId
|
||||
delete params.treeView
|
||||
}
|
||||
|
||||
api(this.apiName, params).then(json => {
|
||||
var responseName
|
||||
var objectName
|
||||
@ -381,33 +413,47 @@ export default {
|
||||
if (!this.items || this.items.length === 0) {
|
||||
this.items = []
|
||||
}
|
||||
for (let idx = 0; idx < this.items.length; idx++) {
|
||||
this.items[idx].key = idx
|
||||
for (const key in customRender) {
|
||||
const func = customRender[key]
|
||||
if (func && typeof func === 'function') {
|
||||
this.items[idx][key] = func(this.items[idx])
|
||||
if (this.treeView) {
|
||||
this.treeData = this.generateTreeData(this.items)
|
||||
} else {
|
||||
for (let idx = 0; idx < this.items.length; idx++) {
|
||||
this.items[idx].key = idx
|
||||
for (const key in customRender) {
|
||||
const func = customRender[key]
|
||||
if (func && typeof func === 'function') {
|
||||
this.items[idx][key] = func(this.items[idx])
|
||||
}
|
||||
}
|
||||
if (this.$route.path.startsWith('/ssh')) {
|
||||
this.items[idx].id = this.items[idx].name
|
||||
}
|
||||
}
|
||||
if (this.$route.path.startsWith('/ssh')) {
|
||||
this.items[idx].id = this.items[idx].name
|
||||
}
|
||||
}
|
||||
if (this.items.length > 0) {
|
||||
this.resource = this.items[0]
|
||||
this.treeSelected = this.treeView ? this.items[0] : {}
|
||||
} else {
|
||||
this.resource = {}
|
||||
this.treeSelected = {}
|
||||
}
|
||||
}).catch(error => {
|
||||
// handle error
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: error.response.headers['x-description'],
|
||||
duration: 0
|
||||
})
|
||||
if (error.response.status === 431) {
|
||||
|
||||
if ([401, 405].includes(error.response.status)) {
|
||||
this.$router.push({ path: '/exception/403' })
|
||||
}
|
||||
|
||||
if ([430, 431, 432].includes(error.response.status)) {
|
||||
this.$router.push({ path: '/exception/404' })
|
||||
}
|
||||
|
||||
if ([530, 531, 532, 533, 534, 535, 536, 537].includes(error.response.status)) {
|
||||
this.$router.push({ path: '/exception/500' })
|
||||
}
|
||||
}).finally(f => {
|
||||
this.loading = false
|
||||
})
|
||||
@ -422,6 +468,7 @@ export default {
|
||||
this.currentAction = {}
|
||||
},
|
||||
execAction (action) {
|
||||
this.actionData = []
|
||||
if (action.component && action.api && !action.popup) {
|
||||
this.$router.push({ name: action.api })
|
||||
return
|
||||
@ -581,6 +628,11 @@ export default {
|
||||
|
||||
var hasJobId = false
|
||||
api(this.currentAction.api, params).then(json => {
|
||||
// set action data for reload tree-view
|
||||
if (this.treeView) {
|
||||
this.actionData.push(json)
|
||||
}
|
||||
|
||||
for (const obj in json) {
|
||||
if (obj.includes('response')) {
|
||||
for (const res in json[obj]) {
|
||||
@ -630,6 +682,24 @@ export default {
|
||||
this.loading = false
|
||||
this.selectedRowKeys = []
|
||||
}, 1000)
|
||||
},
|
||||
generateTreeData (treeData) {
|
||||
const result = []
|
||||
const rootItem = treeData
|
||||
|
||||
rootItem[0].title = rootItem[0].title ? rootItem[0].title : rootItem[0].name
|
||||
rootItem[0].key = rootItem[0].id ? rootItem[0].id : 0
|
||||
|
||||
if (!rootItem[0].haschild) {
|
||||
rootItem[0].isLeaf = true
|
||||
}
|
||||
|
||||
result.push(rootItem[0])
|
||||
return result
|
||||
},
|
||||
changeResource (resource) {
|
||||
this.treeSelected = resource
|
||||
this.resource = this.treeSelected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user