feat(cmdb-ui):i18n

This commit is contained in:
wang-liang0615 2024-01-02 17:37:28 +08:00
parent 3401cf4a1e
commit 420106ae65
88 changed files with 15822 additions and 14685 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -63,7 +63,7 @@ export default {
updateCI(this.row.ci_id || this.row._id, { updateCI(this.row.ci_id || this.row._id, {
[`${this.column.property}`]: this.default_value_json_right ? this.jsonData : {}, [`${this.column.property}`]: this.default_value_json_right ? this.jsonData : {},
}).then(() => { }).then(() => {
this.$message.success('保存成功!') this.$message.success(this.$t('saveSuccess'))
this.handleCancel() this.handleCancel()
this.$emit('jsonEditorOk', this.row, this.column, this.default_value_json_right ? this.jsonData : {}) this.$emit('jsonEditorOk', this.row, this.column, this.default_value_json_right ? this.jsonData : {})
}) })

View File

@ -7,7 +7,7 @@
width: '200px', width: '200px',
height: `${height}px`, height: `${height}px`,
}" }"
:titles="['未选属性', '已选属性']" :titles="[$t('cmdb.components.unselectAttributes'), $t('cmdb.components.selectAttributes')]"
:render="(item) => item.title" :render="(item) => item.title"
:targetKeys="targetKeys" :targetKeys="targetKeys"
@change="handleChange" @change="handleChange"
@ -16,7 +16,7 @@
:filterOption="filterOption" :filterOption="filterOption"
class="cmdb-transfer" class="cmdb-transfer"
> >
<span slot="notFoundContent">暂无数据</span> <span slot="notFoundContent">{{ $t('noData') }}</span>
<template slot="children" slot-scope="{ props: { direction, filteredItems } }"> <template slot="children" slot-scope="{ props: { direction, filteredItems } }">
<div class="ant-transfer-list-content" v-if="direction === 'right'"> <div class="ant-transfer-list-content" v-if="direction === 'right'">
<draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable"> <draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable">
@ -27,10 +27,11 @@
:style="{ height: '38px' }" :style="{ height: '38px' }"
> >
<li <li
:class="{ :class="
'ant-transfer-list-content-item': true, `ant-transfer-list-content-item ${
'ant-transfer-list-content-item-selected': selectedKeys.includes(item.key), selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
}" }`
"
@click="setSelectedKeys(item)" @click="setSelectedKeys(item)"
> >
<OpsMoveIcon class="move-icon" /> <OpsMoveIcon class="move-icon" />
@ -62,9 +63,11 @@
:style="{ height: '38px' }" :style="{ height: '38px' }"
> >
<li <li
:class="`ant-transfer-list-content-item ${ :class="
selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : '' `ant-transfer-list-content-item ${
}`" selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
}`
"
@click="setSelectedKeys(item)" @click="setSelectedKeys(item)"
> >
<div class="ant-transfer-list-content-item-text" style="display: inline"> <div class="ant-transfer-list-content-item-text" style="display: inline">
@ -83,7 +86,7 @@
</template> </template>
</a-transfer> </a-transfer>
<div v-if="hasFooter" :style="{ marginTop: '5px', height: '20px' }"> <div v-if="hasFooter" :style="{ marginTop: '5px', height: '20px' }">
<a-button :style="{ float: 'right' }" size="small" @click="handleSubmit" type="primary">确定</a-button> <a-button :style="{ float: 'right' }" size="small" @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</div> </div>
</template> </template>
@ -110,12 +113,12 @@ export default {
default: true, default: true,
}, },
isSortable: { isSortable: {
// 右侧是否可排序 // Is the right side sortable?
type: Boolean, type: Boolean,
default: true, default: true,
}, },
isFixable: { isFixable: {
// 右侧是否可固定 // Can the right side be fixed?
type: Boolean, type: Boolean,
default: true, default: true,
}, },

View File

@ -1,21 +1,30 @@
<template> <template>
<a-modal :visible="visible" title="导出数据" @cancel="handleCancel" okText="导出" @ok="handleOk"> <a-modal
:visible="visible"
:title="$t('cmdb.components.downloadCI')"
@cancel="handleCancel"
@ok="handleOk"
width="700px"
>
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }"> <a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }">
<a-form-item label="文件名"> <a-form-item :label="$t('cmdb.components.filename')">
<a-input <a-input
placeholder="请输入文件名" :placeholder="$t('cmdb.components.filenameInputTips')"
v-decorator="['filename', { rules: [{ required: true, message: '请输入文件名' }] }]" v-decorator="['filename', { rules: [{ required: true, message: $t('cmdb.components.filenameInputTips') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="保存类型"> <a-form-item :label="$t('cmdb.components.saveType')">
<a-select <a-select
placeholder="请选择保存类型" :placeholder="$t('cmdb.components.saveTypeTips')"
v-decorator="['type', { rules: [{ required: true, message: '请选择保存类型' }], initialValue: 'xlsx' }]" v-decorator="[
'type',
{ rules: [{ required: true, message: $t('cmdb.components.saveTypeTips') }], initialValue: 'xlsx' },
]"
> >
<a-select-option v-for="item in typeList" :key="item.id" :values="item.id">{{ item.label }}</a-select-option> <a-select-option v-for="item in typeList" :key="item.id" :values="item.id">{{ item.label }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="选择字段"> <a-form-item :label="$t('cmdb.ciType.selectAttributes')">
<div <div
:style="{ :style="{
paddingLeft: '26px', paddingLeft: '26px',
@ -29,7 +38,7 @@
:checked="checkAll" :checked="checkAll"
@change="onCheckAllChange" @change="onCheckAllChange"
:style="{ marginRight: '10px' }" :style="{ marginRight: '10px' }"
/>全选 />{{ $t('checkAll') }}
</div> </div>
<div <div
:style="{ :style="{
@ -76,30 +85,7 @@ export default {
}, },
}, },
data() { data() {
const typeList = [
{
id: 'xlsx',
label: 'Excel工作簿(*.xlsx)',
},
{
id: 'csv',
label: 'CSV(逗号分隔)(*.csv)',
},
{
id: 'html',
label: '网页(*.html)',
},
{
id: 'xml',
label: 'XML数据(*.xml)',
},
{
id: 'txt',
label: '文本文件(制表符分隔)(*.txt)',
},
]
return { return {
typeList,
visible: false, visible: false,
form: this.$form.createForm(this), form: this.$form.createForm(this),
preferenceAttrList: [], preferenceAttrList: [],
@ -109,6 +95,32 @@ export default {
defaultChecked: [], defaultChecked: [],
} }
}, },
computed: {
typeList() {
return [
{
id: 'xlsx',
label: this.$t('cmdb.components.xlsx'),
},
{
id: 'csv',
label: this.$t('cmdb.components.csv'),
},
{
id: 'html',
label: this.$t('cmdb.components.html'),
},
{
id: 'xml',
label: this.$t('cmdb.components.xml'),
},
{
id: 'txt',
label: this.$t('cmdb.components.txt'),
},
]
},
},
methods: { methods: {
...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']), ...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']),
open({ preferenceAttrList, ciTypeName = undefined }) { open({ preferenceAttrList, ciTypeName = undefined }) {

View File

@ -25,17 +25,17 @@
</vxe-column> </vxe-column>
<template #empty> <template #empty>
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB"> <div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
<a-icon type="loading" /> 加载中... <a-icon type="loading" /> {{ $t('loading') }}
</div> </div>
<div v-else> <div v-else>
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
<div>暂无数据</div> <div>{{ $t('noData') }}</div>
</div> </div>
</template> </template>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">授权用户/部门</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">授权角色</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
@ -95,10 +95,8 @@ export default {
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
}, permMap() {
data() { return permMap()
return {
permMap,
} }
}, },
methods: { methods: {
@ -131,8 +129,8 @@ export default {
} else { } else {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: `确认删除 ${row.name} 授权 权限`, content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }),
onOk() { onOk() {
that.handleChange({ target: { checked: false } }, col, row) that.handleChange({ target: { checked: false } }, col, row)
const _idx = that.tableData.findIndex((item) => item.rid === row.rid) const _idx = that.tableData.findIndex((item) => item.rid === row.rid)

View File

@ -1,11 +1,15 @@
export const permMap = { import i18n from '@/lang'
read: '查看',
add: '新增', export const permMap = () => {
create: '新增', return {
update: '修改', read: i18n.t('view'),
delete: '删除', add: i18n.t('new'),
config: '配置', create: i18n.t('new'),
grant: '授权', update: i18n.t('update'),
'read_attr': '查看字段', delete: i18n.t('delete'),
'read_ci': '查看实例' config: i18n.t('cmdb.components.config'),
grant: i18n.t('grant'),
'read_attr': i18n.t('cmdb.components.readAttribute'),
'read_ci': i18n.t('cmdb.components.readCI')
}
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }"> <div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
<template v-if="cmdbGrantType.includes('ci_type')"> <template v-if="cmdbGrantType.includes('ci_type')">
<div class="cmdb-grant-title">模型权限</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
<CiTypeGrant <CiTypeGrant
:CITypeId="CITypeId" :CITypeId="CITypeId"
:tableData="tableData" :tableData="tableData"
@ -18,7 +18,7 @@
cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type')) cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type'))
" "
> >
<div class="cmdb-grant-title">实例权限</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.ciGrant') }}</div>
<CiTypeGrant <CiTypeGrant
:CITypeId="CITypeId" :CITypeId="CITypeId"
:tableData="tableData" :tableData="tableData"
@ -32,7 +32,7 @@
/> />
</template> </template>
<template v-if="cmdbGrantType.includes('type_relation')"> <template v-if="cmdbGrantType.includes('type_relation')">
<div class="cmdb-grant-title">关系权限</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.relationGrant') }}</div>
<TypeRelationGrant <TypeRelationGrant
:typeRelationIds="typeRelationIds" :typeRelationIds="typeRelationIds"
:tableData="tableData" :tableData="tableData"
@ -45,7 +45,7 @@
/> />
</template> </template>
<template v-if="cmdbGrantType.includes('relation_view')"> <template v-if="cmdbGrantType.includes('relation_view')">
<div class="cmdb-grant-title">{{ resourceTypeName }}权限</div> <div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
<RelationViewGrant <RelationViewGrant
:resourceTypeName="resourceTypeName" :resourceTypeName="resourceTypeName"
:tableData="tableData" :tableData="tableData"
@ -116,7 +116,7 @@ export default {
attrGroup: [], attrGroup: [],
filerPerimissions: {}, filerPerimissions: {},
loading: false, loading: false,
addedRids: [], // 本次新增的rid addedRids: [], // added rid this time
} }
}, },
computed: { computed: {
@ -203,12 +203,12 @@ export default {
this.tableData = perms this.tableData = perms
this.loading = false this.loading = false
}, },
// 授权common-setting中的部门 从中拿到roleid // Grant the department in common-setting and get the roleid from it
grantDepart(grantType) { grantDepart(grantType) {
this.$refs.grantModal.open('depart') this.$refs.grantModal.open('depart')
this.grantType = grantType this.grantType = grantType
}, },
// 授权最古老的角色权限 // Grant the oldest role permissions
grantRole(grantType) { grantRole(grantType) {
this.$refs.grantModal.open('role') this.$refs.grantModal.open('role')
this.grantType = grantType this.grantType = grantType

View File

@ -26,9 +26,9 @@ export default {
computed: { computed: {
title() { title() {
if (this.type === 'depart') { if (this.type === 'depart') {
return '授权用户/部门' return this.$t('cmdb.components.grantUser')
} }
return '授权角色' return this.$t('cmdb.components.grantRole')
}, },
}, },
methods: { methods: {

View File

@ -2,9 +2,9 @@
<a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel"> <a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel">
<CustomRadio <CustomRadio
:radioList="[ :radioList="[
{ value: 1, label: '全部' }, { value: 1, label: $t('cmdb.components.all') },
{ value: 2, label: '自定义', layout: 'vertical' }, { value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
{ value: 3, label: '' }, { value: 3, label: $t('cmdb.components.none') },
]" ]"
v-model="radioValue" v-model="radioValue"
> >
@ -16,7 +16,7 @@
:clearable="true" :clearable="true"
searchable searchable
:options="attrGroup" :options="attrGroup"
placeholder="请选择属性字段" :placeholder="$t('cmdb.ciType.selectAttributes')"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
:limit="10" :limit="10"
:limitText="(count) => `+ ${count}`" :limitText="(count) => `+ ${count}`"
@ -24,8 +24,8 @@
(node) => { (node) => {
return { return {
id: node.name || -1, id: node.name || -1,
label: node.alias || node.name || '其他', label: node.alias || node.name || $t('other'),
title: node.alias || node.name || '其他', title: node.alias || node.name || $t('other'),
children: node.attributes, children: node.attributes,
} }
} }
@ -42,7 +42,7 @@
:wrapperCol="{ span: 10 }" :wrapperCol="{ span: 10 }"
ref="form" ref="form"
> >
<a-form-model-item label="名称" prop="name"> <a-form-model-item :label="$t('name')" prop="name">
<a-input v-model="form.name" /> <a-input v-model="form.name" />
</a-form-model-item> </a-form-model-item>
<FilterComp <FilterComp
@ -99,16 +99,16 @@ export default {
name: '', name: '',
}, },
rules: { rules: {
name: [{ required: true, message: '请输入自定义筛选条件名' }], name: [{ required: true, message: this.$t('cmdb.components.customizeFilterName') }],
}, },
} }
}, },
computed: { computed: {
title() { title() {
if (this.colType === 'read_attr') { if (this.colType === 'read_attr') {
return '字段权限' return this.$t('cmdb.components.attributeGrant')
} }
return '实例权限' return this.$t('cmdb.components.ciGrant')
}, },
attrGroup() { attrGroup() {
return this.provide_attrGroup() return this.provide_attrGroup()

View File

@ -17,8 +17,8 @@
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">授权用户/部门</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">授权角色</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
@ -51,7 +51,6 @@ export default {
}, },
data() { data() {
return { return {
permMap,
columns: ['read', 'grant'], columns: ['read', 'grant'],
} }
}, },
@ -65,6 +64,9 @@ export default {
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
permMap() {
return permMap()
}
}, },
methods: { methods: {
getCurrentRowStyle, getCurrentRowStyle,

View File

@ -17,8 +17,8 @@
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">授权用户/部门</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">授权角色</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
@ -51,7 +51,6 @@ export default {
}, },
data() { data() {
return { return {
permMap,
columns: ['create', 'grant', 'delete'], columns: ['create', 'grant', 'delete'],
} }
}, },
@ -65,6 +64,9 @@ export default {
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
permMap() {
return permMap()
}
}, },
methods: { methods: {
getCurrentRowStyle, getCurrentRowStyle,

View File

@ -132,7 +132,7 @@ export default {
} else if (color.indexOf('rgb') !== -1) { } else if (color.indexOf('rgb') !== -1) {
hsvObj = this.rgbToHSV(color) hsvObj = this.rgbToHSV(color)
} else { } else {
throw new Error('初始化颜色格式错误,使用#fff或rgb格式') throw new Error(this.$t('cmdb.components.colorPickerError'))
// this.$message.error('颜色格式错误使用16进制格式') // this.$message.error('颜色格式错误使用16进制格式')
} }
if (hsvObj) { if (hsvObj) {

View File

@ -15,19 +15,19 @@
:edit-config="isEdit ? { trigger: 'click', mode: 'cell' } : {}" :edit-config="isEdit ? { trigger: 'click', mode: 'cell' } : {}"
> >
<template v-if="isEdit"> <template v-if="isEdit">
<vxe-colgroup title="自动发现"> <vxe-colgroup :title="$t('cmdb.ciType.autoDiscovery')">
<vxe-column field="name" title="名称" width="100"> </vxe-column> <vxe-column field="name" :title="$t('name')" width="100"> </vxe-column>
<vxe-column field="type" title="类型" width="80"> </vxe-column> <vxe-column field="type" :title="$t('type')" width="80"> </vxe-column>
<vxe-column field="example" title="示例值"> <vxe-column field="example" :title="$t('cmdb.components.example')">
<template #default="{row}"> <template #default="{row}">
<span v-if="row.type === 'json'">{{ JSON.stringify(row.example) }}</span> <span v-if="row.type === 'json'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span> <span v-else>{{ row.example }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="desc" title="描述"> </vxe-column> <vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</vxe-colgroup> </vxe-colgroup>
<vxe-colgroup title="模型属性"> <vxe-colgroup :title="$t('cmdb.ciType.attributes')">
<vxe-column field="attr" title="名称" :edit-render="{}"> <vxe-column field="attr" :title="$t('name')" :edit-render="{}">
<template #default="{row}"> <template #default="{row}">
{{ row.attr }} {{ row.attr }}
</template> </template>
@ -45,15 +45,15 @@
</vxe-colgroup> </vxe-colgroup>
</template> </template>
<template v-else> <template v-else>
<vxe-column field="name" title="名称" width="100"> </vxe-column> <vxe-column field="name" :title="$t('name')" width="100"> </vxe-column>
<vxe-column field="type" title="类型" width="80"> </vxe-column> <vxe-column field="type" :title="$t('type')" width="80"> </vxe-column>
<vxe-column field="example" title="示例值"> <vxe-column field="example" :title="$t('cmdb.components.example')">
<template #default="{row}"> <template #default="{row}">
<span v-if="row.type === 'object'">{{ JSON.stringify(row.example) }}</span> <span v-if="row.type === 'object'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span> <span v-else>{{ row.example }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="desc" title="描述"> </vxe-column> <vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</template> </template>
</vxe-table> </vxe-table>
</div> </div>
@ -61,12 +61,6 @@
<script> <script>
import { getHttpCategories, getHttpAttributes, getSnmpAttributes } from '../../api/discovery' import { getHttpCategories, getHttpAttributes, getSnmpAttributes } from '../../api/discovery'
const httpMap = {
阿里云: { name: 'aliyun' },
腾讯云: { name: 'tencentcloud' },
华为云: { name: 'huaweicloud' },
AWS: { name: 'aws' },
}
export default { export default {
name: 'HttpSnmpAD', name: 'HttpSnmpAD',
props: { props: {
@ -107,13 +101,21 @@ export default {
const { ruleType, ruleName } = this const { ruleType, ruleName } = this
return { ruleType, ruleName } return { ruleType, ruleName }
}, },
httpMap() {
return {
[this.$t('cmdb.components.aliyun')]: { name: 'aliyun' },
[this.$t('cmdb.components.tencentcloud')]: { name: 'tencentcloud' },
[this.$t('cmdb.components.huaweicloud')]: { name: 'huaweicloud' },
AWS: { name: 'aws' },
}
},
}, },
watch: { watch: {
currentCate: { currentCate: {
immediate: true, immediate: true,
handler(newVal) { handler(newVal) {
if (newVal) { if (newVal) {
getHttpAttributes(httpMap[`${this.ruleName}`].name, { category: newVal }).then((res) => { getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { category: newVal }).then((res) => {
if (this.isEdit) { if (this.isEdit) {
this.formatTableData(res) this.formatTableData(res)
} else { } else {
@ -139,7 +141,7 @@ export default {
}) })
} }
if (ruleType === 'http' && ruleName) { if (ruleType === 'http' && ruleName) {
getHttpCategories(httpMap[`${this.ruleName}`].name).then((res) => { getHttpCategories(this.httpMap[`${this.ruleName}`].name).then((res) => {
this.categories = res this.categories = res
if (res && res.length) { if (res && res.length) {
this.currentCate = res[0] this.currentCate = res[0]

View File

@ -1,199 +1,199 @@
<template> <template>
<div class="notice-content"> <div class="notice-content">
<div class="notice-content-main"> <div class="notice-content-main">
<Toolbar <Toolbar
:editor="editor" :editor="editor"
:defaultConfig="{ :defaultConfig="{
excludeKeys: [ excludeKeys: [
'emotion', 'emotion',
'group-image', 'group-image',
'group-video', 'group-video',
'insertTable', 'insertTable',
'codeBlock', 'codeBlock',
'blockquote', 'blockquote',
'fullScreen', 'fullScreen',
], ],
}" }"
mode="default" mode="default"
/> />
<Editor class="notice-content-editor" :defaultConfig="editorConfig" mode="simple" @onCreated="onCreated" /> <Editor class="notice-content-editor" :defaultConfig="editorConfig" mode="simple" @onCreated="onCreated" />
<div class="notice-content-sidebar"> <div class="notice-content-sidebar">
<template v-if="needOld"> <template v-if="needOld">
<div class="notice-content-sidebar-divider">变更前</div> <div class="notice-content-sidebar-divider">{{ $t('cmdb.components.beforeChange') }}</div>
<div <div
@dblclick="dblclickSidebar(`old_${attr.name}`, attr.alias || attr.name)" @dblclick="dblclickSidebar(`old_${attr.name}`, attr.alias || attr.name)"
class="notice-content-sidebar-item" class="notice-content-sidebar-item"
v-for="attr in attrList" v-for="attr in attrList"
:key="`old_${attr.id}`" :key="`old_${attr.id}`"
:title="attr.alias || attr.name" :title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</div> </div>
<div class="notice-content-sidebar-divider">变更后</div> <div class="notice-content-sidebar-divider">{{ $t('cmdb.components.afterChange') }}</div>
</template> </template>
<div <div
@dblclick="dblclickSidebar(attr.name, attr.alias || attr.name)" @dblclick="dblclickSidebar(attr.name, attr.alias || attr.name)"
class="notice-content-sidebar-item" class="notice-content-sidebar-item"
v-for="attr in attrList" v-for="attr in attrList"
:key="attr.id" :key="attr.id"
:title="attr.alias || attr.name" :title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import '@wangeditor/editor/dist/css/style.css' import '@wangeditor/editor/dist/css/style.css'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default { export default {
name: 'NoticeContent', name: 'NoticeContent',
components: { Editor, Toolbar }, components: { Editor, Toolbar },
props: { props: {
attrList: { attrList: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
needOld: { needOld: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
data() { data() {
return { return {
editor: null, editor: null,
editorConfig: { placeholder: '请输入通知内容', readOnly: this.readOnly }, editorConfig: { placeholder: this.$t('cmdb.components.noticeContentTips'), readOnly: this.readOnly },
content: '', content: '',
defaultParams: [], defaultParams: [],
value2LabelMap: {}, value2LabelMap: {},
} }
}, },
beforeDestroy() { beforeDestroy() {
const editor = this.editor const editor = this.editor
if (editor == null) return if (editor == null) return
editor.destroy() // 组件销毁时及时销毁编辑器 editor.destroy() // When the component is destroyed, destroy the editor in time
}, },
methods: { methods: {
onCreated(editor) { onCreated(editor) {
this.editor = Object.seal(editor) // 一定要用 Object.seal() 否则会报错 this.editor = Object.seal(editor) // Be sure to use Object.seal(), otherwise an error will be reported
}, },
getContent() { getContent() {
const html = _.cloneDeep(this.editor.getHtml()) const html = _.cloneDeep(this.editor.getHtml())
const _html = html.replace( const _html = html.replace(
/<span data-w-e-type="attachment" (data-w-e-is-void|data-w-e-is-void="") (data-w-e-is-inline|data-w-e-is-inline="").*?<\/span>/gm, /<span data-w-e-type="attachment" (data-w-e-is-void|data-w-e-is-void="") (data-w-e-is-inline|data-w-e-is-inline="").*?<\/span>/gm,
(value) => { (value) => {
const _match = value.match(/(?<=data-attachment(V|v)alue=").*?(?=")/) const _match = value.match(/(?<=data-attachment(V|v)alue=").*?(?=")/)
return `{{${_match[0]}}}` return `{{${_match[0]}}}`
} }
) )
return { body_html: html, body: _html } return { body_html: html, body: _html }
}, },
setContent(html) { setContent(html) {
this.editor.setHtml(html) this.editor.setHtml(html)
}, },
dblclickSidebar(value, label) { dblclickSidebar(value, label) {
if (!this.readOnly) { if (!this.readOnly) {
this.editor.restoreSelection() this.editor.restoreSelection()
const node = { const node = {
type: 'attachment', type: 'attachment',
attachmentValue: value, attachmentValue: value,
attachmentLabel: `${label}`, attachmentLabel: `${label}`,
children: [{ text: '' }], children: [{ text: '' }],
} }
this.editor.insertNode(node) this.editor.insertNode(node)
} }
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less'; @import '~@/style/static.less';
.notice-content { .notice-content {
width: 100%; width: 100%;
& &-main { & &-main {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
position: relative; position: relative;
.notice-content-editor { .notice-content-editor {
height: 300px; height: 300px;
width: 75%; width: 75%;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
border-top: none; border-top: none;
overflow: hidden; overflow: hidden;
} }
.notice-content-sidebar { .notice-content-sidebar {
width: 25%; width: 25%;
position: absolute; position: absolute;
height: 300px; height: 300px;
bottom: 0; bottom: 0;
left: 0; left: 0;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
border-top: none; border-top: none;
border-right: none; border-right: none;
overflow: auto; overflow: auto;
.notice-content-sidebar-divider { .notice-content-sidebar-divider {
position: sticky; position: sticky;
top: 0; top: 0;
margin: 0; margin: 0;
font-size: 12px; font-size: 12px;
color: #afafaf; color: #afafaf;
background-color: #fff; background-color: #fff;
line-height: 20px; line-height: 20px;
padding-left: 12px; padding-left: 12px;
&::before, &::before,
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
border-top: 1px solid #d1d1d1; border-top: 1px solid #d1d1d1;
top: 50%; top: 50%;
transition: translateY(-50%); transition: translateY(-50%);
} }
&::before { &::before {
left: 3px; left: 3px;
width: 5px; width: 5px;
} }
&::after { &::after {
right: 3px; right: 3px;
width: 78px; width: 78px;
} }
} }
.notice-content-sidebar-item:first-child { .notice-content-sidebar-item:first-child {
margin-top: 10px; margin-top: 10px;
} }
.notice-content-sidebar-item { .notice-content-sidebar-item {
line-height: 1.5; line-height: 1.5;
padding: 4px 12px; padding: 4px 12px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
&:hover { &:hover {
background-color: #custom_colors[color_2]; background-color: #custom_colors[color_2];
color: #custom_colors[color_1]; color: #custom_colors[color_1];
} }
} }
} }
} }
} }
</style> </style>
<style lang="less"> <style lang="less">
@import '~@/style/static.less'; @import '~@/style/static.less';
.notice-content { .notice-content {
.w-e-bar { .w-e-bar {
background-color: #custom_colors[color_2]; background-color: #custom_colors[color_2];
} }
.w-e-text-placeholder { .w-e-text-placeholder {
line-height: 1.5; line-height: 1.5;
} }
} }
</style> </style>

View File

@ -11,7 +11,7 @@
@blur="handleInputConfirm" @blur="handleInputConfirm"
@keyup.enter="handleInputConfirm" @keyup.enter="handleInputConfirm"
/> />
<a-button v-else type="primary" size="small" ghost @click="showInput">保存筛选条件</a-button> <a-button v-else type="primary" size="small" ghost @click="showInput">{{ $t('cmdb.components.saveQuery') }}</a-button>
</span> </span>
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)"> <template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
<span <span
@ -26,7 +26,7 @@
<a-tooltip :title="item.name"> <a-tooltip :title="item.name">
<span @click="clickPreferenceSearch(item)">{{ `${item.name.slice(0, 6)}...` }}</span> <span @click="clickPreferenceSearch(item)">{{ `${item.name.slice(0, 6)}...` }}</span>
</a-tooltip> </a-tooltip>
<a-popconfirm title="确认删除?" @confirm="deletePreferenceSearch(item)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="deletePreferenceSearch(item)">
<a-icon type="close" /> <a-icon type="close" />
</a-popconfirm> </a-popconfirm>
</span> </span>
@ -40,7 +40,7 @@
}" }"
> >
<span @click="clickPreferenceSearch(item)">{{ item.name }}</span> <span @click="clickPreferenceSearch(item)">{{ item.name }}</span>
<a-popconfirm title="确认删除?" @confirm="deletePreferenceSearch(item)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="deletePreferenceSearch(item)">
<a-icon type="close" /> <a-icon type="close" />
</a-popconfirm> </a-popconfirm>
</span> </span>
@ -73,7 +73,7 @@
{{ item.name }} {{ item.name }}
</div> </div>
<a-popconfirm <a-popconfirm
title="确认删除?" :title="$t('cmdb.ciType.confirmDelete2')"
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
placement="left" placement="left"
@confirm=" @confirm="

View File

@ -15,7 +15,7 @@
:limit="1" :limit="1"
:limitText="(count) => `+ ${count}`" :limitText="(count) => `+ ${count}`"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="模型" :placeholder="$t('cmdb.ciType.ciType')"
@close="closeCiTypeGroup" @close="closeCiTypeGroup"
@open="openCiTypeGroup" @open="openCiTypeGroup"
@input="inputCiTypeGroup" @input="inputCiTypeGroup"
@ -23,8 +23,8 @@
(node) => { (node) => {
return { return {
id: node.id || -1, id: node.id || -1,
label: node.alias || node.name || '其他', label: node.alias || node.name || $t('other'),
title: node.alias || node.name || '其他', title: node.alias || node.name || $t('other'),
children: node.ci_types, children: node.ci_types,
} }
} }
@ -42,7 +42,7 @@
<a-input <a-input
v-model="fuzzySearch" v-model="fuzzySearch"
:style="{ display: 'inline-block', width: '244px' }" :style="{ display: 'inline-block', width: '244px' }"
placeholder="请查找" :placeholder="$t('cmdb.components.pleaseSearch')"
@pressEnter="emitRefresh" @pressEnter="emitRefresh"
class="ops-input ops-input-radius" class="ops-input ops-input-radius"
> >
@ -54,7 +54,7 @@
/> />
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }"> <a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }">
<template slot="title"> <template slot="title">
1json属性不能搜索<br />2搜索内容包括逗号则需转义 \,<br />3只搜索索引属性非索引属性使用条件过滤 {{ $t('cmdb.components.ciSearchTips') }}
</template> </template>
<a><a-icon type="question-circle"/></a> <a><a-icon type="question-circle"/></a>
</a-tooltip> </a-tooltip>
@ -68,7 +68,7 @@
> >
<div slot="popover_item" class="search-form-bar-filter"> <div slot="popover_item" class="search-form-bar-filter">
<a-icon class="search-form-bar-filter-icon" type="filter" /> <a-icon class="search-form-bar-filter-icon" type="filter" />
条件过滤 {{ $t('cmdb.components.conditionFilter') }}
<a-icon class="search-form-bar-filter-icon" type="down" /> <a-icon class="search-form-bar-filter-icon" type="down" />
</div> </div>
</FilterComp> </FilterComp>
@ -97,8 +97,8 @@
</a-space> </a-space>
</div> </div>
<a-space> <a-space>
<a-button @click="reset" size="small">重置</a-button> <a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
<a-tooltip title="属性说明" v-if="type === 'relationView'"> <a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
<a <a
@click=" @click="
() => { () => {
@ -149,7 +149,7 @@ export default {
}, },
data() { data() {
return { return {
// 高级搜索 展开/关闭 // Advanced Search Expand/Close
advanced: false, advanced: false,
queryParam: {}, queryParam: {},
isFocusExpression: false, isFocusExpression: false,
@ -163,7 +163,7 @@ export default {
computed: { computed: {
placeholder() { placeholder() {
return this.isFocusExpression ? 'q=hostname:*0.0.0.0*' : '表达式' return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr')
}, },
width() { width() {
return '200px' return '200px'

View File

@ -11,13 +11,13 @@
> >
<a-tabs v-model="activeKey"> <a-tabs v-model="activeKey">
<a-tab-pane key="1"> <a-tab-pane key="1">
<span slot="tab"><ops-icon type="cmdb-ci" />资源数据</span> <span slot="tab"><ops-icon type="cmdb-ci" />{{ $t('cmdb.menu.ciTable') }}</span>
<div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }"> <div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }">
<div class="cmdb-subscribe-drawer-container-title"> <div class="cmdb-subscribe-drawer-container-title">
<span>订阅模型{{ ciType.alias || ciType.name }}</span> <span>{{ $t('cmdb.components.subCIType') }}: {{ ciType.alias || ciType.name }}</span>
<span :style="{ fontWeight: 500, color: instanceSubscribed ? 'green' : 'red' }">{{ <span :style="{ fontWeight: 500, color: instanceSubscribed ? 'green' : 'red' }">{{
`${instanceSubscribed ? '' : ''}订阅` `${instanceSubscribed ? $t('cmdb.components.already') : $t('cmdb.components.not')}`
}}</span> }}{{ $t('cmdb.components.sub') }})</span>
</div> </div>
<template> <template>
<AttributesTransfer <AttributesTransfer
@ -31,22 +31,22 @@
:height="windowHeight - 170" :height="windowHeight - 170"
/> />
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="subInstanceSubmit" type="primary">订阅</a-button> <a-button @click="subInstanceSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
</div> </div>
</template> </template>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" force-render> <a-tab-pane key="2" force-render>
<span slot="tab"><ops-icon type="cmdb-tree" />资源层级</span> <span slot="tab"><ops-icon type="cmdb-tree" />{{ $t('cmdb.menu.ciTree') }}</span>
<div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }"> <div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }">
<div class="cmdb-subscribe-drawer-container-title"> <div class="cmdb-subscribe-drawer-container-title">
<span>订阅模型{{ ciType.alias || ciType.name }}</span> <span>{{ $t('cmdb.components.subCIType') }}: {{ ciType.alias || ciType.name }}</span>
<span :style="{ fontWeight: 500, color: treeSubscribed ? 'green' : 'red' }">{{ <span :style="{ fontWeight: 500, color: treeSubscribed ? 'green' : 'red' }">{{
`${treeSubscribed ? '' : ''}订阅` `${treeSubscribed ? $t('cmdb.components.already') : $t('cmdb.components.not')}`
}}</span> }}{{ $t('cmdb.components.sub') }})</span>
</div> </div>
<div class="cmdb-subscribe-drawer-tree-header" :style="{ maxHeight: `${(windowHeight - 170) / 3 - 20}px` }"> <div class="cmdb-subscribe-drawer-tree-header" :style="{ maxHeight: `${(windowHeight - 170) / 3 - 20}px` }">
<span v-if="!treeViews.length">请在下方进行选择</span> <span v-if="!treeViews.length">{{ $t('cmdb.components.selectBelow') }}</span>
<div <div
class="cmdb-subscribe-drawer-tree-header-selected" class="cmdb-subscribe-drawer-tree-header-selected"
:style="{ marginLeft: `${18 * index}px` }" :style="{ marginLeft: `${18 * index}px` }"
@ -70,7 +70,7 @@
</div> </div>
</div> </div>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="subTreeSubmit" type="primary">订阅</a-button> <a-button @click="subTreeSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
</div> </div>
</div> </div>
</a-tab-pane> </a-tab-pane>
@ -179,7 +179,7 @@ export default {
}, },
subTreeSubmit() { subTreeSubmit() {
subscribeTreeView(this.ciType.type_id, this.treeViews).then((res) => { subscribeTreeView(this.ciType.type_id, this.treeViews).then((res) => {
this.$message.success('订阅成功') this.$message.success(this.$t('cmdb.components.subSuccess'))
if (this.treeViews.length > 0) { if (this.treeViews.length > 0) {
this.treeSubscribed = true this.treeSubscribed = true
} else { } else {
@ -194,7 +194,7 @@ export default {
return [item, !!this.fixedList.includes(item)] return [item, !!this.fixedList.includes(item)]
}) })
).then((res) => { ).then((res) => {
this.$message.success('订阅成功') this.$message.success(this.$t('cmdb.components.subSuccess'))
this.resetRoute() this.resetRoute()
if (this.selectedAttrList.length > 0) { if (this.selectedAttrList.length > 0) {
this.instanceSubscribed = true this.instanceSubscribed = true

View File

@ -1,144 +1,144 @@
<template> <template>
<div class="authorization-wrapper"> <div class="authorization-wrapper">
<div class="authorization-header"> <div class="authorization-header">
<a-space> <a-space>
<span>Authorization Type</span> <span>Authorization Type</span>
<a-select size="small" v-model="authorizationType" style="width: 200px" :showSearch="true"> <a-select size="small" v-model="authorizationType" style="width: 200px" :showSearch="true">
<a-select-option value="none"> <a-select-option value="none">
None None
</a-select-option> </a-select-option>
<a-select-option value="BasicAuth"> <a-select-option value="BasicAuth">
Basic Auth Basic Auth
</a-select-option> </a-select-option>
<a-select-option value="Bearer"> <a-select-option value="Bearer">
Bearer Bearer
</a-select-option> </a-select-option>
<a-select-option value="APIKey"> <a-select-option value="APIKey">
APIKey APIKey
</a-select-option> </a-select-option>
<a-select-option value="OAuth2.0"> <a-select-option value="OAuth2.0">
OAuth2.0 OAuth2.0
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-space> </a-space>
</div> </div>
<div style="margin-top:10px"> <div style="margin-top:10px">
<table v-if="authorizationType === 'BasicAuth'"> <table v-if="authorizationType === 'BasicAuth'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="BasicAuth.username" placeholder="用户名" /></td> <td><a-input class="authorization-input" v-model="BasicAuth.username" :placeholder="$t('cmdb.ciType.username')" /></td>
</tr> </tr>
<tr> <tr>
<td><a-input class="authorization-input" v-model="BasicAuth.password" placeholder="密码" /></td> <td><a-input class="authorization-input" v-model="BasicAuth.password" :placeholder="$t('cmdb.ciType.password')" /></td>
</tr> </tr>
</table> </table>
<table v-else-if="authorizationType === 'Bearer'"> <table v-else-if="authorizationType === 'Bearer'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="Bearer.token" placeholder="token" /></td> <td><a-input class="authorization-input" v-model="Bearer.token" placeholder="token" /></td>
</tr> </tr>
</table> </table>
<table v-else-if="authorizationType === 'APIKey'"> <table v-else-if="authorizationType === 'APIKey'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="APIKey.key" placeholder="key" /></td> <td><a-input class="authorization-input" v-model="APIKey.key" placeholder="key" /></td>
</tr> </tr>
<tr> <tr>
<td><a-input class="authorization-input" v-model="APIKey.value" placeholder="value" /></td> <td><a-input class="authorization-input" v-model="APIKey.value" placeholder="value" /></td>
</tr> </tr>
</table> </table>
<table v-else-if="authorizationType === 'OAuth2.0'"> <table v-else-if="authorizationType === 'OAuth2.0'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="OAuth2.client_id" placeholder="client_id" /></td> <td><a-input class="authorization-input" v-model="OAuth2.client_id" placeholder="client_id" /></td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input class="authorization-input" v-model="OAuth2.client_secret" placeholder="client_secret" /> <a-input class="authorization-input" v-model="OAuth2.client_secret" placeholder="client_secret" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input <a-input
class="authorization-input" class="authorization-input"
v-model="OAuth2.authorization_base_url" v-model="OAuth2.authorization_base_url"
placeholder="authorization_base_url" placeholder="authorization_base_url"
/> />
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input class="authorization-input" v-model="OAuth2.token_url" placeholder="token_url" /> <a-input class="authorization-input" v-model="OAuth2.token_url" placeholder="token_url" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a-input class="authorization-input" v-model="OAuth2.redirect_url" placeholder="redirect_url" /></td> <td><a-input class="authorization-input" v-model="OAuth2.redirect_url" placeholder="redirect_url" /></td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input class="authorization-input" v-model="OAuth2.scope" placeholder="scope" /> <a-input class="authorization-input" v-model="OAuth2.scope" placeholder="scope" />
</td> </td>
</tr> </tr>
</table> </table>
<a-empty <a-empty
v-else v-else
:image-style="{ :image-style="{
height: '60px', height: '60px',
}" }"
> >
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> 暂无请求认证 </span> <span slot="description"> {{ $t('cmdb.components.noAuthRequest') }} </span>
</a-empty> </a-empty>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Authorization', name: 'Authorization',
data() { data() {
return { return {
authorizationType: 'none', authorizationType: 'none',
BasicAuth: { BasicAuth: {
username: '', username: '',
password: '', password: '',
}, },
Bearer: { Bearer: {
token: '', token: '',
}, },
APIKey: { APIKey: {
key: '', key: '',
value: '', value: '',
}, },
OAuth2: { OAuth2: {
client_id: '', client_id: '',
client_secret: '', client_secret: '',
authorization_base_url: '', authorization_base_url: '',
token_url: '', token_url: '',
redirect_url: '', redirect_url: '',
scope: '', scope: '',
}, },
} }
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.authorization-wrapper { .authorization-wrapper {
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
table, table,
td, td,
th { th {
border: 1px solid #f3f4f6; border: 1px solid #f3f4f6;
} }
.authorization-input { .authorization-input {
border: none; border: none;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }
} }
} }
</style> </style>

View File

@ -1,101 +1,101 @@
<template> <template>
<div> <div>
<div class="headers-header"> <div class="headers-header">
<span>请求参数</span> <span>{{ $t('cmdb.components.requestParam') }}</span>
<a-space> <a-space>
<a-tooltip title="清空"> <a-tooltip :title="$t('cmdb.components.clear')">
<ops-icon <ops-icon
type="icon-xianxing-delete" type="icon-xianxing-delete"
@click=" @click="
() => { () => {
headers = [ headers = [
{ {
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}, },
] ]
} }
" "
/> />
</a-tooltip> </a-tooltip>
<a-tooltip title="新增"> <a-tooltip :title="$t('new')">
<a-icon type="plus" @click="add" /> <a-icon type="plus" @click="add" />
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</div> </div>
<div class="headers-box"> <div class="headers-box">
<table> <table>
<tr v-for="(item, index) in headers" :key="item.id"> <tr v-for="(item, index) in headers" :key="item.id">
<td><a-input class="headers-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td> <td><a-input class="headers-input" v-model="item.key" :placeholder="$t('cmdb.components.param', { param: `${index + 1}` })" /></td>
<td><a-input class="headers-input" v-model="item.value" :placeholder="`${index + 1}`" /></td> <td><a-input class="headers-input" v-model="item.value" :placeholder="$t('cmdb.components.value', { value: `${index + 1}` })" /></td>
<td> <td>
<a style="color:red"> <a style="color:red">
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" /> <ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
</a> </a>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
export default { export default {
name: 'Header', name: 'Header',
data() { data() {
return { return {
headers: [ headers: [
{ {
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}, },
], ],
} }
}, },
methods: { methods: {
uuidv4, uuidv4,
add() { add() {
this.headers.push({ this.headers.push({
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}) })
}, },
deleteParam(index) { deleteParam(index) {
this.headers.splice(index, 1) this.headers.splice(index, 1)
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.headers-header { .headers-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
i { i {
cursor: pointer; cursor: pointer;
} }
} }
.headers-box { .headers-box {
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
table, table,
td, td,
th { th {
border: 1px solid #f3f4f6; border: 1px solid #f3f4f6;
} }
.headers-input { .headers-input {
border: none; border: none;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }
} }
} }
</style> </style>

View File

@ -1,147 +1,147 @@
<template> <template>
<div> <div>
<a-input-group compact> <a-input-group compact>
<treeselect <treeselect
:disable-branch-nodes="true" :disable-branch-nodes="true"
class="custom-treeselect custom-treeselect-bgcAndBorder" class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{ :style="{
'--custom-height': '30px', '--custom-height': '30px',
lineHeight: '30px', lineHeight: '30px',
'--custom-bg-color': '#fff', '--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9', '--custom-border': '1px solid #d9d9d9',
display: 'inline-block', display: 'inline-block',
width: '100px', width: '100px',
}" }"
v-model="method" v-model="method"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="methodList" :options="methodList"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="请选择方式" :placeholder="$t('cmdb.components.selectMethods')"
> >
</treeselect> </treeselect>
<a-input :style="{ display: 'inline-block', width: 'calc(100% - 100px)' }" v-model="url" /> <a-input :style="{ display: 'inline-block', width: 'calc(100% - 100px)' }" v-model="url" />
</a-input-group> </a-input-group>
<a-tabs> <a-tabs>
<a-tab-pane key="Parameters" tab="Parameters"> <a-tab-pane key="Parameters" tab="Parameters">
<Parameters ref="Parameters" /> <Parameters ref="Parameters" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="Body" tab="Body" force-render> <a-tab-pane key="Body" tab="Body" force-render>
<Body ref="Body" /> <Body ref="Body" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="Headers" tab="Headers" force-render> <a-tab-pane key="Headers" tab="Headers" force-render>
<Header ref="Header" /> <Header ref="Header" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="Authorization" tab="Authorization" force-render> <a-tab-pane key="Authorization" tab="Authorization" force-render>
<Authorization ref="Authorization" /> <Authorization ref="Authorization" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</div> </div>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import Parameters from './paramaters.vue' import Parameters from './paramaters.vue'
import Body from './body.vue' import Body from './body.vue'
import Header from './header.vue' import Header from './header.vue'
import Authorization from './authorization.vue' import Authorization from './authorization.vue'
export default { export default {
name: 'Webhook', name: 'Webhook',
components: { Parameters, Body, Header, Authorization }, components: { Parameters, Body, Header, Authorization },
data() { data() {
const methodList = [ const methodList = [
{ {
id: 'GET', id: 'GET',
label: 'GET', label: 'GET',
}, },
{ {
id: 'POST', id: 'POST',
label: 'POST', label: 'POST',
}, },
{ {
id: 'PUT', id: 'PUT',
label: 'PUT', label: 'PUT',
}, },
{ {
id: 'DELETE', id: 'DELETE',
label: 'DELETE', label: 'DELETE',
}, },
] ]
return { return {
methodList, methodList,
method: 'GET', method: 'GET',
url: '', url: '',
} }
}, },
methods: { methods: {
getParams() { getParams() {
const parameters = {} const parameters = {}
this.$refs.Parameters.parameters.forEach((item) => { this.$refs.Parameters.parameters.forEach((item) => {
parameters[item.key] = item.value parameters[item.key] = item.value
}) })
let body = this.$refs.Body.jsonData let body = this.$refs.Body.jsonData
try { try {
JSON.parse(body) JSON.parse(body)
body = JSON.parse(body) body = JSON.parse(body)
} catch {} } catch {}
const headers = {} const headers = {}
this.$refs.Header.headers.forEach((item) => { this.$refs.Header.headers.forEach((item) => {
headers[item.key] = item.value headers[item.key] = item.value
}) })
let authorization = {} let authorization = {}
const type = this.$refs.Authorization.authorizationType const type = this.$refs.Authorization.authorizationType
if (type !== 'none') { if (type !== 'none') {
if (type === 'OAuth2.0') { if (type === 'OAuth2.0') {
authorization = { ...this.$refs.Authorization['OAuth2'], type } authorization = { ...this.$refs.Authorization['OAuth2'], type }
} else { } else {
authorization = { ...this.$refs.Authorization[type], type } authorization = { ...this.$refs.Authorization[type], type }
} }
} }
const { method, url } = this const { method, url } = this
return { method, url, parameters, body, headers, authorization } return { method, url, parameters, body, headers, authorization }
}, },
setParams(params) { setParams(params) {
const { method, url, parameters, body, headers, authorization = {} } = params ?? {} const { method, url, parameters, body, headers, authorization = {} } = params ?? {}
this.method = method this.method = method
this.url = url this.url = url
this.$refs.Parameters.parameters = this.$refs.Parameters.parameters =
Object.keys(parameters).map((key) => { Object.keys(parameters).map((key) => {
return { return {
id: uuidv4(), id: uuidv4(),
key: key, key: key,
value: parameters[key], value: parameters[key],
} }
}) || [] }) || []
if (body && Object.prototype.toString.call(body) === '[object Object]') { if (body && Object.prototype.toString.call(body) === '[object Object]') {
this.$refs.Body.jsonData = JSON.stringify(body) this.$refs.Body.jsonData = JSON.stringify(body)
} else { } else {
this.$refs.Body.jsonData = body this.$refs.Body.jsonData = body
} }
this.$refs.Header.headers = this.$refs.Header.headers =
Object.keys(headers).map((key) => { Object.keys(headers).map((key) => {
return { return {
id: uuidv4(), id: uuidv4(),
key: key, key: key,
value: headers[key], value: headers[key],
} }
}) || [] }) || []
const { type = 'none' } = authorization const { type = 'none' } = authorization
console.log(type) console.log(type)
this.$refs.Authorization.authorizationType = type this.$refs.Authorization.authorizationType = type
if (type !== 'none') { if (type !== 'none') {
const _authorization = _.cloneDeep(authorization) const _authorization = _.cloneDeep(authorization)
delete _authorization.type delete _authorization.type
if (type === 'OAuth2.0') { if (type === 'OAuth2.0') {
this.$refs.Authorization.OAuth2 = _authorization this.$refs.Authorization.OAuth2 = _authorization
} else { } else {
this.$refs.Authorization[type] = _authorization this.$refs.Authorization[type] = _authorization
} }
} }
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -1,100 +1,100 @@
<template> <template>
<div> <div>
<div class="parameters-header"> <div class="parameters-header">
<span>请求参数</span> <span>{{ $t('cmdb.components.requestParam') }}</span>
<a-space> <a-space>
<a-tooltip title="清空"> <a-tooltip :title="$t('cmdb.components.clear')">
<ops-icon <ops-icon
type="icon-xianxing-delete" type="icon-xianxing-delete"
@click=" @click="
() => { () => {
parameters = [] parameters = []
} }
" "
/> />
</a-tooltip> </a-tooltip>
<a-tooltip title="新增"> <a-tooltip :title="$t('new')">
<a-icon type="plus" @click="add" /> <a-icon type="plus" @click="add" />
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</div> </div>
<div class="parameters-box" v-if="parameters && parameters.length"> <div class="parameters-box" v-if="parameters && parameters.length">
<table> <table>
<tr v-for="(item, index) in parameters" :key="item.id"> <tr v-for="(item, index) in parameters" :key="item.id">
<td><a-input class="parameters-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td> <td><a-input class="parameters-input" v-model="item.key" :placeholder="$t('cmdb.components.param', { param: `${index + 1}` })" /></td>
<td><a-input class="parameters-input" v-model="item.value" :placeholder="`${index + 1}`" /></td> <td><a-input class="parameters-input" v-model="item.value" :placeholder="$t('cmdb.components.value', { value: `${index + 1}` })" /></td>
<td> <td>
<a style="color:red"> <a style="color:red">
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" /> <ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
</a> </a>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<a-empty <a-empty
v-else v-else
:image-style="{ :image-style="{
height: '60px', height: '60px',
}" }"
> >
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> 暂无请求参数 </span> <span slot="description"> {{ $t('cmdb.components.noParamRequest') }} </span>
<a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary"> <a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary">
添加 {{ $t('add') }}
</a-button> </a-button>
</a-empty> </a-empty>
</div> </div>
</template> </template>
<script> <script>
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
export default { export default {
name: 'Parameters', name: 'Parameters',
data() { data() {
return { return {
parameters: [], parameters: [],
} }
}, },
methods: { methods: {
add() { add() {
this.parameters.push({ this.parameters.push({
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}) })
}, },
deleteParam(index) { deleteParam(index) {
this.parameters.splice(index, 1) this.parameters.splice(index, 1)
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.parameters-header { .parameters-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
i { i {
cursor: pointer; cursor: pointer;
} }
} }
.parameters-box { .parameters-box {
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
table, table,
td, td,
th { th {
border: 1px solid #f3f4f6; border: 1px solid #f3f4f6;
} }
.parameters-input { .parameters-input {
border: none; border: none;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }
} }
} }
</style> </style>

View File

@ -0,0 +1,483 @@
const cmdb_en = {
relation: 'Relation',
attribute: 'Attributes',
menu: {
views: 'Views',
config: 'Configuration',
backend: 'Management',
ciTable: 'Resource Views',
ciTree: 'Tree Views',
ciSearch: 'Search',
adCIs: 'AutoDiscovery Pool',
preference: 'Preference',
batchUpload: 'Batch Import',
citypeManage: 'Modeling',
backendManage: 'Backend',
customDashboard: 'Custom Dashboard',
serviceTreeDefine: 'Service Tree',
citypeRelation: 'CIType Relation',
operationHistory: 'Operation Audit',
relationType: 'Relation Type',
ad: 'AutoDiscovery',
},
ciType: {
ciType: 'CIType',
attributes: 'Attributes',
relation: 'Relation',
trigger: 'Triggers',
attributeAD: 'Attributes AutoDiscovery',
relationAD: 'Relation AutoDiscovery',
grant: 'Grant',
addGroup: 'New Group',
editGroup: 'Edit Group',
group: 'Group',
attributeLibray: 'Attribute Library',
addCITypeInGroup: 'Add a new CIType to the group',
addCIType: 'Add CIType',
editGroupName: 'Edit group name',
deleteGroup: 'Delete this group',
CITypeName: 'Name(English)',
English: 'English',
inputAttributeName: 'Please enter the attribute name',
attributeNameTips: 'It cannot start with a number, it can be English numbers and underscores (_)',
editCIType: 'Edit CIType',
defaultSort: 'Default sort',
selectDefaultOrderAttr: 'Select default sorting attributes',
asec: 'Forward order',
desc: 'Reverse order',
uniqueKey: 'Uniquely Identifies',
uniqueKeySelect: 'Please select a unique identifier',
notfound: 'Can\'t find what you want?',
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
confirmDeleteCIType: 'Are you sure you want to delete model [{typeName}]?',
uploading: 'Uploading',
uploadFailed: 'Upload failed, please try again later',
addPlugin: 'New plugin',
deletePlugin: 'Delete plugin',
confirmDeleteADT: 'Do you confirm to delete [{pluginName}]',
attributeMap: 'Attribute mapping',
autoDiscovery: 'AutoDiscovery',
node: 'Node',
adExecConfig: 'Execute configuration',
adExecTarget: 'Execute targets',
oneagentIdTips: 'Please enter the hexadecimal OneAgent ID starting with 0x',
selectFromCMDBTips: 'Select from CMDB ',
adAutoInLib: 'Save as CI auto',
adInterval: 'Collection frequency',
byInterval: 'by interval',
allNodes: 'All nodes',
specifyNodes: 'Specify Node',
specifyNodesTips: 'Please fill in the specify node!',
username: 'Username',
password: 'Password',
link: 'Link',
list: 'List',
listTips: 'The value of the field is one or more, and the type of the value returned by the interface is list.',
computeForAllCITips: 'All CI trigger computes',
confirmcomputeForAllCITips: 'Confirm triggering computes for all CIs?',
isUnique: 'Is it unique',
unique: 'Unique',
isChoice: 'Choiced',
defaultShow: 'Default Display',
defaultShowTips: 'The CI instance table displays this field by default',
isSortable: 'Sortable',
isIndex: 'Indexed',
index: 'Index',
indexTips: 'Fields can be used for retrieval to speed up queries',
confirmDelete: 'Confirm to delete [{name}]?',
confirmDelete2: 'Confirm to delete?',
computeSuccess: 'Triggered successfully!',
basicConfig: 'Basic Settings',
AttributeName: 'Name(English)',
DataType: 'Data Type',
defaultValue: 'Default value',
autoIncID: 'Auto-increment ID',
customTime: 'Custom time',
advancedSettings: 'Advanced Settings',
font: 'Font',
color: 'Color',
choiceValue: 'Predefined value',
computedAttribute: 'Computed Attribute',
computedAttributeTips: 'The value of this attribute is calculated through an expression constructed from other attributes of the CIType or by executing a piece of code. The reference method of the attribute is: {{ attribute name }}',
addAttribute: 'New attribute',
existedAttributes: 'Already have attributes',
editAttribute: 'Edit attribute',
addAttributeTips1: 'If sorting is selected, it must also be selected!',
uniqueConstraint: 'Unique Constraint',
up: 'Move up',
down: 'Move down',
selectAttribute: 'Select Attribute',
groupExisted: 'Group name already exists',
attributeSortedTips: 'Attributes in other groups cannot be sorted. If you need to sort, please drag them to a custom group first!',
buildinAttribute: 'built-in attributes',
expr: 'Expression',
code: 'Code',
apply: 'apply',
continueAdd: 'Keep adding',
filter: 'Filter',
choiceOther: 'Other CIType Attributes',
choiceWebhookTips: 'The returned results are filtered by fields, and the hierarchical nesting is separated by ##, such as k1##k2. The web request returns {k1: [{k2: 1}, {k2: 2}]}, and the parsing result is [1, 2 ]',
selectCIType: 'Please select a CMDB CIType',
selectCITypeAttributes: 'Please select CIType attributes',
selectAttributes: 'Please select attributes',
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n Execution entry, returns predefined value\n :return: Returns a list, the type of the value is the same as the type of the attribute\n For example:\n return ["online", "offline"]\n """\n return []',
valueExisted: 'The current value already exists!',
addRelation: 'Add Relation',
sourceCIType: 'Source CIType',
sourceCITypeTips: 'Please select Source CIType',
dstCIType: 'Target CIType',
dstCITypeTips: 'Please select target CIType',
relationType: 'Relation Type',
relationTypeTips: 'Please select relation type',
isParent: 'is parent',
relationConstraint: 'Constraints',
relationConstraintTips: 'please select a relationship constraint',
one2Many: 'One to Many',
one2One: 'One to One',
many2Many: 'Many to Many',
basicInfo: 'Basic Information',
nameInputTips: 'Please enter name',
triggerDataChange: 'Data changes',
triggerDate: 'Date attribute',
triggerEnable: 'Turn on',
descInput: 'Please enter remarks',
triggerCondition: 'Triggering conditions',
addInstance: 'Add new instance',
deleteInstance: 'Delete instance',
changeInstance: 'Instance changes',
selectMutipleAttributes: 'Please select attributes (multiple selections)',
selectSingleAttribute: 'Please select an attribute (single choice)',
beforeDays: 'ahead of time',
days: 'Days',
notifyAt: 'Send time',
notify: 'Notify',
triggerAction: 'Trigger action',
receivers: 'Recipients',
emailTips: 'Please enter your email address, separate multiple email addresses with ;',
customEmail: 'Custom recipients',
notifySubject: 'Notification title',
notifySubjectTips: 'Please enter notification title',
notifyContent: 'Content',
notifyMethod: 'Notify methods',
botSelect: 'Please select a robot',
refAttributeTips: 'The title and content can reference the attribute value of the CIType. The reference method is: {{ attr_name }}',
webhookRefAttributeTips: 'Request parameters can reference the attribute value of the model. The reference method is: {{ attr_name }}',
newTrigger: 'Add trigger',
editTriggerTitle: 'Edit trigger {name}',
newTriggerTitle: 'Add trigger {name}',
confirmDeleteTrigger: 'Are you sure to delete this trigger?',
int: 'Integer',
float: 'Float',
text: 'Text',
datetime: 'DateTime',
date: 'Date',
time: 'Time',
json: 'JSON',
event: 'Event'
},
components: {
unselectAttributes: 'Unselected',
selectAttributes: 'Selected',
downloadCI: 'Export data',
filename: 'Filename',
filenameInputTips: 'Please enter filename',
saveType: 'Save type',
saveTypeTips: 'Please select save type',
xlsx: 'Excel workbook (*.xlsx)',
csv: 'CSV (comma separated) (*.csv)',
html: 'Web page (*.html)',
xml: 'XML data (*.xml)',
txt: 'Text file (tab delimited) (*.txt)',
grantUser: 'Grant User/Department',
grantRole: 'Grant Role',
confirmRevoke: 'Confirm to delete the [Authorization] permission of [{name}]?',
readAttribute: 'View Attributes',
readCI: 'View CIs',
config: 'Configuration',
ciTypeGrant: 'Grant CIType',
ciGrant: 'Grant CI',
attributeGrant: 'Grant Attribute',
relationGrant: 'Grant Relation',
perm: 'Permissions',
all: 'All',
customize: 'Customize',
none: 'None',
customizeFilterName: 'Please enter a custom filter name',
colorPickerError: 'Initialization color format error, use #fff or rgb format',
example: 'Example value',
aliyun: 'aliyun',
tencentcloud: 'Tencent Cloud',
huaweicloud: 'Huawei Cloud',
beforeChange: 'Before change',
afterChange: 'After change',
noticeContentTips: 'Please enter notification content',
saveQuery: 'Save Filters',
pleaseSearch: 'Please search',
conditionFilter: 'Conditional filtering',
attributeDesc: 'Attribute Description',
ciSearchTips: '1. JSON attributes cannot be searched<br />2. If the search content includes commas, they need to be escaped,<br />3. Only index attributes are searched, non-index attributes use conditional filtering',
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
subCIType: 'Subscription CIType',
already: 'already',
not: 'not',
sub: 'subscription',
selectBelow: 'Please select below',
subSuccess: 'Subscription successful',
selectMethods: 'Please select a method',
noAuthRequest: 'No certification requested yet',
noParamRequest: 'No parameter certification yet',
requestParam: 'Request parameters',
param: 'Parameter{param}',
value: 'Value{value}',
clear: 'Clear',
},
batch: {
downloadFailed: 'Download failed',
unselectCIType: 'No CIType selected yet',
pleaseUploadFile: 'Please upload files',
batchUploadCanceled: 'Batch upload canceled',
selectCITypeTips: 'Please select CIType',
downloadTemplate: 'Download Template',
drawTips: 'Click or drag files here to upload!',
supportFileTypes: 'Supported file types: xls, xlsx',
uploadResult: 'Upload results',
total: 'total',
successItems: 'items, succeeded',
failedItems: 'items, failed',
items: 'items',
errorTips: 'Error message',
requestFailedTips: 'An error occurred with the request, please try again later',
requestSuccessTips: 'Upload completed',
},
preference: {
mySub: 'My Subscription',
sub: 'Subscribe',
cancelSub: 'Unsubscribe',
editSub: 'Edit subscription',
peopleSub: ' people subscribed',
noSub: 'No subscribed',
cancelSubSuccess: 'Unsubscribe successfully',
confirmcancelSub: 'Are you sure to cancel your subscription?',
confirmcancelSub2: 'Are you sure you want to unsubscribe {name}?',
of: 'of',
hoursAgo: 'hours ago',
daysAgo: 'days ago',
monthsAgo: 'month ago',
yearsAgo: 'years ago',
just: 'just now',
},
custom_dashboard: {
charts: 'Chart',
newChart: 'Add Chart',
editChart: 'Edit Chart',
title: 'Title',
titleTips: 'Please enter a chart title',
calcIndicators: 'Counter',
dimensions: 'Dimensions',
selectDimensions: 'Please select a dimension',
quantity: 'Quantity',
childCIType: 'Relational CIType',
level: 'Level',
levelTips: 'Please enter the relationship level',
preview: 'Preview',
showIcon: 'Display icon',
chartType: 'Chart Type',
dataFilter: 'Data Filtering',
format: 'Formats',
fontColor: 'Font Color',
backgroundColor: 'Background',
chartColor: 'Chart Color',
chartLength: 'Length',
barType: 'Bar Type',
stackedBar: 'Stacked Bar',
multipleSeriesBar: 'Multiple Series Bar ',
axis: 'Axis',
direction: 'Direction',
lowerShadow: 'Lower Shadow',
count: 'Indicator',
bar: 'Bar',
line: 'Line',
pie: 'Pie',
table: 'Table',
default: 'default',
relation: 'Relation',
noCustomDashboard: 'The administrator has not customized the dashboard yet',
},
preference_relation: {
newServiceTree: 'Add ServiceTree',
serviceTreeName: 'Name',
public: 'Public',
saveLayout: 'Save Layout',
childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!',
tips1: 'Cannot form a view with the currently selected node, please select again!',
tips2: 'Please enter the new serviceTree name!',
tips3: 'Please select at least two nodes!',
},
history: {
ciChange: 'CI',
relationChange: 'Relation',
ciTypeChange: 'CIType',
triggerHistory: 'Triggers',
opreateTime: 'Operate Time',
user: 'User',
userTips: 'Enter filter username',
filter: 'Search',
filterOperate: 'fitler operation',
attribute: 'Attribute',
old: 'Old',
new: 'New',
noUpdate: 'No update',
itemsPerPage: '/page',
triggerName: 'Name',
event: 'Event',
action: 'Actoin',
status: 'Status',
done: 'Done',
undone: 'Undone',
triggerTime: 'Trigger Time',
totalItems: '{total} records in total',
pleaseSelect: 'Please select',
startTime: 'Start Time',
endTime: 'End Time',
deleteCIType: 'Delete CIType',
addCIType: 'Add CIType',
updateCIType: 'Update CIType',
addAttribute: 'Add Attribute',
updateAttribute: 'Update Attribute',
deleteAttribute: 'Delete Attribute',
addTrigger: 'Add Trigger',
updateTrigger: 'Update Trigger',
deleteTrigger: 'Delete Trigger',
addUniqueConstraint: 'Add Unique Constraint',
updateUniqueConstraint: 'Update Unique Constraint',
deleteUniqueConstraint: 'Delete Unique Constraint',
addRelation: 'Add Relation',
deleteRelation: 'Delete Relation',
noModifications: 'No Modifications',
attr: 'attribute',
attrId: 'attribute id',
changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}'
},
relation_type: {
addRelationType: 'New',
nameTips: 'Please enter a type name',
},
ad: {
upload: 'Import',
download: 'Export',
accpet: 'Accept',
accpetBy: 'Accept By',
acceptTime: 'Accept Time',
confirmAccept: 'Confirm Accept?',
accpetSuccess: 'Accept successfully',
isAccpet: 'Is accept',
deleteADC: 'Confirm to delete this data?',
batchDelete: 'Confirm to delete this data?',
agent: 'Built-in & Plug-ins',
snmp: 'Network Devices',
http: 'Public Clouds',
rule: 'AutoDiscovery Rules',
timeout: 'Timeout error',
mode: 'Mode',
collectSettings: 'Collection Settings',
updateFields: 'Update Field',
pluginScript: `# -*- coding:utf-8 -*-
import json
class AutoDiscovery(object):
@property
def unique_key(self):
"""
:return: Returns the name of a unique attribute
"""
return
@staticmethod
def attributes():
"""
Define attribute fields
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
type: String Integer Float Date DateTime Time JSON
For example:
return [
("ci_type", "String", "CIType name"),
("private_ip", "String", "Internal IP, multiple values separated by commas")
]
"""
return []
@staticmethod
def run():
"""
Execution entry, returns collected attribute values
:return:
Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value
For example:
return [dict(ci_type="server", private_ip="192.168.1.1")]
"""
return []
if __name__ == "__main__":
result = AutoDiscovery().run()
if isinstance(result, list):
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
else:
print("ERROR: The collection return must be a list")
`,
server: 'Server',
vserver: 'VServer',
nic: 'NIC',
disk: 'harddisk',
},
ci: {
attributeDesc: 'Attribute Description',
selectRows: 'Select: {rows} items',
addRelation: 'Add Relation',
all: 'All',
batchUpdate: 'Batch Update',
batchUpdateConfirm: 'Are you sure you want to make batch updates?',
batchUpdateInProgress: 'Currently being updated in batches',
batchUpdateInProgress2: 'Updating in batches, {total} in total, {successNum} successful, {errorNum} failed',
batchDeleting: 'Deleting...',
batchDeleting2: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed',
copyFailed: 'Copy failed',
noLevel: 'No hierarchical relationship!',
batchAddRelation: 'Batch Add Relation',
history: 'History',
topo: 'Topology',
table: 'Table',
m2mTips: 'The current CIType relationship is many-to-many, please go to the SerivceTree(relation view) to add or delete',
confirmDeleteRelation: 'Confirm to delete the relationship?',
tips1: 'Use commas to separate multiple values',
tips2: 'The field can be modified as needed. When the value is empty, the field will be left empty.',
tips3: 'Please select the fields that need to be modified',
tips4: 'At least one field must be selected',
tips5: 'Search name | alias',
tips6: 'Speed up retrieval, full-text search possible, no need to use conditional filtering\n\n json currently does not support indexing \n\nText characters longer than 190 cannot be indexed',
tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value',
tips8: 'Multiple values, such as intranet IP',
tips9: 'For front-end only',
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
newUpdateField: 'Add a Attribute',
attributeSettings: 'Attribute Settings',
},
serviceTree: {
deleteNode: 'Delete Node',
tips1: 'For example: q=os_version:centos&sort=os_version',
tips2: 'Expression search',
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
copyFailed: 'Copy failed',
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
},
tree: {
tips1: 'Please go to Preference page first to complete your subscription!',
subSettings: 'Settings',
}
}
export default cmdb_en

View File

@ -0,0 +1,482 @@
const cmdb_zh = {
relation: '关系',
attribute: '属性',
menu: {
views: '视图',
config: '配置',
backend: '管理端',
ciTable: '资源数据',
ciTree: '资源层级',
ciSearch: '资源搜索',
adCIs: '自动发现池',
preference: '我的订阅',
batchUpload: '批量导入',
citypeManage: '模型配置',
backendManage: '后台管理',
customDashboard: '定制仪表盘',
serviceTreeDefine: '服务树定义',
citypeRelation: '模型关系',
operationHistory: '操作审计',
relationType: '关系类型',
ad: '自动发现',
},
ciType: {
ciType: '模型',
attributes: '模型属性',
relation: '模型关联',
trigger: '触发器',
attributeAD: '属性自动发现',
relationAD: '关系自动发现',
grant: '权限配置',
addGroup: '新增分组',
editGroup: '修改分组',
group: '分组',
attributeLibray: '属性库',
addCITypeInGroup: '在该组中新增CI模型',
addCIType: '新增CI模型',
editGroupName: '编辑组名称',
deleteGroup: '删除该组',
CITypeName: '模型名(英文)',
English: '英文',
inputAttributeName: '请输入属性名',
attributeNameTips: '不能以数字开头,可以是英文 数字以及下划线 (_)',
editCIType: '编辑模型',
defaultSort: '默认排序',
selectDefaultOrderAttr: '选择默认排序属性',
asec: '正序',
desc: '倒序',
uniqueKey: '唯一标识',
uniqueKeySelect: '请选择唯一标识',
notfound: '找不到想要的?',
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
confirmDeleteCIType: '确定要删除模型 【{typeName}】 吗?',
uploading: '正在导入中',
uploadFailed: '导入失败,请稍后重试',
addPlugin: '新建plugin',
deletePlugin: '删除plugin',
confirmDeleteADT: '确认删除 【{pluginName}】',
attributeMap: '字段映射',
autoDiscovery: '自动发现',
node: '节点',
adExecConfig: '执行配置',
adExecTarget: '执行机器',
oneagentIdTips: '请输入以0x开头的16进制OneAgent ID',
selectFromCMDBTips: '从CMDB中选择 ',
adAutoInLib: '自动入库',
adInterval: '采集频率',
byInterval: '按间隔',
allNodes: '所有节点',
specifyNodes: '指定节点',
specifyNodesTips: '请填写指定节点!',
username: '用户名',
password: '密码',
link: '链接',
list: '多值',
listTips: '字段的值是1个或者多个接口返回的值的类型是list',
computeForAllCITips: '所有CI触发计算',
confirmcomputeForAllCITips: '确认触发所有CI的计算',
isUnique: '是否唯一',
unique: '唯一',
isChoice: '是否选择',
defaultShow: '默认显示',
defaultShowTips: 'CI实例表格默认展示该字段',
isSortable: '可排序',
isIndex: '是否索引',
index: '索引',
indexTips: '字段可被用于检索,加速查询',
confirmDelete: '确认删除【{name}】?',
confirmDelete2: '确认删除?',
computeSuccess: '触发成功!',
basicConfig: '基础设置',
AttributeName: '属性名(英文)',
DataType: '数据类型',
defaultValue: '默认值',
autoIncID: '自增ID',
customTime: '自定义时间',
advancedSettings: '高级设置',
font: '字体',
color: '颜色',
choiceValue: '预定义值',
computedAttribute: '计算属性',
computedAttributeTips: '该属性的值是通过模型的其它属性构建的表达式或者执行一段代码的方式计算而来,属性的引用方法为: {{ 属性名 }}',
addAttribute: '新增属性',
existedAttributes: '已有属性',
editAttribute: '编辑属性',
addAttributeTips1: '选中排序,则必须也要选中!',
uniqueConstraint: '唯一校验',
up: '上移',
down: '下移',
selectAttribute: '添加属性',
groupExisted: '分组名称已存在',
attributeSortedTips: '其他分组中的属性不能进行排序,如需排序请先拖至自定义的分组!',
buildinAttribute: '内置字段',
expr: '表达式',
code: '代码',
apply: '应用',
continueAdd: '继续添加',
filter: '过滤',
choiceOther: '其他模型属性',
choiceWebhookTips: '返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]',
selectCIType: '请选择CMDB模型',
selectCITypeAttributes: '请选择模型属性',
selectAttributes: '请选择属性',
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n 执行入口, 返回预定义值\n :return: 返回一个列表, 值的类型同属性的类型\n 例如:\n return ["在线", "下线"]\n """\n return []',
valueExisted: '当前值已存在!',
addRelation: '新增关系',
sourceCIType: '源模型',
sourceCITypeTips: '请选择源模型',
dstCIType: '目标模型名',
dstCITypeTips: '请选择目标模型',
relationType: '关联类型',
relationTypeTips: '请选择关联类型',
isParent: '被',
relationConstraint: '关系约束',
relationConstraintTips: '请选择关系约束',
one2Many: '一对多',
one2One: '一对一',
many2Many: '多对多',
basicInfo: '基本信息',
nameInputTips: '请输入名称',
triggerDataChange: '数据变更',
triggerDate: '日期属性',
triggerEnable: '开启',
descInput: '请输入备注',
triggerCondition: '触发条件',
addInstance: '新增实例',
deleteInstance: '删除实例',
changeInstance: '实例变更',
selectMutipleAttributes: '请选择属性(多选)',
selectSingleAttribute: '请选择属性(单选)',
beforeDays: '提前',
days: '天',
notifyAt: '发送时间',
notify: '通知',
triggerAction: '触发动作',
receivers: '收件人',
emailTips: '请输入邮箱,多个邮箱用;分隔',
customEmail: '自定义收件人',
notifySubject: '通知标题',
notifySubjectTips: '请输入通知标题',
notifyContent: '内容',
notifyMethod: '通知方式',
botSelect: '请选择机器人',
refAttributeTips: '标题、内容可以引用该模型的属性值,引用方法为: {{ attr_name }}',
webhookRefAttributeTips: '请求参数可以引用该模型的属性值,引用方法为: {{ attr_name }}',
newTrigger: '新增触发器',
editTriggerTitle: '编辑触发器 {name}',
newTriggerTitle: '新增触发器 {name}',
confirmDeleteTrigger: '确认删除该触发器吗?',
int: '整数',
float: '浮点数',
text: '文本',
datetime: '日期时间',
date: '日期',
time: '时间',
json: 'JSON',
event: '事件'
},
components: {
unselectAttributes: '未选属性',
selectAttributes: '已选属性',
downloadCI: '导出数据',
filename: '文件名',
filenameInputTips: '请输入文件名',
saveType: '保存类型',
saveTypeTips: '请选择保存类型',
xlsx: 'Excel工作簿(*.xlsx)',
csv: 'CSV(逗号分隔)(*.csv)',
html: '网页(*.html)',
xml: 'XML数据(*.xml)',
txt: '文本文件(制表符分隔)(*.txt)',
grantUser: '授权用户/部门',
grantRole: '授权角色',
confirmRevoke: '确认删除 【{name}】 的 【授权】 权限?',
readAttribute: '查看字段',
readCI: '查看实例',
config: '配置',
ciTypeGrant: '模型权限',
ciGrant: '实例权限',
attributeGrant: '字段权限',
relationGrant: '关系权限',
perm: '权限',
all: '全部',
customize: '自定义',
none: '无',
customizeFilterName: '请输入自定义筛选条件名',
colorPickerError: '初始化颜色格式错误,使用#fff或rgb格式',
example: '示例值',
aliyun: '阿里云',
tencentcloud: '腾讯云',
huaweicloud: '华为云',
beforeChange: '变更前',
afterChange: '变更后',
noticeContentTips: '请输入通知内容',
saveQuery: '保存筛选条件',
pleaseSearch: '请查找',
conditionFilter: '条件过滤',
attributeDesc: '属性说明',
ciSearchTips: '1. json属性不能搜索<br />2. 搜索内容包括逗号, 则需转义 ,<br />3. 只搜索索引属性, 非索引属性使用条件过滤',
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
subCIType: '订阅模型',
already: '已',
not: '未',
sub: '订阅',
selectBelow: '请在下方进行选择',
subSuccess: '订阅成功',
selectMethods: '请选择方式',
noAuthRequest: '暂无请求认证',
noParamRequest: '暂无参数认证',
requestParam: '请求参数',
param: '参数{param}',
value: '值{value}',
clear: '清空',
},
batch: {
downloadFailed: '失败下载',
unselectCIType: '尚未选择模板类型',
pleaseUploadFile: '请上传文件',
batchUploadCanceled: '批量上传已取消',
selectCITypeTips: '请选择模板类型',
downloadTemplate: '下载模板',
drawTips: '点击或拖拽文件至此上传!',
supportFileTypes: '支持文件类型xlsxlsx',
uploadResult: '上传结果',
total: '共',
successItems: '条,已成功',
failedItems: '条,失败',
items: '条',
errorTips: '错误信息',
requestFailedTips: '请求出现错误,请稍后再试',
requestSuccessTips: '批量上传已完成',
},
preference: {
mySub: '我的订阅',
sub: '订阅',
cancelSub: '取消订阅',
editSub: '编辑订阅',
peopleSub: '位同事已订阅',
noSub: '暂无同事订阅',
cancelSubSuccess: '取消订阅成功',
confirmcancelSub: '确认取消订阅',
confirmcancelSub2: '确认取消订阅 {name} 吗?',
of: '的',
hoursAgo: '小时前',
daysAgo: '天前',
monthsAgo: '月前',
yearsAgo: '年前',
just: '刚刚',
},
custom_dashboard: {
charts: '图表',
newChart: '新增图表',
editChart: '编辑图表',
title: '标题',
titleTips: '请输入图表标题',
calcIndicators: '计算指标',
dimensions: '维度',
selectDimensions: '请选择维度',
quantity: '数量',
childCIType: '关系模型',
level: '层级',
levelTips: '请输入关系层级',
preview: '预览',
showIcon: '是否显示icon',
chartType: '图表类型',
dataFilter: '数据筛选',
format: '格式',
fontColor: '字体颜色',
backgroundColor: '背景颜色',
chartColor: '图表颜色',
chartLength: '图表长度',
barType: '柱状图类型',
stackedBar: '堆积柱状图',
multipleSeriesBar: '多系列柱状图',
axis: '轴',
direction: '方向',
lowerShadow: '下方阴影',
count: '指标',
bar: '柱状图',
line: '折线图',
pie: '饼状图',
table: '表格',
default: '默认',
relation: '关系',
noCustomDashboard: '管理员暂未定制仪表盘',
},
preference_relation: {
newServiceTree: '新增服务树',
serviceTreeName: '服务树名',
public: '公开',
saveLayout: '保存布局',
childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!',
tips1: '不能与当前选中节点形成视图,请重新选择!',
tips2: '请输入新增服务树名!',
tips3: '请选择至少两个节点!',
},
history: {
ciChange: 'CI变更',
relationChange: '关系变更',
ciTypeChange: '模型变更',
triggerHistory: '触发历史',
opreateTime: '操作时间',
user: '用户',
userTips: '输入筛选用户名',
filter: '筛选',
filterOperate: '筛选操作',
attribute: '属性',
old: '旧',
new: '新',
noUpdate: '没有修改',
itemsPerPage: '/页',
triggerName: '触发器名称',
event: '事件',
action: '动作',
status: '状态',
done: '已完成',
undone: '未完成',
triggerTime: '触发时间',
totalItems: '共 {total} 条记录',
pleaseSelect: '请选择',
startTime: '开始时间',
endTime: '结束时间',
deleteCIType: '删除模型',
addCIType: '新增模型',
updateCIType: '修改模型',
addAttribute: '新增属性',
updateAttribute: '修改属性',
deleteAttribute: '删除属性',
addTrigger: '新增触发器',
updateTrigger: '修改触发器',
deleteTrigger: '删除触发器',
addUniqueConstraint: '新增联合唯一',
updateUniqueConstraint: '修改联合唯一',
deleteUniqueConstraint: '删除联合唯一',
addRelation: '新增关系',
deleteRelation: '删除关系',
noModifications: '没有修改',
attr: '属性名',
attrId: '属性ID',
changeDescription: '属性ID{attr_id},提前:{before_days}天,主题:{subject}\n内容{body}\n通知时间{notify_at}'
},
relation_type: {
addRelationType: '新增关系类型',
nameTips: '请输入类型名',
},
ad: {
upload: '规则导入',
download: '规则导出',
accpet: '入库',
accpetBy: '入库人',
acceptTime: '入库时间',
confirmAccept: '确认入库?',
accpetSuccess: '入库成功',
isAccpet: '是否入库',
deleteADC: '确认删除该条数据?',
batchDelete: '确认删除这些数据?',
agent: '内置 & 插件',
snmp: '网络设备',
http: '公有云资源',
rule: '自动发现规则',
timeout: '超时错误',
mode: '模式',
collectSettings: '采集设置',
updateFields: '更新字段',
pluginScript: `# -*- coding:utf-8 -*-
import json
class AutoDiscovery(object):
@property
def unique_key(self):
"""
:return: 返回唯一属性的名字
"""
return
@staticmethod
def attributes():
"""
定义属性字段
:return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文
类型: String Integer Float Date DateTime Time JSON
例如:
return [
("ci_type", "String", "模型名称"),
("private_ip", "String", "内网IP, 多值逗号分隔")
]
"""
return []
@staticmethod
def run():
"""
执行入口, 返回采集的属性值
:return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值
例如:
return [dict(ci_type="server", private_ip="192.168.1.1")]
"""
return []
if __name__ == "__main__":
result = AutoDiscovery().run()
if isinstance(result, list):
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
else:
print("ERROR: 采集返回必须是列表")
`,
server: '物理机',
vserver: '虚拟机',
nic: '网卡',
disk: '硬盘',
},
ci: {
attributeDesc: '属性说明',
selectRows: '选取:{rows} 项',
addRelation: '添加关系',
all: '全部',
batchUpdate: '批量修改',
batchUpdateConfirm: '确认要批量修改吗?',
batchUpdateInProgress: '正在批量修改',
batchUpdateInProgress2: '正在批量修改,共{total}个,成功{successNum}个,失败{errorNum}个',
batchDeleting: '正在删除...',
batchDeleting2: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
copyFailed: '复制失败!',
noLevel: '无层级关系!',
batchAddRelation: '批量添加关系',
history: '操作历史',
topo: '拓扑',
table: '表格',
m2mTips: '当前模型关系为多对多,请前往关系视图进行增删操作',
confirmDeleteRelation: '确认删除关系?',
tips1: '多个值使用,分割',
tips2: '可根据需要修改字段,当值为 空 时,则该字段 置空',
tips3: '请选择需要修改的字段',
tips4: '必须至少选择一个字段',
tips5: '搜索 名称 | 别名',
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引',
tips7: '表现形式是下拉框, 值必须在预定义值里',
tips8: '多值, 比如内网IP',
tips9: '仅针对前端',
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
newUpdateField: '新增修改字段',
attributeSettings: '字段设置',
},
serviceTree: {
deleteNode: '删除节点',
tips1: '例q=os_version:centos&sort=os_version',
tips2: '表达式搜索',
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
copyFailed: '复制失败',
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
},
tree: {
tips1: '请先到 我的订阅 页面完成订阅!',
subSettings: '订阅设置',
}
}
export default cmdb_zh

View File

@ -13,19 +13,19 @@ const genCmdbRoutes = async () => {
{ {
path: '/cmdb/dashboard', path: '/cmdb/dashboard',
name: 'cmdb_dashboard', name: 'cmdb_dashboard',
meta: { title: '仪表盘', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard-selected', keepAlive: false }, meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard-selected', keepAlive: false },
component: () => import('../views/dashboard/index_v2.vue') component: () => import('../views/dashboard/index_v2.vue')
}, },
{ {
path: '/cmdb/disabled1', path: '/cmdb/disabled1',
name: 'cmdb_disabled1', name: 'cmdb_disabled1',
meta: { title: '视图', disabled: true }, meta: { title: 'cmdb.menu.views', disabled: true },
}, },
{ {
path: '/cmdb/resourceviews', path: '/cmdb/resourceviews',
name: 'cmdb_resource_views', name: 'cmdb_resource_views',
component: RouteView, component: RouteView,
meta: { title: '资源数据', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource-selected', keepAlive: true }, meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource-selected', keepAlive: true },
hideChildrenInMenu: false, hideChildrenInMenu: false,
children: [] children: []
}, },
@ -33,108 +33,108 @@ const genCmdbRoutes = async () => {
path: '/cmdb/tree_views', path: '/cmdb/tree_views',
component: () => import('../views/tree_views'), component: () => import('../views/tree_views'),
name: 'cmdb_tree_views', name: 'cmdb_tree_views',
meta: { title: '资源层级', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree-selected', keepAlive: false }, meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree-selected', keepAlive: false },
hideChildrenInMenu: true, hideChildrenInMenu: true,
children: [ children: [
{ {
path: '/cmdb/tree_views/:typeId', path: '/cmdb/tree_views/:typeId',
name: 'cmdb_tree_views_item', name: 'cmdb_tree_views_item',
component: () => import('../views/tree_views'), component: () => import('../views/tree_views'),
meta: { title: '资源层级', keepAlive: false }, meta: { title: 'cmdb.menu.ciTree', keepAlive: false },
hidden: true hidden: true
}] }]
}, },
{ {
path: '/cmdb/resourcesearch', path: '/cmdb/resourcesearch',
name: 'cmdb_resource_search', name: 'cmdb_resource_search',
meta: { title: '资源搜索', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search-selected', keepAlive: false }, meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search-selected', keepAlive: false },
component: () => import('../views/resource_search/index.vue') component: () => import('../views/resource_search/index.vue')
}, },
{ {
path: '/cmdb/adc', path: '/cmdb/adc',
name: 'cmdb_auto_discovery_ci', name: 'cmdb_auto_discovery_ci',
meta: { title: '自动发现池', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false }, meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
component: () => import('../views/discoveryCI/index.vue') component: () => import('../views/discoveryCI/index.vue')
}, },
{ {
path: '/cmdb/disabled2', path: '/cmdb/disabled2',
name: 'cmdb_disabled2', name: 'cmdb_disabled2',
meta: { title: '配置', disabled: true, }, meta: { title: 'cmdb.menu.config', disabled: true, },
}, },
{ {
path: '/cmdb/preference', path: '/cmdb/preference',
component: () => import('../views/preference/index'), component: () => import('../views/preference/index'),
name: 'cmdb_preference', name: 'cmdb_preference',
meta: { title: '我的订阅', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference-selected', keepAlive: false } meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference-selected', keepAlive: false }
}, },
{ {
path: '/cmdb/batch', path: '/cmdb/batch',
component: () => import('../views/batch'), component: () => import('../views/batch'),
name: 'cmdb_batch', name: 'cmdb_batch',
meta: { 'title': '批量导入', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch-selected', keepAlive: false } meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch-selected', keepAlive: false }
}, },
{ {
path: '/cmdb/ci_types', path: '/cmdb/ci_types',
name: 'ci_type', name: 'ci_type',
component: () => import('../views/ci_types/index'), component: () => import('../views/ci_types/index'),
meta: { title: '模型配置', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] } meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
}, },
{ {
path: '/cmdb/disabled3', path: '/cmdb/disabled3',
name: 'cmdb_disabled3', name: 'cmdb_disabled3',
meta: { title: '管理端', disabled: true, permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], }, meta: { title: 'cmdb.menu.backend', disabled: true, permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], },
}, },
{ {
path: '/cmdb/citypes', path: '/cmdb/citypes',
name: 'cmdb_ci_type', name: 'cmdb_ci_type',
component: RouteView, component: RouteView,
redirect: '/cmdb/ci_type', redirect: '/cmdb/ci_type',
meta: { title: '后台管理', icon: 'setting', permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], }, meta: { title: 'cmdb.menu.backendManage', icon: 'setting', permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], },
children: [ children: [
{ {
path: '/cmdb/customdashboard', path: '/cmdb/customdashboard',
name: 'cmdb_custom_dashboard', name: 'cmdb_custom_dashboard',
component: () => import('../views/custom_dashboard/index'), component: () => import('../views/custom_dashboard/index'),
meta: { title: '定制仪表盘', keepAlive: false, icon: 'ops-cmdb-customdashboard', selectedIcon: 'ops-cmdb-customdashboard-selected' } meta: { title: 'cmdb.menu.customDashboard', keepAlive: false, icon: 'ops-cmdb-customdashboard', selectedIcon: 'ops-cmdb-customdashboard-selected' }
}, },
{ {
path: '/cmdb/preferencerelation', path: '/cmdb/preferencerelation',
name: 'preference_relation', name: 'preference_relation',
component: () => import('../views/preference_relation/index'), component: () => import('../views/preference_relation/index'),
meta: { title: '业务关系定义', keepAlive: false, icon: 'ops-cmdb-preferencerelation', selectedIcon: 'ops-cmdb-preferencerelation-selected' } meta: { title: 'cmdb.menu.serviceTreeDefine', keepAlive: false, icon: 'ops-cmdb-preferencerelation', selectedIcon: 'ops-cmdb-preferencerelation-selected' }
}, },
{ {
path: '/cmdb/modelrelation', path: '/cmdb/modelrelation',
name: 'model_relation', name: 'model_relation',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/model_relation/index'), component: () => import('../views/model_relation/index'),
meta: { title: '模型关系', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' } meta: { title: 'cmdb.menu.citypeRelation', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' }
}, },
{ {
path: '/cmdb/operationhistory', path: '/cmdb/operationhistory',
name: 'operation_history', name: 'operation_history',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/operation_history/index'), component: () => import('../views/operation_history/index'),
meta: { title: '操作审计', keepAlive: false, icon: 'ops-cmdb-operation', selectedIcon: 'ops-cmdb-operation-selected' } meta: { title: 'cmdb.menu.operationHistory', keepAlive: false, icon: 'ops-cmdb-operation', selectedIcon: 'ops-cmdb-operation-selected' }
}, },
{ {
path: '/cmdb/relationtype', path: '/cmdb/relationtype',
name: 'relation_type', name: 'relation_type',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/relation_type/index'), component: () => import('../views/relation_type/index'),
meta: { title: '关系类型', keepAlive: false, icon: 'ops-cmdb-relationtype', selectedIcon: 'ops-cmdb-relationtype-selected' } meta: { title: 'cmdb.menu.relationType', keepAlive: false, icon: 'ops-cmdb-relationtype', selectedIcon: 'ops-cmdb-relationtype-selected' }
}, },
{ {
path: '/cmdb/discovery', path: '/cmdb/discovery',
name: 'discovery', name: 'discovery',
component: () => import('../views/discovery/index'), component: () => import('../views/discovery/index'),
meta: { title: '自动发现', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' } meta: { title: 'cmdb.menu.ad', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' }
}, },
] ]
} }
] ]
} }
// 动态添加订阅的条目及业务关系 // Dynamically add subscription items and business relationships
const [preference, relation] = await Promise.all([getPreference(), getRelationView()]) const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
preference.forEach(item => { preference.forEach(item => {
@ -143,7 +143,7 @@ const genCmdbRoutes = async () => {
component: () => import(`../views/ci/index`), component: () => import(`../views/ci/index`),
name: `cmdb_${item.id}`, name: `cmdb_${item.id}`,
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon }, meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
// hideChildrenInMenu: true // 强制显示 MenuItem 而不是 SubMenu // hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
}) })
}) })
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined

View File

@ -1,28 +1,32 @@
export const valueTypeMap = { import i18n from '@/lang'
'0': '整数',
'1': '浮点数', export const valueTypeMap = () => {
'2': '文本', return {
'3': '日期时间', '0': i18n.t('cmdb.ciType.int'),
'4': '日期', '1': i18n.t('cmdb.ciType.float'),
'5': '时间', '2': i18n.t('cmdb.ciType.text'),
'6': 'JSON', '3': i18n.t('cmdb.ciType.datetime'),
'7': '密码', '4': i18n.t('cmdb.ciType.date'),
'8': '链接' '5': i18n.t('cmdb.ciType.time'),
} '6': 'JSON',
'7': i18n.t('cmdb.ciType.password'),
export const defautValueColor = [ '8': i18n.t('cmdb.ciType.link')
{ value: '#d9d9d9' }, }
{ value: '#ffccc7' }, }
{ value: '#ffd8bf' },
{ value: '#ffe7ba' }, export const defautValueColor = [
{ value: '#fff1b8' }, { value: '#d9d9d9' },
{ value: '#f4ffb8' }, { value: '#ffccc7' },
{ value: '#d9f7be' }, { value: '#ffd8bf' },
{ value: '#b5f5ec' }, { value: '#ffe7ba' },
{ value: '#bae7ff' }, { value: '#fff1b8' },
{ value: '#d6e4ff' }, { value: '#f4ffb8' },
{ value: '#efdbff' }, { value: '#d9f7be' },
{ value: '#ffd6e7' }, { value: '#b5f5ec' },
] { value: '#bae7ff' },
{ value: '#d6e4ff' },
export const defaultBGColors = ['#ffccc7', '#ffd8bf', '#ffe7ba', '#fff1b8', '#d9f7be', '#b5f5ec', '#bae7ff', '#d6e4ff', '#efdbff', '#ffd6e7'] { value: '#efdbff' },
{ value: '#ffd6e7' },
]
export const defaultBGColors = ['#ffccc7', '#ffd8bf', '#ffe7ba', '#fff1b8', '#d9f7be', '#b5f5ec', '#bae7ff', '#d6e4ff', '#efdbff', '#ffd6e7']

View File

@ -16,9 +16,9 @@
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable> <CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
<div class="cmdb-batch-upload-action"> <div class="cmdb-batch-upload-action">
<a-space size="large"> <a-space size="large">
<a-button type="primary" ghost @click="handleCancel">取消</a-button> <a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button>
<a-button @click="handleUpload" type="primary">上传</a-button> <a-button @click="handleUpload" type="primary">{{ $t('upload') }}</a-button>
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">失败下载</a-button> <a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{ $t('cmdb.batch.downloadFailed') }}</a-button>
</a-space> </a-space>
</div> </div>
</a-col> </a-col>
@ -109,7 +109,7 @@ export default {
}, },
handleUpload() { handleUpload() {
if (!this.ciType) { if (!this.ciType) {
this.$message.error('尚未选择模板类型') this.$message.error(this.$t('cmdb.batch.unselectCIType'))
return return
} }
if (this.uploadData && this.uploadData.length > 0) { if (this.uploadData && this.uploadData.length > 0) {
@ -118,7 +118,7 @@ export default {
this.$refs.uploadResult.upload2Server() this.$refs.uploadResult.upload2Server()
}) })
} else { } else {
this.$message.error('请上传文件') this.$message.error(this.$t('cmdb.batch.pleaseUploadFile'))
} }
}, },
handleCancel() { handleCancel() {
@ -127,7 +127,7 @@ export default {
this.$refs.ciTypeChoice.selectNum = null this.$refs.ciTypeChoice.selectNum = null
this.hasError = false this.hasError = false
} else { } else {
this.$message.warning('批量上传已取消') this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled'))
this.isUploading = false this.isUploading = false
} }
}, },

View File

@ -1,9 +1,9 @@
<template> <template>
<a-space> <a-space>
<span>模板类型</span> <span>{{ $t('cmdb.ciType.ciType') }}: </span>
<a-select <a-select
showSearch showSearch
placeholder="请选择模板类型" :placeholder="$t('cmdb.batch.selectCITypeTips')"
@change="selectCiType" @change="selectCiType"
:style="{ width: '300px' }" :style="{ width: '300px' }"
class="ops-select" class="ops-select"
@ -20,7 +20,7 @@
type="primary" type="primary"
class="ops-button-primary" class="ops-button-primary"
icon="download" icon="download"
>下载模板</a-button >{{ $t('cmdb.batch.downloadTemplate') }}</a-button
> >
<a-modal <a-modal
:bodyStyle="{ paddingTop: 0 }" :bodyStyle="{ paddingTop: 0 }"
@ -31,14 +31,14 @@
@ok="handleOk" @ok="handleOk"
wrapClassName="ci-type-choice-modal" wrapClassName="ci-type-choice-modal"
> >
<a-divider orientation="left">模型属性</a-divider> <a-divider orientation="left">{{ $t('cmdb.ciType.attributes') }}</a-divider>
<a-checkbox <a-checkbox
@change="changeCheckAll" @change="changeCheckAll"
:style="{ marginBottom: '20px' }" :style="{ marginBottom: '20px' }"
:indeterminate="indeterminate" :indeterminate="indeterminate"
:checked="checkAll" :checked="checkAll"
> >
全选 {{ $t('checkAll') }}
</a-checkbox> </a-checkbox>
<br /> <br />
<a-checkbox-group style="width:100%" v-model="checkedAttrs"> <a-checkbox-group style="width:100%" v-model="checkedAttrs">
@ -52,7 +52,7 @@
</a-row> </a-row>
</a-checkbox-group> </a-checkbox-group>
<template v-if="parentsType && parentsType.length"> <template v-if="parentsType && parentsType.length">
<a-divider orientation="left">模型关联</a-divider> <a-divider orientation="left">{{ $t('cmdb.ciType.relation') }}</a-divider>
<a-row :gutter="[24, 24]" align="top" type="flex"> <a-row :gutter="[24, 24]" align="top" type="flex">
<a-col :style="{ display: 'inline-flex' }" :span="12" v-for="item in parentsType" :key="item.id"> <a-col :style="{ display: 'inline-flex' }" :span="12" v-for="item in parentsType" :key="item.id">
<a-checkbox @click="clickParent(item)" :checked="checkedParents.includes(item.alias || item.name)"> <a-checkbox @click="clickParent(item)" :checked="checkedParents.includes(item.alias || item.name)">
@ -150,7 +150,7 @@ export default {
}, },
methods: { methods: {
selectCiType(el) { selectCiType(el) {
// 当选择好模板类型时的回调函数 // Callback function when a template type is selected
getCITypeAttributesById(el).then((res) => { getCITypeAttributesById(el).then((res) => {
this.$emit('getCiTypeAttr', res) this.$emit('getCiTypeAttr', res)
this.selectCiTypeAttrList = res this.selectCiTypeAttrList = res

View File

@ -10,8 +10,8 @@
:disabled="!ciType || isUploading" :disabled="!ciType || isUploading"
> >
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" /> <img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
<p class="ant-upload-text">点击或拖拽文件至此上传</p> <p class="ant-upload-text">{{ $t('cmdb.batch.drawTips') }}</p>
<p class="ant-upload-hint">支持文件类型xlsxlsx</p> <p class="ant-upload-hint">{{ $t('cmdb.batch.supportFileTypes') }}</p>
</a-upload-dragger> </a-upload-dragger>
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file"> <div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span> <span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="cmdb-batch-upload-result" v-if="visible"> <div class="cmdb-batch-upload-result" v-if="visible">
<h3 class="cmdb-batch-upload-result-title">上传结果</h3> <h3 class="cmdb-batch-upload-result-title">{{ $t('cmdb.batch.uploadResult') }}</h3>
<div class="cmdb-batch-upload-result-content"> <div class="cmdb-batch-upload-result-content">
<h4> <h4>
&nbsp;<span style="color: blue">{{ total }}</span> 已成功 {{ $t('cmdb.batch.total') }}&nbsp;<span style="color: blue">{{ total }}</span> {{ $t('cmdb.batch.successItems') }}
<span style="color: lightgreen">{{ success }}</span> 失败 <span style="color: red">{{ errorNum }} </span> <span style="color: lightgreen">{{ success }}</span> {{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }}
</h4> </h4>
<div> <div>
<span>错误信息</span> <span>{{ $t('cmdb.batch.errorTips') }}: </span>
<ol> <ol>
<li :key="item + index" v-for="(item, index) in errorItems">{{ item }}</li> <li :key="item + index" v-for="(item, index) in errorItems">{{ item }}</li>
</ol> </ol>
@ -71,7 +71,7 @@ export default {
if (r.status === 'fulfilled') { if (r.status === 'fulfilled') {
this.success += 1 this.success += 1
} else { } else {
this.errorItems.push(r?.reason?.response?.data.message ?? '请求出现错误,请稍后再试') this.errorItems.push(r?.reason?.response?.data.message ?? this.$t('cmdb.batch.requestFailedTips'))
this.errorNum += 1 this.errorNum += 1
this.$emit('uploadResultError', 6 * i + j) this.$emit('uploadResultError', 6 * i + j)
} }
@ -86,7 +86,7 @@ export default {
} }
if (this.isUploading) { if (this.isUploading) {
this.$emit('uploadResultDone') this.$emit('uploadResultDone')
this.$message.success('批量上传已完成') this.$message.success(this.$t('cmdb.batch.requestSuccessTips'))
} }
}, },
}, },

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
<template> <template>
<a-drawer <a-drawer
title="批量添加关系" :title="$t('cmdb.ci.batchAddRelation')"
width="50%" width="50%"
@close="() => { visible = false; $emit('refresh', true) }" @close="() => { visible = false; $emit('refresh', true) }"
:visible="visible" :visible="visible"

View File

@ -1,342 +1,344 @@
<template> <template>
<CustomDrawer <CustomDrawer
width="80%" width="80%"
placement="left" placement="left"
@close=" @close="
() => { () => {
visible = false visible = false
} }
" "
:visible="visible" :visible="visible"
:hasTitle="false" :hasTitle="false"
:hasFooter="false" :hasFooter="false"
:bodyStyle="{ padding: 0, height: '100vh' }" :bodyStyle="{ padding: 0, height: '100vh' }"
wrapClassName="ci-detail" wrapClassName="ci-detail"
destroyOnClose destroyOnClose
> >
<a-tabs v-model="activeTabKey" @change="changeTab"> <a-tabs v-model="activeTabKey" @change="changeTab">
<a-tab-pane key="tab_1"> <a-tab-pane key="tab_1">
<span slot="tab"><a-icon type="book" />属性</span> <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
<div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr"> <div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr">
<el-descriptions <el-descriptions
:title="group.name || '其他'" :title="group.name || $t('other')"
:key="group.name" :key="group.name"
v-for="group in attributeGroups" v-for="group in attributeGroups"
border border
:column="3" :column="3"
> >
<el-descriptions-item <el-descriptions-item
:label="`${attr.alias || attr.name}`" :label="`${attr.alias || attr.name}`"
:key="attr.name" :key="attr.name"
v-for="attr in group.attributes" v-for="attr in group.attributes"
> >
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" /> <CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_2"> <a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />关系</span> <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
<div :style="{ padding: '24px' }"> <div :style="{ padding: '24px' }">
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> <CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_3"> <a-tab-pane key="tab_3">
<span slot="tab"><a-icon type="clock-circle" />操作历史</span> <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }"> <div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
<vxe-table <vxe-table
ref="xTable" ref="xTable"
:data="ciHistory" :data="ciHistory"
size="small" size="small"
:max-height="`${windowHeight - 94}px`" :max-height="`${windowHeight - 94}px`"
:span-method="mergeRowMethod" :span-method="mergeRowMethod"
border border
:scroll-y="{ enabled: false }" :scroll-y="{ enabled: false }"
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-table-column sortable field="created_at" title="时间"></vxe-table-column> <vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
<vxe-table-column <vxe-table-column
field="username" field="username"
title="用户" :title="$t('user')"
:filters="[]" :filters="[]"
:filter-method="filterUsernameMethod" :filter-method="filterUsernameMethod"
></vxe-table-column> ></vxe-table-column>
<vxe-table-column <vxe-table-column
field="operate_type" field="operate_type"
:filters="[ :filters="[
{ value: 0, label: '新增' }, { value: 0, label: $t('new') },
{ value: 1, label: '删除' }, { value: 1, label: $t('delete') },
{ value: 3, label: '修改' }, { value: 3, label: $t('update') },
]" ]"
:filter-method="filterOperateMethod" :filter-method="filterOperateMethod"
title="操作" :title="$t('operation')"
> >
<template #default="{ row }"> <template #default="{ row }">
{{ operateTypeMap[row.operate_type] }} {{ operateTypeMap[row.operate_type] }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column <vxe-table-column
field="attr_alias" field="attr_alias"
title="属性" :title="$t('cmdb.attribute')"
:filters="[]" :filters="[]"
:filter-method="filterAttrMethod" :filter-method="filterAttrMethod"
></vxe-table-column> ></vxe-table-column>
<vxe-table-column field="old" title=""></vxe-table-column> <vxe-table-column field="old" :title="$t('cmdb.history.old')"></vxe-table-column>
<vxe-table-column field="new" title=""></vxe-table-column> <vxe-table-column field="new" :title="$t('cmdb.history.new')"></vxe-table-column>
</vxe-table> </vxe-table>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_4"> <a-tab-pane key="tab_4">
<span slot="tab"><ops-icon type="itsm_auto_trigger" />触发历史</span> <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }"> <div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
<TriggerTable :ci_id="ci._id" /> <TriggerTable :ci_id="ci._id" />
</div> </div>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { Descriptions, DescriptionsItem } from 'element-ui' import { Descriptions, DescriptionsItem } from 'element-ui'
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
import { getCIHistory } from '@/modules/cmdb/api/history' import { getCIHistory } from '@/modules/cmdb/api/history'
import { getCIById } from '@/modules/cmdb/api/ci' import { getCIById } from '@/modules/cmdb/api/ci'
import CiDetailAttrContent from './ciDetailAttrContent.vue' import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue' import CiDetailRelation from './ciDetailRelation.vue'
import TriggerTable from '../../operation_history/modules/triggerTable.vue' import TriggerTable from '../../operation_history/modules/triggerTable.vue'
export default { export default {
components: { components: {
ElDescriptions: Descriptions, ElDescriptions: Descriptions,
ElDescriptionsItem: DescriptionsItem, ElDescriptionsItem: DescriptionsItem,
CiDetailAttrContent, CiDetailAttrContent,
CiDetailRelation, CiDetailRelation,
TriggerTable, TriggerTable,
}, },
props: { props: {
typeId: { typeId: {
type: Number, type: Number,
required: true, required: true,
}, },
treeViewsLevels: { treeViewsLevels: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}, },
data() { data() {
const operateTypeMap = { return {
0: '新增', visible: false,
1: '删除', ci: {},
2: '修改', attributeGroups: [],
} activeTabKey: 'tab_1',
return { rowSpanMap: {},
operateTypeMap, ciHistory: [],
visible: false, ciId: null,
ci: {}, ci_types: [],
attributeGroups: [], }
activeTabKey: 'tab_1', },
rowSpanMap: {}, computed: {
ciHistory: [], windowHeight() {
ciId: null, return this.$store.state.windowHeight
ci_types: [], },
}
}, operateTypeMap() {
computed: { return {
windowHeight() { 0: this.$t('new'),
return this.$store.state.windowHeight 1: this.$t('delete'),
}, 2: this.$t('update'),
}, }
provide() { },
return { },
ci_types: () => { provide() {
return this.ci_types return {
}, ci_types: () => {
} return this.ci_types
}, },
inject: ['reload', 'handleSearch', 'attrList'], }
methods: { },
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') { inject: ['reload', 'handleSearch', 'attrList'],
this.visible = true methods: {
this.activeTabKey = activeTabKey create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
if (activeTabKey === 'tab_2') { this.visible = true
this.$nextTick(() => { this.activeTabKey = activeTabKey
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey if (activeTabKey === 'tab_2') {
}) this.$nextTick(() => {
} this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
this.ciId = ciId })
this.getAttributes() }
this.getCI() this.ciId = ciId
this.getCIHistory() this.getAttributes()
getCITypes().then((res) => { this.getCI()
this.ci_types = res.ci_types this.getCIHistory()
}) getCITypes().then((res) => {
}, this.ci_types = res.ci_types
getAttributes() { })
getCITypeGroupById(this.typeId, { need_other: 1 }) },
.then((res) => { getAttributes() {
this.attributeGroups = res getCITypeGroupById(this.typeId, { need_other: 1 })
}) .then((res) => {
.catch((e) => {}) this.attributeGroups = res
}, })
getCI() { .catch((e) => {})
getCIById(this.ciId) },
.then((res) => { getCI() {
// this.ci = res.ci getCIById(this.ciId)
this.ci = res.result[0] .then((res) => {
}) // this.ci = res.ci
.catch((e) => {}) this.ci = res.result[0]
}, })
.catch((e) => {})
getCIHistory() { },
getCIHistory(this.ciId)
.then((res) => { getCIHistory() {
this.ciHistory = res getCIHistory(this.ciId)
.then((res) => {
const rowSpanMap = {} this.ciHistory = res
let startIndex = 0
let startCount = 1 const rowSpanMap = {}
res.forEach((item, index) => { let startIndex = 0
if (index === 0) { let startCount = 1
return res.forEach((item, index) => {
} if (index === 0) {
if (res[index].record_id === res[startIndex].record_id) { return
startCount += 1 }
rowSpanMap[index] = 0 if (res[index].record_id === res[startIndex].record_id) {
if (index === res.length - 1) { startCount += 1
rowSpanMap[startIndex] = startCount rowSpanMap[index] = 0
} if (index === res.length - 1) {
} else { rowSpanMap[startIndex] = startCount
rowSpanMap[startIndex] = startCount }
startIndex = index } else {
startCount = 1 rowSpanMap[startIndex] = startCount
if (index === res.length - 1) { startIndex = index
rowSpanMap[index] = 1 startCount = 1
} if (index === res.length - 1) {
} rowSpanMap[index] = 1
}) }
this.rowSpanMap = rowSpanMap }
}) })
.catch((e) => { this.rowSpanMap = rowSpanMap
console.log(e) })
}) .catch((e) => {
}, console.log(e)
changeTab(key) { })
this.activeTabKey = key },
if (key === 'tab_3') { changeTab(key) {
this.$nextTick(() => { this.activeTabKey = key
const $table = this.$refs.xTable if (key === 'tab_3') {
if ($table) { this.$nextTick(() => {
const usernameColumn = $table.getColumnByField('username') const $table = this.$refs.xTable
const attrColumn = $table.getColumnByField('attr_alias') if ($table) {
if (usernameColumn) { const usernameColumn = $table.getColumnByField('username')
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))] const attrColumn = $table.getColumnByField('attr_alias')
$table.setFilter( if (usernameColumn) {
usernameColumn, const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
usernameList.map((item) => { $table.setFilter(
return { usernameColumn,
value: item, usernameList.map((item) => {
label: item, return {
} value: item,
}) label: item,
) }
} })
if (attrColumn) { )
$table.setFilter( }
attrColumn, if (attrColumn) {
this.attrList().map((attr) => { $table.setFilter(
return { value: attr.alias || attr.name, label: attr.alias || attr.name } attrColumn,
}) this.attrList().map((attr) => {
) return { value: attr.alias || attr.name, label: attr.alias || attr.name }
} })
} )
}) }
} }
}, })
filterUsernameMethod({ value, row, column }) { }
return row.username === value },
}, filterUsernameMethod({ value, row, column }) {
filterOperateMethod({ value, row, column }) { return row.username === value
return Number(row.operate_type) === Number(value) },
}, filterOperateMethod({ value, row, column }) {
filterAttrMethod({ value, row, column }) { return Number(row.operate_type) === Number(value)
return row.attr_alias === value },
}, filterAttrMethod({ value, row, column }) {
refresh(editAttrName) { return row.attr_alias === value
this.getCI() },
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) refresh(editAttrName) {
// 修改的字段为树形视图订阅的字段 则全部reload this.getCI()
setTimeout(() => { const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
if (_find) { // 修改的字段为树形视图订阅的字段 则全部reload
this.reload() setTimeout(() => {
} else { if (_find) {
this.handleSearch() this.reload()
} } else {
}, 500) this.handleSearch()
}, }
mergeRowMethod({ row, _rowIndex, column, visibleData }) { }, 500)
const fields = ['created_at', 'username'] },
const cellValue1 = row['created_at'] mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const cellValue2 = row['username'] const fields = ['created_at', 'username']
if (cellValue1 && cellValue2 && fields.includes(column.property)) { const cellValue1 = row['created_at']
const prevRow = visibleData[_rowIndex - 1] const cellValue2 = row['username']
let nextRow = visibleData[_rowIndex + 1] if (cellValue1 && cellValue2 && fields.includes(column.property)) {
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) { const prevRow = visibleData[_rowIndex - 1]
return { rowspan: 0, colspan: 0 } let nextRow = visibleData[_rowIndex + 1]
} else { if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
let countRowspan = 1 return { rowspan: 0, colspan: 0 }
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) { } else {
nextRow = visibleData[++countRowspan + _rowIndex] let countRowspan = 1
} while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
if (countRowspan > 1) { nextRow = visibleData[++countRowspan + _rowIndex]
return { rowspan: countRowspan, colspan: 1 } }
} if (countRowspan > 1) {
} return { rowspan: countRowspan, colspan: 1 }
} }
}, }
updateCIByself(params, editAttrName) { }
const _ci = { ..._.cloneDeep(this.ci), ...params } },
this.ci = _ci updateCIByself(params, editAttrName) {
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) const _ci = { ..._.cloneDeep(this.ci), ...params }
// 修改的字段为树形视图订阅的字段 则全部reload this.ci = _ci
setTimeout(() => { const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
if (_find) { // 修改的字段为树形视图订阅的字段 则全部reload
this.reload() setTimeout(() => {
} else { if (_find) {
this.handleSearch() this.reload()
} } else {
}, 500) this.handleSearch()
}, }
}, }, 500)
} },
</script> },
}
<style lang="less" scoped></style> </script>
<style lang="less">
.ci-detail { <style lang="less" scoped></style>
.ant-tabs-bar { <style lang="less">
margin: 0; .ci-detail {
} .ant-tabs-bar {
.ci-detail-attr { margin: 0;
.el-descriptions-item__content { }
cursor: default; .ci-detail-attr {
&:hover a { .el-descriptions-item__content {
opacity: 1 !important; cursor: default;
} &:hover a {
} opacity: 1 !important;
.el-descriptions:first-child > .el-descriptions__header { }
margin-top: 0; }
} .el-descriptions:first-child > .el-descriptions__header {
.el-descriptions__header { margin-top: 0;
margin-bottom: 5px; }
margin-top: 20px; .el-descriptions__header {
} margin-bottom: 5px;
.ant-form-item { margin-top: 20px;
margin-bottom: 0; }
} .ant-form-item {
.ant-form-item-control { margin-bottom: 0;
line-height: 19px; }
} .ant-form-item-control {
} line-height: 19px;
} }
</style> }
}
</style>

View File

@ -1,386 +1,388 @@
<template> <template>
<CustomDrawer <CustomDrawer
:title="title + CIType.alias" :title="title + CIType.alias"
width="800" width="800"
@close="handleClose" @close="handleClose"
:maskClosable="false" :maskClosable="false"
:visible="visible" :visible="visible"
wrapClassName="create-instance-form" wrapClassName="create-instance-form"
:bodyStyle="{ paddingTop: 0 }" :bodyStyle="{ paddingTop: 0 }"
:headerStyle="{ borderBottom: 'none' }" :headerStyle="{ borderBottom: 'none' }"
> >
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleClose">取消</a-button> <a-button @click="handleClose">{{ $t('cancel') }}</a-button>
<a-button type="primary" @click="createInstance">提交</a-button> <a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button>
</div> </div>
<template v-if="action === 'create'"> <template v-if="action === 'create'">
<template v-for="group in attributesByGroup"> <template v-for="group in attributesByGroup">
<CreateInstanceFormByGroup <CreateInstanceFormByGroup
:ref="`createInstanceFormByGroup_${group.id}`" :ref="`createInstanceFormByGroup_${group.id}`"
:key="group.id || group.name" :key="group.id || group.name"
:group="group" :group="group"
@handleFocusInput="handleFocusInput" @handleFocusInput="handleFocusInput"
:attributeList="attributeList" :attributeList="attributeList"
/> />
</template> </template>
<template v-if="parentsType && parentsType.length"> <template v-if="parentsType && parentsType.length">
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">模型关系</a-divider> <a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ $t('cmdb.menu.citypeRelation') }}</a-divider>
<a-form> <a-form>
<a-row :gutter="24" align="top" type="flex"> <a-row :gutter="24" align="top" type="flex">
<a-col :span="12" v-for="item in parentsType" :key="item.id"> <a-col :span="12" v-for="item in parentsType" :key="item.id">
<a-form-item :label="item.alias || item.name" :colon="false"> <a-form-item :label="item.alias || item.name" :colon="false">
<a-input-group compact style="width: 100%"> <a-input-group compact style="width: 100%">
<a-select v-model="parentsForm[item.name].attr"> <a-select v-model="parentsForm[item.name].attr">
<a-select-option <a-select-option
:title="attr.alias || attr.name" :title="attr.alias || attr.name"
v-for="attr in item.attributes" v-for="attr in item.attributes"
:key="attr.name" :key="attr.name"
:value="attr.name" :value="attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input placeholder="多个值使用,分割" v-model="parentsForm[item.name].value" style="width: 50%" /> <a-input :placeholder="$t('cmdb.ci.tips1')" v-model="parentsForm[item.name].value" style="width: 50%" />
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</a-form> </a-form>
</template> </template>
</template> </template>
<template v-if="action === 'update'"> <template v-if="action === 'update'">
<a-form :form="form"> <a-form :form="form">
<p>可根据需要修改字段当值为<strong></strong>则该字段<strong>置空</strong></p> <p>{{ $t('cmdb.ci.tips2') }}</p>
<a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name"> <a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name">
<a-col :span="11"> <a-col :span="11">
<a-form-item> <a-form-item>
<el-select showSearch size="small" filterable v-model="list.name" placeholder="请选择需要修改的字段"> <el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')">
<el-option <el-option
v-for="attr in attributeList" v-for="attr in attributeList"
:key="attr.name" :key="attr.name"
:value="attr.name" :value="attr.name"
:disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1" :disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1"
:label="attr.alias || attr.name" :label="attr.alias || attr.name"
> >
</el-option> </el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="11"> <a-col :span="11">
<a-form-item> <a-form-item>
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="getFieldType(list.name).split('%%')[0] === 'select'" v-if="getFieldType(list.name).split('%%')[0] === 'select'"
:mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'" :mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'"
showSearch showSearch
allowClear allowClear
> >
<a-select-option <a-select-option
:value="choice[0]" :value="choice[0]"
:key="'New_' + choice + choice_idx" :key="'New_' + choice + choice_idx"
v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)" v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)"
> >
<span :style="choice[1] ? choice[1].style || {} : {}"> <span :style="choice[1] ? choice[1].style || {} : {}">
<ops-icon <ops-icon
:style="{ color: choice[1].icon.color }" :style="{ color: choice[1].icon.color }"
v-if="choice[1] && choice[1].icon && choice[1].icon.name" v-if="choice[1] && choice[1].icon && choice[1].icon.name"
:type="choice[1].icon.name" :type="choice[1].icon.name"
/> />
{{ choice[0] }} {{ choice[0] }}
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input-number <a-input-number
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
style="width: 100%" style="width: 100%"
v-if="getFieldType(list.name) === 'input_number'" v-if="getFieldType(list.name) === 'input_number'"
/> />
<a-date-picker <a-date-picker
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
style="width: 100%" style="width: 100%"
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'" v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'"
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }" :showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }"
/> />
<a-input <a-input
v-if="getFieldType(list.name) === 'input'" v-if="getFieldType(list.name) === 'input'"
@focus="(e) => handleFocusInput(e, list)" @focus="(e) => handleFocusInput(e, list)"
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="2"> <a-col :span="2">
<a-form-item> <a-form-item>
<a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)"> <a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)">
<a-icon type="delete" /> <a-icon type="delete" />
</a> </a>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-button type="primary" ghost icon="plus" @click="handleAdd">新增修改字段</a-button> <a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button>
</a-form> </a-form>
</template> </template>
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import { Select, Option } from 'element-ui' import { Select, Option } from 'element-ui'
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType' import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
import { addCI } from '@/modules/cmdb/api/ci' import { addCI } from '@/modules/cmdb/api/ci'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import { valueTypeMap } from '../../../utils/const' import { valueTypeMap } from '../../../utils/const'
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue' import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation' import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
export default { export default {
name: 'CreateInstanceForm', name: 'CreateInstanceForm',
components: { components: {
ElSelect: Select, ElSelect: Select,
ElOption: Option, ElOption: Option,
JsonEditor, JsonEditor,
CreateInstanceFormByGroup, CreateInstanceFormByGroup,
}, },
props: { props: {
typeIdFromRelation: { typeIdFromRelation: {
type: Number, type: Number,
default: 0, default: 0,
}, },
}, },
data() { data() {
return { return {
valueTypeMap, action: '',
action: '', form: this.$form.createForm(this),
form: this.$form.createForm(this), visible: false,
visible: false, attributeList: [],
attributeList: [],
CIType: {},
CIType: {},
batchUpdateLists: [],
batchUpdateLists: [], editAttr: null,
editAttr: null, attributesByGroup: [],
attributesByGroup: [], parentsType: [],
parentsType: [], parentsForm: {},
parentsForm: {}, canEdit: {},
canEdit: {}, }
} },
}, computed: {
computed: { title() {
title() { return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' '
return this.action === 'create' ? '创建 ' : '批量修改 ' },
}, typeId() {
typeId() { if (this.typeIdFromRelation) {
if (this.typeIdFromRelation) { return this.typeIdFromRelation
return this.typeIdFromRelation }
} return this.$router.currentRoute.meta.typeId
return this.$router.currentRoute.meta.typeId },
}, valueTypeMap() {
}, return valueTypeMap()
provide() { },
return { },
getFieldType: this.getFieldType, provide() {
} return {
}, getFieldType: this.getFieldType,
inject: ['attrList'], }
methods: { },
moment, inject: ['attrList'],
async getCIType() { methods: {
await getCIType(this.typeId).then((res) => { moment,
this.CIType = res.ci_types[0] async getCIType() {
}) await getCIType(this.typeId).then((res) => {
}, this.CIType = res.ci_types[0]
async getAttributeList() { })
const _attrList = this.attrList() },
this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required) async getAttributeList() {
await getCITypeGroupById(this.typeId).then((res1) => { const _attrList = this.attrList()
const _attributesByGroup = res1.map((g) => { this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required)
g.attributes = g.attributes.filter((attr) => !attr.is_computed) await getCITypeGroupById(this.typeId).then((res1) => {
return g const _attributesByGroup = res1.map((g) => {
}) g.attributes = g.attributes.filter((attr) => !attr.is_computed)
const attrHasGroupIds = [] return g
res1.forEach((g) => { })
const id = g.attributes.map((attr) => attr.id) const attrHasGroupIds = []
attrHasGroupIds.push(...id) res1.forEach((g) => {
}) const id = g.attributes.map((attr) => attr.id)
const otherGroupAttr = this.attributeList.filter( attrHasGroupIds.push(...id)
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed })
) const otherGroupAttr = this.attributeList.filter(
if (otherGroupAttr.length) { (attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed
_attributesByGroup.push({ id: -1, name: '其他', attributes: otherGroupAttr }) )
} if (otherGroupAttr.length) {
this.attributesByGroup = _attributesByGroup _attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
}) }
}, this.attributesByGroup = _attributesByGroup
createInstance() { })
const _this = this },
if (_this.action === 'update') { createInstance() {
this.form.validateFields((err, values) => { const _this = this
if (err) { if (_this.action === 'update') {
return this.form.validateFields((err, values) => {
} if (err) {
Object.keys(values).forEach((k) => { return
const _tempFind = this.attributeList.find((item) => item.name === k) }
if ( Object.keys(values).forEach((k) => {
_tempFind.value_type === '3' && const _tempFind = this.attributeList.find((item) => item.name === k)
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '3' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if ( values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
_tempFind.value_type === '4' && }
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '4' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if (_tempFind.value_type === '6') { values[k] = values[k].format('YYYY-MM-DD')
values[k] = values[k] ? JSON.parse(values[k]) : undefined }
} if (_tempFind.value_type === '6') {
}) values[k] = values[k] ? JSON.parse(values[k]) : undefined
}
_this.$emit('submit', values) })
})
} else { _this.$emit('submit', values)
let values = {} })
for (let i = 0; i < this.attributesByGroup.length; i++) { } else {
const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData() let values = {}
if (data === 'error') { for (let i = 0; i < this.attributesByGroup.length; i++) {
return const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData()
} if (data === 'error') {
values = { ...values, ...data } return
} }
values = { ...values, ...data }
Object.keys(values).forEach((k) => { }
const _tempFind = this.attributeList.find((item) => item.name === k)
if ( Object.keys(values).forEach((k) => {
_tempFind.value_type === '3' && const _tempFind = this.attributeList.find((item) => item.name === k)
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '3' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if ( values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
_tempFind.value_type === '4' && }
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '4' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if (_tempFind.value_type === '6') { values[k] = values[k].format('YYYY-MM-DD')
values[k] = values[k] ? JSON.parse(values[k]) : undefined }
} if (_tempFind.value_type === '6') {
}) values[k] = values[k] ? JSON.parse(values[k]) : undefined
values.ci_type = _this.typeId }
console.log(this.parentsForm) })
Object.keys(this.parentsForm).forEach((type) => { values.ci_type = _this.typeId
if (this.parentsForm[type].value) { console.log(this.parentsForm)
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value Object.keys(this.parentsForm).forEach((type) => {
} if (this.parentsForm[type].value) {
}) values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
addCI(values).then((res) => { }
_this.$message.success('新增成功!') })
_this.visible = false addCI(values).then((res) => {
_this.$emit('reload', { ci_id: res.ci_id }) _this.$message.success(this.$t('addSuccess'))
}) _this.visible = false
} _this.$emit('reload', { ci_id: res.ci_id })
}, })
handleClose() { }
this.visible = false },
}, handleClose() {
handleOpen(visible, action) { this.visible = false
this.visible = visible },
this.action = action handleOpen(visible, action) {
this.$nextTick(() => { this.visible = visible
this.form.resetFields() this.action = action
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => { this.$nextTick(() => {
this.batchUpdateLists = [{ name: this.attributeList[0].name }] this.form.resetFields()
}) Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
if (action === 'create') { this.batchUpdateLists = [{ name: this.attributeList[0].name }]
getCITypeParent(this.typeId).then(async (res) => { })
for (let i = 0; i < res.parents.length; i++) { if (action === 'create') {
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => { getCITypeParent(this.typeId).then(async (res) => {
this.canEdit = { for (let i = 0; i < res.parents.length; i++) {
..._.cloneDeep(this.canEdit), await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
[res.parents[i].id]: p_res.result, this.canEdit = {
} ..._.cloneDeep(this.canEdit),
}) [res.parents[i].id]: p_res.result,
} }
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id]) })
const _parentsForm = {} }
res.parents.forEach((item) => { this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
const _find = item.attributes.find((attr) => attr.id === item.unique_id) const _parentsForm = {}
_parentsForm[item.name] = { attr: _find.name, value: '' } res.parents.forEach((item) => {
}) const _find = item.attributes.find((attr) => attr.id === item.unique_id)
this.parentsForm = _parentsForm _parentsForm[item.name] = { attr: _find.name, value: '' }
}) })
} this.parentsForm = _parentsForm
}) })
}, }
getFieldType(name) { })
const _find = this.attributeList.find((item) => item.name === name) },
if (_find) { getFieldType(name) {
if (_find.is_choice) { const _find = this.attributeList.find((item) => item.name === name)
if (_find.is_list) { if (_find) {
return 'select%%multiple' if (_find.is_choice) {
} if (_find.is_list) {
return 'select' return 'select%%multiple'
} else if (_find.value_type === '0' || _find.value_type === '1') { }
return 'input_number' return 'select'
} else if (_find.value_type === '4' || _find.value_type === '3') { } else if (_find.value_type === '0' || _find.value_type === '1') {
return valueTypeMap[_find.value_type] return 'input_number'
} else { } else if (_find.value_type === '4' || _find.value_type === '3') {
return 'input' return this.valueTypeMap[_find.value_type]
} } else {
} return 'input'
return 'input' }
}, }
getSelectFieldOptions(name) { return 'input'
const _find = this.attributeList.find((item) => item.name === name) },
if (_find) { getSelectFieldOptions(name) {
return _find.choice_value const _find = this.attributeList.find((item) => item.name === name)
} if (_find) {
return [] return _find.choice_value
}, }
handleAdd() { return []
this.batchUpdateLists.push({ name: undefined }) },
}, handleAdd() {
handleDelete(name) { this.batchUpdateLists.push({ name: undefined })
const _idx = this.batchUpdateLists.findIndex((item) => item.name === name) },
if (_idx > -1) { handleDelete(name) {
this.batchUpdateLists.splice(_idx, 1) const _idx = this.batchUpdateLists.findIndex((item) => item.name === name)
} if (_idx > -1) {
}, this.batchUpdateLists.splice(_idx, 1)
handleFocusInput(e, attr) { }
console.log(attr) },
const _tempFind = this.attributeList.find((item) => item.name === attr.name) handleFocusInput(e, attr) {
if (_tempFind.value_type === '6') { console.log(attr)
this.editAttr = attr const _tempFind = this.attributeList.find((item) => item.name === attr.name)
e.srcElement.blur() if (_tempFind.value_type === '6') {
const jsonData = this.form.getFieldValue(attr.name) this.editAttr = attr
this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {}) e.srcElement.blur()
} else { const jsonData = this.form.getFieldValue(attr.name)
this.editAttr = null this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {})
} } else {
}, this.editAttr = null
jsonEditorOk(jsonData) { }
this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) }) },
}, jsonEditorOk(jsonData) {
}, this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) })
} },
</script> },
<style lang="less"> }
.create-instance-form { </script>
.ant-form-item { <style lang="less">
margin-bottom: 5px; .create-instance-form {
} .ant-form-item {
.ant-drawer-body { margin-bottom: 5px;
overflow-y: auto; }
max-height: calc(100vh - 110px); .ant-drawer-body {
} overflow-y: auto;
} max-height: calc(100vh - 110px);
</style> }
}
</style>

View File

@ -1,225 +1,228 @@
<template> <template>
<CustomDrawer <CustomDrawer
:visible="visible" :visible="visible"
:hasFooter="false" :hasFooter="false"
@close=" @close="
() => { () => {
visible = false visible = false
} }
" "
title="属性说明" :title="$t('cmdb.ci.attributeDesc')"
width="72%" width="72%"
:bodyStyle="{ height: '100vh' }" :bodyStyle="{ height: '100vh' }"
> >
<vxe-toolbar> <vxe-toolbar>
<template #buttons> <template #buttons>
<a-input <a-input
v-model="searchKey" v-model="searchKey"
:style="{ display: 'inline-block', width: '244px' }" :style="{ display: 'inline-block', width: '244px' }"
class="ops-input ops-input-radius" class="ops-input ops-input-radius"
type="search" type="search"
placeholder="搜索 名称 | 别名" :placeholder="$t('cmdb.ci.tips5')"
@keyup="searchAttributes" @keyup="searchAttributes"
> >
<a-icon type="search" slot="suffix" /> <a-icon type="search" slot="suffix" />
</a-input> </a-input>
</template> </template>
</vxe-toolbar> </vxe-toolbar>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<vxe-table <vxe-table
resizable resizable
border border
size="mini" size="mini"
:height="windowHeight - 160" :height="windowHeight - 160"
:data="list" :data="list"
:scroll-x="{ enabled: true, gt: 0 }" :scroll-x="{ enabled: true, gt: 0 }"
show-overflow show-overflow
show-header-overflow show-header-overflow
align="center" align="center"
highlight-hover-row highlight-hover-row
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-column <vxe-column
v-for="(column, index) in columns" v-for="(column, index) in columns"
:field="column.field" :field="column.field"
:title="column.title" :title="column.title"
:min-width="column.width" :min-width="column.width"
:align="column.align" :align="column.align"
:key="column.field" :key="column.field"
:fixed="index < 3 ? 'left' : ''" :fixed="index < 3 ? 'left' : ''"
:sortable="index < 3 ? true : false" :sortable="index < 3 ? true : false"
:title-help="column.help !== null ? { message: column.help } : null" :title-help="column.help !== null ? { message: column.help } : null"
:filters=" :filters="
index < 2 index < 2
? null ? null
: index === 2 : index === 2
? valueTypeFilters ? valueTypeFilters
: [ : [
{ label: '', value: true }, { label: $t('yes'), value: true },
{ label: '', value: false }, { label: $t('no'), value: false },
] ]
" "
type="html" type="html"
> >
<template #default="{ row }"> <template #default="{ row }">
<span v-if="column.field !== 'name' && column.field !== 'alias' && column.field !== 'value_type'"> <span v-if="column.field !== 'name' && column.field !== 'alias' && column.field !== 'value_type'">
<a-icon :style="{ color: '#1fb51f' }" type="check" v-if="row[column.field]" /> <a-icon :style="{ color: '#1fb51f' }" type="check" v-if="row[column.field]" />
</span> </span>
<span v-else-if="column.field === 'value_type'" v-html="valueTypeMap[row.value_type]"> </span> <span v-else-if="column.field === 'value_type'" v-html="valueTypeMap[row.value_type]"> </span>
<span v-else v-html="row[column.field]"> </span> <span v-else v-html="row[column.field]"> </span>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
</a-spin> </a-spin>
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr' import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
import { valueTypeMap } from '@/modules/cmdb/utils/const' import { valueTypeMap } from '@/modules/cmdb/utils/const'
export default { export default {
name: 'MetadataDrawer', name: 'MetadataDrawer',
data() { data() {
const columns = [ return {
{ visible: false,
field: 'name', list: [],
title: '名称', tableData: [],
width: 150, loading: false,
align: 'left', valueTypeFilters: [],
help: null, searchKey: '',
}, }
{ },
field: 'alias', computed: {
title: '别名', windowHeight() {
width: 150, return this.$store.state.windowHeight
align: 'left', },
help: null, valueTypeMap() {
}, return valueTypeMap()
{ },
field: 'value_type', columns() {
title: '类型', return [
width: 100, {
align: 'left', field: 'name',
help: null, title: this.$t('name'),
}, width: 150,
{ align: 'left',
field: 'is_index', help: null,
title: '是否索引', },
width: 110, {
help: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引', field: 'alias',
}, title: this.$t('alias'),
{ width: 150,
field: 'default_show', align: 'left',
title: '默认显示', help: null,
width: 110, },
help: '订阅CI默认显示在table里的属性', {
}, field: 'value_type',
{ title: this.$t('type'),
field: 'is_unique', width: 100,
title: '是否唯一', align: 'left',
width: 110, help: null,
help: null, },
}, {
{ field: 'is_index',
field: 'is_choice', title: this.$t('cmdb.ciType.isIndex'),
title: '是否选择', width: 110,
width: 110, help: this.$t('cmdb.ci.tips6'),
help: '表现形式是下拉框, 值必须在预定义值里', },
}, {
{ field: 'default_show',
field: 'is_list', title: this.$t('cmdb.ciType.defaultShow'),
title: '是否列表', width: 110,
width: 110, help: this.$t('cmdb.ciType.defaultShowTips'),
help: '多值, 比如内网IP', },
}, {
{ field: 'is_unique',
field: 'is_sortable', title: this.$t('cmdb.ciType.isUnique'),
title: '可排序', width: 110,
width: 100, help: null,
help: '仅针对前端', },
}, {
{ field: 'is_choice',
field: 'is_computed', title: this.$t('cmdb.ciType.isChoice'),
title: '计算属性', width: 110,
width: 110, help: this.$t('cmdb.ci.tips7'),
help: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值', },
}, {
] field: 'is_list',
return { title: this.$t('cmdb.ciType.list'),
columns, width: 110,
visible: false, help: this.$t('cmdb.ci.tips8'),
list: [], },
tableData: [], {
loading: false, field: 'is_sortable',
valueTypeMap, title: this.$t('cmdb.ciType.isSortable'),
valueTypeFilters: [], width: 100,
searchKey: '', help: this.$t('cmdb.ci.tips9'),
} },
}, {
computed: { field: 'is_computed',
windowHeight() { title: this.$t('cmdb.ciType.computedAttribute'),
return this.$store.state.windowHeight width: 110,
}, help: this.$t('cmdb.ci.tips10'),
}, },
created: function() { ]
this.valueTypeFilters = Object.keys(this.valueTypeMap).map((key) => { },
return { label: this.valueTypeMap[key], value: key } },
}) created: function() {
}, this.valueTypeFilters = Object.keys(this.valueTypeMap).map((key) => {
methods: { return { label: this.valueTypeMap[key], value: key }
open(typeId) { })
this.visible = true },
this.typeId = typeId methods: {
this.getAttrs() open(typeId) {
}, this.visible = true
async getAttrs() { this.typeId = typeId
this.loading = true this.getAttrs()
const { attributes = [] } = await getCITypeAttributesByName(this.typeId) },
this.tableData = attributes.map((attr) => { async getAttrs() {
if (attr.is_password) { this.loading = true
attr.value_type = '7' const { attributes = [] } = await getCITypeAttributesByName(this.typeId)
} this.tableData = attributes.map((attr) => {
if (attr.is_link) { if (attr.is_password) {
attr.value_type = '8' attr.value_type = '7'
} }
return attr if (attr.is_link) {
}) attr.value_type = '8'
this.loading = false }
this.searchAttributes() return attr
}, })
searchAttributes() { this.loading = false
const filterName = XEUtils.toValueString(this.searchKey) this.searchAttributes()
.trim() },
.toLowerCase() searchAttributes() {
if (filterName) { const filterName = XEUtils.toValueString(this.searchKey)
const filterRE = new RegExp(filterName, 'gi') .trim()
const searchProps = ['name', 'alias', 'value_type'] .toLowerCase()
const rest = this.tableData.filter((item) => if (filterName) {
searchProps.some( const filterRE = new RegExp(filterName, 'gi')
(key) => const searchProps = ['name', 'alias', 'value_type']
XEUtils.toValueString(item[key]) const rest = this.tableData.filter((item) =>
.toLowerCase() searchProps.some(
.indexOf(filterName) > -1 (key) =>
) XEUtils.toValueString(item[key])
) .toLowerCase()
this.list = rest.map((row) => { .indexOf(filterName) > -1
const item = Object.assign({}, row) )
searchProps.forEach((key) => { )
item[key] = XEUtils.toValueString(item[key]).replace( this.list = rest.map((row) => {
filterRE, const item = Object.assign({}, row)
(match) => `<span style='background: yellow'>${match}</span>` searchProps.forEach((key) => {
) item[key] = XEUtils.toValueString(item[key]).replace(
}) filterRE,
return item (match) => `<span style='background: yellow'>${match}</span>`
}) )
} else { })
this.list = this.tableData return item
} })
}, } else {
}, this.list = this.tableData
} }
</script> },
},
<style></style> }
</script>
<style></style>

View File

@ -1,318 +1,318 @@
<template> <template>
<span :id="`ci-detail-attr-${attr.name}`"> <span :id="`ci-detail-attr-${attr.name}`">
<span v-if="!isEdit || attr.value_type === '6'"> <span v-if="!isEdit || attr.value_type === '6'">
<PasswordField <PasswordField
:style="{ display: 'inline-block' }" :style="{ display: 'inline-block' }"
v-if="attr.is_password && ci[attr.name]" v-if="attr.is_password && ci[attr.name]"
:ci_id="ci._id" :ci_id="ci._id"
:attr_id="attr.id" :attr_id="attr.id"
></PasswordField> ></PasswordField>
<template v-else-if="attr.value_type === '6'">{{ JSON.stringify(ci[attr.name] || {}) }}</template> <template v-else-if="attr.value_type === '6'">{{ JSON.stringify(ci[attr.name] || {}) }}</template>
<template v-else-if="attr.is_choice"> <template v-else-if="attr.is_choice">
<template v-if="attr.is_list"> <template v-if="attr.is_list">
<span <span
v-for="value in ci[attr.name]" v-for="value in ci[attr.name]"
:key="value" :key="value"
:style="{ :style="{
borderRadius: '4px', borderRadius: '4px',
padding: '1px 5px', padding: '1px 5px',
margin: '2px', margin: '2px',
...getChoiceValueStyle(attr, value), ...getChoiceValueStyle(attr, value),
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
}" }"
> >
<img <img
v-if="getChoiceValueIcon(attr, value).id && getChoiceValueIcon(attr, value).url" v-if="getChoiceValueIcon(attr, value).id && getChoiceValueIcon(attr, value).url"
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, value).url}`" :src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, value).url}`"
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }" :style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/> />
<ops-icon <ops-icon
v-else v-else
:style="{ color: getChoiceValueIcon(attr, value).color, marginRight: '5px' }" :style="{ color: getChoiceValueIcon(attr, value).color, marginRight: '5px' }"
:type="getChoiceValueIcon(attr, value).name" :type="getChoiceValueIcon(attr, value).name"
/> />
{{ value }}</span {{ value }}</span
> >
</template> </template>
<span <span
v-else v-else
:style="{ :style="{
borderRadius: '4px', borderRadius: '4px',
padding: '1px 5px', padding: '1px 5px',
margin: '2px 0', margin: '2px 0',
...getChoiceValueStyle(attr, ci[attr.name]), ...getChoiceValueStyle(attr, ci[attr.name]),
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
}" }"
> >
<img <img
v-if="getChoiceValueIcon(attr, ci[attr.name]).id && getChoiceValueIcon(attr, ci[attr.name]).url" v-if="getChoiceValueIcon(attr, ci[attr.name]).id && getChoiceValueIcon(attr, ci[attr.name]).url"
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, ci[attr.name]).url}`" :src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, ci[attr.name]).url}`"
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }" :style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/> />
<ops-icon <ops-icon
v-else v-else
:style="{ color: getChoiceValueIcon(attr, ci[attr.name]).color, marginRight: '5px' }" :style="{ color: getChoiceValueIcon(attr, ci[attr.name]).color, marginRight: '5px' }"
:type="getChoiceValueIcon(attr, ci[attr.name]).name" :type="getChoiceValueIcon(attr, ci[attr.name]).name"
/> />
{{ ci[attr.name] }} {{ ci[attr.name] }}
</span> </span>
</template> </template>
<template v-else-if="attr.is_list"> <template v-else-if="attr.is_list">
<span> {{ ci[attr.name].join(',') }}</span> <span> {{ ci[attr.name].join(',') }}</span>
</template> </template>
<template v-else>{{ getName(ci[attr.name]) }}</template> <template v-else>{{ getName(ci[attr.name]) }}</template>
</span> </span>
<template v-else> <template v-else>
<a-form :form="form"> <a-form :form="form">
<a-form-item label="" :colon="false"> <a-form-item label="" :colon="false">
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="attr.is_choice" v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
showSearch showSearch
allowClear allowClear
size="small" size="small"
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
> >
<a-select-option <a-select-option
:value="choice[0]" :value="choice[0]"
:key="'New_' + attr.name + choice_idx" :key="'New_' + attr.name + choice_idx"
v-for="(choice, choice_idx) in attr.choice_value" v-for="(choice, choice_idx) in attr.choice_value"
> >
<span :style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }"> <span :style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }">
<template v-if="choice[1] && choice[1].icon && choice[1].icon.name"> <template v-if="choice[1] && choice[1].icon && choice[1].icon.name">
<img <img
v-if="choice[1].icon.id && choice[1].icon.url" v-if="choice[1].icon.id && choice[1].icon.url"
:src="`/api/common-setting/v1/file/${choice[1].icon.url}`" :src="`/api/common-setting/v1/file/${choice[1].icon.url}`"
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }" :style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/> />
<ops-icon <ops-icon
v-else v-else
:style="{ color: choice[1].icon.color, marginRight: '5px' }" :style="{ color: choice[1].icon.color, marginRight: '5px' }"
:type="choice[1].icon.name" :type="choice[1].icon.name"
/> />
</template> </template>
{{ choice[0] }} {{ choice[0] }}
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-else-if="attr.is_list" v-else-if="attr.is_list"
mode="tags" mode="tags"
showSearch showSearch
allowClear allowClear
size="small" size="small"
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
> >
</a-select> </a-select>
<a-input-number <a-input-number
size="small" size="small"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else-if="attr.value_type === '0' || attr.value_type === '1'" v-else-if="attr.value_type === '0' || attr.value_type === '1'"
/> />
<a-date-picker <a-date-picker
size="small" size="small"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
:format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-else-if="attr.value_type === '4' || attr.value_type === '3'" v-else-if="attr.value_type === '4' || attr.value_type === '3'"
:showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }" :showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }"
/> />
<!-- <a-input <!-- <a-input
size="small" size="small"
@focus="(e) => handleFocusInput(e, attr)" @focus="(e) => handleFocusInput(e, attr)"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else-if="attr.value_type === '6'" v-else-if="attr.value_type === '6'"
/> --> /> -->
<a-input <a-input
size="small" size="small"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else v-else
/> />
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
<a v-if="!isEdit && !attr.is_computed" @click="handleEdit" :style="{ opacity: 0 }"><a-icon type="edit"/></a> <a v-if="!isEdit && !attr.is_computed" @click="handleEdit" :style="{ opacity: 0 }"><a-icon type="edit"/></a>
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
</span> </span>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import { updateCI } from '@/modules/cmdb/api/ci' import { updateCI } from '@/modules/cmdb/api/ci'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import PasswordField from '../../../components/passwordField/index.vue' import PasswordField from '../../../components/passwordField/index.vue'
import { getAttrPassword } from '../../../api/CITypeAttr' import { getAttrPassword } from '../../../api/CITypeAttr'
export default { export default {
name: 'CiDetailAttrContent', name: 'CiDetailAttrContent',
components: { JsonEditor, PasswordField }, components: { JsonEditor, PasswordField },
props: { props: {
ci: { ci: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
attr: { attr: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
}, },
data() { data() {
return { return {
isEdit: false, isEdit: false,
form: this.$form.createForm(this, this.attr.name), form: this.$form.createForm(this, this.attr.name),
} }
}, },
mounted() { mounted() {
document.addEventListener('click', this.eventListener) document.addEventListener('click', this.eventListener)
}, },
beforeDestroy() { beforeDestroy() {
document.removeEventListener('click', this.eventListener) document.removeEventListener('click', this.eventListener)
}, },
methods: { methods: {
moment, moment,
eventListener(e) { eventListener(e) {
const datePickerContainer = document.getElementsByClassName('ant-calendar-picker-container') const datePickerContainer = document.getElementsByClassName('ant-calendar-picker-container')
if (this.isEdit && !datePickerContainer.length) { if (this.isEdit && !datePickerContainer.length) {
const dom = document.getElementById(`ci-detail-attr-${this.attr.name}`) const dom = document.getElementById(`ci-detail-attr-${this.attr.name}`)
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
if (dom) { if (dom) {
const isSelf = dom.contains(e.target) const isSelf = dom.contains(e.target)
if (!isSelf) { if (!isSelf) {
this.handleCloseEdit() this.handleCloseEdit()
} }
} }
} }
}, },
handleEdit(e) { handleEdit(e) {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
if (this.attr.value_type === '6') { if (this.attr.value_type === '6') {
const jsonData = this.ci[this.attr.name] const jsonData = this.ci[this.attr.name]
this.$refs.jsonEditor.open(null, null, jsonData || {}) this.$refs.jsonEditor.open(null, null, jsonData || {})
return return
} }
this.isEdit = true this.isEdit = true
this.$nextTick(async () => { this.$nextTick(async () => {
if (this.attr.is_list && !this.attr.is_choice) { if (this.attr.is_list && !this.attr.is_choice) {
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: this.ci[this.attr.name] || null, [`${this.attr.name}`]: this.ci[this.attr.name] || null,
}) })
return return
} }
if (this.attr.is_password) { if (this.attr.is_password) {
await getAttrPassword(this.ci._id, this.attr.id).then((res) => { await getAttrPassword(this.ci._id, this.attr.id).then((res) => {
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: res.value ?? null, [`${this.attr.name}`]: res.value ?? null,
}) })
}) })
return return
} }
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: this.ci[this.attr.name] ?? null, [`${this.attr.name}`]: this.ci[this.attr.name] ?? null,
}) })
}) })
}, },
async handleCloseEdit() { async handleCloseEdit() {
const newData = this.form.getFieldValue(this.attr.name) const newData = this.form.getFieldValue(this.attr.name)
if (!_.isEqual(this.ci[this.attr.name], newData)) { if (!_.isEqual(this.ci[this.attr.name], newData)) {
await updateCI(this.ci._id, { [`${this.attr.name}`]: newData }) await updateCI(this.ci._id, { [`${this.attr.name}`]: newData })
.then(() => { .then(() => {
this.$message.success('更新成功!') this.$message.success(this.$t('updateSuccess'))
this.$emit('updateCIByself', { [`${this.attr.name}`]: newData }, this.attr.name) this.$emit('updateCIByself', { [`${this.attr.name}`]: newData }, this.attr.name)
}) })
.catch(() => { .catch(() => {
this.$emit('refresh', this.attr.name) this.$emit('refresh', this.attr.name)
}) })
} }
this.isEdit = false this.isEdit = false
}, },
// handleFocusInput(e, attr) { // handleFocusInput(e, attr) {
// console.log('focus') // console.log('focus')
// if (this.attr.value_type === '6') { // if (this.attr.value_type === '6') {
// e.preventDefault() // e.preventDefault()
// e.stopPropagation() // e.stopPropagation()
// // e.srcElement.blur() // // e.srcElement.blur()
// const jsonData = this.form.getFieldValue(attr.name) // const jsonData = this.form.getFieldValue(attr.name)
// this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {}) // this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {})
// } // }
// }, // },
jsonEditorOk(jsonData) { jsonEditorOk(jsonData) {
if (!_.isEqual(this.ci[this.attr.name], jsonData)) { if (!_.isEqual(this.ci[this.attr.name], jsonData)) {
updateCI(this.ci._id, { [`${this.attr.name}`]: jsonData }) updateCI(this.ci._id, { [`${this.attr.name}`]: jsonData })
.then(() => { .then(() => {
this.$message.success('更新成功!') this.$message.success(this.$t('updateSuccess'))
this.$emit('updateCIByself', { [`${this.attr.name}`]: jsonData }, this.attr.name) this.$emit('updateCIByself', { [`${this.attr.name}`]: jsonData }, this.attr.name)
}) })
.catch(() => { .catch(() => {
this.$emit('refresh', this.attr.name) this.$emit('refresh', this.attr.name)
}) })
} }
}, },
getChoiceValueStyle(col, colValue) { getChoiceValueStyle(col, colValue) {
const _find = col.choice_value.find((item) => String(item[0]) === String(colValue)) const _find = col.choice_value.find((item) => String(item[0]) === String(colValue))
if (_find) { if (_find) {
return _find[1]?.style || {} return _find[1]?.style || {}
} }
return {} return {}
}, },
getChoiceValueIcon(col, colValue) { getChoiceValueIcon(col, colValue) {
const _find = col.choice_value.find((item) => String(item[0]) === String(colValue)) const _find = col.choice_value.find((item) => String(item[0]) === String(colValue))
if (_find) { if (_find) {
return _find[1]?.icon || {} return _find[1]?.icon || {}
} }
return {} return {}
}, },
getName(name) { getName(name) {
return name ?? '' return name ?? ''
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -2,10 +2,10 @@
<div class="ci-detail-relation"> <div class="ci-detail-relation">
<a-radio-group v-model="activeKey" size="small" @change="handleChangeActiveKey"> <a-radio-group v-model="activeKey" size="small" @change="handleChangeActiveKey">
<a-radio-button value="1"> <a-radio-button value="1">
拓扑 {{ $t('cmdb.ci.topo') }}
</a-radio-button> </a-radio-button>
<a-radio-button value="2"> <a-radio-button value="2">
表格 {{ $t('cmdb.ci.table') }}
</a-radio-button> </a-radio-button>
</a-radio-group> </a-radio-group>
<CiDetailRelationTopo ref="ciDetailRelationTopo" v-if="activeKey === '1'" /> <CiDetailRelationTopo ref="ciDetailRelationTopo" v-if="activeKey === '1'" />
@ -24,7 +24,7 @@
><a-icon ><a-icon
type="plus-square" type="plus-square"
/></a> /></a>
<span v-if="!canEdit[parent.id]">当前模型关系为多对多请前往关系视图进行增删操作</span> <span v-if="!canEdit[parent.id]">{{ $t('cmdb.ci.m2mTips') }}</span>
</div> </div>
<vxe-grid <vxe-grid
v-if="firstCIs[parent.name]" v-if="firstCIs[parent.name]"
@ -39,7 +39,7 @@
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(row._id, ciId)"> <a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(row._id, ciId)">
<a <a
:disabled="!canEdit[parent.id]" :disabled="!canEdit[parent.id]"
:style="{ :style="{
@ -68,7 +68,7 @@
><a-icon ><a-icon
type="plus-square" type="plus-square"
/></a> /></a>
<span v-if="!canEdit[child.id]">当前模型关系为多对多请前往关系视图进行增删操作</span> <span v-if="!canEdit[child.id]">{{ $t('cmdb.ci.m2mTips') }}</span>
</div> </div>
<vxe-grid <vxe-grid
v-if="secondCIs[child.name]" v-if="secondCIs[child.name]"
@ -82,7 +82,7 @@
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(ciId, row._id)"> <a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(ciId, row._id)">
<a <a
:disabled="!canEdit[child.id]" :disabled="!canEdit[child.id]"
:style="{ :style="{
@ -338,7 +338,7 @@ export default {
firstCIColumns[item.id].push({ firstCIColumns[item.id].push({
key: 'p_operation', key: 'p_operation',
field: 'operation', field: 'operation',
title: '操作', title: this.$t('operation'),
width: '60px', width: '60px',
fixed: 'right', fixed: 'right',
slots: { slots: {
@ -379,7 +379,7 @@ export default {
secondCIColumns[item.id].push({ secondCIColumns[item.id].push({
key: 'c_operation', key: 'c_operation',
field: 'operation', field: 'operation',
title: '操作', title: this.$t('operation'),
width: '60px', width: '60px',
fixed: 'right', fixed: 'right',
slots: { slots: {

View File

@ -1,168 +1,168 @@
<template> <template>
<div <div
id="ci-detail-relation-topo" id="ci-detail-relation-topo"
class="ci-detail-relation-topo" class="ci-detail-relation-topo"
:style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }" :style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }"
></div> ></div>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { TreeCanvas } from 'butterfly-dag' import { TreeCanvas } from 'butterfly-dag'
import { searchCIRelation } from '@/modules/cmdb/api/CIRelation' import { searchCIRelation } from '@/modules/cmdb/api/CIRelation'
import Node from './node.js' import Node from './node.js'
import 'butterfly-dag/dist/index.css' import 'butterfly-dag/dist/index.css'
import './index.less' import './index.less'
export default { export default {
name: 'CiDetailRelationTopo', name: 'CiDetailRelationTopo',
data() { data() {
return { return {
topoData: {}, topoData: {},
exsited_ci: [], exsited_ci: [],
} }
}, },
inject: ['ci_types'], inject: ['ci_types'],
mounted() {}, mounted() {},
methods: { methods: {
init() { init() {
const root = document.getElementById('ci-detail-relation-topo') const root = document.getElementById('ci-detail-relation-topo')
this.canvas = new TreeCanvas({ this.canvas = new TreeCanvas({
root: root, root: root,
disLinkable: false, // 可删除连线 disLinkable: false, // 可删除连线
linkable: false, // 可连线 linkable: false, // 可连线
draggable: true, // 可拖动 draggable: true, // 可拖动
zoomable: true, // 可放大 zoomable: true, // 可放大
moveable: true, // 可平移 moveable: true, // 可平移
theme: { theme: {
edge: { edge: {
shapeType: 'AdvancedBezier', shapeType: 'AdvancedBezier',
arrow: true, arrow: true,
arrowPosition: 1, arrowPosition: 1,
}, },
}, },
layout: { layout: {
type: 'mindmap', type: 'mindmap',
options: { options: {
direction: 'H', direction: 'H',
getSide(d) { getSide(d) {
return d.data.side || 'right' return d.data.side || 'right'
}, },
getHeight(d) { getHeight(d) {
return 10 return 10
}, },
getWidth(d) { getWidth(d) {
return 40 return 40
}, },
getHGap(d) { getHGap(d) {
return 80 return 80
}, },
getVGap(d) { getVGap(d) {
return 40 return 40
}, },
}, },
}, },
}) })
this.canvas.setZoomable(true, true) this.canvas.setZoomable(true, true)
this.canvas.on('events', ({ type, data }) => { this.canvas.on('events', ({ type, data }) => {
const sourceNode = data?.id || null const sourceNode = data?.id || null
if (type === 'custom:clickLeft') { if (type === 'custom:clickLeft') {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => { searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'left') this.redrawData(res, sourceNode, 'left')
}) })
} }
if (type === 'custom:clickRight') { if (type === 'custom:clickRight') {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => { searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'right') this.redrawData(res, sourceNode, 'right')
}) })
} }
}) })
}, },
setTopoData(data) { setTopoData(data) {
this.canvas = null this.canvas = null
this.init() this.init()
this.topoData = _.cloneDeep(data) this.topoData = _.cloneDeep(data)
this.canvas.draw(data, {}, () => { this.canvas.draw(data, {}, () => {
this.canvas.focusCenterWithAnimate() this.canvas.focusCenterWithAnimate()
}) })
}, },
redrawData(res, sourceNode, side) { redrawData(res, sourceNode, side) {
const newNodes = [] const newNodes = []
const newEdges = [] const newEdges = []
if (!res.result.length) { if (!res.result.length) {
this.$message.info('无层级关系!') this.$message.info(this.$t('cmdb.ci.noLevel'))
return return
} }
const ci_types_list = this.ci_types() const ci_types_list = this.ci_types()
res.result.forEach((r) => { res.result.forEach((r) => {
if (!this.exsited_ci.includes(r._id)) { if (!this.exsited_ci.includes(r._id)) {
const _findCiType = ci_types_list.find((item) => item.id === r._type) const _findCiType = ci_types_list.find((item) => item.id === r._type)
newNodes.push({ newNodes.push({
id: `${r._id}`, id: `${r._id}`,
Class: Node, Class: Node,
title: r.ci_type_alias || r.ci_type, title: r.ci_type_alias || r.ci_type,
name: r.ci_type, name: r.ci_type,
side: side, side: side,
unique_alias: r.unique_alias, unique_alias: r.unique_alias,
unique_name: r.unique, unique_name: r.unique,
unique_value: r[r.unique], unique_value: r[r.unique],
children: [], children: [],
icon: _findCiType?.icon || '', icon: _findCiType?.icon || '',
endpoints: [ endpoints: [
{ {
id: 'left', id: 'left',
orientation: [-1, 0], orientation: [-1, 0],
pos: [0, 0.5], pos: [0, 0.5],
}, },
{ {
id: 'right', id: 'right',
orientation: [1, 0], orientation: [1, 0],
pos: [0, 0.5], pos: [0, 0.5],
}, },
], ],
}) })
} }
newEdges.push({ newEdges.push({
id: `${r._id}`, id: `${r._id}`,
source: 'right', source: 'right',
target: 'left', target: 'left',
sourceNode: side === 'right' ? sourceNode : `${r._id}`, sourceNode: side === 'right' ? sourceNode : `${r._id}`,
targetNode: side === 'right' ? `${r._id}` : sourceNode, targetNode: side === 'right' ? `${r._id}` : sourceNode,
type: 'endpoint', type: 'endpoint',
}) })
}) })
const { nodes, edges } = this.canvas.getDataMap() const { nodes, edges } = this.canvas.getDataMap()
// 删除原节点和边 // 删除原节点和边
this.canvas.removeNodes(nodes.map((node) => node.id)) this.canvas.removeNodes(nodes.map((node) => node.id))
this.canvas.removeEdges(edges) this.canvas.removeEdges(edges)
const _topoData = _.cloneDeep(this.topoData) const _topoData = _.cloneDeep(this.topoData)
_topoData.edges.push(...newEdges) _topoData.edges.push(...newEdges)
let result let result
const getTreeItem = (data, id) => { const getTreeItem = (data, id) => {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
if (data[i].id === id) { if (data[i].id === id) {
result = data[i] // 结果赋值 result = data[i] // 结果赋值
result.edges = _topoData.edges result.edges = _topoData.edges
break break
} else { } else {
if (data[i].children && data[i].children.length) { if (data[i].children && data[i].children.length) {
getTreeItem(data[i].children, id) getTreeItem(data[i].children, id)
} }
} }
} }
} }
getTreeItem(_topoData.nodes.children, sourceNode) getTreeItem(_topoData.nodes.children, sourceNode)
result.children.push(...newNodes) result.children.push(...newNodes)
this.topoData = _topoData this.topoData = _topoData
this.canvas.draw(_topoData, {}, () => {}) this.canvas.draw(_topoData, {}, () => {})
this.exsited_ci = [...new Set([...this.exsited_ci, ...res.result.map((r) => r._id)])] this.exsited_ci = [...new Set([...this.exsited_ci, ...res.result.map((r) => r._id)])]
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -1,56 +1,57 @@
/* eslint-disable no-useless-constructor */ /* eslint-disable no-useless-constructor */
import { TreeNode } from 'butterfly-dag' import { TreeNode } from 'butterfly-dag'
import i18n from '@/lang'
import $ from 'jquery'
import $ from 'jquery'
class BaseNode extends TreeNode {
constructor(opts) { class BaseNode extends TreeNode {
super(opts) constructor(opts) {
} super(opts)
}
draw = (opts) => {
const container = $(`<div class="${opts.id.startsWith('Root') ? 'root' : ''} ci-detail-relation-topo-node"></div>`) draw = (opts) => {
.css('top', opts.top) const container = $(`<div class="${opts.id.startsWith('Root') ? 'root' : ''} ci-detail-relation-topo-node"></div>`)
.css('left', opts.left) .css('top', opts.top)
.attr('id', opts.id) .css('left', opts.left)
let icon .attr('id', opts.id)
if (opts.options.icon) { let icon
if (opts.options.icon.split('$$')[2]) { if (opts.options.icon) {
icon = $(`<img style="max-width:16px;max-height:16px;" src="/api/common-setting/v1/file/${opts.options.icon.split('$$')[3]}" />`) if (opts.options.icon.split('$$')[2]) {
} else { icon = $(`<img style="max-width:16px;max-height:16px;" src="/api/common-setting/v1/file/${opts.options.icon.split('$$')[3]}" />`)
icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`) } else {
} icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`)
} else { }
icon = $(`<span class="icon icon-default">${opts.options.name[0].toUpperCase()}</span>`) } else {
} icon = $(`<span class="icon icon-default">${opts.options.name[0].toUpperCase()}</span>`)
}
const titleContent = $(`<div title=${opts.options.title} class="title">${opts.options.title}</div>`)
const uniqueDom = $(`<div class="unique">${opts.options.unique_alias || opts.options.unique_name}${opts.options.unique_value}<div>`) const titleContent = $(`<div title=${opts.options.title} class="title">${opts.options.title}</div>`)
container.append(icon) const uniqueDom = $(`<div class="unique">${opts.options.unique_alias || opts.options.unique_name}${opts.options.unique_value}<div>`)
container.append(titleContent) container.append(icon)
container.append(uniqueDom) container.append(titleContent)
container.append(uniqueDom)
if (opts.options.side && (!opts.options.children.length && !(opts.options.edges && opts.options.edges.length && opts.options.edges.find(e => e.source === opts.options.side && e.sourceNode === opts.options.id)))) {
const addIcon = $(`<i aria-label="图标: plus-square" class="anticon anticon-plus-square add-icon-${opts.options.side}"><svg viewBox="64 64 896 896" data-icon="plus-square" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"></path></svg></i>`) if (opts.options.side && (!opts.options.children.length && !(opts.options.edges && opts.options.edges.length && opts.options.edges.find(e => e.source === opts.options.side && e.sourceNode === opts.options.id)))) {
container.append(addIcon) const addIcon = $(`<i aria-label="${i18n.t('icon')}: plus-square" class="anticon anticon-plus-square add-icon-${opts.options.side}"><svg viewBox="64 64 896 896" data-icon="plus-square" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"></path></svg></i>`)
addIcon.on('click', () => { container.append(addIcon)
if (opts.options.side === 'left') { addIcon.on('click', () => {
this.emit('events', { if (opts.options.side === 'left') {
type: 'custom:clickLeft', this.emit('events', {
data: { ...this } type: 'custom:clickLeft',
}) data: { ...this }
} })
if (opts.options.side === 'right') { }
this.emit('events', { if (opts.options.side === 'right') {
type: 'custom:clickRight', this.emit('events', {
data: { ...this } type: 'custom:clickRight',
}) data: { ...this }
} })
}) }
} })
}
return container[0]
} return container[0]
} }
}
export default BaseNode
export default BaseNode

View File

@ -1,6 +1,6 @@
<template> <template>
<a-form :form="form"> <a-form :form="form">
<a-divider style="font-size: 14px; margin: 14px 0; font-weight: 700">{{ group.name || '其他' }}</a-divider> <a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ group.name || $t('other') }}</a-divider>
<a-row :gutter="24" align="top" type="flex"> <a-row :gutter="24" align="top" type="flex">
<a-col <a-col
:span="12" :span="12"
@ -21,11 +21,11 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请选择${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null, initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
}, },
]" ]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="attr.is_choice" v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
showSearch showSearch
@ -60,7 +60,7 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请选择${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null, initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
}, },
]" ]"
@ -70,7 +70,7 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请输入${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : null, initialValue: attr.default && attr.default.default ? attr.default.default : null,
}, },
]" ]"
@ -81,7 +81,7 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请选择${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? moment(attr.default.default) : null, initialValue: attr.default && attr.default.default ? moment(attr.default.default) : null,
}, },
]" ]"
@ -96,7 +96,7 @@
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required, message: `请输入${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? JSON.stringify(attr.default.default) : '', initialValue: attr.default && attr.default.default ? JSON.stringify(attr.default.default) : '',
}, },
]" ]"
@ -109,7 +109,7 @@
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required, message: `请输入${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : null, initialValue: attr.default && attr.default.default ? attr.default.default : null,
}, },
]" ]"

View File

@ -7,7 +7,7 @@
visible = false visible = false
} }
" "
title="字段设置" :title="$t('cmdb.ci.attributeSettings')"
> >
<CustomTransfer <CustomTransfer
ref="customTransfer" ref="customTransfer"
@ -17,16 +17,16 @@
width: '230px', width: '230px',
height: '500px', height: '500px',
}" }"
:titles="['未选属性', '已选属性']" :titles="[$t('cmdb.components.unselectAttributes'), $t('cmdb.components.selectAttributes')]"
:render="item => item.title" :render="item => item.title"
:targetKeys="selectedAttrList" :targetKeys="selectedAttrList"
@change="handleChange" @change="handleChange"
@selectChange="selectChange" @selectChange="selectChange"
> >
<span slot="notFoundContent">没数据</span> <span slot="notFoundContent">{{ $t('noData') }}</span>
</CustomTransfer> </CustomTransfer>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleSubmit" type="primary">确定</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</CustomDrawer> </CustomDrawer>
</template> </template>
@ -79,16 +79,17 @@ export default {
this.selectedAttrList = targetKeys this.selectedAttrList = targetKeys
}, },
handleSubmit() { handleSubmit() {
const that = this
if (this.selectedAttrList.length) { if (this.selectedAttrList.length) {
subscribeCIType(this.typeId, this.selectedAttrList).then(res => { subscribeCIType(this.typeId, this.selectedAttrList).then(res => {
this.$message.success('订阅成功!') this.$message.success(this.$t('cmdb.components.subSuccess'))
this.visible = false this.visible = false
this.$emit('refresh') this.$emit('refresh')
}) })
} else { } else {
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: '必须至少选择一个字段', content: that.$t('cmdb.ci.tips4'),
}) })
} }
}, },

View File

@ -75,12 +75,12 @@ export default {
return [item, !!this.fixedList.includes(item)] return [item, !!this.fixedList.includes(item)]
}) })
).then((res) => { ).then((res) => {
this.$message.success('订阅成功!') this.$message.success(this.$t('cmdb.components.subSuccess'))
this.visible = false this.visible = false
this.$emit('refresh') this.$emit('refresh')
}) })
} else { } else {
this.$message.error('请至少选择一个字段!') this.$message.error(this.$t('cmdb.ci.tips4'))
} }
}, },
setTargetKeys(targetKeys) { setTargetKeys(targetKeys) {

View File

@ -3,9 +3,9 @@
<Discovery :isSelected="true" :style="{ maxHeight: '75vh', overflow: 'auto' }" /> <Discovery :isSelected="true" :style="{ maxHeight: '75vh', overflow: 'auto' }" />
<template #footer> <template #footer>
<a-space> <a-space>
<a-button @click="handleCancel">取消</a-button> <a-button @click="handleCancel">{{ $t('cancel') }}</a-button>
<a-button type="primary" @click="handleOK">确认</a-button> <a-button type="primary" @click="handleOK">{{ $t('confirm') }}</a-button>
<a-button type="primary" @click="addPlugin">新建plugin</a-button> <a-button type="primary" @click="addPlugin">{{ $t('cmdb.ciType.addPlugin') }}</a-button>
</a-space> </a-space>
</template> </template>
</a-modal> </a-modal>
@ -55,7 +55,7 @@ export default {
await Promise.all(promises) await Promise.all(promises)
.then((res) => { .then((res) => {
this.getCITypeDiscovery(res[0].id) this.getCITypeDiscovery(res[0].id)
this.$message.success('添加成功') this.$message.success(this.$t('addSuccess'))
}) })
.catch(() => { .catch(() => {
this.getCITypeDiscovery() this.getCITypeDiscovery()

View File

@ -29,7 +29,7 @@
slot="tabBarExtraContent" slot="tabBarExtraContent"
:style="{ cursor: 'pointer' }" :style="{ cursor: 'pointer' }"
> >
<ops-icon type="icon-xianxing-tianjia" :style="{ color: '#2F54EB' }" /><a>添加</a> <ops-icon type="icon-xianxing-tianjia" :style="{ color: '#2F54EB' }" /><a>{{ $t('add') }}</a>
</a-space> </a-space>
</a-tabs> </a-tabs>
</div> </div>
@ -40,7 +40,7 @@
}" }"
> >
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> 暂无数据 </span> <span slot="description"> {{ $t('noData') }} </span>
<a-button <a-button
@click=" @click="
() => { () => {
@ -52,7 +52,7 @@
icon="plus" icon="plus"
class="ops-button-primary" class="ops-button-primary"
> >
添加 {{ $t('add') }}
</a-button> </a-button>
</a-empty> </a-empty>
<ADModal ref="adModal" :CITypeId="CITypeId" @addPlugin="openEditDrawer(null, 'add', 'agent')" /> <ADModal ref="adModal" :CITypeId="CITypeId" @addPlugin="openEditDrawer(null, 'add', 'agent')" />
@ -158,10 +158,10 @@ export default {
e.stopPropagation() e.stopPropagation()
const that = this const that = this
this.$confirm({ this.$confirm({
title: `确认删除 ${item?.extra_option?.alias || this.getADCITypeParam(item.adr_id)}`, title: that.$t('cmdb.ciType.confirmDeleteADT', { pluginName: `${item?.extra_option?.alias || this.getADCITypeParam(item.adr_id)}` }),
content: (h) => ( content: (h) => (
<div> <div>
<a-checkbox v-model={that.deletePlugin}>删除插件</a-checkbox> <a-checkbox v-model={that.deletePlugin}>{that.$t('cmdb.ciType.deletePlugin')}</a-checkbox>
</div> </div>
), ),
onOk() { onOk() {
@ -169,7 +169,7 @@ export default {
if (that.currentTab === item.id) { if (that.currentTab === item.id) {
that.currentTab = '' that.currentTab = ''
} }
that.$message.success('删除成功!') that.$message.success(that.$t('deleteSuccess'))
that.getCITypeDiscovery() that.getCITypeDiscovery()
if (that.deletePlugin) { if (that.deletePlugin) {
await deleteDiscovery(item.adr_id).finally(() => { await deleteDiscovery(item.adr_id).finally(() => {

View File

@ -1,396 +1,398 @@
<template> <template>
<div :style="{ height: `${windowHeight - 156}px`, overflow: 'auto', position: 'relative' }"> <div :style="{ height: `${windowHeight - 156}px`, overflow: 'auto', position: 'relative' }">
<a <a
v-if="!adrIsInner" v-if="!adrIsInner"
:style="{ position: 'absolute', right: 0, top: 0 }" :style="{ position: 'absolute', right: 0, top: 0 }"
@click=" @click="
() => { () => {
$emit('openEditDrawer', currentAdr, 'edit', 'agent') $emit('openEditDrawer', currentAdr, 'edit', 'agent')
} }
" "
> >
<a-space> <a-space>
<ops-icon type="icon-xianxing-edit" /> <ops-icon type="icon-xianxing-edit" />
<span>编辑</span> <span>{{ $t('edit') }}</span>
</a-space> </a-space>
</a> </a>
<div>别名<a-input v-model="alias" style="width:200px;" /></div> <div>{{ $t('alias') }}<a-input v-model="alias" style="width:200px;" /></div>
<div class="attr-ad-header">字段映射</div> <div class="attr-ad-header">{{ $t('cmdb.ciType.attributeMap') }}</div>
<vxe-table <vxe-table
v-if="adrType === 'agent'" v-if="adrType === 'agent'"
ref="xTable" ref="xTable"
:edit-config="{ trigger: 'click', mode: 'cell' }" :edit-config="{ trigger: 'click', mode: 'cell' }"
size="mini" size="mini"
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
:data="tableData" :data="tableData"
:style="{ width: '700px', marginBottom: '20px' }" :style="{ width: '700px', marginBottom: '20px' }"
> >
<vxe-colgroup title="自动发现"> <vxe-colgroup :title="$t('cmdb.ciType.autoDiscovery')">
<vxe-column field="name" title="名称"> </vxe-column> <vxe-column field="name" :title="$t('name')"> </vxe-column>
<vxe-column field="type" title="类型"> </vxe-column> <vxe-column field="type" :title="$t('type')"> </vxe-column>
<vxe-column field="desc" title="描述"> </vxe-column> <vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</vxe-colgroup> </vxe-colgroup>
<vxe-colgroup title="模型属性"> <vxe-colgroup :title="$t('cmdb.ciType.attributes')">
<vxe-column field="attr" title="名称" :edit-render="{}"> <vxe-column field="attr" :title="$t('name')" :edit-render="{}">
<template #default="{row}"> <template #default="{row}">
{{ row.attr }} {{ row.attr }}
</template> </template>
<template #edit="{ row }"> <template #edit="{ row }">
<vxe-select <vxe-select
filterable filterable
clearable clearable
v-model="row.attr" v-model="row.attr"
type="text" type="text"
:options="ciTypeAttributes" :options="ciTypeAttributes"
transfer transfer
></vxe-select> ></vxe-select>
</template> </template>
</vxe-column> </vxe-column>
</vxe-colgroup> </vxe-colgroup>
</vxe-table> </vxe-table>
<HttpSnmpAD <HttpSnmpAD
v-else v-else
:isEdit="true" :isEdit="true"
ref="httpSnmpAd" ref="httpSnmpAd"
:ruleType="adrType" :ruleType="adrType"
:ruleName="adrName" :ruleName="adrName"
:ciTypeAttributes="ciTypeAttributes" :ciTypeAttributes="ciTypeAttributes"
:adCITypeList="adCITypeList" :adCITypeList="adCITypeList"
:currentTab="adr_id" :currentTab="adr_id"
:style="{ marginBottom: '20px' }" :style="{ marginBottom: '20px' }"
/> />
<a-form-model <a-form-model
v-if="adrType === 'http'" v-if="adrType === 'http'"
:model="form2" :model="form2"
:labelCol="{ span: 2 }" :labelCol="{ span: 2 }"
:wrapperCol="{ span: 8 }" :wrapperCol="{ span: 8 }"
:style="{ margin: '20px 0' }" :style="{ margin: '20px 0' }"
> >
<a-form-model-item label="key"> <a-form-model-item label="key">
<a-input-password v-model="form2.key" /> <a-input-password v-model="form2.key" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="secret"> <a-form-model-item label="secret">
<a-input-password v-model="form2.secret" /> <a-input-password v-model="form2.secret" />
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
<a-form :form="form3" v-if="adrType === 'snmp'" class="attr-ad-snmp-form"> <a-form :form="form3" v-if="adrType === 'snmp'" class="attr-ad-snmp-form">
<a-col :span="24"> <a-col :span="24">
<a-form-item label="节点" :labelCol="{ span: 2 }" :wrapperCol="{ span: 20 }"> <a-form-item :label="$t('cmdb.ciType.node')" :labelCol="{ span: 2 }" :wrapperCol="{ span: 20 }">
<MonitorNodeSetting ref="monitorNodeSetting" :initNodes="nodes" :form="form3" /> <MonitorNodeSetting ref="monitorNodeSetting" :initNodes="nodes" :form="form3" />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-form> </a-form>
<div class="attr-ad-header">执行配置</div> <div class="attr-ad-header">{{ $t('cmdb.ciType.adExecConfig') }}</div>
<a-form-model :model="form" :labelCol="{ span: 2 }" :wrapperCol="{ span: 20 }"> <a-form-model :model="form" :labelCol="{ span: 2 }" :wrapperCol="{ span: 20 }">
<a-form-model-item label="执行机器"> <a-form-model-item :label="$t('cmdb.ciType.adExecTarget')">
<CustomRadio v-model="agent_type" :radioList="agentTypeRadioList"> <CustomRadio v-model="agent_type" :radioList="agentTypeRadioList">
<a-input <a-input
:style="{ width: '300px' }" :style="{ width: '300px' }"
placeholder="请输入以0x开头的16进制OneAgent ID" :placeholder="$t('cmdb.ciType.oneagentIdTips')"
v-show="agent_type === 'agent_id'" v-show="agent_type === 'agent_id'"
slot="extra_agent_id" slot="extra_agent_id"
v-model="form.agent_id" v-model="form.agent_id"
/> />
<a-input <a-input
:style="{ width: '300px' }" :style="{ width: '300px' }"
v-show="agent_type === 'query_expr'" v-show="agent_type === 'query_expr'"
slot="extra_query_expr" slot="extra_query_expr"
placeholder="从CMDB选择" :placeholder="$t('cmdb.ciType.selectFromCMDBTips')"
v-model="form.query_expr" v-model="form.query_expr"
> >
<a @click="handleOpenCmdb" slot="suffix"><a-icon type="menu"/></a> <a @click="handleOpenCmdb" slot="suffix"><a-icon type="menu"/></a>
</a-input> </a-input>
</CustomRadio> </CustomRadio>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="自动入库"> <a-form-model-item :label="$t('cmdb.ciType.adAutoInLib')">
<a-switch v-model="form.auto_accept" /> <a-switch v-model="form.auto_accept" />
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
<div class="attr-ad-header">采集频率</div> <div class="attr-ad-header">{{ $t('cmdb.ciType.adInterval') }}</div>
<CustomRadio :radioList="radioList" v-model="interval"> <CustomRadio :radioList="radioList" v-model="interval">
<span v-show="interval === 'interval'" slot="extra_interval"> <span v-show="interval === 'interval'" slot="extra_interval">
<a-input-number v-model="intervalValue" :min="1" /> <a-input-number v-model="intervalValue" :min="1" /> {{ $t('seconds') }}
</span> </span>
</CustomRadio> </CustomRadio>
<div class="attr-ad-footer"> <div class="attr-ad-footer">
<a-button type="primary" @click="handleSave">保存</a-button> <a-button type="primary" @click="handleSave">{{ $t('save') }}</a-button>
</div> </div>
<CMDBExprDrawer ref="cmdbDrawer" @copySuccess="copySuccess" /> <CMDBExprDrawer ref="cmdbDrawer" @copySuccess="copySuccess" />
</div> </div>
</template> </template>
<script> <script>
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import Vcrontab from '@/components/Crontab' import Vcrontab from '@/components/Crontab'
import { putCITypeDiscovery } from '../../api/discovery' import { putCITypeDiscovery } from '../../api/discovery'
import HttpSnmpAD from '../../components/httpSnmpAD' import HttpSnmpAD from '../../components/httpSnmpAD'
import CMDBExprDrawer from '@/components/CMDBExprDrawer' import CMDBExprDrawer from '@/components/CMDBExprDrawer'
import MonitorNodeSetting from '@/components/MonitorNodeSetting' import MonitorNodeSetting from '@/components/MonitorNodeSetting'
export default { export default {
name: 'AttrADTabpane', name: 'AttrADTabpane',
components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting }, components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting },
props: { props: {
adr_id: { adr_id: {
type: Number, type: Number,
default: 0, default: 0,
}, },
adrList: { adrList: {
type: Array, type: Array,
default: () => {}, default: () => {},
}, },
adCITypeList: { adCITypeList: {
type: Array, type: Array,
default: () => {}, default: () => {},
}, },
currentAdt: { currentAdt: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
currentAdr: { currentAdr: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
ciTypeAttributes: { ciTypeAttributes: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}, },
data() { data() {
const radioList = [ return {
{ value: 'interval', label: '按间隔' }, tableData: [],
] form: {
return { agent_id: '',
radioList, auto_accept: false,
tableData: [], query_expr: '',
form: { },
agent_id: '', form2: {
auto_accept: false, key: '',
query_expr: '', secret: '',
}, },
form2: { interval: 'interval', // interval cron
key: '', cron: '',
secret: '', intervalValue: 3,
}, agent_type: 'agent_id',
interval: 'interval', // interval cron nodes: [
cron: '', {
intervalValue: 3, id: uuidv4(),
agent_type: 'agent_id', ip: '',
nodes: [ community: '',
{ version: '',
id: uuidv4(), },
ip: '', ],
community: '', form3: this.$form.createForm(this, { name: 'snmp_form' }),
version: '', alias: '',
}, }
], },
form3: this.$form.createForm(this, { name: 'snmp_form' }), computed: {
alias: '', ...mapState({
} windowHeight: (state) => state.windowHeight,
}, userRoles: (state) => state.user.roles,
computed: { }),
...mapState({ adrType() {
windowHeight: (state) => state.windowHeight, return this.currentAdr.type
userRoles: (state) => state.user.roles, },
}), adrName() {
adrType() { return this.currentAdr.name
return this.currentAdr.type },
}, adrIsInner() {
adrName() { return this.currentAdr.is_inner
return this.currentAdr.name },
}, agentTypeRadioList() {
adrIsInner() { const { permissions = [] } = this.userRoles
return this.currentAdr.is_inner if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType !== 'http') {
}, return [
agentTypeRadioList() { { value: 'all', label: this.$t('cmdb.ciType.allNodes') },
const { permissions = [] } = this.userRoles { value: 'agent_id', label: this.$t('cmdb.ciType.specifyNodes') },
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType !== 'http') { { value: 'query_expr', label: this.$t('cmdb.ciType.selectFromCMDBTips') },
return [ ]
{ value: 'all', label: '所有节点' }, }
{ value: 'agent_id', label: '指定节点' }, return [
{ value: 'query_expr', label: '从CMDB中选择 ' }, { value: 'agent_id', label: this.$t('cmdb.ciType.specifyNodes') },
] { value: 'query_expr', label: this.$t('cmdb.ciType.selectFromCMDBTips') },
} ]
return [ },
{ value: 'agent_id', label: '指定节点' }, radioList() {
{ value: 'query_expr', label: '从CMDB中选择 ' }, return [
] { value: 'interval', label: this.$t('cmdb.ciType.byInterval') },
}, // { value: 'cron', label: '按cron', layout: 'vertical' },
}, ]
mounted() {}, },
methods: { },
init() { mounted() {},
const _find = this.adrList.find((item) => Number(item.id) === Number(this.adr_id)) methods: {
const _findADT = this.adCITypeList.find((item) => Number(item.id) === Number(this.currentAdt.id)) init() {
this.alias = _findADT?.extra_option?.alias ?? '' const _find = this.adrList.find((item) => Number(item.id) === Number(this.adr_id))
if (this.adrType === 'http') { const _findADT = this.adCITypeList.find((item) => Number(item.id) === Number(this.currentAdt.id))
const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {} this.alias = _findADT?.extra_option?.alias ?? ''
this.form2 = { if (this.adrType === 'http') {
key, const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {}
secret, this.form2 = {
} key,
this.$refs.httpSnmpAd.setCurrentCate(category) secret,
} }
if (this.adrType === 'snmp') { this.$refs.httpSnmpAd.setCurrentCate(category)
this.nodes = _findADT?.extra_option?.nodes ?? [ }
{ if (this.adrType === 'snmp') {
id: uuidv4(), this.nodes = _findADT?.extra_option?.nodes ?? [
ip: '', {
community: '', id: uuidv4(),
version: '', ip: '',
}, community: '',
] version: '',
this.$nextTick(() => { },
this.$refs.monitorNodeSetting.initNodesFunc() ]
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.monitorNodeSetting.setNodeField() this.$refs.monitorNodeSetting.initNodesFunc()
}) this.$nextTick(() => {
}) this.$refs.monitorNodeSetting.setNodeField()
} })
if (this.adrType === 'agent') { })
this.tableData = (_find?.attributes || []).map((item) => { }
if (_findADT.attributes) { if (this.adrType === 'agent') {
return { this.tableData = (_find?.attributes || []).map((item) => {
...item, if (_findADT.attributes) {
attr: _findADT.attributes[`${item.name}`], return {
} ...item,
} else { attr: _findADT.attributes[`${item.name}`],
const _find = this.ciTypeAttributes.find((ele) => ele.name === item.name) }
if (_find) { } else {
return { const _find = this.ciTypeAttributes.find((ele) => ele.name === item.name)
...item, if (_find) {
attr: _find.name, return {
} ...item,
} attr: _find.name,
return item }
} }
}) return item
} }
this.form = { })
auto_accept: _findADT?.auto_accept || false, }
agent_id: _findADT.agent_id || '', this.form = {
query_expr: _findADT.query_expr || '', auto_accept: _findADT?.auto_accept || false,
} agent_id: _findADT.agent_id || '',
if (_findADT.query_expr) { query_expr: _findADT.query_expr || '',
this.agent_type = 'query_expr' }
} else if (_findADT.agent_id) { if (_findADT.query_expr) {
this.agent_type = 'agent_id' this.agent_type = 'query_expr'
} else { } else if (_findADT.agent_id) {
this.agent_type = this.agentTypeRadioList[0].value this.agent_type = 'agent_id'
} } else {
if (_findADT.interval || (!_findADT.interval && !_findADT.cron)) { this.agent_type = this.agentTypeRadioList[0].value
this.interval = 'interval' }
this.intervalValue = _findADT.interval || '' if (_findADT.interval || (!_findADT.interval && !_findADT.cron)) {
} else { this.interval = 'interval'
this.interval = 'cron' this.intervalValue = _findADT.interval || ''
this.cron = `0 ${_findADT.cron}` } else {
} this.interval = 'cron'
}, this.cron = `0 ${_findADT.cron}`
getAttrNameByAttrName(attrName) { }
const _find = this.ciTypeAttributes.find((item) => item.name === attrName) },
return _find?.alias || _find?.name || '' getAttrNameByAttrName(attrName) {
}, const _find = this.ciTypeAttributes.find((item) => item.name === attrName)
crontabFill(cron) { return _find?.alias || _find?.name || ''
this.cron = cron },
}, crontabFill(cron) {
handleSave() { this.cron = cron
const { currentAdt, alias } = this },
let params handleSave() {
if (this.adrType === 'http') { const { currentAdt, alias } = this
params = { let params
extra_option: { if (this.adrType === 'http') {
...this.form2, params = {
category: this.$refs.httpSnmpAd.currentCate, extra_option: {
}, ...this.form2,
} category: this.$refs.httpSnmpAd.currentCate,
} },
if (this.adrType === 'snmp') { }
params = { }
extra_option: { nodes: this.$refs.monitorNodeSetting?.getNodeValue() ?? [] }, if (this.adrType === 'snmp') {
} params = {
} extra_option: { nodes: this.$refs.monitorNodeSetting?.getNodeValue() ?? [] },
if (this.adrType === 'agent') { }
const $table = this.$refs.xTable }
const { fullData: _tableData } = $table.getTableData() if (this.adrType === 'agent') {
const attributes = {} const $table = this.$refs.xTable
_tableData.forEach((td) => { const { fullData: _tableData } = $table.getTableData()
if (td.attr) { const attributes = {}
attributes[`${td.name}`] = td.attr _tableData.forEach((td) => {
} if (td.attr) {
}) attributes[`${td.name}`] = td.attr
params = { }
...params, })
attributes, params = {
} ...params,
} else { attributes,
const _tableData = this.$refs.httpSnmpAd.getTableData() }
const attributes = {} } else {
_tableData.forEach((td) => { const _tableData = this.$refs.httpSnmpAd.getTableData()
if (td.attr) { const attributes = {}
attributes[`${td.name}`] = td.attr _tableData.forEach((td) => {
} if (td.attr) {
}) attributes[`${td.name}`] = td.attr
params = { }
...params, })
attributes, params = {
} ...params,
} attributes,
if (this.interval === 'cron') { }
this.$refs.cronTab.submitFill() }
} if (this.interval === 'cron') {
params = { this.$refs.cronTab.submitFill()
...params, }
...this.form, params = {
type_id: this.CITypeId, ...params,
adr_id: currentAdt.adr_id, ...this.form,
interval: this.interval === 'interval' ? this.intervalValue : null, type_id: this.CITypeId,
cron: this.interval === 'cron' ? this.cron : null, adr_id: currentAdt.adr_id,
} interval: this.interval === 'interval' ? this.intervalValue : null,
if (this.agent_type === 'agent_id' || this.agent_type === 'all') { cron: this.interval === 'cron' ? this.cron : null,
params.query_expr = '' }
if (this.agent_type === 'agent_id' && !params.agent_id) { if (this.agent_type === 'agent_id' || this.agent_type === 'all') {
this.$message.error('请填写指定节点!') params.query_expr = ''
return if (this.agent_type === 'agent_id' && !params.agent_id) {
} this.$message.error(this.$t('cmdb.ciType.specifyNodesTips'))
} return
if (this.agent_type === 'query_expr' || this.agent_type === 'all') { }
params.agent_id = '' }
if (this.agent_type === 'query_expr' && !params.query_expr) { if (this.agent_type === 'query_expr' || this.agent_type === 'all') {
this.$message.error('请从cmdb中选择') params.agent_id = ''
return if (this.agent_type === 'query_expr' && !params.query_expr) {
} this.$message.error(this.$t('cmdb.ciType.selectFromCMDBTips'))
} return
if (params.extra_option) { }
params.extra_option.alias = alias }
} else { if (params.extra_option) {
params.extra_option = {} params.extra_option.alias = alias
params.extra_option.alias = alias } else {
} params.extra_option = {}
putCITypeDiscovery(currentAdt.id, params).then((res) => { params.extra_option.alias = alias
this.$message.success('保存成功') }
this.$emit('handleSave') putCITypeDiscovery(currentAdt.id, params).then((res) => {
}) this.$message.success(this.$t('saveSuccess'))
}, this.$emit('handleSave')
handleOpenCmdb() { })
this.$refs.cmdbDrawer.open() },
}, handleOpenCmdb() {
copySuccess(text) { this.$refs.cmdbDrawer.open()
this.form = { },
...this.form, copySuccess(text) {
query_expr: `${text}`, this.form = {
} ...this.form,
}, query_expr: `${text}`,
}, }
} },
</script> },
}
<style lang="less"> </script>
.attr-ad-snmp-form {
.ant-form-item { <style lang="less">
margin-bottom: 0; .attr-ad-snmp-form {
} .ant-form-item {
} margin-bottom: 0;
</style> }
}
</style>

View File

@ -1,278 +1,282 @@
<template> <template>
<div class="attribute-card"> <div class="attribute-card">
<div class="attribute-card-content"> <div class="attribute-card-content">
<div class="attribute-card-value-type-icon handle" :style="{ ...getPropertyStyle(property) }"> <div class="attribute-card-value-type-icon handle" :style="{ ...getPropertyStyle(property) }">
<ValueTypeIcon :attr="property" /> <ValueTypeIcon :attr="property" />
</div> </div>
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }"> <div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }"> <div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
{{ property.alias || property.name }} {{ property.alias || property.name }}
</div> </div>
<div v-if="property.is_password" class="attribute-card_value-type">密码</div> <div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
<div v-else-if="property.is_link" class="attribute-card_value-type">链接</div> <div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div> <div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
</div> </div>
<div <div
class="attribute-card-trigger" class="attribute-card-trigger"
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore" v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
> >
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a> <a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
</div> </div>
</div> </div>
<div class="attribute-card-footer"> <div class="attribute-card-footer">
<a-popover <a-popover
trigger="click" trigger="click"
:arrowPointAtCenter="true" :arrowPointAtCenter="true"
placement="bottom" placement="bottom"
overlayClassName="attribute-card-footer-popover" overlayClassName="attribute-card-footer-popover"
> >
<div slot="content"> <div slot="content">
<h3 :style="{ textAlign: 'center', paddingTop: '0.5em' }"> <h3 :style="{ textAlign: 'center', paddingTop: '0.5em' }">
<span>{{ property.alias }}({{ property.name }})</span> <span>{{ property.alias }}({{ property.name }})</span>
</h3> </h3>
<a-descriptions layout="horizontal" bordered size="small" :column="2"> <a-descriptions layout="horizontal" bordered size="small" :column="2">
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label"> <a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
<components <components
:is="`ops_${item.property}`" :is="`ops_${item.property}`"
v-if="property[item.property]" v-if="property[item.property]"
:style="{ width: '1em', height: '1em' }" :style="{ width: '1em', height: '1em' }"
/> />
<ops-icon v-else :type="`ops-${item.property}-disabled`" /> <ops-icon v-else :type="`ops-${item.property}-disabled`" />
</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item label></a-descriptions-item> <a-descriptions-item label></a-descriptions-item>
</a-descriptions> </a-descriptions>
</div> </div>
<a-space :style="{ cursor: 'pointer' }"> <a-space :style="{ cursor: 'pointer' }">
<components <components
v-for="item in propertyList.filter((p) => property[p.property])" v-for="item in propertyList.filter((p) => property[p.property])"
:key="item.property" :key="item.property"
:is="`ops_${item.property}`" :is="`ops_${item.property}`"
/> />
</a-space> </a-space>
</a-popover> </a-popover>
<a-space class="attribute-card-operation"> <a-space class="attribute-card-operation">
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a> <a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
<a-tooltip title="所有CI触发计算"> <a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a> <a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
</a-tooltip> </a-tooltip>
<a style="color:red;"><a-icon type="delete" @click="handleDelete"/></a> <a style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
</a-space> </a-space>
</div> </div>
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" /> <TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
</div> </div>
</template> </template>
<script> <script>
import { deleteCITypeAttributesById, deleteAttributesById, calcComputedAttribute } from '@/modules/cmdb/api/CITypeAttr' import { deleteCITypeAttributesById, deleteAttributesById, calcComputedAttribute } from '@/modules/cmdb/api/CITypeAttr'
import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon' import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon'
import { import {
ops_default_show, ops_default_show,
ops_is_choice, ops_is_choice,
ops_is_index, ops_is_index,
ops_is_link, ops_is_link,
ops_is_password, ops_is_password,
ops_is_sortable, ops_is_sortable,
ops_is_unique, ops_is_unique,
} from '@/core/icons' } from '@/core/icons'
import { valueTypeMap } from '../../utils/const' import { valueTypeMap } from '../../utils/const'
import { getPropertyStyle } from '../../utils/helper' import { getPropertyStyle } from '../../utils/helper'
import TriggerForm from './triggerForm.vue' import TriggerForm from './triggerForm.vue'
export default { export default {
name: 'AttributeCard', name: 'AttributeCard',
components: { components: {
ValueTypeIcon, ValueTypeIcon,
TriggerForm, TriggerForm,
ops_default_show, ops_default_show,
ops_is_choice, ops_is_choice,
ops_is_index, ops_is_index,
ops_is_link, ops_is_link,
ops_is_password, ops_is_password,
ops_is_sortable, ops_is_sortable,
ops_is_unique, ops_is_unique,
}, },
props: { props: {
property: { property: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
CITypeId: { CITypeId: {
type: Number, type: Number,
default: null, default: null,
}, },
isStore: { isStore: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
attributes: { attributes: {
type: Array, type: Array,
default: () => [] default: () => [],
} },
}, },
data() { data() {
const propertyList = [ return {}
{ },
label: '是否唯一', computed: {
property: 'is_unique', valueTypeMap() {
}, return valueTypeMap()
{ },
label: '是否选择', propertyList() {
property: 'is_choice', return [
}, {
{ label: this.$t('cmdb.ciType.isUnique'),
label: '默认显示', property: 'is_unique',
property: 'default_show', },
}, {
{ label: this.$t('cmdb.ciType.isChoice'),
label: '可排序', property: 'is_choice',
property: 'is_sortable', },
}, {
{ label: this.$t('cmdb.ciType.defaultShow'),
label: '是否索引', property: 'default_show',
property: 'is_index', },
}, {
] label: this.$t('cmdb.ciType.isSortable'),
return { property: 'is_sortable',
valueTypeMap, },
propertyList, {
} label: this.$t('cmdb.ciType.isIndex'),
}, property: 'is_index',
methods: { },
getPropertyStyle, ]
handleEdit() { },
this.$emit('edit') },
}, methods: {
handleDelete() { getPropertyStyle,
const that = this handleEdit() {
this.$confirm({ this.$emit('edit')
title: '警告', },
content: `确认删除 ${that.property.alias || that.property.name}`, handleDelete() {
onOk() { const that = this
if (that.isStore) { this.$confirm({
deleteAttributesById(that.property.id).then(() => { title: that.$t('warning'),
that.$message.success('删除成功!') content: that.$t('cmdb.ciType.confirmDelete', { name: `${that.property.alias || that.property.name}` }),
that.$emit('ok') onOk() {
}) if (that.isStore) {
} else { deleteAttributesById(that.property.id).then(() => {
deleteCITypeAttributesById(that.CITypeId, { attr_id: [that.property.id] }).then(() => { that.$message.success(that.$t('deleteSuccess'))
that.$message.success('删除成功!') that.$emit('ok')
that.$emit('ok') })
}) } else {
} deleteCITypeAttributesById(that.CITypeId, { attr_id: [that.property.id] }).then(() => {
}, that.$message.success(that.$t('deleteSuccess'))
onCancel() {}, that.$emit('ok')
}) })
}, }
openTrigger() { },
this.$refs.triggerForm.open(this.property, this.attributes) onCancel() {},
}, })
handleCalcComputed() { },
const that = this openTrigger() {
this.$confirm({ this.$refs.triggerForm.open(this.property, this.attributes)
title: '警告', },
content: `确认触发所有CI的计算`, handleCalcComputed() {
onOk() { const that = this
calcComputedAttribute(that.property.id).then(() => { this.$confirm({
that.$message.success('触发成功!') title: that.$t('warning'),
}) content: that.$t('cmdb.ciType.confirmcomputeForAllCITips'),
}, onOk() {
}) calcComputedAttribute(that.property.id).then(() => {
}, that.$message.success(that.$t('cmdb.ciType.computeSuccess'))
}, })
} },
</script> })
},
<style lang="less" scoped> },
.attribute-card { }
width: 182px; </script>
height: 80px;
background: #f8faff; <style lang="less" scoped>
border-radius: 5px; .attribute-card {
position: relative; width: 182px;
margin-bottom: 16px; height: 80px;
transition: all 0.3s; background: #f8faff;
&:hover { border-radius: 5px;
box-shadow: 0 4px 12px #4e5ea066; position: relative;
.attribute-card-operation { margin-bottom: 16px;
visibility: visible !important; transition: all 0.3s;
} &:hover {
} box-shadow: 0 4px 12px #4e5ea066;
.attribute-card-content { .attribute-card-operation {
height: 50px; visibility: visible !important;
display: inline-flex; }
align-items: center; }
padding: 8px; .attribute-card-content {
width: 100%; height: 50px;
.attribute-card-value-type-icon { display: inline-flex;
width: 32px; align-items: center;
height: 32px; padding: 8px;
font-size: 12px; width: 100%;
cursor: move; .attribute-card-value-type-icon {
background: #ffffff !important; width: 32px;
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2); height: 32px;
border-radius: 2px; font-size: 12px;
text-align: center; cursor: move;
line-height: 32px; background: #ffffff !important;
} box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
.attribute-card-content-inner { border-radius: 2px;
padding-left: 12px; text-align: center;
font-weight: 400; line-height: 32px;
font-size: 12px; }
width: 120px; .attribute-card-content-inner {
position: relative; padding-left: 12px;
.attribute-card-name { font-weight: 400;
width: 100%; font-size: 12px;
color: rgba(0, 0, 0, 0.8); width: 120px;
overflow: hidden; position: relative;
text-overflow: ellipsis; .attribute-card-name {
white-space: nowrap; width: 100%;
} color: rgba(0, 0, 0, 0.8);
.attribute-card-name-default-show { overflow: hidden;
color: #2f54eb; text-overflow: ellipsis;
} white-space: nowrap;
.attribute-card_value-type { }
font-size: 10px; .attribute-card-name-default-show {
color: rgba(0, 0, 0, 0.35); color: #2f54eb;
} }
} .attribute-card_value-type {
.attribute-card-name-required::before { font-size: 10px;
content: '*'; color: rgba(0, 0, 0, 0.35);
width: 5px; }
color: red; }
position: absolute; .attribute-card-name-required::before {
left: 3px; content: '*';
} width: 5px;
.attribute-card-trigger { color: red;
position: absolute; position: absolute;
right: 8px; left: 3px;
top: 8px; }
} .attribute-card-trigger {
} position: absolute;
.attribute-card-footer { right: 8px;
width: 182px; top: 8px;
height: 30px; }
padding: 0 8px; }
position: absolute; .attribute-card-footer {
bottom: 0; width: 182px;
left: 0; height: 30px;
background: linear-gradient(180deg, #96abd6 0%, #ecf2ff 0.01%, #ffffff 143.33%); padding: 0 8px;
border-radius: 0px 0px 5px 5px; position: absolute;
display: inline-flex; bottom: 0;
align-items: center; left: 0;
justify-content: space-between; background: linear-gradient(180deg, #96abd6 0%, #ecf2ff 0.01%, #ffffff 143.33%);
.attribute-card-operation { border-radius: 0px 0px 5px 5px;
visibility: hidden; display: inline-flex;
} align-items: center;
} justify-content: space-between;
} .attribute-card-operation {
</style> visibility: hidden;
<style lang="less"> }
.attribute-card-footer-popover { }
.ant-popover-inner-content { }
padding: 0; </style>
} <style lang="less">
.ant-descriptions-bordered .ant-descriptions-item-label { .attribute-card-footer-popover {
background-color: #f8faff; .ant-popover-inner-content {
} padding: 0;
} }
</style> .ant-descriptions-bordered .ant-descriptions-item-label {
background-color: #f8faff;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,15 @@
<a-modal wrapClassName="attrbute-store-wrapper" width="80%" :visible="visible" @cancel="handleCancel"> <a-modal wrapClassName="attrbute-store-wrapper" width="80%" :visible="visible" @cancel="handleCancel">
<template slot="title"> <template slot="title">
<div class="attrbute-store-header"> <div class="attrbute-store-header">
<span>属性库</span> <span>{{ $t('cmdb.ciType.attributeLibray') }}</span>
<div class="attrbute-store-search"> <div class="attrbute-store-search">
<a-input-group compact> <a-input-group compact>
<a-select class="attrbute-store-search-select" v-model="searchKey"> <a-select class="attrbute-store-search-select" v-model="searchKey">
<a-select-option value="alias"> <a-select-option value="alias">
别名 {{ $t('alias') }}
</a-select-option> </a-select-option>
<a-select-option value="name"> <a-select-option value="name">
名称 {{ $t('name') }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input <a-input
@ -53,7 +53,7 @@
</a-row> </a-row>
<a-empty v-else> <a-empty v-else>
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> 暂无数据 </span> <span slot="description"> {{ $t('noData') }} </span>
</a-empty> </a-empty>
</a-spin> </a-spin>
<template slot="footer"> <template slot="footer">
@ -63,7 +63,7 @@
show-quick-jumper show-quick-jumper
:current="tablePage.currentPage" :current="tablePage.currentPage"
:total="tablePage.totalResult" :total="tablePage.totalResult"
:show-total="(total, range) => `当前展示 ${range[0]}-${range[1]} 条数据, 共 ${total} 条`" :show-total="(total, range) => $t('pagination.total', { total: total, range0: range[0], range1: range[1] })"
:page-size="tablePage.pageSize" :page-size="tablePage.pageSize"
:default-current="1" :default-current="1"
@change="pageOrSizeChange" @change="pageOrSizeChange"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,22 @@
<template> <template>
<a-card :bordered="false" :bodyStyle="{ padding: '0' }"> <a-card :bordered="false" :bodyStyle="{ padding: '0' }">
<a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab" type="card"> <a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab" type="card">
<a-tab-pane key="1" tab="模型属性"> <a-tab-pane key="1" :tab="$t('cmdb.ciType.attributes')">
<AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable> <AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable>
</a-tab-pane> </a-tab-pane>
<a-tab-pane forceRender key="2" tab="模型关联"> <a-tab-pane forceRender key="2" :tab="$t('cmdb.ciType.relation')">
<RelationTable :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable> <RelationTable :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab="触发器"> <a-tab-pane key="3" :tab="$t('cmdb.ciType.trigger')">
<TriggerTable ref="triggerTable" :CITypeId="CITypeId"></TriggerTable> <TriggerTable ref="triggerTable" :CITypeId="CITypeId"></TriggerTable>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="4" tab="属性自动发现"> <a-tab-pane key="4" :tab="$t('cmdb.ciType.attributeAD')">
<AttrAD :CITypeId="CITypeId"></AttrAD> <AttrAD :CITypeId="CITypeId"></AttrAD>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab="关系自动发现"> <a-tab-pane key="5" :tab="$t('cmdb.ciType.relationAD')">
<RelationAD :CITypeId="CITypeId"></RelationAD> <RelationAD :CITypeId="CITypeId"></RelationAD>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="6" tab="权限设置"> <a-tab-pane key="6" :tab="$t('cmdb.ciType.grant')">
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp> <GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>

View File

@ -1,90 +1,90 @@
<template> <template>
<a-tabs v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }"> <a-tabs v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }">
<a-tab-pane key="expr" :disabled="!canDefineComputed"> <a-tab-pane key="expr" :disabled="!canDefineComputed">
<span style="font-size:12px;" slot="tab">表达式</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.expr') }}</span>
<a-textarea v-model="compute_expr" :placeholder="`{{a}}+{{b}}`" :rows="2" :disabled="!canDefineComputed" /> <a-textarea v-model="compute_expr" :placeholder="`{{a}}+{{b}}`" :rows="2" :disabled="!canDefineComputed" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="script" :disabled="!canDefineComputed"> <a-tab-pane key="script" :disabled="!canDefineComputed">
<span style="font-size:12px;" slot="tab">代码</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span>
<codemirror style="z-index: 9999" :options="cmOptions" v-model="compute_script"></codemirror> <codemirror style="z-index: 9999" :options="cmOptions" v-model="compute_script"></codemirror>
</a-tab-pane> </a-tab-pane>
<template slot="tabBarExtraContent" v-if="showCalcComputed"> <template slot="tabBarExtraContent" v-if="showCalcComputed">
<a-button type="primary" size="small" @click="handleCalcComputed"> <a-button type="primary" size="small" @click="handleCalcComputed">
应用 {{ $t('cmdb.ciType.apply') }}
</a-button> </a-button>
<a-tooltip title="所有CI触发计算"> <a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
<a-icon type="question-circle" style="margin-left:5px" /> <a-icon type="question-circle" style="margin-left:5px" />
</a-tooltip> </a-tooltip>
</template> </template>
</a-tabs> </a-tabs>
</template> </template>
<script> <script>
import { codemirror } from 'vue-codemirror' import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css' import 'codemirror/theme/monokai.css'
require('codemirror/mode/python/python.js') require('codemirror/mode/python/python.js')
export default { export default {
name: 'ComputedArea', name: 'ComputedArea',
components: { codemirror }, components: { codemirror },
props: { props: {
canDefineComputed: { canDefineComputed: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
showCalcComputed: { showCalcComputed: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
data() { data() {
return { return {
activeKey: 'expr', // expr script activeKey: 'expr', // expr script
compute_expr: '', compute_expr: '',
compute_script: 'def computed(): \n return', compute_script: 'def computed(): \n return',
cmOptions: { cmOptions: {
lineNumbers: true, lineNumbers: true,
mode: 'python', mode: 'python',
height: '200px', height: '200px',
theme: 'monokai', theme: 'monokai',
tabSize: 4, tabSize: 4,
lineWrapping: true, lineWrapping: true,
readOnly: !this.canDefineComputed, readOnly: !this.canDefineComputed,
}, },
} }
}, },
methods: { methods: {
getData() { getData() {
const { activeKey, compute_expr, compute_script } = this const { activeKey, compute_expr, compute_script } = this
if (activeKey === 'expr') { if (activeKey === 'expr') {
return { compute_expr, compute_script: null } return { compute_expr, compute_script: null }
} else if (activeKey === 'script') { } else if (activeKey === 'script') {
return { compute_script, compute_expr: null } return { compute_script, compute_expr: null }
} }
}, },
setData(data) { setData(data) {
const { compute_expr, compute_script } = data const { compute_expr, compute_script } = data
this.compute_expr = compute_expr this.compute_expr = compute_expr
this.compute_script = compute_script || 'def computed(): \n return' this.compute_script = compute_script || 'def computed(): \n return'
if (compute_script) { if (compute_script) {
this.activeKey = 'script' this.activeKey = 'script'
} else { } else {
this.activeKey = 'expr' this.activeKey = 'expr'
} }
}, },
handleCalcComputed() { handleCalcComputed() {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: this.$t('warning'),
content: `确认触发将保存当前配置及触发所有CI的计算`, content: this.$t('cmdb.ciType.confirmcomputeForAllCITips'),
onOk() { onOk() {
that.$emit('handleCalcComputed') that.$emit('handleCalcComputed')
}, },
}) })
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

File diff suppressed because it is too large Load Diff

View File

@ -18,18 +18,18 @@
visible = false visible = false
} }
" "
>取消</a-button >{{ $t('cancel') }}</a-button
> >
<a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">继续添加</a-button> <a-button :loading="confirmLoading" @click="handleSubmit(false)" type="primary">{{ $t('cmdb.ciType.continueAdd') }}</a-button>
<a-button :loading="confirmLoading" type="primary" @click="handleSubmit">确定</a-button> <a-button :loading="confirmLoading" type="primary" @click="handleSubmit">{{ $t('confirm') }}</a-button>
</template> </template>
<a-tabs v-model="activeKey"> <a-tabs v-model="activeKey">
<a-tab-pane key="1" tab="新建属性"> <a-tab-pane key="1" :tab="$t('cmdb.ciType.addAttribute')">
<div :style="{ overflow: 'auto', maxHeight: '480px' }"> <div :style="{ overflow: 'auto', maxHeight: '480px' }">
<create-new-attribute ref="createNewAttribute" :hasFooter="false" @done="handleAddNewAttr" /> <create-new-attribute ref="createNewAttribute" :hasFooter="false" @done="handleAddNewAttr" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="已有属性" force-render> <a-tab-pane key="2" :tab="$t('cmdb.ciType.existedAttributes')" force-render>
<AttributesTransfer <AttributesTransfer
:dataSource="unLinkdAttrs" :dataSource="unLinkdAttrs"
:targetKeys="targetKeys" :targetKeys="targetKeys"
@ -67,7 +67,6 @@ export default {
}, },
data() { data() {
return { return {
valueTypeMap,
activeKey: '1', activeKey: '1',
visible: false, visible: false,
attributes: [], attributes: [],
@ -78,6 +77,9 @@ export default {
} }
}, },
computed: { computed: {
valueTypeMap() {
return valueTypeMap()
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
@ -154,7 +156,7 @@ export default {
}, },
handleClose(isCloseModal = true) { handleClose(isCloseModal = true) {
this.$emit('ok') this.$emit('ok')
this.$message.success('添加成功!') this.$message.success(this.$t('addSuccess'))
if (isCloseModal) { if (isCloseModal) {
this.visible = false this.visible = false
} }

View File

@ -1,422 +1,422 @@
<template> <template>
<a-tabs <a-tabs
id="preValueArea" id="preValueArea"
v-model="activeKey" v-model="activeKey"
@change="changeActiveKey" @change="changeActiveKey"
size="small" size="small"
:tabBarStyle="{ borderBottom: 'none' }" :tabBarStyle="{ borderBottom: 'none' }"
> >
<a-tab-pane key="define" :disabled="disabled"> <a-tab-pane key="define" :disabled="disabled">
<span style="font-size:12px;" slot="tab">定义</span> <span style="font-size:12px;" slot="tab">{{ $t('define') }}</span>
<PreValueTag type="add" :item="[]" @add="addNewValue" :disabled="disabled"> <PreValueTag type="add" :item="[]" @add="addNewValue" :disabled="disabled">
<template #default> <template #default>
<a-button <a-button
:style="{ marginBottom: '10px', fontSize: '12px', padding: '1px 7px' }" :style="{ marginBottom: '10px', fontSize: '12px', padding: '1px 7px' }"
type="primary" type="primary"
ghost ghost
:disabled="disabled" :disabled="disabled"
size="small" size="small"
> >
<a-icon type="plus" />添加</a-button <a-icon type="plus" />{{ $t('add') }}</a-button
> >
</template> </template>
</PreValueTag> </PreValueTag>
<draggable :list="valueList" handle=".handle" :disabled="disabled"> <draggable :list="valueList" handle=".handle" :disabled="disabled">
<PreValueTag <PreValueTag
:disabled="disabled" :disabled="disabled"
v-for="(item, index) in valueList" v-for="(item, index) in valueList"
:key="`${item[0]}_${index}`" :key="`${item[0]}_${index}`"
:item="item" :item="item"
@deleteValue="deleteValue" @deleteValue="deleteValue"
@editValue="editValue" @editValue="editValue"
/> />
</draggable> </draggable>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="webhook" :disabled="disabled"> <a-tab-pane key="webhook" :disabled="disabled">
<span style="font-size:12px;" slot="tab">Webhook</span> <span style="font-size:12px;" slot="tab">Webhook</span>
<Webhook ref="webhook" style="margin-top:10px" /> <Webhook ref="webhook" style="margin-top:10px" />
<a-form-model :model="form"> <a-form-model :model="form">
<a-col :span="24"> <a-col :span="24">
<a-form-model-item prop="ret_key" :labelCol="{ span: 3 }" :wrapperCol="{ span: 18 }"> <a-form-model-item prop="ret_key" :labelCol="{ span: 3 }" :wrapperCol="{ span: 18 }">
<template slot="label"> <template slot="label">
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ `过滤` }} >{{ $t('cmdb.ciType.filter') }}
<a-tooltip <a-tooltip
title="返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]" :title="$t('cmdb.ciType.choiceWebhookTips')"
> >
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle" type="question-circle"
theme="filled" theme="filled"
/> />
</a-tooltip> </a-tooltip>
</span> </span>
</template> </template>
<a-input style="width:150px;" v-model="form.ret_key" placeholder="k1##k2" :disabled="disabled" /> <a-input style="width:150px;" v-model="form.ret_key" placeholder="k1##k2" :disabled="disabled" />
</a-form-model-item> </a-form-model-item>
</a-col> </a-col>
</a-form-model> </a-form-model>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="choice_other" :disabled="disabled"> <a-tab-pane key="choice_other" :disabled="disabled">
<span style="font-size:12px;" slot="tab">其他模型属性</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.choiceOther') }}</span>
<a-row :gutter="[24, 24]"> <a-row :gutter="[24, 24]">
<a-col :span="12"> <a-col :span="12">
<a-form-item <a-form-item
:style="{ lineHeight: '24px', marginBottom: '5px' }" :style="{ lineHeight: '24px', marginBottom: '5px' }"
label="模型" :label="$t('cmdb.ciType.ciType')"
:label-col="{ span: 4 }" :label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 20 }"
> >
<treeselect <treeselect
:disable-branch-nodes="true" :disable-branch-nodes="true"
:class="{ :class="{
'custom-treeselect': true, 'custom-treeselect': true,
'custom-treeselect-bgcAndBorder': true, 'custom-treeselect-bgcAndBorder': true,
}" }"
:style="{ :style="{
'--custom-height': '32px', '--custom-height': '32px',
lineHeight: '32px', lineHeight: '32px',
'--custom-bg-color': '#fff', '--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9', '--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '14px', '--custom-multiple-lineHeight': '14px',
}" }"
v-model="choice_other.type_ids" v-model="choice_other.type_ids"
:multiple="true" :multiple="true"
:clearable="true" :clearable="true"
searchable searchable
:options="ciTypeGroup" :options="ciTypeGroup"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="请选择CMDB模型" :placeholder="$t('cmdb.ciType.selectCIType')"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.id || -1, id: node.id || -1,
label: node.alias || node.name || '其他', label: node.alias || node.name || $t('other'),
title: node.alias || node.name || '其他', title: node.alias || node.name || $t('other'),
children: node.ci_types, children: node.ci_types,
} }
} }
" "
appendToBody appendToBody
:zIndex="1050" :zIndex="1050"
@select=" @select="
() => { () => {
choice_other.attr_id = undefined choice_other.attr_id = undefined
} }
" "
> >
<div <div
:title="node.label" :title="node.label"
slot="option-label" slot="option-label"
slot-scope="{ node }" slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
> >
{{ node.label }} {{ node.label }}
</div> </div>
</treeselect> </treeselect>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="12" v-if="choice_other.type_ids && choice_other.type_ids.length"> <a-col :span="12" v-if="choice_other.type_ids && choice_other.type_ids.length">
<a-form-item <a-form-item
:style="{ marginBottom: '5px' }" :style="{ marginBottom: '5px' }"
label="属性" :label="$t('cmdb.ciType.attributes')"
:label-col="{ span: 4 }" :label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 20 }"
> >
<treeselect <treeselect
:disable-branch-nodes="true" :disable-branch-nodes="true"
class="ops-setting-treeselect" class="ops-setting-treeselect"
v-model="choice_other.attr_id" v-model="choice_other.attr_id"
:multiple="false" :multiple="false"
:clearable="true" :clearable="true"
searchable searchable
:options="typeAttrs" :options="typeAttrs"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="请选择模型属性" :placeholder="$t('cmdb.ciType.selectCITypeAttributes')"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.id || -1, id: node.id || -1,
label: node.alias || node.name || '其他', label: node.alias || node.name || $t('other'),
title: node.alias || node.name || '其他', title: node.alias || node.name || $t('other'),
} }
} }
" "
appendToBody appendToBody
:zIndex="1050" :zIndex="1050"
> >
<div <div
:title="node.label" :title="node.label"
slot="option-label" slot="option-label"
slot-scope="{ node }" slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
> >
{{ node.label }} {{ node.label }}
</div> </div>
</treeselect> </treeselect>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24" v-if="choice_other.type_ids && choice_other.type_ids.length"> <a-col :span="24" v-if="choice_other.type_ids && choice_other.type_ids.length">
<a-form-item <a-form-item
:style="{ marginBottom: '5px' }" :style="{ marginBottom: '5px' }"
class="pre-value-filter" class="pre-value-filter"
label="筛选" :label="$t('cmdb.ciType.filter')"
:label-col="{ span: 2 }" :label-col="{ span: 2 }"
:wrapper-col="{ span: 22 }" :wrapper-col="{ span: 22 }"
> >
<FilterComp <FilterComp
ref="filterComp" ref="filterComp"
:isDropdown="false" :isDropdown="false"
:canSearchPreferenceAttrList="typeAttrs" :canSearchPreferenceAttrList="typeAttrs"
@setExpFromFilter="setExpFromFilter" @setExpFromFilter="setExpFromFilter"
:expression="filterExp ? `q=${filterExp}` : ''" :expression="filterExp ? `q=${filterExp}` : ''"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="script" :disabled="disabled || !canDefineScript"> <a-tab-pane key="script" :disabled="disabled || !canDefineScript">
<span style="font-size:12px;" slot="tab">脚本</span> <span style="font-size:12px;" slot="tab">{{ $t('cmdb.ciType.code') }}</span>
<CustomCodeMirror <CustomCodeMirror
codeMirrorId="cmdb-pre-value" codeMirrorId="cmdb-pre-value"
ref="codemirror" ref="codemirror"
@changeCodeContent="changeCodeContent" @changeCodeContent="changeCodeContent"
></CustomCodeMirror> ></CustomCodeMirror>
<!-- <codemirror style="z-index: 9999" :options="cmOptions" v-model="script"></codemirror> --> <!-- <codemirror style="z-index: 9999" :options="cmOptions" v-model="script"></codemirror> -->
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import PreValueTag from './preValueTag.vue' import PreValueTag from './preValueTag.vue'
import { defautValueColor } from '../../utils/const' import { defautValueColor } from '../../utils/const'
import ColorPicker from '../../components/colorPicker/index.vue' import ColorPicker from '../../components/colorPicker/index.vue'
import Webhook from '../../components/webhook' import Webhook from '../../components/webhook'
import { getCITypeGroups } from '../../api/ciTypeGroup' import { getCITypeGroups } from '../../api/ciTypeGroup'
import { getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr' import { getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr'
import FilterComp from '@/components/CMDBFilterComp' import FilterComp from '@/components/CMDBFilterComp'
import CustomCodeMirror from '@/components/CustomCodeMirror' import CustomCodeMirror from '@/components/CustomCodeMirror'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css' import 'codemirror/theme/monokai.css'
require('codemirror/mode/python/python.js') require('codemirror/mode/python/python.js')
export default { export default {
name: 'PreValueArea', name: 'PreValueArea',
components: { draggable, PreValueTag, ColorPicker, Webhook, FilterComp, CustomCodeMirror }, components: { draggable, PreValueTag, ColorPicker, Webhook, FilterComp, CustomCodeMirror },
props: { props: {
disabled: { disabled: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
canDefineScript: { canDefineScript: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
data() { data() {
return { return {
defautValueColor, defautValueColor,
activeKey: 'define', // define webhook activeKey: 'define', // define webhook
valueList: [], valueList: [],
form: { form: {
ret_key: '', ret_key: '',
}, },
choice_other: { choice_other: {
type_ids: undefined, type_ids: undefined,
attr_id: undefined, attr_id: undefined,
}, },
ciTypeGroup: [], ciTypeGroup: [],
typeAttrs: [], typeAttrs: [],
filterExp: '', filterExp: '',
script: script:
'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n 执行入口, 返回预定义值\n :return: 返回一个列表, 值的类型同属性的类型\n 例如:\n return ["在线", "下线"]\n """\n return []', this.$t('cmdb.ciType.choiceScriptDemo'),
cmOptions: { cmOptions: {
lineNumbers: true, lineNumbers: true,
mode: 'python', mode: 'python',
height: '200px', height: '200px',
theme: 'monokai', theme: 'monokai',
tabSize: 4, tabSize: 4,
lineWrapping: true, lineWrapping: true,
readOnly: this.disabled || !this.canDefineScript, readOnly: this.disabled || !this.canDefineScript,
}, },
} }
}, },
watch: { watch: {
disabled: { disabled: {
immediate: false, immediate: false,
handler(newValue) { handler(newValue) {
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar') const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (newValue) { if (newValue) {
// 如果是disabled 把tab 的ink-bar也置灰 // If it is disabled, the ink-bar of the tab will also be grayed out.
dom.style.backgroundColor = '#00000040' dom.style.backgroundColor = '#00000040'
} else { } else {
dom.style.backgroundColor = '#2f54eb' dom.style.backgroundColor = '#2f54eb'
} }
}, },
}, },
'choice_other.type_ids': { 'choice_other.type_ids': {
handler(newValue) { handler(newValue) {
if (newValue && newValue.length) { if (newValue && newValue.length) {
getCITypeCommonAttributesByTypeIds({ type_ids: newValue.join(',') }).then((res) => { getCITypeCommonAttributesByTypeIds({ type_ids: newValue.join(',') }).then((res) => {
this.typeAttrs = res.attributes this.typeAttrs = res.attributes
}) })
} }
}, },
}, },
}, },
created() { created() {
getCITypeGroups({ need_other: true }).then((res) => { getCITypeGroups({ need_other: true }).then((res) => {
this.ciTypeGroup = res this.ciTypeGroup = res
.filter((item) => item.ci_types && item.ci_types.length) .filter((item) => item.ci_types && item.ci_types.length)
.map((item) => { .map((item) => {
item.id = `parent_${item.id || -1}` item.id = `parent_${item.id || -1}`
return { ..._.cloneDeep(item) } return { ..._.cloneDeep(item) }
}) })
}) })
}, },
methods: { methods: {
addNewValue(newValue, newStyle, newIcon) { addNewValue(newValue, newStyle, newIcon) {
if (newValue) { if (newValue) {
const idx = this.valueList.findIndex((v) => v[0] === newValue) const idx = this.valueList.findIndex((v) => v[0] === newValue)
if (idx > -1) { if (idx > -1) {
this.$message.warning('当前值已存在!') this.$message.warning(this.$t('cmdb.ciType.valueExisted'))
} else { } else {
this.valueList.push([newValue, { style: newStyle, icon: { ...newIcon } }]) this.valueList.push([newValue, { style: newStyle, icon: { ...newIcon } }])
} }
} }
}, },
deleteValue(item) { deleteValue(item) {
const _valueList = _.cloneDeep(this.valueList) const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0]) const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) { if (idx > -1) {
_valueList.splice(idx, 1) _valueList.splice(idx, 1)
this.valueList = _valueList this.valueList = _valueList
} }
}, },
editValue(item, newValue, newStyle, newIcon) { editValue(item, newValue, newStyle, newIcon) {
const _valueList = _.cloneDeep(this.valueList) const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0]) const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) { if (idx > -1) {
_valueList[idx] = [newValue, { style: newStyle, icon: { ...newIcon } }] _valueList[idx] = [newValue, { style: newStyle, icon: { ...newIcon } }]
this.valueList = _valueList this.valueList = _valueList
} }
}, },
getData() { getData() {
if (this.activeKey === 'define') { if (this.activeKey === 'define') {
return { return {
choice_value: this.valueList, choice_value: this.valueList,
choice_web_hook: null, choice_web_hook: null,
choice_other: null, choice_other: null,
} }
} else if (this.activeKey === 'webhook') { } else if (this.activeKey === 'webhook') {
const choice_web_hook = this.$refs.webhook.getParams() const choice_web_hook = this.$refs.webhook.getParams()
choice_web_hook.ret_key = this.form.ret_key choice_web_hook.ret_key = this.form.ret_key
return { choice_value: [], choice_web_hook, choice_other: null } return { choice_value: [], choice_web_hook, choice_other: null }
} else if (this.activeKey === 'script') { } else if (this.activeKey === 'script') {
return { return {
choice_value: [], choice_value: [],
choice_web_hook: null, choice_web_hook: null,
choice_other: { choice_other: {
script: this.script, script: this.script,
}, },
} }
} else { } else {
let choice_other = {} let choice_other = {}
if (this.choice_other.type_ids && this.choice_other.type_ids.length) { if (this.choice_other.type_ids && this.choice_other.type_ids.length) {
this.$refs.filterComp.handleSubmit() this.$refs.filterComp.handleSubmit()
choice_other = { ...this.choice_other, filter: this.filterExp } choice_other = { ...this.choice_other, filter: this.filterExp }
} }
return { return {
choice_value: [], choice_value: [],
choice_web_hook: null, choice_web_hook: null,
choice_other, choice_other,
} }
} }
}, },
setData({ choice_value, choice_web_hook, choice_other }) { setData({ choice_value, choice_web_hook, choice_other }) {
if (choice_web_hook) { if (choice_web_hook) {
this.activeKey = 'webhook' this.activeKey = 'webhook'
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.webhook.setParams(choice_web_hook) this.$refs.webhook.setParams(choice_web_hook)
this.form.ret_key = choice_web_hook.ret_key ?? '' this.form.ret_key = choice_web_hook.ret_key ?? ''
}) })
} else if (choice_other) { } else if (choice_other) {
if (choice_other.script) { if (choice_other.script) {
this.activeKey = 'script' this.activeKey = 'script'
this.script = choice_other.script this.script = choice_other.script
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.codemirror.initCodeMirror(choice_other.script) this.$refs.codemirror.initCodeMirror(choice_other.script)
}) })
} else { } else {
this.activeKey = 'choice_other' this.activeKey = 'choice_other'
const { type_ids, attr_id, filter } = choice_other const { type_ids, attr_id, filter } = choice_other
this.choice_other = { type_ids, attr_id } this.choice_other = { type_ids, attr_id }
this.filterExp = filter this.filterExp = filter
if (type_ids && type_ids.length) { if (type_ids && type_ids.length) {
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.filterComp.visibleChange(true, false) this.$refs.filterComp.visibleChange(true, false)
}) })
} }
} }
} else { } else {
this.valueList = choice_value this.valueList = choice_value
this.activeKey = 'define' this.activeKey = 'define'
} }
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar') const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (this.disabled) { if (this.disabled) {
// 如果是disabled 把tab 的ink-bar也置灰 // If it is disabled, the ink-bar of the tab will also be grayed out.
dom.style.backgroundColor = '#00000040' dom.style.backgroundColor = '#00000040'
} else { } else {
dom.style.backgroundColor = '#2f54eb' dom.style.backgroundColor = '#2f54eb'
} }
}, },
setExpFromFilter(filterExp) { setExpFromFilter(filterExp) {
if (filterExp) { if (filterExp) {
this.filterExp = `${filterExp}` this.filterExp = `${filterExp}`
} else { } else {
this.filterExp = '' this.filterExp = ''
} }
}, },
changeCodeContent(value) { changeCodeContent(value) {
this.script = value && value.replace('\t', ' ') this.script = value && value.replace('\t', ' ')
}, },
changeActiveKey(value) { changeActiveKey(value) {
if (value === 'script') { if (value === 'script') {
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.codemirror.initCodeMirror(this.script) this.$refs.codemirror.initCodeMirror(this.script)
}) })
} }
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.pre-value-edit-color { .pre-value-edit-color {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
.pre-value-edit-color-item { .pre-value-edit-color-item {
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
width: 25px; width: 25px;
height: 20px; height: 20px;
margin: 5px; margin: 5px;
} }
} }
</style> </style>
<style lang="less"> <style lang="less">
.pre-value-filter { .pre-value-filter {
.ant-form-item-control { .ant-form-item-control {
line-height: 24px; line-height: 24px;
} }
.table-filter-add { .table-filter-add {
line-height: 40px; line-height: 40px;
} }
} }
</style> </style>

View File

@ -11,9 +11,9 @@
> >
</a-input> </a-input>
<div ref="preValueEdit" slot="content"> <div ref="preValueEdit" slot="content">
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">图标</a-divider> <a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('icon') }}</a-divider>
<IconArea ref="iconArea" /> <IconArea ref="iconArea" />
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">字体</a-divider> <a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('cmdb.ciType.font') }}</a-divider>
<div :style="{ display: 'flex', justifyContent: 'space-around' }"> <div :style="{ display: 'flex', justifyContent: 'space-around' }">
<div <div
@click="changeFontStyle('fontWeight', 'bold')" @click="changeFontStyle('fontWeight', 'bold')"
@ -37,7 +37,7 @@
</div> </div>
</div> </div>
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">颜色</a-divider> <a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('cmdb.ciType.color') }}</a-divider>
<div :style="{ display: 'flex', justifyContent: 'space-around' }"> <div :style="{ display: 'flex', justifyContent: 'space-around' }">
<div class="attributes-font-color"> <div class="attributes-font-color">
<a-icon type="font-colors" /><el-color-picker <a-icon type="font-colors" /><el-color-picker
@ -56,11 +56,11 @@
</el-color-picker> </el-color-picker>
</div> </div>
</div> </div>
<a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">操作</a-divider> <a-divider orientation="left" style="margin:8px 0;color:gray;font-size:10px;">{{ $t('operation') }}</a-divider>
<div style="text-align:right;"> <div style="text-align:right;">
<a-tooltip <a-tooltip
v-if="type !== 'add'" v-if="type !== 'add'"
title="删除" :title="$t('delete')"
><a ><a
><a-icon ><a-icon
@click="handleDelete" @click="handleDelete"
@ -69,11 +69,11 @@
type="delete"/></a type="delete"/></a
></a-tooltip> ></a-tooltip>
<a-tooltip <a-tooltip
title="确定" :title="$t('confirm')"
><a><a-icon @click="handleEditOk" style="margin-right:10px;color:green;" type="check"/></a ><a><a-icon @click="handleEditOk" style="margin-right:10px;color:green;" type="check"/></a
></a-tooltip> ></a-tooltip>
<a-tooltip <a-tooltip
title="取消" :title="$t('cancel')"
><a ><a
><a-icon ><a-icon
@click=" @click="
@ -137,6 +137,9 @@ import _ from 'lodash'
import { ColorPicker } from 'element-ui' import { ColorPicker } from 'element-ui'
import { defautValueColor, defaultBGColors } from '../../utils/const' import { defautValueColor, defaultBGColors } from '../../utils/const'
import IconArea from './iconArea.vue' import IconArea from './iconArea.vue'
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'
locale.use(lang)
export default { export default {
name: 'PreValueTag', name: 'PreValueTag',
components: { ElColorPicker: ColorPicker, IconArea }, components: { ElColorPicker: ColorPicker, IconArea },

View File

@ -10,7 +10,7 @@
searchable searchable
:options="ciTypeADTAttributes" :options="ciTypeADTAttributes"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="请选择属性" :placeholder="$t('cmdb.ciType.selectAttributes')"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
@ -35,15 +35,15 @@
searchable searchable
:options="ciTypeGroup" :options="ciTypeGroup"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="请选择模型" :placeholder="$t('cmdb.ciType.selectCIType')"
:disableBranchNodes="true" :disableBranchNodes="true"
@select="changeType(item)" @select="changeType(item)"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.name || '其他', id: node.name || $t('other'),
label: node.alias || node.name || '其他', label: node.alias || node.name || $t('other'),
title: node.alias || node.name || '其他', title: node.alias || node.name || $t('other'),
children: node.ci_types, children: node.ci_types,
} }
} }
@ -67,7 +67,7 @@
searchable searchable
:options="item.attributes" :options="item.attributes"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="请选择属性" :placeholder="$t('cmdb.ciType.selectAttributes')"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
@ -89,7 +89,7 @@
</treeselect> </treeselect>
</div> </div>
<div class="relation-ad-footer"> <div class="relation-ad-footer">
<a-button type="primary" @click="handleSave">保存</a-button> <a-button type="primary" @click="handleSave">{{ $t('save') }}</a-button>
</div> </div>
</div> </div>
</template> </template>
@ -148,7 +148,7 @@ export default {
}, },
getCITypeDiscovery() { getCITypeDiscovery() {
getCITypeDiscovery(this.CITypeId).then(async (res) => { getCITypeDiscovery(this.CITypeId).then(async (res) => {
// 第一个下拉框的options // Options for the first drop-down box
const _ciTypeADTAttributes = [] const _ciTypeADTAttributes = []
res res
.filter((adt) => adt.adr_id) .filter((adt) => adt.adr_id)
@ -160,7 +160,7 @@ export default {
}) })
console.log(_ciTypeADTAttributes) console.log(_ciTypeADTAttributes)
this.ciTypeADTAttributes = _.uniqBy(_ciTypeADTAttributes, 'name') this.ciTypeADTAttributes = _.uniqBy(_ciTypeADTAttributes, 'name')
// 第一个下拉框的options // Options for the first drop-down box
const _find = res.find((adt) => !adt.adr_id) const _find = res.find((adt) => !adt.adr_id)
if (_find) { if (_find) {
this.adt_id = _find.id this.adt_id = _find.id
@ -242,7 +242,7 @@ export default {
} else { } else {
await postCITypeDiscovery(this.CITypeId, { relation: _relation }) await postCITypeDiscovery(this.CITypeId, { relation: _relation })
} }
this.$message.success('保存成功') this.$message.success(this.$t('saveSuccess'))
this.getCITypeDiscovery() this.getCITypeDiscovery()
} }
}, },

View File

@ -7,7 +7,7 @@
size="small" size="small"
class="ops-button-primary" class="ops-button-primary"
icon="plus" icon="plus"
>新增关系</a-button >{{ $t('cmdb.ciType.addRelation') }}</a-button
> >
<vxe-table <vxe-table
stripe stripe
@ -20,15 +20,15 @@
class="ops-stripe-table" class="ops-stripe-table"
:row-class-name="rowClass" :row-class-name="rowClass"
> >
<vxe-column field="source_ci_type_name" title="源模型英文名"></vxe-column> <vxe-column field="source_ci_type_name" :title="$t('cmdb.ciType.sourceCIType')"></vxe-column>
<vxe-column field="relation_type" title="关联类型"> <vxe-column field="relation_type" :title="$t('cmdb.ciType.relationType')">
<template #default="{row}"> <template #default="{row}">
<span style="color:#2f54eb" v-if="row.isParent"></span> <span style="color:#2f54eb" v-if="row.isParent">{{ $t('cmdb.ciType.isParent') }}</span>
{{ row.relation_type }} {{ row.relation_type }}
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="alias" title="目标模型名"></vxe-column> <vxe-column field="alias" :title="$t('cmdb.ciType.dstCIType')"></vxe-column>
<vxe-column field="constraint" title="关系约束"> <vxe-column field="constraint" :title="$t('cmdb.ciType.relationConstraint')">
<template #default="{row}"> <template #default="{row}">
<span v-if="row.isParent && constraintMap[row.constraint]">{{ <span v-if="row.isParent && constraintMap[row.constraint]">{{
constraintMap[row.constraint] constraintMap[row.constraint]
@ -39,11 +39,11 @@
<span v-else>{{ constraintMap[row.constraint] }}</span> <span v-else>{{ constraintMap[row.constraint] }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operation" title="操作" width="100"> <vxe-column field="operation" :title="$t('operation')" width="100">
<template #default="{row}"> <template #default="{row}">
<a-space v-if="!row.isParent && row.source_ci_type_id"> <a-space v-if="!row.isParent && row.source_ci_type_id">
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a> <a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="handleDelete(row)">
<a style="color: red;"><a-icon type="delete"/></a> <a style="color: red;"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</a-space> </a-space>
@ -52,7 +52,7 @@
<template #empty> <template #empty>
<div> <div>
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
<div>暂无数据</div> <div>{{ $t('noData') }}</div>
</div> </div>
</template> </template>
</vxe-table> </vxe-table>
@ -65,21 +65,24 @@
width="500px" width="500px"
> >
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }"> <a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
<a-form-item label="源模型"> <a-form-item :label="$t('cmdb.ciType.sourceCIType')">
<a-select <a-select
name="source_ci_type_id" name="source_ci_type_id"
v-decorator="['source_ci_type_id', { rules: [{ required: true, message: '请选择源模型' }] }]" v-decorator="[
'source_ci_type_id',
{ rules: [{ required: true, message: $t('cmdb.ciType.sourceCITypeTips') }] },
]"
> >
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in displayCITypes">{{ <a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in displayCITypes">{{
CIType.alias CIType.alias
}}</a-select-option> }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="目标模型"> <a-form-item :label="$t('cmdb.ciType.dstCIType')">
<a-select <a-select
showSearch showSearch
name="ci_type_id" name="ci_type_id"
v-decorator="['ci_type_id', { rules: [{ required: true, message: '请选择目标模型' }] }]" v-decorator="['ci_type_id', { rules: [{ required: true, message: $t('cmdb.ciType.dstCITypeTips') }] }]"
:filterOption="filterOption" :filterOption="filterOption"
> >
<a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in CITypes"> <a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in CITypes">
@ -88,10 +91,13 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="关联关系"> <a-form-item :label="$t('cmdb.ciType.relationType')">
<a-select <a-select
name="relation_type_id" name="relation_type_id"
v-decorator="['relation_type_id', { rules: [{ required: true, message: '请选择关联关系' }] }]" v-decorator="[
'relation_type_id',
{ rules: [{ required: true, message: $t('cmdb.ciType.relationTypeTips') }] },
]"
> >
<a-select-option :value="relationType.id" :key="relationType.id" v-for="relationType in relationTypes">{{ <a-select-option :value="relationType.id" :key="relationType.id" v-for="relationType in relationTypes">{{
relationType.name relationType.name
@ -99,11 +105,16 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="关联约束"> <a-form-item :label="$t('cmdb.ciType.relationConstraint')">
<a-select v-decorator="['constraint', { rules: [{ required: true, message: '请选择关联约束' }] }]"> <a-select
<a-select-option value="0">一对多</a-select-option> v-decorator="[
<a-select-option value="1">一对一</a-select-option> 'constraint',
<a-select-option value="2">多对多</a-select-option> { rules: [{ required: true, message: $t('cmdb.ciType.relationConstraintTips') }] },
]"
>
<a-select-option value="0">{{ $t('cmdb.ciType.one2Many') }}</a-select-option>
<a-select-option value="1">{{ $t('cmdb.ciType.one2One') }}</a-select-option>
<a-select-option value="2">{{ $t('cmdb.ciType.many2Many') }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -145,11 +156,6 @@ export default {
drawerTitle: '', drawerTitle: '',
CITypes: [], CITypes: [],
relationTypes: [], relationTypes: [],
constraintMap: {
'0': '一对多',
'1': '一对一',
'2': '多对多',
},
tableData: [], tableData: [],
parentTableData: [], parentTableData: [],
} }
@ -161,6 +167,13 @@ export default {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
constraintMap() {
return {
'0': this.$t('cmdb.ciType.one2Many'),
'1': this.$t('cmdb.ciType.one2One'),
'2': this.$t('cmdb.ciType.many2Many'),
}
},
}, },
async mounted() { async mounted() {
this.getCITypes() this.getCITypes()
@ -209,7 +222,7 @@ export default {
}, },
handleDelete(record) { handleDelete(record) {
deleteRelation(record.source_ci_type_id, record.id).then((res) => { deleteRelation(record.source_ci_type_id, record.id).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
this.handleOk() this.handleOk()
}) })
}, },
@ -218,7 +231,7 @@ export default {
}, },
handleCreate() { handleCreate() {
this.drawerTitle = '新增关系' this.drawerTitle = this.$t('cmdb.ciType.addRelation')
this.visible = true this.visible = true
this.$nextTick(() => { this.$nextTick(() => {
this.form.setFieldsValue({ this.form.setFieldsValue({
@ -241,7 +254,7 @@ export default {
createRelation(values.source_ci_type_id, values.ci_type_id, values.relation_type_id, values.constraint).then( createRelation(values.source_ci_type_id, values.ci_type_id, values.relation_type_id, values.constraint).then(
(res) => { (res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.onClose() this.onClose()
this.handleOk() this.handleOk()
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,159 +1,139 @@
<template> <template>
<div class="ci-types-triggers"> <div class="ci-types-triggers">
<div style="margin-bottom: 10px"> <div style="margin-bottom: 10px">
<a-button <a-button
type="primary" type="primary"
@click="handleAddTrigger" @click="handleAddTrigger"
size="small" size="small"
class="ops-button-primary" class="ops-button-primary"
icon="plus" icon="plus"
>新增触发器</a-button >{{ $t('cmdb.ciType.newTrigger') }}</a-button
> >
</div> </div>
<vxe-table <ops-table
stripe stripe
:data="tableData" :data="tableData"
size="small" size="small"
show-overflow show-overflow
highlight-hover-row highlight-hover-row
keep-source keep-source
:max-height="windowHeight - 180" :max-height="windowHeight - 180"
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-column field="option.name" title="名称"></vxe-column> <vxe-column field="option.name" :title="$t('name')"></vxe-column>
<vxe-column field="option.description" title="备注"></vxe-column> <vxe-column field="option.description" :title="$t('desc')"></vxe-column>
<vxe-column field="type" title="类型"> <vxe-column field="type" :title="$t('type')">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.attr_id">日期属性</span> <span v-if="row.attr_id">{{ $t('cmdb.ciType.triggerDate') }}</span>
<span v-else>数据变更</span> <span v-else>{{ $t('cmdb.ciType.triggerDataChange') }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="option.enable" title="开启"> <vxe-column field="option.enable" :title="$t('cmdb.ciType.triggerEnable')">
<template #default="{ row }"> <template #default="{ row }">
<a-switch :checked="row.option.enable" @click="changeEnable(row)"></a-switch> <a-switch :checked="row.option.enable" @click="changeEnable(row)"></a-switch>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operation" :title="$t('operation')" width="100px" align="center">
<!-- <vxe-column field="attr_name" title="属性名"></vxe-column> <template #default="{ row }">
<vxe-column field="option.subject" title="主题"></vxe-column> <a-space>
<vxe-column field="option.body" title="内容"></vxe-column> <a @click="handleEdit(row)"><a-icon type="edit"/></a>
<vxe-column field="option.wx_to" title="微信通知"> <a style="color:red;" @click="handleDetele(row.id)"><a-icon type="delete"/></a>
<template #default="{ row }"> </a-space>
<span v-for="(person, index) in row.option.wx_to" :key="person + index">[{{ person }}]</span> </template>
</template> </vxe-column>
</vxe-column> </ops-table>
<vxe-column field="option.mail_to" title="邮件通知"> <TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
<template #default="{ row }"> </div>
<span v-for="(email, index) in row.option.mail_to" :key="email + index">[{{ email }}]</span> </template>
</template>
</vxe-column> <script>
<vxe-column field="option.before_days" title="提前"> import _ from 'lodash'
<template #default="{ row }"> import { getTriggerList, deleteTrigger, updateTrigger } from '../../api/CIType'
<span v-if="row.option.before_days">{{ row.option.before_days }}</span> import { getCITypeAttributesById } from '../../api/CITypeAttr'
</template> import TriggerForm from './triggerForm.vue'
</vxe-column>
<vxe-column field="option.notify_at" title="发送时间"></vxe-column> --> export default {
<vxe-column field="operation" title="操作" width="80px" align="center"> name: 'TriggerTable',
<template #default="{ row }"> components: { TriggerForm },
<a-space> props: {
<a @click="handleEdit(row)"><a-icon type="edit"/></a> CITypeId: {
<a style="color:red;" @click="handleDetele(row.id)"><a-icon type="delete"/></a> type: Number,
</a-space> default: null,
</template> },
</vxe-column> },
</vxe-table> data() {
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" /> return {
</div> tableData: [],
</template> attrList: [],
}
<script> },
import _ from 'lodash' computed: {
import { getTriggerList, deleteTrigger, updateTrigger } from '../../api/CIType' windowHeight() {
import { getCITypeAttributesById } from '../../api/CITypeAttr' return this.$store.state.windowHeight
import TriggerForm from './triggerForm.vue' },
},
export default { provide() {
name: 'TriggerTable', return {
components: { TriggerForm }, refresh: this.getTableData,
props: { }
CITypeId: { },
type: Number, methods: {
default: null, async getTableData() {
}, const [triggerList, attrList] = await Promise.all([
}, getTriggerList(this.CITypeId),
data() { getCITypeAttributesById(this.CITypeId),
return { ])
tableData: [], triggerList.forEach((trigger) => {
attrList: [], const _find = attrList.attributes.find((attr) => attr.id === trigger.attr_id)
} if (_find) {
}, trigger.attr_name = _find.alias || _find.name
computed: { }
windowHeight() { })
return this.$store.state.windowHeight this.tableData = triggerList
}, this.attrList = attrList.attributes
}, },
provide() { handleAddTrigger() {
return { this.$refs.triggerForm.createFromTriggerTable(this.attrList)
refresh: this.getTableData, },
} handleDetele(id) {
}, const that = this
methods: { this.$confirm({
async getTableData() { title: that.$t('warning'),
const [triggerList, attrList] = await Promise.all([ content: that.$t('cmdb.ciType.confirmDeleteTrigger'),
getTriggerList(this.CITypeId), onOk() {
getCITypeAttributesById(this.CITypeId), deleteTrigger(that.CITypeId, id).then(() => {
]) that.$message.success(that.$t('deleteSuccess'))
triggerList.forEach((trigger) => { that.getTableData()
const _find = attrList.attributes.find((attr) => attr.id === trigger.attr_id) })
if (_find) { },
trigger.attr_name = _find.alias || _find.name })
} },
}) handleEdit(row) {
this.tableData = triggerList this.$refs.triggerForm.open(
this.attrList = attrList.attributes {
}, id: row.attr_id,
handleAddTrigger() { alias: row?.option?.name ?? '',
this.$refs.triggerForm.createFromTriggerTable(this.attrList) trigger: { id: row.id, attr_id: row.attr_id, option: row.option },
}, has_trigger: true,
handleDetele(id) { },
const that = this this.attrList
this.$confirm({ )
title: '警告', },
content: '确认删除该触发器吗?', changeEnable(row) {
onOk() { const _row = _.cloneDeep(row)
deleteTrigger(that.CITypeId, id).then(() => { delete _row.id
that.$message.success('删除成功!') const enable = row?.option?.enable ?? true
that.getTableData() _row.option.enable = !enable
}) updateTrigger(this.CITypeId, row.id, _row).then(() => {
}, this.getTableData()
}) })
}, },
handleEdit(row) { },
this.$refs.triggerForm.open( }
{ </script>
id: row.attr_id,
alias: row?.option?.name ?? '', <style lang="less" scoped>
trigger: { id: row.id, attr_id: row.attr_id, option: row.option }, .ci-types-triggers {
has_trigger: true, padding: 16px 24px 24px;
}, }
this.attrList </style>
)
},
changeEnable(row) {
const _row = _.cloneDeep(row)
delete _row.id
const enable = row?.option?.enable ?? true
_row.option.enable = !enable
updateTrigger(this.CITypeId, row.id, _row).then(() => {
this.getTableData()
})
},
},
}
</script>
<style lang="less" scoped>
.ci-types-triggers {
padding: 16px 24px 24px;
}
</style>

View File

@ -7,9 +7,9 @@
icon="plus" icon="plus"
size="small" size="small"
:style="{ marginBottom: '10px' }" :style="{ marginBottom: '10px' }"
>新增</a-button >{{ $t('new') }}</a-button
> >
<vxe-table <ops-table
:loading="loading" :loading="loading"
:data="tableData" :data="tableData"
:edit-config="{ trigger: 'manual', mode: 'row', showIcon: false, autoClear: false, showStatus: true }" :edit-config="{ trigger: 'manual', mode: 'row', showIcon: false, autoClear: false, showStatus: true }"
@ -18,8 +18,10 @@
ref="xTable" ref="xTable"
size="mini" size="mini"
keep-source keep-source
stripe
class="ops-stripe-table"
> >
<vxe-column field="attr_ids" title="属性" :edit-render="{}"> <vxe-column field="attr_ids" :title="$t('cmdb.ciType.attributes')" :edit-render="{}">
<template #default="{ row }"> <template #default="{ row }">
<template v-for="(attr, index) in row.attr_ids"> <template v-for="(attr, index) in row.attr_ids">
<span :key="attr" :style="{ color: '#2f54eb' }">{{ getDisplayName(attr) }}</span> <span :key="attr" :style="{ color: '#2f54eb' }">{{ getDisplayName(attr) }}</span>
@ -37,9 +39,9 @@
</vxe-select> </vxe-select>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column filed="opeartion" title="操作" width="100"> <vxe-column filed="opeartion" :title="$t('operation')" width="100">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="$refs.xTable.isActiveByRow(row)"> <template v-if="$refs.xTable.getVxetableRef().isActiveByRow(row)">
<a-space> <a-space>
<a> <a-icon @click="saveRowEvent(row)" type="save"/></a> <a> <a-icon @click="saveRowEvent(row)" type="save"/></a>
</a-space> </a-space>
@ -47,14 +49,14 @@
<template v-else> <template v-else>
<a-space> <a-space>
<a> <a-icon @click="editRowEvent(row)" type="edit"/></a> <a> <a-icon @click="editRowEvent(row)" type="edit"/></a>
<a-popconfirm title="确认删除?" @confirm="removeRowEvent(row)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="removeRowEvent(row)">
<a :style="{ color: 'red' }"> <a-icon type="delete"/></a> <a :style="{ color: 'red' }"> <a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</a-space> </a-space>
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </ops-table>
</a-modal> </a-modal>
</template> </template>
@ -98,7 +100,7 @@ export default {
}) })
}, },
async handleAddUnique(row) { async handleAddUnique(row) {
const $table = this.$refs.xTable const $table = this.$refs.xTable.getVxetableRef()
const record = { const record = {
attr_ids: [], attr_ids: [],
} }
@ -106,7 +108,7 @@ export default {
await $table.setActiveRow(newRow) await $table.setActiveRow(newRow)
}, },
saveRowEvent(row) { saveRowEvent(row) {
const $table = this.$refs.xTable const $table = this.$refs.xTable.getVxetableRef()
$table.clearActived().then(() => { $table.clearActived().then(() => {
if (row.id) { if (row.id) {
updateUniqueConstraint(this.CITypeId, row.id, { attr_ids: row.attr_ids }).then((res) => { updateUniqueConstraint(this.CITypeId, row.id, { attr_ids: row.attr_ids }).then((res) => {
@ -120,12 +122,12 @@ export default {
}) })
}, },
editRowEvent(row) { editRowEvent(row) {
const $table = this.$refs.xTable const $table = this.$refs.xTable.getVxetableRef()
$table.setActiveRow(row) $table.setActiveRow(row)
}, },
removeRowEvent(row) { removeRowEvent(row) {
deleteUniqueConstraint(this.CITypeId, row.id).then((res) => { deleteUniqueConstraint(this.CITypeId, row.id).then((res) => {
this.$message.success('删除成功!') this.$message.success(this.$t('deleteSuccess'))
this.getTableList() this.getTableList()
}) })
}, },

View File

@ -53,7 +53,7 @@
<span>{{ row[`key${index}`] }}</span> <span>{{ row[`key${index}`] }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="value" title="数量"></vxe-column> <vxe-column field="value" :title="$t('cmdb.custom_dashboard.quantity')"></vxe-column>
</template> </template>
</vxe-table> </vxe-table>
<div <div

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
import i18n from '@/lang'
export const category_1_bar_options = (data, options) => { export const category_1_bar_options = (data, options) => {
// 计算一级分类 // Calculate first level classification
const xData = Object.keys(data) const xData = Object.keys(data)
// 计算共有多少二级分类 // Calculate how many secondary categories there are
const secondCategory = {} const secondCategory = {}
Object.keys(data).forEach(key => { Object.keys(data).forEach(key => {
if (Object.prototype.toString.call(data[key]) === '[object Object]') { if (Object.prototype.toString.call(data[key]) === '[object Object]') {
@ -9,7 +11,7 @@ export const category_1_bar_options = (data, options) => {
secondCategory[key1] = Array.from({ length: xData.length }).fill(0) secondCategory[key1] = Array.from({ length: xData.length }).fill(0)
}) })
} else { } else {
secondCategory['其他'] = Array.from({ length: xData.length }).fill(0) secondCategory[i18n.t('other')] = Array.from({ length: xData.length }).fill(0)
} }
}) })
Object.keys(secondCategory).forEach(key => { Object.keys(secondCategory).forEach(key => {
@ -18,7 +20,7 @@ export const category_1_bar_options = (data, options) => {
secondCategory[key][idx] = data[x][key] secondCategory[key][idx] = data[x][key]
} }
if (typeof data[x] === 'number') { if (typeof data[x] === 'number') {
secondCategory['其他'][idx] = data[x] secondCategory[i18n.t('other')][idx] = data[x]
} }
}) })
}) })
@ -121,7 +123,7 @@ export const category_1_line_options = (data, options) => {
}, { }, {
offset: 1, color: '#ffffff' // 100% 处的颜色 offset: 1, color: '#ffffff' // 100% 处的颜色
}], }],
global: false // 缺省为 false global: false // default is false
} }
} : null } : null
} }

View File

@ -1,4 +1,8 @@
export const dashboardCategory = { import i18n from '@/lang'
1: { label: '默认' },
2: { label: '关系' } export const dashboardCategory = () => {
return {
1: { label: i18n.t('cmdb.custom_dashboard.default') },
2: { label: i18n.t('cmdb.custom_dashboard.relation') }
}
} }

View File

@ -16,7 +16,7 @@
type="primary" type="primary"
icon="plus-circle" icon="plus-circle"
class="ops-button-primary" class="ops-button-primary"
>新增图表</a-button >{{ $t('cmdb.custom_dashboard.newChart') }}</a-button
> >
</div> </div>
<GridLayout <GridLayout
@ -78,10 +78,10 @@
></a> ></a>
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item> <a-menu-item>
<a @click="() => openChartForm('edit', item)"><a-icon style="margin-right:5px" type="edit" />编辑</a> <a @click="() => openChartForm('edit', item)"><a-icon style="margin-right:5px" type="edit" />{{ $t('edit') }}</a>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<a @click="deleteChart(item)"><a-icon style="margin-right:5px" type="delete" />删除</a> <a @click="deleteChart(item)"><a-icon style="margin-right:5px" type="delete" />{{ $t('delete') }}</a>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</a-dropdown> </a-dropdown>
@ -116,7 +116,7 @@
> >
定制仪表盘 定制仪表盘
</a-button> </a-button>
<span v-else>管理员暂未定制仪表盘</span> <span v-else>{{ $t('cmdb.custom_dashboard.noCustomDashboard') }}</span>
</div> </div>
<ChartForm ref="chartForm" @refresh="refresh" :ci_types="ci_types" :totalData="totalData" /> <ChartForm ref="chartForm" @refresh="refresh" :ci_types="ci_types" :totalData="totalData" />
</div> </div>

View File

@ -32,7 +32,7 @@
/></a> /></a>
</a-space> </a-space>
<a v-else @click="handleEdit"><a-icon type="eye"/></a> <a v-else @click="handleEdit"><a-icon type="eye"/></a>
<span>{{ rule.is_plugin ? 'Plugin' : '默认' }}</span> <span>{{ rule.is_plugin ? 'Plugin' : $t('cmdb.custom_dashboard.default') }}</span>
</div> </div>
</template> </template>
</div> </div>
@ -57,7 +57,7 @@ export default {
return this.rule?.option?.icon ?? { color: '', name: 'caise-wuliji' } return this.rule?.option?.icon ?? { color: '', name: 'caise-wuliji' }
}, },
isDeletable() { isDeletable() {
return !['物理机', '虚拟机', '网卡', '硬盘', 'server', 'vserver', 'NIC', 'harddisk'].includes(this.rule.name) 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)
}, },
}, },
inject: { inject: {

View File

@ -1,344 +1,298 @@
<template> <template>
<CustomDrawer width="800px" :title="title" :visible="visible" @close="handleClose"> <CustomDrawer width="800px" :title="title" :visible="visible" @close="handleClose">
<template v-if="adType === 'agent'"> <template v-if="adType === 'agent'">
<a-form-model <a-form-model
ref="autoDiscoveryForm" ref="autoDiscoveryForm"
:model="form" :model="form"
:rules="rules" :rules="rules"
:label-col="{ span: 2 }" :label-col="{ span: 2 }"
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 20 }"
> >
<a-divider :style="{ margin: '5px 0' }">基础设置</a-divider> <a-divider :style="{ margin: '5px 0' }">{{ $t('cmdb.ciType.basicConfig') }}</a-divider>
<a-form-model-item label="名称" prop="name"> <a-form-model-item :label="$t('name')" prop="name">
<a-input v-model="form.name" /> <a-input v-model="form.name" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="图标" v-if="is_inner"> <a-form-model-item :label="$t('icon')" v-if="is_inner">
<CustomIconSelect v-model="customIcon" :style="{ marginTop: '6px' }" /> <CustomIconSelect v-model="customIcon" :style="{ marginTop: '6px' }" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="模式" prop="is_plugin"> <a-form-model-item :label="$t('cmdb.ad.mode')" prop="is_plugin">
<a-radio-group v-model="form.is_plugin" @change="changeIsPlugin" :disabled="!is_inner"> <a-radio-group v-model="form.is_plugin" @change="changeIsPlugin" :disabled="!is_inner">
<a-radio :value="false">默认</a-radio> <a-radio :value="false">{{ $t('cmdb.custom_dashboard.default') }}</a-radio>
<a-radio :value="true">plugin</a-radio> <a-radio :value="true">plugin</a-radio>
</a-radio-group> </a-radio-group>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
<a-divider :style="{ margin: '5px 0' }">采集设置</a-divider> <a-divider :style="{ margin: '5px 0' }">{{ $t('cmdb.ad.collectSettings') }}</a-divider>
<CustomCodeMirror <CustomCodeMirror
codeMirrorId="cmdb-adt" codeMirrorId="cmdb-adt"
v-if="form.is_plugin" v-if="form.is_plugin"
ref="codemirror" ref="codemirror"
@changeCodeContent="changeCodeContent" @changeCodeContent="changeCodeContent"
></CustomCodeMirror> ></CustomCodeMirror>
<div style="margin:10px 0;text-align:right;"> <div style="margin:10px 0;text-align:right;">
<a-button <a-button
v-show="form.is_plugin" v-show="form.is_plugin"
size="small" size="small"
type="primary" type="primary"
ghost ghost
@click="handleSubmit(true)" @click="handleSubmit(true)"
>更新字段</a-button >{{ $t('cmdb.ad.updateFields') }}</a-button
> >
</div> </div>
<a-button <a-button
v-show="!form.is_plugin" v-show="!form.is_plugin"
size="small" size="small"
type="primary" type="primary"
ghost ghost
icon="plus" icon="plus"
:style="{ marginBottom: '10px' }" :style="{ marginBottom: '10px' }"
@click="insertEvent(-1)" @click="insertEvent(-1)"
>新增</a-button >{{ $t('new') }}</a-button
> >
<vxe-table <vxe-table
size="mini" size="mini"
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
show-overflow show-overflow
keep-source keep-source
ref="xTable" ref="xTable"
max-height="400" max-height="400"
:data="tableData" :data="tableData"
:edit-config="{ trigger: 'manual', mode: 'row' }" :edit-config="{ trigger: 'manual', mode: 'row' }"
> >
<vxe-column field="name" title="名称" :edit-render="{ autofocus: '.vxe-input--inner' }"> <vxe-column field="name" :title="$t('name')" :edit-render="{ autofocus: '.vxe-input--inner' }">
<template #edit="{ row }"> <template #edit="{ row }">
<vxe-input v-model="row.name" type="text"></vxe-input> <vxe-input v-model="row.name" type="text"></vxe-input>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="type" title="类型" :edit-render="{}"> <vxe-column field="type" :title="$t('type')" :edit-render="{}">
<template #edit="{ row }"> <template #edit="{ row }">
<vxe-select v-model="row.type" transfer> <vxe-select v-model="row.type" transfer>
<vxe-option v-for="item in typeList" :key="item" :value="item" :label="item"></vxe-option> <vxe-option v-for="item in typeList" :key="item" :value="item" :label="item"></vxe-option>
</vxe-select> </vxe-select>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="desc" title="描述" :edit-render="{ autofocus: '.vxe-input--inner' }"> <vxe-column field="desc" :title="$t('desc')" :edit-render="{ autofocus: '.vxe-input--inner' }">
<template #edit="{ row }"> <template #edit="{ row }">
<vxe-input v-model="row.desc" type="text"></vxe-input> <vxe-input v-model="row.desc" type="text"></vxe-input>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="操作" width="60" v-if="!form.is_plugin"> <vxe-column :title="$t('operation')" width="60" v-if="!form.is_plugin">
<template #default="{ row }"> <template #default="{ row }">
<a-space v-if="$refs.xTable.isActiveByRow(row)"> <a-space v-if="$refs.xTable.isActiveByRow(row)">
<a @click="saveRowEvent(row)"><a-icon type="save"/></a> <a @click="saveRowEvent(row)"><a-icon type="save"/></a>
<a @click="cancelRowEvent(row)"><a-icon type="close"/></a> <a @click="cancelRowEvent(row)"><a-icon type="close"/></a>
</a-space> </a-space>
<a-space v-else> <a-space v-else>
<a @click="editRowEvent(row)"><a-icon type="edit"/></a> <a @click="editRowEvent(row)"><a-icon type="edit"/></a>
<a :style="{ color: 'red' }" @click="deleteRowEvent(row)"><a-icon type="delete"/></a> <a :style="{ color: 'red' }" @click="deleteRowEvent(row)"><a-icon type="delete"/></a>
</a-space> </a-space>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleClose">取消</a-button> <a-button @click="handleClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit(false)" type="primary">保存</a-button> <a-button @click="handleSubmit(false)" type="primary">{{ $t('save') }}</a-button>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleData.name" /> <HttpSnmpAD ref="httpSnmpAd" :ruleType="adType" :ruleName="ruleData.name" />
</template> </template>
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
import CustomIconSelect from '@/components/CustomIconSelect' import CustomIconSelect from '@/components/CustomIconSelect'
import { postDiscovery, putDiscovery } from '../../api/discovery' import { postDiscovery, putDiscovery } from '../../api/discovery'
import HttpSnmpAD from '../../components/httpSnmpAD' import HttpSnmpAD from '../../components/httpSnmpAD'
import CustomCodeMirror from '@/components/CustomCodeMirror' import CustomCodeMirror from '@/components/CustomCodeMirror'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/monokai.css' import 'codemirror/theme/monokai.css'
export default { export default {
name: 'EditDrawer', name: 'EditDrawer',
components: { CustomIconSelect, CustomCodeMirror, HttpSnmpAD }, components: { CustomIconSelect, CustomCodeMirror, HttpSnmpAD },
props: { props: {
is_inner: { is_inner: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
}, },
data() { data() {
const default_plugin_script = `# -*- coding:utf-8 -*- const default_plugin_script = this.$t('cmdb.ad.pluginScript')
const typeList = ['String', 'Integer', 'Float', 'Date', 'DateTime', 'Time', 'JSON']
import json return {
default_plugin_script,
typeList,
class AutoDiscovery(object): visible: false,
ruleData: {},
@property type: 'add',
def unique_key(self): adType: '',
""" form: { name: '', is_plugin: false },
rules: {},
:return: 返回唯一属性的名字 customIcon: { name: '', color: '' },
""" tableData: [],
return editDefaultTableData: [],
plugin_script: '',
@staticmethod cmOptions: {
def attributes(): lineNumbers: true,
""" mode: 'python',
定义属性字段 height: '200px',
:return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文 theme: 'monokai',
类型: String Integer Float Date DateTime Time JSON tabSize: 4,
例如: lineWrapping: true,
return [ },
("ci_type", "String", "模型名称"), }
("private_ip", "String", "内网IP, 多值逗号分隔") },
] computed: {
""" title() {
return [] if (this.adType === 'http' || this.adType === 'snmp') {
return this.ruleData.name
@staticmethod }
def run(): if (this.type === 'edit') {
""" return this.$t('edit') + `${this.ruleData.name}`
执行入口, 返回采集的属性值 }
:return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值 return this.$t('new')
例如: },
return [dict(ci_type="server", private_ip="192.168.1.1")] },
""" inject: {
return [] getDiscovery: {
from: 'getDiscovery',
default: () => {},
if __name__ == "__main__": },
result = AutoDiscovery().run() },
if isinstance(result, list): methods: {
print("AutoDiscovery::Result::{}".format(json.dumps(result))) open(data, type, adType) {
else: this.visible = true
print("ERROR: 采集返回必须是列表") this.type = type
` this.ruleData = data
const typeList = ['String', 'Integer', 'Float', 'Date', 'DateTime', 'Time', 'JSON'] this.adType = adType
return { if (!this.is_inner) {
default_plugin_script, this.form = {
typeList, name: '',
visible: false, is_plugin: true,
ruleData: {}, }
type: 'add', }
adType: '', if (adType === 'http' || adType === 'snmp') {
form: { name: '', is_plugin: false }, return
rules: {}, }
customIcon: { name: '', color: '' }, this.$nextTick(() => {
tableData: [], if (this.type === 'edit') {
editDefaultTableData: [], this.form = {
plugin_script: '', name: data.name,
cmOptions: { is_plugin: data.is_plugin,
lineNumbers: true, }
mode: 'python', this.customIcon = data?.option?.icon ?? { name: 'caise-chajian', color: '' }
height: '200px', this.tableData = data?.attributes ?? []
theme: 'monokai', this.editDefaultTableData = data?.attributes ?? []
tabSize: 4, this.plugin_script = data?.plugin_script ?? this.default_plugin_script
lineWrapping: true, }
}, if (this.type === 'add') {
} this.customIcon = { name: 'caise-chajian', color: '' }
}, // eslint-disable-next-line no-useless-escape
computed: { this.plugin_script = this.default_plugin_script
title() { }
if (this.adType === 'http' || this.adType === 'snmp') { if (data?.is_plugin || !this.is_inner) {
return this.ruleData.name this.$nextTick(() => {
} this.$refs.codemirror.initCodeMirror(this.plugin_script)
if (this.type === 'edit') { })
return `编辑${this.ruleData.name}` }
} })
return '新建' },
}, handleClose() {
}, this.tableData = []
inject: { this.customIcon = { name: '', color: '' }
getDiscovery: { this.form = { name: '', is_plugin: false }
from: 'getDiscovery', if (this.adType === 'agent') {
default: () => {}, this.$refs.autoDiscoveryForm.clearValidate()
}, } else {
}, // this.$refs.httpSnmpAd.currentCate = ''
methods: { }
open(data, type, adType) { this.visible = false
this.visible = true },
this.type = type async insertEvent(row) {
this.ruleData = data const $table = this.$refs.xTable
this.adType = adType const record = {}
if (!this.is_inner) { const { row: newRow } = await $table.insertAt(record, row)
this.form = { await $table.setEditRow(newRow)
name: '', },
is_plugin: true, editRowEvent(row) {
} const $table = this.$refs.xTable
} $table.setActiveRow(row)
if (adType === 'http' || adType === 'snmp') { },
return saveRowEvent() {
} const $table = this.$refs.xTable
this.$nextTick(() => { $table.clearActived().then(() => {
if (this.type === 'edit') { this.loading = true
this.form = { setTimeout(() => {
name: data.name, this.loading = false
is_plugin: data.is_plugin, }, 300)
} })
this.customIcon = data?.option?.icon ?? { name: 'caise-chajian', color: '' } },
this.tableData = data?.attributes ?? [] cancelRowEvent(row) {
this.editDefaultTableData = data?.attributes ?? [] const $table = this.$refs.xTable
this.plugin_script = data?.plugin_script ?? this.default_plugin_script $table.clearActived().then(() => {
} // Restore row data
if (this.type === 'add') { $table.revertData(row)
this.customIcon = { name: 'caise-chajian', color: '' } })
// eslint-disable-next-line no-useless-escape },
this.plugin_script = this.default_plugin_script deleteRowEvent(row) {
} const $table = this.$refs.xTable
if (data?.is_plugin || !this.is_inner) { $table.remove(row)
this.$nextTick(() => { },
this.$refs.codemirror.initCodeMirror(this.plugin_script) async handleSubmit(isUpdateAttr = false) {
}) const $table = this.$refs.xTable
} const { fullData: _tableData } = $table.getTableData()
}) console.log(_tableData)
}, const params = {
handleClose() { ...this.form,
this.tableData = [] type: this.adType,
this.customIcon = { name: '', color: '' } is_inner: this.is_inner,
this.form = { name: '', is_plugin: false } option: { icon: this.customIcon },
if (this.adType === 'agent') { attributes: this.form.is_plugin
this.$refs.autoDiscoveryForm.clearValidate() ? undefined
} else { : _tableData.map(({ name, alias, desc, type }) => {
// this.$refs.httpSnmpAd.currentCate = '' return { name, alias, desc, type }
} }),
this.visible = false plugin_script: this.form.is_plugin ? this.plugin_script : undefined,
}, }
async insertEvent(row) { let res
const $table = this.$refs.xTable if (this.type === 'add') {
const record = {} res = await postDiscovery(params)
const { row: newRow } = await $table.insertAt(record, row) } else {
await $table.setEditRow(newRow) res = await putDiscovery(this.ruleData.id, params)
}, }
editRowEvent(row) { if (isUpdateAttr) {
const $table = this.$refs.xTable this.tableData = res.attributes
$table.setActiveRow(row) this.type = 'edit'
}, this.ruleData = res
saveRowEvent() { this.$message.success(this.$t('updateSuccess'))
const $table = this.$refs.xTable if (this.is_inner) {
$table.clearActived().then(() => { this.getDiscovery()
this.loading = true }
setTimeout(() => { return
this.loading = false }
}, 300) this.handleClose()
}) console.log(this.is_inner)
}, if (this.is_inner) {
cancelRowEvent(row) { this.$message.success(this.$t('saveSuccess'))
const $table = this.$refs.xTable this.getDiscovery()
$table.clearActived().then(() => { } else {
// 还原行数据 this.$emit('updateNotInner', res)
$table.revertData(row) }
}) },
}, changeIsPlugin(e) {
deleteRowEvent(row) { if (e.target.value) {
const $table = this.$refs.xTable this.$nextTick(() => {
$table.remove(row) this.$refs.codemirror.initCodeMirror(this.plugin_script)
}, })
async handleSubmit(isUpdateAttr = false) { }
const $table = this.$refs.xTable },
const { fullData: _tableData } = $table.getTableData() changeCodeContent(value) {
console.log(_tableData) this.plugin_script = value && value.replace('\t', ' ')
const params = { },
...this.form, },
type: this.adType, }
is_inner: this.is_inner, </script>
option: { icon: this.customIcon },
attributes: this.form.is_plugin <style></style>
? undefined
: _tableData.map(({ name, alias, desc, type }) => {
return { name, alias, desc, type }
}),
plugin_script: this.form.is_plugin ? this.plugin_script : undefined,
}
let res
if (this.type === 'add') {
res = await postDiscovery(params)
} else {
res = await putDiscovery(this.ruleData.id, params)
}
if (isUpdateAttr) {
this.tableData = res.attributes
this.type = 'edit'
this.ruleData = res
this.$message.success('更新成功!')
if (this.is_inner) {
this.getDiscovery()
}
return
}
this.handleClose()
console.log(this.is_inner)
if (this.is_inner) {
this.$message.success('保存成功!')
this.getDiscovery()
} else {
this.$emit('updateNotInner', res)
}
},
changeIsPlugin(e) {
if (e.target.value) {
this.$nextTick(() => {
this.$refs.codemirror.initCodeMirror(this.plugin_script)
})
}
},
changeCodeContent(value) {
this.plugin_script = value && value.replace('\t', ' ')
},
},
}
</script>
<style></style>

View File

@ -3,9 +3,9 @@
<div :style="{ textAlign: 'right' }"> <div :style="{ textAlign: 'right' }">
<a-space v-if="!isSelected"> <a-space v-if="!isSelected">
<a-upload name="file" :multiple="false" accept=".json" :fileList="[]" :beforeUpload="beforeUpload"> <a-upload name="file" :multiple="false" accept=".json" :fileList="[]" :beforeUpload="beforeUpload">
<a><a-icon type="upload" />规则导入</a> <a><a-icon type="upload" />{{ $t('cmdb.ad.upload') }}</a>
</a-upload> </a-upload>
<a @click="download"><a-icon type="download" />规则导出</a> <a @click="download"><a-icon type="download" />{{ $t('cmdb.ad.download') }}</a>
</a-space> </a-space>
</div> </div>
<div v-for="{ type, label } in typeCategory" :key="type"> <div v-for="{ type, label } in typeCategory" :key="type">
@ -45,22 +45,26 @@ export default {
}, },
data() { data() {
return { return {
typeCategory: [ typeCategoryChildren: { agent: [], snmp: [], http: [] },
}
},
computed: {
typeCategory() {
return [
{ {
type: 'agent', type: 'agent',
label: '内置 & 插件', label: this.$t('cmdb.ad.agent'),
}, },
{ {
type: 'snmp', type: 'snmp',
label: '网络设备', label: this.$t('cmdb.ad.snmp'),
}, },
{ {
type: 'http', type: 'http',
label: '公有云资源', label: this.$t('cmdb.ad.http'),
}, },
], ]
typeCategoryChildren: { agent: [], snmp: [], http: [] }, },
}
}, },
provide() { provide() {
return { return {
@ -87,11 +91,11 @@ export default {
deleteRule(rule) { deleteRule(rule) {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: `确认删除 ${rule.name}`, content: that.$t('confirmDelete', { name: `${rule.name}` }),
onOk() { onOk() {
deleteDiscovery(rule.id).then(() => { deleteDiscovery(rule.id).then(() => {
that.$message.success('删除成功!') that.$message.success(that.$t('deleteSuccess'))
that.getDiscovery() that.getDiscovery()
}) })
}, },
@ -99,13 +103,14 @@ export default {
}, },
download() { download() {
const x = new XMLHttpRequest() const x = new XMLHttpRequest()
const that = this
x.open('GET', `/api/v0.1/adr/template/export/file`, true) x.open('GET', `/api/v0.1/adr/template/export/file`, true)
x.responseType = 'blob' x.responseType = 'blob'
x.onload = function(e) { x.onload = function(e) {
const url = window.URL.createObjectURL(x.response) const url = window.URL.createObjectURL(x.response)
const a = document.createElement('a') const a = document.createElement('a')
a.href = url a.href = url
a.download = `自动发现规则` a.download = that.$t('cmdb.ad.rule')
a.click() a.click()
} }
x.send() x.send()
@ -120,13 +125,13 @@ export default {
const state = Number(xhr.readyState) const state = Number(xhr.readyState)
if (state === 4) { if (state === 4) {
if (xhr.status === 200) { if (xhr.status === 200) {
that.$message.success('上传成功') that.$message.success(that.$t('uploadSuccess'))
that.getDiscovery() that.getDiscovery()
} }
} }
} }
xhr.ontimeout = function() { xhr.ontimeout = function() {
that.$httpError('超时错误') that.$httpError(that.$t('cmdb.ad.timeout'))
} }
xhr.send(formData) xhr.send(formData)

View File

@ -3,7 +3,7 @@
<template #one> <template #one>
<div v-for="group in ci_types_list" :key="group.id"> <div v-for="group in ci_types_list" :key="group.id">
<div> <div>
<strong>{{ group.name || '其他' }}</strong <strong>{{ group.name || $t('other') }}</strong
><span :style="{ color: 'rgb(195, 205, 215)' }">({{ group.ci_types.length }})</span> ><span :style="{ color: 'rgb(195, 205, 215)' }">({{ group.ci_types.length }})</span>
</div> </div>
<div <div
@ -33,17 +33,17 @@
<template #two> <template #two>
<div id="discovery-ci"> <div id="discovery-ci">
<a-input-search <a-input-search
placeholder="请查找" :placeholder="$t('cmdb.components.pleaseSearch')"
class="ops-input ops-input-radius" class="ops-input ops-input-radius"
:style="{ width: '200px', marginRight: '20px', marginBottom: '10px' }" :style="{ width: '200px', marginRight: '20px', marginBottom: '10px' }"
@search="handleSearch" @search="handleSearch"
allowClear allowClear
/> />
<div class="ops-list-batch-action" :style="{ marginBottom: '10px' }" v-show="!!selectedRowKeys.length"> <div class="ops-list-batch-action" :style="{ marginBottom: '10px' }" v-show="!!selectedRowKeys.length">
<span @click="batchAccept">入库</span> <span @click="batchAccept">{{ $t('cmdb.ad.accept') }}</span>
<a-divider type="vertical" /> <a-divider type="vertical" />
<span @click="batchDelete">删除</span> <span @click="batchDelete">{{ $t('delete') }}</span>
<span>选取{{ selectedRowKeys.length }} </span> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
</div> </div>
<ops-table <ops-table
show-overflow show-overflow
@ -79,33 +79,33 @@
<vxe-column <vxe-column
align="center" align="center"
field="is_accept" field="is_accept"
title="是否入库" :title="$t('cmdb.ad.isAccpet')"
v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }" v-bind="columns.length ? { width: '100px' } : { minWidth: '100px' }"
:filters="[ :filters="[
{ label: '', value: true }, { label: $t('yes'), value: true },
{ label: '', value: false }, { label: $t('no'), value: false },
]" ]"
> >
<template #default="{row}"> <template #default="{row}">
{{ row.is_accept ? '' : '' }} {{ row.is_accept ? $t('yes') : $t('no') }}
</template> </template>
</vxe-column> </vxe-column>
<vxe-column <vxe-column
field="accept_by" field="accept_by"
title="入库人" :title="$t('cmdb.ad.accpetBy')"
v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }" v-bind="columns.length ? { width: '80px' } : { minWidth: '80px' }"
:filters="[]" :filters="[]"
></vxe-column> ></vxe-column>
<vxe-column <vxe-column
field="accept_time" field="accept_time"
title="入库时间" :title="$t('cmdb.ad.acceptTime')"
sortable sortable
v-bind="columns.length ? { width: '130px' } : { minWidth: '130px' }" v-bind="columns.length ? { width: '130px' } : { minWidth: '130px' }"
></vxe-column> ></vxe-column>
<vxe-column title="操作" v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }" align="center"> <vxe-column :title="$t('operation')" v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }" align="center">
<template #default="{row}"> <template #default="{row}">
<a-space> <a-space>
<a-tooltip title="入库"> <a-tooltip :title="$t('cmdb.ad.accept')">
<a v-if="!row.is_accept" @click="accept(row)"><ops-icon type="icon-xianxing-edit"/></a> <a v-if="!row.is_accept" @click="accept(row)"><ops-icon type="icon-xianxing-edit"/></a>
</a-tooltip> </a-tooltip>
<a :style="{ color: 'red' }" @click="deleteADC(row)"><ops-icon type="icon-xianxing-delete"/></a> <a :style="{ color: 'red' }" @click="deleteADC(row)"><ops-icon type="icon-xianxing-delete"/></a>
@ -115,7 +115,7 @@
<template #empty> <template #empty>
<div> <div>
<img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" />
<div>暂无数据</div> <div>{{ $t('noData') }}</div>
</div> </div>
</template> </template>
</ops-table> </ops-table>
@ -242,11 +242,11 @@ export default {
this.$refs.xTable.getVxetableRef().clearCheckboxReserve() this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: `确认入库`, content: that.$t('cmdb.ad.confirmAccept'),
onOk() { onOk() {
updateADCAccept(row.id).then(() => { updateADCAccept(row.id).then(() => {
that.$message.success('入库成功') that.$message.success(that.$t('cmdb.ad.accpetSuccess'))
that.getAdc(false) that.getAdc(false)
}) })
}, },
@ -258,11 +258,11 @@ export default {
this.$refs.xTable.getVxetableRef().clearCheckboxReserve() this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: `确认删除该条数据`, content: that.$t('cmdb.ad.deleteADC'),
onOk() { onOk() {
deleteAdc(row.id).then(() => { deleteAdc(row.id).then(() => {
that.$message.success('删除成功') that.$message.success(that.$t('deleteSuccess'))
that.getAdc(false) that.getAdc(false)
}) })
}, },
@ -273,7 +273,7 @@ export default {
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])
} }
this.$message.success('入库成功') this.$message.success(this.$t('cmdb.ad.acceptSuccess'))
this.getAdc(false) this.getAdc(false)
this.selectedRowKeys = [] this.selectedRowKeys = []
this.$refs.xTable.getVxetableRef().clearCheckboxRow() this.$refs.xTable.getVxetableRef().clearCheckboxRow()
@ -283,13 +283,13 @@ export default {
async batchDelete() { async batchDelete() {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: `确认删除这些数据`, content: that.$t('cmdb.ad.batchDelete'),
async onOk() { async onOk() {
for (let i = 0; i < that.selectedRowKeys.length; i++) { for (let i = 0; i < that.selectedRowKeys.length; i++) {
await deleteAdc(that.selectedRowKeys[i]) await deleteAdc(that.selectedRowKeys[i])
} }
that.$message.success('删除成功') that.$message.success(that.$t('deleteSuccess'))
that.getAdc(false) that.getAdc(false)
that.selectedRowKeys = [] that.selectedRowKeys = []
that.$refs.xTable.getVxetableRef().clearCheckboxRow() that.$refs.xTable.getVxetableRef().clearCheckboxRow()

View File

@ -1,6 +1,8 @@
<template> <template>
<div class="model-relation"> <div class="model-relation">
<a-button @click="handleCreate" type="primary" style="margin-bottom: 15px;" icon="plus">新增关系</a-button> <a-button @click="handleCreate" type="primary" style="margin-bottom: 15px;" icon="plus">{{
$t('cmdb.ciType.addRelation')
}}</a-button>
<model-relation-table ref="table"></model-relation-table> <model-relation-table ref="table"></model-relation-table>
<a-modal <a-modal
:closable="false" :closable="false"
@ -11,11 +13,14 @@
width="500px" width="500px"
> >
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }"> <a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }">
<a-form-item label="源模型"> <a-form-item :label="$t('cmdb.ciType.sourceCIType')">
<a-select <a-select
showSearch showSearch
name="source_ci_type_id" name="source_ci_type_id"
v-decorator="['source_ci_type_id', { rules: [{ required: true, message: '请选择源模型' }] }]" v-decorator="[
'source_ci_type_id',
{ rules: [{ required: true, message: $t('cmdb.ciType.sourceCITypeTips') }] },
]"
@change="handleSourceTypeChange" @change="handleSourceTypeChange"
:filterOption="filterOption" :filterOption="filterOption"
> >
@ -24,11 +29,11 @@
}}</a-select-option> }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="目标模型"> <a-form-item :label="$t('cmdb.ciType.dstCIType')">
<a-select <a-select
showSearch showSearch
name="ci_type_id" name="ci_type_id"
v-decorator="['ci_type_id', { rules: [{ required: true, message: '请选择目标模型' }] }]" v-decorator="['ci_type_id', { rules: [{ required: true, message: $t('cmdb.ciType.dstCITypeTips') }] }]"
@change="handleTargetTypeChange" @change="handleTargetTypeChange"
:filterOption="filterOption" :filterOption="filterOption"
> >
@ -38,10 +43,13 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="关联关系"> <a-form-item :label="$t('cmdb.ciType.relationType')">
<a-select <a-select
name="relation_type_id" name="relation_type_id"
v-decorator="['relation_type_id', { rules: [{ required: true, message: '请选择关联关系' }] }]" v-decorator="[
'relation_type_id',
{ rules: [{ required: true, message: $t('cmdb.ciType.relationTypeTips') }] },
]"
> >
<a-select-option :value="relationType.id" :key="relationType.id" v-for="relationType in relationTypes">{{ <a-select-option :value="relationType.id" :key="relationType.id" v-for="relationType in relationTypes">{{
relationType.name relationType.name
@ -49,11 +57,16 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="关联约束"> <a-form-item :label="$t('cmdb.ciType.relationConstraint')">
<a-select v-decorator="['constraint', { rules: [{ required: true, message: '请选择关联约束' }] }]"> <a-select
<a-select-option value="0">一对多</a-select-option> v-decorator="[
<a-select-option value="1">一对一</a-select-option> 'constraint',
<a-select-option value="2">多对多</a-select-option> { rules: [{ required: true, message: $t('cmdb.ciType.relationConstraintTips') }] },
]"
>
<a-select-option value="0">{{ $t('cmdb.ciType.one2Many') }}</a-select-option>
<a-select-option value="1">{{ $t('cmdb.ciType.one2One') }}</a-select-option>
<a-select-option value="2">{{ $t('cmdb.ciType.many2Many') }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -66,7 +79,7 @@ import ModelRelationTable from './modules/modelRelationTable.vue'
import { searchResourceType } from '@/modules/acl/api/resource' import { searchResourceType } from '@/modules/acl/api/resource'
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup' import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypes } from '@/modules/cmdb/api/CIType'
import { createRelation, deleteRelation, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation' import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
export default { export default {
name: 'Index', name: 'Index',
components: { components: {
@ -85,16 +98,40 @@ export default {
drawerTitle: '', drawerTitle: '',
CITypes: [], CITypes: [],
relationTypes: [], relationTypes: [],
constraintMap: {
'0': '一对多',
'1': '一对一',
'2': '多对多',
},
sourceCITypeId: undefined, sourceCITypeId: undefined,
targetCITypeId: undefined, targetCITypeId: undefined,
} }
}, },
computed: {
currentCId() {
if (this.currentId) {
if (this.currentId.split('%')[1] !== 'null') {
return Number(this.currentId.split('%')[1])
}
return null
}
return null
},
displayCITypes() {
return this.CITypes
// return this.CITypes.filter((c) => c.id !== this.targetCITypeId)
},
displayTargetCITypes() {
return this.CITypes
// return this.CITypes.filter((c) => c.id !== this.sourceCITypeId)
},
CITypeId() {
return this.currentCId
},
constraintMap() {
return {
'0': this.$t('cmdb.ciType.one2Many'),
'1': this.$t('cmdb.ciType.one2One'),
'2': this.$t('cmdb.ciType.many2Many'),
}
},
},
provide() { provide() {
return { return {
resource_type: () => { resource_type: () => {
@ -116,26 +153,6 @@ export default {
}) })
this.loadCITypes(!_currentId) this.loadCITypes(!_currentId)
}, },
computed: {
currentCId() {
if (this.currentId) {
if (this.currentId.split('%')[1] !== 'null') {
return Number(this.currentId.split('%')[1])
}
return null
}
return null
},
displayCITypes() {
return this.CITypes
},
displayTargetCITypes() {
return this.CITypes
},
CITypeId() {
return this.currentCId
},
},
methods: { methods: {
async loadCITypes(isResetCurrentId = false) { async loadCITypes(isResetCurrentId = false) {
const groups = await getCITypeGroupsConfig({ need_other: true }) const groups = await getCITypeGroupsConfig({ need_other: true })
@ -146,7 +163,6 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
groups.forEach((g) => { groups.forEach((g) => {
if (!g.id) { if (!g.id) {
// 给未分组增加一个假的id
g.id = -1 g.id = -1
} }
if (isResetCurrentId && !alreadyReset && g.ci_types && g.ci_types.length) { if (isResetCurrentId && !alreadyReset && g.ci_types && g.ci_types.length) {
@ -172,7 +188,7 @@ export default {
}) })
}, },
handleCreate() { handleCreate() {
this.drawerTitle = '新增关系' this.drawerTitle = this.$t('cmdb.ciType.addRelation')
this.visible = true this.visible = true
this.$nextTick(() => { this.$nextTick(() => {
this.form.setFieldsValue({ this.form.setFieldsValue({
@ -192,7 +208,7 @@ export default {
if (!err) { if (!err) {
createRelation(values.source_ci_type_id, values.ci_type_id, values.relation_type_id, values.constraint).then( createRelation(values.source_ci_type_id, values.ci_type_id, values.relation_type_id, values.constraint).then(
(res) => { (res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.onClose() this.onClose()
this.handleOk() this.handleOk()
} }
@ -207,7 +223,7 @@ export default {
}, },
handleDelete(record) { handleDelete(record) {
deleteRelation(record.source_ci_type_id, record.id).then((res) => { deleteRelation(record.source_ci_type_id, record.id).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
this.handleOk() this.handleOk()
}) })

View File

@ -11,22 +11,27 @@
:data="tableData" :data="tableData"
:sort-config="{ defaultSort: { field: 'created_at', order: 'desc' } }" :sort-config="{ defaultSort: { field: 'created_at', order: 'desc' } }"
> >
<vxe-column field="created_at" title="创建时间" sortable width="159px"></vxe-column> <vxe-column field="created_at" :title="$t('created_at')" sortable width="159px"></vxe-column>
<vxe-column field="parent.alias" title="源模型"></vxe-column> <vxe-column field="parent.alias" :title="$t('cmdb.ciType.sourceCIType')"></vxe-column>
<vxe-column field="relation_type_id" title="关系" :filters="[{ data: '' }]" :filter-multiple="false"> <vxe-column
field="relation_type_id"
:title="$t('cmdb.custom_dashboard.relation')"
:filters="[{ data: '' }]"
:filter-multiple="false"
>
<template #default="{ row }"> <template #default="{ row }">
<a-tag color="cyan"> <a-tag color="cyan">
{{ row.relation_type.name }} {{ row.relation_type.name }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="child.alias" title="目标模型"></vxe-column> <vxe-column field="child.alias" :title="$t('cmdb.ciType.dstCIType')"></vxe-column>
<vxe-column field="constraint" title="关联约束"></vxe-column> <vxe-column field="constraint" :title="$t('cmdb.ciType.relationConstraint')"></vxe-column>
<vxe-column field="authorization" title="操作" width="89px"> <vxe-column field="authorization" :title="$t('operation')" width="89px">
<template #default="{ row }"> <template #default="{ row }">
<a-space> <a-space>
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a> <a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
<a-popconfirm title="确认删除?" @confirm="deleteRelation(row)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="deleteRelation(row)">
<a :style="{ color: 'red' }"><ops-icon type="icon-xianxing-delete"/></a> <a :style="{ color: 'red' }"><ops-icon type="icon-xianxing-delete"/></a>
</a-popconfirm> </a-popconfirm>
</a-space> </a-space>
@ -48,11 +53,6 @@ export default {
drawerVisible: false, drawerVisible: false,
tableData: [], tableData: [],
relationTypeList: null, relationTypeList: null,
constraintMap: {
'0': '一对多',
'1': '一对一',
'2': '多对多',
},
} }
}, },
components: { components: {
@ -65,6 +65,13 @@ export default {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
constraintMap() {
return {
'0': this.$t('cmdb.ciType.one2Many'),
'1': this.$t('cmdb.ciType.one2One'),
'2': this.$t('cmdb.ciType.many2Many'),
}
},
}, },
methods: { methods: {
async refresh() { async refresh() {
@ -103,7 +110,8 @@ export default {
}, },
deleteRelation(row) { deleteRelation(row) {
deleteRelation(row.parent_id, row.child_id).then((res) => { deleteRelation(row.parent_id, row.child_id).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
this.getRelationTypes()
this.refresh() this.refresh()
}) })
}, },

View File

@ -1,43 +1,43 @@
<template> <template>
<div> <div>
<a-card :bordered="false"> <a-card :bordered="false">
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<a-tab-pane key="1" tab="CI变更"> <a-tab-pane key="1" :tab="$t('cmdb.history.ciChange')">
<ci-table></ci-table> <ci-table></ci-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="关系变更"> <a-tab-pane key="2" :tab="$t('cmdb.history.relationChange')">
<relation-table></relation-table> <relation-table></relation-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab="模型变更"> <a-tab-pane key="3" :tab="$t('cmdb.history.ciTypeChange')">
<type-table></type-table> <type-table></type-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="4" tab="触发历史"> <a-tab-pane key="4" :tab="$t('cmdb.history.triggerHistory')">
<TriggerTable></TriggerTable> <TriggerTable></TriggerTable>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</a-card> </a-card>
</div> </div>
</template> </template>
<script> <script>
import CiTable from './modules/ciTable.vue' import CiTable from './modules/ciTable.vue'
import RelationTable from './modules/relation.vue' import RelationTable from './modules/relation.vue'
import TypeTable from './modules/typeTable.vue' import TypeTable from './modules/typeTable.vue'
import TriggerTable from './modules/triggerTable.vue' import TriggerTable from './modules/triggerTable.vue'
export default { export default {
name: 'OperationHistory', name: 'OperationHistory',
components: { components: {
CiTable, CiTable,
RelationTable, RelationTable,
TypeTable, TypeTable,
TriggerTable, TriggerTable,
}, },
data() { data() {
return { return {
userList: [], userList: [],
} }
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -1,421 +1,407 @@
<template> <template>
<div> <div>
<search-form <search-form
ref="child" ref="child"
:attrList="ciTableAttrList" :attrList="ciTableAttrList"
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@searchFormChange="searchFormChange" @searchFormChange="searchFormChange"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
row-id="_XID" row-id="_XID"
:loading="loading" :loading="loading"
border border
size="small" size="small"
show-overflow="tooltip" show-overflow="tooltip"
show-header-overflow="tooltip" show-header-overflow="tooltip"
resizable resizable
:data="tableData" :data="tableData"
:max-height="`${windowHeight - windowHeightMinus}px`" :max-height="`${windowHeight - windowHeightMinus}px`"
:span-method="mergeRowMethod" :span-method="mergeRowMethod"
:scroll-y="{enabled: false}" :scroll-y="{ enabled: false }"
class="ops-unstripe-table" class="ops-unstripe-table"
> >
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="159px" :title="$t('cmdb.history.opreateTime')"></vxe-column>
<vxe-column field="user" width="100px" title="用户"> <vxe-column field="user" width="100px" :title="$t('cmdb.history.user')">
<template #header="{ column }"> <template #header="{ column }">
<span>{{ column.title }}</span> <span>{{ column.title }}</span>
<a-popover trigger="click" placement="bottom"> <a-popover trigger="click" placement="bottom">
<a-icon class="filter" type="filter" theme="filled"/> <a-icon class="filter" type="filter" theme="filled" />
<a slot="content"> <a slot="content">
<a-input placeholder="输入筛选用户名" size="small" v-model="queryParams.username" style="width: 200px" allowClear/> <a-input
<a-button type="link" class="filterButton" @click="filterUser">筛选</a-button> :placeholder="$t('cmdb.history.userTips')"
<a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button> size="small"
</a> v-model="queryParams.username"
</a-popover> style="width: 200px"
</template> allowClear
</vxe-column> />
<vxe-column field="type_id" width="100px" title="模型"></vxe-column> <a-button type="link" class="filterButton" @click="filterUser">{{ $t('cmdb.history.filter') }}</a-button>
<vxe-column field="operate_type" width="89px" title="操作"> <a-button type="link" class="filterResetButton" @click="filterUserReset">{{ $t('reset') }}</a-button>
<template #header="{ column }"> </a>
<span>{{ column.title }}</span> </a-popover>
<a-popover trigger="click" placement="bottom"> </template>
<a-icon class="filter" type="filter" theme="filled"/> </vxe-column>
<a slot="content"> <vxe-column field="type_id" width="100px" :title="$t('cmdb.ciType.ciType')"></vxe-column>
<a-select <vxe-column field="operate_type" width="89px" :title="$t('operation')">
v-model="queryParams.operate_type" <template #header="{ column }">
placeholder="选择筛选操作" <span>{{ column.title }}</span>
show-search <a-popover trigger="click" placement="bottom">
style="width: 200px" <a-icon class="filter" type="filter" theme="filled" />
:filter-option="filterOption" <a slot="content">
allowClear <a-select
> v-model="queryParams.operate_type"
<a-select-option :placeholder="$t('cmdb.history.filterOperate')"
:value="Object.values(choice)[0]" show-search
:key="index" style="width: 200px"
v-for="(choice, index) in ciTableAttrList[4].choice_value" :filter-option="filterOption"
> allowClear
{{ Object.keys(choice)[0] }} >
</a-select-option <a-select-option
> :value="Object.values(choice)[0]"
</a-select> :key="index"
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button> v-for="(choice, index) in ciTableAttrList[4].choice_value"
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button> >
</a> {{ Object.keys(choice)[0] }}
</a-popover> </a-select-option>
</template> </a-select>
<template #default="{ row }"> <a-button type="link" class="filterButton" @click="filterOperate">{{
<a-tag color="green" v-if="row.operate_type === '新增' "> $t('cmdb.history.filter')
{{ row.operate_type }} }}</a-button>
</a-tag> <a-button type="link" class="filterResetButton" @click="filterOperateReset">{{ $t('reset') }}</a-button>
<a-tag color="orange" v-else-if="row.operate_type === '修改' "> </a>
{{ row.operate_type }} </a-popover>
</a-tag> </template>
<a-tag color="red" v-else> <template #default="{ row }">
{{ row.operate_type }} <a-tag color="green" v-if="row.operate_type === $t('new')">
</a-tag> {{ row.operate_type }}
</template> </a-tag>
</vxe-column> <a-tag color="orange" v-else-if="row.operate_type === $t('update')">
<vxe-column field="attr_alias" title="属性"></vxe-column> {{ row.operate_type }}
<vxe-column field="old" title=""></vxe-column> </a-tag>
<vxe-column field="new" title=""></vxe-column> <a-tag color="red" v-else>
</vxe-table> {{ row.operate_type }}
<pager </a-tag>
:current-page.sync="queryParams.page" </template>
:page-size.sync="queryParams.page_size" </vxe-column>
:page-sizes="[50,100,200]" <vxe-column field="attr_alias" :title="$t('cmdb.history.attribute')"></vxe-column>
:total="total" <vxe-column field="old" :title="$t('cmdb.history.old')"></vxe-column>
:isLoading="loading" <vxe-column field="new" :title="$t('cmdb.history.new')"></vxe-column>
@change="onChange" </vxe-table>
@showSizeChange="onShowSizeChange" <pager
></pager> :current-page.sync="queryParams.page"
</div> :page-size.sync="queryParams.page_size"
</template> :page-sizes="[50, 100, 200]"
<script> :total="total"
import Pager from './pager.vue' :isLoading="loading"
import SearchForm from './searchForm.vue' @change="onChange"
import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history' @showSizeChange="onShowSizeChange"
import { getCITypes } from '@/modules/cmdb/api/CIType' ></pager>
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' </div>
export default { </template>
name: 'CiTable', <script>
components: { SearchForm, Pager }, import { mapState } from 'vuex'
data() { import Pager from './pager.vue'
return { import SearchForm from './searchForm.vue'
typeId: undefined, import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history'
operateTypeMap: new Map([ import { getCITypes } from '@/modules/cmdb/api/CIType'
['0', '新增'], import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
['1', '删除'], export default {
['2', '修改'], name: 'CiTable',
]), components: { SearchForm, Pager },
loading: true, inject: ['reload'],
typeList: null, data() {
userList: [], return {
tableData: [], typeId: undefined,
total: 0, loading: true,
isExpand: false, typeList: null,
queryParams: { userList: [],
page: 1, tableData: [],
page_size: 50, total: 0,
}, isExpand: false,
ciTableAttrList: [ queryParams: {
{ page: 1,
alias: '日期', page_size: 50,
is_choice: false, },
name: 'datetime', ciTableAttrList: [
value_type: '3' {
}, alias: this.$t('cmdb.ciType.date'),
{ is_choice: false,
alias: '用户', name: 'datetime',
is_choice: true, value_type: '3',
name: 'username', },
value_type: '2', {
choice_value: [] alias: this.$t('cmdb.history.user'),
}, is_choice: true,
{ name: 'username',
alias: '模型', value_type: '2',
is_choice: true, choice_value: [],
name: 'type_id', },
value_type: '2', {
choice_value: [], alias: this.$t('cmdb.ciType.ciType'),
}, is_choice: true,
{ name: 'type_id',
alias: '属性', value_type: '2',
is_choice: true, choice_value: [],
name: 'attr_id', },
value_type: '2', {
choice_value: [] alias: this.$t('cmdb.history.attribute'),
}, is_choice: true,
{ name: 'attr_id',
alias: '操作', value_type: '2',
is_choice: true, choice_value: [],
name: 'operate_type', },
value_type: '2', {
choice_value: [ alias: this.$t('operation'),
{ '新增': 0 }, is_choice: true,
{ '删除': 1 }, name: 'operate_type',
{ '修改': 2 }, value_type: '2',
] choice_value: [{ [this.$t('new')]: 0 }, { [this.$t('delete')]: 1 }, { [this.$t('update')]: 2 }],
}, },
{ {
alias: 'CI_ID', alias: 'CI_ID',
is_choice: false, is_choice: false,
name: 'ci_id', name: 'ci_id',
value_type: '2' value_type: '2',
} },
], ],
} }
}, },
computed: { computed: {
windowHeight() { ...mapState(['locale']),
return this.$store.state.windowHeight windowHeight() {
}, return this.$store.state.windowHeight
windowHeightMinus() { },
return this.isExpand ? 396 : 331 windowHeightMinus() {
} return this.isExpand ? 396 : 331
}, },
async created() { operateTypeMap() {
this.$watch( return new Map([
function () { ['0', this.$t('new')],
return this.ciTableAttrList[3].choice_value ['1', this.$t('delete')],
}, ['2', this.$t('update')],
function () { ])
delete this.$refs.child.queryParams.attr_id },
} },
) watch: {
await Promise.all([ locale() {
this.getUserList(), this.reload()
this.getTypes() },
]) },
await this.getTable(this.queryParams) async created() {
}, this.$watch(
updated() { function() {
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 return this.ciTableAttrList[3].choice_value
}, },
methods: { function() {
// 获取表格数据 delete this.$refs.child.queryParams.attr_id
async getTable(queryParams) { }
try { )
this.loading = true await Promise.all([this.getUserList(), this.getTypes()])
const res = await getCIHistoryTable(queryParams) await this.getTable(this.queryParams)
const tempArr = [] },
res.records.forEach(item => { updated() {
item[0].type_id = this.handleTypeId(item[0].type_id) this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
item[1].forEach((subItem) => { },
subItem.operate_type = this.handleOperateType(subItem.operate_type) methods: {
const tempObj = Object.assign(subItem, item[0]) async getTable(queryParams) {
tempArr.push(tempObj) try {
}) this.loading = true
}) const res = await getCIHistoryTable(queryParams)
this.tableData = tempArr const tempArr = []
this.total = res.total res.records.forEach((item) => {
} finally { item[0].type_id = this.handleTypeId(item[0].type_id)
this.loading = false item[1].forEach((subItem) => {
} subItem.operate_type = this.handleOperateType(subItem.operate_type)
}, const tempObj = Object.assign(subItem, item[0])
// 获取用户列表 tempArr.push(tempObj)
async getUserList() { })
const res = await getUsers() })
this.userList = res.map(x => { this.tableData = tempArr
const username = x.nickname this.total = res.total
const obj = { } finally {
[username]: username this.loading = false
} }
return obj },
}) async getUserList() {
this.ciTableAttrList[1].choice_value = this.userList const res = await getUsers()
}, this.userList = res.map((x) => {
// 获取模型 const username = x.nickname
async getTypes() { const obj = {
const res = await getCITypes() [username]: username,
const typesArr = [] }
const typesMap = new Map() return obj
res.ci_types.forEach(item => { })
const tempObj = {} this.ciTableAttrList[1].choice_value = this.userList
tempObj[item.alias] = item.id },
if (item.alias) { async getTypes() {
typesArr.push(tempObj) const res = await getCITypes()
typesMap.set(item.id, item.alias) const typesArr = []
} const typesMap = new Map()
}) res.ci_types.forEach((item) => {
this.typeList = typesMap const tempObj = {}
this.ciTableAttrList[2].choice_value = typesArr tempObj[item.alias] = item.id
}, if (item.alias) {
// 获取模型对应属性 typesArr.push(tempObj)
async getAttrs(type_id) { typesMap.set(item.id, item.alias)
if (!type_id) { }
this.ciTableAttrList[3].choice_value = [] })
return this.typeList = typesMap
} this.ciTableAttrList[2].choice_value = typesArr
const res = await getCITypeAttributesById(type_id) },
const attrsArr = [] async getAttrs(type_id) {
res.attributes.forEach(item => { if (!type_id) {
const tempObj = {} this.ciTableAttrList[3].choice_value = []
tempObj[item.alias] = item.id return
if (item.alias) { }
attrsArr.push(tempObj) const res = await getCITypeAttributesById(type_id)
} const attrsArr = []
}) res.attributes.forEach((item) => {
this.ciTableAttrList[3].choice_value = attrsArr const tempObj = {}
}, tempObj[item.alias] = item.id
onShowSizeChange(size) { if (item.alias) {
this.queryParams.page_size = size attrsArr.push(tempObj)
this.queryParams.page = 1 }
this.getTable(this.queryParams) })
}, this.ciTableAttrList[3].choice_value = attrsArr
onChange(pageNum) { },
this.queryParams.page = pageNum onShowSizeChange(size) {
this.getTable(this.queryParams) this.queryParams.page_size = size
}, this.queryParams.page = 1
handleExpandChange(expand) { this.getTable(this.queryParams)
this.isExpand = expand },
}, onChange(pageNum) {
// 处理查询 this.queryParams.page = pageNum
handleSearch(queryParams) { this.getTable(this.queryParams)
this.queryParams = queryParams },
this.getTable(this.queryParams) handleExpandChange(expand) {
}, this.isExpand = expand
// 重置表单 },
searchFormReset() { handleSearch(queryParams) {
this.queryParams = { this.queryParams = queryParams
page: 1, this.getTable(this.queryParams)
page_size: 50, },
start: '', searchFormReset() {
end: '', this.queryParams = {
username: '', page: 1,
ci_id: undefined, page_size: 50,
attr_id: undefined, start: '',
operate_type: undefined end: '',
} username: '',
// 将属性options重置 ci_id: undefined,
this.ciTableAttrList[3].choice_value = [] attr_id: undefined,
this.getTable(this.queryParams) operate_type: undefined,
}, }
// 转换operate_type this.ciTableAttrList[3].choice_value = []
handleOperateType(operate_type) { this.getTable(this.queryParams)
return this.operateTypeMap.get(operate_type) },
}, handleOperateType(operate_type) {
// 转换type_id return this.operateTypeMap.get(operate_type)
handleTypeId(type_id) { },
return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id handleTypeId(type_id) {
}, return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id
// 表单改变重新获取属性列表 },
searchFormChange(queryParams) { searchFormChange(queryParams) {
if (this.typeId !== queryParams.type_id) { if (this.typeId !== queryParams.type_id) {
this.typeId = queryParams.type_id this.typeId = queryParams.type_id
this.getAttrs(queryParams.type_id) this.getAttrs(queryParams.type_id)
} }
if (queryParams.type_id === undefined) { if (queryParams.type_id === undefined) {
this.typeId = undefined this.typeId = undefined
this.$refs.child.queryParams.attr_id = undefined this.$refs.child.queryParams.attr_id = undefined
} }
}, },
// 合并表格 mergeRowMethod({ row, _rowIndex, column, visibleData }) {
mergeRowMethod ({ row, _rowIndex, column, visibleData }) { const fields = ['created_at', 'user', 'type_id']
const fields = ['created_at', 'user', 'type_id'] const cellValue = row[column.property]
// 单元格值 = [.属性] 确定一格 const created_at = row['created_at']
const cellValue = row[column.property] if (column.property === 'created_at') {
const created_at = row['created_at'] if (cellValue && fields.includes(column.property)) {
// 如果单元格值不为空且作用域包含当前列 const prevRow = visibleData[_rowIndex - 1]
if (column.property === 'created_at') { let nextRow = visibleData[_rowIndex + 1]
if (cellValue && fields.includes(column.property)) { if (prevRow && prevRow[column.property] === cellValue) {
// 前一行 return { rowspan: 0, colspan: 0 }
const prevRow = visibleData[_rowIndex - 1] } else {
// 下一行 let countRowspan = 1
let nextRow = visibleData[_rowIndex + 1] while (nextRow && nextRow[column.property] === cellValue) {
// 如果前一行不为空且前一行单元格的值与cellValue相同 nextRow = visibleData[++countRowspan + _rowIndex]
if (prevRow && prevRow[column.property] === cellValue) { }
return { rowspan: 0, colspan: 0 } if (countRowspan > 1) {
} else { return { rowspan: countRowspan, colspan: 1 }
let countRowspan = 1 }
while (nextRow && nextRow[column.property] === cellValue) { }
nextRow = visibleData[++countRowspan + _rowIndex] }
} } else if (column.property === 'user') {
if (countRowspan > 1) { if (cellValue && fields.includes(column.property)) {
return { rowspan: countRowspan, colspan: 1 } const prevRow = visibleData[_rowIndex - 1]
} let nextRow = visibleData[_rowIndex + 1]
} if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
} return { rowspan: 0, colspan: 0 }
} else if (column.property === 'user') { } else {
if (cellValue && fields.includes(column.property)) { let countRowspan = 1
// 前一行 while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
const prevRow = visibleData[_rowIndex - 1] nextRow = visibleData[++countRowspan + _rowIndex]
// 下一行 }
let nextRow = visibleData[_rowIndex + 1] if (countRowspan > 1) {
// 如果前一行不为空且前一行单元格的值与cellValue相同 return { rowspan: countRowspan, colspan: 1 }
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) { }
return { rowspan: 0, colspan: 0 } }
} else { }
let countRowspan = 1 } else if (column.property === 'type_id') {
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) { if (cellValue && fields.includes(column.property)) {
nextRow = visibleData[++countRowspan + _rowIndex] const prevRow = visibleData[_rowIndex - 1]
} let nextRow = visibleData[_rowIndex + 1]
if (countRowspan > 1) { if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
return { rowspan: countRowspan, colspan: 1 } return { rowspan: 0, colspan: 0 }
} } else {
} let countRowspan = 1
} while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
} else if (column.property === 'type_id') { nextRow = visibleData[++countRowspan + _rowIndex]
if (cellValue && fields.includes(column.property)) { }
// 前一行 if (countRowspan > 1) {
const prevRow = visibleData[_rowIndex - 1] return { rowspan: countRowspan, colspan: 1 }
// 下一行 }
let nextRow = visibleData[_rowIndex + 1] }
// 如果前一行不为空且前一行单元格的值与cellValue相同 }
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) { }
return { rowspan: 0, colspan: 0 } },
} else { filterUser() {
let countRowspan = 1 this.queryParams.page = 1
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) { this.queryParams.page_size = 50
nextRow = visibleData[++countRowspan + _rowIndex] this.getTable(this.queryParams)
} },
if (countRowspan > 1) { filterUserReset() {
return { rowspan: countRowspan, colspan: 1 } this.queryParams.page = 1
} this.queryParams.page_size = 50
} this.queryParams.username = ''
} this.getTable(this.queryParams)
} },
}, filterOperate() {
filterUser() { this.queryParams.page = 1
this.queryParams.page = 1 this.queryParams.page_size = 50
this.queryParams.page_size = 50 this.getTable(this.queryParams)
this.getTable(this.queryParams) },
}, filterOperateReset() {
filterUserReset() { this.queryParams.page = 1
this.queryParams.page = 1 this.queryParams.page_size = 50
this.queryParams.page_size = 50 this.queryParams.operate_type = undefined
this.queryParams.username = '' this.getTable(this.queryParams)
this.getTable(this.queryParams) },
}, filterOption(input, option) {
filterOperate() { return option.componentOptions.children[0].text.indexOf(input) >= 0
this.queryParams.page = 1 },
this.queryParams.page_size = 50 },
this.getTable(this.queryParams) }
}, </script>
filterOperateReset() {
this.queryParams.page = 1 <style lang="less" scoped>
this.queryParams.page_size = 50 .filter {
this.queryParams.operate_type = undefined margin-left: 10px;
this.getTable(this.queryParams) color: #c0c4cc;
}, cursor: pointer;
filterOption(input, option) { &:hover {
return ( color: #606266;
option.componentOptions.children[0].text.indexOf(input) >= 0 }
) }
} </style>
}
}
</script>
<style lang="less" scoped>
.filter{
margin-left: 10px;
color: #c0c4cc;
cursor: pointer;
&:hover{
color: #606266;
}
}
</style>

View File

@ -1,116 +1,116 @@
<template> <template>
<div> <div>
<a-row class="row" type="flex" justify="end"> <a-row class="row" type="flex" justify="end">
<a-col> <a-col>
<a-space align="end"> <a-space align="end">
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button> <a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button>
<a-button class="page-button" size="small" >{{ currentPage }}</a-button> <a-button class="page-button" size="small" >{{ currentPage }}</a-button>
<a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button> <a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button>
<a-dropdown class="dropdown" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled"> <a-dropdown class="dropdown" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)"> <a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
{{ size }}/ {{ size }}{{ $t('cmdb.history.itemsPerPage') }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
<a-button size="small"> {{ pageSize }}/ <a-icon type="down" /> </a-button> <a-button size="small"> {{ pageSize }}{{ $t('cmdb.history.itemsPerPage') }} <a-icon type="down" /> </a-button>
</a-dropdown> </a-dropdown>
</a-space> </a-space>
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
currentPage: { currentPage: {
type: Number, type: Number,
required: true required: true
}, },
pageSize: { pageSize: {
type: Number, type: Number,
required: true required: true
}, },
pageSizes: { pageSizes: {
type: Array, type: Array,
required: true required: true
}, },
total: { total: {
type: Number, type: Number,
required: true required: true
}, },
isLoading: { isLoading: {
type: Boolean, type: Boolean,
required: false required: false
} }
}, },
data() { data() {
return { return {
dropdownIsDisabled: false, dropdownIsDisabled: false,
prevIsDisabled: true, prevIsDisabled: true,
} }
}, },
computed: { computed: {
nextIsDisabled() { nextIsDisabled() {
return this.isLoading || this.total < this.pageSize return this.isLoading || this.total < this.pageSize
} }
}, },
watch: { watch: {
isLoading: { isLoading: {
immediate: true, immediate: true,
handler: function (val) { handler: function (val) {
if (val === true) { if (val === true) {
this.dropdownIsDisabled = true this.dropdownIsDisabled = true
this.prevIsDisabled = true this.prevIsDisabled = true
} else { } else {
this.dropdownIsDisabled = false this.dropdownIsDisabled = false
if (this.currentPage === 1) { if (this.currentPage === 1) {
this.prevIsDisabled = true this.prevIsDisabled = true
} else { } else {
this.prevIsDisabled = false this.prevIsDisabled = false
} }
} }
} }
}, },
currentPage: { currentPage: {
immediate: true, immediate: true,
handler: function (val) { handler: function (val) {
if (val === 1) { if (val === 1) {
this.prevIsDisabled = true this.prevIsDisabled = true
} }
} }
} }
}, },
methods: { methods: {
handleItemClick(size) { handleItemClick(size) {
this.$emit('showSizeChange', size) this.$emit('showSizeChange', size)
}, },
nextPage() { nextPage() {
const pageNum = this.currentPage + 1 const pageNum = this.currentPage + 1
this.$emit('change', pageNum) this.$emit('change', pageNum)
}, },
prevPage() { prevPage() {
const pageNum = this.currentPage - 1 const pageNum = this.currentPage - 1
this.$emit('change', pageNum) this.$emit('change', pageNum)
} }
} }
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.row{ .row{
margin-top: 5px; margin-top: 5px;
.left-button{ .left-button{
padding: 0; padding: 0;
width: 24px; width: 24px;
} }
.right-button{ .right-button{
padding: 0; padding: 0;
width: 24px; width: 24px;
} }
.page-button{ .page-button{
padding: 0; padding: 0;
width: 24px; width: 24px;
} }
} }
</style> </style>

View File

@ -1,404 +1,395 @@
<template> <template>
<div> <div>
<search-form <search-form
:attrList="relationTableAttrList" :attrList="relationTableAttrList"
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
:loading="loading" :loading="loading"
size="small" size="small"
show-overflow="tooltip" show-overflow="tooltip"
show-header-overflow="tooltip" show-header-overflow="tooltip"
resizable resizable
:data="tableData" :data="tableData"
:max-height="`${windowHeight - windowHeightMinus}px`" :max-height="`${windowHeight - windowHeightMinus}px`"
row-id="_XID" row-id="_XID"
:scroll-y="{ enabled: false }" :scroll-y="{ enabled: false }"
:span-method="mergeRowMethod" :span-method="mergeRowMethod"
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="159px" :title="$t('cmdb.history.opreateTime')"></vxe-column>
<vxe-column field="user" width="100px" title="用户"> <vxe-column field="user" width="100px" :title="$t('cmdb.history.user')">
<template #header="{ column }"> <template #header="{ column }">
<span>{{ column.title }}</span> <span>{{ column.title }}</span>
<a-popover trigger="click" placement="bottom"> <a-popover trigger="click" placement="bottom">
<a-icon class="filter" type="filter" theme="filled" /> <a-icon class="filter" type="filter" theme="filled" />
<a slot="content"> <a slot="content">
<a-input <a-input
placeholder="输入筛选用户名" :placeholder="$t('cmdb.history.userTips')"
size="small" size="small"
v-model="queryParams.username" v-model="queryParams.username"
style="width: 200px" style="width: 200px"
allowClear allowClear
/> />
<a-button type="link" class="filterButton" @click="filterUser">筛选</a-button> <a-button type="link" class="filterButton" @click="filterUser">{{ $t('cmdb.history.filter') }}</a-button>
<a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button> <a-button type="link" class="filterResetButton" @click="filterUserReset">{{ $t('reset') }}</a-button>
</a> </a>
</a-popover> </a-popover>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operate_type" width="89px" title="操作"> <vxe-column field="operate_type" width="89px" :title="$t('operation')">
<template #header="{ column }"> <template #header="{ column }">
<span>{{ column.title }}</span> <span>{{ column.title }}</span>
<a-popover trigger="click" placement="bottom"> <a-popover trigger="click" placement="bottom">
<a-icon class="filter" type="filter" theme="filled" /> <a-icon class="filter" type="filter" theme="filled" />
<a slot="content"> <a slot="content">
<a-select <a-select
v-model="queryParams.operate_type" v-model="queryParams.operate_type"
placeholder="选择筛选操作" :placeholder="$t('cmdb.history.filterOperate')"
show-search show-search
style="width: 200px" style="width: 200px"
:filter-option="filterOption" :filter-option="filterOption"
allowClear allowClear
> >
<a-select-option <a-select-option
:value="Object.values(choice)[0]" :value="Object.values(choice)[0]"
:key="index" :key="index"
v-for="(choice, index) in relationTableAttrList[4].choice_value" v-for="(choice, index) in relationTableAttrList[4].choice_value"
> >
{{ Object.keys(choice)[0] }} {{ Object.keys(choice)[0] }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button> <a-button type="link" class="filterButton" @click="filterOperate">{{
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button> $t('cmdb.history.filter')
</a> }}</a-button>
</a-popover> <a-button type="link" class="filterResetButton" @click="filterOperateReset">{{ $t('reset') }}</a-button>
</template> </a>
<template #default="{ row }"> </a-popover>
<a-tag color="green" v-if="row.operate_type.includes('新增')"> </template>
{{ row.operate_type }} <template #default="{ row }">
</a-tag> <a-tag color="green" v-if="row.operate_type.includes($t('new'))">
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')"> {{ row.operate_type }}
{{ row.operate_type }} </a-tag>
</a-tag> <a-tag color="orange" v-else-if="row.operate_type.includes($t('update'))">
<a-tag color="red" v-else> {{ row.operate_type }}
{{ row.operate_type }} </a-tag>
</a-tag> <a-tag color="red" v-else>
</template> {{ row.operate_type }}
</vxe-column> </a-tag>
<vxe-column field="changeDescription" title="描述"> </template>
<template #default="{ row }"> </vxe-column>
<a-tag v-if="row && row.first"> <vxe-column field="changeDescription" :title="$t('desc')">
{{ <template #default="{ row }">
`${row.first.ci_type_alias}${ <a-tag v-if="row && row.first">
row.first.unique_alias && row.first[row.first.unique] {{
? `${row.first.unique_alias}${row.first[row.first.unique]}` `${row.first.ci_type_alias}${
: '' row.first.unique_alias && row.first[row.first.unique]
}` ? `${row.first.unique_alias}${row.first[row.first.unique]}`
}} : ''
</a-tag> }`
<a-tag v-if="row.changeDescription === '没有修改'"> }}
{{ row.relation_type_id }} </a-tag>
</a-tag> <a-tag v-if="row.changeDescription === $t('cmdb.history.noUpdate')">
<template v-else-if="row.operate_type.includes('修改')"> {{ row.relation_type_id }}
<a-tag :key="index" color="orange" v-for="(tag, index) in row.changeArr"> </a-tag>
{{ tag }} <template v-else-if="row.operate_type.includes($t('update'))">
</a-tag> <a-tag :key="index" color="orange" v-for="(tag, index) in row.changeArr">
</template> {{ tag }}
<a-tag color="green" v-else-if="row.operate_type.includes('新增')" :style="{ fontWeight: 'bolder' }"> </a-tag>
{{ row.relation_type_id }} </template>
</a-tag> <a-tag color="green" v-else-if="row.operate_type.includes($t('new'))" :style="{ fontWeight: 'bolder' }">
<a-tag color="red" v-else-if="row.operate_type.includes('删除')"> {{ row.relation_type_id }}
{{ row.relation_type_id }} </a-tag>
</a-tag> <a-tag color="red" v-else-if="row.operate_type.includes($t('delete'))">
<a-tag v-if="row && row.second"> {{ row.relation_type_id }}
{{ </a-tag>
`${row.second.ci_type_alias}${ <a-tag v-if="row && row.second">
row.second.unique_alias && row.second[row.second.unique] {{
? `${row.second.unique_alias}${row.second[row.second.unique]}` `${row.second.ci_type_alias}${
: '' row.second.unique_alias && row.second[row.second.unique]
}` ? `${row.second.unique_alias}${row.second[row.second.unique]}`
}} : ''
</a-tag> }`
</template> }}
</vxe-column> </a-tag>
</vxe-table> </template>
<pager </vxe-column>
:current-page.sync="queryParams.page" </vxe-table>
:page-size.sync="queryParams.page_size" <pager
:page-sizes="[50, 100, 200]" :current-page.sync="queryParams.page"
:total="total" :page-size.sync="queryParams.page_size"
:isLoading="loading" :page-sizes="[50, 100, 200]"
@change="onChange" :total="total"
@showSizeChange="onShowSizeChange" :isLoading="loading"
></pager> @change="onChange"
</div> @showSizeChange="onShowSizeChange"
</template> ></pager>
</div>
<script> </template>
import SearchForm from './searchForm'
import Pager from './pager.vue' <script>
import { getCITypes } from '@/modules/cmdb/api/CIType' import { mapState } from 'vuex'
import { getRelationTable, getUsers } from '@/modules/cmdb/api/history' import SearchForm from './searchForm'
import { getRelationTypes } from '@/modules/cmdb/api/relationType' import Pager from './pager.vue'
export default { import { getCITypes } from '@/modules/cmdb/api/CIType'
name: 'RelationTable', import { getRelationTable, getUsers } from '@/modules/cmdb/api/history'
components: { SearchForm, Pager }, import { getRelationTypes } from '@/modules/cmdb/api/relationType'
data() { export default {
return { name: 'RelationTable',
visible: false, components: { SearchForm, Pager },
loading: true, inject: ['reload'],
isExpand: false, data() {
tableData: [], return {
relationTypeList: null, visible: false,
total: 0, loading: true,
userList: [], isExpand: false,
operateTypeMap: new Map([ tableData: [],
['0', '新增'], relationTypeList: null,
['1', '删除'], total: 0,
['2', '修改'], userList: [],
]), queryParams: {
queryParams: { page: 1,
page: 1, page_size: 50,
page_size: 50, start: '',
start: '', end: '',
end: '', username: '',
username: '', first_ci_id: undefined,
first_ci_id: undefined, second_ci_id: undefined,
second_ci_id: undefined, operate_type: undefined,
operate_type: undefined, },
}, relationTableAttrList: [
relationTableAttrList: [ {
{ alias: this.$t('cmdb.ciType.date'),
alias: '日期', is_choice: false,
is_choice: false, name: 'datetime',
name: 'datetime', value_type: '3',
value_type: '3', },
}, {
{ alias: this.$t('cmdb.history.user'),
alias: '用户', is_choice: true,
is_choice: true, name: 'username',
name: 'username', value_type: '2',
value_type: '2', choice_value: [],
choice_value: [], },
}, {
{ alias: 'FirstCI_ID',
alias: 'FirstCI_ID', is_choice: false,
is_choice: false, name: 'first_ci_id',
name: 'first_ci_id', value_type: '2',
value_type: '2', choice_value: [],
choice_value: [], },
}, {
{ alias: 'SecondCI_ID',
alias: 'SecondCI_ID', is_choice: false,
is_choice: false, name: 'second_ci_id',
name: 'second_ci_id', value_type: '2',
value_type: '2', choice_value: [],
choice_value: [], },
}, {
{ alias: this.$t('operation'),
alias: '操作', is_choice: true,
is_choice: true, name: 'operate_type',
name: 'operate_type', value_type: '2',
value_type: '2', choice_value: [{ [this.$t('new')]: 0 }, { [this.$t('delete')]: 1 }, { [this.$t('update')]: 2 }],
choice_value: [{ 新增: 0 }, { 删除: 1 }, { 修改: 2 }], },
}, ],
], }
} },
}, async created() {
async created() { await Promise.all([this.getRelationTypes(), this.getUserList(), this.getTypes()])
await Promise.all([ await this.getTable(this.queryParams)
this.getRelationTypes(), },
this.getUserList(), updated() {
this.getTypes(), this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
]) },
await this.getTable(this.queryParams) computed: {
}, ...mapState(['locale']),
updated() { windowHeight() {
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 return this.$store.state.windowHeight
}, },
computed: { windowHeightMinus() {
windowHeight() { return this.isExpand ? 396 : 331
return this.$store.state.windowHeight },
}, operateTypeMap() {
windowHeightMinus() { return new Map([
return this.isExpand ? 396 : 331 ['0', this.$t('new')],
}, ['1', this.$t('delete')],
}, ['2', this.$t('update')],
methods: { ])
// 获取表格数据 },
async getTable(queryParams) { },
try { watch: {
this.loading = true locale() {
const res = await getRelationTable(queryParams) this.reload()
const tempArr = [] },
res.records.forEach((item) => { },
item[1].forEach((subItem) => { methods: {
subItem.operate_type = this.handleOperateType(subItem.operate_type) async getTable(queryParams) {
subItem.relation_type_id = this.handleRelationType(subItem.relation_type_id) try {
subItem.first = res.cis[String(subItem.first_ci_id)] this.loading = true
subItem.second = res.cis[String(subItem.second_ci_id)] const res = await getRelationTable(queryParams)
const tempObj = Object.assign(subItem, item[0]) const tempArr = []
tempArr.push(tempObj) res.records.forEach((item) => {
}) item[1].forEach((subItem) => {
}) subItem.operate_type = this.handleOperateType(subItem.operate_type)
this.total = res.total subItem.relation_type_id = this.handleRelationType(subItem.relation_type_id)
this.tableData = tempArr subItem.first = res.cis[String(subItem.first_ci_id)]
} finally { subItem.second = res.cis[String(subItem.second_ci_id)]
this.loading = false const tempObj = Object.assign(subItem, item[0])
} tempArr.push(tempObj)
}, })
// 获取用户列表 })
async getUserList() { this.total = res.total
const res = await getUsers() this.tableData = tempArr
this.userList = res.map((x) => { } finally {
const username = x.nickname this.loading = false
const obj = { }
[username]: username, },
} async getUserList() {
return obj const res = await getUsers()
}) this.userList = res.map((x) => {
this.relationTableAttrList[1].choice_value = this.userList const username = x.nickname
}, const obj = {
// 获取模型 [username]: username,
async getTypes() { }
const res = await getCITypes() return obj
const typesArr = [] })
res.ci_types.forEach((item) => { this.relationTableAttrList[1].choice_value = this.userList
const tempObj = {} },
tempObj[item.alias] = item.id async getTypes() {
if (item.alias) { const res = await getCITypes()
typesArr.push(tempObj) const typesArr = []
} res.ci_types.forEach((item) => {
}) const tempObj = {}
this.relationTableAttrList[2].choice_value = typesArr tempObj[item.alias] = item.id
this.relationTableAttrList[3].choice_value = typesArr if (item.alias) {
}, typesArr.push(tempObj)
// 获取关系 }
async getRelationTypes() { })
const res = await getRelationTypes() this.relationTableAttrList[2].choice_value = typesArr
const relationTypeMap = new Map() this.relationTableAttrList[3].choice_value = typesArr
res.forEach((item) => { },
relationTypeMap.set(item.id, item.name) async getRelationTypes() {
}) const res = await getRelationTypes()
this.relationTypeList = relationTypeMap const relationTypeMap = new Map()
}, res.forEach((item) => {
onShowSizeChange(size) { relationTypeMap.set(item.id, item.name)
this.queryParams.page_size = size })
this.queryParams.page = 1 this.relationTypeList = relationTypeMap
this.getTable(this.queryParams) },
}, onShowSizeChange(size) {
onChange(pageNum) { this.queryParams.page_size = size
this.queryParams.page = pageNum this.queryParams.page = 1
this.getTable(this.queryParams) this.getTable(this.queryParams)
}, },
handleExpandChange(expand) { onChange(pageNum) {
this.isExpand = expand this.queryParams.page = pageNum
}, this.getTable(this.queryParams)
// 处理查询 },
handleSearch(queryParams) { handleExpandChange(expand) {
this.queryParams = queryParams this.isExpand = expand
this.getTable(queryParams) },
}, handleSearch(queryParams) {
// 重置表单 this.queryParams = queryParams
searchFormReset() { this.getTable(queryParams)
this.queryParams = { },
page: 1, searchFormReset() {
page_size: 50, this.queryParams = {
start: '', page: 1,
end: '', page_size: 50,
username: '', start: '',
first_ci_id: undefined, end: '',
second_ci_id: undefined, username: '',
operate_type: undefined, first_ci_id: undefined,
} second_ci_id: undefined,
this.getTable(this.queryParams) operate_type: undefined,
}, }
// 转换operate_type this.getTable(this.queryParams)
handleOperateType(operate_type) { },
return this.operateTypeMap.get(operate_type) handleOperateType(operate_type) {
}, return this.operateTypeMap.get(operate_type)
// 转换relation_type_id },
handleRelationType(relation_type_id) { handleRelationType(relation_type_id) {
return this.relationTypeList.get(relation_type_id) return this.relationTypeList.get(relation_type_id)
}, },
// 合并表格 mergeRowMethod({ row, _rowIndex, column, visibleData }) {
mergeRowMethod({ row, _rowIndex, column, visibleData }) { const fields = ['created_at', 'user']
const fields = ['created_at', 'user'] const cellValue = row[column.property]
// 单元格值 = [.属性] 确定一格 const created_at = row['created_at']
const cellValue = row[column.property] if (column.property === 'created_at') {
const created_at = row['created_at'] if (cellValue && fields.includes(column.property)) {
// 如果单元格值不为空且作用域包含当前列 const prevRow = visibleData[_rowIndex - 1]
if (column.property === 'created_at') { let nextRow = visibleData[_rowIndex + 1]
if (cellValue && fields.includes(column.property)) { if (prevRow && prevRow[column.property] === cellValue) {
// 前一行 return { rowspan: 0, colspan: 0 }
const prevRow = visibleData[_rowIndex - 1] } else {
// 下一行 let countRowspan = 1
let nextRow = visibleData[_rowIndex + 1] while (nextRow && nextRow[column.property] === cellValue) {
// 如果前一行不为空且前一行单元格的值与cellValue相同 nextRow = visibleData[++countRowspan + _rowIndex]
if (prevRow && prevRow[column.property] === cellValue) { }
return { rowspan: 0, colspan: 0 } if (countRowspan > 1) {
} else { return { rowspan: countRowspan, colspan: 1 }
let countRowspan = 1 }
while (nextRow && nextRow[column.property] === cellValue) { }
nextRow = visibleData[++countRowspan + _rowIndex] }
} } else if (column.property === 'user') {
if (countRowspan > 1) { if (cellValue && fields.includes(column.property)) {
return { rowspan: countRowspan, colspan: 1 } const prevRow = visibleData[_rowIndex - 1]
} let nextRow = visibleData[_rowIndex + 1]
} if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
} return { rowspan: 0, colspan: 0 }
} else if (column.property === 'user') { } else {
if (cellValue && fields.includes(column.property)) { let countRowspan = 1
// 前一行 while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
const prevRow = visibleData[_rowIndex - 1] nextRow = visibleData[++countRowspan + _rowIndex]
// 下一行 }
let nextRow = visibleData[_rowIndex + 1] if (countRowspan > 1) {
// 如果前一行不为空且前一行单元格的值与cellValue相同 return { rowspan: countRowspan, colspan: 1 }
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) { }
return { rowspan: 0, colspan: 0 } }
} else { }
let countRowspan = 1 }
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) { },
nextRow = visibleData[++countRowspan + _rowIndex] filterUser() {
} this.queryParams.page = 1
if (countRowspan > 1) { this.queryParams.page_size = 50
return { rowspan: countRowspan, colspan: 1 } this.getTable(this.queryParams)
} },
} filterUserReset() {
} this.queryParams.page = 1
} this.queryParams.page_size = 50
}, this.queryParams.username = ''
filterUser() { this.getTable(this.queryParams)
this.queryParams.page = 1 },
this.queryParams.page_size = 50 filterOperate() {
this.getTable(this.queryParams) this.queryParams.page = 1
}, this.queryParams.page_size = 50
filterUserReset() { this.getTable(this.queryParams)
this.queryParams.page = 1 },
this.queryParams.page_size = 50 filterOperateReset() {
this.queryParams.username = '' this.queryParams.page = 1
this.getTable(this.queryParams) this.queryParams.page_size = 50
}, this.queryParams.operate_type = undefined
filterOperate() { this.getTable(this.queryParams)
this.queryParams.page = 1 },
this.queryParams.page_size = 50 filterOption(input, option) {
this.getTable(this.queryParams) return option.componentOptions.children[0].text.indexOf(input) >= 0
}, },
filterOperateReset() { },
this.queryParams.page = 1 }
this.queryParams.page_size = 50 </script>
this.queryParams.operate_type = undefined
this.getTable(this.queryParams) <style lang="less" scoped>
}, .filter {
filterOption(input, option) { margin-left: 10px;
return option.componentOptions.children[0].text.indexOf(input) >= 0 color: #c0c4cc;
}, cursor: pointer;
}, &:hover {
} color: #606266;
</script> }
}
<style lang="less" scoped> </style>
.filter {
margin-left: 10px;
color: #c0c4cc;
cursor: pointer;
&:hover {
color: #606266;
}
}
</style>

View File

@ -1,191 +1,195 @@
<template> <template>
<div> <div>
<a-form :colon="false"> <a-form :colon="false">
<a-row :gutter="24"> <a-row :gutter="24">
<a-col <a-col
:sm="24" :sm="24"
:md="12" :md="12"
:lg="12" :lg="12"
:xl="8" :xl="8"
v-for="attr in attrList.slice(0,3)" v-for="attr in attrList.slice(0,3)"
:key="attr.name" :key="attr.name"
> >
<a-form-item <a-form-item
:label="attr.alias || attr.name " :label="attr.alias || attr.name "
:labelCol="{span:4}" :labelCol="{span:4}"
:wrapperCol="{span:20}" :wrapperCol="{span:20}"
labelAlign="right" labelAlign="right"
> >
<a-select <a-select
v-model="queryParams[attr.name]" v-model="queryParams[attr.name]"
placeholder="请选择" :placeholder="$t('cmdb.history.pleaseSelect')"
v-if="attr.is_choice" v-if="attr.is_choice"
show-search show-search
:filter-option="filterOption" :filter-option="filterOption"
allowClear allowClear
> >
<a-select-option <a-select-option
:value="Object.values(choice)[0]" :value="Object.values(choice)[0]"
v-for="(choice, index) in attr.choice_value" v-for="(choice, index) in attr.choice_value"
:key="'Search_' + attr.name + index" :key="'Search_' + attr.name + index"
> >
{{ Object.keys(choice)[0] }} {{ Object.keys(choice)[0] }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-range-picker <a-range-picker
v-model="date" v-model="date"
@change="onChange" @change="onChange"
:style="{width:'100%'}" :style="{width:'100%'}"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
:placeholder="['开始时间', '结束时间']" :placeholder="[$t('cmdb.history.startTime'), $t('cmdb.history.endTime')]"
:show-time="{ :show-time="{
hideDisabledOptions: true, hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
}" }"
v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'" v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'"
/> />
<a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else /> <a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
</a-form-item> </a-form-item>
</a-col> </a-col>
<template v-if="expand && attrList.length >= 4"> <template v-if="expand && attrList.length >= 4">
<a-col <a-col
:sm="24" :sm="24"
:md="12" :md="12"
:lg="8" :lg="8"
:xl="8" :xl="8"
:key="'expand_' + item.name" :key="'expand_' + item.name"
v-for="item in attrList.slice(3)" v-for="item in attrList.slice(3)"
> >
<a-form-item <a-form-item
:label="item.alias || item.name" :label="item.alias || item.name"
:label-col="{ span: 4 }" :label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 20 }"
labelAlign="right" labelAlign="right"
> >
<a-select <a-select
v-model="queryParams[item.name]" v-model="queryParams[item.name]"
placeholder="请选择" :placeholder="$t('cmdb.history.pleaseSelect')"
v-if="item.is_choice" v-if="item.is_choice"
show-search show-search
:filter-option="filterOption" :filter-option="filterOption"
allowClear allowClear
> >
<a-select-option <a-select-option
:value="Object.values(choice)[0]" :value="Object.values(choice)[0]"
:key="'Search_' + item.name + index" :key="'Search_' + item.name + index"
v-for="(choice, index) in item.choice_value" v-for="(choice, index) in item.choice_value"
> >
{{ Object.keys(choice)[0] }} {{ Object.keys(choice)[0] }}
</a-select-option </a-select-option
> >
</a-select> </a-select>
<a-range-picker <a-range-picker
:style="{width:'100%'}" :style="{width:'100%'}"
@change="onChange" @change="onChange"
format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
:placeholder="['开始时间', '结束时间']" :placeholder="[$t('cmdb.history.startTime'), $t('cmdb.history.endTime')]"
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'" v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
:show-time="{ :show-time="{
hideDisabledOptions: true, hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
}" }"
/> />
<a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else/> <a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else/>
</a-form-item> </a-form-item>
</a-col> </a-col>
</template> </template>
</a-row> </a-row>
<a-row> <a-row>
<a-col :span="24" :style="{ textAlign: 'right' , marginBottom: '10px' }"> <a-col :span="24" :style="{ textAlign: 'right' , marginBottom: '10px' }">
<a-button type="primary" html-type="submit" @click="handleSearch"> <a-button type="primary" html-type="submit" @click="handleSearch">
查询 {{ $t('query') }}
</a-button> </a-button>
<a-button :style="{ marginLeft: '8px' }" @click="handleReset"> <a-button :style="{ marginLeft: '8px' }" @click="handleReset">
重置 {{ $t('reset') }}
</a-button> </a-button>
<a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 4"> <a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 4">
{{ expand?'隐藏':'展开' }} <a-icon :type="expand ? 'up' : 'down'" /> {{ expand?$t('hide'):$t('expand') }} <a-icon :type="expand ? 'up' : 'down'" />
</a> </a>
</a-col> </a-col>
</a-row> </a-row>
</a-form> </a-form>
</div> </div>
</template> </template>
<script> <script>
import moment from 'moment' import moment from 'moment'
import { valueTypeMap } from '../../../utils/const' import { valueTypeMap } from '../../../utils/const'
export default { export default {
name: 'SearchForm', name: 'SearchForm',
props: { props: {
attrList: { attrList: {
type: Array, type: Array,
required: true required: true
} }
}, },
data() { data() {
return { return {
valueTypeMap, expand: false,
expand: false, queryParams: {
queryParams: { page: 1,
page: 1, page_size: 50
page_size: 50 },
}, date: undefined
date: undefined }
} },
}, computed: {
watch: { valueTypeMap() {
queryParams: { return valueTypeMap()
deep: true, },
handler: function (val) { },
this.preProcessData() watch: {
this.$emit('searchFormChange', val) queryParams: {
} deep: true,
}, handler: function (val) {
}, this.preProcessData()
methods: { this.$emit('searchFormChange', val)
moment, }
handleSearch() { },
this.queryParams.page = 1 },
this.$emit('search', this.queryParams) methods: {
}, moment,
handleSearch() {
handleReset() { this.queryParams.page = 1
this.queryParams = { this.$emit('search', this.queryParams)
page: 1, },
page_size: 50
} handleReset() {
this.date = undefined this.queryParams = {
this.$emit('searchFormReset') page: 1,
}, page_size: 50
}
toggle() { this.date = undefined
this.expand = !this.expand this.$emit('searchFormReset')
this.$emit('expandChange', this.expand) },
},
toggle() {
onChange(date, dateString) { this.expand = !this.expand
this.queryParams.start = dateString[0] this.$emit('expandChange', this.expand)
this.queryParams.end = dateString[1] },
},
filterOption(input, option) { onChange(date, dateString) {
return ( this.queryParams.start = dateString[0]
option.componentOptions.children[0].text.indexOf(input) >= 0 this.queryParams.end = dateString[1]
) },
}, filterOption(input, option) {
preProcessData() { return (
Object.keys(this.queryParams).forEach(item => { option.componentOptions.children[0].text.indexOf(input) >= 0
if (this.queryParams[item] === '' || this.queryParams[item] === undefined) { )
delete this.queryParams[item] },
} preProcessData() {
}) Object.keys(this.queryParams).forEach(item => {
return this.queryParams if (this.queryParams[item] === '' || this.queryParams[item] === undefined) {
}, delete this.queryParams[item]
}, }
} })
</script> return this.queryParams
},
<style> },
}
</style> </script>
<style>
</style>

View File

@ -1,123 +1,123 @@
<template> <template>
<div> <div>
<vxe-table <vxe-table
show-overflow show-overflow
show-header-overflow show-header-overflow
stripe stripe
size="small" size="small"
class="ops-stripe-table" class="ops-stripe-table"
:data="tableData" :data="tableData"
v-bind="ci_id ? { maxHeight: `${windowHeight - 94}px` } : { height: `${windowHeight - 225}px` }" v-bind="ci_id ? { maxHeight: `${windowHeight - 94}px` } : { height: `${windowHeight - 225}px` }"
> >
<vxe-column field="trigger_name" title="触发器名称"> </vxe-column> <vxe-column field="trigger_name" :title="$t('cmdb.history.triggerName')"> </vxe-column>
<vxe-column field="type" title="类型"> <vxe-column field="type" :title="$t('type')">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.trigger && row.trigger.attr_id">日期属性</span> <span v-if="row.trigger && row.trigger.attr_id">{{ $t('cmdb.ciType.triggerDate') }}</span>
<span v-else-if="row.trigger && !row.trigger.attr_id">数据变更</span> <span v-else-if="row.trigger && !row.trigger.attr_id">{{ $t('cmdb.ciType.triggerDataChange') }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="事件"> <vxe-column :title="$t('cmdb.history.event')">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.operate_type === '0'">新增实例</span> <span v-if="row.operate_type === '0'">{{ $t('cmdb.ciType.addInstance') }}</span>
<span v-else-if="row.operate_type === '1'">删除实例</span> <span v-else-if="row.operate_type === '1'">{{ $t('cmdb.ciType.deleteInstance') }}</span>
<span v-else-if="row.operate_type === '2'">实例变更</span> <span v-else-if="row.operate_type === '2'">{{ $t('cmdb.ciType.changeInstance') }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="动作"> <vxe-column :title="$t('cmdb.history.action')">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.webhook">Webhook</span> <span v-if="row.webhook">Webhook</span>
<span v-else-if="row.notify">通知</span> <span v-else-if="row.notify">{{ $t('cmdb.ciType.notify') }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="状态"> <vxe-column :title="$t('cmdb.history.status')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag color="green" v-if="row.is_ok">已完成</a-tag> <a-tag color="green" v-if="row.is_ok">{{ $t('cmdb.history.done') }}</a-tag>
<a-tag color="red" v-else>未完成</a-tag> <a-tag color="red" v-else>{{ $t('cmdb.history.undone') }}</a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="触发时间"> <vxe-column :title="$t('cmdb.history.triggerTime')">
<template #default="{row}"> <template #default="{row}">
{{ row.updated_at || row.created_at }} {{ row.updated_at || row.created_at }}
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<div :style="{ textAlign: 'right' }" v-if="!ci_id"> <div :style="{ textAlign: 'right' }" v-if="!ci_id">
<a-pagination <a-pagination
size="small" size="small"
show-size-changer show-size-changer
show-quick-jumper show-quick-jumper
:page-size-options="pageSizeOptions" :page-size-options="pageSizeOptions"
:current="tablePage.currentPage" :current="tablePage.currentPage"
:total="tablePage.totalResult" :total="tablePage.totalResult"
:show-total="(total, range) => `共 ${total} 条记录`" :show-total="(total, range) => $t('cmdb.history.totalItems', { total: total })"
:page-size="tablePage.pageSize" :page-size="tablePage.pageSize"
:default-current="1" :default-current="1"
@change="pageOrSizeChange" @change="pageOrSizeChange"
@showSizeChange="pageOrSizeChange" @showSizeChange="pageOrSizeChange"
> >
</a-pagination> </a-pagination>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { getCiTriggers, getCiTriggersByCiId } from '../../../api/history' import { getCiTriggers, getCiTriggersByCiId } from '../../../api/history'
export default { export default {
name: 'TriggerTable', name: 'TriggerTable',
props: { props: {
ci_id: { ci_id: {
type: Number, type: Number,
default: null, default: null,
}, },
}, },
data() { data() {
return { return {
tableData: [], tableData: [],
tablePage: { tablePage: {
currentPage: 1, currentPage: 1,
pageSize: 50, pageSize: 50,
totalResult: 0, totalResult: 0,
}, },
pageSizeOptions: ['50', '100', '200'], pageSizeOptions: ['50', '100', '200'],
} }
}, },
computed: { computed: {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
}, },
mounted() { mounted() {
this.updateTableData() this.updateTableData()
}, },
methods: { methods: {
updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) { updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) {
const params = { page: currentPage, page_size: pageSize } const params = { page: currentPage, page_size: pageSize }
if (this.ci_id) { if (this.ci_id) {
getCiTriggersByCiId(this.ci_id, params).then((res) => { getCiTriggersByCiId(this.ci_id, params).then((res) => {
this.tableData = res.items.map((item) => { this.tableData = res.items.map((item) => {
return { return {
...item, ...item,
trigger: res.id2trigger[item.trigger_id], trigger: res.id2trigger[item.trigger_id],
} }
}) })
}) })
} else { } else {
getCiTriggers(params).then((res) => { getCiTriggers(params).then((res) => {
this.tableData = res?.result || [] this.tableData = res?.result || []
this.tablePage = { this.tablePage = {
...this.tablePage, ...this.tablePage,
currentPage: res.page, currentPage: res.page,
pageSize: res.page_size, pageSize: res.page_size,
totalResult: res.numfound, totalResult: res.numfound,
} }
}) })
} }
}, },
pageOrSizeChange(currentPage, pageSize) { pageOrSizeChange(currentPage, pageSize) {
this.updateTableData(currentPage, pageSize) this.updateTableData(currentPage, pageSize)
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -1,478 +1,484 @@
<template> <template>
<div> <div>
<search-form <search-form
:attrList="typeTableAttrList" :attrList="typeTableAttrList"
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
:loading="loading" :loading="loading"
resizable resizable
:data="tableData" :data="tableData"
:max-height="`${windowHeight - windowHeightMinus}px`" :max-height="`${windowHeight - windowHeightMinus}px`"
row-id="_XID" row-id="_XID"
size="small" size="small"
:row-config="{isHover: true}" :row-config="{ isHover: true }"
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="159px" :title="$t('cmdb.history.opreateTime')"></vxe-column>
<vxe-column field="user" width="116px" title="用户"></vxe-column> <vxe-column field="user" width="116px" :title="$t('cmdb.history.user')"></vxe-column>
<vxe-column field="operate_type" width="135px" title="操作"> <vxe-column field="operate_type" width="135px" :title="$t('operation')">
<template #header="{ column }"> <template #header="{ column }">
<span>{{ column.title }}</span> <span>{{ column.title }}</span>
<a-popover trigger="click" placement="bottom"> <a-popover trigger="click" placement="bottom">
<a-icon class="filter" type="filter" theme="filled" /> <a-icon class="filter" type="filter" theme="filled" />
<a slot="content"> <a slot="content">
<a-select <a-select
v-model="queryParams.operate_type" v-model="queryParams.operate_type"
placeholder="选择筛选操作" :placeholder="$t('cmdb.history.filterOperate')"
show-search show-search
style="width: 200px" style="width: 200px"
:filter-option="filterOption" :filter-option="filterOption"
allowClear allowClear
> >
<a-select-option <a-select-option
:value="Object.values(choice)[0]" :value="Object.values(choice)[0]"
:key="index" :key="index"
v-for="(choice, index) in typeTableAttrList[1].choice_value" v-for="(choice, index) in typeTableAttrList[1].choice_value"
> >
{{ Object.keys(choice)[0] }} {{ Object.keys(choice)[0] }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button> <a-button type="link" class="filterButton" @click="filterOperate">{{
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button> $t('cmdb.history.filter')
</a> }}</a-button>
</a-popover> <a-button type="link" class="filterResetButton" @click="filterOperateReset">{{ $t('reset') }}</a-button>
</template> </a>
<template #default="{ row }"> </a-popover>
<a-tag color="green" v-if="row.operate_type.includes('新增')"> </template>
{{ row.operate_type }} <template #default="{ row }">
</a-tag> <a-tag color="green" v-if="row.operate_type.includes($t('new'))">
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')"> {{ row.operate_type }}
{{ row.operate_type }} </a-tag>
</a-tag> <a-tag color="orange" v-else-if="row.operate_type.includes($t('update'))">
<a-tag color="red" v-else> {{ row.operate_type }}
{{ row.operate_type }} </a-tag>
</a-tag> <a-tag color="red" v-else>
</template> {{ row.operate_type }}
</vxe-column> </a-tag>
<vxe-column field="type_id" title="模型" width="150px"> </template>
<template #default="{ row }"> </vxe-column>
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id }} <vxe-column field="type_id" :title="$t('cmdb.ciType.ciType')" width="150px">
</template> <template #default="{ row }">
</vxe-column> {{ row.operate_type === $t('cmdb.history.deleteCIType') ? row.change.alias : row.type_id }}
<vxe-column field="changeDescription" title="描述"> </template>
<template #default="{ row }"> </vxe-column>
<p style="color:rgba(0, 0, 0, 0.65);" v-if="row.changeDescription === '没有修改'"> <vxe-column field="changeDescription" :title="$t('desc')">
{{ row.changeDescription }} <template #default="{ row }">
</p> <p style="color:rgba(0, 0, 0, 0.65);" v-if="row.changeDescription === $t('cmdb.history.noUpdate')">
<template v-else-if="row.operate_type.includes('修改')"> {{ row.changeDescription }}
<p :key="index" style="color:#fa8c16;" v-for="(tag, index) in row.changeArr"> </p>
{{ tag }} <template v-else-if="row.operate_type.includes($t('update'))">
</p> <p :key="index" style="color:#fa8c16;" v-for="(tag, index) in row.changeArr">
</template> {{ tag }}
<p class="more-tag" style="color:#52c41a;" v-else-if="row.operate_type.includes('新增')"> </p>
{{ row.changeDescription }} </template>
</p> <p class="more-tag" style="color:#52c41a;" v-else-if="row.operate_type.includes($t('new'))">
<p style="color:#f5222d;" v-else-if="row.operate_type.includes('删除')"> {{ row.changeDescription }}
{{ row.changeDescription }} </p>
</p> <p style="color:#f5222d;" v-else-if="row.operate_type.includes($t('delete'))">
</template> {{ row.changeDescription }}
</vxe-column> </p>
</vxe-table> </template>
<a-row class="row" type="flex" justify="end"> </vxe-column>
<a-col> </vxe-table>
<a-pagination <a-row class="row" type="flex" justify="end">
size="small" <a-col>
v-model="current" <a-pagination
:page-size-options="pageSizeOptions" size="small"
:total="numfound" v-model="current"
show-size-changer :page-size-options="pageSizeOptions"
:page-size="pageSize" :total="numfound"
@change="onChange" show-size-changer
@showSizeChange="onShowSizeChange" :page-size="pageSize"
:show-total="(total) => `共 ${total} 条记录`" @change="onChange"
> @showSizeChange="onShowSizeChange"
</a-pagination> :show-total="(total) => $t('cmdb.history.totalItems', { total: total })"
</a-col> >
</a-row> </a-pagination>
</div> </a-col>
</template> </a-row>
</div>
<script> </template>
import _ from 'lodash'
import SearchForm from './searchForm' <script>
import { getCITypesTable, getUsers } from '@/modules/cmdb/api/history' import _ from 'lodash'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { mapState } from 'vuex'
import { getRelationTypes } from '@/modules/cmdb/api/relationType' import SearchForm from './searchForm'
export default { import { getCITypesTable, getUsers } from '@/modules/cmdb/api/history'
name: 'TypeTable', import { getCITypes } from '@/modules/cmdb/api/CIType'
components: { SearchForm }, import { getRelationTypes } from '@/modules/cmdb/api/relationType'
data() { export default {
return { name: 'TypeTable',
loading: true, components: { SearchForm },
relationTypeList: null, inject: ['reload'],
operateTypeMap: new Map([ data() {
['0', '新增模型'], return {
['1', '修改模型'], loading: true,
['2', '删除模型'], relationTypeList: null,
['3', '新增属性'], typeList: null,
['4', '修改属性'], userList: [],
['5', '删除属性'], pageSizeOptions: ['50', '100', '200'],
['6', '新增触发器'], isExpand: false,
['7', '修改触发器'], current: 1,
['8', '删除触发器'], pageSize: 50,
['9', '新增联合唯一'], total: 0,
['10', '修改联合唯一'], numfound: 0,
['11', '删除联合唯一'], tableData: [],
['12', '新增关系'], queryParams: {
['13', '删除关系'], page: 1,
]), page_size: 50,
typeList: null, type_id: undefined,
userList: [], operate_type: undefined,
typeTableAttrList: [ },
{ typeTableAttrList: [
alias: '模型', {
is_choice: true, alias: this.$t('cmdb.ciType.ciType'),
name: 'type_id', is_choice: true,
value_type: '2', name: 'type_id',
choice_value: [], value_type: '2',
}, choice_value: [],
{ },
alias: '操作', {
is_choice: true, alias: this.$t('operation'),
name: 'operate_type', is_choice: true,
value_type: '2', name: 'operate_type',
choice_value: [ value_type: '2',
{ 新增模型: 0 }, choice_value: [
{ 修改模型: 1 }, { [this.$t('cmdb.history.addCIType')]: 0 },
{ 删除模型: 2 }, { [this.$t('cmdb.history.updateCIType')]: 1 },
{ 新增属性: 3 }, { [this.$t('cmdb.history.deleteCIType')]: 2 },
{ 修改属性: 4 }, { [this.$t('cmdb.history.addAttribute')]: 3 },
{ 删除属性: 5 }, { [this.$t('cmdb.history.updateAttribute')]: 4 },
{ 新增触发器: 6 }, { [this.$t('cmdb.history.deleteAttribute')]: 5 },
{ 修改触发器: 7 }, { [this.$t('cmdb.history.addTrigger')]: 6 },
{ 删除触发器: 8 }, { [this.$t('cmdb.history.updateTrigger')]: 7 },
{ 新增联合唯一: 9 }, { [this.$t('cmdb.history.deleteTrigger')]: 8 },
{ 修改联合唯一: 10 }, { [this.$t('cmdb.history.addUniqueConstraint')]: 9 },
{ 删除联合唯一: 11 }, { [this.$t('cmdb.history.updateUniqueConstraint')]: 10 },
{ 新增关系: 12 }, { [this.$t('cmdb.history.deleteUniqueConstraint')]: 11 },
{ 删除关系: 13 }, { [this.$t('cmdb.history.addRelation')]: 12 },
], { [this.$t('cmdb.history.deleteRelation')]: 13 },
}, ],
], },
pageSizeOptions: ['50', '100', '200'], ],
isExpand: false, }
current: 1, },
pageSize: 50, async created() {
total: 0, await Promise.all([this.getRelationTypes(), this.getTypes(), this.getUserList()])
numfound: 0, await this.getTable(this.queryParams)
tableData: [], },
queryParams: { updated() {
page: 1, this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
page_size: 50, },
type_id: undefined, computed: {
operate_type: undefined, ...mapState(['locale']),
}, windowHeight() {
} return this.$store.state.windowHeight
}, },
async created() { windowHeightMinus() {
await Promise.all([ return this.isExpand ? 396 : 335
this.getRelationTypes(), },
this.getTypes(), operateTypeMap() {
this.getUserList(), return new Map([
]) ['0', this.$t('cmdb.history.addCIType')],
await this.getTable(this.queryParams) ['1', this.$t('cmdb.history.updateCIType')],
}, ['2', this.$t('cmdb.history.deleteCIType')],
updated() { ['3', this.$t('cmdb.history.addAttribute')],
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 ['4', this.$t('cmdb.history.updateAttribute')],
}, ['5', this.$t('cmdb.history.deleteAttribute')],
computed: { ['6', this.$t('cmdb.history.addTrigger')],
windowHeight() { ['7', this.$t('cmdb.history.updateTrigger')],
return this.$store.state.windowHeight ['8', this.$t('cmdb.history.deleteTrigger')],
}, ['9', this.$t('cmdb.history.addUniqueConstraint')],
windowHeightMinus() { ['10', this.$t('cmdb.history.updateUniqueConstraint')],
return this.isExpand ? 396 : 335 ['11', this.$t('cmdb.history.deleteUniqueConstraint')],
}, ['12', this.$t('cmdb.history.addRelation')],
}, ['13', this.$t('cmdb.history.deleteRelation')],
watch: { ])
current(val) { },
this.queryParams.page = val },
}, watch: {
pageSize(val) { current(val) {
this.queryParams.page_size = val this.queryParams.page = val
}, },
}, pageSize(val) {
methods: { this.queryParams.page_size = val
// 获取表格数据 },
async getTable(queryParams) { locale() {
try { this.reload()
this.loading = true },
const res = await getCITypesTable(queryParams) },
res.result.forEach((item) => { methods: {
this.handleChangeDescription(item, item.operate_type) async getTable(queryParams) {
item.operate_type = this.handleOperateType(item.operate_type) try {
item.type_id = this.handleTypeId(item.type_id) this.loading = true
item.uid = this.handleUID(item.uid) const res = await getCITypesTable(queryParams)
}) res.result.forEach((item) => {
this.tableData = res.result this.handleChangeDescription(item, item.operate_type)
this.pageSize = res.page_size item.operate_type = this.handleOperateType(item.operate_type)
this.current = res.page item.type_id = this.handleTypeId(item.type_id)
this.numfound = res.numfound item.uid = this.handleUID(item.uid)
this.total = res.total })
console.log(this.tableData) this.tableData = res.result
} finally { this.pageSize = res.page_size
this.loading = false this.current = res.page
} this.numfound = res.numfound
}, this.total = res.total
// 获取模型 console.log(this.tableData)
async getTypes() { } finally {
const res = await getCITypes() this.loading = false
const typesArr = [] }
const typesMap = new Map() },
res.ci_types.forEach((item) => { async getTypes() {
const tempObj = {} const res = await getCITypes()
tempObj[item.alias] = item.id const typesArr = []
if (item.alias) { const typesMap = new Map()
typesMap.set(item.id, item.alias) res.ci_types.forEach((item) => {
typesArr.push(tempObj) const tempObj = {}
} tempObj[item.alias] = item.id
}) if (item.alias) {
this.typeList = typesMap typesMap.set(item.id, item.alias)
// 设置模型options选项 typesArr.push(tempObj)
this.typeTableAttrList[0].choice_value = typesArr }
}, })
// 获取用户列表 this.typeList = typesMap
async getUserList() { this.typeTableAttrList[0].choice_value = typesArr
const res = await getUsers() },
const userListMap = new Map() async getUserList() {
res.forEach((item) => { const res = await getUsers()
userListMap.set(item.uid, item.nickname) const userListMap = new Map()
}) res.forEach((item) => {
this.userList = userListMap userListMap.set(item.uid, item.nickname)
}, })
// 获取关系 this.userList = userListMap
async getRelationTypes() { },
const res = await getRelationTypes() async getRelationTypes() {
const relationTypeMap = new Map() const res = await getRelationTypes()
res.forEach((item) => { const relationTypeMap = new Map()
relationTypeMap.set(item.id, item.name) res.forEach((item) => {
}) relationTypeMap.set(item.id, item.name)
this.relationTypeList = relationTypeMap })
}, this.relationTypeList = relationTypeMap
onChange(current) { },
this.current = current onChange(current) {
this.getTable(this.queryParams) this.current = current
}, this.getTable(this.queryParams)
onShowSizeChange(current, size) { },
this.current = 1 onShowSizeChange(current, size) {
this.pageSize = size this.current = 1
this.getTable(this.queryParams) this.pageSize = size
}, this.getTable(this.queryParams)
handleExpandChange(expand) { },
this.isExpand = expand handleExpandChange(expand) {
}, this.isExpand = expand
// 处理查询 },
handleSearch(queryParams) { handleSearch(queryParams) {
this.current = 1 this.current = 1
this.queryParams = queryParams this.queryParams = queryParams
this.getTable(this.queryParams) this.getTable(this.queryParams)
}, },
// 重置表单 searchFormReset() {
searchFormReset() { this.queryParams = {
this.queryParams = { page: 1,
page: 1, page_size: 50,
page_size: 50, type_id: undefined,
type_id: undefined, operate_type: undefined,
operate_type: undefined, }
} this.getTable(this.queryParams)
this.getTable(this.queryParams) },
}, handleOperateType(operate_type) {
// 转换operate_type return this.operateTypeMap.get(operate_type)
handleOperateType(operate_type) { },
return this.operateTypeMap.get(operate_type) handleTypeId(type_id) {
}, return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id
// 转换type_id },
handleTypeId(type_id) { handleUID(uid) {
return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id return this.userList.get(uid)
}, },
// 转换uid handleRelationType(relation_type_id) {
handleUID(uid) { return this.relationTypeList.get(relation_type_id)
return this.userList.get(uid) },
}, handleChangeDescription(item, operate_type) {
// 转换relation_type_id switch (operate_type) {
handleRelationType(relation_type_id) { // add CIType
return this.relationTypeList.get(relation_type_id) case '0': {
}, item.changeDescription = this.$t('cmdb.history.addCIType') + ': ' + item.change.alias
// 处理改变描述 break
handleChangeDescription(item, operate_type) { }
switch (operate_type) { // update CIType
// 新增模型 case '1': {
case '0': { item.changeArr = []
item.changeDescription = '新增模型:' + item.change.alias for (const key in item.change.old) {
break const newVal = item.change.new[key]
} const oldVal = item.change.old[key]
// 修改模型 if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
case '1': { if (oldVal === null) {
item.changeArr = [] const str = ` [ ${key} : ${newVal || '""'} ] `
for (const key in item.change.old) { item.changeDescription += str
const newVal = item.change.new[key] item.changeArr.push(str)
const oldVal = item.change.old[key] } else {
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') { const str = ` [ ${key} : ${oldVal || '""'} -> ${newVal || '""'} ] `
if (oldVal === null) { item.changeDescription += ` [ ${key} : ${oldVal || '""'} -> ${newVal || '""'} ] `
const str = ` [ ${key} : 改为 ${newVal || '""'} ] ` item.changeArr.push(str)
item.changeDescription += str }
item.changeArr.push(str) }
} else { }
const str = ` [ ${key} : ${oldVal || '""'} 改为 ${newVal || '""'} ] ` if (!item.changeDescription) item.changeDescription = this.$t('cmdb.history.noModifications')
item.changeDescription += ` [ ${key} : ${oldVal || '""'} 改为 ${newVal || '""'} ] ` break
item.changeArr.push(str) }
} // delete CIType
} case '2': {
} item.changeDescription = this.$t('cmdb.history.addCIType') + ': ' + `${item.change.alias}`
if (!item.changeDescription) item.changeDescription = '没有修改' break
break }
} // add Attribute
// 删除模型 case '3': {
case '2': { item.changeDescription = `${this.$t('cmdb.history.attr')}${item.change.alias}`
item.changeDescription = `删除模型${item.change.alias}` break
break }
} // update Attribute
// 新增属性 case '4': {
case '3': { item.changeArr = []
item.changeDescription = `属性名${item.change.alias}` for (const key in item.change.old) {
break if (!_.isEqual(item.change.new[key], item.change.old[key]) && key !== 'updated_at') {
} let newStr = item.change.new[key]
// 修改属性 let oldStr = item.change.old[key]
case '4': { if (key === 'choice_value') {
item.changeArr = [] newStr = newStr ? newStr.map((item) => item[0]).join(',') : ''
for (const key in item.change.old) { oldStr = oldStr ? oldStr.map((item) => item[0]).join(',') : ''
if (!_.isEqual(item.change.new[key], item.change.old[key]) && key !== 'updated_at') { }
let newStr = item.change.new[key] if (Object.prototype.toString.call(newStr) === '[object Object]') {
let oldStr = item.change.old[key] newStr = JSON.stringify(newStr)
if (key === 'choice_value') { }
newStr = newStr ? newStr.map((item) => item[0]).join(',') : '' if (Object.prototype.toString.call(oldStr) === '[object Object]') {
oldStr = oldStr ? oldStr.map((item) => item[0]).join(',') : '' oldStr = JSON.stringify(oldStr)
} }
if (Object.prototype.toString.call(newStr) === '[object Object]') { const str = `${key} : ${oldStr ? ` ${oldStr || '""'} ` : ''} -> ${newStr || '""'}`
newStr = JSON.stringify(newStr) item.changeDescription += ` [ ${str} ] `
} item.changeArr.push(str)
if (Object.prototype.toString.call(oldStr) === '[object Object]') { }
oldStr = JSON.stringify(oldStr) }
} if (!item.changeDescription) item.changeDescription = this.$t('cmdb.history.noModifications')
const str = `${key} : ${oldStr ? ` ${oldStr || '""'} ` : ''} 改为 ${newStr || '""'}` break
item.changeDescription += ` [ ${str} ] ` }
item.changeArr.push(str) // delete Attribute
} case '5': {
} item.changeDescription = `${this.$t('delete')}${item.change.alias}`
if (!item.changeDescription) item.changeDescription = '没有修改' break
break }
} // add trigger
// 删除属性 case '6': {
case '5': { item.changeDescription = this.$t('cmdb.history.noModifications', {
item.changeDescription = `删除${item.change.alias}` attr_id: item.change.attr_id,
break before_days: item.change.option.before_days,
} subject: item.change.option.subject,
// 新增触发器 body: item.change.option.body,
case '6': { notify_at: item.change.option.notify_at,
item.changeDescription = `属性ID${item.change.attr_id}提前${item.change.option.before_days}主题${item.change.option.subject}\n内容${item.change.option.body}\n通知时间${item.change.option.notify_at}` })
break break
} }
// 修改触发器 // update trigger
case '7': { case '7': {
item.changeArr = [] item.changeArr = []
for (const key in item.change.old.option) { for (const key in item.change.old.option) {
const newVal = item.change.new.option[key] const newVal = item.change.new.option[key]
const oldVal = item.change.old.option[key] const oldVal = item.change.old.option[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
const str = ` [ ${key} : ${oldVal} 改为 ${newVal} ] ` const str = ` [ ${key} : ${oldVal} -> ${newVal} ] `
item.changeDescription += str item.changeDescription += str
item.changeArr.push(str) item.changeArr.push(str)
} }
} }
if (!item.changeDescription) item.changeDescription = '没有修改' if (!item.changeDescription) item.changeDescription = this.$t('cmdb.history.noModifications')
break break
} }
// 删除触发器 // delete trigger
case '8': { case '8': {
item.changeDescription = `属性ID${item.change.attr_id}提前${item.change.option.before_days}主题${item.change.option.subject}\n内容${item.change.option.body}\n通知时间${item.change.option.notify_at}` item.changeDescription = this.$t('cmdb.history.noModifications', {
break attr_id: item.change.attr_id,
} before_days: item.change.option.before_days,
// 新增联合唯一 subject: item.change.option.subject,
case '9': { body: item.change.option.body,
item.changeDescription = `属性id[${item.change.attr_ids}]` notify_at: item.change.option.notify_at,
break })
} break
// 修改联合唯一 }
case '10': { // add unique constraint
item.changeArr = [] case '9': {
const oldVal = item.change.old.attr_ids item.changeDescription = `${this.$t('cmdb.history.attrId')}[${item.change.attr_ids}]`
const newVal = item.change.new.attr_ids break
const str = `属性id[${oldVal}] -> [${newVal}]` }
item.changeDescription = str // update unique constraint
item.changeArr.push(str) case '10': {
break item.changeArr = []
} const oldVal = item.change.old.attr_ids
// 删除联合唯一 const newVal = item.change.new.attr_ids
case '11': { const str = `${this.$t('cmdb.history.attrId')}[${oldVal}] -> [${newVal}]`
item.changeDescription = `属性id[${item.change.attr_ids}]` item.changeDescription = str
break item.changeArr.push(str)
} break
// 新增关系 }
case '12': { // delete unique constraint
item.changeDescription = `新增${item.change.parent.alias} -> ${this.handleRelationType( case '11': {
item.change.relation_type_id item.changeDescription = `${this.$t('cmdb.history.attrId')}[${item.change.attr_ids}]`
)} -> ${item.change.child.alias}` break
break }
} // add relation
// 删除关系 case '12': {
case '13': { item.changeDescription = `${this.$t('new')}${item.change.parent.alias} -> ${this.handleRelationType(
item.changeDescription = `删除${item.change.parent_id.alias} -> ${this.handleRelationType( item.change.relation_type_id
item.change.relation_type_id )} -> ${item.change.child.alias}`
)} -> ${item.change.child.alias}` break
break }
} // delete relation
} case '13': {
}, item.changeDescription = `${this.$t('delete')}${item.change.parent_id.alias} -> ${this.handleRelationType(
filterOperate() { item.change.relation_type_id
this.queryParams.page = 1 )} -> ${item.change.child.alias}`
this.queryParams.page_size = 50 break
this.getTable(this.queryParams) }
}, }
filterOperateReset() { },
this.queryParams.page = 1 filterOperate() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.queryParams.operate_type = undefined this.queryParams.page_size = 50
this.getTable(this.queryParams) this.getTable(this.queryParams)
}, },
filterOption(input, option) { filterOperateReset() {
return option.componentOptions.children[0].text.indexOf(input) >= 0 this.queryParams.page = 1
}, this.queryParams.page_size = 50
}, this.queryParams.operate_type = undefined
} this.getTable(this.queryParams)
</script> },
filterOption(input, option) {
<style lang="less" scoped> return option.componentOptions.children[0].text.indexOf(input) >= 0
.row { },
margin-top: 5px; },
} }
.filter { </script>
margin-left: 10px;
color: #c0c4cc; <style lang="less" scoped>
cursor: pointer; .row {
&:hover { margin-top: 5px;
color: #606266; }
} .filter {
} margin-left: 10px;
.more-tag { color: #c0c4cc;
max-width: 100%; cursor: pointer;
overflow: hidden; &:hover {
text-overflow:ellipsis; color: #606266;
} }
p { }
margin-bottom: 0; .more-tag {
} max-width: 100%;
</style> overflow: hidden;
text-overflow: ellipsis;
}
p {
margin-bottom: 0;
}
</style>

View File

@ -2,10 +2,10 @@
<div class="cmdb-preference" :style="{ height: `${windowHeight - 40}px` }"> <div class="cmdb-preference" :style="{ height: `${windowHeight - 40}px` }">
<div class="cmdb-preference-left"> <div class="cmdb-preference-left">
<div class="cmdb-preference-left-card"> <div class="cmdb-preference-left-card">
<span class="cmdb-preference-left-card-title">我的订阅</span> <span class="cmdb-preference-left-card-title">{{ $t('cmdb.preference.mySub') }}</span>
<span <span
class="cmdb-preference-left-card-content" class="cmdb-preference-left-card-content"
><ops-icon type="cmdb-ci" :style="{ marginRight: '5px' }" />资源数据 ><ops-icon type="cmdb-ci" :style="{ marginRight: '5px' }" />{{ $t('cmdb.menu.ciTable') }}:
<a-badge <a-badge
showZero showZero
:count="self.instance.length" :count="self.instance.length"
@ -20,7 +20,7 @@
> >
<span <span
class="cmdb-preference-left-card-content" class="cmdb-preference-left-card-content"
><ops-icon type="cmdb-tree" :style="{ marginRight: '5px' }" />资源层级 ><ops-icon type="cmdb-tree" :style="{ marginRight: '5px' }" />{{ $t('cmdb.menu.ciTree') }}:
<a-badge <a-badge
showZero showZero
:count="self.tree.length" :count="self.tree.length"
@ -66,14 +66,14 @@
</div> </div>
<span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span> <span class="cmdb-preference-group-content-title">{{ ciType.alias || ciType.name }}</span>
<span class="cmdb-preference-group-content-action"> <span class="cmdb-preference-group-content-action">
<a-tooltip title="取消订阅"> <a-tooltip :title="$t('cmdb.preference.cancelSub')">
<span <span
@click="unsubscribe(ciType, group.type)" @click="unsubscribe(ciType, group.type)"
><ops-icon type="cmdb-preference-cancel-subscribe" /> ><ops-icon type="cmdb-preference-cancel-subscribe" />
</span> </span>
</a-tooltip> </a-tooltip>
<a-divider type="vertical" :style="{ margin: '0 3px' }" /> <a-divider type="vertical" :style="{ margin: '0 3px' }" />
<a-tooltip title="编辑订阅"> <a-tooltip :title="$t('cmdb.preference.editSub')">
<span <span
@click="openSubscribeSetting(ciType, `${index + 1}`)" @click="openSubscribeSetting(ciType, `${index + 1}`)"
><ops-icon ><ops-icon
@ -126,7 +126,8 @@
<div class="cmdb-preference-colleague"> <div class="cmdb-preference-colleague">
<template v-if="type_id2users[item.id] && type_id2users[item.id].length"> <template v-if="type_id2users[item.id] && type_id2users[item.id].length">
<span <span
>{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length }}位同事已订阅</span >{{ type_id2users[item.id].length > 99 ? '99+' : type_id2users[item.id].length
}}{{ $t('cmdb.preference.peopleSub') }}</span
> >
<span class="cmdb-preference-colleague-name"> <span class="cmdb-preference-colleague-name">
<span v-for="uid in type_id2users[item.id].slice(0, 4)" :key="uid"> <span v-for="uid in type_id2users[item.id].slice(0, 4)" :key="uid">
@ -135,11 +136,11 @@
<span class="cmdb-preference-colleague-ellipsis" v-if="type_id2users[item.id].length > 4">...</span> <span class="cmdb-preference-colleague-ellipsis" v-if="type_id2users[item.id].length > 4">...</span>
</span> </span>
</template> </template>
<span v-else :style="{ marginLeft: 'auto' }">暂无同事订阅</span> <span v-else :style="{ marginLeft: 'auto' }">{{ $t('cmdb.preference.noSub') }}</span>
</div> </div>
<div class="cmdb-preference-progress"> <div class="cmdb-preference-progress">
<div class="cmdb-preference-progress-info"> <div class="cmdb-preference-progress-info">
<span>自动发现</span> <span>{{ $t('cmdb.menu.ad') }}</span>
<span>{{ item.integrity }}%</span> <span>{{ item.integrity }}%</span>
</div> </div>
<div class="cmdb-preference-progress-gray"> <div class="cmdb-preference-progress-gray">
@ -148,15 +149,13 @@
</div> </div>
<a-divider :style="{ margin: '10px 0 3px 0' }" /> <a-divider :style="{ margin: '10px 0 3px 0' }" />
<div class="cmdb-preference-footor-subscribed" v-if="item.is_subscribed"> <div class="cmdb-preference-footor-subscribed" v-if="item.is_subscribed">
<span <span><a-icon type="clock-circle" :style="{ marginRight: '3px' }" />{{ getsubscribedDays(item) }}</span>
><a-icon type="clock-circle" :style="{ marginRight: '3px' }" />{{ getsubscribedDays(item) }}订阅</span
>
<span> <span>
<a-tooltip title="取消订阅"> <a-tooltip :title="$t('cmdb.preference.cancelSub')">
<span @click="unsubscribe(item)"><ops-icon type="cmdb-preference-cancel-subscribe" /> </span> <span @click="unsubscribe(item)"><ops-icon type="cmdb-preference-cancel-subscribe" /> </span>
</a-tooltip> </a-tooltip>
<a-divider type="vertical" :style="{ margin: '0 3px' }" /> <a-divider type="vertical" :style="{ margin: '0 3px' }" />
<a-tooltip title="编辑订阅"> <a-tooltip :title="$t('cmdb.preference.editSub')">
<span @click="openSubscribeSetting(item)"><ops-icon type="cmdb-preference-subscribe"/></span> <span @click="openSubscribeSetting(item)"><ops-icon type="cmdb-preference-subscribe"/></span>
</a-tooltip> </a-tooltip>
</span> </span>
@ -164,7 +163,9 @@
<div v-else class="cmdb-preference-footor-unsubscribed"> <div v-else class="cmdb-preference-footor-unsubscribed">
<span <span
@click="openSubscribeSetting(item)" @click="openSubscribeSetting(item)"
><ops-icon :style="{ marginRight: '3px' }" type="cmdb-preference-subscribe" />订阅</span ><ops-icon :style="{ marginRight: '3px' }" type="cmdb-preference-subscribe" />{{
$t('cmdb.preference.sub')
}}</span
> >
</div> </div>
</div> </div>
@ -242,7 +243,7 @@ export default {
} }
if (!group.id) { if (!group.id) {
group.id = -1 group.id = -1
group.name = '其他' group.name = this.$t('other')
} }
}) })
this.citypeData = ciTypeGroup this.citypeData = ciTypeGroup
@ -251,7 +252,7 @@ export default {
this.type_id2users = type_id2users this.type_id2users = type_id2users
const _myPreferences = [ const _myPreferences = [
{ {
name: '资源数据', name: this.$t('cmdb.menu.ciTable'),
ci_types: self.instance.map((item) => { ci_types: self.instance.map((item) => {
const _find = pref.find((ci) => ci.id === item) const _find = pref.find((ci) => ci.id === item)
return _find return _find
@ -260,7 +261,7 @@ export default {
type: 'ci', type: 'ci',
}, },
{ {
name: '资源层级', name: this.$t('cmdb.menu.ciTree'),
ci_types: self.tree.map((item) => { ci_types: self.tree.map((item) => {
const _find = pref.find((ci) => ci.id === item) const _find = pref.find((ci) => ci.id === item)
return _find return _find
@ -285,23 +286,28 @@ export default {
moment.duration(moment().diff(moment(subscribedTime))) moment.duration(moment().diff(moment(subscribedTime)))
const day = moment().diff(moment(subscribedTime), 'day') const day = moment().diff(moment(subscribedTime), 'day')
if (day > 0 && day < 1) { if (day > 0 && day < 1) {
return `${moment().diff(moment(subscribedTime), 'hour')}小时前` return `${moment().diff(moment(subscribedTime), 'hour')}` + this.$t('cmdb.preference.hoursAgo')
} else if (day >= 1 && day <= 31) { } else if (day >= 1 && day <= 31) {
return `${day}天前` return `${day} ` + this.$t('cmdb.preference.daysAgo')
} else if (day > 31 && day < 365) { } else if (day > 31 && day < 365) {
return `${moment().diff(moment(subscribedTime), 'month')}月前` return `${moment().diff(moment(subscribedTime), 'month')}` + this.$t('cmdb.preference.monthsAgo')
} else if (day >= 365) { } else if (day >= 365) {
return `${moment().diff(moment(subscribedTime), 'year')}年前` return `${moment().diff(moment(subscribedTime), 'year')}` + this.$t('cmdb.preference.yearsAgo')
} }
return '刚刚' return this.$t('cmdb.preference.just')
}, },
unsubscribe(ciType, type = 'all') { unsubscribe(ciType, type = 'all') {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: `确认取消订阅 ${ciType.alias || ciType.name} ${ content:
type !== 'all' ? `${type === 'ci' ? '资源数据' : '资源层级'}` : '' that.$t('cmdb.preference.confirmcancelSub') +
} `, ` ${ciType.alias || ciType.name} ${
type !== 'all'
? that.$t('cmdb.preference.of') +
`${type === 'ci' ? this.$t('cmdb.menu.ciTable') : that.$t('cmdb.menu.ciTree')}`
: ''
} `,
onOk() { onOk() {
const promises = [] const promises = []
if (type === 'all' || type === 'ci') { if (type === 'all' || type === 'ci') {
@ -320,7 +326,7 @@ export default {
localStorage.setItem('ops_ci_typeid', '') localStorage.setItem('ops_ci_typeid', '')
} }
} }
that.$message.success('取消订阅成功') that.$message.success(that.$t('cmdb.preference.cancelSubSuccess'))
that.resetRoute() that.resetRoute()
}) })
}, },

View File

@ -12,12 +12,12 @@
isEdit = true isEdit = true
} }
" "
>新增业务关系</a-button >{{ $t('cmdb.preference_relation.newServiceTree') }}</a-button
> >
<template v-else> <template v-else>
<a-input v-model="newRelationViewName" placeholder="新增业务关系名"></a-input> <a-input v-model="newRelationViewName" :placeholder="$t('cmdb.preference_relation.serviceTreeName')"></a-input>
<a-checkbox v-model="is_public">公开</a-checkbox> <a-checkbox v-model="is_public">{{ $t('cmdb.preference_relation.public') }}</a-checkbox>
<a-button type="primary" size="small" @click="handleSaveRelationViews">保存</a-button> <a-button type="primary" size="small" @click="handleSaveRelationViews">{{ $t('save') }}</a-button>
<a-button <a-button
type="primary" type="primary"
size="small" size="small"
@ -29,10 +29,10 @@
newRelationViewName = '' newRelationViewName = ''
} }
" "
>取消</a-button >{{ $t('cancel') }}</a-button
> >
</template> </template>
<a-button type="primary" size="small" @click="handleSave">保存布局</a-button> <a-button type="primary" size="small" @click="handleSave">{{ $t('cmdb.preference_relation.saveLayout') }}</a-button>
</a-space> </a-space>
</div> </div>
<SeeksRelationGraph v-if="isPullConfig" ref="ciTypeRelationGraph" :options="graphOptions"> <SeeksRelationGraph v-if="isPullConfig" ref="ciTypeRelationGraph" :options="graphOptions">
@ -59,7 +59,7 @@
> >
<div class="relation-views"> <div class="relation-views">
<h3 :style="{ padding: '10px 0 0 20px' }">{{ view }}</h3> <h3 :style="{ padding: '10px 0 0 20px' }">{{ view }}</h3>
<a-popconfirm :title="`确认删除 ${view}`" @confirm="confirmDelete(view)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete', { name: `${view}` })" @confirm="confirmDelete(view)">
<a class="relation-views-close"><a-icon type="close"/></a> <a class="relation-views-close"><a-icon type="close"/></a>
</a-popconfirm> </a-popconfirm>
<div :style="{ height: '250px' }"> <div :style="{ height: '250px' }">
@ -246,7 +246,7 @@ export default {
if (e.target.checked) { if (e.target.checked) {
const graph = this.$refs.ciTypeRelationGraph const graph = this.$refs.ciTypeRelationGraph
if (!graph.getNodeById(node.id).targetTo.length) { if (!graph.getNodeById(node.id).targetTo.length) {
this.$message.warning(`${node.text} 不存在子节点不能形成业务关系请重新选择`) this.$message.warning(`${node.text} ` + this.$t('cmdb.preference_relation.childNodesNotFound'))
return return
} }
if (!this.checkedNodes.length) { if (!this.checkedNodes.length) {
@ -278,7 +278,7 @@ export default {
const idFrom = startNode.targetFrom.findIndex((item) => item.id === node.id) const idFrom = startNode.targetFrom.findIndex((item) => item.id === node.id)
const idTo = endNode.targetTo.findIndex((item) => item.id === node.id) const idTo = endNode.targetTo.findIndex((item) => item.id === node.id)
if (idFrom <= -1 && idTo <= -1) { if (idFrom <= -1 && idTo <= -1) {
this.$message.warning(`${node.text} 不能与当前选中节点形成视图请重新选择`) this.$message.warning(`${node.text} ` + this.$t('cmdb.preference_relation.tips1'))
return return
} }
if (idFrom > -1) { if (idFrom > -1) {
@ -290,11 +290,11 @@ export default {
}, },
async handleSaveRelationViews() { async handleSaveRelationViews() {
if (!this.newRelationViewName) { if (!this.newRelationViewName) {
this.$message.warning('请输入新增业务关系名!') this.$message.warning(this.$t('cmdb.preference_relation.tips2'))
return return
} }
if (this.checkedNodes.length < 2) { if (this.checkedNodes.length < 2) {
this.$message.warning('请选择至少两个节点!') this.$message.warning(this.$t('cmdb.preference_relation.tips3'))
return return
} }
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
@ -341,7 +341,7 @@ export default {
} }
}), }),
}).then((res) => { }).then((res) => {
this.$message.success('保存成功!') this.$message.success(this.$t('saveSuccess'))
}) })
} }
}, },

View File

@ -1,18 +1,18 @@
<template> <template>
<CustomDrawer <CustomDrawer
:closable="false" :closable="false"
:title="drawerTitle" :title="$t('cmdb.preference_relation.newServiceTree')"
:visible="drawerVisible" :visible="drawerVisible"
@close="onClose" @close="onClose"
placement="right" placement="right"
width="30%" width="30%"
> >
<a-form :form="form" :layout="formLayout" @submit="handleSubmit"> <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="业务关系名"> <a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('cmdb.preference_relation.serviceTreeName')">
<a-input <a-input
name="name" name="name"
placeholder placeholder
v-decorator="['name', { rules: [{ required: true, message: '请输入类型名' }] }]" v-decorator="['name', { rules: [{ required: true, message: $t('cmdb.preference_relation.tips2')}] }]"
/> />
</a-form-item> </a-form-item>
@ -21,8 +21,8 @@
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">提交</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('submit') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -35,7 +35,6 @@ export default {
name: 'RelationViewForm', name: 'RelationViewForm',
data() { data() {
return { return {
drawerTitle: '新增业务关系',
drawerVisible: false, drawerVisible: false,
formLayout: 'vertical', formLayout: 'vertical',
crIds: [], crIds: [],
@ -95,7 +94,7 @@ export default {
createRelationView(data) { createRelationView(data) {
data.cr_ids = this.crIds data.cr_ids = this.crIds
subscribeRelationView(data).then(res => { subscribeRelationView(data).then(res => {
this.$message.success('新增成功!') this.$message.success(this.$t('addSuccess'))
this.onClose() this.onClose()
this.$emit('refresh') this.$emit('refresh')
}) })

View File

@ -1,7 +1,7 @@
<template> <template>
<a-card :bordered="false"> <a-card :bordered="false">
<div class="action-btn"> <div class="action-btn">
<a-button @click="handleCreate" type="primary" style="margin-bottom: 15px;">新增关系类型</a-button> <a-button @click="handleCreate" type="primary" style="margin-bottom: 15px;">{{ $t('cmdb.relation_type.addRelationType') }}</a-button>
</div> </div>
<vxe-table <vxe-table
ref="relationTypeTable" ref="relationTypeTable"
@ -16,20 +16,20 @@
> >
<vxe-table-column <vxe-table-column
field="name" field="name"
title="名称" :title="$t('name')"
:edit-render="{ name: 'input', attrs: { type: 'text' }, events: { keyup: customCloseEdit } }" :edit-render="{ name: 'input', attrs: { type: 'text' }, events: { keyup: customCloseEdit } }"
></vxe-table-column> ></vxe-table-column>
<vxe-table-column field="updateTime" title="更新时间"> <vxe-table-column field="updateTime" :title="$t('updated_at')">
<template #default="{row}"> <template #default="{row}">
{{ row.updated_at || row.created_at }} {{ row.updated_at || row.created_at }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="operation" title="操作" align="center"> <vxe-table-column field="operation" :title="$t('operation')" align="center">
<template #default="{row}"> <template #default="{row}">
<template> <template>
<a><a-icon type="edit" @click="handleEdit(row)"/></a> <a><a-icon type="edit" @click="handleEdit(row)"/></a>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-popconfirm title="确认删除吗?" @confirm="handleDelete(row)" okText="" cancelText=""> <a-popconfirm :title="$t('confirmDelete')" @confirm="handleDelete(row)" :okText="$t('yes')" :cancelText="$t('no')">
<a :style="{ color: 'red' }"><a-icon type="delete"/></a> <a :style="{ color: 'red' }"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</template> </template>
@ -50,6 +50,7 @@ import {
export default { export default {
name: 'RelationType', name: 'RelationType',
components: {},
data() { data() {
return { return {
tableData: [], tableData: [],
@ -97,14 +98,14 @@ export default {
}, },
updateRelationType(id, data) { updateRelationType(id, data) {
updateRelationType(id, data).then((res) => { updateRelationType(id, data).then((res) => {
this.$message.success('更新成功!') this.$message.success(this.$t('updateSuccess'))
this.loadData() this.loadData()
}) })
}, },
createRelationType(data) { createRelationType(data) {
addRelationType(data).then((res) => { addRelationType(data).then((res) => {
this.$message.success('新增成功!') this.$message.success(this.$t('addSuccess'))
this.loadData() this.loadData()
}) })
}, },
@ -113,12 +114,12 @@ export default {
}, },
deleteRelationType(id) { deleteRelationType(id) {
deleteRelationType(id).then((res) => { deleteRelationType(id).then((res) => {
this.$message.success('删除成功!') this.$message.success(this.$t('deleteSuccess'))
this.loadData() this.loadData()
}) })
}, },
customCloseEdit(value, $event) { customCloseEdit(value, $event) {
// 回车结束编辑 // enter, close edit
if ($event.keyCode === 13) { if ($event.keyCode === 13) {
const $table = this.$refs.relationTypeTable const $table = this.$refs.relationTypeTable
$table.clearActived() $table.clearActived()

File diff suppressed because it is too large Load Diff

View File

@ -11,12 +11,24 @@
> >
<div :style="{ width: '100%' }" id="add-table-modal"> <div :style="{ width: '100%' }" id="add-table-modal">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<!-- <a-input
v-model="expression"
class="ci-searchform-expression"
:style="{ width, marginBottom: '10px' }"
:placeholder="placeholder"
@focus="
() => {
isFocusExpression = true
}
"
/> -->
<SearchForm <SearchForm
ref="searchForm" ref="searchForm"
:typeId="addTypeId" :typeId="addTypeId"
:preferenceAttrList="preferenceAttrList" :preferenceAttrList="preferenceAttrList"
@refresh="handleSearch" @refresh="handleSearch"
/> />
<!-- <a @click="handleSearch"><a-icon type="search"/></a> -->
<vxe-table <vxe-table
ref="xTable" ref="xTable"
row-id="_id" row-id="_id"
@ -52,7 +64,14 @@
:total="totalNumber" :total="totalNumber"
show-quick-jumper show-quick-jumper
:page-size="50" :page-size="50"
:show-total="(total, range) => `当前${range[0]}-${range[1]} 共 ${total}条记录`" :show-total="
(total, range) =>
$t('pagination.total', {
range0: range[0],
range1: range[1],
total,
})
"
:style="{ textAlign: 'right', marginTop: '10px' }" :style="{ textAlign: 'right', marginTop: '10px' }"
@change="handleChangePage" @change="handleChangePage"
/> />
@ -94,7 +113,7 @@ export default {
return this.$store.state.windowHeight - 250 return this.$store.state.windowHeight - 250
}, },
placeholder() { placeholder() {
return this.isFocusExpression ? 'q=os_version:centos&sort=os_version' : '表达式搜索' return this.isFocusExpression ? this.$t('cmdb.serviceTreetips1') : this.$t('cmdb.serviceTreetips2')
}, },
width() { width() {
return this.isFocusExpression ? '500px' : '100px' return this.isFocusExpression ? '500px' : '100px'
@ -103,6 +122,7 @@ export default {
watch: {}, watch: {},
methods: { methods: {
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) { async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
console.log(ciObj, ciId, addTypeId, type)
this.visible = true this.visible = true
this.ciObj = ciObj this.ciObj = ciObj
this.ciId = ciId this.ciId = ciId
@ -121,6 +141,10 @@ export default {
}, },
async fetchData(isInit) { async fetchData(isInit) {
this.loading = true this.loading = true
// if (isInit) {
// const subscribed = await getSubscribeAttributes(this.addTypeId)
// this.preferenceAttrList = subscribed.attributes // 已经订阅的全部列
// }
let sort, fuzzySearch, expression, exp let sort, fuzzySearch, expression, exp
if (!isInit) { if (!isInit) {
fuzzySearch = this.$refs['searchForm'].fuzzySearch fuzzySearch = this.$refs['searchForm'].fuzzySearch
@ -179,7 +203,7 @@ export default {
await batchUpdateCIRelationParents(ciIds, [this.ciId]) await batchUpdateCIRelationParents(ciIds, [this.ciId])
} }
setTimeout(() => { setTimeout(() => {
this.$message.success('添加成功!') this.$message.success(this.$t('addSuccess'))
this.handleClose() this.handleClose()
this.$emit('reload') this.$emit('reload')
}, 500) }, 500)

View File

@ -1,8 +1,8 @@
<template> <template>
<a-dropdown :trigger="['contextmenu']"> <a-dropdown :trigger="['contextmenu']">
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)"> <a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)">
<a-menu-item v-for="item in menuList" :key="item.id">新增{{ item.alias }}</a-menu-item> <a-menu-item v-for="item in menuList" :key="item.id">{{ $t('new') }} {{ item.alias }}</a-menu-item>
<a-menu-item v-if="showDelete" key="delete">删除节点</a-menu-item> <a-menu-item v-if="showDelete" key="delete">{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item>
</a-menu> </a-menu>
<div <div
:style="{ :style="{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff