feat(cmdb-ui):citype show attr && service tree search (#479)

This commit is contained in:
dagongren
2024-04-17 17:59:21 +08:00
committed by GitHub
parent d8a7728f1d
commit dc8b1a5de2
40 changed files with 710 additions and 559 deletions

View File

@@ -73,3 +73,11 @@ export function deleteCIRelationView(firstCiId, secondCiId, data) {
data
})
}
export function searchCIRelationFull(params) {
return axios({
url: `/v0.1/ci_relations/search/full`,
method: 'GET',
params,
})
}

View File

@@ -95,12 +95,12 @@
isFocusExpression = false
}
"
class="ci-searchform-expression"
:class="{ 'ci-searchform-expression': true, 'ci-searchform-expression-has-value': expression }"
:style="{ width }"
:placeholder="placeholder"
@keyup.enter="emitRefresh"
>
<ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" />
<a-icon slot="suffix" type="check-circle" @click="handleCopyExpression" />
</a-input>
<slot></slot>
</a-space>
@@ -284,6 +284,9 @@ export default {
cursor: pointer;
}
}
.ci-searchform-expression-has-value .ant-input-suffix {
color: @func-color_3;
}
.cmdb-search-form {
.ant-form-item-label {
overflow: hidden;
@@ -294,7 +297,6 @@ export default {
</style>
<style lang="less" scoped>
.search-form-bar {
margin-bottom: 20px;
display: flex;

View File

@@ -194,7 +194,10 @@ const cmdb_en = {
attributeAssociationTip3: 'Two Attributes must be selected',
attributeAssociationTip4: 'Please select a attribute from Source CIType',
attributeAssociationTip5: 'Please select a attribute from Target CIType',
show: 'show attribute',
setAsShow: 'Set as show attribute',
cancelSetAsShow: 'Cancel show attribute',
showTips: 'The names of nodes in the service tree and topology view'
},
components: {
unselectAttributes: 'Unselected',
@@ -530,7 +533,8 @@ if __name__ == "__main__":
peopleHasRead: 'Personnel authorized to read:',
authorizationPolicy: 'CI Authorization Policy:',
idAuthorizationPolicy: 'Authorized by node:',
view: 'View permissions'
view: 'View permissions',
searchTips: 'Search in service tree'
},
tree: {
tips1: 'Please go to Preference page first to complete your subscription!',

View File

@@ -194,6 +194,10 @@ const cmdb_zh = {
attributeAssociationTip3: '属性关联必须选择两个属性',
attributeAssociationTip4: '请选择原模型属性',
attributeAssociationTip5: '请选择目标模型属性',
show: '展示属性',
setAsShow: '设置为展示属性',
cancelSetAsShow: '取消设置为展示属性',
showTips: '服务树和拓扑视图里节点的名称'
},
components: {
unselectAttributes: '未选属性',
@@ -529,7 +533,8 @@ if __name__ == "__main__":
peopleHasRead: '当前有查看权限的人员:',
authorizationPolicy: '实例授权策略:',
idAuthorizationPolicy: '按节点授权的:',
view: '查看权限'
view: '查看权限',
searchTips: '在服务树中筛选'
},
tree: {
tips1: '请先到 我的订阅 页面完成订阅!',

View File

@@ -150,11 +150,11 @@ export default {
computed: {
topoData() {
const ci_types_list = this.ci_types()
const unique_id = this.attributes().unique_id
const unique_name = this.attributes().unique
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
const unique_id = _findCiType.show_id || this.attributes().unique_id
const unique_name = _findCiType.show_name || this.attributes().unique
const _findUnique = this.attrList().find((attr) => attr.id === unique_id)
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
const nodes = {
isRoot: true,
id: `Root_${this.typeId}`,
@@ -183,6 +183,10 @@ export default {
this.parentCITypes.forEach((parent) => {
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
if (this.firstCIs[parent.name]) {
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = parent.attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
this.firstCIs[parent.name].forEach((parentCi) => {
nodes.children.push({
id: `${parentCi._id}`,
@@ -190,9 +194,9 @@ export default {
title: parent.alias || parent.name,
name: parent.name,
side: 'left',
unique_alias: parentCi.unique_alias,
unique_name: parentCi.unique,
unique_value: parentCi[parentCi.unique],
unique_alias,
unique_name,
unique_value: parentCi[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
@@ -222,6 +226,10 @@ export default {
this.childCITypes.forEach((child) => {
const _findCiType = ci_types_list.find((item) => item.id === child.id)
if (this.secondCIs[child.name]) {
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = child.attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
this.secondCIs[child.name].forEach((childCi) => {
nodes.children.push({
id: `${childCi._id}`,
@@ -229,9 +237,9 @@ export default {
title: child.alias || child.name,
name: child.name,
side: 'right',
unique_alias: childCi.unique_alias,
unique_name: childCi.unique,
unique_value: childCi[childCi.unique],
unique_alias,
unique_name,
unique_value: childCi[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
@@ -333,7 +341,6 @@ export default {
secondCIs[item.ci_type] = [item]
}
})
console.log(_.cloneDeep(secondCIs))
this.secondCIs = secondCIs
})
.catch((e) => {})

View File

@@ -15,7 +15,7 @@
position: absolute;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 10px;
border-radius: 2px;
padding: 4px 8px;
width: 100px;
text-align: center;
@@ -74,13 +74,11 @@
}
.root {
width: 100px;
background: #2f54eb;
border: none;
border-radius: 5px;
font-weight: 500;
border-color: @primary-color;
font-weight: 700;
padding: 4px 8px;
.title {
color: #fff;
color: @primary-color;
}
}
}

View File

@@ -10,6 +10,7 @@
import _ from 'lodash'
import { TreeCanvas } from 'butterfly-dag'
import { searchCIRelation } from '@/modules/cmdb/api/CIRelation'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import Node from './node.js'
import 'butterfly-dag/dist/index.css'
@@ -87,7 +88,7 @@ export default {
this.canvas.focusCenterWithAnimate()
})
},
redrawData(res, sourceNode, side) {
async redrawData(res, sourceNode, side) {
const newNodes = []
const newEdges = []
if (!res.result.length) {
@@ -95,18 +96,24 @@ export default {
return
}
const ci_types_list = this.ci_types()
res.result.forEach((r) => {
for (let i = 0; i < res.result.length; i++) {
const r = res.result[i]
if (!this.exsited_ci.includes(r._id)) {
const _findCiType = ci_types_list.find((item) => item.id === r._type)
const { attributes } = await getCITypeAttributesById(_findCiType.id)
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
newNodes.push({
id: `${r._id}`,
Class: Node,
title: r.ci_type_alias || r.ci_type,
name: r.ci_type,
side: side,
unique_alias: r.unique_alias,
unique_name: r.unique,
unique_value: r[r.unique],
unique_alias,
unique_name,
unique_value: r[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
@@ -131,7 +138,8 @@ export default {
targetNode: side === 'right' ? `${r._id}` : sourceNode,
type: 'endpoint',
})
})
}
const { nodes, edges } = this.canvas.getDataMap()
// 删除原节点和边
this.canvas.removeNodes(nodes.map((node) => node.id))

View File

@@ -10,6 +10,7 @@
:class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }"
>
<div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div>
<div class="attribute-card-uniqueKey" v-if="isShowId">{{ $t('cmdb.ciType.show') }}</div>
<template v-if="!isAdd">
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
<div class="attribute-card-content">
@@ -27,6 +28,7 @@
<div
class="attribute-card-trigger"
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
:style="{ top: isShowId ? '18px' : '' }"
>
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
</div>
@@ -64,12 +66,24 @@
</a-space>
</a-popover>
<a-space class="attribute-card-operation" v-if="!inherited">
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
<a-space class="attribute-card-operation">
<a v-if="!isStore && !inherited"><a-icon type="edit" @click="handleEdit"/></a>
<a-tooltip
v-if="
!isStore &&
!isUnique &&
!['6'].includes(property.value_type) &&
!property.is_password &&
!property.is_list
"
:title="$t(isShowId ? 'cmdb.ciType.cancelSetAsShow' : 'cmdb.ciType.setAsShow')"
>
<a><ops-icon type="veops-show" @click="setAsShow"/></a>
</a-tooltip>
<a v-if="!isUnique" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
<a-tooltip v-if="!isStore && property.is_computed" :title="$t('cmdb.ciType.computeForAllCITips')">
<a><a-icon type="redo" @click="handleCalcComputed"/></a>
</a-tooltip>
<a v-if="!isUnique && !inherited" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
</a-space>
</div>
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
@@ -84,17 +98,9 @@
<script>
import { deleteCITypeAttributesById, deleteAttributesById, calcComputedAttribute } from '@/modules/cmdb/api/CITypeAttr'
import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon'
import {
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
} from '@/core/icons'
import { valueTypeMap } from '../../utils/const'
import TriggerForm from './triggerForm.vue'
import { updateCIType } from '@/modules/cmdb/api/CIType'
export default {
name: 'AttributeCard',
inject: {
@@ -102,17 +108,14 @@ export default {
from: 'unique',
default: () => undefined,
},
show_id: {
from: 'show_id',
default: () => undefined,
},
},
components: {
ValueTypeIcon,
TriggerForm,
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
},
props: {
property: {
@@ -146,6 +149,12 @@ export default {
}
return false
},
isShowId() {
if (this.show_id) {
return this.property?.id === this.show_id()
}
return false
},
valueTypeMap() {
return valueTypeMap()
},
@@ -217,6 +226,11 @@ export default {
},
})
},
setAsShow() {
updateCIType(this.CITypeId, { show_id: this.isShowId ? null : this.property?.id }).then((res) => {
this.$emit('ok')
})
},
},
}
</script>

View File

@@ -186,6 +186,7 @@ import {
createCITypeGroupById,
updateCITypeGroupById,
getTriggerList,
getCIType,
} from '@/modules/cmdb/api/CIType'
import {
getCITypeAttributesById,
@@ -231,6 +232,7 @@ export default {
newGroupName: '',
attrTypeFilter: [],
unique: '',
show_id: null,
}
},
computed: {
@@ -250,20 +252,31 @@ export default {
unique: () => {
return this.unique
},
show_id: () => {
return this.show_id
},
}
},
beforeCreate() {},
created() {},
mounted() {
this.getCITypeGroupData()
this.init()
},
methods: {
getPropertyIcon,
init() {
getCIType(this.CITypeId).then((res) => {
if (res?.ci_types && res.ci_types.length) {
this.show_id = res.ci_types[0]?.show_id ?? null
}
})
this.getCITypeGroupData()
},
handleEditProperty(property) {
this.$refs.attributeEditForm.handleEdit(property, this.attributes)
},
handleOk() {
this.getCITypeGroupData()
this.init()
},
setOtherGroupAttributes() {
const orderMap = this.attributes.reduce(function(map, obj) {
@@ -591,7 +604,6 @@ export default {
</script>
<style lang="less" scoped>
.fold {
width: calc(100% - 216px);
display: inline-block;

View File

@@ -53,11 +53,10 @@
</a-menu-item>
<a-menu-item key="1">
<a-space>
<a
href="/api/v0.1/ci_types/template/export/file"
><a-icon type="download" /> {{ $t('download') }}</a
>
</a-space>
<a href="/api/v0.1/ci_types/template/export/file">
<a-icon type="download" /> {{ $t('download') }}
</a></a-space
>
</a-menu-item>
</a-menu>
</a-dropdown>
@@ -262,6 +261,7 @@
'default_order_attr',
{ rules: [{ required: false, message: $t('cmdb.ciType.selectDefaultOrderAttr') }] },
]"
:placeholder="$t('placeholder2')"
>
<el-option
:key="item.name"
@@ -299,6 +299,7 @@
filterInput = ''
}
"
@change="handleChangeUnique"
>
<el-option
:key="item.id"
@@ -313,6 +314,40 @@
<a-divider type="vertical" />
<a @click="handleCreatNewAttr">{{ $t('cmdb.ciType.notfound') }}</a>
</a-form-item>
<a-form-item
:help="$t('cmdb.ciType.showTips')"
:label="$t('cmdb.ciType.show')"
v-if="drawerTitle === $t('cmdb.ciType.editCIType')"
>
<el-select
size="small"
filterable
clearable
name="show_id"
:filter-method="
(input) => {
showIdFilterInput = input
}
"
v-decorator="['show_id', { rules: [{ required: false }] }]"
:placeholder="$t('placeholder2')"
@visible-change="
() => {
showIdFilterInput = ''
}
"
>
<el-option
:key="item.id"
:value="item.id"
v-for="item in showIdSelectOptions"
:label="item.alias || item.name"
>
<span> {{ item.alias || item.name }}</span>
<span :title="item.name" style="font-size: 10px; color: #afafaf"> {{ item.name }}</span>
</el-option>
</el-select>
</a-form-item>
<div v-if="newAttrAreaVisible" :style="{ padding: '15px 8px 0 8px', backgroundColor: '#fafafa' }">
<create-new-attribute
ref="createNewAttribute"
@@ -418,14 +453,17 @@ export default {
addId: null,
filterInput: '',
showIdFilterInput: '',
orderSelectionOptions: [],
currentTypeAttrs: [],
default_order_asc: '1',
allTreeDepAndEmp: [],
editCiType: null,
isInherit: false,
unique_id: null,
}
},
computed: {
@@ -482,6 +520,22 @@ export default {
}
return _attributes
},
orderSelectionOptions() {
return this.currentTypeAttrs.filter((item) => item.is_required)
},
showIdSelectOptions() {
const _showIdSelectOptions = this.currentTypeAttrs.filter(
(item) => item.id !== this.unique_id && !['6'].includes(item.value_type) && !item.is_password && !item.is_list
)
if (this.showIdFilterInput) {
return _showIdSelectOptions.filter(
(item) =>
item.name.toLowerCase().includes(this.showIdFilterInput.toLowerCase()) ||
item.alias.toLowerCase().includes(this.showIdFilterInput.toLowerCase())
)
}
return _showIdSelectOptions
},
},
provide() {
return {
@@ -659,6 +713,7 @@ export default {
delete values.parent_ids
await this.updateCIType(values.id, {
...values,
show_id: values.show_id || null,
icon,
})
} else {
@@ -864,7 +919,8 @@ export default {
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
this.drawerVisible = true
await getCITypeAttributesById(record.id).then((res) => {
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required)
this.currentTypeAttrs = res.attributes
this.unique_id = res.unique_id
})
await getCIType(record.id).then((res) => {
const ci_type = res.ci_types[0]
@@ -877,6 +933,9 @@ export default {
})
})
}
this.form.setFieldsValue({
show_id: ci_type.show_id ?? null,
})
})
this.$nextTick(() => {
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
@@ -941,12 +1000,18 @@ export default {
this.$message.error({ content: this.$t('cmdb.ciType.uploadFailed'), key, duration: 2 })
}
},
handleChangeUnique(value) {
this.unique_id = value
const show_id = this.form.getFieldValue('show_id')
if (show_id === value) {
this.form.setFieldsValue({ show_id: '' })
}
},
},
}
</script>
<style lang="less" scoped>
.ci-types-wrap {
margin: 0 0 -24px 0;
.ci-types-empty {

View File

@@ -11,6 +11,14 @@
<template #one>
<div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }">
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
<a-input
:placeholder="$t('cmdb.serviceTree.searchTips')"
class="relation-views-left-input"
@pressEnter="handleSearchFull"
v-model="fullSearchValue"
>
<a-icon slot="prefix" type="search" />
</a-input>
<div
class="ops-list-batch-action"
:style="{ marginBottom: '10px' }"
@@ -47,6 +55,7 @@
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
</div>
<a-tree
v-if="!isFullSearch"
:selectedKeys="selectedKeys"
:loadData="onLoadData"
:treeData="treeData"
@@ -55,13 +64,10 @@
@drop="onDrop"
:expandedKeys="expandedKeys"
>
<template #title="{ key: treeKey, title,number, isLeaf }">
<template #title="treeNodeData">
<ContextMenu
:title="title"
:number="number"
:treeKey="treeKey"
:treeNodeData="treeNodeData"
:levels="levels"
:isLeaf="isLeaf"
:currentViews="relationViews.views[viewName]"
:id2type="relationViews.id2type"
@onContextMenuClick="onContextMenuClick"
@@ -74,6 +80,30 @@
/>
</template>
</a-tree>
<a-tree
v-else
:treeData="filterFullTreeData"
defaultExpandAll
:selectedKeys="selectedKeys"
:expandedKeys="expandedKeys"
>
<template #title="treeNodeData">
<ContextMenu
:treeNodeData="treeNodeData"
:levels="levels"
:currentViews="relationViews.views[viewName]"
:id2type="relationViews.id2type"
@onContextMenuClick="onContextMenuClick"
@onNodeClick="onNodeClick"
:ciTypeIcons="ciTypeIcons"
:showBatchLevel="showBatchLevel"
:batchTreeKey="batchTreeKey"
@clickCheckbox="clickCheckbox"
@updateTreeData="updateTreeData"
:fullSearchValue="fullSearchValue"
/>
</template>
</a-tree>
</div>
</template>
<template #two>
@@ -342,7 +372,6 @@
total,
})
"
:style="{ alignSelf: 'flex-end', marginRight: '12px' }"
>
<template slot="buildOptionText" slot-scope="props">
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('cmdb.history.itemsPerPage') }}</span>
@@ -392,6 +421,7 @@ import {
batchDeleteCIRelation,
batchUpdateCIRelationChildren,
addCIRelationView,
searchCIRelationFull,
} from '@/modules/cmdb/api/CIRelation'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
@@ -489,9 +519,13 @@ export default {
statisticsObj: {},
viewOption: {},
loadRootStatisticsParams: {},
fullSearchValue: '',
isFullSearch: false,
fullTreeData: [],
filterFullTreeData: [],
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
@@ -500,9 +534,6 @@ export default {
return this.windowHeight - 244
},
selectedKeys() {
if (this.treeKeys.length <= 1) {
return this.treeKeys.map((item) => `@^@${item}`)
}
return [this.treeKeys.join('@^@')]
},
isLeaf() {
@@ -576,7 +607,7 @@ export default {
this.reload()
},
pageNo: function(newPage, oldPage) {
this.loadData({ pageNo: newPage }, undefined, this.sortByTable)
this.loadData({ params: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable })
},
},
@@ -604,7 +635,7 @@ export default {
this.loadData()
},
async loadData(parameter, refreshType = undefined, sortByTable = undefined) {
async loadData({ parameter, refreshType = undefined, sortByTable = undefined } = {}) {
// refreshType='refreshNumber' 树图只更新number
const params = Object.assign(parameter || {}, (this.$refs.search || {}).queryParam)
let q = ''
@@ -624,8 +655,6 @@ export default {
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
if (exp) {
// exp = exp.replace(/(\:)/g, '$1*')
// exp = exp.replace(/(\,)/g, '*$1')
q = `${q},${exp}`
}
@@ -656,38 +685,9 @@ export default {
}
if (this.treeKeys.length === 0) {
// await this.judgeCITypes(q)
if (!refreshType) {
if (!refreshType && !this.isFullSearch) {
await this.loadRoot()
}
// const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
// if (fuzzySearch) {
// q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
// } else {
// q = `q=_type:${this.currentTypeId[0]},` + q
// }
// if (this.currentTypeId[0] && this.treeData && this.treeData.length) {
// // default select first node
// this.onNodeClick(this.treeData[0].key)
// const res = await searchCI2(q)
// const root_id = this.treeData.map((item) => item.id).join(',')
// q += `&root_id=${root_id}`
// this.pageNo = res.page
// this.numfound = res.numfound
// res.result.forEach((item, index) => (item.key = item._id))
// const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
// console.log(jsonAttrList)
// this.instanceList = res['result'].map((item) => {
// jsonAttrList.forEach(
// (jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
// )
// return { ..._.cloneDeep(item) }
// })
// this.initialInstanceList = _.cloneDeep(this.instanceList)
// this.calcColumns()
// }
} else {
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
@@ -726,7 +726,7 @@ export default {
}
q += `&level=${this.topo_flatten.includes(this.currentTypeId[0]) ? 1 : level.join(',')}`
if (!refreshType) {
if (!refreshType && !this.isFullSearch) {
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
}
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
@@ -812,9 +812,6 @@ export default {
this.selectedRowKeys = []
this.currentTypeId = [typeId]
this.loadColumns()
// this.$nextTick(() => {
// this.refreshTable()
// })
},
async judgeCITypes() {
@@ -854,64 +851,6 @@ export default {
this.currentTypeId = [this.showTypeIds[0]]
await this.loadColumns()
}
// const showTypeIds = []
// let _showTypes = []
// let _showTypeIds = []
// if (this.treeKeys.length) {
// const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
// _showTypes = this.node2ShowTypes[typeId + '']
// _showTypes.forEach((item) => {
// _showTypeIds.push(item.id)
// })
// } else {
// _showTypeIds = JSON.parse(JSON.stringify(this.origShowTypeIds))
// _showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
// }
// const promises = _showTypeIds.map((typeId) => {
// let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
// if (Object.values(this.level2constraint).includes('2')) {
// _q = _q + `&has_m2m=1`
// }
// if (this.root_parent_path) {
// _q = _q + `&root_parent_path=${this.root_parent_path}`
// }
// // if (this.treeKeys.length === 0) {
// // return searchCI2(_q).then((res) => {
// // if (res.numfound !== 0) {
// // showTypeIds.push(typeId)
// // }
// // })
// // } else {
// _q = _q + `&descendant_ids=${this.descendant_ids}`
// return searchCIRelation(_q).then((res) => {
// if (res.numfound !== 0) {
// showTypeIds.push(typeId)
// }
// })
// // }
// })
// await Promise.all(promises).then(async () => {
// if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
// const showTypes = []
// _showTypes.forEach((item) => {
// if (showTypeIds.includes(item.id)) {
// showTypes.push(item)
// }
// })
// console.log(showTypes)
// this.showTypes = showTypes
// this.showTypeIds = showTypeIds
// if (
// !this.currentTypeId.length ||
// (this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
// ) {
// this.currentTypeId = [this.showTypeIds[0]]
// await this.loadColumns()
// }
// }
// })
},
async loadRoot() {
@@ -919,29 +858,41 @@ export default {
const facet = []
const ciIds = []
res.result.forEach((item) => {
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
const showName = this.relationViews.id2type[item._type]?.show_name ?? null
facet.push({
showName,
showNameValue: item[showName] ?? null,
uniqueValue: item[item.unique],
number: 0,
ciId: item._id,
typeId: item._type,
unique: item.unique,
})
ciIds.push(item._id)
})
const promises = this.leaf.map((leafId) => {
let level = 0
this.levels.forEach((item, idx) => {
if (item.includes(leafId)) {
level = idx + 1
}
})
return statisticsCIRelation({
root_ids: ciIds.join(','),
level: level,
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => {
facet.forEach((item, idx) => {
item[1] += num[ciIds[idx] + '']
})
const leafId = this.leaf[0]
let level = 0
this.levels.forEach((item, idx) => {
if (item.includes(leafId)) {
level = idx + 1
}
})
const params = {
level,
root_ids: ciIds.join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
}
this.loadRootStatisticsParams = params
await statisticsCIRelation({
...params,
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => {
facet.forEach((item, idx) => {
item.number += num[ciIds[idx] + '']
})
})
await Promise.all(promises)
this.wrapTreeData(facet)
// default select first node
this.onNodeClick(this.treeData[0].key)
@@ -976,7 +927,16 @@ export default {
const facet = []
const ciIds = []
res.result.forEach((item) => {
facet.push([item[item.unique], 0, item._id, item._type, item.unique])
const showName = this.relationViews.id2type[item._type]?.show_name ?? null
facet.push({
showName,
showNameValue: item[showName] ?? null,
uniqueValue: item[item.unique],
number: 0,
ciId: item._id,
typeId: item._type,
unique: item.unique,
})
ciIds.push(item._id)
})
let ancestor_ids
@@ -998,7 +958,7 @@ export default {
descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => {
facet.forEach((item, idx) => {
item[1] += num[ciIds[idx] + '']
item.number += num[ciIds[idx] + '']
})
})
}
@@ -1009,7 +969,7 @@ export default {
}
},
onNodeClick(keys) {
onNodeClick(keys, callback = undefined) {
this.triggerSelect = true
if (keys) {
const _tempKeys = keys.split('@^@').filter((item) => item !== '')
@@ -1028,19 +988,25 @@ export default {
}
this.refreshTable()
if (callback && typeof callback === 'function') {
callback()
}
},
wrapTreeData(facet) {
if (this.triggerSelect) {
return
}
const treeData = []
const _treeKeys = _.cloneDeep(this.treeKeys)
facet.forEach((item) => {
_treeKeys.push(item.ciId + '%' + item.typeId + '%' + `{"${item.unique}":"${item.uniqueValue}"}`)
treeData.push({
title: item[0],
number: item[1],
key: this.treeKeys.join('@^@') + '@^@' + item[2] + '%' + item[3] + '%' + `{"${item[4]}":"${item[0]}"}`,
isLeaf: this.leaf.includes(item[3]),
id: item[2],
title: item.showName ? item.showNameValue : item.uniqueValue,
number: item.number,
key: _treeKeys.join('@^@'),
isLeaf: this.leaf.includes(item.typeId),
id: item.ciId,
showName: item.showName,
})
})
if (this.treeNode === null) {
@@ -1060,7 +1026,6 @@ export default {
}
this.treeKeys = treeNode.eventKey.split('@^@').filter((item) => item !== '')
this.treeNode = treeNode
// this.refreshTable()
resolve()
})
},
@@ -1228,7 +1193,7 @@ export default {
that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve()
that.selectedRowKeys = []
that.loadData({}, 'refreshNumber')
that.loadData({ params: {}, refreshType: 'refreshNumber' })
})
},
})
@@ -1241,9 +1206,7 @@ export default {
const _splitTargetKey = targetKey.split('@^@').filter((item) => item !== '')
if (_splitDragKey.length - 1 === _splitTargetKey.length) {
const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0]
// const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2])
const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0]
// TODO 拖拽这里不造咋弄 等等再说吧
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
this.reload()
})
@@ -1278,7 +1241,7 @@ export default {
this.sortByTable = sortByTable
this.$nextTick(() => {
if (this.pageNo === 1) {
this.loadData({}, undefined, sortByTable)
this.loadData({ params: {}, refreshType: undefined, sortByTable })
} else {
this.pageNo = 1
}
@@ -1437,7 +1400,7 @@ export default {
onOk() {
deleteCI(record.ci_id || record._id).then((res) => {
that.$message.success(that.$t('deleteSuccess'))
that.loadData({}, 'refreshNumber')
that.loadData({ params: {}, refreshType: 'refreshNumber' })
})
},
})
@@ -1457,7 +1420,7 @@ export default {
}
addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => {
setTimeout(() => {
this.loadData({}, 'refreshNumber')
this.loadData({ params: {}, refreshType: 'refreshNumber' })
}, 500)
})
},
@@ -1495,7 +1458,7 @@ export default {
.finally(() => {
that.loading = false
setTimeout(() => {
that.loadData({})
that.loadData({ params: {} })
}, 800)
})
},
@@ -1558,7 +1521,7 @@ export default {
that.selectedRowKeys = []
that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve()
that.loadData({}, 'refreshNumber')
that.loadData({ params: {}, refreshType: 'refreshNumber' })
})
},
})
@@ -1568,7 +1531,6 @@ export default {
},
jsonEditorOk(row, column, jsonData) {
// 后端写数据有快慢不拉接口直接修改table的数据
// this.reloadData()
this.instanceList.forEach((item) => {
if (item._id === row._id) {
item[column.property] = JSON.stringify(jsonData)
@@ -1577,7 +1539,7 @@ export default {
this.$refs.xTable.refreshColumn()
},
relationViewRefreshNumber() {
this.loadData({}, 'refreshNumber')
this.loadData({ params: {}, refreshType: 'refreshNumber' })
},
onShowSizeChange(current, pageSize) {
this.pageSize = pageSize
@@ -1754,11 +1716,9 @@ export default {
return node[i]
}
if (node[i].children && node[i].children.length) {
for (let i = 0; i < node[i].children.length; i++) {
const found = this.findNode(node[i].children, target)
if (found) {
return found
}
const found = this.findNode(node[i].children, target)
if (found) {
return found
}
}
}
@@ -1771,6 +1731,79 @@ export default {
}
this.refreshTable()
},
handleSearchFull(e) {
const value = e.target.value
this.treeKeys = []
this.expandedKeys = []
if (!value) {
this.reload()
return
}
if (this.isFullSearch) {
this.calcFilterFullTreeData()
return
}
searchCIRelationFull({
...this.loadRootStatisticsParams,
type_ids: this.topo_flatten.join(','),
}).then((res) => {
this.isFullSearch = true
this.fullTreeData = this.formatTreeData(res)
this.calcFilterFullTreeData()
})
},
calcFilterFullTreeData() {
const _expandedKeys = []
const predicateCiIds = []
const filterTree = (node, predicate) => {
if (predicate(node)) {
predicateCiIds.push(node.id)
return true
}
if (node.children) {
node.children = node.children.filter((child) => {
if (predicateCiIds.some((id) => child.key.includes(String(id)))) {
return true
}
return filterTree(child, predicate)
})
if (node.children.length && !predicateCiIds.some((id) => node.key.includes(String(id)))) {
_expandedKeys.push(node.key)
}
return node.children.length > 0
}
return false
}
const predicate = (node) =>
String(node.title)
.toLowerCase()
.includes(this.fullSearchValue.toLowerCase())
const _fullTreeData = _.cloneDeep(this.fullTreeData)
this.filterFullTreeData = _fullTreeData.filter((item) => filterTree(item, predicate))
if (this.filterFullTreeData && this.filterFullTreeData.length) {
this.onNodeClick(this.filterFullTreeData[0].key, () => {
this.expandedKeys = _expandedKeys
})
} else {
this.treeKeys = []
this.instanceList = []
}
},
formatTreeData(array, parentKey = '') {
array.forEach((item) => {
const showName = this.relationViews.id2type[item.type_id]?.show_name ?? null
const uniqueName = this.relationViews.id2type[item.type_id]?.unique_name ?? null
const keyList = parentKey.split('@^@').filter((item) => !!item)
keyList.push(item.id + '%' + item.type_id + '%' + `{"${uniqueName}":"${item.uniqueValue}"}`)
const key = keyList.join('@^@')
item.key = key
item.showName = showName
if (!item.isLeaf && item.children && item.children.length) {
item.children = this.formatTreeData(item.children, key)
}
})
return array
},
},
}
</script>
@@ -1819,6 +1852,18 @@ export default {
padding: 0 6px;
}
}
.relation-views-left-input {
margin-bottom: 12px;
input {
background-color: transparent;
border-top: none;
border-right: none;
border-left: none;
}
.ant-input:focus {
box-shadow: none;
}
}
}
.relation-views-right {
width: 100%;

View File

@@ -27,7 +27,13 @@
/>
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
</template>
<span class="relation-views-node-title" v-if="!isEditNodeName" :title="title">{{ title }}</span>
<span
class="relation-views-node-title"
v-if="!isEditNodeName"
:title="title"
v-highlight="{ value: fullSearchValue, class: 'relation-views-node-title-highlight' }"
>{{ title }}
</span>
<a-input
ref="input"
@blur="changeNodeName"
@@ -67,10 +73,7 @@
key="editNodeName"
><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item
>
<a-menu-item
key="batch"
><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item
>
<a-menu-item key="batch"><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item>
</template>
<template v-else>
<a-menu-item
@@ -103,20 +106,17 @@
<script>
import { updateCI } from '../../../api/ci.js'
import highlight from '@/directive/highlight'
export default {
name: 'ContextMenu',
directives: {
highlight,
},
props: {
title: {
type: String,
default: '',
},
number: {
type: Number,
default: 0,
},
treeKey: {
type: String,
default: '',
treeNodeData: {
type: Object,
default: () => {},
},
levels: {
type: Array,
@@ -130,10 +130,6 @@ export default {
type: Object,
default: () => {},
},
isLeaf: {
type: Boolean,
default: () => false,
},
ciTypeIcons: {
type: Object,
default: () => {},
@@ -146,6 +142,10 @@ export default {
type: Array,
default: () => [],
},
fullSearchValue: {
type: String,
default: '',
},
},
data() {
return {
@@ -201,6 +201,21 @@ export default {
showCheckbox() {
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
},
title() {
return this.treeNodeData.title
},
number() {
return this.treeNodeData.number
},
treeKey() {
return this.treeNodeData.key
},
isLeaf() {
return this.treeNodeData.isLeaf
},
showName() {
return this.treeNodeData.showName
},
},
methods: {
onContextMenuClick(treeKey, menuKey) {
@@ -230,8 +245,11 @@ export default {
.split('%')
const unique = Object.keys(JSON.parse(ci[2]))[0]
const ciId = Number(ci[0])
updateCI(ciId, { [unique]: value }).then((res) => {
let editAttrName = unique
if (this.showName) {
editAttrName = this.showName
}
updateCI(ciId, { [editAttrName]: value }).then((res) => {
this.$message.success(this.$t('updateSuccess'))
this.$emit('updateTreeData', ciId, value)
})
@@ -244,7 +262,6 @@ export default {
</script>
<style lang="less" scoped>
.relation-views-node {
width: 100%;
display: inline-flex;
@@ -313,6 +330,9 @@ export default {
</style>
<style lang="less">
.relation-views-node-title-highlight {
color: @func-color_1;
}
.relation-views-left {
ul:has(.relation-views-node-checkbox) > li > ul {
margin-left: 26px;