diff --git a/cmdb-ui/public/iconfont/demo_index.html b/cmdb-ui/public/iconfont/demo_index.html index 608363b..16fe770 100644 --- a/cmdb-ui/public/iconfont/demo_index.html +++ b/cmdb-ui/public/iconfont/demo_index.html @@ -5196,9 +5196,9 @@ <pre><code class="language-css" >@font-face { font-family: 'iconfont'; - src: url('iconfont.woff2?t=1719208046306') format('woff2'), - url('iconfont.woff?t=1719208046306') format('woff'), - url('iconfont.ttf?t=1719208046306') format('truetype'); + src: url('iconfont.woff2?t=1719307117118') format('woff2'), + url('iconfont.woff?t=1719307117118') format('woff'), + url('iconfont.ttf?t=1719307117118') format('truetype'); } </code></pre> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> diff --git a/cmdb-ui/public/iconfont/iconfont.css b/cmdb-ui/public/iconfont/iconfont.css index 918935e..ce80e4f 100644 --- a/cmdb-ui/public/iconfont/iconfont.css +++ b/cmdb-ui/public/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 3857903 */ - src: url('iconfont.woff2?t=1719208046306') format('woff2'), - url('iconfont.woff?t=1719208046306') format('woff'), - url('iconfont.ttf?t=1719208046306') format('truetype'); + src: url('iconfont.woff2?t=1719307117118') format('woff2'), + url('iconfont.woff?t=1719307117118') format('woff'), + url('iconfont.ttf?t=1719307117118') format('truetype'); } .iconfont { diff --git a/cmdb-ui/public/iconfont/iconfont.ttf b/cmdb-ui/public/iconfont/iconfont.ttf index f166f25..bd2233b 100644 Binary files a/cmdb-ui/public/iconfont/iconfont.ttf and b/cmdb-ui/public/iconfont/iconfont.ttf differ diff --git a/cmdb-ui/public/iconfont/iconfont.woff b/cmdb-ui/public/iconfont/iconfont.woff index 9492660..3d84f09 100644 Binary files a/cmdb-ui/public/iconfont/iconfont.woff and b/cmdb-ui/public/iconfont/iconfont.woff differ diff --git a/cmdb-ui/public/iconfont/iconfont.woff2 b/cmdb-ui/public/iconfont/iconfont.woff2 index cf86593..cc98eb1 100644 Binary files a/cmdb-ui/public/iconfont/iconfont.woff2 and b/cmdb-ui/public/iconfont/iconfont.woff2 differ diff --git a/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/httpADCategory.vue b/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/httpADCategory.vue index 2ea5f9f..cbd03fb 100644 --- a/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/httpADCategory.vue +++ b/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/httpADCategory.vue @@ -1,13 +1,22 @@ <template> <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 v-for="(category, categoryIndex) in categories" :key="category.category" 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 v-for="(item, itemIndex) in category.items" @@ -100,7 +109,8 @@ export default { }, data() { return { - searchValue: '' + searchValue: '', + isPreviewDetail: false, } }, computed: { @@ -121,6 +131,10 @@ export default { }, clickCategory(item) { this.$emit('clickCategory', item) + this.isPreviewDetail = true + }, + clickBack() { + this.isPreviewDetail = false } } } @@ -143,8 +157,8 @@ export default { padding-right: 10px; &-item { - &:not(:first-child) { - margin-top: 24px; + &:not(:last-child) { + margin-bottom: 24px; } .category-side-title { @@ -167,6 +181,7 @@ export default { cursor: pointer; position: relative; + margin-top: 5px; display: flex; align-items: center; diff --git a/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/index.vue b/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/index.vue index ff66e50..970a2cd 100644 --- a/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/index.vue +++ b/cmdb-ui/src/modules/cmdb/components/httpSnmpAD/index.vue @@ -9,7 +9,7 @@ @clickCategory="setCurrentCate" /> <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> <AttrMapTable @@ -103,7 +103,7 @@ export default { immediate: true, handler(newVal) { if (newVal) { - getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { resource: newVal }).then((res) => { + getHttpAttributes(this.ruleName, { resource: newVal }).then((res) => { if (this.isEdit) { this.formatTableData(res) } else { @@ -130,7 +130,7 @@ export default { } if (this.isCloud && ruleName) { - getHttpCategories(this.httpMap[`${this.ruleName}`].name).then((res) => { + getHttpCategories(this.ruleName).then((res) => { this.categories = res const categoriesSelect = [] res.forEach((category) => { diff --git a/cmdb-ui/src/modules/cmdb/lang/en.js b/cmdb-ui/src/modules/cmdb/lang/en.js index 20e1db1..517f021 100644 --- a/cmdb-ui/src/modules/cmdb/lang/en.js +++ b/cmdb-ui/src/modules/cmdb/lang/en.js @@ -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', deleteRelationAdTip: 'Cannot be deleted again', 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: { unselectAttributes: 'Unselected', @@ -651,7 +656,8 @@ if __name__ == "__main__": confirmDeleteView: 'Are you sure you want to delete this view ?', noInstancePerm: 'You do not have read permissions for this instance', 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 diff --git a/cmdb-ui/src/modules/cmdb/lang/zh.js b/cmdb-ui/src/modules/cmdb/lang/zh.js index 1dfbab3..8eb4975 100644 --- a/cmdb-ui/src/modules/cmdb/lang/zh.js +++ b/cmdb-ui/src/modules/cmdb/lang/zh.js @@ -244,6 +244,11 @@ const cmdb_zh = { relationADTip3: '如果自动发现的属性值是列表,则会和关联模型建立多个关系', deleteRelationAdTip: '不可再删除', cronTips: '格式同crontab, 例如:0 15 * * 1-5', + privateCloud: 'vSphere API配置', + host: '地址', + account: '账号', + insecure: '是否证书验证', + vcenterName: '虚拟平台名', }, components: { unselectAttributes: '未选属性', @@ -650,7 +655,8 @@ if __name__ == "__main__": confirmDeleteView: '您确定要删除该视图吗?', noInstancePerm: '您没有该实例的查看权限', noPreferenceAttributes: '该实例没有订阅属性或者没有默认展示的属性', - topoViewSearchPlaceholder: '请输入节点名字' + topoViewSearchPlaceholder: '请输入节点名字', + moreBtn: '展示更多({count})' }, } export default cmdb_zh diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue index d5436e6..685fd41 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue @@ -273,8 +273,12 @@ export default { const adtIndex = this.clientCITypeList.findIndex((item) => item.id === id) this.clientCITypeList[adtIndex].extra_option.alias = value } else { + const adtIndex = this.adCITypeList.findIndex((item) => item.id === id) + const oldExtraOption = this.adCITypeList?.[adtIndex]?.extra_option + const params = { extra_option: { + ...(oldExtraOption || {}), alias: value } } diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue index 13510ed..9b0e620 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue @@ -51,7 +51,7 @@ :wrapperCol="{ span: 14 }" 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"> <a-input :style="{ width: '300px' }" @@ -82,6 +82,7 @@ :labelCol="labelCol" :wrapperCol="{ span: 6 }" :label="$t('cmdb.ciType.adInterval')" + :required="true" > <el-popover v-model="cronVisible" trigger="click"> <template slot> @@ -104,31 +105,33 @@ </a-form-model-item> </a-form-model> <template v-if="adrType === 'http'"> - <template v-if="isVCenter"> - <div class="attr-ad-header">私有云</div> - <a-form-model - :model="privateCloudForm" - labelAlign="left" - :labelCol="labelCol" - :wrapperCol="{ span: 6 }" - class="attr-ad-form" - > - <a-form-model-item label="地址"> - <a-input v-model="privateCloudForm.host" /> - </a-form-model-item> - <a-form-model-item label="账号"> - <a-input v-model="privateCloudForm.account" /> - </a-form-model-item> - <a-form-model-item label="密码"> - <a-input-password v-model="privateCloudForm.password" /> - </a-form-model-item> - <a-form-model-item label="是否证书验证"> - <a-switch v-model="privateCloudForm.insecure" /> - </a-form-model-item> - <a-form-model-item label="虚拟平台名"> - <a-input v-model="privateCloudForm.vcenterName" /> - </a-form-model-item> - </a-form-model> + <template v-if="isPrivateCloud"> + <template v-if="privateCloudName === PRIVATE_CLOUD_NAME.VCenter"> + <div class="attr-ad-header">{{ $t('cmdb.ciType.privateCloud') }}</div> + <a-form-model + :model="privateCloudForm" + labelAlign="left" + :labelCol="labelCol" + :wrapperCol="{ span: 6 }" + class="attr-ad-form" + > + <a-form-model-item :required="true" :label="$t('cmdb.ciType.host')"> + <a-input v-model="privateCloudForm.host" /> + </a-form-model-item> + <a-form-model-item :required="true" :label="$t('cmdb.ciType.account')"> + <a-input v-model="privateCloudForm.account" /> + </a-form-model-item> + <a-form-model-item :required="true" :label="$t('cmdb.ciType.password')"> + <a-input-password v-model="privateCloudForm.password" /> + </a-form-model-item> + <a-form-model-item :label="$t('cmdb.ciType.insecure')"> + <a-switch v-model="privateCloudForm.insecure" /> + </a-form-model-item> + <a-form-model-item :label="$t('cmdb.ciType.vcenterName')"> + <a-input v-model="privateCloudForm.vcenterName" /> + </a-form-model-item> + </a-form-model> + </template> </template> <template v-else> @@ -141,10 +144,10 @@ :wrapperCol="{ span: 6 }" 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-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-form-model-item> </a-form-model> @@ -167,6 +170,7 @@ import { v4 as uuidv4 } from 'uuid' import { mapState } from 'vuex' import Vcrontab from '@/components/Crontab' import { putCITypeDiscovery, postCITypeDiscovery } from '../../api/discovery' +import { PRIVATE_CLOUD_NAME } from '@/modules/cmdb/views/discovery/constants.js' import HttpSnmpAD from '../../components/httpSnmpAD' import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue' @@ -250,7 +254,9 @@ export default { form3: this.$form.createForm(this, { name: 'snmp_form' }), cronVisible: false, uniqueKey: '', - isVCenter: false, + isPrivateCloud: false, + privateCloudName: '', + PRIVATE_CLOUD_NAME } }, computed: { @@ -262,7 +268,7 @@ export default { return this.currentAdr?.type || '' }, adrName() { - return this.currentAdr?.name || '' + return this?.currentAdr?.option?.en || this.currentAdr?.name || '' }, adrIsInner() { return this.currentAdr?.is_inner || '' @@ -287,7 +293,7 @@ export default { ] }, labelCol() { - const span = this.$i18n.locale === 'en' ? 4 : 2 + const span = this.$i18n.locale === 'en' ? 5 : 3 return { span } @@ -312,17 +318,21 @@ export default { vcenterName = '' } = _findADT?.extra_option ?? {} - if (_find?.name === 'VCenter') { - this.isVCenter = true - this.privateCloudForm = { - host, - account, - password, - insecure, - vcenterName, + if (_find?.option?.category === 'private_cloud') { + this.isPrivateCloud = true + this.privateCloudName = _find?.option?.en || '' + + if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) { + this.privateCloudForm = { + host, + account, + password, + insecure, + vcenterName, + } } } else { - this.isVCenter = false + this.isPrivateCloud = false this.form2 = { key, secret, @@ -389,10 +399,25 @@ export default { handleSave() { const { currentAdt } = this let params + + const isError = this.validateForm() + if (isError) { + return + } + if (this.adrType === 'http') { + let cloudOption = {} + if (this.isPrivateCloud) { + if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) { + cloudOption = this.privateCloudForm + } + } else { + cloudOption = this.form2 + } + params = { extra_option: { - ...(this.isVCenter ? this.privateCloudForm : this.form2), + ...cloudOption, category: this.$refs.httpSnmpAd.currentCate, }, } @@ -457,13 +482,14 @@ export default { return } - if (currentAdt?.isClient) { - if (currentAdt?.extra_option) { - params.extra_option = { - ...(params?.extra_option || {}), - ...(currentAdt?.extra_option || {}) - } + if (currentAdt?.extra_option) { + params.extra_option = { + ...(currentAdt?.extra_option || {}), + ...(params?.extra_option || {}) } + } + + if (currentAdt?.isClient) { postCITypeDiscovery(this.CITypeId, params).then((res) => { this.$message.success(this.$t('saveSuccess')) 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() { this.$refs.cmdbDrawer.open() }, diff --git a/cmdb-ui/src/modules/cmdb/views/discovery/constants.js b/cmdb-ui/src/modules/cmdb/views/discovery/constants.js index 23554d2..c77998c 100644 --- a/cmdb-ui/src/modules/cmdb/views/discovery/constants.js +++ b/cmdb-ui/src/modules/cmdb/views/discovery/constants.js @@ -6,3 +6,7 @@ export const DISCOVERY_CATEGORY_TYPE = { COMPONENT: 'components', PRIVATE_CLOUD: 'private_cloud' } + +export const PRIVATE_CLOUD_NAME = { + VCenter: 'vcenter' +} diff --git a/cmdb-ui/src/modules/cmdb/views/discovery/editDrawer.vue b/cmdb-ui/src/modules/cmdb/views/discovery/editDrawer.vue index d895bb5..2243077 100644 --- a/cmdb-ui/src/modules/cmdb/views/discovery/editDrawer.vue +++ b/cmdb-ui/src/modules/cmdb/views/discovery/editDrawer.vue @@ -112,7 +112,7 @@ </div> </template> <template v-else> - <HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleData.name" /> + <HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleName" /> </template> </CustomDrawer> </template> @@ -179,6 +179,9 @@ export default { } return this.$t('new') }, + ruleName() { + return this?.ruleData?.option?.en || this?.ruleData?.name || '' + } }, inject: { getDiscovery: { diff --git a/cmdb-ui/src/modules/cmdb/views/discovery/index.vue b/cmdb-ui/src/modules/cmdb/views/discovery/index.vue index 39984e8..6bccee8 100644 --- a/cmdb-ui/src/modules/cmdb/views/discovery/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/discovery/index.vue @@ -299,7 +299,6 @@ export default { align-items: center; gap: 14px; flex-shrink: 0; - margin-left: 20px; &-btn { display: flex; diff --git a/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue b/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue index 085e62f..c65f9ca 100644 --- a/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/topology_view/index.vue @@ -160,7 +160,9 @@ class="relation-graph-node-icon" /> </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> </template> <template #graph-plug> @@ -960,7 +962,7 @@ export default { const id = uuidv4() jsonData.nodes.set(id, { id, - text: `展示更多(${childs.length - showNodeCount})`, + text: childs.length - showNodeCount, data: { btnType: 'more' }, @@ -1008,7 +1010,7 @@ export default { if (showNodeCount === childs.length) { moreBtnNode.isHide = true } else { - moreBtnNode.text = `展示更多(${childs.length - showNodeCount})` + moreBtnNode.text = childs.length - showNodeCount } }