diff --git a/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue b/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue
index c783920..bbb65c6 100644
--- a/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue
+++ b/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue
@@ -1,317 +1,319 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/cmdb-ui/src/modules/cmdb/lang/en.js b/cmdb-ui/src/modules/cmdb/lang/en.js
index 22e9d5d..539b0a5 100644
--- a/cmdb-ui/src/modules/cmdb/lang/en.js
+++ b/cmdb-ui/src/modules/cmdb/lang/en.js
@@ -589,6 +589,7 @@ if __name__ == "__main__":
confirmDeleteView: 'Are you sure you want to delete this view ?',
noInstancePerm: 'You do not have read permissions for this instance',
noPreferenceAttributes: 'This instance has no subscription attributes or no default displayed attributes',
+ topoViewSearchPlaceholder: 'Please enter the node name.'
},
}
export default cmdb_en
diff --git a/cmdb-ui/src/modules/cmdb/lang/zh.js b/cmdb-ui/src/modules/cmdb/lang/zh.js
index 35b6a1b..3daf592 100644
--- a/cmdb-ui/src/modules/cmdb/lang/zh.js
+++ b/cmdb-ui/src/modules/cmdb/lang/zh.js
@@ -589,6 +589,7 @@ if __name__ == "__main__":
confirmDeleteView: '您确定要删除该视图吗?',
noInstancePerm: '您没有该实例的查看权限',
noPreferenceAttributes: '该实例没有订阅属性或者没有默认展示的属性',
+ topoViewSearchPlaceholder: '请输入节点名字'
},
}
export default cmdb_zh
diff --git a/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue b/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue
index dc8377f..726315c 100644
--- a/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue
+++ b/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue
@@ -140,12 +140,33 @@
-
-
-
{{ node.text }}aaa
+
+
+
+
+
+
{{ node.text }}
+
-
+
-
@@ -253,9 +274,27 @@
>
-
- {{ node.text }}
-
+
+
+
+
+
+
+
{{ node.text }}
+
+
@@ -290,6 +329,7 @@ import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
import { searchCI } from '@/modules/cmdb/api/ci'
import { getTopoGroups, postTopoGroup, putTopoGroupByGId, putTopoGroupsOrder, deleteTopoGroup, getTopoView, addTopoView, updateTopoView, deleteTopoView, getRelationsByTypeId, previewTopoView, showTopoView } from '@/modules/cmdb/api/topology'
import CMDBExprDrawer from '@/components/CMDBExprDrawer'
+import { v4 as uuidv4 } from 'uuid'
const currentTopoKey = 'ops_cmdb_topo_currentId'
export default {
@@ -326,6 +366,7 @@ export default {
const graphOptions2 = {
// ...defaultOptions,
backgrounImageNoRepeat: true,
+ ovUseNodeSlot: true,
placeOtherGroup: true,
moveToCenterWhenRefresh: true,
zoomToFitWhenRefresh: true,
@@ -339,10 +380,11 @@ export default {
max_per_width: 200,
min_per_height: 40,
max_per_height: undefined,
- defaultLineColor: '#CACDD9',
- defaultNodeColor: '#29AAE1',
- defaultNodeFontColor: '#ffffff',
- defaultNodeBorderColor: '#b1c9ff',
+ backgroundColor: '#FFFFFF',
+ // defaultLineColor: '#CACDD9',
+ // defaultNodeColor: '#29AAE1',
+ // defaultNodeFontColor: '#ffffff',
+ // defaultNodeBorderColor: '#b1c9ff',
defaultExpandHolderPosition: 'right',
defaultJunctionPoint: 'lr',
layouts: [
@@ -350,7 +392,7 @@ export default {
layoutName: 'tree',
from: 'left',
layoutClassName: 'seeks-layout-center',
- defaultExpandHolderPosition: 'hide',
+ defaultExpandHolderPosition: 'right',
defaultJunctionPoint: 'border',
},
],
@@ -411,6 +453,29 @@ export default {
errorMessageShow: false,
errorMessage: '',
+ nodeStyle: {
+ '0': {
+ backgroundColor: '#2F54EB'
+ },
+ '1': {
+ backgroundColor: '#29AAE1'
+ },
+ '2': {
+ backgroundColor: '#7F97FA'
+ },
+ '3': {
+ backgroundColor: '##75C5CA'
+ },
+ '4': {
+ backgroundColor: '#A699F6'
+ },
+ '5': {
+ backgroundColor: '#A4B5E1'
+ }
+ }, // 拓扑图节点分级别样式
+ topoViewJsonData: {}, // 拓扑图 JSON 数据
+ topoViewOption: {}, // 拓扑图配置数据 子节点分页
+ topoViewSearchValue: '', // 拓扑图搜索
}
},
provide() {
@@ -485,6 +550,30 @@ export default {
: {}
},
},
+ watch: {
+ '$i18n.locale': {
+ immediate: true,
+ handler(newVal) {
+ this.changeTopoViewToolbarLang(newVal)
+ },
+ },
+ isShowPreview: {
+ immediate: true,
+ handler(newVal) {
+ if (newVal) {
+ this.changeTopoViewToolbarLang(this.$i18n.locale)
+ }
+ },
+ },
+ drawerVisible: {
+ immediate: true,
+ handler(newVal) {
+ if (newVal) {
+ this.changeTopoViewToolbarLang(this.$i18n.locale)
+ }
+ },
+ }
+ },
methods: {
closeNodeTips(e) {
e.preventDefault()
@@ -578,7 +667,7 @@ export default {
payload.view_ids = g.views.map(i => i.id)
}
if (groupId) {
- putTopoGroupByGId(groupId, { view_ids: g.views.map((i) => i.id) })
+ putTopoGroupByGId(groupId, { view_ids: payload.view_ids })
.then(() => {
this.$message.success(that.$t('saveSuccess'))
})
@@ -729,12 +818,21 @@ export default {
disableDefaultClickEffect: true,
})
})
+ const type2meta = res?.type2meta
res.nodes.forEach(item => {
+ const icon = type2meta?.[item?.type_id] || ''
nodes.push({
id: `${item.id}`,
text: item.name,
nodeShape: 1,
borderWidth: -1,
+ color: 'transparent',
+ styleClass: {
+ padding: '0px'
+ },
+ data: {
+ icon
+ },
disableDefaultClickEffect: true,
})
})
@@ -752,11 +850,15 @@ export default {
}
})
},
- showTopoView(viewId) {
+ async showTopoView(viewId) {
if (viewId === 'null' || !viewId) {
return
}
- showTopoView(viewId).then(res => {
+ const topoViewRes = await getTopoView(viewId)
+ if (topoViewRes?.option) {
+ this.topoViewOption = topoViewRes.option
+ }
+ showTopoView(viewId).then(async res => {
const nodes = []
const links = []
this.currentNodes = res.nodes
@@ -768,11 +870,21 @@ export default {
disableDefaultClickEffect: false,
})
})
+ const type2meta = res?.type2meta
res.nodes.forEach(item => {
+ const icon = type2meta?.[item?.type_id] || ''
nodes.push({
id: `${item.id}`,
text: item.name,
- data: {},
+ color: 'transparent',
+ styleClass: {
+ padding: '0px'
+ },
+ data: {
+ icon
+ },
+ isHide: false,
+ opacity: 1,
})
})
const _graphJsonData = {
@@ -783,11 +895,132 @@ export default {
this.$message.error(this.$t('cmdb.topo.noData'))
return
}
- // this.$nextTick(() => {
- this.$refs.showTopoView.setJsonData(_graphJsonData)
- // })
+ this.$refs.showTopoView.setJsonData(_.cloneDeep(_graphJsonData), async () => {
+ this.topoViewSearchValue = ''
+
+ // map 结构存储 节点
+ const nodeMap = _graphJsonData.nodes.reduce((map, node) => {
+ map.set(node.id, node)
+ return map
+ }, new Map())
+ _graphJsonData.nodes = nodeMap
+
+ if (this?.topoViewOption?.aggregation_count) {
+ const instance = this.$refs.showTopoView.getInstance()
+ const nodes = instance.getNodes()
+ const rootNodes = nodes.filter((node) => node.lot.level === 0)
+ rootNodes.forEach((node) => {
+ this.initMoreNodesData(node, _graphJsonData)
+ })
+
+ this.$refs.showTopoView.setJsonData(_.cloneDeep({
+ nodes: _graphJsonData.nodes.values(),
+ links: _graphJsonData.links
+ }))
+ }
+ this.topoViewJsonData = _graphJsonData
+ this.changeTopoViewToolbarLang(this.$i18n.locale)
+ })
})
},
+
+ /**
+ * 初始化子节点分页数据
+ */
+ initMoreNodesData(node, jsonData) {
+ const childs = node.lot.childs
+ // 没有子节点 终止遍历
+ if (!childs?.length) {
+ return
+ }
+
+ // 子节点分页数量
+ const aggregation_count = this?.topoViewOption?.aggregation_count || 1
+ // 展示节点数量
+ let showNodeCount = 0
+
+ childs.forEach((childNode, index) => {
+ if (childNode?.data?.btnType !== 'more') {
+ if (showNodeCount >= aggregation_count) {
+ const originNode = jsonData?.nodes?.get(childNode.id)
+ if (originNode) {
+ originNode.isHide = true
+ }
+ } else if (!childNode.isHide) {
+ showNodeCount++
+ }
+ this.initMoreNodesData(childNode, jsonData)
+ }
+ })
+
+ if (childs.length - showNodeCount > 0) {
+ const id = uuidv4()
+ jsonData.nodes.set(id, {
+ id,
+ text: `展示更多(${childs.length - showNodeCount})`,
+ data: {
+ btnType: 'more'
+ },
+ color: 'transparent',
+ styleClass: {
+ padding: '0px'
+ },
+ })
+
+ jsonData.links.push({
+ from: node.id,
+ to: id,
+ })
+ }
+ },
+
+ async clickMoreBtn(node) {
+ const childs = node?.lot?.parent?.lot?.childs
+ if (childs?.length) {
+ const topoViewJsonData = this.topoViewJsonData
+ let moreBtnNode = null
+ let showNodeCount = 0
+ let toggleNodeCount = 0
+ const aggregation_count = this?.topoViewOption?.aggregation_count || 1
+
+ childs.forEach((child) => {
+ if (!child.isHide) {
+ showNodeCount++
+ }
+
+ if (toggleNodeCount < aggregation_count && child.isHide) {
+ const childNode = topoViewJsonData?.nodes?.get(child.id)
+ if (childNode) {
+ childNode.isHide = false
+ toggleNodeCount++
+ showNodeCount++
+ }
+ }
+
+ if (child.data.btnType === 'more') {
+ moreBtnNode = topoViewJsonData?.nodes?.get(child.id)
+ }
+ })
+ if (moreBtnNode) {
+ if (showNodeCount === childs.length) {
+ moreBtnNode.isHide = true
+ } else {
+ moreBtnNode.text = `展示更多(${childs.length - showNodeCount})`
+ }
+ }
+
+ const instance = this.$refs.showTopoView.getInstance()
+ instance.setJsonData(
+ {
+ links: topoViewJsonData.links,
+ nodes: topoViewJsonData.nodes.values()
+ },
+ false
+ )
+ this.topoViewJsonData = topoViewJsonData
+ }
+ },
+
handleOpenCmdb() {
this.$refs.cmdbDrawer.open()
},
@@ -921,9 +1154,16 @@ export default {
}
},
async showNodeTips(nodeObject, $event) {
- console.log('node click')
+ console.log('node click', nodeObject)
$event.preventDefault()
$event.stopPropagation()
+
+ const btnType = nodeObject?.data?.btnType
+ if (btnType === 'more') {
+ this.clickMoreBtn(nodeObject)
+ return
+ }
+
const _base_position = this.$refs.showTopoView.getInstance().options.fullscreen ? { x: 0, y: 0 } : this.$refs.rightTopoView.getBoundingClientRect()
if (this.currentNode !== nodeObject) {
this.currentNodeValues = null
@@ -934,25 +1174,16 @@ export default {
const [ attributes ] = await Promise.all([getSubscribeAttributes(rawNode.type_id)])
this.currentNodeAttributes = attributes?.attributes || []
if (!this.currentNodeAttributes.length) {
- this.errorMessage = this.$t('cmdb.topo.noPreferenceAttributes')
- this.errorMessageShow = true
- this.currentNodeValues = null
- this.isShowNodeTipsPanel = false
+ this.handleNullNodeTips(this.$t('cmdb.topo.noPreferenceAttributes'))
}
await searchCI({ q: `_id:${rawNode.id}` }, false).then(res => {
if (!res.result.length) {
- this.errorMessage = this.$t('cmdb.topo.noInstancePerm')
- this.errorMessageShow = true
- this.currentNodeValues = null
- this.isShowNodeTipsPanel = false
+ this.handleNullNodeTips(this.$t('cmdb.topo.noInstancePerm'))
} else {
this.currentNodeValues = res.result[0]
}
}).catch(error => {
- this.errorMessage = ((error.response || {}).data || {}).message
- this.errorMessageShow = true
- this.currentNodeValues = null
- this.isShowNodeTipsPanel = false
+ this.handleNullNodeTips(((error.response || {}).data || {}).message)
})
}
}
@@ -971,9 +1202,71 @@ export default {
this.isShowNodeTipsPanel = true
console.log(this.nodeTipsPosition)
},
+
+ handleNullNodeTips(errorMessage) {
+ this.errorMessage = errorMessage
+ this.errorMessageShow = true
+ this.currentNodeValues = null
+ this.isShowNodeTipsPanel = false
+ this.currentNode = {}
+ },
+
hideNodeTips(nodeObject, $event) {
this.isShowNodeTipsPanel = false
},
+
+ handleSearchTopoView(v) {
+ const topoViewJsonData = this.topoViewJsonData
+ topoViewJsonData.nodes.keys().forEach((key) => {
+ const node = topoViewJsonData?.nodes?.get(key)
+ if (node?.data?.btnType !== 'more') {
+ node.opacity = node?.text?.indexOf(v) !== -1 ? 1 : 0.1
+ }
+ })
+ const instance = this.$refs.showTopoView.getInstance()
+ instance.setJsonData(
+ {
+ links: topoViewJsonData.links,
+ nodes: topoViewJsonData.nodes.values()
+ },
+ false
+ )
+ this.topoViewJsonData = topoViewJsonData
+ },
+
+ changeTopoViewToolbarLang(lang) {
+ setTimeout(() => {
+ const toolbarElements = document.getElementsByClassName('rel-toolbar')
+ const zhlangMap = {
+ '全屏/退出全屏': 'Full Screen/Exit Full Screen',
+ '放大': 'zoom in',
+ '缩小': 'zoom out',
+ '刷新': 'refresh ',
+ '下载图片': 'download image'
+ }
+ const enlangMap = {
+ 'Full Screen/Exit Full Screen': '全屏/退出全屏',
+ 'zoom in': '放大',
+ 'zoom out': '缩小',
+ 'refresh': '刷新 ',
+ 'download image': '下载图片'
+ }
+
+ toolbarElements.forEach((toolbarElement) => {
+ if (toolbarElement?.children?.length) {
+ toolbarElement.children.forEach((node) => {
+ const oldTitle = node?.getAttribute('title')
+ if (oldTitle) {
+ const newTitle = lang === 'en' ? zhlangMap[oldTitle] : enlangMap[oldTitle]
+ if (newTitle) {
+ node.setAttribute('title', newTitle)
+ }
+ }
+ })
+ }
+ })
+ }, 300)
+ },
},
}
@@ -1098,6 +1391,14 @@ export default {
top: 40%;
transform: translate(-50%, -50%);
}
+
+ .relation-graph-search {
+ position: absolute;
+ z-index: 10;
+ top: 20px;
+ left: 20px;
+ width: 300px;
+ }
}
.topo-left,
.topo-right {
@@ -1133,6 +1434,54 @@ export default {
border-radius: 8px;
}
}
+
+.relation-graph-node {
+ padding: 6px 3px;
+ border-radius: 2px;
+ border-width: 2px;
+ border-style: solid;
+ background-color: transparent;
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &-text {
+ color: #000000;
+ font-size: 12px;
+ font-weight: 400;
+ margin-left: 6px;
+ word-break: break-all;
+ }
+
+ &-icon {
+ font-size: 12px;
+ color: rgba(0, 0, 0, 0.65);
+ }
+
+ &-image {
+ max-height: 20px;
+ max-width: 20px;
+ }
+}
+
+/deep/ .relation-graph {
+ background-color: #FFFFFF;
+
+ .rel-node {
+ padding: 0px;
+ height: auto !important;
+ }
+ .rel-node-checked {
+ box-shadow: none;
+ }
+ .c-expanded {
+ background-color: rgb(64, 158, 255) !important;
+ }
+ .c-collapsed {
+ background-color: rgb(64, 158, 255) !important;
+ }
+}