mirror of https://github.com/veops/cmdb.git
feat(cmdb-ui):service tree grant (#425)
This commit is contained in:
parent
482d34993b
commit
42feb4b862
|
@ -1,41 +1,41 @@
|
|||
import i18n from '@/lang'
|
||||
|
||||
export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
export const expList = () => {
|
||||
return [
|
||||
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
|
||||
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
|
||||
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
|
||||
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
|
||||
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
|
||||
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
|
||||
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
|
||||
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = () => {
|
||||
return [
|
||||
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
|
||||
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
|
||||
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
|
||||
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
|
||||
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
|
||||
]
|
||||
}
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
{ value: '2', label: '>=' },
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
||||
import i18n from '@/lang'
|
||||
|
||||
export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
export const expList = () => {
|
||||
return [
|
||||
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
|
||||
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
|
||||
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
|
||||
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
|
||||
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
|
||||
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
|
||||
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
|
||||
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = () => {
|
||||
return [
|
||||
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
|
||||
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
|
||||
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
|
||||
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
|
||||
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
|
||||
]
|
||||
}
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
{ value: '2', label: '>=' },
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
||||
|
|
|
@ -1,332 +1,346 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '70px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '130px', '--custom-height': '24px' }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '24px' }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...getExpListByProperty(item.property), ...advancedExpList]"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input-group
|
||||
size="small"
|
||||
compact
|
||||
v-else-if="item.exp === 'range' || item.exp === '~range'"
|
||||
:style="{ width: '175px' }"
|
||||
>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.min"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('min')"
|
||||
/>
|
||||
~
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.max"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('max')"
|
||||
/>
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': '24px' }"
|
||||
v-model="item.compareType"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else-if="item.exp !== 'value' && item.exp !== '~value'"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:style="{ width: '175px' }"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<div class="table-filter-add">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
ruleTypeList() {
|
||||
return ruleTypeList()
|
||||
},
|
||||
expList() {
|
||||
return expList()
|
||||
},
|
||||
advancedExpList() {
|
||||
return advancedExpList()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
return this.expList
|
||||
}
|
||||
return this.expList
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleAddRuleAt(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 0, {
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<div>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '70px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '130px', '--custom-height': '24px' }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '24px' }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...getExpListByProperty(item.property), ...advancedExpList]"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input-group
|
||||
size="small"
|
||||
compact
|
||||
v-else-if="item.exp === 'range' || item.exp === '~range'"
|
||||
:style="{ width: '175px' }"
|
||||
>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.min"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('min')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
~
|
||||
<a-input
|
||||
class="ops-input"
|
||||
size="small"
|
||||
v-model="item.max"
|
||||
:style="{ width: '78px' }"
|
||||
:placeholder="$t('max')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': '24px' }"
|
||||
v-model="item.compareType"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else-if="item.exp !== 'value' && item.exp !== '~value'"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:style="{ width: '175px' }"
|
||||
:disabled="disabled"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<template v-if="!disabled">
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
<div class="table-filter-add" v-if="!disabled">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
ruleTypeList() {
|
||||
return ruleTypeList()
|
||||
},
|
||||
expList() {
|
||||
return expList()
|
||||
},
|
||||
advancedExpList() {
|
||||
return advancedExpList()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
return this.expList
|
||||
}
|
||||
return this.expList
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleAddRuleAt(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 0, {
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -1,296 +1,302 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
v-if="isDropdown"
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
overlayClassName="table-filter"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<slot name="popover_item">
|
||||
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
|
||||
</slot>
|
||||
<template slot="content">
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:554px">
|
||||
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
||||
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
||||
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-else
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Expression from './expression.vue'
|
||||
import { advancedExpList, compareTypeList } from './constants'
|
||||
|
||||
export default {
|
||||
name: 'FilterComp',
|
||||
components: { Expression },
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottomRight',
|
||||
},
|
||||
isDropdown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
v-if="isDropdown"
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
overlayClassName="table-filter"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<slot name="popover_item">
|
||||
<a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
|
||||
</slot>
|
||||
<template slot="content">
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:554px">
|
||||
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
||||
<a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
|
||||
<a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<Expression
|
||||
:needAddHere="needAddHere"
|
||||
v-else
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Expression from './expression.vue'
|
||||
import { advancedExpList, compareTypeList } from './constants'
|
||||
|
||||
export default {
|
||||
name: 'FilterComp',
|
||||
components: { Expression },
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottomRight',
|
||||
},
|
||||
isDropdown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
needAddHere: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,179 +1,183 @@
|
|||
<template>
|
||||
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
|
||||
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
|
||||
<slot name="one"></slot>
|
||||
</div>
|
||||
|
||||
<div class="spliter-wrap">
|
||||
<a-button
|
||||
v-show="collapsable"
|
||||
:icon="isExpanded ? 'left' : 'right'"
|
||||
class="collapse-btn"
|
||||
@click="handleExpand"
|
||||
></a-button>
|
||||
<div
|
||||
class="pane-trigger"
|
||||
@mousedown="handleMouseDown"
|
||||
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
|
||||
<slot name="two"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 90,
|
||||
},
|
||||
|
||||
paneLengthPixel: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
appName: {
|
||||
type: String,
|
||||
default: 'viewer',
|
||||
},
|
||||
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f0f2f5',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerLeftOffset: 0, // 鼠标距滑动器左(顶)侧偏移量
|
||||
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
|
||||
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
|
||||
: false,
|
||||
parentContainer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lengthType() {
|
||||
return this.direction === 'row' ? 'width' : 'height'
|
||||
},
|
||||
|
||||
minLengthType() {
|
||||
return this.direction === 'row' ? 'minWidth' : 'minHeight'
|
||||
},
|
||||
|
||||
paneLengthValue1() {
|
||||
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthValue2() {
|
||||
const rest = 100 - this.paneLengthPercent
|
||||
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthPercent() {
|
||||
const clientRectWidth = this.parentContainer
|
||||
? this.parentContainer.clientWidth
|
||||
: document.documentElement.getBoundingClientRect().width
|
||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(newValue) {
|
||||
if (newValue) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
document.addEventListener('mouseup', this.handleMouseUp)
|
||||
if (this.direction === 'row') {
|
||||
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
|
||||
} else {
|
||||
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
|
||||
}
|
||||
},
|
||||
|
||||
// 按下滑动器后移动鼠标
|
||||
handleMouseMove(e) {
|
||||
this.isExpanded = false
|
||||
this.$emit('expand', this.isExpanded)
|
||||
const clientRect = this.$refs.splitPane.getBoundingClientRect()
|
||||
let paneLengthPixel = 0
|
||||
|
||||
if (this.direction === 'row') {
|
||||
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
} else {
|
||||
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
}
|
||||
|
||||
if (paneLengthPixel < this.min) {
|
||||
paneLengthPixel = this.min
|
||||
}
|
||||
if (paneLengthPixel > this.max) {
|
||||
paneLengthPixel = this.max
|
||||
}
|
||||
|
||||
this.$emit('update:paneLengthPixel', paneLengthPixel)
|
||||
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
||||
handleExpand() {
|
||||
this.isExpanded = !this.isExpanded
|
||||
this.$emit('expand', this.isExpanded)
|
||||
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
<template>
|
||||
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
|
||||
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
|
||||
<slot name="one"></slot>
|
||||
</div>
|
||||
|
||||
<div class="spliter-wrap">
|
||||
<a-button
|
||||
v-show="collapsable"
|
||||
:icon="isExpanded ? 'left' : 'right'"
|
||||
class="collapse-btn"
|
||||
@click="handleExpand"
|
||||
></a-button>
|
||||
<div
|
||||
class="pane-trigger"
|
||||
@mousedown="handleMouseDown"
|
||||
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
|
||||
<slot name="two"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 90,
|
||||
},
|
||||
|
||||
paneLengthPixel: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
appName: {
|
||||
type: String,
|
||||
default: 'viewer',
|
||||
},
|
||||
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f0f2f5',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerLeftOffset: 0, // 鼠标距滑动器左(顶)侧偏移量
|
||||
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
|
||||
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
|
||||
: false,
|
||||
parentContainer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lengthType() {
|
||||
return this.direction === 'row' ? 'width' : 'height'
|
||||
},
|
||||
|
||||
minLengthType() {
|
||||
return this.direction === 'row' ? 'minWidth' : 'minHeight'
|
||||
},
|
||||
|
||||
paneLengthValue1() {
|
||||
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthValue2() {
|
||||
const rest = 100 - this.paneLengthPercent
|
||||
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthPercent() {
|
||||
const clientRectWidth = this.parentContainer
|
||||
? this.parentContainer.clientWidth
|
||||
: document.documentElement.getBoundingClientRect().width
|
||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(newValue) {
|
||||
if (newValue) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const paneLengthPixel = localStorage.getItem(`${this.appName}-paneLengthPixel`)
|
||||
if (paneLengthPixel) {
|
||||
this.$emit('update:paneLengthPixel', Number(paneLengthPixel))
|
||||
}
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
document.addEventListener('mouseup', this.handleMouseUp)
|
||||
if (this.direction === 'row') {
|
||||
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
|
||||
} else {
|
||||
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
|
||||
}
|
||||
},
|
||||
|
||||
// 按下滑动器后移动鼠标
|
||||
handleMouseMove(e) {
|
||||
this.isExpanded = false
|
||||
this.$emit('expand', this.isExpanded)
|
||||
const clientRect = this.$refs.splitPane.getBoundingClientRect()
|
||||
let paneLengthPixel = 0
|
||||
|
||||
if (this.direction === 'row') {
|
||||
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
} else {
|
||||
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
}
|
||||
|
||||
if (paneLengthPixel < this.min) {
|
||||
paneLengthPixel = this.min
|
||||
}
|
||||
if (paneLengthPixel > this.max) {
|
||||
paneLengthPixel = this.max
|
||||
}
|
||||
|
||||
this.$emit('update:paneLengthPixel', paneLengthPixel)
|
||||
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
||||
handleExpand() {
|
||||
this.isExpanded = !this.isExpanded
|
||||
this.$emit('expand', this.isExpanded)
|
||||
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import SplitPane from './SplitPane'
|
||||
export default SplitPane
|
||||
import SplitPane from './SplitPane'
|
||||
export default SplitPane
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
.split-pane {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.split-pane .pane-two {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.split-pane .pane-trigger {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-one {
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
.split-pane.column .pane {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-trigger {
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=')
|
||||
1px 50% no-repeat #f0f2f5;
|
||||
}
|
||||
|
||||
.split-pane .collapse-btn {
|
||||
width: 25px;
|
||||
height: 70px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: calc(50% - 35px);
|
||||
background-color: #f0f2f5;
|
||||
border-color: transparent;
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
.anticon {
|
||||
color: #7cb0fe;
|
||||
}
|
||||
}
|
||||
|
||||
.split-pane .spliter-wrap {
|
||||
position: relative;
|
||||
}
|
||||
.split-pane {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.split-pane .pane-two {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.split-pane .pane-trigger {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-one {
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
.split-pane.column .pane {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-trigger {
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=')
|
||||
1px 50% no-repeat #f0f2f5;
|
||||
}
|
||||
|
||||
.split-pane .collapse-btn {
|
||||
width: 25px;
|
||||
height: 70px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: calc(50% - 35px);
|
||||
background-color: #f0f2f5;
|
||||
border-color: transparent;
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
.anticon {
|
||||
color: #7cb0fe;
|
||||
}
|
||||
}
|
||||
|
||||
.split-pane .spliter-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export default {
|
|||
deleting: 'Deleting',
|
||||
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
|
||||
grant: 'Grant',
|
||||
revoke: 'Revoke',
|
||||
login_at: 'Login At',
|
||||
logout_at: 'Logout At',
|
||||
createSuccess: 'Create Success',
|
||||
|
|
|
@ -25,6 +25,7 @@ export default {
|
|||
deleting: '正在删除',
|
||||
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
|
||||
grant: '授权',
|
||||
revoke: '回收',
|
||||
login_at: '登录时间',
|
||||
logout_at: '登出时间',
|
||||
createSuccess: '创建成功',
|
||||
|
|
|
@ -223,3 +223,10 @@ export function deleteCiTypeInheritance(data) {
|
|||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeIcons() {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types/icons',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,150 +1,150 @@
|
|||
<template>
|
||||
<div class="ci-type-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="filterTableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<ReadCheckbox
|
||||
v-if="['read'].includes(col.split('_')[0])"
|
||||
:value="row[col.split('_')[0]]"
|
||||
:valueKey="col"
|
||||
:rid="row.rid"
|
||||
@openReadGrantModal="() => openReadGrantModal(col, row)"
|
||||
/>
|
||||
<a-checkbox v-else-if="col === 'grant'" :checked="row[col]" @click="clickGrant(col, row)"></a-checkbox>
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-else v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
|
||||
<a-icon type="loading" /> {{ $t('loading') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { permMap } from './constants.js'
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import ReadCheckbox from './readCheckbox.vue'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeGrant',
|
||||
components: { ReadCheckbox },
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'ci_type',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filterTableData() {
|
||||
const _tableData = this.tableData.filter((data) => {
|
||||
const _intersection = _.intersection(
|
||||
Object.keys(data),
|
||||
this.columns.map((col) => col.split('_')[0])
|
||||
)
|
||||
return _intersection && _intersection.length
|
||||
})
|
||||
return _.uniqBy(_tableData, (item) => item.rid)
|
||||
},
|
||||
columns() {
|
||||
if (this.grantType === 'ci_type') {
|
||||
return ['config', 'grant']
|
||||
}
|
||||
return ['read_attr', 'read_ci', 'create', 'update', 'delete']
|
||||
},
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
async handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
await revokeCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
openReadGrantModal(col, row) {
|
||||
this.$emit('openReadGrantModal', col, row)
|
||||
},
|
||||
clickGrant(col, row, rowIndex) {
|
||||
if (!row[col]) {
|
||||
this.handleChange({ target: { checked: true } }, col, row)
|
||||
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
|
||||
this.$set(this.tableData, _idx, { ...this.tableData[_idx], grant: true })
|
||||
} else {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }),
|
||||
onOk() {
|
||||
that.handleChange({ target: { checked: false } }, col, row)
|
||||
const _idx = that.tableData.findIndex((item) => item.rid === row.rid)
|
||||
that.$set(that.tableData, _idx, { ...that.tableData[_idx], grant: false })
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-type-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="ci-type-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="filterTableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<ReadCheckbox
|
||||
v-if="['read'].includes(col.split('_')[0])"
|
||||
:value="row[col.split('_')[0]]"
|
||||
:valueKey="col"
|
||||
:rid="row.rid"
|
||||
@openReadGrantModal="() => openReadGrantModal(col, row)"
|
||||
/>
|
||||
<a-checkbox v-else-if="col === 'grant'" :checked="row[col]" @click="clickGrant(col, row)"></a-checkbox>
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-else v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
|
||||
<a-icon type="loading" /> {{ $t('loading') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>{{ $t('noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { permMap } from './constants.js'
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import ReadCheckbox from './readCheckbox.vue'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeGrant',
|
||||
components: { ReadCheckbox },
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'ci_type',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filterTableData() {
|
||||
const _tableData = this.tableData.filter((data) => {
|
||||
const _intersection = _.intersection(
|
||||
Object.keys(data),
|
||||
this.columns.map((col) => col.split('_')[0])
|
||||
)
|
||||
return _intersection && _intersection.length
|
||||
})
|
||||
return _.uniqBy(_tableData, (item) => item.rid)
|
||||
},
|
||||
columns() {
|
||||
if (this.grantType === 'ci_type') {
|
||||
return ['config', 'grant']
|
||||
}
|
||||
return ['read_attr', 'read_ci', 'create', 'update', 'delete']
|
||||
},
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
async handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
await revokeCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
openReadGrantModal(col, row) {
|
||||
this.$emit('openReadGrantModal', col, row)
|
||||
},
|
||||
clickGrant(col, row, rowIndex) {
|
||||
if (!row[col]) {
|
||||
this.handleChange({ target: { checked: true } }, col, row)
|
||||
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
|
||||
this.$set(this.tableData, _idx, { ...this.tableData[_idx], grant: true })
|
||||
} else {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }),
|
||||
onOk() {
|
||||
that.handleChange({ target: { checked: false } }, col, row)
|
||||
const _idx = that.tableData.findIndex((item) => item.rid === row.rid)
|
||||
that.$set(that.tableData, _idx, { ...that.tableData[_idx], grant: false })
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-type-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import i18n from '@/lang'
|
||||
|
||||
export const permMap = () => {
|
||||
return {
|
||||
read: i18n.t('view'),
|
||||
add: i18n.t('new'),
|
||||
create: i18n.t('new'),
|
||||
update: i18n.t('update'),
|
||||
delete: i18n.t('delete'),
|
||||
config: i18n.t('cmdb.components.config'),
|
||||
grant: i18n.t('grant'),
|
||||
'read_attr': i18n.t('cmdb.components.readAttribute'),
|
||||
'read_ci': i18n.t('cmdb.components.readCI')
|
||||
}
|
||||
}
|
||||
import i18n from '@/lang'
|
||||
|
||||
export const permMap = () => {
|
||||
return {
|
||||
read: i18n.t('view'),
|
||||
add: i18n.t('new'),
|
||||
create: i18n.t('new'),
|
||||
update: i18n.t('update'),
|
||||
delete: i18n.t('delete'),
|
||||
config: i18n.t('cmdb.components.config'),
|
||||
grant: i18n.t('grant'),
|
||||
'read_attr': i18n.t('cmdb.components.readAttribute'),
|
||||
'read_ci': i18n.t('cmdb.components.readCI')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,343 +1,343 @@
|
|||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
:CITypeId="CITypeId"
|
||||
:tableData="tableData"
|
||||
grantType="ci_type"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grant_ci_type"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type'))
|
||||
"
|
||||
>
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
:CITypeId="CITypeId"
|
||||
:tableData="tableData"
|
||||
grantType="ci"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
@openReadGrantModal="openReadGrantModal"
|
||||
ref="grant_ci"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('type_relation')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.relationGrant') }}</div>
|
||||
<TypeRelationGrant
|
||||
:typeRelationIds="typeRelationIds"
|
||||
:tableData="tableData"
|
||||
grantType="type_relation"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grant_type_relation"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('relation_view')">
|
||||
<div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
|
||||
<RelationViewGrant
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:tableData="tableData"
|
||||
grantType="relation_view"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grant_relation_view"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<GrantModal ref="grantModal" @handleOk="handleOk" />
|
||||
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import CiTypeGrant from './ciTypeGrant.vue'
|
||||
import TypeRelationGrant from './typeRelationGrant.vue'
|
||||
import { searchResource } from '@/modules/acl/api/resource'
|
||||
import { getResourcePerms } from '@/modules/acl/api/permission'
|
||||
import GrantModal from './grantModal.vue'
|
||||
import ReadGrantModal from './readGrantModal'
|
||||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
resourceType: {
|
||||
type: String,
|
||||
default: 'CIType',
|
||||
},
|
||||
app_id: {
|
||||
type: String,
|
||||
default: 'cmdb',
|
||||
},
|
||||
cmdbGrantType: {
|
||||
type: String,
|
||||
default: 'ci_type,ci',
|
||||
},
|
||||
typeRelationIds: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
inject: ['resource_type'],
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
grantType: '',
|
||||
resource_id: null,
|
||||
attrGroup: [],
|
||||
filerPerimissions: {},
|
||||
loading: false,
|
||||
addedRids: [], // added rid this time
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
allEmployees: (state) => state.user.allEmployees,
|
||||
allDepartments: (state) => state.user.allDepartments,
|
||||
}),
|
||||
child_resource_type() {
|
||||
return this.resource_type()
|
||||
},
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrGroup: () => {
|
||||
return this.attrGroup
|
||||
},
|
||||
filerPerimissions: () => {
|
||||
return this.filerPerimissions
|
||||
},
|
||||
loading: () => {
|
||||
return this.loading
|
||||
},
|
||||
isModal: this.isModal,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
resourceTypeName: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.init()
|
||||
},
|
||||
},
|
||||
CITypeId: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (this.CITypeId && this.cmdbGrantType.includes('ci')) {
|
||||
this.getFilterPermissions()
|
||||
this.getAttrGroup()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
getAttrGroup() {
|
||||
getCITypeGroupById(this.CITypeId, { need_other: true }).then((res) => {
|
||||
this.attrGroup = res
|
||||
})
|
||||
},
|
||||
getFilterPermissions() {
|
||||
ciTypeFilterPermissions(this.CITypeId).then((res) => {
|
||||
this.filerPerimissions = res
|
||||
})
|
||||
},
|
||||
async init() {
|
||||
const _find = this.child_resource_type.groups.find((item) => item.name === this.resourceType)
|
||||
const resource_type_id = _find?.id ?? 0
|
||||
const res = await searchResource({
|
||||
app_id: this.app_id,
|
||||
resource_type_id,
|
||||
page_size: 9999,
|
||||
})
|
||||
const _tempFind = res.resources.find((item) => item.name === this.resourceTypeName)
|
||||
console.log(this.resourceTypeName)
|
||||
this.resource_id = _tempFind?.id || 0
|
||||
this.getTableData()
|
||||
},
|
||||
async getTableData() {
|
||||
this.loading = true
|
||||
const _tableData = await getResourcePerms(this.resource_id, { need_users: 0 })
|
||||
const perms = []
|
||||
for (const key in _tableData) {
|
||||
const obj = {}
|
||||
obj.name = key
|
||||
_tableData[key].perms.forEach((perm) => {
|
||||
obj[`${perm.name}`] = true
|
||||
obj.rid = perm?.rid ?? null
|
||||
})
|
||||
perms.push(obj)
|
||||
}
|
||||
this.tableData = perms
|
||||
this.loading = false
|
||||
},
|
||||
// Grant the department in common-setting and get the roleid from it
|
||||
grantDepart(grantType) {
|
||||
this.$refs.grantModal.open('depart')
|
||||
this.grantType = grantType
|
||||
},
|
||||
// Grant the oldest role permissions
|
||||
grantRole(grantType) {
|
||||
this.$refs.grantModal.open('role')
|
||||
this.grantType = grantType
|
||||
},
|
||||
handleOk(params, type) {
|
||||
const { grantType } = this
|
||||
let rids
|
||||
if (type === 'depart') {
|
||||
rids = [
|
||||
...params.department.map((rid) => {
|
||||
const _find = this.allDepartments.find((dep) => dep.acl_rid === rid)
|
||||
return { rid, name: _find?.department_name ?? rid }
|
||||
}),
|
||||
...params.user.map((rid) => {
|
||||
const _find = this.allEmployees.find((dep) => dep.acl_rid === rid)
|
||||
return { rid, name: _find?.nickname ?? rid }
|
||||
}),
|
||||
]
|
||||
}
|
||||
if (type === 'role') {
|
||||
rids = [
|
||||
...params.map((role) => {
|
||||
return { rid: role.id, name: role.name }
|
||||
}),
|
||||
]
|
||||
}
|
||||
if (grantType === 'ci_type') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
const _find = this.tableData.find((item) => item.rid === rid)
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
conifg: false,
|
||||
grant: false,
|
||||
..._find,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'ci') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
const _find = this.tableData.find((item) => item.rid === rid)
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read_attr: false,
|
||||
read_ci: false,
|
||||
create: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
..._find,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'type_relation') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
create: false,
|
||||
grant: false,
|
||||
delete: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'relation_view') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read: false,
|
||||
grant: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
this.addedRids = rids
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.$refs[`grant_${grantType}`].$refs.xTable.elemStore['main-body-wrapper'].scrollTo(0, 0)
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
openReadGrantModal(col, row) {
|
||||
this.$refs.readGrantModal.open(col, row)
|
||||
},
|
||||
updateTableDataRead(row, hasRead) {
|
||||
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
|
||||
this.$set(this.tableData, _idx, { ...this.tableData[_idx], read: hasRead })
|
||||
this.getFilterPermissions()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
position: relative;
|
||||
padding: 24px 24px 0 24px;
|
||||
overflow: auto;
|
||||
.cmdb-grant-title {
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
.grant-button {
|
||||
padding: 6px 8px;
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin: 15px 0;
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 2px 3px 4px #custom_colors[color_2];
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
:CITypeId="CITypeId"
|
||||
:tableData="tableData"
|
||||
grantType="ci_type"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grant_ci_type"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type'))
|
||||
"
|
||||
>
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.ciGrant') }}</div>
|
||||
<CiTypeGrant
|
||||
:CITypeId="CITypeId"
|
||||
:tableData="tableData"
|
||||
grantType="ci"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
@openReadGrantModal="openReadGrantModal"
|
||||
ref="grant_ci"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('type_relation')">
|
||||
<div class="cmdb-grant-title">{{ $t('cmdb.components.relationGrant') }}</div>
|
||||
<TypeRelationGrant
|
||||
:typeRelationIds="typeRelationIds"
|
||||
:tableData="tableData"
|
||||
grantType="type_relation"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grant_type_relation"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('relation_view')">
|
||||
<div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
|
||||
<RelationViewGrant
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:tableData="tableData"
|
||||
grantType="relation_view"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
ref="grant_relation_view"
|
||||
:addedRids="addedRids"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<GrantModal ref="grantModal" @handleOk="handleOk" />
|
||||
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import CiTypeGrant from './ciTypeGrant.vue'
|
||||
import TypeRelationGrant from './typeRelationGrant.vue'
|
||||
import { searchResource } from '@/modules/acl/api/resource'
|
||||
import { getResourcePerms } from '@/modules/acl/api/permission'
|
||||
import GrantModal from './grantModal.vue'
|
||||
import ReadGrantModal from './readGrantModal'
|
||||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
resourceType: {
|
||||
type: String,
|
||||
default: 'CIType',
|
||||
},
|
||||
app_id: {
|
||||
type: String,
|
||||
default: 'cmdb',
|
||||
},
|
||||
cmdbGrantType: {
|
||||
type: String,
|
||||
default: 'ci_type,ci',
|
||||
},
|
||||
typeRelationIds: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
inject: ['resource_type'],
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
grantType: '',
|
||||
resource_id: null,
|
||||
attrGroup: [],
|
||||
filerPerimissions: {},
|
||||
loading: false,
|
||||
addedRids: [], // added rid this time
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
allEmployees: (state) => state.user.allEmployees,
|
||||
allDepartments: (state) => state.user.allDepartments,
|
||||
}),
|
||||
child_resource_type() {
|
||||
return this.resource_type()
|
||||
},
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrGroup: () => {
|
||||
return this.attrGroup
|
||||
},
|
||||
filerPerimissions: () => {
|
||||
return this.filerPerimissions
|
||||
},
|
||||
loading: () => {
|
||||
return this.loading
|
||||
},
|
||||
isModal: this.isModal,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
resourceTypeName: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.init()
|
||||
},
|
||||
},
|
||||
CITypeId: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (this.CITypeId && this.cmdbGrantType.includes('ci')) {
|
||||
this.getFilterPermissions()
|
||||
this.getAttrGroup()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
getAttrGroup() {
|
||||
getCITypeGroupById(this.CITypeId, { need_other: true }).then((res) => {
|
||||
this.attrGroup = res
|
||||
})
|
||||
},
|
||||
getFilterPermissions() {
|
||||
ciTypeFilterPermissions(this.CITypeId).then((res) => {
|
||||
this.filerPerimissions = res
|
||||
})
|
||||
},
|
||||
async init() {
|
||||
const _find = this.child_resource_type.groups.find((item) => item.name === this.resourceType)
|
||||
const resource_type_id = _find?.id ?? 0
|
||||
const res = await searchResource({
|
||||
app_id: this.app_id,
|
||||
resource_type_id,
|
||||
page_size: 9999,
|
||||
})
|
||||
const _tempFind = res.resources.find((item) => item.name === this.resourceTypeName)
|
||||
console.log(this.resourceTypeName)
|
||||
this.resource_id = _tempFind?.id || 0
|
||||
this.getTableData()
|
||||
},
|
||||
async getTableData() {
|
||||
this.loading = true
|
||||
const _tableData = await getResourcePerms(this.resource_id, { need_users: 0 })
|
||||
const perms = []
|
||||
for (const key in _tableData) {
|
||||
const obj = {}
|
||||
obj.name = key
|
||||
_tableData[key].perms.forEach((perm) => {
|
||||
obj[`${perm.name}`] = true
|
||||
obj.rid = perm?.rid ?? null
|
||||
})
|
||||
perms.push(obj)
|
||||
}
|
||||
this.tableData = perms
|
||||
this.loading = false
|
||||
},
|
||||
// Grant the department in common-setting and get the roleid from it
|
||||
grantDepart(grantType) {
|
||||
this.$refs.grantModal.open('depart')
|
||||
this.grantType = grantType
|
||||
},
|
||||
// Grant the oldest role permissions
|
||||
grantRole(grantType) {
|
||||
this.$refs.grantModal.open('role')
|
||||
this.grantType = grantType
|
||||
},
|
||||
handleOk(params, type) {
|
||||
const { grantType } = this
|
||||
let rids
|
||||
if (type === 'depart') {
|
||||
rids = [
|
||||
...params.department.map((rid) => {
|
||||
const _find = this.allDepartments.find((dep) => dep.acl_rid === rid)
|
||||
return { rid, name: _find?.department_name ?? rid }
|
||||
}),
|
||||
...params.user.map((rid) => {
|
||||
const _find = this.allEmployees.find((dep) => dep.acl_rid === rid)
|
||||
return { rid, name: _find?.nickname ?? rid }
|
||||
}),
|
||||
]
|
||||
}
|
||||
if (type === 'role') {
|
||||
rids = [
|
||||
...params.map((role) => {
|
||||
return { rid: role.id, name: role.name }
|
||||
}),
|
||||
]
|
||||
}
|
||||
if (grantType === 'ci_type') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
const _find = this.tableData.find((item) => item.rid === rid)
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
conifg: false,
|
||||
grant: false,
|
||||
..._find,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'ci') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
const _find = this.tableData.find((item) => item.rid === rid)
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read_attr: false,
|
||||
read_ci: false,
|
||||
create: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
..._find,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'type_relation') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
create: false,
|
||||
grant: false,
|
||||
delete: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'relation_view') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read: false,
|
||||
grant: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
this.addedRids = rids
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.$refs[`grant_${grantType}`].$refs.xTable.elemStore['main-body-wrapper'].scrollTo(0, 0)
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
openReadGrantModal(col, row) {
|
||||
this.$refs.readGrantModal.open(col, row)
|
||||
},
|
||||
updateTableDataRead(row, hasRead) {
|
||||
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
|
||||
this.$set(this.tableData, _idx, { ...this.tableData[_idx], read: hasRead })
|
||||
this.getFilterPermissions()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
position: relative;
|
||||
padding: 24px 24px 0 24px;
|
||||
overflow: auto;
|
||||
.cmdb-grant-title {
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
.grant-button {
|
||||
padding: 6px 8px;
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin: 15px 0;
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 2px 3px 4px #custom_colors[color_2];
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,57 +1,67 @@
|
|||
<template>
|
||||
<a-modal :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel" destroyOnClose>
|
||||
<EmployeeTransfer
|
||||
:isDisabledAllCompany="true"
|
||||
v-if="type === 'depart'"
|
||||
uniqueKey="acl_rid"
|
||||
ref="employeeTransfer"
|
||||
:height="350"
|
||||
/>
|
||||
<RoleTransfer app_id="cmdb" :height="350" ref="roleTransfer" v-if="type === 'role'" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
||||
import RoleTransfer from '@/components/RoleTransfer'
|
||||
export default {
|
||||
name: 'GrantModal',
|
||||
components: { EmployeeTransfer, RoleTransfer },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
type: 'depart',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.type === 'depart') {
|
||||
return this.$t('cmdb.components.grantUser')
|
||||
}
|
||||
return this.$t('cmdb.components.grantRole')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open(type) {
|
||||
this.visible = true
|
||||
this.type = type
|
||||
},
|
||||
handleOk() {
|
||||
let params
|
||||
if (this.type === 'depart') {
|
||||
params = this.$refs.employeeTransfer.getValues()
|
||||
}
|
||||
if (this.type === 'role') {
|
||||
params = this.$refs.roleTransfer.getValues()
|
||||
}
|
||||
this.handleCancel()
|
||||
this.$emit('handleOk', params, this.type)
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<a-modal :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel" destroyOnClose>
|
||||
<EmployeeTransfer
|
||||
:isDisabledAllCompany="true"
|
||||
v-if="type === 'depart'"
|
||||
uniqueKey="acl_rid"
|
||||
ref="employeeTransfer"
|
||||
:height="350"
|
||||
/>
|
||||
<RoleTransfer app_id="cmdb" :height="350" ref="roleTransfer" v-if="type === 'role'" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
||||
import RoleTransfer from '@/components/RoleTransfer'
|
||||
|
||||
export default {
|
||||
name: 'GrantModal',
|
||||
components: { EmployeeTransfer, RoleTransfer },
|
||||
props: {
|
||||
customTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
type: 'depart',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.customTitle) {
|
||||
return this.customTitle
|
||||
}
|
||||
if (this.type === 'depart') {
|
||||
return this.$t('cmdb.components.grantUser')
|
||||
}
|
||||
return this.$t('cmdb.components.grantRole')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open(type) {
|
||||
this.visible = true
|
||||
this.type = type
|
||||
},
|
||||
handleOk() {
|
||||
let params
|
||||
if (this.type === 'depart') {
|
||||
params = this.$refs.employeeTransfer.getValues()
|
||||
}
|
||||
if (this.type === 'role') {
|
||||
params = this.$refs.roleTransfer.getValues()
|
||||
}
|
||||
this.handleCancel()
|
||||
this.$emit('handleOk', params, this.type)
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -1,57 +1,57 @@
|
|||
<template>
|
||||
<a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }">
|
||||
<GrantComp
|
||||
:resourceType="resourceType"
|
||||
:app_id="app_id"
|
||||
:cmdbGrantType="cmdbGrantType"
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:typeRelationIds="typeRelationIds"
|
||||
:CITypeId="CITypeId"
|
||||
:isModal="true"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GrantComp from './grantComp.vue'
|
||||
export default {
|
||||
name: 'CMDBGrant',
|
||||
components: { GrantComp },
|
||||
props: {
|
||||
resourceType: {
|
||||
type: String,
|
||||
default: 'CIType',
|
||||
},
|
||||
app_id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
resourceTypeName: '',
|
||||
typeRelationIds: [],
|
||||
cmdbGrantType: '',
|
||||
CITypeId: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open({ name, typeRelationIds = [], cmdbGrantType, CITypeId }) {
|
||||
this.visible = true
|
||||
this.resourceTypeName = name
|
||||
this.typeRelationIds = typeRelationIds
|
||||
this.cmdbGrantType = cmdbGrantType
|
||||
this.CITypeId = CITypeId
|
||||
},
|
||||
handleOk() {
|
||||
this.handleCancel()
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }">
|
||||
<GrantComp
|
||||
:resourceType="resourceType"
|
||||
:app_id="app_id"
|
||||
:cmdbGrantType="cmdbGrantType"
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:typeRelationIds="typeRelationIds"
|
||||
:CITypeId="CITypeId"
|
||||
:isModal="true"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GrantComp from './grantComp.vue'
|
||||
export default {
|
||||
name: 'CMDBGrant',
|
||||
components: { GrantComp },
|
||||
props: {
|
||||
resourceType: {
|
||||
type: String,
|
||||
default: 'CIType',
|
||||
},
|
||||
app_id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
resourceTypeName: '',
|
||||
typeRelationIds: [],
|
||||
cmdbGrantType: '',
|
||||
CITypeId: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open({ name, typeRelationIds = [], cmdbGrantType, CITypeId }) {
|
||||
this.visible = true
|
||||
this.resourceTypeName = name
|
||||
this.typeRelationIds = typeRelationIds
|
||||
this.cmdbGrantType = cmdbGrantType
|
||||
this.CITypeId = CITypeId
|
||||
},
|
||||
handleOk() {
|
||||
this.handleCancel()
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -1,89 +1,89 @@
|
|||
<template>
|
||||
<div :class="{ 'read-checkbox': true, 'ant-checkbox-wrapper': isHalfChecked }" @click="openReadGrantModal">
|
||||
<a-tooltip
|
||||
v-if="value && isHalfChecked"
|
||||
:title="valueKey === 'read_ci' ? filerPerimissions[this.rid].name || '' : ''"
|
||||
>
|
||||
<div v-if="value && isHalfChecked" :class="{ 'read-checkbox-half-checked': true, 'ant-checkbox': true }"></div>
|
||||
</a-tooltip>
|
||||
<a-checkbox v-else :checked="value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ReadCheckbox',
|
||||
inject: {
|
||||
provide_filerPerimissions: {
|
||||
from: 'filerPerimissions',
|
||||
},
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'read_attr',
|
||||
},
|
||||
rid: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filerPerimissions() {
|
||||
return this.provide_filerPerimissions()
|
||||
},
|
||||
filterKey() {
|
||||
if (this.valueKey === 'read_attr') {
|
||||
return 'attr_filter'
|
||||
}
|
||||
return 'ci_filter'
|
||||
},
|
||||
isHalfChecked() {
|
||||
if (this.filerPerimissions[this.rid]) {
|
||||
const _tempValue = this.filerPerimissions[this.rid][this.filterKey]
|
||||
return !!(_tempValue && _tempValue.length)
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openReadGrantModal() {
|
||||
this.$emit('openReadGrantModal')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.read-checkbox {
|
||||
.read-checkbox-half-checked {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
// background-color: #custom_colors[color_1];
|
||||
border-radius: 2px;
|
||||
border: 14px solid transparent;
|
||||
border-left-color: #custom_colors[color_1];
|
||||
transform: rotate(225deg);
|
||||
top: -16px;
|
||||
left: -17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div :class="{ 'read-checkbox': true, 'ant-checkbox-wrapper': isHalfChecked }" @click="openReadGrantModal">
|
||||
<a-tooltip
|
||||
v-if="value && isHalfChecked"
|
||||
:title="valueKey === 'read_ci' ? filerPerimissions[this.rid].name || '' : ''"
|
||||
>
|
||||
<div v-if="value && isHalfChecked" :class="{ 'read-checkbox-half-checked': true, 'ant-checkbox': true }"></div>
|
||||
</a-tooltip>
|
||||
<a-checkbox v-else :checked="value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ReadCheckbox',
|
||||
inject: {
|
||||
provide_filerPerimissions: {
|
||||
from: 'filerPerimissions',
|
||||
},
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'read_attr',
|
||||
},
|
||||
rid: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filerPerimissions() {
|
||||
return this.provide_filerPerimissions()
|
||||
},
|
||||
filterKey() {
|
||||
if (this.valueKey === 'read_attr') {
|
||||
return 'attr_filter'
|
||||
}
|
||||
return 'ci_filter'
|
||||
},
|
||||
isHalfChecked() {
|
||||
if (this.filerPerimissions[this.rid]) {
|
||||
const _tempValue = this.filerPerimissions[this.rid][this.filterKey]
|
||||
return !!(_tempValue && _tempValue.length)
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openReadGrantModal() {
|
||||
this.$emit('openReadGrantModal')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.read-checkbox {
|
||||
.read-checkbox-half-checked {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
// background-color: #custom_colors[color_1];
|
||||
border-radius: 2px;
|
||||
border: 14px solid transparent;
|
||||
border-left-color: #custom_colors[color_1];
|
||||
transform: rotate(225deg);
|
||||
top: -16px;
|
||||
left: -17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,205 +1,212 @@
|
|||
<template>
|
||||
<a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel">
|
||||
<CustomRadio
|
||||
:radioList="[
|
||||
{ value: 1, label: $t('cmdb.components.all') },
|
||||
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
|
||||
{ value: 3, label: $t('cmdb.components.none') },
|
||||
]"
|
||||
v-model="radioValue"
|
||||
>
|
||||
<template slot="extra_2" v-if="radioValue === 2">
|
||||
<treeselect
|
||||
v-if="colType === 'read_attr'"
|
||||
v-model="selectedAttr"
|
||||
:multiple="true"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="attrGroup"
|
||||
:placeholder="$t('cmdb.ciType.selectAttributes')"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:limit="10"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name || -1,
|
||||
label: node.alias || node.name || $t('other'),
|
||||
title: node.alias || node.name || $t('other'),
|
||||
children: node.attributes,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<a-form-model
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
v-if="colType === 'read_ci'"
|
||||
:labelCol="{ span: 2 }"
|
||||
:wrapperCol="{ span: 10 }"
|
||||
ref="form"
|
||||
>
|
||||
<a-form-model-item :label="$t('name')" prop="name">
|
||||
<a-input v-model="form.name" />
|
||||
</a-form-model-item>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:isDropdown="false"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="expression"
|
||||
/>
|
||||
</a-form-model>
|
||||
</template>
|
||||
</CustomRadio>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import { getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
|
||||
export default {
|
||||
name: 'ReadGrantModal',
|
||||
components: { FilterComp },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
provide_attrGroup: {
|
||||
from: 'attrGroup',
|
||||
},
|
||||
provide_filerPerimissions: {
|
||||
from: 'filerPerimissions',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
colType: '',
|
||||
row: {},
|
||||
radioValue: 1,
|
||||
radioStyle: {
|
||||
display: 'block',
|
||||
height: '30px',
|
||||
lineHeight: '30px',
|
||||
},
|
||||
selectedAttr: [],
|
||||
ruleList: [],
|
||||
canSearchPreferenceAttrList: [],
|
||||
expression: '',
|
||||
form: {
|
||||
name: '',
|
||||
},
|
||||
rules: {
|
||||
name: [{ required: true, message: this.$t('cmdb.components.customizeFilterName') }],
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.colType === 'read_attr') {
|
||||
return this.$t('cmdb.components.attributeGrant')
|
||||
}
|
||||
return this.$t('cmdb.components.ciGrant')
|
||||
},
|
||||
attrGroup() {
|
||||
return this.provide_attrGroup()
|
||||
},
|
||||
filerPerimissions() {
|
||||
return this.provide_filerPerimissions()
|
||||
},
|
||||
filterKey() {
|
||||
if (this.colType === 'read_attr') {
|
||||
return 'attr_filter'
|
||||
}
|
||||
return 'ci_filter'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async open(colType, row) {
|
||||
this.visible = true
|
||||
this.colType = colType
|
||||
this.row = row
|
||||
if (this.colType === 'read_ci') {
|
||||
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
||||
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
||||
})
|
||||
}
|
||||
if (this.filerPerimissions[row.rid]) {
|
||||
const _tempValue = this.filerPerimissions[row.rid][this.filterKey]
|
||||
if (_tempValue && _tempValue.length) {
|
||||
this.radioValue = 2
|
||||
if (this.colType === 'read_attr') {
|
||||
this.selectedAttr = _tempValue
|
||||
} else {
|
||||
this.expression = `q=${_tempValue}`
|
||||
this.form = {
|
||||
name: this.filerPerimissions[row.rid].name || '',
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filterComp.visibleChange(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.form = {
|
||||
name: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
async handleOk() {
|
||||
if (this.radioValue === 1) {
|
||||
await grantCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? [] : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? '' : undefined,
|
||||
})
|
||||
} else if (this.radioValue === 2) {
|
||||
if (this.colType === 'read_ci') {
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
}
|
||||
await grantCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? this.selectedAttr : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? this.expression.slice(2) : undefined,
|
||||
name: this.colType === 'read_ci' ? this.form.name : undefined,
|
||||
})
|
||||
} else {
|
||||
const _tempValue = this.filerPerimissions?.[this.row.rid]?.[this.filterKey]
|
||||
await revokeCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? _tempValue : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? _tempValue : undefined,
|
||||
})
|
||||
}
|
||||
this.$emit('updateTableDataRead', this.row, this.radioValue === 1 || this.radioValue === 2)
|
||||
this.handleCancel()
|
||||
},
|
||||
handleCancel() {
|
||||
this.radioValue = 1
|
||||
this.selectedAttr = []
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.resetFields()
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
let expression = ''
|
||||
if (filterExp) {
|
||||
expression = `q=${filterExp}`
|
||||
}
|
||||
this.expression = expression
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel">
|
||||
<CustomRadio
|
||||
:radioList="[
|
||||
{ value: 1, label: $t('cmdb.components.all') },
|
||||
{ value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
|
||||
{ value: 3, label: $t('cmdb.components.none') },
|
||||
]"
|
||||
:value="radioValue"
|
||||
@change="changeRadioValue"
|
||||
>
|
||||
<template slot="extra_2" v-if="radioValue === 2">
|
||||
<treeselect
|
||||
v-if="colType === 'read_attr'"
|
||||
v-model="selectedAttr"
|
||||
:multiple="true"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="attrGroup"
|
||||
:placeholder="$t('cmdb.ciType.selectAttributes')"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:limit="10"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name || -1,
|
||||
label: node.alias || node.name || $t('other'),
|
||||
title: node.alias || node.name || $t('other'),
|
||||
children: node.attributes,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<a-form-model
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
v-if="colType === 'read_ci'"
|
||||
:labelCol="{ span: 2 }"
|
||||
:wrapperCol="{ span: 10 }"
|
||||
ref="form"
|
||||
>
|
||||
<a-form-model-item :label="$t('name')" prop="name">
|
||||
<a-input v-model="form.name" />
|
||||
</a-form-model-item>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:isDropdown="false"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="expression"
|
||||
/>
|
||||
</a-form-model>
|
||||
</template>
|
||||
</CustomRadio>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import { getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
|
||||
export default {
|
||||
name: 'ReadGrantModal',
|
||||
components: { FilterComp },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
provide_attrGroup: {
|
||||
from: 'attrGroup',
|
||||
},
|
||||
provide_filerPerimissions: {
|
||||
from: 'filerPerimissions',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
colType: '',
|
||||
row: {},
|
||||
radioValue: 1,
|
||||
radioStyle: {
|
||||
display: 'block',
|
||||
height: '30px',
|
||||
lineHeight: '30px',
|
||||
},
|
||||
selectedAttr: [],
|
||||
ruleList: [],
|
||||
canSearchPreferenceAttrList: [],
|
||||
expression: '',
|
||||
form: {
|
||||
name: '',
|
||||
},
|
||||
rules: {
|
||||
name: [{ required: true, message: this.$t('cmdb.components.customizeFilterName') }],
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.colType === 'read_attr') {
|
||||
return this.$t('cmdb.components.attributeGrant')
|
||||
}
|
||||
return this.$t('cmdb.components.ciGrant')
|
||||
},
|
||||
attrGroup() {
|
||||
return this.provide_attrGroup()
|
||||
},
|
||||
filerPerimissions() {
|
||||
return this.provide_filerPerimissions()
|
||||
},
|
||||
filterKey() {
|
||||
if (this.colType === 'read_attr') {
|
||||
return 'attr_filter'
|
||||
}
|
||||
return 'ci_filter'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async open(colType, row) {
|
||||
this.visible = true
|
||||
this.colType = colType
|
||||
this.row = row
|
||||
this.form = {
|
||||
name: '',
|
||||
}
|
||||
if (this.colType === 'read_ci') {
|
||||
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
||||
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
||||
})
|
||||
}
|
||||
if (this.filerPerimissions[row.rid]) {
|
||||
const _tempValue = this.filerPerimissions[row.rid][this.filterKey]
|
||||
if (_tempValue && _tempValue.length) {
|
||||
this.radioValue = 2
|
||||
if (this.colType === 'read_attr') {
|
||||
this.selectedAttr = _tempValue
|
||||
} else {
|
||||
this.expression = `q=${_tempValue}`
|
||||
this.form = {
|
||||
name: this.filerPerimissions[row.rid].name || '',
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filterComp.visibleChange(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async handleOk() {
|
||||
if (this.radioValue === 1) {
|
||||
await grantCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? [] : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? '' : undefined,
|
||||
})
|
||||
} else if (this.radioValue === 2) {
|
||||
if (this.colType === 'read_ci') {
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
}
|
||||
await grantCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? this.selectedAttr : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? this.expression.slice(2) : undefined,
|
||||
name: this.colType === 'read_ci' ? this.form.name : undefined,
|
||||
})
|
||||
} else {
|
||||
const _tempValue = this.filerPerimissions?.[this.row.rid]?.[this.filterKey]
|
||||
await revokeCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? _tempValue : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? _tempValue : undefined,
|
||||
})
|
||||
}
|
||||
this.$emit('updateTableDataRead', this.row, this.radioValue === 1 || this.radioValue === 2)
|
||||
this.handleCancel()
|
||||
},
|
||||
handleCancel() {
|
||||
this.radioValue = 1
|
||||
this.selectedAttr = []
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.resetFields()
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
let expression = ''
|
||||
if (filterExp) {
|
||||
expression = `q=${filterExp}`
|
||||
}
|
||||
this.expression = expression
|
||||
},
|
||||
changeRadioValue(value) {
|
||||
if (this.id_filter) {
|
||||
this.$message.warning(this.$t('cmdb.serviceTree.grantedByServiceTreeTips'))
|
||||
} else {
|
||||
this.radioValue = value
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -1,98 +1,98 @@
|
|||
<template>
|
||||
<div class="ci-relation-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantRelationView, revokeRelationView } from '../../api/preference.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'RelationViewGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'relation_view',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: ['read', 'grant'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
grantRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-relation-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="ci-relation-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantRelationView, revokeRelationView } from '../../api/preference.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'RelationViewGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'relation_view',
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: ['read', 'grant'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
grantRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-relation-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<a-modal :visible="visible" @cancel="handleCancel" @ok="handleOK" :title="$t('revoke')">
|
||||
<a-form-model :model="form" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-model-item :label="$t('user')">
|
||||
<EmployeeTreeSelect
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
:style="{
|
||||
'--custom-height': '32px',
|
||||
lineHeight: '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '18px',
|
||||
}"
|
||||
:multiple="true"
|
||||
v-model="form.users"
|
||||
:placeholder="$t('cmdb.serviceTree.userPlaceholder')"
|
||||
:idType="2"
|
||||
departmentKey="acl_rid"
|
||||
employeeKey="acl_rid"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :label="$t('role')">
|
||||
<treeselect
|
||||
v-model="form.roles"
|
||||
:multiple="true"
|
||||
:options="filterAllRoles"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
:style="{
|
||||
'--custom-height': '32px',
|
||||
lineHeight: '32px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '18px',
|
||||
}"
|
||||
:limit="10"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.name,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
zIndex="1050"
|
||||
:placeholder="$t('cmdb.serviceTree.rolePlaceholder')"
|
||||
@search-change="searchRole"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vue'
|
||||
import { getAllDepAndEmployee } from '@/api/company'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
|
||||
export default {
|
||||
name: 'RevokeModal',
|
||||
components: { EmployeeTreeSelect },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
form: {
|
||||
users: undefined,
|
||||
roles: undefined,
|
||||
},
|
||||
allTreeDepAndEmp: [],
|
||||
allRoles: [],
|
||||
filterAllRoles: [],
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
provide_allTreeDepAndEmp: () => {
|
||||
return this.allTreeDepAndEmp
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getAllDepAndEmployee()
|
||||
this.loadRoles()
|
||||
},
|
||||
methods: {
|
||||
async loadRoles() {
|
||||
const res = await searchRole({ page_size: 9999, app_id: 'cmdb', is_all: true })
|
||||
this.allRoles = res.roles
|
||||
this.filterAllRoles = this.allRoles.slice(0, 100)
|
||||
},
|
||||
getAllDepAndEmployee() {
|
||||
getAllDepAndEmployee({ block: 0 }).then((res) => {
|
||||
this.allTreeDepAndEmp = res
|
||||
})
|
||||
},
|
||||
open() {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.form = {
|
||||
users: undefined,
|
||||
roles: undefined,
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
searchRole(searchQuery) {
|
||||
this.filterAllRoles = this.allRoles
|
||||
.filter((item) => item.name.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
.slice(0, 100)
|
||||
},
|
||||
handleOK() {
|
||||
this.$emit('handleRevoke', this.form)
|
||||
this.handleCancel()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -1,100 +1,100 @@
|
|||
<template>
|
||||
<div class="ci-relation-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTypeRelation, revokeTypeRelation } from '../../api/CITypeRelation.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'TypeRelationGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'type_relation',
|
||||
},
|
||||
typeRelationIds: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: ['create', 'grant', 'delete'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
const first = this.typeRelationIds[0]
|
||||
const second = this.typeRelationIds[1]
|
||||
if (e.target.checked) {
|
||||
grantTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-relation-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="ci-relation-grant">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
size="mini"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
:max-height="`${tableHeight}px`"
|
||||
:row-style="(params) => getCurrentRowStyle(params, addedRids)"
|
||||
>
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
|
||||
<span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTypeRelation, revokeTypeRelation } from '../../api/CITypeRelation.js'
|
||||
import { getCurrentRowStyle } from './utils'
|
||||
|
||||
export default {
|
||||
name: 'TypeRelationGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'type_relation',
|
||||
},
|
||||
typeRelationIds: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
addedRids: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: ['create', 'grant', 'delete'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
permMap() {
|
||||
return permMap()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRowStyle,
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
const first = this.typeRelationIds[0]
|
||||
const second = this.typeRelationIds[1]
|
||||
if (e.target.checked) {
|
||||
grantTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-relation-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const getCurrentRowStyle = ({ row }, addedRids) => {
|
||||
const idx = addedRids.findIndex(item => item.rid === row.rid)
|
||||
return idx > -1 ? 'background-color:#E0E7FF!important' : ''
|
||||
}
|
||||
export const getCurrentRowStyle = ({ row }, addedRids) => {
|
||||
const idx = addedRids.findIndex(item => item.rid === row.rid)
|
||||
return idx > -1 ? 'background-color:#E0E7FF!important' : ''
|
||||
}
|
||||
|
|
|
@ -1,301 +1,305 @@
|
|||
<template>
|
||||
<div>
|
||||
<div id="search-form-bar" class="search-form-bar">
|
||||
<div :style="{ display: 'inline-flex', alignItems: 'center' }">
|
||||
<a-space>
|
||||
<treeselect
|
||||
v-if="type === 'resourceSearch'"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }"
|
||||
v-model="currenCiType"
|
||||
:multiple="true"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="ciTypeGroup"
|
||||
:limit="1"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.ciType')"
|
||||
@close="closeCiTypeGroup"
|
||||
@open="openCiTypeGroup"
|
||||
@input="inputCiTypeGroup"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.id || -1,
|
||||
label: node.alias || node.name || $t('other'),
|
||||
title: node.alias || node.name || $t('other'),
|
||||
children: node.ci_types,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input
|
||||
v-model="fuzzySearch"
|
||||
:style="{ display: 'inline-block', width: '244px' }"
|
||||
:placeholder="$t('cmdb.components.pleaseSearch')"
|
||||
@pressEnter="emitRefresh"
|
||||
class="ops-input ops-input-radius"
|
||||
>
|
||||
<a-icon
|
||||
type="search"
|
||||
slot="suffix"
|
||||
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }"
|
||||
@click="emitRefresh"
|
||||
/>
|
||||
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }">
|
||||
<template slot="title">
|
||||
{{ $t('cmdb.components.ciSearchTips') }}
|
||||
</template>
|
||||
<a><a-icon type="question-circle"/></a>
|
||||
</a-tooltip>
|
||||
</a-input>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="expression"
|
||||
placement="bottomLeft"
|
||||
>
|
||||
<div slot="popover_item" class="search-form-bar-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" />
|
||||
</div>
|
||||
</FilterComp>
|
||||
<a-input
|
||||
v-if="isShowExpression"
|
||||
v-model="expression"
|
||||
v-show="!selectedRowKeys.length"
|
||||
@focus="
|
||||
() => {
|
||||
isFocusExpression = true
|
||||
}
|
||||
"
|
||||
@blur="
|
||||
() => {
|
||||
isFocusExpression = false
|
||||
}
|
||||
"
|
||||
class="ci-searchform-expression"
|
||||
:style="{ width }"
|
||||
:placeholder="placeholder"
|
||||
@keyup.enter="emitRefresh"
|
||||
>
|
||||
<a-icon slot="suffix" type="copy" @click="handleCopyExpression" />
|
||||
</a-input>
|
||||
<slot></slot>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-space>
|
||||
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
|
||||
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
||||
<a
|
||||
@click="
|
||||
() => {
|
||||
$refs.metadataDrawer.open(typeId)
|
||||
}
|
||||
"
|
||||
><a-icon
|
||||
v-if="type === 'relationView'"
|
||||
type="question-circle"
|
||||
/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||
export default {
|
||||
name: 'SearchForm',
|
||||
components: { MetadataDrawer, FilterComp, Treeselect },
|
||||
props: {
|
||||
preferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
isShowExpression: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
typeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
selectedRowKeys: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Advanced Search Expand/Close
|
||||
advanced: false,
|
||||
queryParam: {},
|
||||
isFocusExpression: false,
|
||||
expression: '',
|
||||
fuzzySearch: '',
|
||||
currenCiType: [],
|
||||
ciTypeGroup: [],
|
||||
lastCiType: [],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
placeholder() {
|
||||
return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr')
|
||||
},
|
||||
width() {
|
||||
return '200px'
|
||||
},
|
||||
canSearchPreferenceAttrList() {
|
||||
return this.preferenceAttrList.filter((item) => item.value_type !== '6')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.path': function(newValue, oldValue) {
|
||||
this.queryParam = {}
|
||||
this.expression = ''
|
||||
this.fuzzySearch = ''
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
setPreferenceSearchCurrent: {
|
||||
from: 'setPreferenceSearchCurrent',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.type === 'resourceSearch') {
|
||||
this.getCITypeGroups()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCITypeGroups() {
|
||||
getCITypeGroups({ need_other: true }).then((res) => {
|
||||
this.ciTypeGroup = res
|
||||
.filter((item) => item.ci_types && item.ci_types.length)
|
||||
.map((item) => {
|
||||
item.id = `parent_${item.id || -1}`
|
||||
return { ..._.cloneDeep(item) }
|
||||
})
|
||||
})
|
||||
},
|
||||
reset() {
|
||||
this.queryParam = {}
|
||||
this.expression = ''
|
||||
this.fuzzySearch = ''
|
||||
this.currenCiType = []
|
||||
this.emitRefresh()
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
const regSort = /(?<=sort=).+/g
|
||||
const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
|
||||
let expression = ''
|
||||
if (filterExp) {
|
||||
expression = `q=${filterExp}`
|
||||
}
|
||||
if (expSort) {
|
||||
expression += `&sort=${expSort}`
|
||||
}
|
||||
this.expression = expression
|
||||
this.emitRefresh()
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
},
|
||||
openCiTypeGroup() {
|
||||
this.lastCiType = _.cloneDeep(this.currenCiType)
|
||||
},
|
||||
closeCiTypeGroup(value) {
|
||||
if (!_.isEqual(value, this.lastCiType)) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
}
|
||||
},
|
||||
inputCiTypeGroup(value) {
|
||||
console.log(value)
|
||||
if (!value || !value.length) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
}
|
||||
},
|
||||
emitRefresh() {
|
||||
if (this.setPreferenceSearchCurrent) {
|
||||
this.setPreferenceSearchCurrent(null)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$emit('refresh', true)
|
||||
})
|
||||
},
|
||||
handleCopyExpression() {
|
||||
this.$emit('copyExpression')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '../../views/index.less';
|
||||
.ci-searchform-expression {
|
||||
> input {
|
||||
border-bottom: 2px solid #d9d9d9;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-bottom: 2px solid #2f54eb;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 2px 2px -2px #1f78d133;
|
||||
}
|
||||
}
|
||||
.ant-input-suffix {
|
||||
color: #2f54eb;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.cmdb-search-form {
|
||||
.ant-form-item-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.search-form-bar {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.search-form-bar-filter {
|
||||
.ops_display_wrapper();
|
||||
.search-form-bar-filter-icon {
|
||||
color: #custom_colors[color_1];
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<div id="search-form-bar" class="search-form-bar">
|
||||
<div :style="{ display: 'inline-flex', alignItems: 'center' }">
|
||||
<a-space>
|
||||
<treeselect
|
||||
v-if="type === 'resourceSearch'"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }"
|
||||
v-model="currenCiType"
|
||||
:multiple="true"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="ciTypeGroup"
|
||||
:limit="1"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.ciType')"
|
||||
@close="closeCiTypeGroup"
|
||||
@open="openCiTypeGroup"
|
||||
@input="inputCiTypeGroup"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.id || -1,
|
||||
label: node.alias || node.name || $t('other'),
|
||||
title: node.alias || node.name || $t('other'),
|
||||
children: node.ci_types,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input
|
||||
v-model="fuzzySearch"
|
||||
:style="{ display: 'inline-block', width: '244px' }"
|
||||
:placeholder="$t('cmdb.components.pleaseSearch')"
|
||||
@pressEnter="emitRefresh"
|
||||
class="ops-input ops-input-radius"
|
||||
>
|
||||
<a-icon
|
||||
type="search"
|
||||
slot="suffix"
|
||||
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }"
|
||||
@click="emitRefresh"
|
||||
/>
|
||||
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
|
||||
<template slot="title">
|
||||
{{ $t('cmdb.components.ciSearchTips') }}
|
||||
</template>
|
||||
<a><a-icon type="question-circle"/></a>
|
||||
</a-tooltip>
|
||||
</a-input>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="expression"
|
||||
placement="bottomLeft"
|
||||
>
|
||||
<div slot="popover_item" class="search-form-bar-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" />
|
||||
</div>
|
||||
</FilterComp>
|
||||
<a-input
|
||||
v-if="isShowExpression"
|
||||
v-model="expression"
|
||||
v-show="!selectedRowKeys.length"
|
||||
@focus="
|
||||
() => {
|
||||
isFocusExpression = true
|
||||
}
|
||||
"
|
||||
@blur="
|
||||
() => {
|
||||
isFocusExpression = false
|
||||
}
|
||||
"
|
||||
class="ci-searchform-expression"
|
||||
:style="{ width }"
|
||||
:placeholder="placeholder"
|
||||
@keyup.enter="emitRefresh"
|
||||
>
|
||||
<a-icon slot="suffix" type="copy" @click="handleCopyExpression" />
|
||||
</a-input>
|
||||
<slot></slot>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-space>
|
||||
<slot name="extraContent"></slot>
|
||||
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
|
||||
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
|
||||
<a
|
||||
@click="
|
||||
() => {
|
||||
$refs.metadataDrawer.open(typeId)
|
||||
}
|
||||
"
|
||||
><a-icon
|
||||
v-if="type === 'relationView'"
|
||||
type="question-circle"
|
||||
/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<MetadataDrawer ref="metadataDrawer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||
export default {
|
||||
name: 'SearchForm',
|
||||
components: { MetadataDrawer, FilterComp, Treeselect },
|
||||
props: {
|
||||
preferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
isShowExpression: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
typeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
selectedRowKeys: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Advanced Search Expand/Close
|
||||
advanced: false,
|
||||
queryParam: {},
|
||||
isFocusExpression: false,
|
||||
expression: '',
|
||||
fuzzySearch: '',
|
||||
currenCiType: [],
|
||||
ciTypeGroup: [],
|
||||
lastCiType: [],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
placeholder() {
|
||||
return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr')
|
||||
},
|
||||
width() {
|
||||
return '200px'
|
||||
},
|
||||
canSearchPreferenceAttrList() {
|
||||
return this.preferenceAttrList.filter((item) => item.value_type !== '6')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.path': function(newValue, oldValue) {
|
||||
this.queryParam = {}
|
||||
this.expression = ''
|
||||
this.fuzzySearch = ''
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
setPreferenceSearchCurrent: {
|
||||
from: 'setPreferenceSearchCurrent',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.type === 'resourceSearch') {
|
||||
this.getCITypeGroups()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// toggleAdvanced() {
|
||||
// this.advanced = !this.advanced
|
||||
// },
|
||||
getCITypeGroups() {
|
||||
getCITypeGroups({ need_other: true }).then((res) => {
|
||||
this.ciTypeGroup = res
|
||||
.filter((item) => item.ci_types && item.ci_types.length)
|
||||
.map((item) => {
|
||||
item.id = `parent_${item.id || -1}`
|
||||
return { ..._.cloneDeep(item) }
|
||||
})
|
||||
})
|
||||
},
|
||||
reset() {
|
||||
this.queryParam = {}
|
||||
this.expression = ''
|
||||
this.fuzzySearch = ''
|
||||
this.currenCiType = []
|
||||
this.emitRefresh()
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
const regSort = /(?<=sort=).+/g
|
||||
const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
|
||||
let expression = ''
|
||||
if (filterExp) {
|
||||
expression = `q=${filterExp}`
|
||||
}
|
||||
if (expSort) {
|
||||
expression += `&sort=${expSort}`
|
||||
}
|
||||
this.expression = expression
|
||||
this.emitRefresh()
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
},
|
||||
openCiTypeGroup() {
|
||||
this.lastCiType = _.cloneDeep(this.currenCiType)
|
||||
},
|
||||
closeCiTypeGroup(value) {
|
||||
if (!_.isEqual(value, this.lastCiType)) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
}
|
||||
},
|
||||
inputCiTypeGroup(value) {
|
||||
console.log(value)
|
||||
if (!value || !value.length) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
}
|
||||
},
|
||||
emitRefresh() {
|
||||
if (this.setPreferenceSearchCurrent) {
|
||||
this.setPreferenceSearchCurrent(null)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$emit('refresh', true)
|
||||
})
|
||||
},
|
||||
handleCopyExpression() {
|
||||
this.$emit('copyExpression')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '../../views/index.less';
|
||||
.ci-searchform-expression {
|
||||
> input {
|
||||
border-bottom: 2px solid #d9d9d9;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-bottom: 2px solid #2f54eb;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 2px 2px -2px #1f78d133;
|
||||
}
|
||||
}
|
||||
.ant-input-suffix {
|
||||
color: #2f54eb;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.cmdb-search-form {
|
||||
.ant-form-item-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.search-form-bar {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.search-form-bar-filter {
|
||||
.ops_display_wrapper();
|
||||
.search-form-bar-filter-icon {
|
||||
color: #custom_colors[color_1];
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,492 +1,503 @@
|
|||
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',
|
||||
cidetail: 'CI Detail'
|
||||
},
|
||||
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',
|
||||
reg: 'Regex',
|
||||
isInherit: 'Inherit',
|
||||
inheritType: 'Inherit Type',
|
||||
inheritTypePlaceholder: 'Please select inherit types',
|
||||
inheritFrom: 'inherit from {name}',
|
||||
groupInheritFrom: 'Please go to the {name} for modification'
|
||||
},
|
||||
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',
|
||||
accept: 'Accept',
|
||||
acceptBy: 'Accept By',
|
||||
acceptTime: 'Accept Time',
|
||||
confirmAccept: 'Confirm Accept?',
|
||||
acceptSuccess: 'Accept successfully',
|
||||
isAccept: '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',
|
||||
share: 'Share',
|
||||
noPermission: 'No Permission'
|
||||
},
|
||||
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
|
||||
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',
|
||||
cidetail: 'CI Detail'
|
||||
},
|
||||
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: 'Unique Identifies',
|
||||
uniqueKeySelect: 'Please select a unique identifier',
|
||||
uniqueKeyTips: 'json/password/computed/choice can not be unique identifies',
|
||||
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',
|
||||
reg: 'Regex',
|
||||
isInherit: 'Inherit',
|
||||
inheritType: 'Inherit Type',
|
||||
inheritTypePlaceholder: 'Please select inherit types',
|
||||
inheritFrom: 'inherit from {name}',
|
||||
groupInheritFrom: 'Please go to the {name} for modification'
|
||||
},
|
||||
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/password/link attributes cannot be searched\n2. If the search content includes commas, they need to be escaped\n3. 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',
|
||||
accept: 'Accept',
|
||||
acceptBy: 'Accept By',
|
||||
acceptTime: 'Accept Time',
|
||||
confirmAccept: 'Confirm Accept?',
|
||||
acceptSuccess: 'Accept successfully',
|
||||
isAccept: '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/link/password 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',
|
||||
share: 'Share',
|
||||
noPermission: 'No Permission'
|
||||
},
|
||||
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?',
|
||||
batch: 'Batch',
|
||||
grantTitle: 'Grant(read)',
|
||||
userPlaceholder: 'Please select users',
|
||||
rolePlaceholder: 'Please select roles',
|
||||
grantedByServiceTree: 'Granted By Service Tree:',
|
||||
grantedByServiceTreeTips: 'Please delete id_filter in Servive Tree',
|
||||
peopleHasRead: 'Personnel authorized to read:',
|
||||
authorizationPolicy: 'CI Authorization Policy:',
|
||||
idAuthorizationPolicy: 'Authorized by node:',
|
||||
view: 'View permissions'
|
||||
},
|
||||
tree: {
|
||||
tips1: 'Please go to Preference page first to complete your subscription!',
|
||||
subSettings: 'Settings',
|
||||
}
|
||||
}
|
||||
export default cmdb_en
|
||||
|
|
|
@ -1,491 +1,502 @@
|
|||
const cmdb_zh = {
|
||||
relation: '关系',
|
||||
attribute: '属性',
|
||||
menu: {
|
||||
views: '视图',
|
||||
config: '配置',
|
||||
backend: '管理端',
|
||||
ciTable: '资源数据',
|
||||
ciTree: '资源层级',
|
||||
ciSearch: '资源搜索',
|
||||
adCIs: '自动发现池',
|
||||
preference: '我的订阅',
|
||||
batchUpload: '批量导入',
|
||||
citypeManage: '模型配置',
|
||||
backendManage: '后台管理',
|
||||
customDashboard: '定制仪表盘',
|
||||
serviceTreeDefine: '服务树定义',
|
||||
citypeRelation: '模型关系',
|
||||
operationHistory: '操作审计',
|
||||
relationType: '关系类型',
|
||||
ad: '自动发现',
|
||||
cidetail: 'CI 详情'
|
||||
},
|
||||
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##k2,web请求返回{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: '事件',
|
||||
reg: '正则校验',
|
||||
isInherit: '是否继承',
|
||||
inheritType: '继承模型',
|
||||
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||
inheritFrom: '属性继承自{name}',
|
||||
groupInheritFrom: '请至{name}进行修改'
|
||||
},
|
||||
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: '支持文件类型:xls,xlsx',
|
||||
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: '规则导出',
|
||||
accept: '入库',
|
||||
acceptBy: '入库人',
|
||||
acceptTime: '入库时间',
|
||||
confirmAccept: '确认入库?',
|
||||
acceptSuccess: '入库成功',
|
||||
isAccept: '是否入库',
|
||||
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: '字段设置',
|
||||
share: '分享',
|
||||
noPermission: '暂无权限'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: '删除节点',
|
||||
tips1: '例:q=os_version:centos&sort=os_version',
|
||||
tips2: '表达式搜索',
|
||||
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
||||
copyFailed: '复制失败',
|
||||
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
||||
},
|
||||
tree: {
|
||||
tips1: '请先到 我的订阅 页面完成订阅!',
|
||||
subSettings: '订阅设置',
|
||||
}
|
||||
}
|
||||
export default cmdb_zh
|
||||
const cmdb_zh = {
|
||||
relation: '关系',
|
||||
attribute: '属性',
|
||||
menu: {
|
||||
views: '视图',
|
||||
config: '配置',
|
||||
backend: '管理端',
|
||||
ciTable: '资源数据',
|
||||
ciTree: '资源层级',
|
||||
ciSearch: '资源搜索',
|
||||
adCIs: '自动发现池',
|
||||
preference: '我的订阅',
|
||||
batchUpload: '批量导入',
|
||||
citypeManage: '模型配置',
|
||||
backendManage: '后台管理',
|
||||
customDashboard: '定制仪表盘',
|
||||
serviceTreeDefine: '服务树定义',
|
||||
citypeRelation: '模型关系',
|
||||
operationHistory: '操作审计',
|
||||
relationType: '关系类型',
|
||||
ad: '自动发现',
|
||||
cidetail: 'CI 详情'
|
||||
},
|
||||
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: '请选择唯一标识',
|
||||
uniqueKeyTips: 'json、密码、计算属性、预定义值属性不能作为唯一标识',
|
||||
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##k2,web请求返回{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: '事件',
|
||||
reg: '正则校验',
|
||||
isInherit: '是否继承',
|
||||
inheritType: '继承模型',
|
||||
inheritTypePlaceholder: '请选择继承模型(多选)',
|
||||
inheritFrom: '属性继承自{name}',
|
||||
groupInheritFrom: '请至{name}进行修改'
|
||||
},
|
||||
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、密码、链接属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤',
|
||||
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: '支持文件类型:xls,xlsx',
|
||||
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: '规则导出',
|
||||
accept: '入库',
|
||||
acceptBy: '入库人',
|
||||
acceptTime: '入库时间',
|
||||
confirmAccept: '确认入库?',
|
||||
acceptSuccess: '入库成功',
|
||||
isAccept: '是否入库',
|
||||
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: '字段设置',
|
||||
share: '分享',
|
||||
noPermission: '暂无权限'
|
||||
},
|
||||
serviceTree: {
|
||||
deleteNode: '删除节点',
|
||||
tips1: '例:q=os_version:centos&sort=os_version',
|
||||
tips2: '表达式搜索',
|
||||
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
|
||||
copyFailed: '复制失败',
|
||||
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
|
||||
batch: '批量操作',
|
||||
grantTitle: '授权(查看权限)',
|
||||
userPlaceholder: '请选择用户',
|
||||
rolePlaceholder: '请选择角色',
|
||||
grantedByServiceTree: '服务树授权:',
|
||||
grantedByServiceTreeTips: '请先在服务树里删掉节点授权',
|
||||
peopleHasRead: '当前有查看权限的人员:',
|
||||
authorizationPolicy: '实例授权策略:',
|
||||
idAuthorizationPolicy: '按节点授权的:',
|
||||
view: '查看权限'
|
||||
},
|
||||
tree: {
|
||||
tips1: '请先到 我的订阅 页面完成订阅!',
|
||||
subSettings: '订阅设置',
|
||||
}
|
||||
}
|
||||
export default cmdb_zh
|
||||
|
|
|
@ -1,394 +1,430 @@
|
|||
<template>
|
||||
<CustomDrawer
|
||||
:title="title + CIType.alias"
|
||||
width="800"
|
||||
@close="handleClose"
|
||||
:maskClosable="false"
|
||||
:visible="visible"
|
||||
wrapClassName="create-instance-form"
|
||||
:bodyStyle="{ paddingTop: 0 }"
|
||||
:headerStyle="{ borderBottom: 'none' }"
|
||||
>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleClose">{{ $t('cancel') }}</a-button>
|
||||
<a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button>
|
||||
</div>
|
||||
<template v-if="action === 'create'">
|
||||
<template v-for="group in attributesByGroup">
|
||||
<CreateInstanceFormByGroup
|
||||
:ref="`createInstanceFormByGroup_${group.id}`"
|
||||
:key="group.id || group.name"
|
||||
:group="group"
|
||||
@handleFocusInput="handleFocusInput"
|
||||
:attributeList="attributeList"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="parentsType && parentsType.length">
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{
|
||||
$t('cmdb.menu.citypeRelation')
|
||||
}}</a-divider>
|
||||
<a-form>
|
||||
<a-row :gutter="24" align="top" type="flex">
|
||||
<a-col :span="12" v-for="item in parentsType" :key="item.id">
|
||||
<a-form-item :label="item.alias || item.name" :colon="false">
|
||||
<a-input-group compact style="width: 100%">
|
||||
<a-select v-model="parentsForm[item.name].attr">
|
||||
<a-select-option
|
||||
:title="attr.alias || attr.name"
|
||||
v-for="attr in item.attributes"
|
||||
:key="attr.name"
|
||||
:value="attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input
|
||||
:placeholder="$t('cmdb.ci.tips1')"
|
||||
v-model="parentsForm[item.name].value"
|
||||
style="width: 50%"
|
||||
/>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="action === 'update'">
|
||||
<a-form :form="form">
|
||||
<p>{{ $t('cmdb.ci.tips2') }}</p>
|
||||
<a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name">
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')">
|
||||
<el-option
|
||||
v-for="attr in attributeList"
|
||||
:key="attr.name"
|
||||
:value="attr.name"
|
||||
:disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1"
|
||||
:label="attr.alias || attr.name"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-if="getFieldType(list.name).split('%%')[0] === 'select'"
|
||||
:mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'"
|
||||
showSearch
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="choice[0]"
|
||||
:key="'New_' + choice + choice_idx"
|
||||
v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)"
|
||||
>
|
||||
<span :style="choice[1] ? choice[1].style || {} : {}">
|
||||
<ops-icon
|
||||
:style="{ color: choice[1].icon.color }"
|
||||
v-if="choice[1] && choice[1].icon && choice[1].icon.name"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
{{ choice[0] }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input-number
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
style="width: 100%"
|
||||
v-if="getFieldType(list.name) === 'input_number'"
|
||||
/>
|
||||
<a-date-picker
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
style="width: 100%"
|
||||
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'"
|
||||
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
<a-input
|
||||
v-if="getFieldType(list.name) === 'input'"
|
||||
@focus="(e) => handleFocusInput(e, list)"
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-form-item>
|
||||
<a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button>
|
||||
</a-form>
|
||||
</template>
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
||||
import { addCI } from '@/modules/cmdb/api/ci'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import { valueTypeMap } from '../../../utils/const'
|
||||
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
||||
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
|
||||
export default {
|
||||
name: 'CreateInstanceForm',
|
||||
components: {
|
||||
ElSelect: Select,
|
||||
ElOption: Option,
|
||||
JsonEditor,
|
||||
CreateInstanceFormByGroup,
|
||||
},
|
||||
props: {
|
||||
typeIdFromRelation: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
action: '',
|
||||
form: this.$form.createForm(this),
|
||||
visible: false,
|
||||
attributeList: [],
|
||||
|
||||
CIType: {},
|
||||
|
||||
batchUpdateLists: [],
|
||||
editAttr: null,
|
||||
attributesByGroup: [],
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' '
|
||||
},
|
||||
typeId() {
|
||||
if (this.typeIdFromRelation) {
|
||||
return this.typeIdFromRelation
|
||||
}
|
||||
return this.$router.currentRoute.meta.typeId
|
||||
},
|
||||
valueTypeMap() {
|
||||
return valueTypeMap()
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
getFieldType: this.getFieldType,
|
||||
}
|
||||
},
|
||||
inject: ['attrList'],
|
||||
methods: {
|
||||
moment,
|
||||
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)
|
||||
await getCITypeGroupById(this.typeId).then((res1) => {
|
||||
const _attributesByGroup = res1.map((g) => {
|
||||
g.attributes = g.attributes.filter((attr) => !attr.is_computed)
|
||||
return g
|
||||
})
|
||||
const attrHasGroupIds = []
|
||||
res1.forEach((g) => {
|
||||
const id = g.attributes.map((attr) => attr.id)
|
||||
attrHasGroupIds.push(...id)
|
||||
})
|
||||
const otherGroupAttr = this.attributeList.filter(
|
||||
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed
|
||||
)
|
||||
if (otherGroupAttr.length) {
|
||||
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
||||
}
|
||||
this.attributesByGroup = _attributesByGroup
|
||||
})
|
||||
},
|
||||
createInstance() {
|
||||
const _this = this
|
||||
if (_this.action === 'update') {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
Object.keys(values).forEach((k) => {
|
||||
const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
if (
|
||||
_tempFind.value_type === '3' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
if (
|
||||
_tempFind.value_type === '4' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD')
|
||||
}
|
||||
if (_tempFind.value_type === '6') {
|
||||
values[k] = values[k] ? JSON.parse(values[k]) : undefined
|
||||
}
|
||||
})
|
||||
|
||||
_this.$emit('submit', values)
|
||||
})
|
||||
} else {
|
||||
let values = {}
|
||||
for (let i = 0; i < this.attributesByGroup.length; i++) {
|
||||
const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData()
|
||||
if (data === 'error') {
|
||||
return
|
||||
}
|
||||
values = { ...values, ...data }
|
||||
}
|
||||
|
||||
Object.keys(values).forEach((k) => {
|
||||
const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
if (
|
||||
_tempFind.value_type === '3' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
if (
|
||||
_tempFind.value_type === '4' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD')
|
||||
}
|
||||
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) => {
|
||||
if (this.parentsForm[type].value) {
|
||||
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
|
||||
}
|
||||
})
|
||||
addCI(values).then((res) => {
|
||||
_this.$message.success(this.$t('addSuccess'))
|
||||
_this.visible = false
|
||||
_this.$emit('reload', { ci_id: res.ci_id })
|
||||
})
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
},
|
||||
handleOpen(visible, action) {
|
||||
this.visible = visible
|
||||
this.action = action
|
||||
this.$nextTick(() => {
|
||||
this.form.resetFields()
|
||||
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
|
||||
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
|
||||
})
|
||||
if (action === 'create') {
|
||||
getCITypeParent(this.typeId).then(async (res) => {
|
||||
for (let i = 0; i < res.parents.length; i++) {
|
||||
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
|
||||
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) => {
|
||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||
_parentsForm[item.name] = { attr: _find.name, value: '' }
|
||||
})
|
||||
this.parentsForm = _parentsForm
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getFieldType(name) {
|
||||
const _find = this.attributeList.find((item) => item.name === name)
|
||||
if (_find) {
|
||||
if (_find.is_choice) {
|
||||
if (_find.is_list) {
|
||||
return 'select%%multiple'
|
||||
}
|
||||
return 'select'
|
||||
} else if (_find.value_type === '0' || _find.value_type === '1') {
|
||||
return 'input_number'
|
||||
} else if (_find.value_type === '4' || _find.value_type === '3') {
|
||||
return this.valueTypeMap[_find.value_type]
|
||||
} else {
|
||||
return 'input'
|
||||
}
|
||||
}
|
||||
return 'input'
|
||||
},
|
||||
getSelectFieldOptions(name) {
|
||||
const _find = this.attributeList.find((item) => item.name === name)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleAdd() {
|
||||
this.batchUpdateLists.push({ name: undefined })
|
||||
},
|
||||
handleDelete(name) {
|
||||
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)
|
||||
if (_tempFind.value_type === '6') {
|
||||
this.editAttr = attr
|
||||
e.srcElement.blur()
|
||||
const jsonData = this.form.getFieldValue(attr.name)
|
||||
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) })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.create-instance-form {
|
||||
.ant-form-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.ant-drawer-body {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 110px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:title="title + CIType.alias"
|
||||
width="800"
|
||||
@close="handleClose"
|
||||
:maskClosable="false"
|
||||
:visible="visible"
|
||||
wrapClassName="create-instance-form"
|
||||
:bodyStyle="{ paddingTop: 0 }"
|
||||
:headerStyle="{ borderBottom: 'none' }"
|
||||
>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleClose">{{ $t('cancel') }}</a-button>
|
||||
<a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button>
|
||||
</div>
|
||||
<template v-if="action === 'create'">
|
||||
<template v-for="group in attributesByGroup">
|
||||
<CreateInstanceFormByGroup
|
||||
:ref="`createInstanceFormByGroup_${group.id}`"
|
||||
:key="group.id || group.name"
|
||||
:group="group"
|
||||
@handleFocusInput="handleFocusInput"
|
||||
:attributeList="attributeList"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="parentsType && parentsType.length">
|
||||
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{
|
||||
$t('cmdb.menu.citypeRelation')
|
||||
}}</a-divider>
|
||||
<a-form>
|
||||
<a-row :gutter="24" align="top" type="flex">
|
||||
<a-col :span="12" v-for="item in parentsType" :key="item.id">
|
||||
<a-form-item :label="item.alias || item.name" :colon="false">
|
||||
<a-input-group compact style="width: 100%">
|
||||
<a-select v-model="parentsForm[item.name].attr">
|
||||
<a-select-option
|
||||
:title="attr.alias || attr.name"
|
||||
v-for="attr in item.attributes"
|
||||
:key="attr.name"
|
||||
:value="attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input
|
||||
:placeholder="$t('cmdb.ci.tips1')"
|
||||
v-model="parentsForm[item.name].value"
|
||||
style="width: 50%"
|
||||
/>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="action === 'update'">
|
||||
<a-form :form="form">
|
||||
<p>{{ $t('cmdb.ci.tips2') }}</p>
|
||||
<a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name">
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')">
|
||||
<el-option
|
||||
v-for="attr in attributeList"
|
||||
:key="attr.name"
|
||||
:value="attr.name"
|
||||
:disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1"
|
||||
:label="attr.alias || attr.name"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<a-select
|
||||
:style="{ width: '100%' }"
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
:placeholder="$t('placeholder2')"
|
||||
v-if="getFieldType(list.name).split('%%')[0] === 'select'"
|
||||
:mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'"
|
||||
showSearch
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="choice[0]"
|
||||
:key="'New_' + choice + choice_idx"
|
||||
v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)"
|
||||
>
|
||||
<span :style="choice[1] ? choice[1].style || {} : {}">
|
||||
<ops-icon
|
||||
:style="{ color: choice[1].icon.color }"
|
||||
v-if="choice[1] && choice[1].icon && choice[1].icon.name"
|
||||
:type="choice[1].icon.name"
|
||||
/>
|
||||
{{ choice[0] }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input-number
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
style="width: 100%"
|
||||
v-if="getFieldType(list.name) === 'input_number'"
|
||||
/>
|
||||
<a-date-picker
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
style="width: 100%"
|
||||
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
|
||||
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'"
|
||||
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }"
|
||||
/>
|
||||
<a-input
|
||||
v-if="getFieldType(list.name) === 'input'"
|
||||
@focus="(e) => handleFocusInput(e, list)"
|
||||
v-decorator="[list.name, { rules: [{ required: false }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-form-item>
|
||||
<a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button>
|
||||
</a-form>
|
||||
</template>
|
||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
|
||||
import { addCI } from '@/modules/cmdb/api/ci'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import { valueTypeMap } from '../../../utils/const'
|
||||
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
|
||||
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
|
||||
|
||||
export default {
|
||||
name: 'CreateInstanceForm',
|
||||
components: {
|
||||
ElSelect: Select,
|
||||
ElOption: Option,
|
||||
JsonEditor,
|
||||
CreateInstanceFormByGroup,
|
||||
},
|
||||
props: {
|
||||
typeIdFromRelation: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
action: '',
|
||||
form: this.$form.createForm(this),
|
||||
visible: false,
|
||||
attributeList: [],
|
||||
|
||||
CIType: {},
|
||||
|
||||
batchUpdateLists: [],
|
||||
editAttr: null,
|
||||
attributesByGroup: [],
|
||||
parentsType: [],
|
||||
parentsForm: {},
|
||||
canEdit: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' '
|
||||
},
|
||||
typeId() {
|
||||
if (this.typeIdFromRelation) {
|
||||
return this.typeIdFromRelation
|
||||
}
|
||||
return this.$router.currentRoute.meta.typeId
|
||||
},
|
||||
valueTypeMap() {
|
||||
return valueTypeMap()
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
getFieldType: this.getFieldType,
|
||||
}
|
||||
},
|
||||
inject: ['attrList'],
|
||||
methods: {
|
||||
moment,
|
||||
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)
|
||||
await getCITypeGroupById(this.typeId).then((res1) => {
|
||||
const _attributesByGroup = res1.map((g) => {
|
||||
g.attributes = g.attributes.filter((attr) => !attr.is_computed)
|
||||
return g
|
||||
})
|
||||
const attrHasGroupIds = []
|
||||
res1.forEach((g) => {
|
||||
const id = g.attributes.map((attr) => attr.id)
|
||||
attrHasGroupIds.push(...id)
|
||||
})
|
||||
const otherGroupAttr = this.attributeList.filter(
|
||||
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed
|
||||
)
|
||||
if (otherGroupAttr.length) {
|
||||
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
||||
}
|
||||
console.log(otherGroupAttr, _attributesByGroup)
|
||||
this.attributesByGroup = _attributesByGroup
|
||||
})
|
||||
},
|
||||
createInstance() {
|
||||
const _this = this
|
||||
if (_this.action === 'update') {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
Object.keys(values).forEach((k) => {
|
||||
const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
if (
|
||||
_tempFind.value_type === '3' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
if (
|
||||
_tempFind.value_type === '4' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD')
|
||||
}
|
||||
if (_tempFind.value_type === '6') {
|
||||
values[k] = values[k] ? JSON.parse(values[k]) : undefined
|
||||
}
|
||||
})
|
||||
|
||||
_this.$emit('submit', values)
|
||||
})
|
||||
} else {
|
||||
let values = {}
|
||||
for (let i = 0; i < this.attributesByGroup.length; i++) {
|
||||
const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData()
|
||||
if (data === 'error') {
|
||||
return
|
||||
}
|
||||
values = { ...values, ...data }
|
||||
}
|
||||
|
||||
Object.keys(values).forEach((k) => {
|
||||
const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
if (
|
||||
_tempFind.value_type === '3' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
if (
|
||||
_tempFind.value_type === '4' &&
|
||||
values[k] &&
|
||||
Object.prototype.toString.call(values[k]) === '[object Object]'
|
||||
) {
|
||||
values[k] = values[k].format('YYYY-MM-DD')
|
||||
}
|
||||
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) => {
|
||||
if (this.parentsForm[type].value) {
|
||||
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
|
||||
}
|
||||
})
|
||||
addCI(values).then((res) => {
|
||||
_this.$message.success(this.$t('addSuccess'))
|
||||
_this.visible = false
|
||||
_this.$emit('reload', { ci_id: res.ci_id })
|
||||
})
|
||||
}
|
||||
|
||||
// this.form.validateFields((err, values) => {
|
||||
// if (err) {
|
||||
// _this.$message.error('字段填写不符合要求!')
|
||||
// return
|
||||
// }
|
||||
// Object.keys(values).forEach((k) => {
|
||||
// if (Object.prototype.toString.call(values[k]) === '[object Object]' && values[k]) {
|
||||
// values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
|
||||
// }
|
||||
// const _tempFind = this.attributeList.find((item) => item.name === k)
|
||||
// if (_tempFind.value_type === '6') {
|
||||
// values[k] = values[k] ? JSON.parse(values[k]) : undefined
|
||||
// }
|
||||
// })
|
||||
|
||||
// if (_this.action === 'update') {
|
||||
// _this.$emit('submit', values)
|
||||
// return
|
||||
// }
|
||||
// values.ci_type = _this.typeId
|
||||
// console.log(values)
|
||||
// this.attributesByGroup.forEach((group) => {
|
||||
// this.$refs[`createInstanceFormByGroup_${group.id}`][0].getData()
|
||||
// })
|
||||
// console.log(1111)
|
||||
// // addCI(values).then((res) => {
|
||||
// // _this.$message.success('新增成功!')
|
||||
// // _this.visible = false
|
||||
// // _this.$emit('reload')
|
||||
// // })
|
||||
// })
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
},
|
||||
handleOpen(visible, action) {
|
||||
this.visible = visible
|
||||
this.action = action
|
||||
this.$nextTick(() => {
|
||||
this.form.resetFields()
|
||||
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
|
||||
this.batchUpdateLists = [{ name: this.attributeList[0].name }]
|
||||
})
|
||||
if (action === 'create') {
|
||||
getCITypeParent(this.typeId).then(async (res) => {
|
||||
for (let i = 0; i < res.parents.length; i++) {
|
||||
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
|
||||
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) => {
|
||||
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
|
||||
_parentsForm[item.name] = { attr: _find.name, value: '' }
|
||||
})
|
||||
this.parentsForm = _parentsForm
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getFieldType(name) {
|
||||
const _find = this.attributeList.find((item) => item.name === name)
|
||||
if (_find) {
|
||||
if (_find.is_choice) {
|
||||
if (_find.is_list) {
|
||||
return 'select%%multiple'
|
||||
}
|
||||
return 'select'
|
||||
} else if (_find.value_type === '0' || _find.value_type === '1') {
|
||||
return 'input_number'
|
||||
} else if (_find.value_type === '4' || _find.value_type === '3') {
|
||||
return this.valueTypeMap[_find.value_type]
|
||||
} else {
|
||||
return 'input'
|
||||
}
|
||||
}
|
||||
return 'input'
|
||||
},
|
||||
getSelectFieldOptions(name) {
|
||||
const _find = this.attributeList.find((item) => item.name === name)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleAdd() {
|
||||
this.batchUpdateLists.push({ name: undefined })
|
||||
},
|
||||
handleDelete(name) {
|
||||
const _idx = this.batchUpdateLists.findIndex((item) => item.name === name)
|
||||
if (_idx > -1) {
|
||||
this.batchUpdateLists.splice(_idx, 1)
|
||||
}
|
||||
},
|
||||
// filterOption(input, option) {
|
||||
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// },
|
||||
handleFocusInput(e, attr) {
|
||||
console.log(attr)
|
||||
const _tempFind = this.attributeList.find((item) => item.name === attr.name)
|
||||
if (_tempFind.value_type === '6') {
|
||||
this.editAttr = attr
|
||||
e.srcElement.blur()
|
||||
const jsonData = this.form.getFieldValue(attr.name)
|
||||
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) })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.create-instance-form {
|
||||
.ant-form-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.ant-drawer-body {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 110px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,390 +1,390 @@
|
|||
<template>
|
||||
<div :style="{ height: '100%' }">
|
||||
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
|
||||
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
|
||||
<a-icon type="share-alt" />
|
||||
{{ $t('cmdb.ci.share') }}
|
||||
</a>
|
||||
<a-tab-pane key="tab_1">
|
||||
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
|
||||
<div class="ci-detail-attr">
|
||||
<el-descriptions
|
||||
:title="group.name || $t('other')"
|
||||
:key="group.name"
|
||||
v-for="group in attributeGroups"
|
||||
border
|
||||
:column="3"
|
||||
>
|
||||
<el-descriptions-item
|
||||
:label="`${attr.alias || attr.name}`"
|
||||
:key="attr.name"
|
||||
v-for="attr in group.attributes"
|
||||
>
|
||||
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
||||
<div :style="{ height: '100%', padding: '24px' }">
|
||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:data="ciHistory"
|
||||
size="small"
|
||||
height="auto"
|
||||
:span-method="mergeRowMethod"
|
||||
border
|
||||
:scroll-y="{ enabled: false }"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
:title="$t('user')"
|
||||
:filters="[]"
|
||||
:filter-method="filterUsernameMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="operate_type"
|
||||
:filters="[
|
||||
{ value: 0, label: $t('new') },
|
||||
{ value: 1, label: $t('delete') },
|
||||
{ value: 3, label: $t('update') },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
:title="$t('operation')"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ operateTypeMap[row.operate_type] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="attr_alias"
|
||||
:title="$t('cmdb.attribute')"
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column field="old" :title="$t('cmdb.history.old')"></vxe-table-column>
|
||||
<vxe-table-column field="new" :title="$t('cmdb.history.new')"></vxe-table-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_4">
|
||||
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<TriggerTable :ci_id="ci._id" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '100px',
|
||||
}"
|
||||
:style="{ paddingTop: '20%' }"
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
|
||||
</a-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { Descriptions, DescriptionsItem } from 'element-ui'
|
||||
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCIHistory } from '@/modules/cmdb/api/history'
|
||||
import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
export default {
|
||||
name: 'CiDetailTab',
|
||||
components: {
|
||||
ElDescriptions: Descriptions,
|
||||
ElDescriptionsItem: DescriptionsItem,
|
||||
CiDetailAttrContent,
|
||||
CiDetailRelation,
|
||||
TriggerTable,
|
||||
},
|
||||
props: {
|
||||
typeId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
treeViewsLevels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ci: {},
|
||||
attributeGroups: [],
|
||||
activeTabKey: 'tab_1',
|
||||
rowSpanMap: {},
|
||||
ciHistory: [],
|
||||
ciId: null,
|
||||
ci_types: [],
|
||||
hasPermission: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
operateTypeMap() {
|
||||
return {
|
||||
0: this.$t('new'),
|
||||
1: this.$t('delete'),
|
||||
2: this.$t('update'),
|
||||
}
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
ci_types: () => {
|
||||
return this.ci_types
|
||||
},
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
reload: {
|
||||
from: 'reload',
|
||||
default: null,
|
||||
},
|
||||
handleSearch: {
|
||||
from: 'handleSearch',
|
||||
default: null,
|
||||
},
|
||||
attrList: {
|
||||
from: 'attrList',
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
await this.getCI()
|
||||
if (this.hasPermission) {
|
||||
this.getAttributes()
|
||||
this.getCIHistory()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
}
|
||||
},
|
||||
getAttributes() {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
.then((res) => {
|
||||
this.attributeGroups = res
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
async getCI() {
|
||||
await getCIById(this.ciId)
|
||||
.then((res) => {
|
||||
if (res.result.length) {
|
||||
this.ci = res.result[0]
|
||||
} else {
|
||||
this.hasPermission = false
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
|
||||
getCIHistory() {
|
||||
getCIHistory(this.ciId)
|
||||
.then((res) => {
|
||||
this.ciHistory = res
|
||||
|
||||
const rowSpanMap = {}
|
||||
let startIndex = 0
|
||||
let startCount = 1
|
||||
res.forEach((item, index) => {
|
||||
if (index === 0) {
|
||||
return
|
||||
}
|
||||
if (res[index].record_id === res[startIndex].record_id) {
|
||||
startCount += 1
|
||||
rowSpanMap[index] = 0
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
}
|
||||
} else {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
startIndex = index
|
||||
startCount = 1
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[index] = 1
|
||||
}
|
||||
}
|
||||
})
|
||||
this.rowSpanMap = rowSpanMap
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
},
|
||||
changeTab(key) {
|
||||
this.activeTabKey = key
|
||||
if (key === 'tab_3') {
|
||||
this.$nextTick(() => {
|
||||
const $table = this.$refs.xTable
|
||||
if ($table) {
|
||||
const usernameColumn = $table.getColumnByField('username')
|
||||
const attrColumn = $table.getColumnByField('attr_alias')
|
||||
if (usernameColumn) {
|
||||
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
|
||||
$table.setFilter(
|
||||
usernameColumn,
|
||||
usernameList.map((item) => {
|
||||
return {
|
||||
value: item,
|
||||
label: item,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (attrColumn) {
|
||||
$table.setFilter(
|
||||
attrColumn,
|
||||
this.attrList().map((attr) => {
|
||||
return { value: attr.alias || attr.name, label: attr.alias || attr.name }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
filterUsernameMethod({ value, row, column }) {
|
||||
return row.username === value
|
||||
},
|
||||
filterOperateMethod({ value, row, column }) {
|
||||
return Number(row.operate_type) === Number(value)
|
||||
},
|
||||
filterAttrMethod({ value, row, column }) {
|
||||
return row.attr_alias === value
|
||||
},
|
||||
refresh(editAttrName) {
|
||||
this.getCI()
|
||||
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'username']
|
||||
const cellValue1 = row['created_at']
|
||||
const cellValue2 = row['username']
|
||||
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCIByself(params, editAttrName) {
|
||||
const _ci = { ..._.cloneDeep(this.ci), ...params }
|
||||
this.ci = _ci
|
||||
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
shareCi() {
|
||||
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
|
||||
this.$copyText(text)
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('copySuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ci-detail-tab {
|
||||
height: 100%;
|
||||
.ant-tabs-content {
|
||||
height: calc(100% - 45px);
|
||||
.ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.ant-tabs-bar {
|
||||
margin: 0;
|
||||
}
|
||||
.ant-tabs-extra-content {
|
||||
line-height: 44px;
|
||||
}
|
||||
.ci-detail-attr {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
.el-descriptions-item__content {
|
||||
cursor: default;
|
||||
&:hover a {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
.el-descriptions:first-child > .el-descriptions__header {
|
||||
margin-top: 0;
|
||||
}
|
||||
.el-descriptions__header {
|
||||
margin-bottom: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-form-item-control {
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div :style="{ height: '100%' }">
|
||||
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
|
||||
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
|
||||
<a-icon type="share-alt" />
|
||||
{{ $t('cmdb.ci.share') }}
|
||||
</a>
|
||||
<a-tab-pane key="tab_1">
|
||||
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
|
||||
<div class="ci-detail-attr">
|
||||
<el-descriptions
|
||||
:title="group.name || $t('other')"
|
||||
:key="group.name"
|
||||
v-for="group in attributeGroups"
|
||||
border
|
||||
:column="3"
|
||||
>
|
||||
<el-descriptions-item
|
||||
:label="`${attr.alias || attr.name}`"
|
||||
:key="attr.name"
|
||||
v-for="attr in group.attributes"
|
||||
>
|
||||
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
|
||||
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
|
||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:data="ciHistory"
|
||||
size="small"
|
||||
height="auto"
|
||||
:span-method="mergeRowMethod"
|
||||
border
|
||||
:scroll-y="{ enabled: false }"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
:title="$t('user')"
|
||||
:filters="[]"
|
||||
:filter-method="filterUsernameMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="operate_type"
|
||||
:filters="[
|
||||
{ value: 0, label: $t('new') },
|
||||
{ value: 1, label: $t('delete') },
|
||||
{ value: 3, label: $t('update') },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
:title="$t('operation')"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ operateTypeMap[row.operate_type] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="attr_alias"
|
||||
:title="$t('cmdb.attribute')"
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column field="old" :title="$t('cmdb.history.old')"></vxe-table-column>
|
||||
<vxe-table-column field="new" :title="$t('cmdb.history.new')"></vxe-table-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_4">
|
||||
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
|
||||
<div :style="{ padding: '24px', height: '100%' }">
|
||||
<TriggerTable :ci_id="ci._id" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '100px',
|
||||
}"
|
||||
:style="{ paddingTop: '20%' }"
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
|
||||
</a-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { Descriptions, DescriptionsItem } from 'element-ui'
|
||||
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCIHistory } from '@/modules/cmdb/api/history'
|
||||
import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
export default {
|
||||
name: 'CiDetailTab',
|
||||
components: {
|
||||
ElDescriptions: Descriptions,
|
||||
ElDescriptionsItem: DescriptionsItem,
|
||||
CiDetailAttrContent,
|
||||
CiDetailRelation,
|
||||
TriggerTable,
|
||||
},
|
||||
props: {
|
||||
typeId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
treeViewsLevels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ci: {},
|
||||
attributeGroups: [],
|
||||
activeTabKey: 'tab_1',
|
||||
rowSpanMap: {},
|
||||
ciHistory: [],
|
||||
ciId: null,
|
||||
ci_types: [],
|
||||
hasPermission: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
operateTypeMap() {
|
||||
return {
|
||||
0: this.$t('new'),
|
||||
1: this.$t('delete'),
|
||||
2: this.$t('update'),
|
||||
}
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
ci_types: () => {
|
||||
return this.ci_types
|
||||
},
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
reload: {
|
||||
from: 'reload',
|
||||
default: null,
|
||||
},
|
||||
handleSearch: {
|
||||
from: 'handleSearch',
|
||||
default: null,
|
||||
},
|
||||
attrList: {
|
||||
from: 'attrList',
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
await this.getCI()
|
||||
if (this.hasPermission) {
|
||||
this.getAttributes()
|
||||
this.getCIHistory()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
}
|
||||
},
|
||||
getAttributes() {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
.then((res) => {
|
||||
this.attributeGroups = res
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
async getCI() {
|
||||
await getCIById(this.ciId)
|
||||
.then((res) => {
|
||||
if (res.result.length) {
|
||||
this.ci = res.result[0]
|
||||
} else {
|
||||
this.hasPermission = false
|
||||
}
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
|
||||
getCIHistory() {
|
||||
getCIHistory(this.ciId)
|
||||
.then((res) => {
|
||||
this.ciHistory = res
|
||||
|
||||
const rowSpanMap = {}
|
||||
let startIndex = 0
|
||||
let startCount = 1
|
||||
res.forEach((item, index) => {
|
||||
if (index === 0) {
|
||||
return
|
||||
}
|
||||
if (res[index].record_id === res[startIndex].record_id) {
|
||||
startCount += 1
|
||||
rowSpanMap[index] = 0
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
}
|
||||
} else {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
startIndex = index
|
||||
startCount = 1
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[index] = 1
|
||||
}
|
||||
}
|
||||
})
|
||||
this.rowSpanMap = rowSpanMap
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
},
|
||||
changeTab(key) {
|
||||
this.activeTabKey = key
|
||||
if (key === 'tab_3') {
|
||||
this.$nextTick(() => {
|
||||
const $table = this.$refs.xTable
|
||||
if ($table) {
|
||||
const usernameColumn = $table.getColumnByField('username')
|
||||
const attrColumn = $table.getColumnByField('attr_alias')
|
||||
if (usernameColumn) {
|
||||
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
|
||||
$table.setFilter(
|
||||
usernameColumn,
|
||||
usernameList.map((item) => {
|
||||
return {
|
||||
value: item,
|
||||
label: item,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (attrColumn) {
|
||||
$table.setFilter(
|
||||
attrColumn,
|
||||
this.attrList().map((attr) => {
|
||||
return { value: attr.alias || attr.name, label: attr.alias || attr.name }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
filterUsernameMethod({ value, row, column }) {
|
||||
return row.username === value
|
||||
},
|
||||
filterOperateMethod({ value, row, column }) {
|
||||
return Number(row.operate_type) === Number(value)
|
||||
},
|
||||
filterAttrMethod({ value, row, column }) {
|
||||
return row.attr_alias === value
|
||||
},
|
||||
refresh(editAttrName) {
|
||||
this.getCI()
|
||||
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'username']
|
||||
const cellValue1 = row['created_at']
|
||||
const cellValue2 = row['username']
|
||||
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCIByself(params, editAttrName) {
|
||||
const _ci = { ..._.cloneDeep(this.ci), ...params }
|
||||
this.ci = _ci
|
||||
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
if (this.reload) {
|
||||
this.reload()
|
||||
}
|
||||
} else {
|
||||
if (this.handleSearch) {
|
||||
this.handleSearch()
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
shareCi() {
|
||||
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
|
||||
this.$copyText(text)
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('copySuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ci-detail-tab {
|
||||
height: 100%;
|
||||
.ant-tabs-content {
|
||||
height: calc(100% - 45px);
|
||||
.ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.ant-tabs-bar {
|
||||
margin: 0;
|
||||
}
|
||||
.ant-tabs-extra-content {
|
||||
line-height: 44px;
|
||||
}
|
||||
.ci-detail-attr {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
.el-descriptions-item__content {
|
||||
cursor: default;
|
||||
&:hover a {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
.el-descriptions:first-child > .el-descriptions__header {
|
||||
margin-top: 0;
|
||||
}
|
||||
.el-descriptions__header {
|
||||
margin-bottom: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-form-item-control {
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,224 +1,250 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model="visible"
|
||||
width="90%"
|
||||
:closable="false"
|
||||
:centered="true"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
@cancel="handleClose"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<div :style="{ width: '100%' }" id="add-table-modal">
|
||||
<a-spin :spinning="loading">
|
||||
<!-- <a-input
|
||||
v-model="expression"
|
||||
class="ci-searchform-expression"
|
||||
:style="{ width, marginBottom: '10px' }"
|
||||
:placeholder="placeholder"
|
||||
@focus="
|
||||
() => {
|
||||
isFocusExpression = true
|
||||
}
|
||||
"
|
||||
/> -->
|
||||
<SearchForm
|
||||
ref="searchForm"
|
||||
:typeId="addTypeId"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
@refresh="handleSearch"
|
||||
/>
|
||||
<!-- <a @click="handleSearch"><a-icon type="search"/></a> -->
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
row-id="_id"
|
||||
:data="tableData"
|
||||
:height="tableHeight"
|
||||
highlight-hover-row
|
||||
:checkbox-config="{ reserve: true }"
|
||||
@checkbox-change="onSelectChange"
|
||||
@checkbox-all="onSelectChange"
|
||||
show-overflow="tooltip"
|
||||
show-header-overflow="tooltip"
|
||||
:scroll-y="{ enabled: true, gt: 50 }"
|
||||
:scroll-x="{ enabled: true, gt: 0 }"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-column align="center" type="checkbox" width="60" fixed="left"></vxe-column>
|
||||
<vxe-table-column
|
||||
v-for="col in columns"
|
||||
:key="col.field"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:sortable="col.sortable"
|
||||
>
|
||||
<template #default="{row}" v-if="col.value_type === '6'">
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
<a-pagination
|
||||
v-model="currentPage"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="50"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
:style="{ textAlign: 'right', marginTop: '10px' }"
|
||||
@change="handleChangePage"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable no-useless-escape */
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation'
|
||||
import { getCITableColumns } from '../../../utils/helper'
|
||||
import SearchForm from '../../../components/searchForm/SearchForm.vue'
|
||||
export default {
|
||||
name: 'AddTableModal',
|
||||
components: { SearchForm },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
currentPage: 1,
|
||||
totalNumber: 0,
|
||||
tableData: [],
|
||||
columns: [],
|
||||
ciObj: {},
|
||||
ciId: null,
|
||||
addTypeId: null,
|
||||
loading: false,
|
||||
expression: '',
|
||||
isFocusExpression: false,
|
||||
type: 'children',
|
||||
preferenceAttrList: [],
|
||||
ancestor_ids: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableHeight() {
|
||||
return this.$store.state.windowHeight - 250
|
||||
},
|
||||
placeholder() {
|
||||
return this.isFocusExpression ? this.$t('cmdb.serviceTreetips1') : this.$t('cmdb.serviceTreetips2')
|
||||
},
|
||||
width() {
|
||||
return this.isFocusExpression ? '500px' : '100px'
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
||||
console.log(ciObj, ciId, addTypeId, type)
|
||||
this.visible = true
|
||||
this.ciObj = ciObj
|
||||
this.ciId = ciId
|
||||
this.addTypeId = addTypeId
|
||||
this.type = type
|
||||
this.ancestor_ids = ancestor_ids
|
||||
await getSubscribeAttributes(addTypeId).then((res) => {
|
||||
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
||||
})
|
||||
this.getTableData(true)
|
||||
},
|
||||
async getTableData(isInit) {
|
||||
if (this.addTypeId) {
|
||||
await this.fetchData(isInit)
|
||||
}
|
||||
},
|
||||
async fetchData(isInit) {
|
||||
this.loading = true
|
||||
// if (isInit) {
|
||||
// const subscribed = await getSubscribeAttributes(this.addTypeId)
|
||||
// this.preferenceAttrList = subscribed.attributes // 已经订阅的全部列
|
||||
// }
|
||||
let sort, fuzzySearch, expression, exp
|
||||
if (!isInit) {
|
||||
fuzzySearch = this.$refs['searchForm'].fuzzySearch
|
||||
expression = this.$refs['searchForm'].expression || ''
|
||||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
|
||||
exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
}
|
||||
|
||||
await searchCI({
|
||||
q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||
count: 50,
|
||||
page: this.currentPage,
|
||||
sort,
|
||||
})
|
||||
.then((res) => {
|
||||
this.tableData = res.result
|
||||
this.totalNumber = res.numfound
|
||||
this.columns = this.getColumns(res.result, this.preferenceAttrList)
|
||||
this.$nextTick(() => {
|
||||
const _table = this.$refs.xTable
|
||||
if (_table) {
|
||||
_table.refreshColumn()
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
getColumns(data, attrList) {
|
||||
const modalDom = document.getElementById('add-table-modal')
|
||||
if (modalDom) {
|
||||
const width = modalDom.clientWidth - 50
|
||||
return getCITableColumns(data, attrList, width)
|
||||
}
|
||||
return []
|
||||
},
|
||||
onSelectChange() {},
|
||||
handleClose() {
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.currentPage = 1
|
||||
this.expression = ''
|
||||
this.isFocusExpression = false
|
||||
this.visible = false
|
||||
},
|
||||
async handleOk() {
|
||||
const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords()
|
||||
const selectRecordsReserved = this.$refs.xTable.getCheckboxReserveRecords()
|
||||
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
|
||||
if (ciIds.length) {
|
||||
if (this.type === 'children') {
|
||||
await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids)
|
||||
} else {
|
||||
await batchUpdateCIRelationParents(ciIds, [this.ciId])
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.handleClose()
|
||||
this.$emit('reload')
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.currentPage = 1
|
||||
this.fetchData()
|
||||
},
|
||||
handleChangePage(page, pageSize) {
|
||||
this.currentPage = page
|
||||
this.fetchData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<template>
|
||||
<a-modal
|
||||
v-model="visible"
|
||||
width="90%"
|
||||
:closable="false"
|
||||
:centered="true"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
@cancel="handleClose"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<div :style="{ width: '100%' }" id="add-table-modal">
|
||||
<a-spin :spinning="loading">
|
||||
<SearchForm
|
||||
ref="searchForm"
|
||||
:typeId="addTypeId"
|
||||
:preferenceAttrList="preferenceAttrList"
|
||||
@refresh="handleSearch"
|
||||
>
|
||||
<a-button
|
||||
@click="
|
||||
() => {
|
||||
$refs.createInstanceForm.handleOpen(true, 'create')
|
||||
}
|
||||
"
|
||||
slot="extraContent"
|
||||
type="primary"
|
||||
size="small"
|
||||
>新增</a-button
|
||||
>
|
||||
</SearchForm>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
row-id="_id"
|
||||
:data="tableData"
|
||||
:height="tableHeight"
|
||||
highlight-hover-row
|
||||
:checkbox-config="{ reserve: true }"
|
||||
@checkbox-change="onSelectChange"
|
||||
@checkbox-all="onSelectChange"
|
||||
show-overflow="tooltip"
|
||||
show-header-overflow="tooltip"
|
||||
:scroll-y="{ enabled: true, gt: 50 }"
|
||||
:scroll-x="{ enabled: true, gt: 0 }"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-column align="center" type="checkbox" width="60" fixed="left"></vxe-column>
|
||||
<vxe-table-column
|
||||
v-for="col in columns"
|
||||
:key="col.field"
|
||||
:title="col.title"
|
||||
:field="col.field"
|
||||
:width="col.width"
|
||||
:sortable="col.sortable"
|
||||
>
|
||||
<template #default="{row}" v-if="col.value_type === '6'">
|
||||
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
</vxe-table>
|
||||
<a-pagination
|
||||
v-model="currentPage"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="50"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
:style="{ textAlign: 'right', marginTop: '10px' }"
|
||||
@change="handleChangePage"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
<CreateInstanceForm
|
||||
ref="createInstanceForm"
|
||||
:typeIdFromRelation="addTypeId"
|
||||
@reload="
|
||||
() => {
|
||||
currentPage = 1
|
||||
getTableData(true)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||
import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation'
|
||||
import { getCITableColumns } from '../../../utils/helper'
|
||||
import SearchForm from '../../../components/searchForm/SearchForm.vue'
|
||||
import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
|
||||
export default {
|
||||
name: 'AddTableModal',
|
||||
components: { SearchForm, CreateInstanceForm },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
currentPage: 1,
|
||||
totalNumber: 0,
|
||||
tableData: [],
|
||||
columns: [],
|
||||
ciObj: {},
|
||||
ciId: null,
|
||||
addTypeId: null,
|
||||
loading: false,
|
||||
expression: '',
|
||||
isFocusExpression: false,
|
||||
type: 'children',
|
||||
preferenceAttrList: [],
|
||||
ancestor_ids: undefined,
|
||||
attrList1: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableHeight() {
|
||||
return this.$store.state.windowHeight - 250
|
||||
},
|
||||
placeholder() {
|
||||
return this.isFocusExpression ? this.$t('cmdb.serviceTreetips1') : this.$t('cmdb.serviceTreetips2')
|
||||
},
|
||||
width() {
|
||||
return this.isFocusExpression ? '500px' : '100px'
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrList: () => {
|
||||
return this.attrList
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
||||
console.log(ciObj, ciId, addTypeId, type)
|
||||
this.visible = true
|
||||
this.ciObj = ciObj
|
||||
this.ciId = ciId
|
||||
this.addTypeId = addTypeId
|
||||
this.type = type
|
||||
this.ancestor_ids = ancestor_ids
|
||||
await getSubscribeAttributes(addTypeId).then((res) => {
|
||||
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
||||
})
|
||||
getCITypeAttributesById(addTypeId).then((res) => {
|
||||
this.attrList = res.attributes
|
||||
})
|
||||
this.getTableData(true)
|
||||
},
|
||||
async getTableData(isInit) {
|
||||
if (this.addTypeId) {
|
||||
await this.fetchData(isInit)
|
||||
}
|
||||
},
|
||||
async fetchData(isInit) {
|
||||
this.loading = true
|
||||
// if (isInit) {
|
||||
// const subscribed = await getSubscribeAttributes(this.addTypeId)
|
||||
// this.preferenceAttrList = subscribed.attributes // 已经订阅的全部列
|
||||
// }
|
||||
let sort, fuzzySearch, expression, exp
|
||||
if (!isInit) {
|
||||
fuzzySearch = this.$refs['searchForm'].fuzzySearch
|
||||
expression = this.$refs['searchForm'].expression || ''
|
||||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
|
||||
exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
}
|
||||
|
||||
await searchCI({
|
||||
q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||
count: 50,
|
||||
page: this.currentPage,
|
||||
sort,
|
||||
})
|
||||
.then((res) => {
|
||||
this.tableData = res.result
|
||||
this.totalNumber = res.numfound
|
||||
this.columns = this.getColumns(res.result, this.preferenceAttrList)
|
||||
this.$nextTick(() => {
|
||||
const _table = this.$refs.xTable
|
||||
if (_table) {
|
||||
_table.refreshColumn()
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
getColumns(data, attrList) {
|
||||
const modalDom = document.getElementById('add-table-modal')
|
||||
if (modalDom) {
|
||||
const width = modalDom.clientWidth - 50
|
||||
return getCITableColumns(data, attrList, width)
|
||||
}
|
||||
return []
|
||||
},
|
||||
onSelectChange() {},
|
||||
handleClose() {
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.currentPage = 1
|
||||
this.expression = ''
|
||||
this.isFocusExpression = false
|
||||
this.visible = false
|
||||
},
|
||||
async handleOk() {
|
||||
const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords()
|
||||
const selectRecordsReserved = this.$refs.xTable.getCheckboxReserveRecords()
|
||||
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
|
||||
if (ciIds.length) {
|
||||
if (this.type === 'children') {
|
||||
await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids)
|
||||
} else {
|
||||
await batchUpdateCIRelationParents(ciIds, [this.ciId])
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.$message.success(this.$t('addSuccess'))
|
||||
this.handleClose()
|
||||
this.$emit('reload')
|
||||
}, 500)
|
||||
} else {
|
||||
this.handleClose()
|
||||
this.$emit('reload')
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.currentPage = 1
|
||||
this.fetchData()
|
||||
},
|
||||
handleChangePage(page, pageSize) {
|
||||
this.currentPage = page
|
||||
this.fetchData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -1,166 +1,253 @@
|
|||
<template>
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)">
|
||||
<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">{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item>
|
||||
</a-menu>
|
||||
<div
|
||||
:style="{
|
||||
width: '100%',
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
@click="clickNode"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<template v-if="icon">
|
||||
<img
|
||||
v-if="icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span
|
||||
:style="{
|
||||
display: 'inline-block',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#d3d3d3',
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
lineHeight: '16px',
|
||||
fontSize: '12px',
|
||||
}"
|
||||
v-else
|
||||
>{{ ciTypeName ? ciTypeName[0].toUpperCase() : 'i' }}</span
|
||||
>
|
||||
<span :style="{ marginLeft: '5px' }">{{ this.title }}</span>
|
||||
</span>
|
||||
<a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
|
||||
</div>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ContextMenu',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
treeKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
levels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
currentViews: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
id2type: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isLeaf: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
ciTypes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
switchIcon: 'down',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
childLength() {
|
||||
const reg = /(?<=\()\S+(?=\))/g
|
||||
return Number(this.title.match(reg)[0])
|
||||
},
|
||||
splitTreeKey() {
|
||||
return this.treeKey.split('@^@')
|
||||
},
|
||||
_tempTree() {
|
||||
return this.splitTreeKey[this.splitTreeKey.length - 1].split('%')
|
||||
},
|
||||
_typeIdIdx() {
|
||||
return this.levels.findIndex((level) => level[0] === Number(this._tempTree[1])) // 当前节点在levels中的index
|
||||
},
|
||||
showDelete() {
|
||||
if (this._typeIdIdx === 0) {
|
||||
// 如果是第一层节点,则不能删除
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
menuList() {
|
||||
let _menuList = []
|
||||
if (this._typeIdIdx > -1 && this._typeIdIdx < this.levels.length - 1) {
|
||||
// 不是叶子节点
|
||||
const id = Number(this.levels[this._typeIdIdx + 1])
|
||||
_menuList = [
|
||||
{
|
||||
id,
|
||||
alias: this.id2type[id].alias || this.id2type[id].name,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
// 叶子节点
|
||||
_menuList = this.currentViews.node2show_types[this._tempTree[1]].map((item) => {
|
||||
return { id: item.id, alias: item.alias || item.name }
|
||||
})
|
||||
}
|
||||
return _menuList
|
||||
},
|
||||
icon() {
|
||||
const _split = this.treeKey.split('@^@')
|
||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||
const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
|
||||
return _find?.icon || null
|
||||
},
|
||||
ciTypeName() {
|
||||
const _split = this.treeKey.split('@^@')
|
||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||
const _find = this.ciTypes.find((type) => type.id === Number(currentNodeTypeId))
|
||||
return _find?.name || ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onContextMenuClick(treeKey, menuKey) {
|
||||
this.$emit('onContextMenuClick', treeKey, menuKey)
|
||||
},
|
||||
clickNode() {
|
||||
this.$emit('onNodeClick', this.treeKey)
|
||||
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'relation-views-node': true,
|
||||
'relation-views-node-checkbox': showCheckbox,
|
||||
}"
|
||||
@click="clickNode"
|
||||
>
|
||||
<span>
|
||||
<a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" />
|
||||
<template v-if="icon">
|
||||
<img
|
||||
v-if="icon.includes('$$') && icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="icon.includes('$$') && icon.split('$$')[0]"
|
||||
:style="{
|
||||
color: icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="icon.split('$$')[0]"
|
||||
/>
|
||||
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
|
||||
</template>
|
||||
<span class="relation-views-node-title">{{ this.title }}</span>
|
||||
</span>
|
||||
<a-dropdown>
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)">
|
||||
<template v-if="showBatchLevel === null">
|
||||
<a-menu-item
|
||||
v-for="item in menuList"
|
||||
:key="item.id"
|
||||
><a-icon type="plus-circle" />{{ $t('new') }} {{ item.alias }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
v-if="showDelete"
|
||||
key="delete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item>
|
||||
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item>
|
||||
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item
|
||||
key="batch"
|
||||
><ops-icon type="icon-xianxing-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchGrant"
|
||||
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchRevoke"
|
||||
><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchDelete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
</template>
|
||||
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
<a-icon class="relation-views-node-operation" type="ellipsis" />
|
||||
</a-dropdown>
|
||||
<a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ContextMenu',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
treeKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
levels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
currentViews: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
id2type: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isLeaf: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
ciTypeIcons: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
showBatchLevel: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
batchTreeKey: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
switchIcon: 'down',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
childLength() {
|
||||
const reg = /(?<=\()\S+(?=\))/g
|
||||
return Number(this.title.match(reg)[0])
|
||||
},
|
||||
splitTreeKey() {
|
||||
return this.treeKey.split('@^@')
|
||||
},
|
||||
_tempTree() {
|
||||
return this.splitTreeKey[this.splitTreeKey.length - 1].split('%')
|
||||
},
|
||||
_typeIdIdx() {
|
||||
return this.levels.findIndex((level) => level[0] === Number(this._tempTree[1])) // 当前节点在levels中的index
|
||||
},
|
||||
showDelete() {
|
||||
if (this._typeIdIdx === 0) {
|
||||
// 如果是第一层节点,则不能删除
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
menuList() {
|
||||
let _menuList = []
|
||||
if (this._typeIdIdx > -1 && this._typeIdIdx < this.levels.length - 1) {
|
||||
// 不是叶子节点
|
||||
const id = Number(this.levels[this._typeIdIdx + 1])
|
||||
_menuList = [
|
||||
{
|
||||
id,
|
||||
alias: this.id2type[id].alias || this.id2type[id].name,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
// 叶子节点
|
||||
_menuList = this.currentViews.node2show_types[this._tempTree[1]].map((item) => {
|
||||
return { id: item.id, alias: item.alias || item.name }
|
||||
})
|
||||
}
|
||||
return _menuList
|
||||
},
|
||||
icon() {
|
||||
const _split = this.treeKey.split('@^@')
|
||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||
return this.ciTypeIcons[Number(currentNodeTypeId)] ?? null
|
||||
},
|
||||
showCheckbox() {
|
||||
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onContextMenuClick(treeKey, menuKey) {
|
||||
this.$emit('onContextMenuClick', treeKey, menuKey)
|
||||
},
|
||||
clickNode() {
|
||||
this.$emit('onNodeClick', this.treeKey)
|
||||
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down'
|
||||
},
|
||||
clickCheckbox() {
|
||||
this.$emit('clickCheckbox', this.treeKey)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.relation-views-node {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
> span {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
.relation-views-node-icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: #d3d3d3;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.relation-views-node-title {
|
||||
padding-left: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: calc(100% - 16px);
|
||||
}
|
||||
}
|
||||
.relation-views-node-operation {
|
||||
display: none;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.relation-views-node-checkbox,
|
||||
.relation-views-node-moveright {
|
||||
> span {
|
||||
.relation-views-node-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.relation-views-node-title {
|
||||
width: calc(100% - 42px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.relation-views-left .ant-tree-node-content-wrapper:hover {
|
||||
.relation-views-node-operation {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.relation-views-left {
|
||||
ul:has(.relation-views-node-checkbox) > li > ul {
|
||||
margin-left: 26px;
|
||||
}
|
||||
ul:has(.relation-views-node-checkbox) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<a-modal
|
||||
width="600px"
|
||||
:bodyStyle="{
|
||||
paddingTop: 0,
|
||||
}"
|
||||
:visible="visible"
|
||||
:footer="null"
|
||||
@cancel="handleCancel"
|
||||
:title="$t('view')"
|
||||
>
|
||||
<div>
|
||||
<template v-if="readCIIdFilterPermissions && readCIIdFilterPermissions.length">
|
||||
<p>
|
||||
<strong>{{ $t('cmdb.serviceTree.idAuthorizationPolicy') }}</strong>
|
||||
<a
|
||||
@click="
|
||||
() => {
|
||||
showAllReadCIIdFilterPermissions = !showAllReadCIIdFilterPermissions
|
||||
}
|
||||
"
|
||||
v-if="readCIIdFilterPermissions.length > 10"
|
||||
><a-icon
|
||||
:type="showAllReadCIIdFilterPermissions ? 'caret-down' : 'caret-up'"
|
||||
/></a>
|
||||
</p>
|
||||
<a-tag
|
||||
v-for="item in showAllReadCIIdFilterPermissions
|
||||
? readCIIdFilterPermissions
|
||||
: readCIIdFilterPermissions.slice(0, 10)"
|
||||
:key="item.name"
|
||||
color="blue"
|
||||
:style="{ marginBottom: '5px' }"
|
||||
>{{ item.name }}</a-tag
|
||||
>
|
||||
<a-tag
|
||||
:style="{ marginBottom: '5px' }"
|
||||
v-if="readCIIdFilterPermissions.length > 10 && !showAllReadCIIdFilterPermissions"
|
||||
>+{{ readCIIdFilterPermissions.length - 10 }}</a-tag
|
||||
>
|
||||
</template>
|
||||
<a-empty v-else>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> {{ $t('noData') }} </span>
|
||||
</a-empty>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ciTypeFilterPermissions, getCIType } from '../../../api/CIType'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
|
||||
export default {
|
||||
name: 'ReadPermissionsModal',
|
||||
components: { FilterComp },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
filerPerimissions: {},
|
||||
readCIIdFilterPermissions: [],
|
||||
canSearchPreferenceAttrList: [],
|
||||
showAllReadCIIdFilterPermissions: false,
|
||||
allRoles: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRoles()
|
||||
},
|
||||
methods: {
|
||||
async loadRoles() {
|
||||
const res = await searchRole({ page_size: 9999, app_id: 'cmdb', is_all: true })
|
||||
this.allRoles = res.roles
|
||||
},
|
||||
async open(treeKey) {
|
||||
this.visible = true
|
||||
const _splitTreeKey = treeKey.split('@^@').filter((item) => !!item)
|
||||
const _treeKey = _splitTreeKey.slice(_splitTreeKey.length - 1, _splitTreeKey.length)[0].split('%')
|
||||
|
||||
const typeId = _treeKey[1]
|
||||
const _treeKeyPath = _splitTreeKey.map((item) => item.split('%')[0]).join(',')
|
||||
await ciTypeFilterPermissions(typeId).then((res) => {
|
||||
this.filerPerimissions = res
|
||||
})
|
||||
const readCIIdFilterPermissions = []
|
||||
Object.entries(this.filerPerimissions).forEach(([k, v]) => {
|
||||
const { id_filter } = v
|
||||
if (id_filter && Object.keys(id_filter).includes(_treeKeyPath)) {
|
||||
const _find = this.allRoles.find((item) => item.id === Number(k))
|
||||
readCIIdFilterPermissions.push({ name: _find?.name ?? k, rid: k })
|
||||
}
|
||||
})
|
||||
this.readCIIdFilterPermissions = readCIIdFilterPermissions
|
||||
console.log(readCIIdFilterPermissions)
|
||||
},
|
||||
handleCancel() {
|
||||
this.showAllReadCIIdFilterPermissions = false
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,97 +1,106 @@
|
|||
<template>
|
||||
<treeselect
|
||||
:disable-branch-nodes="multiple ? false : true"
|
||||
:multiple="multiple"
|
||||
:options="employeeTreeSelectOption"
|
||||
:placeholder="readOnly ? '' : placeholder || $t('cs.components.selectEmployee')"
|
||||
v-model="treeValue"
|
||||
:max-height="200"
|
||||
:noChildrenText="$t('cs.components.empty')"
|
||||
:noOptionsText="$t('cs.components.empty')"
|
||||
:class="className ? className : 'ops-setting-treeselect'"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:limit="20"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
v-bind="$attrs"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import { formatOption } from '@/utils/util'
|
||||
export default {
|
||||
name: 'EmployeeTreeSelect',
|
||||
components: {
|
||||
Treeselect,
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array, Number, null],
|
||||
default: null,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: 'ops-setting-treeselect',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
idType: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
departmentKey: {
|
||||
type: String,
|
||||
default: 'department_id',
|
||||
},
|
||||
employeeKey: {
|
||||
type: String,
|
||||
default: 'employee_id',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
inject: {
|
||||
provide_allTreeDepAndEmp: {
|
||||
from: 'provide_allTreeDepAndEmp',
|
||||
},
|
||||
readOnly: {
|
||||
from: 'readOnly',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
treeValue: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
allTreeDepAndEmp() {
|
||||
return this.provide_allTreeDepAndEmp()
|
||||
},
|
||||
employeeTreeSelectOption() {
|
||||
return formatOption(this.allTreeDepAndEmp, this.idType, false, this.departmentKey, this.employeeKey)
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<template>
|
||||
<treeselect
|
||||
:disable-branch-nodes="multiple ? false : true"
|
||||
:multiple="multiple"
|
||||
:options="employeeTreeSelectOption"
|
||||
:placeholder="readOnly ? '' : placeholder || $t('cs.components.selectEmployee')"
|
||||
v-model="treeValue"
|
||||
:max-height="200"
|
||||
:noChildrenText="$t('cs.components.empty')"
|
||||
:noOptionsText="$t('cs.components.empty')"
|
||||
:class="className ? className : 'ops-setting-treeselect'"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:limit="limit"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
v-bind="$attrs"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:flat="flat"
|
||||
>
|
||||
</treeselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
import { formatOption } from '@/utils/util'
|
||||
export default {
|
||||
name: 'EmployeeTreeSelect',
|
||||
components: {
|
||||
Treeselect,
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array, Number, null],
|
||||
default: null,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: 'ops-setting-treeselect',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
idType: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
departmentKey: {
|
||||
type: String,
|
||||
default: 'department_id',
|
||||
},
|
||||
employeeKey: {
|
||||
type: String,
|
||||
default: 'employee_id',
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
flat: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
inject: {
|
||||
provide_allTreeDepAndEmp: {
|
||||
from: 'provide_allTreeDepAndEmp',
|
||||
},
|
||||
readOnly: {
|
||||
from: 'readOnly',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
treeValue: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
allTreeDepAndEmp() {
|
||||
return this.provide_allTreeDepAndEmp()
|
||||
},
|
||||
employeeTreeSelectOption() {
|
||||
return formatOption(this.allTreeDepAndEmp, this.idType, false, this.departmentKey, this.employeeKey)
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
Loading…
Reference in New Issue