Merge pull request #689 from veops/dev_ui_250408

feat(ui): CI Type[AD] - update scanning configuration
This commit is contained in:
Leo Song
2025-04-08 15:53:03 +08:00
committed by GitHub
8 changed files with 462 additions and 210 deletions

View File

@@ -1,147 +0,0 @@
<template>
<div class="node-setting-wrap">
<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-model="row.version"
:placeholder="$t('cmdb.ciType.nodeSettingVersionTip')"
allowClear
class="node-setting-select"
>
<a-select-option value="1">
v1
</a-select-option>
<a-select-option value="2c">
v2c
</a-select-option>
</a-select>
</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>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
export default {
name: 'MonitorNodeSetting',
props: {
initNodes: {
type: Array,
default: () => [],
},
form: {
type: Object,
default: null,
},
},
data() {
return {
nodes: [],
}
},
methods: {
initNodesFunc() {
this.nodes = _.cloneDeep(this.initNodes)
},
addNode() {
const newNode = {
id: uuidv4(),
ip: '',
community: 'public',
version: '',
}
this.nodes.push(newNode)
},
removeNode(removeId, minLength) {
if (this.nodes.length <= minLength) {
this.$message.error('不可再删除!')
return
}
const _idx = this.nodes.findIndex((item) => item.id === removeId)
if (_idx > -1) {
this.nodes.splice(_idx, 1)
}
},
copyNode(id) {
const copyNode = this.nodes.find((item) => item.id === id)
if (copyNode) {
const newNode = {
...copyNode,
id: uuidv4(),
}
this.nodes.push(newNode)
}
},
getNodeValue() {
const nodes = this.nodes.map((node) => {
return _.pick(node, ['ip', 'community', 'version'])
})
return nodes
},
},
}
</script>
<style lang="less" scoped>
.node-setting-wrap {
margin-left: 17px;
width: 600px;
.ant-row {
/deep/ .ant-input-clear-icon {
color: rgba(0,0,0,.25);
&:hover {
color: rgba(0, 0, 0, 0.45);
}
}
}
.node-setting-select {
width: 150px;
}
}
.action {
height: 36px;
display: flex;
align-items: center;
gap: 12px;
}
</style>

View File

