Compare commits

..

11 Commits

16 changed files with 448 additions and 164 deletions

View File

@@ -255,7 +255,9 @@ def cmdb_trigger():
try: try:
ready_cis = CITriggerManager.waiting_cis(trigger) ready_cis = CITriggerManager.waiting_cis(trigger)
except Exception as e: except Exception as e:
print(e) import traceback
current_app.logger.error("cmdb trigger waiting_cis exception for trigger_id {}: {}".format(trigger.id, e))
current_app.logger.error("traceback: {}".format(traceback.format_exc()))
continue continue
if trigger.id not in trigger2cis: if trigger.id not in trigger2cis:

View File

@@ -256,8 +256,13 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
db.session.commit() db.session.commit()
rules = cls.cls.get_by(to_dict=True) rules = cls.cls.get_by(to_dict=True)
last_update_at = last_update_at or ""
new_last_update_at = ""
for rule in rules: for rule in rules:
if not rule['enabled']: if not rule['enabled']:
if (rule['created_at'] or '') > last_update_at or (
rule['updated_at'] or '') > last_update_at:
new_last_update_at = max([rule['created_at'] or '', rule['updated_at'] or ''])
continue continue
if isinstance(rule.get("extra_option"), dict): if isinstance(rule.get("extra_option"), dict):
@@ -299,7 +304,6 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
result.append(rule) result.append(rule)
ad_rules_updated_at = (SystemConfigManager.get('ad_rules_updated_at') or {}).get('option', {}).get('v') or "" ad_rules_updated_at = (SystemConfigManager.get('ad_rules_updated_at') or {}).get('option', {}).get('v') or ""
new_last_update_at = ""
for i in result: for i in result:
i['adr'] = AutoDiscoveryRule.get_by_id(i['adr_id']).to_dict() i['adr'] = AutoDiscoveryRule.get_by_id(i['adr_id']).to_dict()
i['adr'].pop("attributes", None) i['adr'].pop("attributes", None)
@@ -308,6 +312,8 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if new_last_update_at < __last_update_at: if new_last_update_at < __last_update_at:
new_last_update_at = __last_update_at new_last_update_at = __last_update_at
new_last_update_at = new_last_update_at or ad_rules_updated_at
write_ad_rule_sync_history.apply_async(args=(result, oneagent_id, oneagent_name, datetime.datetime.now()), write_ad_rule_sync_history.apply_async(args=(result, oneagent_id, oneagent_name, datetime.datetime.now()),
queue=CMDB_QUEUE) queue=CMDB_QUEUE)
if not last_update_at or new_last_update_at > last_update_at: if not last_update_at or new_last_update_at > last_update_at:
@@ -693,6 +699,11 @@ class AutoDiscoveryCICRUD(DBMixin):
stdout="delete resource: {}".format(unique_value)) stdout="delete resource: {}".format(unique_value))
# TODO: delete ci # TODO: delete ci
@classmethod
def get_instance_by_id(cls, adc_id):
adc = cls.cls.get_by_id(adc_id) or abort(404, ErrFormat.adc_not_found)
return adc.instance
@classmethod @classmethod
def accept(cls, adc, adc_id=None, nickname=None): def accept(cls, adc, adc_id=None, nickname=None):
if adc_id is not None: if adc_id is not None:

View File

@@ -1618,7 +1618,8 @@ class CITriggerManager(object):
ci_id, need_children=False, use_master=False, enum_use_label=True) ci_id, need_children=False, use_master=False, enum_use_label=True)
try: try:
response = webhook_request(webhook, ci_dict).text response = webhook_request(webhook, ci_dict)
response.raise_for_status()
is_ok = True is_ok = True
except Exception as e: except Exception as e:
current_app.logger.warning("exec webhook failed: {}".format(e)) current_app.logger.warning("exec webhook failed: {}".format(e))

View File

