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}]',
attributeMap: 'Attribute mapping',
nodeConfig: 'Node Configuration',
scanningParameter: 'Scanning Parameter',
autoDiscovery: 'AutoDiscovery',
node: 'Node',
adExecConfig: 'Execute configuration',
@@ -253,6 +254,20 @@ const cmdb_en = {
checkModalColumn4: 'Last checkup time',
testModalTitle: 'Automated discovery testing',
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',
nodeSettingIpTip: 'Please enter the ip address',
nodeSettingIpTip1: 'ip address format error',

View File

@@ -77,6 +77,7 @@ const cmdb_zh = {
confirmDeleteADT: '确认删除 【{pluginName}】',
attributeMap: '字段映射',
nodeConfig: '节点配置',
scanningParameter: '扫描参数',
autoDiscovery: '自动发现属性',
node: '节点',
adExecConfig: '执行配置',
@@ -253,6 +254,20 @@ const cmdb_zh = {
checkModalColumn4: '最近检查时间',
testModalTitle: '自动发现测试',
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地址',
nodeSettingIpTip: '请输入 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>
<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"
<a-form-item
label="CIDR"
:labelCol="labelCol"
:wrapperCol="{ span: 18 }"
>
<div class="cidr-tag">
<div
v-for="(item) in list"
:key="item.id"
class="cidr-tag-item"
>
<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>
<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>
</template>
<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>
<template v-if="adrType === DISCOVERY_CATEGORY_TYPE.SNMP">
<div class="attr-ad-header">{{ $t('cmdb.ciType.nodeConfig') }}</div>
<a-form :form="nodeSettingForm" layout="inline" class="attr-ad-snmp-form">
<NodeSetting ref="nodeSetting" :initNodes="nodes" />
<CIDRTags v-model="cidrList" />
</a-form>
<div class="attr-ad-header">{{ $t('cmdb.ciType.scanningParameter') }}</div>
<div class="attr-ad-form attr-ad-snmp-form">
<div class="attr-ad-snmp-form-title">
{{ $t('cmdb.ciType.SNMPConfiguration') }}
<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>
<div class="attr-ad-header">{{ $t('cmdb.ciType.adExecConfig') }}</div>
<a-form-model
@@ -177,13 +204,15 @@ import { TAB_KEY } from './attrAD/constants.js'
import HttpSnmpAD from '../../components/httpSnmpAD'
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
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 { 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'
import SNMPScanningConfig from './attrAD/SNMPScanningConfig/index.vue'
import SNMPConfig from './attrAD/SNMPConfig/index.vue'
export default {
name: 'AttrADTabpane',
@@ -198,7 +227,9 @@ export default {
VcenterForm,
PublicCloud,
PortScanConfig,
CIDRTags
CIDRTags,
SNMPScanningConfig,
SNMPConfig
},
props: {
adr_id: {
@@ -263,14 +294,6 @@ export default {
cronVisible: false,
intervalValue: 3,
agent_type: 'agent_id',
nodes: [
{
id: uuidv4(),
ip: '',
community: 'public',
version: '',
},
],
nodeSettingForm: this.$form.createForm(this, { name: 'snmp_form' }),
uniqueKey: '',
isPrivateCloud: false,
@@ -278,7 +301,16 @@ export default {
PRIVATE_CLOUD_NAME,
DISCOVERY_CATEGORY_TYPE,
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() {
@@ -323,13 +355,13 @@ export default {
const isEn = this.$i18n.locale === 'en'
return {
xl: {
span: isEn ? 4 : 2
span: isEn ? 4 : 3
},
lg: {
span: isEn ? 5 : 3
span: isEn ? 5 : 4
},
sm: {
span: isEn ? 6 : 4
span: isEn ? 6 : 5
}
}
}
@@ -404,7 +436,13 @@ export default {
}
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(),
ip: '',
@@ -412,13 +450,11 @@ export default {
version: '',
},
]
this.nodes = nodes
this.$nextTick(() => {
this.$refs.nodeSetting.initNodesFunc()
this.$refs.nodeSetting.initNodesFunc(InitializeNodes)
})
let cidrList = []
const cidr = _findADT?.extra_option?.cidr
if (Array.isArray(cidr) && cidr?.length) {
cidrList = cidr.map((v) => {
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) {
this.tableData = (_find?.attributes || []).map((item) => {
@@ -501,12 +546,27 @@ export default {
}
if (this.adrType === DISCOVERY_CATEGORY_TYPE.SNMP) {
const {
cidr,
...otherConfigForm
} = this.SNMPScanningConfigForm
const nodes = this.$refs.nodeSetting?.getNodeValue() ?? []
params = {
extra_option: {
nodes: this.$refs.nodeSetting?.getNodeValue() ?? [],
cidr: this?.cidrList?.map((item) => item.value) || []
...otherConfigForm,
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) {
const $table = this.$refs.attrMapTable
@@ -761,8 +821,18 @@ export default {
}
}
.attr-ad-snmp-form {
.ant-form-item {
margin-bottom: 0;
&-title {
font-size: 16px;
color: #000000;
margin-bottom: 12px;
& > i {
font-size: 14px;
}
}
/deep/ .ant-input-number {
width: 100%;
}
}
</style>