feat: add accounts config

This commit is contained in:
songlh 2024-07-22 17:38:48 +08:00
parent fa9bd5a926
commit 88def811ec
22 changed files with 1520 additions and 80 deletions

View File

@ -54,6 +54,84 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe96f;</span>
<div class="name">caise-数据中心</div>
<div class="code-name">&amp;#xe96f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe970;</span>
<div class="name">caise-文件夹</div>
<div class="code-name">&amp;#xe970;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe971;</span>
<div class="name">caise-资源池</div>
<div class="code-name">&amp;#xe971;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe972;</span>
<div class="name">caise-网络</div>
<div class="code-name">&amp;#xe972;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe973;</span>
<div class="name">caise-分布式交换机</div>
<div class="code-name">&amp;#xe973;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe974;</span>
<div class="name">caise-标准式交换机</div>
<div class="code-name">&amp;#xe974;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe975;</span>
<div class="name">caise-主机集群</div>
<div class="code-name">&amp;#xe975;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe976;</span>
<div class="name">caise-数据存储集群</div>
<div class="code-name">&amp;#xe976;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe977;</span>
<div class="name">caise-数据存储</div>
<div class="code-name">&amp;#xe977;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe96e;</span>
<div class="name">veops-account</div>
<div class="code-name">&amp;#xe96e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe96d;</span>
<div class="name">veops-collect</div>
<div class="code-name">&amp;#xe96d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe96c;</span>
<div class="name">veops-collected</div>
<div class="code-name">&amp;#xe96c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe96b;</span>
<div class="name">veops-text</div>
<div class="code-name">&amp;#xe96b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe96a;</span>
<div class="name">veops-markdown</div>
@ -5244,9 +5322,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1719487341033') format('woff2'),
url('iconfont.woff?t=1719487341033') format('woff'),
url('iconfont.ttf?t=1719487341033') format('truetype');
src: url('iconfont.woff2?t=1721640768584') format('woff2'),
url('iconfont.woff?t=1721640768584') format('woff'),
url('iconfont.ttf?t=1721640768584') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -5272,6 +5350,123 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont caise-data_center"></span>
<div class="name">
caise-数据中心
</div>
<div class="code-name">.caise-data_center
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-folder"></span>
<div class="name">
caise-文件夹
</div>
<div class="code-name">.caise-folder
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-resource_pool"></span>
<div class="name">
caise-资源池
</div>
<div class="code-name">.caise-resource_pool
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-network"></span>
<div class="name">
caise-网络
</div>
<div class="code-name">.caise-network
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-distributed_switch"></span>
<div class="name">
caise-分布式交换机
</div>
<div class="code-name">.caise-distributed_switch
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-standard_switch"></span>
<div class="name">
caise-标准式交换机
</div>
<div class="code-name">.caise-standard_switch
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-host_cluster"></span>
<div class="name">
caise-主机集群
</div>
<div class="code-name">.caise-host_cluster
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-storage_cluster"></span>
<div class="name">
caise-数据存储集群
</div>
<div class="code-name">.caise-storage_cluster
</div>
</li>
<li class="dib">
<span class="icon iconfont caise-data_storage"></span>
<div class="name">
caise-数据存储
</div>
<div class="code-name">.caise-data_storage
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-account"></span>
<div class="name">
veops-account
</div>
<div class="code-name">.veops-account
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-collect"></span>
<div class="name">
veops-collect
</div>
<div class="code-name">.veops-collect
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-collected"></span>
<div class="name">
veops-collected
</div>
<div class="code-name">.veops-collected
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-text"></span>
<div class="name">
veops-text
</div>
<div class="code-name">.veops-text
</div>
</li>
<li class="dib">
<span class="icon iconfont veops-markdown"></span>
<div class="name">
@ -13057,6 +13252,110 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-data_center"></use>
</svg>
<div class="name">caise-数据中心</div>
<div class="code-name">#caise-data_center</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-folder"></use>
</svg>
<div class="name">caise-文件夹</div>
<div class="code-name">#caise-folder</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-resource_pool"></use>
</svg>
<div class="name">caise-资源池</div>
<div class="code-name">#caise-resource_pool</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-network"></use>
</svg>
<div class="name">caise-网络</div>
<div class="code-name">#caise-network</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-distributed_switch"></use>
</svg>
<div class="name">caise-分布式交换机</div>
<div class="code-name">#caise-distributed_switch</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-standard_switch"></use>
</svg>
<div class="name">caise-标准式交换机</div>
<div class="code-name">#caise-standard_switch</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-host_cluster"></use>
</svg>
<div class="name">caise-主机集群</div>
<div class="code-name">#caise-host_cluster</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-storage_cluster"></use>
</svg>
<div class="name">caise-数据存储集群</div>
<div class="code-name">#caise-storage_cluster</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-data_storage"></use>
</svg>
<div class="name">caise-数据存储</div>
<div class="code-name">#caise-data_storage</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-account"></use>
</svg>
<div class="name">veops-account</div>
<div class="code-name">#veops-account</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-collect"></use>
</svg>
<div class="name">veops-collect</div>
<div class="code-name">#veops-collect</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-collected"></use>
</svg>
<div class="name">veops-collected</div>
<div class="code-name">#veops-collected</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-text"></use>
</svg>
<div class="name">veops-text</div>
<div class="code-name">#veops-text</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-markdown"></use>

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1719487341033') format('woff2'),
url('iconfont.woff?t=1719487341033') format('woff'),
url('iconfont.ttf?t=1719487341033') format('truetype');
src: url('iconfont.woff2?t=1721640768584') format('woff2'),
url('iconfont.woff?t=1721640768584') format('woff'),
url('iconfont.ttf?t=1721640768584') format('truetype');
}
.iconfont {
@ -13,6 +13,58 @@
-moz-osx-font-smoothing: grayscale;
}
.caise-data_center:before {
content: "\e96f";
}
.caise-folder:before {
content: "\e970";
}
.caise-resource_pool:before {
content: "\e971";
}
.caise-network:before {
content: "\e972";
}
.caise-distributed_switch:before {
content: "\e973";
}
.caise-standard_switch:before {
content: "\e974";
}
.caise-host_cluster:before {
content: "\e975";
}
.caise-storage_cluster:before {
content: "\e976";
}
.caise-data_storage:before {
content: "\e977";
}
.veops-account:before {
content: "\e96e";
}
.veops-collect:before {
content: "\e96d";
}
.veops-collected:before {
content: "\e96c";
}
.veops-text:before {
content: "\e96b";
}
.veops-markdown:before {
content: "\e96a";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,97 @@
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "41143117",
"name": "caise-数据中心",
"font_class": "caise-data_center",
"unicode": "e96f",
"unicode_decimal": 59759
},
{
"icon_id": "41143118",
"name": "caise-文件夹",
"font_class": "caise-folder",
"unicode": "e970",
"unicode_decimal": 59760
},
{
"icon_id": "41143119",
"name": "caise-资源池",
"font_class": "caise-resource_pool",
"unicode": "e971",
"unicode_decimal": 59761
},
{
"icon_id": "41143120",
"name": "caise-网络",
"font_class": "caise-network",
"unicode": "e972",
"unicode_decimal": 59762
},
{
"icon_id": "41143121",
"name": "caise-分布式交换机",
"font_class": "caise-distributed_switch",
"unicode": "e973",
"unicode_decimal": 59763
},
{
"icon_id": "41143122",
"name": "caise-标准式交换机",
"font_class": "caise-standard_switch",
"unicode": "e974",
"unicode_decimal": 59764
},
{
"icon_id": "41143128",
"name": "caise-主机集群",
"font_class": "caise-host_cluster",
"unicode": "e975",
"unicode_decimal": 59765
},
{
"icon_id": "41143129",
"name": "caise-数据存储集群",
"font_class": "caise-storage_cluster",
"unicode": "e976",
"unicode_decimal": 59766
},
{
"icon_id": "41143143",
"name": "caise-数据存储",
"font_class": "caise-data_storage",
"unicode": "e977",
"unicode_decimal": 59767
},
{
"icon_id": "41141857",
"name": "veops-account",
"font_class": "veops-account",
"unicode": "e96e",
"unicode_decimal": 59758
},
{
"icon_id": "41128804",
"name": "veops-collect",
"font_class": "veops-collect",
"unicode": "e96d",
"unicode_decimal": 59757
},
{
"icon_id": "41128781",
"name": "veops-collected",
"font_class": "veops-collected",
"unicode": "e96c",
"unicode_decimal": 59756
},
{
"icon_id": "41106846",
"name": "veops-text",
"font_class": "veops-text",
"unicode": "e96b",
"unicode_decimal": 59755
},
{
"icon_id": "40896913",
"name": "veops-markdown",

Binary file not shown.

View File

@ -62,6 +62,22 @@ export function getHttpAttrMapping(name, resource) {
})
}
export function getHTTPAccounts(params) {
return axios({
url: `/v0.1/adr/accounts`,
method: 'GET',
params
})
}
export function postHTTPAccounts(data) {
return axios({
url: `/v0.1/adr/accounts`,
method: 'POST',
data
})
}
export function getCITypeDiscovery(type_id) {
return axios({
url: `/v0.1/adt/ci_types/${type_id}`,

View File

@ -571,7 +571,13 @@ if __name__ == "__main__":
discoveryCardResoureTip: 'Number of resource types automatically discovered',
addPlugin: 'Add plugin',
pluginSearchTip: 'Please search the rules',
innerFlag: 'Inner'
innerFlag: 'Inner',
defaultName: 'Default Name',
deleteTip: 'Cannot be deleted again.',
tabCustom: 'Custom',
tabConfig: 'Configured',
addConfig: 'Add Config',
configErrTip: 'Please select config'
},
ci: {
attributeDesc: 'Attribute Description',

View File

@ -570,7 +570,13 @@ if __name__ == "__main__":
discoveryCardResoureTip: '自动发现的资源类型数',
addPlugin: '新增插件',
pluginSearchTip: '请搜索规则',
innerFlag: '内置'
innerFlag: '内置',
defaultName: '默认名称',
deleteTip: '不可再删除',
tabCustom: '自定义',
tabConfig: '已有配置',
addConfig: '添加配置',
configErrTip: '请选择配置'
},
ci: {
attributeDesc: '查看属性配置',

View File

@ -94,6 +94,7 @@ export default {
clientCITypeList: [],
currentTab: '',
deletePlugin: false,
queryLoaded: false,
}
},
computed: {
@ -116,7 +117,7 @@ export default {
watch: {
currentTab: {
handler() {
if (this.currentTab) {
if (this.currentTab && this.queryLoaded) {
this.$nextTick(() => {
this.$refs[`attrAdTabpaneRef`].init()
})
@ -131,6 +132,7 @@ export default {
this.ciTypeAttributes = res.attributes.map((item) => {
return { ...item, value: item.name, label: item.name }
})
this.queryLoaded = true
if (this.currentTab) {
this.$nextTick(() => {
this.$refs[`attrAdTabpaneRef`].init()

View File

@ -0,0 +1,77 @@
<template>
<div class="cloud-tabs">
<div
v-for="(item) in tabList"
:key="item.key"
:class="['cloud-tabs-item', activeKey === item.key ? 'cloud-tabs-item-active' : '']"
@click="clickTab(item.key)"
>
{{ $t(item.text) }}
</div>
</div>
</template>
<script>
import { tabList, TAB_KEY } from '../constants.js'
export default {
name: 'CloudTab',
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: String,
default: TAB_KEY.CUSTOM,
},
},
computed: {
activeKey: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
},
data() {
return {
tabList
}
},
methods: {
clickTab(key) {
this.$emit('change', key)
}
}
}
</script>
<style lang="less" scoped >
.cloud-tabs {
display: flex;
align-items: center;
margin-bottom: 26px;
&-item {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 400;
color: #4E5969;
background-color: #F7F8FA;
width: 105px;
height: 32px;
cursor: pointer;
&-active {
border: solid 1px #B1C9FF;
background-color: #E1EFFF;
color: #2F54EB;
}
}
}
</style>

View File

@ -0,0 +1,15 @@
export const TAB_KEY = {
CUSTOM: 'custom',
CONFIG: 'config'
}
export const tabList = [
{
key: TAB_KEY.CUSTOM,
text: 'cmdb.ad.tabCustom'
},
{
key: TAB_KEY.CONFIG,
text: 'cmdb.ad.tabConfig'
}
]

View File

@ -0,0 +1,154 @@
<template>
<div class="private-cloud-wrap">
<CloudTab
v-model="formData.tabActive"
@change="handleTabChange"
/>
<a-form-model
:model="formData"
labelAlign="left"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item
v-if="formData.tabActive === TAB_KEY.CONFIG"
:required="true"
:label="$t('cmdb.ad.tabConfig')"
>
<a-select
showSearch
optionFilterProp="title"
v-model="formData._reference"
@change="handleSelectChange"
>
<a-select-option
v-for="(item) in accountsList"
:key="item.id"
:value="item.id"
:title="item.name"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.host')">
<a-input
:disabled="formData.tabActive === TAB_KEY.CONFIG"
v-model="formData.host"
/>
</a-form-model-item>
<a-form-model-item :required="true" :label="$t('cmdb.ciType.account')">
<a-input
:disabled="formData.tabActive === TAB_KEY.CONFIG"
v-model="formData.account"
/>
</a-form-model-item>
<a-form-model-item v-if="formData.tabActive === TAB_KEY.CUSTOM" :required="true" :label="$t('cmdb.ciType.password')">
<a-input-password v-model="formData.password" />
</a-form-model-item>
<a-form-model-item :label="$t('cmdb.ciType.vcenterName')">
<a-input v-model="formData.vcenterName" />
</a-form-model-item>
</a-form-model>
</div>
</template>
<script>
import { getHTTPAccounts } from '@/modules/cmdb/api/discovery'
import { TAB_KEY } from '../constants.js'
import CloudTab from '../cloudTab/index.vue'
export default {
name: 'VCenterForm',
components: {
CloudTab
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Object,
default: () => {},
},
},
data() {
return {
TAB_KEY,
accountsList: [],
}
},
inject: ['provide_labelCol'],
computed: {
formData: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
methods: {
async init(id) {
const res = await getHTTPAccounts({
adr_id: id
})
this.accountsList = res?.length ? res : []
this.$nextTick(() => {
const { _reference = '', host = '', account = '', tabActive } = this?.formData || {}
const findSelect = this.accountsList?.find((item) => item.id === _reference)
const newFormData = findSelect?.config || {}
const changeData = {
...this.value,
_reference: findSelect?.id ?? '',
}
if (tabActive === TAB_KEY.CONFIG) {
changeData.host = newFormData?.host ?? host
changeData.account = newFormData?.account ?? account
}
this.$emit('change', changeData)
})
},
handleTabChange(key) {
if (key === TAB_KEY.CONFIG) {
this.handleSelectChange(this.formData._referenceValue)
}
},
handleSelectChange(id) {
const accountConfig = this.accountsList.find((item) => item.id === id)?.config || {}
const { host, account } = this?.value
this.$emit('change', {
...this.value,
host: accountConfig?.host ?? host ?? '',
account: accountConfig?.account ?? account ?? ''
})
}
}
}
</script>
<style lang="less" scoped>
.private-cloud-wrap {
margin-left: 17px;
.input-disabled {
/deep/ input {
color: rgba(0, 0, 0, 0.25);
background-color: #f5f5f5;
pointer-events: none;
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<div class="public-cloud-wrap">
<CloudTab
v-model="formData.tabActive"
@change="handleTabChange"
/>
<a-form-model
:model="formData"
labelAlign="left"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item
v-if="formData.tabActive === TAB_KEY.CONFIG"
:required="true"
:label="$t('cmdb.ad.tabConfig')"
>
<a-select
showSearch
optionFilterProp="title"
v-model="formData._reference"
@change="handleSelectChange"
>
<a-select-option
v-for="(item) in accountsList"
:key="item.id"
:value="item.id"
:title="item.name"
>
{{ item.name }}
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item :required="true" label="key">
<a-input-password
:class="[formData.tabActive === TAB_KEY.CONFIG ? 'input-disabled' : '']"
v-model="formData.key"
/>
</a-form-model-item>
<a-form-model-item v-if="formData.tabActive === TAB_KEY.CUSTOM" :required="true" label="secret">
<a-input-password v-model="formData.secret" />
</a-form-model-item>
</a-form-model>
</div>
</template>
<script>
import { getHTTPAccounts } from '@/modules/cmdb/api/discovery'
import { TAB_KEY } from '../constants.js'
import CloudTab from '../cloudTab/index.vue'
export default {
name: 'PublicCloud',
components: {
CloudTab
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: Object,
default: () => {},
},
},
data() {
return {
TAB_KEY,
accountsList: []
}
},
inject: ['provide_labelCol'],
computed: {
formData: {
get() {
return this.value
},
set(newValue) {
this.$emit('change', newValue)
}
},
labelCol() {
return this.provide_labelCol()
}
},
methods: {
async init(id) {
const res = await getHTTPAccounts({
adr_id: id
})
this.accountsList = res?.length ? res : []
this.$nextTick(() => {
const { _reference = '', key = '', tabActive } = this?.formData || {}
const findSelect = this.accountsList?.find((item) => item.id === _reference)
const newFormData = findSelect?.config || {}
const changeData = {
...this.value,
_reference: findSelect?.id ?? '',
}
if (tabActive === TAB_KEY.CONFIG) {
changeData.key = newFormData?.key ?? key
}
this.$emit('change', changeData)
})
},
handleTabChange(key) {
if (key === TAB_KEY.CONFIG) {
this.handleSelectChange(this.formData._reference)
}
},
handleSelectChange(id) {
const accountConfig = this.accountsList.find((item) => item.id === id)?.config || {}
const { key } = this?.value
this.$emit('change', {
...this.value,
key: accountConfig?.key ?? key ?? '',
})
}
}
}
</script>
<style lang="less" scoped>
.public-cloud-wrap {
margin-left: 17px;
.input-disabled {
/deep/ input {
color: rgba(0, 0, 0, 0.25);
background-color: #f5f5f5;
pointer-events: none;
opacity: 1;
}
}
}
</style>

View File

@ -131,49 +131,20 @@
<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>
<VcenterForm
v-model="privateCloudForm"
ref="httpForm"
/>
</template>
</template>
<template v-else>
<div class="attr-ad-header">{{ $t('cmdb.ciType.cloudAccessKey') }}</div>
<!-- <div class="public-cloud-info">{{ $t('cmdb.ciType.cloudAccessKeyTip') }}</div> -->
<a-form-model
:model="form2"
labelAlign="left"
:labelCol="labelCol"
:wrapperCol="{ span: 6 }"
class="attr-ad-form"
>
<a-form-model-item :required="true" label="key">
<a-input-password v-model="form2.key" />
</a-form-model-item>
<a-form-model-item :required="true" label="secret">
<a-input-password v-model="form2.secret" />
</a-form-model-item>
</a-form-model>
<PublicCloud
v-model="form2"
ref="httpForm"
/>
</template>
</template>
@ -195,6 +166,7 @@ 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 { TAB_KEY } from './attrAD/constants.js'
import HttpSnmpAD from '../../components/httpSnmpAD'
import AttrMapTable from '@/modules/cmdb/components/attrMapTable/index.vue'
@ -202,6 +174,8 @@ import CMDBExprDrawer from '@/components/CMDBExprDrawer'
import NodeSetting from '@/modules/cmdb/components/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'
export default {
name: 'AttrADTabpane',
@ -212,7 +186,9 @@ export default {
NodeSetting,
AttrMapTable,
AttrADTest,
ElPopover: Popover
ElPopover: Popover,
VcenterForm,
PublicCloud
},
props: {
adr_id: {
@ -256,6 +232,8 @@ export default {
form2: {
key: '',
secret: '',
_reference: '',
tabActive: TAB_KEY.CUSTOM,
},
privateCloudForm: {
host: '',
@ -263,6 +241,8 @@ export default {
password: '',
// insecure: false,
vcenterName: '',
_reference: '',
tabActive: TAB_KEY.CUSTOM,
},
interval: 'cron', // interval cron
cron: '',
@ -285,6 +265,13 @@ export default {
isClient: false, // 是否前端新增临时数据
}
},
provide() {
return {
provide_labelCol: () => {
return this.labelCol
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
@ -346,31 +333,43 @@ export default {
account = '',
password = '',
// insecure = false,
vcenterName = ''
vcenterName = '',
_reference = ''
} = _findADT?.extra_option ?? {}
if (_find?.option?.category === 'private_cloud') {
this.isPrivateCloud = true
this.privateCloudName = _find?.option?.en || ''
if (this.privateCloudName === PRIVATE_CLOUD_NAME.VCenter) {
switch (this.privateCloudName) {
case PRIVATE_CLOUD_NAME.VCenter:
this.privateCloudForm = {
host,
account,
password,
// insecure,
vcenterName,
_reference,
tabActive: _reference ? TAB_KEY.CONFIG : TAB_KEY.CUSTOM
}
break
default:
break
}
} else {
this.isPrivateCloud = false
this.form2 = {
key,
secret,
_reference,
tabActive: _reference ? TAB_KEY.CONFIG : TAB_KEY.CUSTOM
}
}
this.$refs.httpSnmpAd.setCurrentCate(category)
this.$nextTick(() => {
this.$refs.httpForm.init(this.adr_id)
})
}
if (this.adrType === 'snmp') {
this.nodes = _findADT?.extra_option?.nodes?.length ? _findADT?.extra_option?.nodes : [
@ -432,21 +431,13 @@ export default {
const { currentAdt } = this
let params
const isError = this.validateForm()
// 校验 HTTP 表单
const { isError, data: cloudOption } = this.validateHTTPForm()
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: {
...cloudOption,
@ -525,8 +516,9 @@ export default {
}
}
// 去除合并后的旧配置
if (params.extra_option) {
params.extra_option = _.omit(params.extra_option, 'insecure')
params.extra_option = this.handleOldExtraOption(params.extra_option)
}
if (currentAdt?.isClient) {
@ -542,12 +534,32 @@ export default {
}
},
validateForm() {
validateHTTPForm() {
let isError = false
let data = {}
if (this.adrType === 'http') {
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'form2']
if (formData.tabActive === TAB_KEY.CONFIG) {
if (!formData._reference) {
isError = true
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) {
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')}`,
@ -560,6 +572,7 @@ export default {
}
}
} else {
data = _.pick(this.form2, ['key', 'secret'])
const publicCloudErros = {
'key': `${this.$t('placeholder1')} key`,
'secret': `${this.$t('placeholder1')} secret`
@ -572,12 +585,40 @@ export default {
}
}
return isError
return {
isError,
data
}
},
handleOldExtraOption(option) {
let extra_option = _.cloneDeep(option)
// VCenter 旧配置
if (extra_option?.insecure) {
Reflect.deleteProperty(extra_option, 'insecure')
}
// 根据 HTTP 选项去除多余属性
const formData = this?.[this.isPrivateCloud ? 'privateCloudForm' : 'form2']
switch (formData.tabActive) {
case TAB_KEY.CUSTOM:
Reflect.deleteProperty(extra_option, '_reference')
break
case TAB_KEY.CONFIG:
extra_option = _.omit(extra_option, ['host', 'account', 'password', 'key', 'secret'])
break
default:
break
}
return extra_option
},
handleOpenCmdb() {
this.$refs.cmdbDrawer.open()
},
copySuccess(text) {
this.form = {
...this.form,

View File

@ -0,0 +1,11 @@
export const defaultConfig = {
public: {
key: '',
secret: ''
},
vcenter: {
host: '',
account: '',
password: ''
}
}

View File

@ -0,0 +1,137 @@
<template>
<a-modal
:visible="visible"
:title="title"
:width="880"
:bodyStyle="{ maxHeight: '60vh', overflowY: 'auto' }"
@ok="handleOk"
@cancel="handleCancel"
>
<PublicTable
v-if="httpName === 'public'"
ref="publicTable"
/>
<VCenterTable
v-else-if="httpName === 'vcenter'"
ref="vcenterTable"
/>
</a-modal>
</template>
<script>
import { getHTTPAccounts, postHTTPAccounts } from '@/modules/cmdb/api/discovery'
import { defaultConfig } from './constants.js'
import PublicTable from './publicTable.vue'
import VCenterTable from './vcenterTable.vue'
export default {
name: 'AccountConfig',
components: {
PublicTable,
VCenterTable
},
props: {},
data() {
return {
visible: false,
rule: {}
}
},
computed: {
title() {
if (this?.rule?.option?.category === 'private_cloud') {
return `${this.rule?.name || ''} ${this.$t('cmdb.ciType.account')}`
}
return this.$t('cmdb.ciType.cloudAccessKey')
},
httpName() {
if (this?.rule?.option?.category === 'private_cloud') {
return this?.rule?.option?.en || ''
}
return 'public'
}
},
methods: {
async open(rule) {
if (!rule?.id) {
return
}
this.rule = rule
const res = await getHTTPAccounts({
adr_id: rule.id
})
console.log('getHTTPAccounts res', res)
this.visible = true
this.$nextTick(() => {
const data = res?.length ? this.handleAccountsData(res) : []
switch (this.httpName) {
case 'public':
this.$refs.publicTable.setData(data)
break
case 'vcenter':
this.$refs.vcenterTable.setData(data)
break
default:
break
}
})
},
handleAccountsData(accounts) {
const config = defaultConfig[this.httpName] || {}
return accounts.map((item) => {
return {
id: item?.id,
name: item?.name || '',
...config,
...(item?.config || {})
}
})
},
handleCancel() {
this.visible = false
},
async handleOk() {
let tableData = {}
switch (this.httpName) {
case 'public':
tableData = this.$refs.publicTable.getData()
break
case 'vcenter':
tableData = this.$refs.vcenterTable.getData()
break
default:
break
}
if (tableData.isError) {
return
}
const accounts = tableData.data.map((item) => {
const { name, id, ...otherConfig } = item
const newData = {
name,
config: otherConfig,
}
if (id) {
newData.id = id
}
return newData
})
postHTTPAccounts({
adr_id: this.rule.id,
accounts,
}).then(() => {
this.$message.success(this.$t('updateSuccess'))
this.handleCancel()
})
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,178 @@
<template>
<div class="table-wrap">
<div @click="addItem" class="add-btn" v-if="configData.length === 0">
<a-icon class="add-btn-icon" type="plus-circle" theme="twoTone" />
<span class="add-btn-text">{{ $t('cmdb.ad.addConfig') }}</span>
</div>
<template v-else>
<ops-table
:data="configData"
size="mini"
show-overflow
show-header-overflow
:row-config="{ height: 42 }"
:min-height="78"
>
<vxe-column width="170" :title="$t('name')">
<template #header="{ column }">
<span class="column-header-required">*</span>
{{ column.title }}
</template>
<template #default="{ row }">
<a-input v-model="row.name"></a-input>
</template>
</vxe-column>
<vxe-column width="300" title="key">
<template #header="{ column }">
<span class="column-header-required">*</span>
{{ column.title }}
</template>
<template #default="{ row }">
<a-input-password v-model="row.key"></a-input-password>
</template>
</vxe-column>
<vxe-column width="300" title="secret">
<template #default="{ row }">
<a-input-password v-model="row.secret"></a-input-password>
</template>
</vxe-column>
</ops-table>
<div class="actions">
<div
v-for="(item, index) in configData"
:key="item.client_id"
class="actions-item"
>
<a-icon
type="minus-circle"
class="actions-item-btn"
@click="deleteItem(index)"
/>
<a-icon
type="plus-circle"
class="actions-item-btn"
@click="addItem"
/>
</div>
</div>
</template>
</div>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
import { defaultConfig } from './constants.js'
export default {
name: 'PublicTable',
data() {
return {
configData: []
}
},
methods: {
setData(data = []) {
this.configData = data.map((item) => {
return {
...item,
client_id: uuidv4()
}
})
},
getData() {
let isError = false
const keyArr = ['name', 'key', 'secret', 'id']
const data = this.configData.map((item) => {
const pickData = _.pickBy(item, (v, k) => {
return keyArr.includes(k) && v
})
return pickData
})
const errMsg = {
name: this.$t('name'),
key: 'key'
}
let errKey
for (let i = 0; i < data.length && !errKey; i++) {
const item = data[i]
const curErrKey = keyArr.find((key) => !item?.[key] && errMsg?.[key])
if (curErrKey) {
errKey = curErrKey
}
}
if (errKey) {
isError = true
this.$message.error(`${this.$t('placeholder1')} ${errMsg[errKey]}`)
}
return {
isError,
data
}
},
deleteItem(index) {
this.configData.splice(index, 1)
},
addItem() {
this.configData.push({
name: `${this.$t('cmdb.ad.defaultName')}${this.configData.length + 1}`,
...defaultConfig['public']
})
}
}
}
</script>
<style lang="less" scoped>
.table-wrap {
display: flex;
.add-btn {
padding: 5px 12px;
cursor: pointer;
border-radius: 1px;
border: 1px solid #B1C9FF;
background-color: #F4F9FF;
display: flex;
align-items: center;
justify-content: center;
&-icon {
font-size: 12px;
}
&-text {
font-size: 12px;
font-weight: 400;
color: #2F54EB;
margin-left: 6px;
}
}
.column-header-required {
color: #FD4C6A;
}
.actions {
padding-top: 36px;
margin-left: 16px;
&-item {
height: 42px;
display: flex;
align-items: center;
gap: 12px;
&-btn {
cursor: pointer;
color: #2f54eb;
}
}
}
}
</style>

View File

@ -0,0 +1,188 @@
<template>
<div class="table-wrap">
<div @click="addItem" class="add-btn" v-if="configData.length === 0">
<a-icon class="add-btn-icon" type="plus-circle" theme="twoTone" />
<span class="add-btn-text">{{ $t('cmdb.ad.addConfig') }}</span>
</div>
<template v-else>
<ops-table
:data="configData"
size="mini"
show-overflow
show-header-overflow
:row-config="{ height: 42 }"
:min-height="78"
>
<vxe-column width="170" :title="$t('name')">
<template #header="{ column }">
<span class="column-header-required">*</span>
{{ column.title }}
</template>
<template #default="{ row }">
<a-input v-model="row.name"></a-input>
</template>
</vxe-column>
<vxe-column width="200" :title="$t('cmdb.ciType.host')">
<template #header="{ column }">
<span class="column-header-required">*</span>
{{ column.title }}
</template>
<template #default="{ row }">
<a-input v-model="row.host"></a-input>
</template>
</vxe-column>
<vxe-column width="200" :title="$t('cmdb.ciType.account')">
<template #header="{ column }">
<span class="column-header-required">*</span>
{{ column.title }}
</template>
<template #default="{ row }">
<a-input v-model="row.account"></a-input>
</template>
</vxe-column>
<vxe-column width="200" :title="$t('cmdb.ciType.password')">
<template #default="{ row }">
<a-input-password v-model="row.password"></a-input-password>
</template>
</vxe-column>
</ops-table>
<div class="actions">
<div
v-for="(item, index) in configData"
:key="item.client_id"
class="actions-item"
>
<a-icon
type="minus-circle"
class="actions-item-btn"
@click="deleteItem(index)"
/>
<a-icon
type="plus-circle"
class="actions-item-btn"
@click="addItem"
/>
</div>
</div>
</template>
</div>
</template>
<script>
import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid'
import { defaultConfig } from './constants.js'
export default {
name: 'VCenterTable',
data() {
return {
configData: []
}
},
methods: {
setData(data) {
this.configData = data.map((item) => {
return {
...item,
client_id: uuidv4()
}
})
},
getData() {
let isError = false
const keyArr = ['name', 'host', 'account', 'password', 'id']
const data = this.configData.map((item) => {
const pickData = _.pickBy(item, (v, k) => {
return keyArr.includes(k) && v
})
return pickData
})
const errMsg = {
name: this.$t('name'),
host: this.$t('cmdb.ciType.host'),
account: this.$t('cmdb.ciType.account'),
}
let errKey
for (let i = 0; i < data.length && !errKey; i++) {
const item = data[i]
const curErrKey = keyArr.find((key) => !item?.[key] && errMsg?.[key])
if (curErrKey) {
errKey = curErrKey
}
}
if (errKey) {
isError = true
this.$message.error(`${this.$t('placeholder1')} ${errMsg[errKey]}`)
}
return {
isError,
data
}
},
deleteItem(index) {
this.configData.splice(index, 1)
},
addItem() {
this.configData.push({
name: `${this.$t('cmdb.ad.defaultName')}${this.configData.length + 1}`,
...defaultConfig['vcenter']
})
}
}
}
</script>
<style lang="less" scoped>
.table-wrap {
display: flex;
.add-btn {
padding: 5px 12px;
cursor: pointer;
border-radius: 1px;
border: 1px solid #B1C9FF;
background-color: #F4F9FF;
display: flex;
align-items: center;
justify-content: center;
&-icon {
font-size: 12px;
}
&-text {
font-size: 12px;
font-weight: 400;
color: #2F54EB;
margin-left: 6px;
}
}
.column-header-required {
color: #FD4C6A;
}
.actions {
padding-top: 36px;
margin-left: 16px;
&-item {
height: 42px;
display: flex;
align-items: center;
gap: 12px;
&-btn {
cursor: pointer;
color: #2f54eb;
}
}
}
}
</style>

View File

@ -79,8 +79,14 @@
<a-icon type="delete" />
</a>
</a-space>
<a v-else @click="handleEdit"><a-icon type="eye"/></a>
<span>{{ rule.is_plugin ? 'Plugin' : $t('cmdb.custom_dashboard.default') }}</span>
<a
v-if="showHTTPAcountBtn"
class="discovery-footer-account"
@click="openAccountConfig"
>
<ops-icon type="veops-account"/>
</a>
<span class="discovery-footer-tag">{{ rule.is_plugin ? 'Plugin' : $t('cmdb.custom_dashboard.default') }}</span>
</div>
</template>
</div>
@ -120,6 +126,10 @@ export default {
isDeletable() {
return ![this.$t('cmdb.ad.server'), this.$t('cmdb.ad.vserver'), this.$t('cmdb.ad.nic'), this.$t('cmdb.ad.disk'), 'server', 'vserver', 'NIC', 'harddisk'].includes(this.rule.name)
},
showHTTPAcountBtn() {
const showNameList = ['aliyun', 'tencentcloud', 'huaweicloud', 'aws', 'vcenter']
return this?.rule?.type === DISCOVERY_CATEGORY_TYPE.HTTP && showNameList.includes(this?.rule?.option?.en || '')
}
},
inject: {
setSelectedIds: {
@ -142,6 +152,9 @@ export default {
this.setSelectedIds(this.rule.id, this.rule.type)
}
},
openAccountConfig() {
this.$emit('openAccountConfig')
}
},
}
</script>
@ -264,8 +277,13 @@ export default {
.discovery-footer {
display: flex;
align-items: center;
justify-content: space-between;
> span {
&-account {
margin-left: 9px;
}
&-tag {
margin-left: auto;
color: #86909c;
background-color: #f0f5ff;
border-radius: 2px;
@ -292,11 +310,6 @@ export default {
width: 170px;
height: 80px;
cursor: pointer;
// &:hover {
// .discovery-top {
// background-color: #f0f1f5;
// }
// }
}
.discovery-card-small:hover,
.discovery-card-small-selected {

View File

@ -57,6 +57,7 @@
:isSelected="isSelected"
@editRule="handleOpenEditDrawer(rule, 'edit', type)"
@deleteRule="deleteRule(rule)"
@openAccountConfig="openAccountConfig(rule)"
/>
<div
v-if="showAddPlugin && type === DISCOVERY_CATEGORY_TYPE.PLUGIN"
@ -78,6 +79,7 @@
</div>
</div>
<EditDrawer ref="editDrawer" />
<AccountConfig ref="accountConfig"/>
</div>
</template>
@ -88,10 +90,15 @@ import { getDiscovery, deleteDiscovery } from '../../api/discovery'
import { DISCOVERY_CATEGORY_TYPE } from './constants.js'
import DiscoveryCard from './discoveryCard.vue'
import EditDrawer from './editDrawer.vue'
import AccountConfig from './accountConfig/index.vue'
export default {
name: 'AutoDiscovery',
components: { DiscoveryCard, EditDrawer },
components: {
DiscoveryCard,
EditDrawer,
AccountConfig
},
props: {
isSelected: {
type: Boolean,
@ -104,6 +111,7 @@ export default {
DISCOVERY_CATEGORY_TYPE,
radioKey: '',
searchValue: '',
accountConfigVisible: false,
}
},
computed: {
@ -282,6 +290,10 @@ export default {
changeRadio(key) {
this.radioKey = key === this.radioKey ? '' : key
},
openAccountConfig(rule) {
this.$refs.accountConfig.open(rule)
}
},
}