Compare commits

...

5 Commits

11 changed files with 148 additions and 18 deletions

View File

@@ -308,6 +308,8 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
if 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()),
queue=CMDB_QUEUE)
if not last_update_at or new_last_update_at > last_update_at:
@@ -693,6 +695,11 @@ class AutoDiscoveryCICRUD(DBMixin):
stdout="delete resource: {}".format(unique_value))
# 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
def accept(cls, adc, adc_id=None, nickname=None):
if adc_id is not None:
@@ -725,7 +732,7 @@ class AutoDiscoveryCICRUD(DBMixin):
from api.tasks.cmdb import add_net_device_ports
add_net_device_ports.apply_async(args=(ci_id, adc.instance['ports']),
queue=CMDB_QUEUE)
adc.update(is_accept=True,
accept_by=nickname or current_user.nickname,
accept_time=datetime.datetime.now(),

View File

@@ -205,12 +205,14 @@ class AutoDiscoveryCITypeRelationView(APIView):
class AutoDiscoveryCIView(APIView):
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:
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")
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_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) {
return axios({
url: `v0.1/adc/${adc_id}`,

View File

@@ -686,7 +686,8 @@ if __name__ == "__main__":
tabCustom: 'Custom',
tabConfig: 'Configured',
addConfig: 'Add Config',
configErrTip: 'Please select config'
configErrTip: 'Please select config',
viewRawData: 'View Raw Data'
},
ci: {
attributeDesc: 'Attribute Description',

View File

@@ -685,7 +685,8 @@ if __name__ == "__main__":
tabCustom: '自定义',
tabConfig: '已有配置',
addConfig: '添加配置',
configErrTip: '请选择配置'
configErrTip: '请选择配置',
viewRawData: '查看原始数据'
},
ci: {
attributeDesc: '查看属性配置',

View File

@@ -1,6 +1,10 @@
<template>
<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-button icon="plus" size="small" type="primary" @click="handleClickAddGroup">{{
$t('cmdb.ciType.addGroup')
@@ -485,6 +489,7 @@ export default {
searchValue: '',
modelExportVisible: false,
pageLoading: false
}
},
computed: {
@@ -578,7 +583,7 @@ export default {
},
}
},
mounted() {
async mounted() {
this.getAllDepAndEmployee()
const _currentId = localStorage.getItem('ops_cityps_currentId')
if (_currentId) {
@@ -587,7 +592,11 @@ export default {
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then((res) => {
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()
},
methods: {
@@ -1082,6 +1091,12 @@ export default {
<style lang="less" scoped>
.ci-types-wrap {
margin: 0 0 -24px 0;
.ci-types-loading {
text-align: center;
padding-top: 150px;
}
.ci-types-empty {
position: absolute;
text-align: center;

View File

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

@@ -92,15 +92,13 @@
:width="col.width"
:sortable="col.sortable"
>
<template v-if="col.value_type === '6' || col.is_password" #default="{row}">
<template #default="{row}">
<PasswordField
v-if="col.is_password"
:password="row[col.field]"
/>
<span
v-else-if="col.value_type === '6' && row[col.field]"
>
{{ row[col.field] }}
<span>
{{ typeof row[col.field] === 'object' ? JSON.stringify(row[col.field]) : row[col.field] }}
</span>
</template>
</vxe-column>
@@ -137,7 +135,7 @@
></vxe-column>
<vxe-column
:title="$t('operation')"
v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }"
v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
align="center"
fixed="right"
>
@@ -146,6 +144,9 @@
<a-tooltip :title="$t('cmdb.ad.accept')">
<a v-if="!row.is_accept" @click="accept(row)"><ops-icon type="cmdb-manual_warehousing"/></a>
</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-space>
</template>
@@ -175,6 +176,8 @@
</p>
</a-modal>
</div>
<RawDataModal ref="rawDataModalRef" />
</template>
</TwoColumnLayout>
</template>
@@ -185,6 +188,7 @@ import XEUtils from 'xe-utils'
import TwoColumnLayout from '@/components/TwoColumnLayout'
import AdcCounter from './components/adcCounter.vue'
import PasswordField from './components/passwordField.vue'
import RawDataModal from './components/rawDataModal.vue'
import {
getADCCiTypes,
@@ -192,7 +196,8 @@ import {
updateADCAccept,
getADCCiTypesAttrs,
deleteAdc,
getAdcExecHistories
getAdcExecHistories,
getAdcById
} from '../../api/discovery'
import { getCITableColumns } from '../../utils/helper'
@@ -201,7 +206,8 @@ export default {
components: {
TwoColumnLayout,
AdcCounter,
PasswordField
PasswordField,
RawDataModal
},
data() {
return {
@@ -352,6 +358,12 @@ export default {
onCancel() {},
})
},
async viewADC(row) {
const res = await getAdcById(row.id)
this.$refs.rawDataModalRef.open(res || {})
},
async batchAccept() {
for (let i = 0; i < this.selectedRowKeys.length; i++) {
await updateADCAccept(this.selectedRowKeys[i])

View File

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

View File

@@ -1,6 +1,9 @@
<template>
<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>
<template #message>
<span>{{ $t('cmdb.preference.tips1') }}</span>
@@ -638,7 +641,11 @@ export default {
},
columnDrop() {
this.$nextTick(() => {
const xTable = this.$refs.xTable.getVxetableRef()
const xTable = this.$refs?.xTable?.getVxetableRef?.()
if (!xTable) {
return
}
this.sortable = Sortable.create(
xTable.$el.querySelector('.body--wrapper>.vxe-table--header .vxe-header--row'),
{
@@ -1015,6 +1022,11 @@ export default {
<style lang="less">
@import '../index.less';
.page-loading {
text-align: center;
padding-top: 150px;
}
.tree-views {
width: 100%;
height: calc(100% - 32px);