feat(cmdb-ui):ci type inherit (#404)

This commit is contained in:
dagongren 2024-03-01 13:39:20 +08:00 committed by GitHub
parent f010b9625e
commit 27affe02a8
11 changed files with 390 additions and 87 deletions

View File

@ -205,3 +205,21 @@ export function ciTypeFilterPermissions(type_id) {
method: 'get',
})
}
// parent_ids, child_id
export function postCiTypeInheritance(data) {
return axios({
url: `/v0.1/ci_types/inheritance`,
method: 'post',
data
})
}
// parent_id, child_id
export function deleteCiTypeInheritance(data) {
return axios({
url: `/v0.1/ci_types/inheritance`,
method: 'delete',
data
})
}

View File

@ -0,0 +1,148 @@
<template>
<treeselect
:disabled="disabled"
ref="cmdb_type_select"
:disable-branch-nodes="true"
class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{
'--custom-height': '30px',
lineHeight: '30px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
}"
v-model="currenCiType"
:multiple="multiple"
:clearable="true"
searchable
:options="ciTypeGroup"
value-consists-of="LEAF_PRIORITY"
:placeholder="placeholder || `${$t(`placeholder2`)}`"
:load-options="loadOptions"
@select="
(node, instanceId) => {
$emit('select', node, instanceId)
}
"
@deselect="
(node, instanceId) => {
$emit('deselect', node, instanceId)
}
"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || '其他',
title: node.alias || node.name || '其他',
}
}
"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
<div slot="value-label" slot-scope="{ node }">{{ getTreeSelectLabel(node) }}</div>
</treeselect>
</template>
<script>
import _ from 'lodash'
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { getTreeSelectLabel } from '../../utils/helper'
export default {
name: 'CMDBTypeSelect',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: [String, Number, Array],
default: null,
},
selectType: {
type: String,
default: 'attributes',
},
attrIdkey: {
type: String,
default: 'id',
},
disabled: {
type: Boolean,
default: false,
},
multiple: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: '',
},
},
data() {
return {
ciTypeGroup: [],
childrenOptions: [],
}
},
computed: {
currenCiType: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
return val
},
},
},
async mounted() {
if (this.value) {
const typeId = this.value.split('-')[0]
await getCITypeAttributesById(this.value.split('-')[0]).then((res) => {
this.childrenOptions = res.attributes.map((item) => ({ ...item, id: `${typeId}-${item[this.attrIdkey]}` }))
})
}
this.getCITypeGroups()
},
methods: {
getTreeSelectLabel,
getCITypeGroups() {
getCITypeGroupsConfig({ need_other: true }).then((res) => {
this.ciTypeGroup = res
.filter((item) => item.ci_types && item.ci_types.length)
.map((item) => {
item.id = `type_${item.id || -1}`
item.children = item.ci_types.map((type) => {
const obj = { ...type }
if (this.selectType === 'attributes') {
obj.children = this.value && type.id === Number(this.value.split('-')[0]) ? this.childrenOptions : null
}
return obj
})
return { ..._.cloneDeep(item) }
})
})
},
loadOptions({ action, parentNode, callback }) {
getCITypeAttributesById(parentNode.id).then((res) => {
parentNode.children = res.attributes.map((item) => ({
...item,
id: `${parentNode.id}-${item[this.attrIdkey]}`,
}))
callback()
})
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,2 @@
import CMDBTypeSelect from './cmdbTypeSelect.vue'
export default CMDBTypeSelect

View File

@ -176,7 +176,12 @@ const cmdb_en = {
time: 'Time',
json: 'JSON',
event: 'Event',
reg: 'Regex'
reg: 'Regex',
isInherit: 'Inherit',
inheritType: 'Inherit Type',
inheritTypePlaceholder: 'Please select inherit types',
inheritFrom: 'inherit from {name}',
groupInheritFrom: 'Please go to the {name} for modification'
},
components: {
unselectAttributes: 'Unselected',

View File

@ -176,7 +176,12 @@ const cmdb_zh = {
time: '时间',
json: 'JSON',
event: '事件',
reg: '正则校验'
reg: '正则校验',
isInherit: '是否继承',
inheritType: '继承模型',
inheritTypePlaceholder: '请选择继承模型(多选)',
inheritFrom: '属性继承自{name}',
groupInheritFrom: '请至{name}进行修改'
},
components: {
unselectAttributes: '未选属性',

View File

@ -179,3 +179,13 @@ export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH
// STEP 4: Write Excel file to browser #导出
XLSXS.writeFile(wb, fileName + '.xlsx')
}
export const getAllParentNodesLabel = (node, label) => {
if (node.parentNode) {
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`)
}
return label
}
export const getTreeSelectLabel = (node) => {
return `${getAllParentNodesLabel(node, node.label)}`
}

View File

@ -1,6 +1,6 @@
<template>
<div>
<div class="ci-detail-header">{{ this.type.name }}</div>
<div class="ci-detail-header">{{ this.type.alias }}</div>
<div class="ci-detail-page">
<CiDetailTab ref="ciDetailTab" :typeId="typeId" />
</div>

View File

@ -1,7 +1,11 @@
<template>
<div class="attribute-card">
<div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }">
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
<div class="attribute-card-content">
<div class="attribute-card-value-type-icon handle" :style="{ ...getPropertyStyle(property) }">
<div
:class="{ 'attribute-card-value-type-icon': true, handle: !inherited }"
:style="{ ...getPropertyStyle(property) }"
>
<ValueTypeIcon :attr="property" />
</div>
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
@ -19,6 +23,8 @@
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
</div>
</div>
</a-tooltip>
<div class="attribute-card-footer">
<a-popover
trigger="click"
@ -51,7 +57,7 @@
</a-space>
</a-popover>
<a-space class="attribute-card-operation">
<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>
@ -140,6 +146,9 @@ export default {
},
]
},
inherited() {
return this.property.inherited || false
},
},
methods: {
getPropertyStyle,
@ -211,13 +220,15 @@ export default {
width: 32px;
height: 32px;
font-size: 12px;
cursor: move;
background: #ffffff !important;
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
border-radius: 2px;
text-align: center;
line-height: 32px;
}
.handle {
cursor: move;
}
.attribute-card-content-inner {
padding-left: 12px;
font-weight: 400;
@ -269,6 +280,12 @@ export default {
}
}
}
.attribute-card-inherited {
background: #f3f4f7;
.attribute-card-footer {
background: #eaedf3;
}
}
</style>
<style lang="less">
.attribute-card-footer-popover {

View File

@ -1,6 +1,11 @@
<template>
<div>
<a-modal v-model="addGroupModal" :title="$t('cmdb.ciType.addGroup')" @cancel="handleCancelCreateGroup" @ok="handleCreateGroup">
<a-modal
v-model="addGroupModal"
:title="$t('cmdb.ciType.addGroup')"
@cancel="handleCancelCreateGroup"
@ok="handleCreateGroup"
>
<span>
<a-form-item :label="$t('name')" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-input type="text" v-model.trim="newGroupName" />
@ -9,21 +14,12 @@
</a-modal>
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }">
<a-space style="margin-bottom: 10px">
<a-button
type="primary"
@click="handleAddGroup"
size="small"
class="ops-button-primary"
icon="plus"
>{{ $t('cmdb.ciType.group') }}</a-button
>
<a-button
type="primary"
@click="handleOpenUniqueConstraint"
size="small"
class="ops-button-primary"
>{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button
>
<a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{
$t('cmdb.ciType.group')
}}</a-button>
<a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{
$t('cmdb.ciType.uniqueConstraint')
}}</a-button>
</a-space>
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
<div>
@ -33,7 +29,11 @@
>
<span style="font-weight:700">{{ CITypeGroup.name }}</span>
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
<a @click="handleEditGroupName(index, CITypeGroup)">
<a v-if="!CITypeGroup.inherited" @click="handleEditGroupName(index, CITypeGroup)">
<a-icon type="edit" />
</a>
<a v-else :style="{ cursor: 'not-allowed', color: 'gray' }">
<a-icon type="edit" />
</a>
</div>
@ -71,7 +71,13 @@
</a-tooltip>
<a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template>
<a style="color:red;"><a-icon type="delete" @click="handleDeleteGroup(CITypeGroup)"/></a>
<a
:style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }"
:disabled="CITypeGroup.inherited"
><a-icon
type="delete"
@click="handleDeleteGroup(CITypeGroup)"
/></a>
</a-tooltip>
</a-space>
</div>
@ -82,7 +88,7 @@
@start="drag = true"
@change="
(e) => {
handleChange(e, CITypeGroup.id)
handleChange(e, CITypeGroup.name)
}
"
:filter="'.filter-empty'"
@ -124,7 +130,7 @@
@start="drag = true"
@change="
(e) => {
handleChange(e, -1)
handleChange(e, null)
}
"
:animation="300"
@ -144,18 +150,18 @@
</draggable>
</div>
</div>
<attribute-edit-form
<AttributeEditForm
ref="attributeEditForm"
:CITypeId="CITypeId"
:CITypeName="CITypeName"
@ok="handleOk"
></attribute-edit-form>
<new-ci-type-attr-modal
></AttributeEditForm>
<NewCiTypeAttrModal
ref="newCiTypeAttrModal"
:CITypeId="CITypeId"
:linked-ids="linkedIds"
@ok="handleOk"
></new-ci-type-attr-modal>
></NewCiTypeAttrModal>
<UniqueConstraint ref="uniqueConstraint" :CITypeId="CITypeId" />
</div>
</template>
@ -347,8 +353,8 @@ export default {
},
handleMoveGroup(beforeIndex, afterIndex) {
const fromGroupId = this.CITypeGroups[beforeIndex].id
const toGroupId = this.CITypeGroups[afterIndex].id
const fromGroupId = this.CITypeGroups[beforeIndex].name
const toGroupId = this.CITypeGroups[afterIndex].name
transferCITypeGroupIndex(this.CITypeId, { from: fromGroupId, to: toGroupId }).then((res) => {
this.$message.success(this.$t('operateSuccess'))
const beforeGroup = this.CITypeGroups[beforeIndex]
@ -414,14 +420,14 @@ export default {
})
},
handleChange(e, group) {
console.log('changess')
console.log('changess', group)
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
if (group === -1) {
this.$message.error(this.$t('cmdb.ciType.attributeSortedTips'))
} else {
transferCITypeAttrIndex(this.CITypeId, {
from: { attr_id: e.moved.element.id, group_id: group > -1 ? group : null },
to: { order: e.moved.newIndex, group_id: group > -1 ? group : null },
from: { attr_id: e.moved.element.id, group_name: group },
to: { order: e.moved.newIndex, group_name: group },
})
.then((res) => this.$message.success(this.$t('updateSuccess')))
.catch(() => {
@ -431,14 +437,14 @@ export default {
}
if (e.hasOwnProperty('added')) {
this.addRemoveGroupFlag = { to: { group_id: group > -1 ? group : null, order: e.added.newIndex }, inited: true }
this.addRemoveGroupFlag = { to: { group_name: group, order: e.added.newIndex }, inited: true }
}
if (e.hasOwnProperty('removed')) {
this.$nextTick(() => {
transferCITypeAttrIndex(this.CITypeId, {
from: { attr_id: e.removed.element.id, group_id: group > -1 ? group : null },
to: { group_id: this.addRemoveGroupFlag.to.group_id, order: this.addRemoveGroupFlag.to.order },
from: { attr_id: e.removed.element.id, group_name: group },
to: { group_name: this.addRemoveGroupFlag.to.group_name, order: this.addRemoveGroupFlag.to.order },
})
.then((res) => this.$message.success(this.$t('saveSuccess')))
.catch(() => {

View File

@ -139,6 +139,7 @@
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a>
<a
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
:disabled="ci.inherited"
@click="(e) => handleDownloadCiType(e, ci)"
>
<a-icon type="download" />
@ -176,6 +177,7 @@
placement="right"
width="900px"
:destroyOnClose="true"
:bodyStyle="{ height: 'calc(100vh - 108px)' }"
>
<a-form
:form="form"
@ -204,6 +206,35 @@
<a-form-item :label="$t('alias')">
<a-input name="alias" v-decorator="['alias', { rules: [] }]" />
</a-form-item>
<a-form-item :label="$t('cmdb.ciType.isInherit')">
<a-radio-group v-model="isInherit">
<a-radio :value="true">
</a-radio>
<a-radio :value="false">
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item v-if="isInherit" :label="$t('cmdb.ciType.inheritType')">
<CMDBTypeSelect
multiple
:placeholder="$t('cmdb.ciType.inheritTypePlaceholder')"
v-decorator="[
'parent_ids',
{ rules: [{ required: true, message: $t('cmdb.ciType.inheritTypePlaceholder') }] },
]"
selectType="ci_type"
:class="{
'custom-treeselect': true,
}"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-multiple-lineHeight': '14px',
}"
/>
</a-form-item>
<a-form-item :label="$t('icon')">
<IconArea class="ci_types-icon-area" ref="iconArea" />
</a-form-item>
@ -296,7 +327,14 @@ import router, { resetRouter } from '@/router'
import store from '@/store'
import draggable from 'vuedraggable'
import { Select, Option } from 'element-ui'
import { createCIType, updateCIType, deleteCIType } from '@/modules/cmdb/api/CIType'
import {
createCIType,
updateCIType,
deleteCIType,
getCIType,
postCiTypeInheritance,
deleteCiTypeInheritance,
} from '@/modules/cmdb/api/CIType'
import {
getCITypeGroupsConfig,
postCITypeGroup,
@ -316,6 +354,7 @@ import CMDBGrant from '../../components/cmdbGrant'
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
import AttributeStore from './attributeStore.vue'
import { getAllDepAndEmployee } from '@/api/company'
import CMDBTypeSelect from '../../components/CMDBTypeSelect'
export default {
name: 'CITypes',
@ -330,6 +369,7 @@ export default {
SplitPane,
OpsMoveIcon,
AttributeStore,
CMDBTypeSelect,
},
inject: ['reload'],
data() {
@ -368,6 +408,9 @@ export default {
default_order_asc: '1',
allTreeDepAndEmp: [],
editCiType: null,
isInherit: false,
}
},
computed: {
@ -563,6 +606,7 @@ export default {
this.filterInput = ''
this.form.resetFields()
this.drawerVisible = false
this.isInherit = false
},
handleCreateNewAttrDone() {
this.getAttributes()
@ -583,6 +627,22 @@ export default {
const icon =
_icon && _icon.name ? `${_icon.name}$$${_icon.color || ''}$$${_icon.id || ''}$$${_icon.url || ''}` : ''
if (values.id) {
const { parent_ids: oldP = [] } = this.editCiType
const { parent_ids: newP = [] } = values
const { remove, add } = this.compareArrays(newP, oldP)
if (add && add.length) {
await postCiTypeInheritance({ parent_ids: add, child_id: values.id }).catch(() => {
this.loading = false
})
}
if (remove && remove.length) {
for (let i = 0; i < remove.length; i++) {
await deleteCiTypeInheritance({ parent_id: remove[i], child_id: values.id }).catch(() => {
this.loading = false
})
}
}
delete values.parent_ids
await this.updateCIType(values.id, {
...values,
icon,
@ -593,6 +653,23 @@ export default {
}
})
},
compareArrays(newArr, oldArr) {
const remove = []
const add = []
for (let i = 0; i < oldArr.length; i++) {
const item = oldArr[i]
if (newArr.indexOf(item) === -1) {
remove.push(item)
}
}
for (let i = 0; i < newArr.length; i++) {
const item = newArr[i]
if (oldArr.indexOf(item) === -1) {
add.push(item)
}
}
return { remove, add }
},
start(g) {
console.log('start', g)
this.startId = g.id
@ -767,13 +844,26 @@ export default {
router.addRoutes(store.getters.appRoutes)
})
},
handleEdit(e, record) {
async handleEdit(e, record) {
e.preventDefault()
e.stopPropagation()
this.drawerTitle = this.$t('cmdb.ciType.editCIType')
this.drawerVisible = true
getCITypeAttributesById(record.id).then((res) => {
await getCITypeAttributesById(record.id).then((res) => {
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required)
})
await getCIType(record.id).then((res) => {
const ci_type = res.ci_types[0]
this.editCiType = ci_type ?? null
if (ci_type.parent_ids && ci_type.parent_ids.length) {
this.isInherit = true
this.$nextTick(() => {
this.form.setFieldsValue({
parent_ids: ci_type.parent_ids,
})
})
}
})
this.$nextTick(() => {
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
@ -787,6 +877,7 @@ export default {
? record.default_order_attr.slice(1)
: record.default_order_attr,
})
this.$refs.iconArea.setIcon(
record.icon
? {
@ -798,7 +889,6 @@ export default {
: {}
)
})
})
},
handleCreatNewAttr() {
this.newAttrAreaVisible = !this.newAttrAreaVisible

View File

@ -20,7 +20,9 @@
"
>{{ $t('cancel') }}</a-button
>
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{ $t('cmdb.ciType.continueAdd') }}</a-button>
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{
$t('cmdb.ciType.continueAdd')
}}</a-button>
<a-button :loading="confirmLoading" type="primary" @click="handleSubmit">{{ $t('confirm') }}</a-button>
</template>
<a-tabs v-model="activeKey">
@ -47,7 +49,7 @@
<script>
import _ from 'lodash'
import { searchAttributes, createCITypeAttributes, updateCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { updateCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
import { createCITypeGroupById, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
import CreateNewAttribute from './ceateNewAttribute.vue'
import { valueTypeMap } from '../../utils/const'
import AttributesTransfer from '../../components/attributesTransfer'
@ -102,11 +104,11 @@ export default {
if (this.currentGroup) {
await this.updateCurrentGroup()
const { id, name, order, attributes } = this.currentGroup
const attrIds = attributes.map((i) => i.id)
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
this.targetKeys.forEach((key) => {
attrIds.push(Number(key))
})
await updateCITypeGroupById(id, { name, order, attributes: [...new Set(attrIds)] })
await createCITypeGroupById(this.CITypeId, { name, order, attributes: [...new Set(attrIds)] })
}
this.confirmLoading = false
this.handleClose(isCloseModal)
@ -140,9 +142,9 @@ export default {
if (this.currentGroup) {
await this.updateCurrentGroup()
const { id, name, order, attributes } = this.currentGroup
const attrIds = attributes.map((i) => i.id)
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
attrIds.push(newAttrId)
await updateCITypeGroupById(id, { name, order, attributes: attrIds })
await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })
}
this.confirmLoading = false
this.loadTotalAttrs()