@@ -77,6 +77,7 @@ const cmdb_en = {
confirmDeleteADT: 'Do you confirm to delete [{pluginName}]', confirmDeleteADT: 'Do you confirm to delete [{pluginName}]',
attributeMap: 'Attribute mapping', attributeMap: 'Attribute mapping',
nodeConfig: 'Node Configuration', nodeConfig: 'Node Configuration',
scanningParameter: 'Scanning Parameter',
autoDiscovery: 'AutoDiscovery', autoDiscovery: 'AutoDiscovery',
node: 'Node', node: 'Node',
adExecConfig: 'Execute configuration', adExecConfig: 'Execute configuration',
@@ -253,6 +254,20 @@ 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',
SNMPConfiguration: 'SNMP Configuration',
nodeList: 'Node List',
defaultVersion: 'Default Version',
defaultCommunity: 'Default Community',
timeout: 'Timeout',
retryCount: 'Retry Count',
scanningConfiguration: 'Scanning Configuration',
initialNode: 'Initial Node',
defaultGateway: 'Default Gateway',
recursiveOrNot: 'Recursive Or Not',
recursiveTip: 'Scanning Configuration: When disabling recursion, the node list must be configured.',
maximumDepth: 'Maximum Depth',
snmpFormTip1: 'Node list: need to be configured separately if it is not the default SNMP version and Community\nTimeout: timeout for establishing SNMP connection\nRetries: number of retries for establishing SNMP connection',
snmpFormTip2: 'Initial Node: the first node to start scanning, if not configured then the scanning starts recursively from the default gateway\nWhether recursive: on by default, it means that all the network devices and topology relationships are found as far as possible; if off, only the devices in the node list are scanned\nMaximum Depth: the depth of the topology of the network devices\nCIDR: the results of the scanning are filtered with CIDR, if not configured then it will not be filtered. Format: 192.168.1.0/24',
nodeSettingIp: 'Network device IP address', 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',

View File

@@ -77,6 +77,7 @@ const cmdb_zh = {
confirmDeleteADT: '确认删除 【{pluginName}】', confirmDeleteADT: '确认删除 【{pluginName}】',
attributeMap: '字段映射', attributeMap: '字段映射',
nodeConfig: '节点配置', nodeConfig: '节点配置',
scanningParameter: '扫描参数',
autoDiscovery: '自动发现属性', autoDiscovery: '自动发现属性',
node: '节点', node: '节点',
adExecConfig: '执行配置', adExecConfig: '执行配置',
@@ -253,6 +254,20 @@ const cmdb_zh = {
checkModalColumn4: '最近检查时间', checkModalColumn4: '最近检查时间',
testModalTitle: '自动发现测试', testModalTitle: '自动发现测试',
attrMapTableAttrPlaceholder: '请编辑名称', attrMapTableAttrPlaceholder: '请编辑名称',
SNMPConfiguration: 'SNMP配置',
nodeList: '节点列表',
defaultVersion: '默认版本',
defaultCommunity: '默认 Community',
timeout: '超时时间',
retryCount: '重试次数',
scanningConfiguration: '扫描配置',
initialNode: '初始节点',
defaultGateway: '默认网关',
recursiveOrNot: '是否递归',
recursiveTip: '扫描配置关闭递归时, 必须配置节点列表',
maximumDepth: '最大深度',
snmpFormTip1: '节点列表如果不是默认的SNMP版本和Community则需要单独配置\n超时时间建立SNMP连接的超时时间\n重试次数建立SNMP连接的重试次数',
snmpFormTip2: '初始节点: 开始扫描的第一个节点,如果不配置则是从默认网关开始递归扫描\n是否递归: 默认开启,表示尽可能发现所有网络设备和拓扑关系;如果关闭,则仅扫描节点列表里的设备\n最大深度: 网络设备拓扑的深度\nCIDR扫描的结果用CIDR进行过滤不配置则不会过滤。格式: 192.168.1.0/24',
nodeSettingIp: '网络设备IP地址', nodeSettingIp: '网络设备IP地址',
nodeSettingIpTip: '请输入 ip 地址', nodeSettingIpTip: '请输入 ip 地址',
nodeSettingIpTip1: 'ip地址格式错误', nodeSettingIpTip1: 'ip地址格式错误',

View File

@@ -0,0 +1,84 @@
<template>
<a-form-model
:model="formData"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item
:label="$t('cmdb.ciType.defaultVersion')"
>
<a-select
v-model="formData.version"
allowClear
>
<a-select-option value="1">
v1
</a-select-option>
<a-select-option value="2c">
v2c
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item
:label="$t('cmdb.ciType.defaultCommunity')"
>
<a-input v-model="formData.community" />
</a-form-model-item>
<a-form-model-item
:label="$t('cmdb.ciType.timeout')"
>
<a-input-number
v-model="formData.timeout"
:min="0"
:precision="0"
/>
</a-form-model-item>
<a-form-model-item
:label="$t('cmdb.ciType.retryCount')"
>
<a-input-number
v-model="formData.retries"
:min="0"
:precision="0"
/>
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
name: 'SNMPConfig',
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

@@ -0,0 +1,66 @@
<template>
<a-form-model
:model="formData"
labelAlign="right"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item
:label="$t('cmdb.ciType.initialNode')"
>
<a-input
v-model="formData.initial_node"
:placeholder="$t('cmdb.ciType.defaultGateway')"
/>
</a-form-model-item>
<a-form-model-item
:label="$t('cmdb.ciType.recursiveOrNot')"
>
<a-switch v-model="formData.recursive_scan" />
</a-form-model-item>
<a-form-model-item
:label="$t('cmdb.ciType.maximumDepth')"
>
<a-input-number
v-model="formData.max_depth"
:min="0"
:precision="0"
/>
</a-form-model-item>
</a-form-model>
</template>
<script>
export default {
name: 'SNMPScanningConfig',
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()
}
},
methods: {
}
}
</script>

View File

@@ -1,40 +1,34 @@
<template> <template>
<a-row class="attr-ad-form"> <a-form-item
<a-col :span="24"> label="CIDR"
<a-form-item :labelCol="labelCol"
label="CIDR" :wrapperCol="{ span: 18 }"
:labelCol="labelCol" >
:wrapperCol="{ span: 18 }" <div class="cidr-tag">
labelAlign="right" <div
style="width: 100%; margin-top: 20px" v-for="(item) in list"
:key="item.id"
class="cidr-tag-item"
> >
<div class="cidr-tag"> <a-tooltip :title="item.value">
<div <span class="cidr-tag-text">{{ item.value }}</span>
v-for="(item) in list" </a-tooltip>
:key="item.id" <a-icon
class="cidr-tag-item" class="cidrv-tag-close"
> type="close"
<a-tooltip :title="item.value"> @click.stop="clickClose(item.id)"
<span class="cidr-tag-text">{{ item.value }}</span> />
</a-tooltip> </div>
<a-icon <a-input
class="cidrv-tag-close" v-if="showAddInput"
type="close" class="cidr-tag-input"
@click.stop="clickClose(item.id)" autofocus
/> @blur="addPreValue"
</div> @pressEnter="showAddInput = false"
<a-input ></a-input>
v-if="showAddInput" <a v-else class="cidr-tag-add" @click="showAddInput = true">+ {{ $t('new') }}</a>
class="cidr-tag-input" </div>
autofocus </a-form-item>
@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> </template>
<script> <script>

View File

@@ -0,0 +1,155 @@
<template>
<a-form-item
:label="$t('cmdb.ciType.nodeList')"
:labelCol="labelCol"
:wrapperCol="{ span: 18 }"
>
<div class="node-setting-wrap">
<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-model="row.version"
:placeholder="$t('cmdb.ciType.nodeSettingVersionTip')"
allowClear
class="node-setting-select"
>
<a-select-option value="1">
v1
</a-select-option>
<a-select-option value="2c">
v2c
</a-select-option>
</a-select>
</template>
</vxe-column>
<vxe-column min-wdith="90">
<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>
</a-form-item>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
export default {
name: 'MonitorNodeSetting',
inject: ['provide_labelCol'],
props: {
form: {
type: Object,
default: null,
},
},
data() {
return {
nodes: [],
}
},
computed: {
labelCol() {
return this.provide_labelCol()
}
},
methods: {
initNodesFunc(nodes) {
this.nodes = _.cloneDeep(nodes)
},
addNode() {
const newNode = {
id: uuidv4(),
ip: '',
community: 'public',
version: '',
}
this.nodes.push(newNode)
},
removeNode(removeId, minLength) {
if (this.nodes.length <= minLength) {
this.$message.error('不可再删除!')
return
}
const _idx = this.nodes.findIndex((item) => item.id === removeId)
if (_idx > -1) {
this.nodes.splice(_idx, 1)
}
},
copyNode(id) {
const copyNode = this.nodes.find((item) => item.id === id)
if (copyNode) {
const newNode = {
...copyNode,
id: uuidv4(),
}
this.nodes.push(newNode)
}
},
getNodeValue() {
const nodes = this.nodes.map((node) => {
return _.pick(node, ['ip', 'community', 'version'])
})
return nodes
},
},
}
</script>
<style lang="less" scoped>
.node-setting-wrap {
max-width: 600px;
.ant-row {
/deep/ .ant-input-clear-icon {
color: rgba(0,0,0,.25);
&:hover {
color: rgba(0, 0, 0, 0.45);
}
}
}
.node-setting-select {
width: 150px;
}
}
.action {
height: 36px;
display: flex;
align-items: center;
gap: 12px;
}
</style>

View File

@@ -54,11 +54,38 @@
/> />
</div> </div>
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.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.scanningParameter') }}</div>
<a-form :form="nodeSettingForm" layout="inline" class="attr-ad-snmp-form"> <div class="attr-ad-form attr-ad-snmp-form">
<NodeSetting ref="nodeSetting" :initNodes="nodes" /> <div class="attr-ad-snmp-form-title">
<CIDRTags v-model="cidrList" /> {{ $t('cmdb.ciType.SNMPConfiguration') }}
</a-form> <a-tooltip
:title="$t('cmdb.ciType.snmpFormTip1')"
:overlayStyle="{
whiteSpace: 'pre',
textWrap: 'wrap'
}"
>
<a-icon type="question-circle" />
</a-tooltip>
</div>
<NodeSetting ref="nodeSetting" />
<SNMPConfig v-model="SNMPScanningConfigForm" />
<div class="attr-ad-snmp-form-title">
{{ $t('cmdb.ciType.scanningConfiguration') }}
<a-tooltip
:title="$t('cmdb.ciType.snmpFormTip2')"
:overlayStyle="{
whiteSpace: 'pre',
textWrap: 'wrap'
}"
>
<a-icon type="question-circle" />
</a-tooltip>
</div>
<SNMPScanningConfig v-model="SNMPScanningConfigForm" />
<CIDRTags v-model="SNMPScanningConfigForm.cidr" />
</div>
</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
@@ -177,13 +204,15 @@ import { TAB_KEY } from './attrAD/constants.js'
import HttpSnmpAD from '../../components/httpSnmpAD' import HttpSnmpAD from '../../components/httpSnmpAD'
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue' import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
import CMDBExprDrawer from '@/components/CMDBExprDrawer' import CMDBExprDrawer from '@/components/CMDBExprDrawer'
import NodeSetting from '@/modules/cmdb/components/nodeSetting/index.vue' import NodeSetting from './attrAD/nodeSetting/index.vue'
import AttrADTest from './attrADTest.vue' 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 PortScanConfig from './attrAD/portScanConfig/index.vue'
import CIDRTags from './attrAD/cidrTags/index.vue' import CIDRTags from './attrAD/cidrTags/index.vue'
import SNMPScanningConfig from './attrAD/SNMPScanningConfig/index.vue'
import SNMPConfig from './attrAD/SNMPConfig/index.vue'
export default { export default {
name: 'AttrADTabpane', name: 'AttrADTabpane',
@@ -198,7 +227,9 @@ export default {
VcenterForm, VcenterForm,
PublicCloud, PublicCloud,
PortScanConfig, PortScanConfig,
CIDRTags CIDRTags,
SNMPScanningConfig,
SNMPConfig
}, },
props: { props: {
adr_id: { adr_id: {
@@ -263,14 +294,6 @@ export default {
cronVisible: false, cronVisible: false,
intervalValue: 3, intervalValue: 3,
agent_type: 'agent_id', agent_type: 'agent_id',
nodes: [
{
id: uuidv4(),
ip: '',
community: 'public',
version: '',
},
],
nodeSettingForm: this.$form.createForm(this, { name: 'snmp_form' }), nodeSettingForm: this.$form.createForm(this, { name: 'snmp_form' }),
uniqueKey: '', uniqueKey: '',
isPrivateCloud: false, isPrivateCloud: false,
@@ -278,7 +301,16 @@ export default {
PRIVATE_CLOUD_NAME, PRIVATE_CLOUD_NAME,
DISCOVERY_CATEGORY_TYPE, DISCOVERY_CATEGORY_TYPE,
isClient: false, // 是否前端新增临时数据 isClient: false, // 是否前端新增临时数据
cidrList: [], SNMPScanningConfigForm: {
version: '2c',
community: 'public',
timeout: 5,
retries: 3,
initial_node: '',
recursive_scan: true,
max_depth: 5,
cidr: []
}, // snmp scanning parameter form data
} }
}, },
provide() { provide() {
@@ -323,13 +355,13 @@ export default {
const isEn = this.$i18n.locale === 'en' const isEn = this.$i18n.locale === 'en'
return { return {
xl: { xl: {
span: isEn ? 4 : 2 span: isEn ? 4 : 3
}, },
lg: { lg: {
span: isEn ? 5 : 3 span: isEn ? 5 : 4
}, },
sm: { sm: {
span: isEn ? 6 : 4 span: isEn ? 6 : 5
} }
} }
} }
@@ -404,7 +436,13 @@ export default {
} }
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) { if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
const nodes = _findADT?.extra_option?.nodes?.length ? _findADT?.extra_option?.nodes : [ const extra_option = _findADT?.extra_option ?? {}
const {
nodes,
cidr = []
} = extra_option
const InitializeNodes = nodes?.length ? nodes : [
{ {
id: uuidv4(), id: uuidv4(),
ip: '', ip: '',
@@ -412,13 +450,11 @@ export default {
version: '', version: '',
}, },
] ]
this.nodes = nodes
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.nodeSetting.initNodesFunc() this.$refs.nodeSetting.initNodesFunc(InitializeNodes)
}) })
let cidrList = [] let cidrList = []
const cidr = _findADT?.extra_option?.cidr
if (Array.isArray(cidr) && cidr?.length) { if (Array.isArray(cidr) && cidr?.length) {
cidrList = cidr.map((v) => { cidrList = cidr.map((v) => {
return { return {
@@ -427,7 +463,16 @@ export default {
} }
}) })
} }
this.cidrList = cidrList this.SNMPScanningConfigForm = {
version: extra_option?.version ?? '2c',
community: extra_option?.community ?? 'public',
timeout: extra_option?.timeout ?? 5,
retries: extra_option?.retries ?? 3,
initial_node: extra_option?.initial_node ?? '',
recursive_scan: extra_option?.recursive_scan ?? true,
max_depth: extra_option?.max_depth ?? 5,
cidr: cidrList
}
} }
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) { if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
this.tableData = (_find?.attributes || []).map((item) => { this.tableData = (_find?.attributes || []).map((item) => {
@@ -501,12 +546,27 @@ export default {
} }
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) { if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
const {
cidr,
...otherConfigForm
} = this.SNMPScanningConfigForm
const nodes = this.$refs.nodeSetting?.getNodeValue() ?? []
params = { params = {
extra_option: { extra_option: {
nodes: this.$refs.nodeSetting?.getNodeValue() ?? [], ...otherConfigForm,
cidr: this?.cidrList?.map((item) => item.value) || [] nodes,
cidr: cidr?.map((item) => item.value) || []
}, },
} }
if (
!otherConfigForm?.recursive_scan &&
nodes?.some((item) => !item?.ip)
) {
this.$message.error(this.$t('cmdb.ciType.recursiveTip'))
return
}
} }
if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) { if (this.adrType === DISCOVERY_CATEGORY_TYPE.AGENT) {
const $table = this.$refs.attrMapTable const $table = this.$refs.attrMapTable
@@ -761,8 +821,18 @@ export default {
} }
} }
.attr-ad-snmp-form { .attr-ad-snmp-form {
.ant-form-item { &-title {
margin-bottom: 0; font-size: 16px;
color: #000000;
margin-bottom: 12px;
& > i {
font-size: 14px;
}
}
/deep/ .ant-input-number {
width: 100%;
} }
} }
</style> </style>