@@ -1144,6 +1144,8 @@ class CITypeAttributeGroupManager(object):
group2pos[group['name']] = group_pos group2pos[group['name']] = group_pos
else: else:
group_pos = group2pos[group['name']] group_pos = group2pos[group['name']]
if type_id == group['type_id']:
result[group_pos]['type_id'] = type_id
for i in items: for i in items:
if i.attr_id in id2attr: if i.attr_id in id2attr:
@@ -1564,6 +1566,8 @@ class CITypeTemplateManager(object):
continue continue
for order, attr in enumerate(group['attributes'] or []): for order, attr in enumerate(group['attributes'] or []):
if attr.get('inherited'):
continue
item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id, item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id,
attr_id=attr_id_map.get(attr['id'], attr['id']), attr_id=attr_id_map.get(attr['id'], attr['id']),
first=True, to_dict=False) first=True, to_dict=False)

View File

@@ -205,12 +205,14 @@ class AutoDiscoveryCITypeRelationView(APIView):
class AutoDiscoveryCIView(APIView): class AutoDiscoveryCIView(APIView):
url_prefix = ("/adc", "/adc/<int:adc_id>", "/adc/ci_types/<int:type_id>/attributes", "/adc/ci_types") url_prefix = ("/adc", "/adc/<int:adc_id>", "/adc/ci_types/<int:type_id>/attributes", "/adc/ci_types")
def get(self, type_id=None): def get(self, type_id=None, adc_id=None):
if "attributes" in request.url: if "attributes" in request.url:
return self.jsonify(AutoDiscoveryCICRUD.get_attributes_by_type_id(type_id)) return self.jsonify(AutoDiscoveryCICRUD.get_attributes_by_type_id(type_id))
elif "ci_types" in request.url: if "ci_types" in request.url:
need_other = request.values.get("need_other") need_other = request.values.get("need_other")
return self.jsonify(AutoDiscoveryCICRUD.get_ci_types(need_other)) return self.jsonify(AutoDiscoveryCICRUD.get_ci_types(need_other))
if adc_id is not None:
return self.jsonify(AutoDiscoveryCICRUD.get_instance_by_id(adc_id))
page = get_page(request.values.pop('page', 1)) page = get_page(request.values.pop('page', 1))
page_size = get_page_size(request.values.pop('page_size', None)) page_size = get_page_size(request.values.pop('page_size', None))

View File

