fix(ui): auto discovery

This commit is contained in:
songlh 2024-06-25 17:35:29 +08:00
parent 26266b2d6d
commit ca352e984b
15 changed files with 167 additions and 68 deletions

View File

@ -5196,9 +5196,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=1719208046306') format('woff2'), src: url('iconfont.woff2?t=1719307117118') format('woff2'),
url('iconfont.woff?t=1719208046306') format('woff'), url('iconfont.woff?t=1719307117118') format('woff'),
url('iconfont.ttf?t=1719208046306') format('truetype'); url('iconfont.ttf?t=1719307117118') 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=1719208046306') format('woff2'), src: url('iconfont.woff2?t=1719307117118') format('woff2'),
url('iconfont.woff?t=1719208046306') format('woff'), url('iconfont.woff?t=1719307117118') format('woff'),
url('iconfont.ttf?t=1719208046306') format('truetype'); url('iconfont.ttf?t=1719307117118') format('truetype');
} }
.iconfont { .iconfont {

Binary file not shown.

View File

@ -1,13 +1,22 @@
<template> <template>
<div class="http-ad-category"> <div class="http-ad-category">
<div class="http-ad-category-preview" v-if="currentCate"> <div class="http-ad-category-preview" v-if="currentCate && isPreviewDetail">
<div class="category-side"> <div class="category-side">
<div <div
v-for="(category, categoryIndex) in categories" v-for="(category, categoryIndex) in categories"
:key="category.category" :key="category.category"
class="category-side-item" class="category-side-item"
> >
<div class="category-side-title">{{ category.category }}</div> <div class="category-side-title">
<div class="category-side-title">
<a-icon
v-if="categoryIndex === 0"
type="left"
@click="clickBack"
/>
{{ category.category }}
</div>
</div>
<div class="category-side-children"> <div class="category-side-children">
<div <div
v-for="(item, itemIndex) in category.items" v-for="(item, itemIndex) in category.items"
@ -100,7 +109,8 @@ export default {
}, },
data() { data() {
return { return {
searchValue: '' searchValue: '',
isPreviewDetail: false,
} }
}, },
computed: { computed: {
@ -121,6 +131,10 @@ export default {
}, },
clickCategory(item) { clickCategory(item) {
this.$emit('clickCategory', item) this.$emit('clickCategory', item)
this.isPreviewDetail = true
},
clickBack() {
this.isPreviewDetail = false
} }
} }
} }
@ -143,8 +157,8 @@ export default {
padding-right: 10px; padding-right: 10px;
&-item { &-item {
&:not(:first-child) { &:not(:last-child) {
margin-top: 24px; margin-bottom: 24px;
} }
.category-side-title { .category-side-title {
@ -167,6 +181,7 @@ export default {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
margin-top: 5px;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -9,7 +9,7 @@
@clickCategory="setCurrentCate" @clickCategory="setCurrentCate"
/> />
<template v-else> <template v-else>
<a-select v-if="isCloud" :style="{ marginBottom: '10px' }" v-model="currentCate"> <a-select v-if="isCloud" :style="{ marginBottom: '10px', minWidth: '120px' }" v-model="currentCate">
<a-select-option v-for="cate in categoriesSelect" :key="cate" :value="cate">{{ cate }}</a-select-option> <a-select-option v-for="cate in categoriesSelect" :key="cate" :value="cate">{{ cate }}</a-select-option>
</a-select> </a-select>
<AttrMapTable <AttrMapTable
@ -103,7 +103,7 @@ export default {
immediate: true, immediate: true,
handler(newVal) { handler(newVal) {
if (newVal) { if (newVal) {
getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { resource: newVal }).then((res) => { getHttpAttributes(this.ruleName, { resource: newVal }).then((res) => {
if (this.isEdit) { if (this.isEdit) {
this.formatTableData(res) this.formatTableData(res)
} else { } else {
@ -130,7 +130,7 @@ export default {
} }
if (this.isCloud && ruleName) { if (this.isCloud && ruleName) {
getHttpCategories(this.httpMap[`${this.ruleName}`].name).then((res) => { getHttpCategories(this.ruleName).then((res) => {
this.categories = res this.categories = res
const categoriesSelect = [] const categoriesSelect = []
res.forEach((category) => { res.forEach((category) => {

View File

@ -244,6 +244,11 @@ const cmdb_en = {
relationADTip3: 'If the value of the auto-discovered attribute is a list, multiple relationships are established with the association model', relationADTip3: 'If the value of the auto-discovered attribute is a list, multiple relationships are established with the association model',
deleteRelationAdTip: 'Cannot be deleted again', deleteRelationAdTip: 'Cannot be deleted again',
cronTips: 'The format is the same as crontab, for example: 0 15 * * 1-5', cronTips: 'The format is the same as crontab, for example: 0 15 * * 1-5',
privateCloud: 'vSphere API Configuration',
host: 'Host',
account: 'Account',
insecure: 'Certificate Validation',
vcenterName: 'Platform Name',
}, },
components: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@ -651,7 +656,8 @@ if __name__ == "__main__":
confirmDeleteView: 'Are you sure you want to delete this view ?', confirmDeleteView: 'Are you sure you want to delete this view ?',
noInstancePerm: 'You do not have read permissions for this instance', noInstancePerm: 'You do not have read permissions for this instance',
noPreferenceAttributes: 'This instance has no subscription attributes or no default displayed attributes', noPreferenceAttributes: 'This instance has no subscription attributes or no default displayed attributes',
topoViewSearchPlaceholder: 'Please enter the node name.' topoViewSearchPlaceholder: 'Please enter the node name.',
moreBtn: 'Show more({count})'
}, },
} }
export default cmdb_en export default cmdb_en

View File

@ -244,6 +244,11 @@ const cmdb_zh = {
relationADTip3: '如果自动发现的属性值是列表,则会和关联模型建立多个关系', relationADTip3: '如果自动发现的属性值是列表,则会和关联模型建立多个关系',
deleteRelationAdTip: '不可再删除', deleteRelationAdTip: '不可再删除',
cronTips: '格式同crontab, 例如0 15 * * 1-5', cronTips: '格式同crontab, 例如0 15 * * 1-5',
privateCloud: 'vSphere API配置',
host: '地址',
account: '账号',
insecure: '是否证书验证',
vcenterName: '虚拟平台名',
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@ -650,7 +655,8 @@ if __name__ == "__main__":
confirmDeleteView: '您确定要删除该视图吗?', confirmDeleteView: '您确定要删除该视图吗?',
noInstancePerm: '您没有该实例的查看权限', noInstancePerm: '您没有该实例的查看权限',
noPreferenceAttributes: '该实例没有订阅属性或者没有默认展示的属性', noPreferenceAttributes: '该实例没有订阅属性或者没有默认展示的属性',
topoViewSearchPlaceholder: '请输入节点名字' topoViewSearchPlaceholder: '请输入节点名字',
moreBtn: '展示更多({count})'
}, },
} }
export default cmdb_zh export default cmdb_zh

View File

@ -273,8 +273,12 @@ export default {
const adtIndex = this.clientCITypeList.findIndex((item) => item.id === id) const adtIndex = this.clientCITypeList.findIndex((item) => item.id === id)
this.clientCITypeList[adtIndex].extra_option.alias = value this.clientCITypeList[adtIndex].extra_option.alias = value
} else { } else {
const adtIndex = this.adCITypeList.findIndex((item) => item.id === id)
const oldExtraOption = this.adCITypeList?.[adtIndex]?.extra_option
const params = { const params = {
extra_option: { extra_option: {
...(oldExtraOption || {}),
alias: value alias: value
} }
} }

View File

@ -51,7 +51,7 @@
:wrapperCol="{ span: 14 }" :wrapperCol="{ span: 14 }"
class="attr-ad-form" class="attr-ad-form"
> >
<a-form-model-item :label="$t('cmdb.ciType.adExecTarget')"> <a-form-model-item :required="true" :label="$t('cmdb.ciType.adExecTarget')">
<CustomRadio v-model="agent_type" :radioList="agentTypeRadioList"> <CustomRadio v-model="agent_type" :radioList="agentTypeRadioList">
<a-input <a-input
:style="{ width: '300px' }" :style="{ width: '300px' }"
@ -82,6 +82,7 @@
:labelCol="labelCol" :labelCol="labelCol"
:wrapperCol="{ span: 6 }" :wrapperCol="{ span: 6 }"
:label="$t('cmdb.ciType.adInterval')" :label="$t('cmdb.ciType.adInterval')"
:required="true"
> >
<el-popover v-model="cronVisible" trigger="click"> <el-popover v-model="cronVisible" trigger="click">
<template slot> <template slot>
@ -104,8 +105,9 @@
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
<template v-if="adrType === 'http'"> <template v-if="adrType === 'http'">
<template v-if="isVCenter"> <template v-if="isPrivateCloud">
<div class="attr-ad-header">私有云</div> <template v-if="privateCloudName === PRIVATE_CLOUD_NAME.VCenter">
<div class="attr-ad-header">{{ $t('cmdb.ciType.privateCloud') }}</div>
<a-form-model <a-form-model
:model="privateCloudForm" :model="privateCloudForm"
labelAlign="left" labelAlign="left"
@ -113,23 +115,24 @@
:wrapperCol="{ span: 6 }" :wrapperCol="{ span: 6 }"
class="attr-ad-form" class="attr-ad-form"
> >
<a-form-model-item label="地址"> <a-form-model-item :required="true" :label="$t('cmdb.ciType.host')">
<a-input v-model="privateCloudForm.host" /> <a-input v-model="privateCloudForm.host" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="账号"> <a-form-model-item :required="true" :label="$t('cmdb.ciType.account')">
<a-input v-model="privateCloudForm.account" /> <a-input v-model="privateCloudForm.account" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="密码"> <a-form-model-item :required="true" :label="$t('cmdb.ciType.password')">
<a-input-password v-model="privateCloudForm.password" /> <a-input-password v-model="privateCloudForm.password" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="是否证书验证"> <a-form-model-item :label="$t('cmdb.ciType.insecure')">
<a-switch v-model="privateCloudForm.insecure" /> <a-switch v-model="privateCloudForm.insecure" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="虚拟平台名"> <a-form-model-item :label="$t('cmdb.ciType.vcenterName')">
<a-input v-model="privateCloudForm.vcenterName" /> <a-input v-model="privateCloudForm.vcenterName" />
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</template> </template>
</template>
<template v-else> <template v-else>
<div class="attr-ad-header">{{ $t('cmdb.ciType.cloudAccessKey') }}</div> <div class="attr-ad-header">{{ $t('cmdb.ciType.cloudAccessKey') }}</div>
@ -141,10 +144,10 @@
:wrapperCol="{ span: 6 }" :wrapperCol="{ span: 6 }"
class="attr-ad-form" class="attr-ad-form"
> >
<a-form-model-item label="key"> <a-form-model-item :required="true" label="key">
<a-input-password v-model="form2.key" /> <a-input-password v-model="form2.key" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="secret"> <a-form-model-item :required="true" label="secret">
<a-input-password v-model="form2.secret" /> <a-input-password v-model="form2.secret" />
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
@ -167,6 +170,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 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'
@ -250,7 +254,9 @@ export default {
form3: this.$form.createForm(this, { name: 'snmp_form' }), form3: this.$form.createForm(this, { name: 'snmp_form' }),
cronVisible: false, cronVisible: false,
uniqueKey: '', uniqueKey: '',
isVCenter: false, isPrivateCloud: false,
privateCloudName: '',
PRIVATE_CLOUD_NAME
} }
}, },
computed: { computed: {
@ -262,7 +268,7 @@ export default {
return this.currentAdr?.type || '' return this.currentAdr?.type || ''
}, },
adrName() { adrName() {
return this.currentAdr?.name || '' return this?.currentAdr?.option?.en || this.currentAdr?.name || ''
}, },
adrIsInner() { adrIsInner() {
return this.currentAdr?.is_inner || '' return this.currentAdr?.is_inner || ''
@ -287,7 +293,7 @@ export default {
] ]
}, },
labelCol() { labelCol() {
const span = this.$i18n.locale === 'en' ? 4 : 2 const span = this.$i18n.locale === 'en' ? 5 : 3
return { return {
span span
} }
@ -312,8 +318,11 @@ export default {
vcenterName = '' vcenterName = ''
} = _findADT?.extra_option ?? {} } = _findADT?.extra_option ?? {}
if (_find?.name === 'VCenter') { if (_find?.option?.category === 'private_cloud') {
this.isVCenter = true this.isPrivateCloud = true
this.privateCloudName = _find?.option?.en || ''
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
this.privateCloudForm = { this.privateCloudForm = {
host, host,
account, account,
@ -321,8 +330,9 @@ export default {
insecure, insecure,
vcenterName, vcenterName,
} }
}
} else { } else {
this.isVCenter = false this.isPrivateCloud = false
this.form2 = { this.form2 = {
key, key,
secret, secret,
@ -389,10 +399,25 @@ export default {
handleSave() { handleSave() {
const { currentAdt } = this const { currentAdt } = this
let params let params
const isError = this.validateForm()
if (isError) {
return
}
if (this.adrType === 'http') { if (this.adrType === 'http') {
let cloudOption = {}
if (this.isPrivateCloud) {
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
cloudOption = this.privateCloudForm
}
} else {
cloudOption = this.form2
}
params = { params = {
extra_option: { extra_option: {
...(this.isVCenter ? this.privateCloudForm : this.form2), ...cloudOption,
category: this.$refs.httpSnmpAd.currentCate, category: this.$refs.httpSnmpAd.currentCate,
}, },
} }
@ -457,13 +482,14 @@ export default {
return return
} }
if (currentAdt?.isClient) {
if (currentAdt?.extra_option) { if (currentAdt?.extra_option) {
params.extra_option = { params.extra_option = {
...(params?.extra_option || {}), ...(currentAdt?.extra_option || {}),
...(currentAdt?.extra_option || {}) ...(params?.extra_option || {})
} }
} }
if (currentAdt?.isClient) {
postCITypeDiscovery(this.CITypeId, params).then((res) => { postCITypeDiscovery(this.CITypeId, params).then((res) => {
this.$message.success(this.$t('saveSuccess')) this.$message.success(this.$t('saveSuccess'))
this.$emit('handleSave', res.id) this.$emit('handleSave', res.id)
@ -475,6 +501,40 @@ export default {
}) })
} }
}, },
validateForm() {
let isError = false
if (this.adrType === 'http') {
if (this.isPrivateCloud) {
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
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]))
}
}
} else {
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) {
isError = true
this.$message.error(this.$t(publicCloudErros[findError]))
}
}
}
return isError
},
handleOpenCmdb() { handleOpenCmdb() {
this.$refs.cmdbDrawer.open() this.$refs.cmdbDrawer.open()
}, },

View File

@ -6,3 +6,7 @@ export const DISCOVERY_CATEGORY_TYPE = {
COMPONENT: 'components', COMPONENT: 'components',
PRIVATE_CLOUD: 'private_cloud' PRIVATE_CLOUD: 'private_cloud'
} }
export const PRIVATE_CLOUD_NAME = {
VCenter: 'vcenter'
}

View File

@ -112,7 +112,7 @@
</div> </div>
</template> </template>
<template v-else> <template v-else>
<HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleData.name" /> <HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleName" />
</template> </template>
</CustomDrawer> </CustomDrawer>
</template> </template>
@ -179,6 +179,9 @@ export default {
} }
return this.$t('new') return this.$t('new')
}, },
ruleName() {
return this?.ruleData?.option?.en || this?.ruleData?.name || ''
}
}, },
inject: { inject: {
getDiscovery: { getDiscovery: {

View File

@ -299,7 +299,6 @@ export default {
align-items: center; align-items: center;
gap: 14px; gap: 14px;
flex-shrink: 0; flex-shrink: 0;
margin-left: 20px;
&-btn { &-btn {
display: flex; display: flex;

View File

@ -160,7 +160,9 @@
class="relation-graph-node-icon" class="relation-graph-node-icon"
/> />
</template> </template>
<span class="relation-graph-node-text">{{ node.text }}</span> <span class="relation-graph-node-text">
{{ node.data.btnType === 'more' ? $t('cmdb.topo.moreBtn', { count: node.text }) : node.text }}
</span>
</div> </div>
</template> </template>
<template #graph-plug> <template #graph-plug>
@ -960,7 +962,7 @@ export default {
const id = uuidv4() const id = uuidv4()
jsonData.nodes.set(id, { jsonData.nodes.set(id, {
id, id,
text: `展示更多(${childs.length - showNodeCount})`, text: childs.length - showNodeCount,
data: { data: {
btnType: 'more' btnType: 'more'
}, },
@ -1008,7 +1010,7 @@ export default {
if (showNodeCount === childs.length) { if (showNodeCount === childs.length) {
moreBtnNode.isHide = true moreBtnNode.isHide = true
} else { } else {
moreBtnNode.text = `展示更多(${childs.length - showNodeCount})` moreBtnNode.text = childs.length - showNodeCount
} }
} }