feat(ui): update auto discovery

This commit is contained in:
songlh 2024-07-26 10:40:37 +08:00
parent 1336a24044
commit e4c3a4bee1
28 changed files with 870 additions and 362 deletions

View File

@ -5322,9 +5322,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1721640768584') format('woff2'),
url('iconfont.woff?t=1721640768584') format('woff'),
url('iconfont.ttf?t=1721640768584') format('truetype');
src: url('iconfont.woff2?t=1721959219377') format('woff2'),
url('iconfont.woff?t=1721959219377') format('woff'),
url('iconfont.ttf?t=1721959219377') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1721640768584') format('woff2'),
url('iconfont.woff?t=1721640768584') format('woff'),
url('iconfont.ttf?t=1721640768584') format('truetype');
src: url('iconfont.woff2?t=1721959219377') format('woff2'),
url('iconfont.woff?t=1721959219377') format('woff'),
url('iconfont.ttf?t=1721959219377') format('truetype');
}
.iconfont {

Binary file not shown.

View File

@ -905,6 +905,33 @@ export const multicolorIconList = [
value: 'caise-application',
label: '应用',
list: [{
value: 'caise-data_center',
label: '数据中心'
}, {
value: 'caise-folder',
label: '文件夹'
}, {
value: 'caise-resource_pool',
label: '资源池'
}, {
value: 'caise-network',
label: '网络'
}, {
value: 'caise-distributed_switch',
label: '分布式交换机'
}, {
value: 'caise-standard_switch',
label: '标准式交换机'
}, {
value: 'caise-host_cluster',
label: '主机集群'
}, {
value: 'caise-storage_cluster',
label: '数据存储集群'
}, {
value: 'caise-data_storage',
label: '数据存储'
}, {
value: 'caise-yilianjie',
label: '已连接'
}, {

View File

@ -21,15 +21,15 @@
>
*
</span>
<vxe-select
filterable
clearable
<a-select
v-model="row.attr"
type="text"
:options="ciTypeAttributes"
transfer
:placeholder="$t('cmdb.ciType.attrMapTableAttrPlaceholder')"
></vxe-select>
showSearch
allowClear
:options="ciTypeAttributes"
style="width: 100%; height: 28px; line-height: 28px;"
class="attr-map-table-left-select"
></a-select>
</div>
</template>
</vxe-column>
@ -49,7 +49,7 @@
>
<vxe-column field="name" :title="$t('name')"></vxe-column>
<vxe-column field="type" :title="$t('type')"></vxe-column>
<vxe-column v-if="ruleType !== 'agent'" field="example" :title="$t('cmdb.components.example')">
<vxe-column v-if="ruleType !== DISCOVERY_CATEGORY_TYPE.AGENT" field="example" :title="$t('cmdb.components.example')">
<template #default="{row}">
<span v-if="row.type === 'json'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span>
@ -72,6 +72,8 @@
</template>
<script>
import { DISCOVERY_CATEGORY_TYPE } from '@/modules/cmdb/views/discovery/constants.js'
export default {
name: 'AttrMapTable',
props: {
@ -93,7 +95,9 @@ export default {
}
},
data() {
return {}
return {
DISCOVERY_CATEGORY_TYPE
}
},
methods: {
getTableData() {
@ -123,6 +127,18 @@ export default {
&-left {
width: 30%;
&-select {
/deep/ .ant-select-selection {
height: 28px;
line-height: 28px;
.ant-select-selection__rendered {
height: 28px;
line-height: 28px;
}
}
}
}
&-right {

View File

@ -31,6 +31,7 @@
<script>
import _ from 'lodash'
import { getHttpCategories, getHttpAttributes, getSnmpAttributes, getHttpAttrMapping } from '../../api/discovery'
import { DISCOVERY_CATEGORY_TYPE } from '@/modules/cmdb/views/discovery/constants.js'
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
import ADPreviewTable from './adPreviewTable.vue'
import HttpADCategory from './httpADCategory.vue'
@ -101,7 +102,7 @@ export default {
}
},
isCloud() {
return ['http', 'private_cloud'].includes(this.ruleType)
return [DISCOVERY_CATEGORY_TYPE.HTTP, DISCOVERY_CATEGORY_TYPE.PRIVATE_CLOUD].includes(this.ruleType)
}
},
watch: {
@ -119,7 +120,7 @@ export default {
this.currentCate = ''
this.$nextTick(() => {
const { ruleType, ruleName } = newVal
if (['snmp', 'components'].includes(ruleType) && ruleName) {
if ([DISCOVERY_CATEGORY_TYPE.SNMP, DISCOVERY_CATEGORY_TYPE.COMPONENT].includes(ruleType) && ruleName) {
getSnmpAttributes(ruleType, ruleName).then((res) => {
if (this.isEdit) {
this.formatTableData(res)

View File

@ -1,53 +1,27 @@
<template>
<div class="node-setting-wrap">
<a-row v-for="(node) in nodes" :key="node.id">
<a-col :span="6">
<a-form-item :label="$t('cmdb.ciType.nodeSettingIp')">
<a-input
allowClear
v-decorator="[
`node_ip_${node.id}`,
{
rules: [
{ required: false, message: $t('cmdb.ciType.nodeSettingIpTip') },
{
pattern:
'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
message: $t('cmdb.ciType.nodeSettingIpTip1'),
trigger: 'blur',
},
],
},
]"
:placeholder="$t('cmdb.ciType.nodeSettingIpTip')"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item :label="$t('cmdb.ciType.nodeSettingCommunity')">
<a-input
allowClear
v-decorator="[
`node_community_${node.id}`,
{
rules: [{ required: false, message: $t('cmdb.ciType.nodeSettingCommunityTip') }],
},
]"
:placeholder="$t('cmdb.ciType.nodeSettingCommunityTip')"
/>
</a-form-item>
</a-col>
<a-col :span="5">
<a-form-item :label="$t('cmdb.ciType.nodeSettingVersion')">
<ops-table
:data="nodes"
size="mini"
show-header-overflow
:row-config="{ height: 42 }"
border
:min-height="78"
>
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingIp')">
<template #default="{ row }">
<a-input v-model="row.ip"></a-input>
</template>
</vxe-column>
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingCommunity')">
<template #default="{ row }">
<a-input v-model="row.community"></a-input>
</template>
</vxe-column>
<vxe-column width="170" :title="$t('cmdb.ciType.nodeSettingVersion')">
<template #default="{ row }">
<a-select
v-decorator="[
`node_version_${node.id}`,
{
rules: [{ required: false, message: $t('cmdb.ciType.nodeSettingVersionTip') }],
},
]"
v-model="row.version"
:placeholder="$t('cmdb.ciType.nodeSettingVersionTip')"
allowClear
class="node-setting-select"
@ -58,26 +32,25 @@
<a-select-option value="2c">
v2c
</a-select-option>
<a-select-option value="3">
v3
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="3">
<div class="action">
<a @click="() => copyNode(node.id)">
<a-icon type="copy" />
</a>
<a @click="() => removeNode(node.id, 1)">
<a-icon type="minus-circle" />
</a>
<a @click="addNode">
<a-icon type="plus-circle" />
</a>
</div>
</a-col>
</a-row>
</template>
</vxe-column>
<vxe-column wdith="170">
<template #default="{ row }">
<div class="action">
<a @click="() => copyNode(row.id)">
<a-icon type="copy" />
</a>
<a @click="() => removeNode(row.id, 1)">
<a-icon type="minus-circle" />
</a>
<a @click="addNode">
<a-icon type="plus-circle" />
</a>
</div>
</template>
</vxe-column>
</ops-table>
</div>
</template>
@ -110,17 +83,10 @@ export default {
const newNode = {
id: uuidv4(),
ip: '',
community: '',
community: 'public',
version: '',
}
this.nodes.push(newNode)
this.$nextTick(() => {
this.form.setFieldsValue({
[`node_ip_${newNode.id}`]: newNode.ip,
[`node_community_${newNode.id}`]: newNode.community,
[`node_version_${newNode.id}`]: newNode.version,
})
})
},
removeNode(removeId, minLength) {
if (this.nodes.length <= minLength) {
@ -133,45 +99,20 @@ export default {
}
},
copyNode(id) {
const newNode = {
id: uuidv4(),
ip: this.form.getFieldValue(`node_ip_${id}`),
community: this.form.getFieldValue(`node_community_${id}`),
version: this.form.getFieldValue(`node_version_${id}`),
}
this.nodes.push(newNode)
this.$nextTick(() => {
this.form.setFieldsValue({
[`node_ip_${newNode.id}`]: newNode.ip,
[`node_community_${newNode.id}`]: newNode.community,
[`node_version_${newNode.id}`]: newNode.version,
})
})
},
getInfoValuesFromForm(values) {
return this.nodes.map((item) => {
return {
id: item.id,
ip: values[`node_ip_${item.id}`],
community: values[`node_community_${item.id}`],
version: values[`node_version_${item.id}`],
const copyNode = this.nodes.find((item) => item.id === id)
if (copyNode) {
const newNode = {
...copyNode,
id: uuidv4(),
}
})
},
setNodeField() {
if (this.nodes && this.nodes.length) {
this.nodes.forEach((item) => {
this.form.setFieldsValue({
[`node_ip_${item.id}`]: item.ip,
[`node_community_${item.id}`]: item.community,
[`node_version_${item.id}`]: item.version,
})
})
this.nodes.push(newNode)
}
},
getNodeValue() {
const values = this.form.getFieldsValue()
return this.getInfoValuesFromForm(values)
const nodes = this.nodes.map((node) => {
return _.pick(node, ['ip', 'community', 'version'])
})
return nodes
},
},
}
@ -180,10 +121,9 @@ export default {
<style lang="less" scoped>
.node-setting-wrap {
margin-left: 17px;
width: 600px;
.ant-row {
// display: flex;
/deep/ .ant-input-clear-icon {
color: rgba(0,0,0,.25);

View File

@ -236,7 +236,7 @@ const cmdb_en = {
checkModalColumn4: 'Last checkup time',
testModalTitle: 'Automated discovery testing',
attrMapTableAttrPlaceholder: 'Please edit the name',
nodeSettingIp: 'IP Addresses',
nodeSettingIp: 'Network device IP address',
nodeSettingIpTip: 'Please enter the ip address',
nodeSettingIpTip1: 'ip address format error',
nodeSettingCommunity: 'Community',
@ -264,6 +264,18 @@ const cmdb_en = {
resourceSearchTip3: 'Note 2: If you do not need to filter, please click the grey button to copy and paste directly to configure for all nodes',
enable: 'Enable',
enableTip: 'Confirm switching on?',
portScanConfig: 'Port Scan Config',
portScanLabel1: 'CIDR',
portScanLabel2: 'Port Range',
portScanLabel3: 'AgentID',
viewAllAttr: 'View All Prop',
attrGroup: 'Attr Group',
attrName: 'Attr Name',
attrAlias: 'Attr Alias',
attrCode: 'Attr Code',
computedAttrTip1: 'Reference attributes follow jinja2 syntax',
computedAttrTip2: `Multi-valued attributes (lists) are rendered with [ ] included by default, if you want to remove it, the reference method is: {{ attr_name | join(,)}}} where commas are separators`,
example: 'Example'
},
components: {
unselectAttributes: 'Unselected',

View File

@ -236,7 +236,7 @@ const cmdb_zh = {
checkModalColumn4: '最近检查时间',
testModalTitle: '自动发现测试',
attrMapTableAttrPlaceholder: '请编辑名称',
nodeSettingIp: 'ip地址',
nodeSettingIp: '网络设备IP地址',
nodeSettingIpTip: '请输入 ip 地址',
nodeSettingIpTip1: 'ip地址格式错误',
nodeSettingCommunity: 'Community',
@ -264,6 +264,18 @@ const cmdb_zh = {
resourceSearchTip3: '注2如不需要筛选请直接点击灰色按钮进行复制粘贴即可配置为所有节点',
enable: '开启',
enableTip: '确定切换开启状态吗',
portScanConfig: '端口扫描配置',
portScanLabel1: 'CIDR',
portScanLabel2: '端口范围',
portScanLabel3: 'AgentID',
viewAllAttr: '查看所有属性',
attrGroup: '属性分组',
attrName: '属性名称',
attrAlias: '属性别名',
attrCode: '属性代码',
computedAttrTip1: '引用属性遵循jinja2语法',
computedAttrTip2: `多值属性(列表)默认呈现包括[ ], 如果要去掉, 引用方法为: """{{ attr_name | join(',')}}""" 其中逗号为分隔符`,
example: '例如'
},
components: {
unselectAttributes: '未选属性',

View File

@ -140,6 +140,7 @@
:mode="col.is_list ? 'multiple' : 'default'"
class="ci-table-edit-select"
allowClear
showSearch
>
<a-select-option
:value="choice[0]"
@ -161,7 +162,7 @@
:type="choice[1].icon.name"
/>
</template>
{{ choice[0] }}
<span>{{ choice[0] }}</span>
</span>
</a-select-option>
</a-select>
@ -199,6 +200,7 @@
borderRadius: '4px',
padding: '1px 5px',
margin: '2px',
verticalAlign: 'bottom',
...getChoiceValueStyle(col, value),
display: 'inline-flex',
alignItems: 'center',
@ -210,7 +212,7 @@
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/>
<ops-icon
v-else
v-else-if="getChoiceValueIcon(col, value).name"
:style="{ color: getChoiceValueIcon(col, value).color, marginRight: '5px' }"
:type="getChoiceValueIcon(col, value).name"
/>{{ value }}
@ -222,6 +224,7 @@
borderRadius: '4px',
padding: '1px 5px',
margin: '2px 0',
verticalAlign: 'bottom',
...getChoiceValueStyle(col, row[col.field]),
display: 'inline-flex',
alignItems: 'center',
@ -233,7 +236,7 @@
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/>
<ops-icon
v-else
v-else-if="getChoiceValueIcon(col, row[col.field]).name"
:style="{ color: getChoiceValueIcon(col, row[col.field]).color, marginRight: '5px' }"
:type="getChoiceValueIcon(col, row[col.field]).name"
/>

View File

@ -105,7 +105,7 @@ export default {
default: true,
},
attrList: {
type: Array,
type: Function,
default: () => [],
}
},

View File

@ -132,6 +132,10 @@ export default {
type: Object,
default: () => {},
},
initQueryLoading: {
type: Boolean,
default: false,
}
},
data() {
return {
@ -145,129 +149,13 @@ export default {
firstCIJsonAttr: {},
secondCIJsonAttr: {},
canEdit: {},
topoData: {
nodes: {},
edges: []
}
}
},
computed: {
topoData() {
const ci_types_list = this.ci_types()
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 nodes = {
isRoot: true,
id: `Root_${this.typeId}`,
title: _findCiType.alias || _findCiType.name, // 中文名
name: _findCiType.name, // 英文名
Class: Node,
unique_alias,
unique_name,
unique_value: this.ci[unique_name],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
children: [],
}
const edges = []
this.parentCITypes.forEach((parent) => {
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
if (this.firstCIs[parent.name] && _findCiType) {
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}`,
Class: Node,
title: parent.alias || parent.name,
name: parent.name,
side: 'left',
unique_alias,
unique_name,
unique_value: parentCi[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
})
edges.push({
id: `${parentCi._id}_Root`,
source: 'right',
target: 'left',
sourceNode: `${parentCi._id}`,
targetNode: `Root_${this.typeId}`,
type: 'endpoint',
})
})
}
})
this.childCITypes.forEach((child) => {
const _findCiType = ci_types_list.find((item) => item.id === child.id)
if (this.secondCIs[child.name] && _findCiType) {
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}`,
Class: Node,
title: child.alias || child.name,
name: child.name,
side: 'right',
unique_alias,
unique_name,
unique_value: childCi[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
})
edges.push({
id: `Root_${childCi._id}`,
source: 'right',
target: 'left',
sourceNode: `Root_${this.typeId}`,
targetNode: `${childCi._id}`,
type: 'endpoint',
})
})
}
})
return { nodes, edges }
},
exsited_ci() {
const _exsited_ci = [this.ciId]
this.parentCITypes.forEach((parent) => {
@ -297,20 +185,28 @@ export default {
},
},
mounted() {
this.init(true)
if (!this.initQueryLoading) {
this.init(true)
}
},
methods: {
async init(isFirst) {
await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
if (isFirst && this.$refs.ciDetailRelationTopo) {
const ci_types_list = this.ci_types()
this.handleTopoData()
if (
isFirst &&
this.$refs.ciDetailRelationTopo &&
ci_types_list.length
) {
this.$refs.ciDetailRelationTopo.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
}
})
},
async getFirstCIs() {
await searchCIRelation(`root_id=${Number(this.ciId)}&&level=1&&reverse=1&&count=10000`)
await searchCIRelation(`root_id=${Number(this.ciId)}&level=1&reverse=1&count=10000`)
.then((res) => {
const firstCIs = {}
res.result.forEach((item) => {
@ -328,7 +224,7 @@ export default {
.catch((e) => {})
},
async getSecondCIs() {
await searchCIRelation(`root_id=${Number(this.ciId)}&&level=1&&reverse=0&&count=10000`)
await searchCIRelation(`root_id=${Number(this.ciId)}&level=1&reverse=0&count=10000`)
.then((res) => {
const secondCIs = {}
res.result.forEach((item) => {
@ -445,6 +341,137 @@ export default {
})
}
},
handleTopoData() {
const ci_types_list = this.ci_types()
if (!ci_types_list?.length) {
this.$set(this, 'topoData', {
nodes: {},
edges: []
})
return
}
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 nodes = {
isRoot: true,
id: `Root_${this.typeId}`,
title: _findCiType.alias || _findCiType.name, // 中文名
name: _findCiType.name, // 英文名
Class: Node,
unique_alias,
unique_name,
unique_value: this.ci[unique_name],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
children: [],
}
const edges = []
this.parentCITypes.forEach((parent) => {
const _findCiType = ci_types_list.find((item) => item.id === parent.id)
if (this.firstCIs[parent.name] && _findCiType) {
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}`,
Class: Node,
title: parent.alias || parent.name,
name: parent.name,
side: 'left',
unique_alias,
unique_name,
unique_value: parentCi[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
})
edges.push({
id: `${parentCi._id}_Root`,
source: 'right',
target: 'left',
sourceNode: `${parentCi._id}`,
targetNode: `Root_${this.typeId}`,
type: 'endpoint',
})
})
}
})
this.childCITypes.forEach((child) => {
const _findCiType = ci_types_list.find((item) => item.id === child.id)
if (this.secondCIs[child.name] && _findCiType) {
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}`,
Class: Node,
title: child.alias || child.name,
name: child.name,
side: 'right',
unique_alias,
unique_name,
unique_value: childCi[unique_name],
children: [],
icon: _findCiType?.icon || '',
endpoints: [
{
id: 'left',
orientation: [-1, 0],
pos: [0, 0.5],
},
{
id: 'right',
orientation: [1, 0],
pos: [0, 0.5],
},
],
})
edges.push({
id: `Root_${childCi._id}`,
source: 'right',
target: 'left',
sourceNode: `Root_${this.typeId}`,
targetNode: `${childCi._id}`,
type: 'endpoint',
})
})
}
})
this.$set(this, 'topoData', {
nodes,
edges
})
}
},
}
</script>

View File

@ -17,7 +17,7 @@
border: 1px solid #d9d9d9;
border-radius: 2px;
padding: 4px 8px;
width: 100px;
width: auto;
text-align: center;
.title {
font-size: 16px;
@ -73,7 +73,7 @@
}
}
.root {
width: 100px;
width: auto;
border-color: @primary-color;
font-weight: 700;
padding: 4px 8px;

View File

@ -29,6 +29,10 @@ export default {
methods: {
init() {
const root = document.getElementById('ci-detail-relation-topo')
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
context.font = '16px'
this.canvas = new TreeCanvas({
root: root,
disLinkable: false, // 可删除连线
@ -54,7 +58,15 @@ export default {
return 10
},
getWidth(d) {
return 40
const metrics = context.measureText(d?.title || '')
const width = metrics.width
/**
* width 文字宽度
* 20 icon 宽度
* 4 盒子内边距
* 40 节点间距
*/
return width + 20 + 4 + 40
},
getHGap(d) {
return 80
@ -69,22 +81,27 @@ export default {
this.canvas.on('events', ({ type, data }) => {
const sourceNode = data?.id || null
if (type === 'custom:clickLeft') {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => {
searchCIRelation(`root_id=${Number(sourceNode)}&level=1&reverse=1&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'left')
})
}
if (type === 'custom:clickRight') {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => {
searchCIRelation(`root_id=${Number(sourceNode)}&level=1&reverse=0&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'right')
})
}
})
},
setTopoData(data) {
const root = document.getElementById('ci-detail-relation-topo')
if (root && root?.innerHTML) {
root.innerHTML = ''
}
this.canvas = null
this.init()
this.topoData = _.cloneDeep(data)
this.canvas.draw(data, {}, () => {
this.canvas.redraw(data, {}, () => {
this.canvas.focusCenterWithAnimate()
})
},

View File

@ -28,7 +28,7 @@
<a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
<ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
<ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" :initQueryLoading="initQueryLoading" />
</div>
</a-tab-pane>
<a-tab-pane key="tab_3">
@ -181,6 +181,7 @@ export default {
hasPermission: true,
itsmInstalled: true,
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
initQueryLoading: true,
}
},
computed: {
@ -218,6 +219,7 @@ export default {
},
methods: {
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.initQueryLoading = true
this.activeTabKey = activeTabKey
if (activeTabKey === 'tab_2') {
this.$nextTick(() => {
@ -230,10 +232,13 @@ export default {
if (this.hasPermission) {
this.getAttributes()
this.getCIHistory()
getCITypes().then((res) => {
this.ci_types = res.ci_types
})
const ciTypeRes = await getCITypes()
this.ci_types = ciTypeRes.ci_types
if (this.activeTabKey === 'tab_2') {
this.$refs.ciDetailRelation.init(true)
}
}
this.initQueryLoading = false
},
getAttributes() {
getCITypeGroupById(this.typeId, { need_other: 1 })

View File

@ -0,0 +1,140 @@
<template>
<CustomDrawer
:title="$t('cmdb.ciType.viewAllAttr')"
:visible="visible"
placement="right"
width="800"
:bodyStyle="{ height: '100vh' }"
@close="handleClose"
>
<vxe-table
resizable
size="mini"
:span-method="mergeRowMethod"
:data="tableData"
show-overflow
show-header-overflow
border
class="ops-stripe-table"
:height="windowHeight - 160"
>
<vxe-table-column align="center" field="groupId" :title="$t('cmdb.ciType.attrGroup')" :width="100">
<template #default="{row}">
<span>{{ row.groupName }}</span>
</template>
</vxe-table-column>
<vxe-table-column field="name" :title="$t('cmdb.ciType.attrName')" :width="150"></vxe-table-column>
<vxe-table-column field="alias" :title="$t('cmdb.ciType.attrAlias')" :width="150"></vxe-table-column>
<vxe-table-column field="typeText" :title="$t('type')" :width="100"></vxe-table-column>
<vxe-table-column field="code" :title="$t('cmdb.ciType.attrCode')">
<template #default="{row}">
<a @click="copyText(row.code)" >{{ row.code }}</a>
</template>
</vxe-table-column>
</vxe-table>
</CustomDrawer>
</template>
<script>
import { mapState } from 'vuex'
import _ from 'lodash'
import { valueTypeMap } from '@/modules/cmdb/utils/const'
export default {
name: 'AllAttrDrawer',
data() {
return {
visible: false,
tableData: [],
}
},
inject: ['providerGroupsData'],
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
},
methods: {
async open() {
this.visible = true
const tableData = []
const typeMap = valueTypeMap()
const providerGroupsData = _.cloneDeep(this.providerGroupsData() || {})
const groupsData = providerGroupsData?.CITypeGroups || []
const otherAttrData = providerGroupsData?.otherGroupAttributes || []
groupsData.forEach((group) => {
if (group?.attributes?.length) {
const attrArr = group.attributes.map((attr) => {
if (attr.is_password) {
attr.value_type = '7'
}
if (attr.is_link) {
attr.value_type = '8'
}
attr.groupId = group.id
attr.groupName = group.name
attr.code = ['0', '1', '6'].includes(attr.value_type) ? `{{ ${attr.name} }}` : `'''{{ ${attr.name} }}'''`
attr.typeText = typeMap?.[attr.value_type] ?? ''
return attr
})
tableData.push(...attrArr)
}
})
otherAttrData.forEach((attr) => {
if (attr.is_password) {
attr.value_type = '7'
}
if (attr.is_link) {
attr.value_type = '8'
}
attr.groupId = -1
attr.groupName = '其他'
attr.code = `{{ ${attr.name} }}`
attr.typeText = typeMap?.[attr.value_type] ?? ''
})
tableData.push(...otherAttrData)
this.tableData = tableData
},
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const fields = ['groupId']
const currentValue = row.groupId
if (currentValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow.groupId === currentValue) {
return { rowspan: 0, colspan: 0 }
} else {
let countRowspan = 1
while (nextRow && nextRow.groupId === currentValue) {
nextRow = visibleData[++countRowspan + _rowIndex]
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
}
}
}
},
handleClose() {
this.visible = false
},
copyText(text) {
this.$copyText(text)
.then(() => {
this.$message.success(this.$t('copySuccess'))
})
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,156 @@
<template>
<a-row class="attr-ad-form">
<a-col :span="24">
<a-form-item
label="CIDR"
:labelCol="labelCol"
:wrapperCol="{ span: 18 }"
labelAlign="right"
style="width: 100%; margin-top: 20px"
>
<div class="cidr-tag">
<div
v-for="(item) in list"
:key="item.id"
class="cidr-tag-item"
>
<a-tooltip :title="item.value">
<span class="cidr-tag-text">{{ item.value }}</span>
</a-tooltip>
<a-icon
class="cidrv-tag-close"
type="close"
@click.stop="clickClose(item.id)"
/>
</div>
<a-input
v-if="showAddInput"
class="cidr-tag-input"
autofocus
@blur="addPreValue"
@pressEnter="showAddInput = false"
></a-input>
<a v-else class="cidr-tag-add" @click="showAddInput = true">+ {{ $t('new') }}</a>
</div>
</a-form-item>
</a-col>
</a-row>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
export default {
name: 'CIDRTags',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Array,
default: () => [],
},
},
data() {
return {
showAddInput: false,
}
},
inject: ['provide_labelCol'],
computed: {
list: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
methods: {
clickClose(id) {
const list = _.cloneDeep(this.value)
const index = list.findIndex((item) => item.id === id)
if (index !== -1) {
list.splice(index, 1)
this.$emit('change', list)
}
},
addPreValue(e) {
this.showAddInput = false
const val = e.target.value
if (!val) {
return
}
const list = _.cloneDeep(this.value)
list.push({
value: val,
id: uuidv4()
})
this.$emit('change', list)
}
}
}
</script>
<style lang="less" scoped>
.cidr-tag {
width: max-content;
max-width: 100%;
padding: 6px 9px;
border-radius: 2px;
border: 1px solid #E4E7ED;
background: #FFF;
display: flex;
flex-wrap: wrap;
gap: 8px;
&-item {
padding: 3px 6px;
background-color: #F0F5FF;
display: flex;
align-items: center;
}
&-text {
font-size: 12px;
font-weight: 400;
color: #1D2129;
line-height: 18px;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
max-width: 100px;
overflow: hidden;
}
&-close {
font-size: 12px;
color: #1D2129;
margin-left: 4px;
cursor: pointer;
}
&-input {
max-width: 120px;
height: 26px;
line-height: 26px;
padding: 3px 6px;
}
&-add {
border: dashed 1px #e4e7ed;
padding: 3px 6px;
font-size: 12px;
font-weight: 400;
color: #1D2129;
line-height: 18px;
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<a-form-model
:model="formData"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item :extra="`${$t('cmdb.ciType.example')}: 192.168.0.0/16`" :label="$t('cmdb.ciType.portScanLabel1')">
<a-input v-model="formData.cidr" />
</a-form-model-item>
<a-form-model-item :extra="`${$t('cmdb.ciType.example')}: 8000-8800`" :label="$t('cmdb.ciType.portScanLabel2')">
<a-input v-model="formData.ports" />
</a-form-model-item>
<a-form-model-item :extra="`${$t('cmdb.ciType.example')}: 0x1234`" :label="$t('cmdb.ciType.portScanLabel3')">
<a-input v-model="formData.enable_cidr" />
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
name: 'PortScanConfig',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Object,
default: () => {},
},
},
inject: ['provide_labelCol'],
computed: {
formData: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
}
</script>
<style lang="less" scoped>
</style>

View File

@ -6,7 +6,7 @@
/>
<a-form-model
:model="formData"
labelAlign="left"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"

View File

@ -6,7 +6,7 @@
/>
<a-form-model
:model="formData"
labelAlign="left"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"

View File

@ -32,7 +32,7 @@
</div>
<div class="attr-ad-attributemap-main">
<AttrMapTable
v-if="adrType === 'agent'"
v-if="adrType === DISCOVERY_CATEGORY_TYPE.AGENT"
ref="attrMapTable"
:ruleType="adrType"
:tableData="tableData"
@ -53,17 +53,18 @@
:style="{ marginBottom: '20px' }"
/>
</div>
<template v-if="adrType === 'snmp'">
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.SNMP">
<div class="attr-ad-header">{{ $t('cmdb.ciType.nodeConfig') }}</div>
<a-form :form="form3" layout="inline" class="attr-ad-snmp-form">
<NodeSetting ref="nodeSetting" :initNodes="nodes" :form="form3" />
<a-form :form="nodeSettingForm" layout="inline" class="attr-ad-snmp-form">
<NodeSetting ref="nodeSetting" :initNodes="nodes" />
<CIDRTags v-model="cidrList" />
</a-form>
</template>
<div class="attr-ad-header">{{ $t('cmdb.ciType.adExecConfig') }}</div>
<a-form-model
:model="form"
:labelCol="labelCol"
labelAlign="left"
labelAlign="right"
:wrapperCol="{ span: 14 }"
class="attr-ad-form"
>
@ -127,7 +128,7 @@
</el-popover>
</a-form-model-item>
</a-form-model>
<template v-if="adrType === 'http'">
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.HTTP">
<template v-if="isPrivateCloud">
<template v-if="privateCloudName === PRIVATE_CLOUD_NAME.VCenter">
<div class="attr-ad-header">{{ $t('cmdb.ciType.privateCloud') }}</div>
@ -142,12 +143,17 @@
<div class="attr-ad-header">{{ $t('cmdb.ciType.cloudAccessKey') }}</div>
<!-- <div class="public-cloud-info">{{ $t('cmdb.ciType.cloudAccessKeyTip') }}</div> -->
<PublicCloud
v-model="form2"
v-model="publicCloudForm"
ref="httpForm"
/>
</template>
</template>
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.COMPONENT">
<div class="attr-ad-header">{{ $t('cmdb.ciType.portScanConfig') }}</div>
<PortScanConfig v-model="portScanConfigForm" />
</template>
<AttrADTest
:adtId="currentAdt.id"
/>
@ -165,7 +171,7 @@ import { v4 as uuidv4 } from 'uuid'
import { mapState } from 'vuex'
import Vcrontab from '@/components/Crontab'
import { putCITypeDiscovery, postCITypeDiscovery } from '../../api/discovery'
import { PRIVATE_CLOUD_NAME } from '@/modules/cmdb/views/discovery/constants.js'
import { DISCOVERY_CATEGORY_TYPE, PRIVATE_CLOUD_NAME } from '@/modules/cmdb/views/discovery/constants.js'
import { TAB_KEY } from './attrAD/constants.js'
import HttpSnmpAD from '../../components/httpSnmpAD'
@ -176,6 +182,8 @@ import AttrADTest from './attrADTest.vue'
import { Popover } from 'element-ui'
import VcenterForm from './attrAD/privateCloud/vcenterForm.vue'
import PublicCloud from './attrAD/publicCloud/index.vue'
import PortScanConfig from './attrAD/portScanConfig/index.vue'
import CIDRTags from './attrAD/cidrTags/index.vue'
export default {
name: 'AttrADTabpane',
@ -188,7 +196,9 @@ export default {
AttrADTest,
ElPopover: Popover,
VcenterForm,
PublicCloud
PublicCloud,
PortScanConfig,
CIDRTags
},
props: {
adr_id: {
@ -229,7 +239,7 @@ export default {
query_expr: '',
enabled: true,
},
form2: {
publicCloudForm: {
key: '',
secret: '',
_reference: '',
@ -244,25 +254,31 @@ export default {
_reference: '',
tabActive: TAB_KEY.CUSTOM,
},
interval: 'cron', // interval cron
portScanConfigForm: {
cidr: '',
ports: '',
enable_cidr: '',
},
cron: '',
cronVisible: false,
intervalValue: 3,
agent_type: 'agent_id',
nodes: [
{
id: uuidv4(),
ip: '',
community: '',
community: 'public',
version: '',
},
],
form3: this.$form.createForm(this, { name: 'snmp_form' }),
cronVisible: false,
nodeSettingForm: this.$form.createForm(this, { name: 'snmp_form' }),
uniqueKey: '',
isPrivateCloud: false,
privateCloudName: '',
PRIVATE_CLOUD_NAME,
DISCOVERY_CATEGORY_TYPE,
isClient: false, // 是否前端新增临时数据
cidrList: [],
}
},
provide() {
@ -293,26 +309,28 @@ export default {
]
const permissions = this?.user?.roles?.permissions
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType === 'agent') {
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
radios.unshift({ value: 'all', label: this.$t('cmdb.ciType.allNodes') })
}
if (this.adrType !== 'agent' || this?.currentAdr?.is_plugin) {
if (this.adrType !== DISCOVERY_CATEGORY_TYPE.AGENTv || this?.currentAdr?.is_plugin) {
radios.unshift({ value: 'master', label: this.$t('cmdb.ciType.masterNode') })
}
return radios
},
radioList() {
return [
{ value: 'interval', label: this.$t('cmdb.ciType.byInterval') },
{ value: 'cron', label: '按cron', layout: 'vertical' },
]
},
labelCol() {
const span = this.$i18n.locale === 'en' ? 5 : 3
const isEn = this.$i18n.locale === 'en'
return {
span
xl: {
span: isEn ? 4 : 2
},
lg: {
span: isEn ? 5 : 3
},
sm: {
span: isEn ? 6 : 4
}
}
}
},
@ -324,7 +342,7 @@ export default {
this.uniqueKey = _find?.unique_key ?? ''
this.isClient = _findADT?.isClient ?? false
if (this.adrType === 'http') {
if (this.adrType === DISCOVERY_CATEGORY_TYPE.HTTP) {
const {
category = undefined,
key = '',
@ -358,7 +376,7 @@ export default {
}
} else {
this.isPrivateCloud = false
this.form2 = {
this.publicCloudForm = {
key,
secret,
_reference,
@ -371,23 +389,47 @@ export default {
this.$refs.httpForm.init(this.adr_id)
})
}
if (this.adrType === 'snmp') {
this.nodes = _findADT?.extra_option?.nodes?.length ? _findADT?.extra_option?.nodes : [
if (this.adrType === DISCOVERY_CATEGORY_TYPE.COMPONENT) {
const {
cidr = '',
ports = '',
enable_cidr = '',
} = _findADT?.extra_option ?? {}
this.portScanConfigForm = {
cidr,
ports,
enable_cidr
}
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
const nodes = _findADT?.extra_option?.nodes?.length ? _findADT?.extra_option?.nodes : [
{
id: uuidv4(),
ip: '',
community: '',
community: 'public',
version: '',
},
]
this.nodes = nodes
this.$nextTick(() => {
this.$refs.nodeSetting.initNodesFunc()
this.$nextTick(() => {
this.$refs.nodeSetting.setNodeField()
})
})
let cidrList = []
const cidr = _findADT?.extra_option?.cidr
if (Array.isArray(cidr) && cidr?.length) {
cidrList = cidr.map((v) => {
return {
id: uuidv4(),
value: v?.value ? v.value : v
}
})
}
this.cidrList = cidrList
}
if (this.adrType === 'agent') {
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
this.tableData = (_find?.attributes || []).map((item) => {
if (_findADT.attributes) {
return {
@ -420,7 +462,6 @@ export default {
this.agent_type = this.agentTypeRadioList[0].value
}
this.interval = 'cron'
this.cron = _findADT?.cron || ''
},
@ -431,13 +472,12 @@ export default {
const { currentAdt } = this
let params
// 校验 HTTP 表单
const { isError, data: cloudOption } = this.validateHTTPForm()
if (isError) {
return
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.HTTP) {
const { isError, data: cloudOption } = this.validateHTTPForm()
if (isError) {
return
}
if (this.adrType === 'http') {
params = {
extra_option: {
...cloudOption,
@ -445,12 +485,25 @@ export default {
},
}
}
if (this.adrType === 'snmp') {
if (this.adrType === DISCOVERY_CATEGORY_TYPE.COMPONENT) {
const portScanConfigForm = _.omitBy(this.portScanConfigForm, _.isEmpty) || {}
params = {
extra_option: { nodes: this.$refs.nodeSetting?.getNodeValue() ?? [] },
extra_option: {
...portScanConfigForm,
},
}
}
if (this.adrType === 'agent') {
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
params = {
extra_option: {
nodes: this.$refs.nodeSetting?.getNodeValue() ?? [],
cidr: this?.cidrList?.map((item) => item.value) || []
},
}
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
const $table = this.$refs.attrMapTable
const { fullData: _tableData } = $table.getTableData()
const attributes = {}
@ -481,7 +534,7 @@ export default {
...params,
...this.form,
adr_id: currentAdt.adr_id,
cron: this.interval === 'cron' ? this.cron : null,
cron: this.cron,
}
if (this.agent_type === 'agent_id' || this.agent_type === 'all') {
@ -534,55 +587,57 @@ export default {
}
},
/**
* HTTP 表单校验
* 公有云 私有云
*/
validateHTTPForm() {
let isError = false
let data = {}
if (this.adrType === 'http') {
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'form2']
if (formData.tabActive === TAB_KEY.CONFIG) {
if (!formData._reference) {
isError = true
this.$message.error(this.$t('cmdb.ad.configErrTip'))
}
data._reference = formData._reference
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
data.vcenterName = formData.vcenterName
}
return {
isError,
data
}
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'publicCloudForm']
if (formData.tabActive === TAB_KEY.CONFIG) {
if (!formData._reference) {
isError = true
this.$message.error(this.$t('cmdb.ad.configErrTip'))
}
if (this.isPrivateCloud) {
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
data = _.pick(this.privateCloudForm, ['host', 'account', 'password', 'vcenterName'])
const vcenterErros = {
'host': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.host')}`,
'account': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.account')}`,
'password': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.password')}`
}
const findError = Object.keys(this.privateCloudForm).find((key) => !this.privateCloudForm[key] && vcenterErros[key])
if (findError) {
isError = true
this.$message.error(this.$t(vcenterErros[findError]))
}
data._reference = formData._reference
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
data.vcenterName = formData.vcenterName
}
return {
isError,
data
}
}
if (this.isPrivateCloud) {
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
data = _.pick(this.privateCloudForm, ['host', 'account', 'password', 'vcenterName'])
const vcenterErros = {
'host': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.host')}`,
'account': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.account')}`,
'password': `${this.$t('placeholder1')} ${this.$t('cmdb.ciType.password')}`
}
} else {
data = _.pick(this.form2, ['key', 'secret'])
const publicCloudErros = {
'key': `${this.$t('placeholder1')} key`,
'secret': `${this.$t('placeholder1')} secret`
}
const findError = Object.keys(this.form2).find((key) => !this.form2[key] && publicCloudErros[key])
const findError = Object.keys(this.privateCloudForm).find((key) => !this.privateCloudForm[key] && vcenterErros[key])
if (findError) {
isError = true
this.$message.error(this.$t(publicCloudErros[findError]))
this.$message.error(this.$t(vcenterErros[findError]))
}
}
} else {
data = _.pick(this.publicCloudForm, ['key', 'secret'])
const publicCloudErros = {
'key': `${this.$t('placeholder1')} key`,
'secret': `${this.$t('placeholder1')} secret`
}
const findError = Object.keys(this.publicCloudForm).find((key) => !this.publicCloudForm[key] && publicCloudErros[key])
if (findError) {
isError = true
this.$message.error(this.$t(publicCloudErros[findError]))
}
}
return {
@ -591,6 +646,9 @@ export default {
}
},
/**
* 去除多余旧配置
*/
handleOldExtraOption(option) {
let extra_option = _.cloneDeep(option)
@ -600,7 +658,7 @@ export default {
}
// 根据 HTTP 选项去除多余属性
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'form2']
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'publicCloudForm']
switch (formData.tabActive) {
case TAB_KEY.CUSTOM:
Reflect.deleteProperty(extra_option, '_reference')
@ -694,6 +752,7 @@ export default {
.radio-master-tip {
font-size: 12px;
color: #86909c;
line-height: 14px;
}
}
.attr-ad-snmp-form {

View File

@ -375,6 +375,10 @@
name="is_computed"
v-decorator="['is_computed', { rules: [], valuePropName: 'checked' }]"
/>
<div v-show="isShowComputedArea" class="computed-attr-tip">
<div>1. {{ $t('cmdb.ciType.computedAttrTip1') }}</div>
<div>2. {{ $t('cmdb.ciType.computedAttrTip2') }}</div>
</div>
<ComputedArea
showCalcComputed
ref="computedArea"
@ -795,7 +799,13 @@ export default {
}
</script>
<style lang="less" scoped></style>
<style lang="less" scoped>
.computed-attr-tip {
font-size: 12px;
line-height: 22px;
color: #a5a9bc;
}
</style>
<style lang="less">
.attribute-edit-form {
.jsoneditor-outer {

View File

@ -255,6 +255,12 @@ export default {
show_id: () => {
return this.show_id
},
providerGroupsData: () => {
return {
CITypeGroups: this.CITypeGroups,
otherGroupAttributes: this.otherGroupAttributes
}
}
}
},
beforeCreate() {},

View File

@ -363,6 +363,10 @@
name="is_computed"
v-decorator="['is_computed', { rules: [], valuePropName: 'checked' }]"
/>
<div v-show="isShowComputedArea" class="computed-attr-tip">
<div>1. {{ $t('cmdb.ciType.computedAttrTip1') }}</div>
<div>2. {{ $t('cmdb.ciType.computedAttrTip2') }}</div>
</div>
<ComputedArea ref="computedArea" v-if="isShowComputedArea" :canDefineComputed="canDefineComputed" />
</a-form-item>
</a-col>
@ -610,6 +614,13 @@ export default {
},
}
</script>
<style lang="less" scoped>
.computed-attr-tip {
font-size: 12px;
line-height: 22px;
color: #a5a9bc;
}
</style>
<style lang="less">
.create-new-attribute {
.jsoneditor-outer {

View File

@ -13,18 +13,26 @@
@input="onCodeChange"
></codemirror>
</a-tab-pane>
<template slot="tabBarExtraContent" v-if="showCalcComputed">
<a-button type="primary" size="small" @click="handleCalcComputed">
{{ $t('cmdb.ciType.apply') }}
<template slot="tabBarExtraContent">
<a-button size="small" @click="showAllPropDrawer">
{{ $t('cmdb.ciType.viewAllAttr') }}
</a-button>
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
<a-icon type="question-circle" style="margin-left:5px" />
</a-tooltip>
<AllAttrDrawer ref="allAttrDrawer" />
<template v-if="showCalcComputed">
<a-button style="margin: 0px 5px;" type="primary" size="small" @click="handleCalcComputed">
{{ $t('cmdb.ciType.apply') }}
</a-button>
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
<a-icon type="question-circle" />
</a-tooltip>
</template>
</template>
</a-tabs>
</template>
<script>
import AllAttrDrawer from './allAttrDrawer.vue'
import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css'
@ -32,7 +40,10 @@ import 'codemirror/theme/monokai.css'
require('codemirror/mode/python/python.js')
export default {
name: 'ComputedArea',
components: { codemirror },
components: {
codemirror,
AllAttrDrawer
},
props: {
canDefineComputed: {
type: Boolean,
@ -108,6 +119,9 @@ export default {
},
onCodeChange(v) {
this.compute_script = v.replace('\t', ' ')
},
showAllPropDrawer() {
this.$refs.allAttrDrawer.open()
}
},
}

View File

@ -388,7 +388,7 @@ export default {
this.logModalVisible = true
const logRes = await getAdcExecHistories({
type_id: this.currentType,
page_size: 1000
last_size: 1000
})
let logTextArray = []
if (logRes?.result?.length) {