@@ -138,6 +138,14 @@ export function getAdc(params) {
}) })
} }
export function getAdcById(id, params) {
return axios({
url: `v0.1/adc/${id}`,
method: 'GET',
params
})
}
export function deleteAdc(adc_id) { export function deleteAdc(adc_id) {
return axios({ return axios({
url: `v0.1/adc/${adc_id}`, url: `v0.1/adc/${adc_id}`,

View File

@@ -38,6 +38,7 @@ const cmdb_en = {
attributeAD: 'Attributes AutoDiscovery', attributeAD: 'Attributes AutoDiscovery',
relationAD: 'Relation AutoDiscovery', relationAD: 'Relation AutoDiscovery',
grant: 'Grant', grant: 'Grant',
resourceViewTip: 'Not subscribed yet. Please go to the Preference page to complete your subscription.',
addGroup: 'New Group', addGroup: 'New Group',
editGroup: 'Edit Group', editGroup: 'Edit Group',
group: 'Group', group: 'Group',
@@ -602,6 +603,8 @@ const cmdb_en = {
acceptTime: 'Accept Time', acceptTime: 'Accept Time',
confirmAccept: 'Confirm Accept?', confirmAccept: 'Confirm Accept?',
acceptSuccess: 'Accept successfully', acceptSuccess: 'Accept successfully',
batchAccept: 'Currently being stored...',
batchAccept2: 'Currently being stored: {total} items, {successNum} successful, {errorNum} failed',
isAccept: 'Accept', isAccept: 'Accept',
deleteADC: 'Confirm to delete this data?', deleteADC: 'Confirm to delete this data?',
batchDelete: 'Confirm to delete this data?', batchDelete: 'Confirm to delete this data?',
@@ -686,7 +689,8 @@ if __name__ == "__main__":
tabCustom: 'Custom', tabCustom: 'Custom',
tabConfig: 'Configured', tabConfig: 'Configured',
addConfig: 'Add Config', addConfig: 'Add Config',
configErrTip: 'Please select config' configErrTip: 'Please select config',
viewRawData: 'View Raw Data'
}, },
ci: { ci: {
attributeDesc: 'Attribute Description', attributeDesc: 'Attribute Description',

View File

@@ -38,6 +38,7 @@ const cmdb_zh = {
attributeAD: '属性自动发现', attributeAD: '属性自动发现',
relationAD: '关系自动发现', relationAD: '关系自动发现',
grant: '权限配置', grant: '权限配置',
resourceViewTip: '暂未订阅, 请先到我的订阅页面完成订阅',
addGroup: '新增分组', addGroup: '新增分组',
editGroup: '修改分组', editGroup: '修改分组',
group: '分组', group: '分组',
@@ -602,6 +603,8 @@ const cmdb_zh = {
acceptTime: '入库时间', acceptTime: '入库时间',
confirmAccept: '确认入库?', confirmAccept: '确认入库?',
acceptSuccess: '入库成功', acceptSuccess: '入库成功',
batchAccept: '正在入库...',
batchAccept2: '正在入库,共{total}个,成功{successNum}个,失败{errorNum}个',
isAccept: '入库', isAccept: '入库',
deleteADC: '确认删除该条数据?', deleteADC: '确认删除该条数据?',
batchDelete: '确认删除这些数据?', batchDelete: '确认删除这些数据?',
@@ -685,7 +688,8 @@ if __name__ == "__main__":
tabCustom: '自定义', tabCustom: '自定义',
tabConfig: '已有配置', tabConfig: '已有配置',
addConfig: '添加配置', addConfig: '添加配置',
configErrTip: '请选择配置' configErrTip: '请选择配置',
viewRawData: '查看原始数据'
}, },
ci: { ci: {
attributeDesc: '查看属性配置', attributeDesc: '查看属性配置',

View File

@@ -47,6 +47,13 @@
<a-icon type="star" /> <a-icon type="star" />
{{ $t('cmdb.preference.cancelSub') }} {{ $t('cmdb.preference.cancelSub') }}
</a-menu-item> </a-menu-item>
<a-menu-item
key="citypeConfig"
@click="handleCITypeConfig"
>
<ops-icon type="ops-cmdb-citype" />
{{ $t('cmdb.menu.citypeManage') }}
</a-menu-item>
</a-menu> </a-menu>
</a-dropdown> </a-dropdown>
</a-space> </a-space>
@@ -782,6 +789,28 @@ export default {
}, },
}) })
}, },
handleCITypeConfig() {
const { id, name } = this.CIType || {}
if (id && name) {
roleHasPermissionToGrant({
app_id: 'cmdb',
resource_type_name: 'CIType',
perm: 'config',
resource_name: name,
}).then((res) => {
if (res?.result) {
const storageId = `null%${id}%${name}`
localStorage.setItem('ops_cityps_currentId', storageId)
localStorage.setItem('ops_model_config_tab_key', '1')
window.open('/cmdb/ci_types', '_blank')
} else {
this.$message.error(this.$t('noPermission'))
}
})
}
},
handlePerm() { handlePerm() {
roleHasPermissionToGrant({ roleHasPermissionToGrant({
app_id: 'cmdb', app_id: 'cmdb',

View File

@@ -20,6 +20,18 @@
<RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable> <RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-button
slot="tabBarExtraContent"
type="primary"
ghost
size="small"
class="ops-button-ghost ops-tab-button"
@click="jumpResourceView"
>
<ops-icon type="ops-cmdb-resource" />
{{ $t('cmdb.menu.ciTable') }}
</a-button>
</a-tabs> </a-tabs>
</a-card> </a-card>
</template> </template>
@@ -52,6 +64,10 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
preferenceData: {
type: Object,
default: () => {}
}
}, },
data() { data() {
return { return {
@@ -91,6 +107,16 @@ export default {
break break
} }
}) })
},
jumpResourceView() {
const isSub = this?.preferenceData?.type_ids?.includes(this.CITypeId)
if (!isSub) {
this.$message.error(this.$t('cmdb.ciType.resourceViewTip'))
return
}
localStorage.setItem('ops_ci_typeid', this.CITypeId)
window.open('/cmdb/instances/types', '_blank')
} }
}, },
} }

View File

