You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2019/12/06 20:25:33 UTC
[cloudstack-primate] branch master updated: domain: implement
tree-view based domain list view (#53)
This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git
The following commit(s) were added to refs/heads/master by this push:
new 3a7901a domain: implement tree-view based domain list view (#53)
3a7901a is described below
commit 3a7901ad7c3a2c7ed9ee145626d02dc2d6876d7c
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Sat Dec 7 03:25:25 2019 +0700
domain: implement tree-view based domain list view (#53)
Fixes #27
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
---
src/components/header/UserMenu.vue | 14 +-
src/components/view/InfoCard.vue | 6 +-
src/components/view/ResourceView.vue | 3 +-
src/components/view/TreeView.vue | 590 +++++++++++++++++++++++++++++++++++
src/config/router.js | 4 +-
src/config/section/iam.js | 25 +-
src/views/AutogenView.vue | 112 +++++--
7 files changed, 718 insertions(+), 36 deletions(-)
diff --git a/src/components/header/UserMenu.vue b/src/components/header/UserMenu.vue
index b53476c..1f33a0f 100644
--- a/src/components/header/UserMenu.vue
+++ b/src/components/header/UserMenu.vue
@@ -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>
diff --git a/src/components/view/InfoCard.vue b/src/components/view/InfoCard.vue
index e4f973f..b8f4ab5 100644
--- a/src/components/view/InfoCard.vue
+++ b/src/components/view/InfoCard.vue
@@ -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 () {
diff --git a/src/components/view/ResourceView.vue b/src/components/view/ResourceView.vue
index 7b16251..23ae8cc 100644
--- a/src/components/view/ResourceView.vue
+++ b/src/components/view/ResourceView.vue
@@ -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>
diff --git a/src/components/view/TreeView.vue b/src/components/view/TreeView.vue
new file mode 100644
index 0000000..e68b17c
--- /dev/null
+++ b/src/components/view/TreeView.vue
@@ -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>
diff --git a/src/config/router.js b/src/config/router.js
index 51a61e9..62c353b 100644
--- a/src/config/router.js
+++ b/src/config/router.js
@@ -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,
diff --git a/src/config/section/iam.js b/src/config/section/iam.js
index 5b0b2ef..351a871 100644
--- a/src/config/section/iam.js
+++ b/src/config/section/iam.js
@@ -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']
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 4e07402..9c999d5 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -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
}
}
}