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

View File

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

Binary file not shown.

View File

@ -905,6 +905,33 @@ export const multicolorIconList = [
value: 'caise-application', value: 'caise-application',
label: '应用', label: '应用',
list: [{ 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', value: 'caise-yilianjie',
label: '已连接' label: '已连接'
}, { }, {

View File

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

View File

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

View File

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

View File

@ -236,7 +236,7 @@ const cmdb_en = {
checkModalColumn4: 'Last checkup time', checkModalColumn4: 'Last checkup time',
testModalTitle: 'Automated discovery testing', testModalTitle: 'Automated discovery testing',
attrMapTableAttrPlaceholder: 'Please edit the name', attrMapTableAttrPlaceholder: 'Please edit the name',
nodeSettingIp: 'IP Addresses', nodeSettingIp: 'Network device IP address',
nodeSettingIpTip: 'Please enter the ip address', nodeSettingIpTip: 'Please enter the ip address',
nodeSettingIpTip1: 'ip address format error', nodeSettingIpTip1: 'ip address format error',
nodeSettingCommunity: 'Community', 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', 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', enable: 'Enable',
enableTip: 'Confirm switching on?', 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: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',

View File

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

View File

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

View File

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

View File

@ -132,6 +132,10 @@ export default {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
initQueryLoading: {
type: Boolean,
default: false,
}
}, },
data() { data() {
return { return {
@ -145,129 +149,13 @@ export default {
firstCIJsonAttr: {}, firstCIJsonAttr: {},
secondCIJsonAttr: {}, secondCIJsonAttr: {},
canEdit: {}, canEdit: {},
topoData: {
nodes: {},
edges: []
}
} }
}, },
computed: { 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() { exsited_ci() {
const _exsited_ci = [this.ciId] const _exsited_ci = [this.ciId]
this.parentCITypes.forEach((parent) => { this.parentCITypes.forEach((parent) => {
@ -297,20 +185,28 @@ export default {
}, },
}, },
mounted() { mounted() {
this.init(true) if (!this.initQueryLoading) {
this.init(true)
}
}, },
methods: { methods: {
async init(isFirst) { async init(isFirst) {
await Promise.all([this.getParentCITypes(), this.getChildCITypes()]) await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => { 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.exsited_ci = this.exsited_ci
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData) this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
} }
}) })
}, },
async getFirstCIs() { 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) => { .then((res) => {
const firstCIs = {} const firstCIs = {}
res.result.forEach((item) => { res.result.forEach((item) => {
@ -328,7 +224,7 @@ export default {
.catch((e) => {}) .catch((e) => {})
}, },
async getSecondCIs() { 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) => { .then((res) => {
const secondCIs = {} const secondCIs = {}
res.result.forEach((item) => { 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> </script>

View File

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

View File

@ -29,6 +29,10 @@ export default {
methods: { methods: {
init() { init() {
const root = document.getElementById('ci-detail-relation-topo') 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({ this.canvas = new TreeCanvas({
root: root, root: root,
disLinkable: false, // 可删除连线 disLinkable: false, // 可删除连线
@ -54,7 +58,15 @@ export default {
return 10 return 10
}, },
getWidth(d) { 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) { getHGap(d) {
return 80 return 80
@ -69,22 +81,27 @@ export default {
this.canvas.on('events', ({ type, data }) => { this.canvas.on('events', ({ type, data }) => {
const sourceNode = data?.id || null const sourceNode = data?.id || null
if (type === 'custom:clickLeft') { 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') this.redrawData(res, sourceNode, 'left')
}) })
} }
if (type === 'custom:clickRight') { 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') this.redrawData(res, sourceNode, 'right')
}) })
} }
}) })
}, },
setTopoData(data) { setTopoData(data) {
const root = document.getElementById('ci-detail-relation-topo')
if (root && root?.innerHTML) {
root.innerHTML = ''
}
this.canvas = null this.canvas = null
this.init() this.init()
this.topoData = _.cloneDeep(data) this.topoData = _.cloneDeep(data)
this.canvas.draw(data, {}, () => {
this.canvas.redraw(data, {}, () => {
this.canvas.focusCenterWithAnimate() this.canvas.focusCenterWithAnimate()
}) })
}, },

View File

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

View File

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

View File

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

View File

@ -375,6 +375,10 @@
name="is_computed" name="is_computed"
v-decorator="['is_computed', { rules: [], valuePropName: 'checked' }]" 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 <ComputedArea
showCalcComputed showCalcComputed
ref="computedArea" ref="computedArea"
@ -795,7 +799,13 @@ export default {
} }
</script> </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"> <style lang="less">
.attribute-edit-form { .attribute-edit-form {
.jsoneditor-outer { .jsoneditor-outer {

View File

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

View File

@ -363,6 +363,10 @@
name="is_computed" name="is_computed"
v-decorator="['is_computed', { rules: [], valuePropName: 'checked' }]" 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" /> <ComputedArea ref="computedArea" v-if="isShowComputedArea" :canDefineComputed="canDefineComputed" />
</a-form-item> </a-form-item>
</a-col> </a-col>
@ -610,6 +614,13 @@ export default {
}, },
} }
</script> </script>
<style lang="less" scoped>
.computed-attr-tip {
font-size: 12px;
line-height: 22px;
color: #a5a9bc;
}
</style>
<style lang="less"> <style lang="less">
.create-new-attribute { .create-new-attribute {
.jsoneditor-outer { .jsoneditor-outer {

View File

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

View File

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