@@ -1,6 +1,10 @@
<template> <template>
<div class="ci-types-wrap" :style="{ height: `${windowHeight - 96}px` }"> <div class="ci-types-wrap" :style="{ height: `${windowHeight - 96}px` }">
<div v-if="!CITypeGroups.length" class="ci-types-empty"> <div v-if="pageLoading" class="ci-types-loading">
<a-spin size="large" />
</div>
<div v-else-if="!CITypeGroups.length" class="ci-types-empty">
<a-empty :image="emptyImage" description=""></a-empty> <a-empty :image="emptyImage" description=""></a-empty>
<a-button icon="plus" size="small" type="primary" @click="handleClickAddGroup">{{ <a-button icon="plus" size="small" type="primary" @click="handleClickAddGroup">{{
$t('cmdb.ciType.addGroup') $t('cmdb.ciType.addGroup')
@@ -178,7 +182,12 @@
</template> </template>
<template #two> <template #two>
<div class="ci-types-right"> <div class="ci-types-right">
<CITypeDetail v-if="currentCId" :CITypeId="currentCId" :CITypeName="currentCName" /> <CITypeDetail
v-if="currentCId"
:CITypeId="currentCId"
:CITypeName="currentCName"
:preferenceData="preferenceData"
/>
<div v-else class="ci-types-right-empty"> <div v-else class="ci-types-right-empty">
<a-empty :image="emptyImage" description=""></a-empty> <a-empty :image="emptyImage" description=""></a-empty>
<a-button icon="plus" size="small" type="primary" @click="handleCreateCiFromEmpty">{{ <a-button icon="plus" size="small" type="primary" @click="handleCreateCiFromEmpty">{{
@@ -409,6 +418,7 @@ import {
exportCITypeGroups exportCITypeGroups
} from '@/modules/cmdb/api/ciTypeGroup' } from '@/modules/cmdb/api/ciTypeGroup'
import { searchAttributes, getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { searchAttributes, getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { getPreference } from '@/modules/cmdb/api/preference'
import CreateNewAttribute from './ceateNewAttribute.vue' import CreateNewAttribute from './ceateNewAttribute.vue'
import CITypeDetail from './ciTypedetail.vue' import CITypeDetail from './ciTypedetail.vue'
import emptyImage from '@/assets/data_empty.png' import emptyImage from '@/assets/data_empty.png'
@@ -485,6 +495,9 @@ export default {
searchValue: '', searchValue: '',
modelExportVisible: false, modelExportVisible: false,
pageLoading: false,
preferenceData: {}
} }
}, },
computed: { computed: {
@@ -505,14 +518,19 @@ export default {
}, },
currentGId() { currentGId() {
if (this.currentId) { if (this.currentId) {
return Number(this.currentId.split('%')[0]) const id = this?.currentId?.split?.('%')?.[0]
if (id !== 'null') {
return Number(id)
}
return null
} }
return null return null
}, },
currentCId() { currentCId() {
if (this.currentId) { if (this.currentId) {
if (this.currentId.split('%')[1] !== 'null') { const id = this?.currentId?.split?.('%')?.[1]
return Number(this.currentId.split('%')[1]) if (id !== 'null') {
return Number(id)
} }
return null return null
} }
@@ -520,8 +538,9 @@ export default {
}, },
currentCName() { currentCName() {
if (this.currentId) { if (this.currentId) {
if (this.currentId.split('%')[2] !== 'null') { const name = this?.currentId?.split?.('%')?.[2]
return this.currentId.split('%')[2] if (name !== 'null') {
return name
} }
return null return null
} }
@@ -578,7 +597,7 @@ export default {
}, },
} }
}, },
mounted() { async mounted() {
this.getAllDepAndEmployee() this.getAllDepAndEmployee()
const _currentId = localStorage.getItem('ops_cityps_currentId') const _currentId = localStorage.getItem('ops_cityps_currentId')
if (_currentId) { if (_currentId) {
@@ -587,8 +606,13 @@ export default {
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then((res) => { searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then((res) => {
this.resource_type = { groups: res.groups, id2perms: res.id2perms } this.resource_type = { groups: res.groups, id2perms: res.id2perms }
}) })
this.loadCITypes(!_currentId, true)
this.pageLoading = true
await this.loadCITypes(!_currentId, true)
this.pageLoading = false
this.getAttributes() this.getAttributes()
this.getPreference()
}, },
methods: { methods: {
getAllDepAndEmployee() { getAllDepAndEmployee() {
@@ -599,6 +623,13 @@ export default {
handleSearch(e) { handleSearch(e) {
this.searchValue = e.target.value this.searchValue = e.target.value
}, },
getPreference() {
getPreference().then((res) => {
this.preferenceData = res || {}
})
},
async loadCITypes(isResetCurrentId = false, isInit = false) { async loadCITypes(isResetCurrentId = false, isInit = false) {
const groups = await getCITypeGroupsConfig({ need_other: true }) const groups = await getCITypeGroupsConfig({ need_other: true })
let alreadyReset = false let alreadyReset = false
@@ -623,8 +654,10 @@ export default {
if (isInit) { if (isInit) {
const isMatch = groups.some((g) => { const isMatch = groups.some((g) => {
const matchGroup = `${g?.id}%null%null` === this.currentId const matchGroup = `${g?.id}%null%null` === this.currentId
const matchCITypes = g?.ci_types?.some((item) => `${g?.id}%${item?.id}%${item?.name}` === this.currentId) const matchCIType = g?.ci_types?.some((item) => {
return matchGroup || matchCITypes return `${g?.id}%${item?.id}%${item?.name}` === this.currentId || `null%${item?.id}%${item?.name}` === this.currentId
})
return matchGroup || matchCIType
}) })
if (!isMatch) { if (!isMatch) {
@@ -1082,6 +1115,12 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.ci-types-wrap { .ci-types-wrap {
margin: 0 0 -24px 0; margin: 0 0 -24px 0;
.ci-types-loading {
text-align: center;
padding-top: 150px;
}
.ci-types-empty { .ci-types-empty {
position: absolute; position: absolute;
text-align: center; text-align: center;

View File

@@ -239,6 +239,8 @@
<a-form-item> <a-form-item>
<a-select <a-select
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')" :placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
optionFilterProp="title"
show-search
allowClear allowClear
v-model="item.parentAttrId" v-model="item.parentAttrId"
> >
@@ -250,6 +252,7 @@
'parent' 'parent'
)" )"
:key="attr.id" :key="attr.id"
:title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>
@@ -263,6 +266,8 @@
<a-form-item> <a-form-item>
<a-select <a-select
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')" :placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
optionFilterProp="title"
show-search
allowClear allowClear
v-model="item.childAttrId" v-model="item.childAttrId"
> >
@@ -274,6 +279,7 @@
'child' 'child'
)" )"
:key="attr.id" :key="attr.id"
:title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>

View File

@@ -0,0 +1,60 @@
<template>
<a-modal
:title="$t('cmdb.ad.viewRawData')"
:visible="visible"
wrapClassName="ci-json-editor"
width="50%"
:footer="null"
@cancel="handleCancel"
>
<vue-json-editor
v-model="jsonData"
:style="{ '--custom-height': `${windowHeight - 300}px` }"
:showBtns="false"
:mode="'code'"
lang="zh"
/>
</a-modal>
</template>
<script>
import vueJsonEditor from 'vue-json-editor'
export default {
name: 'RawDataModal',
components: { vueJsonEditor },
data() {
return {
visible: false,
jsonData: {},
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
},
methods: {
open(jsonData) {
this.visible = true
this.jsonData = jsonData
},
handleCancel() {
this.visible = false
this.jsonData = {}
}
},
}
</script>
<style lang="less">
.ci-json-editor {
.jsoneditor-outer {
height: var(--custom-height) !important;
border: 1px solid #2f54eb;
}
div.jsoneditor-menu {
background-color: #2f54eb;
}
}
</style>

View File

@@ -33,10 +33,11 @@
<template #two> <template #two>
<div id="discovery-ci"> <div id="discovery-ci">
<AdcCounter :typeId="currentType" /> <AdcCounter :typeId="currentType" />
<a-spin :tip="loadTip" :spinning="loading">
<div class="discovery-ci-header"> <div class="discovery-ci-header">
<a-input-search <a-input-search
:placeholder="$t('cmdb.components.pleaseSearch')" :placeholder="$t('cmdb.components.pleaseSearch')"
:style="{ width: '200px', marginRight: '20px' }" :style="{ width: '200px' }"
@search="handleSearch" @search="handleSearch"
allowClear allowClear
/> />
@@ -46,6 +47,15 @@
<span @click="batchDelete">{{ $t('delete') }}</span> <span @click="batchDelete">{{ $t('delete') }}</span>
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedCount }) }}</span> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedCount }) }}</span>
</span> </span>
<a-button
type="primary"
class="ops-button-ghost"
ghost
@click="getAdc()"
>
<ops-icon type="veops-refresh" />
{{ $t('refresh') }}
</a-button>
<a-button <a-button
type="primary" type="primary"
ghost ghost
@@ -68,11 +78,11 @@
:height="tableHeight" :height="tableHeight"
:scroll-y="{ enabled: true, gt: 50 }" :scroll-y="{ enabled: true, gt: 50 }"
:scroll-x="{ enabled: true, gt: 0 }" :scroll-x="{ enabled: true, gt: 0 }"
:checkbox-config="{ reserve: true, highlight: true, range: true }"
:sort-config="{ remote: false, trigger: 'cell' }"
@checkbox-change="onSelectChange" @checkbox-change="onSelectChange"
@checkbox-all="onSelectChange" @checkbox-all="onSelectChange"
@checkbox-range-end="onSelectChange" @checkbox-range-end="onSelectChange"
:checkbox-config="{ reserve: true, highlight: true, range: true }"
:sort-config="{ remote: false, trigger: 'cell' }"
> >
<vxe-column <vxe-column
align="center" align="center"
@@ -92,15 +102,13 @@
:width="col.width" :width="col.width"
:sortable="col.sortable" :sortable="col.sortable"
> >
<template v-if="col.value_type === '6' || col.is_password" #default="{row}"> <template #default="{row}">
<PasswordField <PasswordField
v-if="col.is_password" v-if="col.is_password"
:password="row[col.field]" :password="row[col.field]"
/> />
<span <span>
v-else-if="col.value_type === '6' && row[col.field]" {{ typeof row[col.field] === 'object' ? JSON.stringify(row[col.field]) : row[col.field] }}
>
{{ row[col.field] }}
</span> </span>
</template> </template>
</vxe-column> </vxe-column>
@@ -137,7 +145,7 @@
></vxe-column> ></vxe-column>
<vxe-column <vxe-column
:title="$t('operation')" :title="$t('operation')"
v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }" v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
align="center" align="center"
fixed="right" fixed="right"
> >
@@ -146,6 +154,9 @@
<a-tooltip :title="$t('cmdb.ad.accept')"> <a-tooltip :title="$t('cmdb.ad.accept')">
<a v-if="!row.is_accept" @click="accept(row)"><ops-icon type="cmdb-manual_warehousing"/></a> <a v-if="!row.is_accept" @click="accept(row)"><ops-icon type="cmdb-manual_warehousing"/></a>
</a-tooltip> </a-tooltip>
<a-tooltip :title="$t('cmdb.ad.viewRawData')">
<a @click="viewADC(row)"><a-icon type="eye"/></a>
</a-tooltip>
<a :style="{ color: 'red' }" @click="deleteADC(row)"><a-icon type="delete"/></a> <a :style="{ color: 'red' }" @click="deleteADC(row)"><a-icon type="delete"/></a>
</a-space> </a-space>
</template> </template>
@@ -157,6 +168,7 @@
</div> </div>
</template> </template>
</ops-table> </ops-table>
</a-spin>
<a-modal <a-modal
v-model="logModalVisible" v-model="logModalVisible"
@@ -175,6 +187,8 @@
</p> </p>
</a-modal> </a-modal>
</div> </div>
<RawDataModal ref="rawDataModalRef" />
</template> </template>
</TwoColumnLayout> </TwoColumnLayout>
</template> </template>
@@ -185,6 +199,7 @@ import XEUtils from 'xe-utils'
import TwoColumnLayout from '@/components/TwoColumnLayout' import TwoColumnLayout from '@/components/TwoColumnLayout'
import AdcCounter from './components/adcCounter.vue' import AdcCounter from './components/adcCounter.vue'
import PasswordField from './components/passwordField.vue' import PasswordField from './components/passwordField.vue'
import RawDataModal from './components/rawDataModal.vue'
import { import {
getADCCiTypes, getADCCiTypes,
@@ -192,7 +207,8 @@ import {
updateADCAccept, updateADCAccept,
getADCCiTypesAttrs, getADCCiTypesAttrs,
deleteAdc, deleteAdc,
getAdcExecHistories getAdcExecHistories,
getAdcById
} from '../../api/discovery' } from '../../api/discovery'
import { getCITableColumns } from '../../utils/helper' import { getCITableColumns } from '../../utils/helper'
@@ -201,7 +217,8 @@ export default {
components: { components: {
TwoColumnLayout, TwoColumnLayout,
AdcCounter, AdcCounter,
PasswordField PasswordField,
RawDataModal
}, },
data() { data() {
return { return {
@@ -216,6 +233,8 @@ export default {
logTextArray: [], logTextArray: [],
acceptByFilters: [], acceptByFilters: [],
selectedCount: 0, selectedCount: 0,
loading: false,
loadTip: ''
} }
}, },
computed: { computed: {
@@ -283,6 +302,9 @@ export default {
this.$refs.xTable.getVxetableRef().clearSort() this.$refs.xTable.getVxetableRef().clearSort()
}, },
getAdc(isInit) { getAdc(isInit) {
this.loading = true
this.loadTip = this.$t('loading')
getAdc({ getAdc({
type_id: this.currentType, type_id: this.currentType,
page_size: 100000, page_size: 100000,
@@ -314,6 +336,9 @@ export default {
xTable.refreshColumn() xTable.refreshColumn()
} }
}) })
.finally(() => {
this.loading = false
})
}, },
getColumns(data, attrList) { getColumns(data, attrList) {
const width = document.getElementById('discovery-ci').clientWidth - 50 const width = document.getElementById('discovery-ci').clientWidth - 50
@@ -352,36 +377,80 @@ export default {
onCancel() {}, onCancel() {},
}) })
}, },
async viewADC(row) {
const res = await getAdcById(row.id)
this.$refs.rawDataModalRef.open(res || {})
},
async batchAccept() { async batchAccept() {
let successNum = 0
let errorNum = 0
this.loading = true
this.loadTip = this.$t('cmdb.ad.batchAccept')
for (let i = 0; i < this.selectedRowKeys.length; i++) { for (let i = 0; i < this.selectedRowKeys.length; i++) {
await updateADCAccept(this.selectedRowKeys[i]) await updateADCAccept(this.selectedRowKeys[i]).then((res) => {
successNum += 1
}).catch(() => {
errorNum += 1
}).finally(() => {
this.loadTip = this.$t('cmdb.ad.batchAccept2', {
total: this.selectedRowKeys.length,
successNum: successNum,
errorNum: errorNum,
})
})
} }
this.$message.success(this.$t('cmdb.ad.acceptSuccess'))
this.getAdc(false) this.loading = false
this.loadTip = ''
this.selectedRowKeys = [] this.selectedRowKeys = []
this.getAdc(false)
this.$refs.xTable.getVxetableRef().clearCheckboxRow() this.$refs.xTable.getVxetableRef().clearCheckboxRow()
this.$refs.xTable.getVxetableRef().clearCheckboxReserve() this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
this.$refs.xTable.getVxetableRef().clearSort() this.$refs.xTable.getVxetableRef().clearSort()
}, },
async batchDelete() { async batchDelete() {
const that = this
this.$confirm({ this.$confirm({
title: that.$t('warning'), title: this.$t('warning'),
content: that.$t('cmdb.ad.batchDelete'), content: this.$t('cmdb.ad.batchDelete'),
async onOk() { onOk: () => {
for (let i = 0; i < that.selectedRowKeys.length; i++) { this.batchDeleteAsync()
await deleteAdc(that.selectedRowKeys[i])
} }
that.$message.success(that.$t('deleteSuccess'))
that.getAdc(false)
that.selectedRowKeys = []
that.$refs.xTable.getVxetableRef().clearCheckboxRow()
that.$refs.xTable.getVxetableRef().clearCheckboxReserve()
that.$refs.xTable.getVxetableRef().clearSort()
},
onCancel() {},
}) })
}, },
async batchDeleteAsync() {
let successNum = 0
let errorNum = 0
this.loading = true
this.loadTip = this.$t('cmdb.ci.batchDeleting')
for (let i = 0; i < this.selectedRowKeys.length; i++) {
await deleteAdc(this.selectedRowKeys[i]).then((res) => {
successNum += 1
}).catch(() => {
errorNum += 1
}).finally(() => {
this.loadTip = this.$t('cmdb.ci.batchDeleting2', {
total: this.selectedRowKeys.length,
successNum: successNum,
errorNum: errorNum,
})
})
}
this.loading = false
this.loadTip = ''
this.selectedRowKeys = []
this.getAdc(false)
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
this.$refs.xTable.getVxetableRef().clearSort()
},
onSelectChange({ records, checked }) { onSelectChange({ records, checked }) {
this.selectedRowKeys = records.map((item) => item.id) this.selectedRowKeys = records.map((item) => item.id)
}, },
@@ -458,6 +527,7 @@ export default {
.discovery-ci-header { .discovery-ci-header {
display: flex; display: flex;
align-items: center; align-items: center;
column-gap: 20px;
padding-top: 18px; padding-top: 18px;
padding-bottom: 10px; padding-bottom: 10px;
} }

View File

@@ -77,12 +77,15 @@
<a-form-item> <a-form-item>
<a-select <a-select
:placeholder="$t('cmdb.ciType.attributeAssociationTip4')" :placeholder="$t('cmdb.ciType.attributeAssociationTip4')"
optionFilterProp="title"
show-search
allowClear allowClear
v-model="item.parentAttrId" v-model="item.parentAttrId"
> >
<a-select-option <a-select-option
v-for="attr in filterAttributes(modalParentAttributes, item.childAttrId, modalChildAttributes, 'parent')" v-for="attr in filterAttributes(modalParentAttributes, item.childAttrId, modalChildAttributes, 'parent')"
:key="attr.id" :key="attr.id"
:title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>
@@ -96,12 +99,15 @@
<a-form-item> <a-form-item>
<a-select <a-select
:placeholder="$t('cmdb.ciType.attributeAssociationTip5')" :placeholder="$t('cmdb.ciType.attributeAssociationTip5')"
optionFilterProp="title"
show-search
allowClear allowClear
v-model="item.childAttrId" v-model="item.childAttrId"
> >
<a-select-option <a-select-option
v-for="attr in filterAttributes(modalChildAttributes, item.parentAttrId, modalParentAttributes, 'child')" v-for="attr in filterAttributes(modalChildAttributes, item.parentAttrId, modalParentAttributes, 'child')"
:key="attr.id" :key="attr.id"
:title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>

View File

@@ -1,6 +1,9 @@
<template> <template>
<div :style="{ marginBottom: '-24px' }"> <div :style="{ marginBottom: '-24px' }">
<div v-if="!subscribeTreeViewCiTypesLoading && subscribeTreeViewCiTypes.length === 0"> <div v-if="subscribeTreeViewCiTypesLoading" class="page-loading">
<a-spin size="large" />
</div>
<div v-else-if="subscribeTreeViewCiTypes.length === 0">
<a-alert banner> <a-alert banner>
<template #message> <template #message>
<span>{{ $t('cmdb.preference.tips1') }}</span> <span>{{ $t('cmdb.preference.tips1') }}</span>
@@ -638,7 +641,11 @@ export default {
}, },
columnDrop() { columnDrop() {
this.$nextTick(() => { this.$nextTick(() => {
const xTable = this.$refs.xTable.getVxetableRef() const xTable = this.$refs?.xTable?.getVxetableRef?.()
if (!xTable) {
return
}
this.sortable = Sortable.create( this.sortable = Sortable.create(
xTable.$el.querySelector('.body--wrapper>.vxe-table--header .vxe-header--row'), xTable.$el.querySelector('.body--wrapper>.vxe-table--header .vxe-header--row'),
{ {
@@ -1015,6 +1022,11 @@ export default {
<style lang="less"> <style lang="less">
@import '../index.less'; @import '../index.less';
.page-loading {
text-align: center;
padding-top: 150px;
}
.tree-views { .tree-views {
width: 100%; width: 100%;
height: calc(100% - 32px); height: calc(100% - 32px);