diff --git a/src/components/Dialog/index.vue b/src/components/Dialog/index.vue index 1996dec03..b74c8284c 100644 --- a/src/components/Dialog/index.vue +++ b/src/components/Dialog/index.vue @@ -12,8 +12,8 @@ - + @@ -25,6 +25,7 @@ import Select2 from '../Select2.vue' import DataTable from '@/components/DataTable/index.vue' import Dialog from '@/components/Dialog/index.vue' import DataForm from '@/components/DataForm/index.vue' +import ValueFormatter from './ValueFormatter.vue' import ValueField from './ValueField.vue' export default { @@ -61,14 +62,24 @@ export default { { label: this.$t('common.Startswith'), value: 'startswith' }, { label: this.$t('common.Endswith'), value: 'endswith' }, { label: this.$t('common.Regex'), value: 'regex' }, - { label: this.$t('common.IPMatch'), value: 'ip_in' } + { label: this.$t('common.BelongTo'), value: 'm2m' }, + { label: this.$t('common.IPMatch'), value: 'ip_in' }, + { label: this.$t('common.GreatEqualThan'), value: 'gte' }, + { label: this.$t('common.LessEqualThan'), value: 'lte' } ] - const attrRelOptions = [ - { label: this.$t('common.RelAnd'), value: 'and' }, - { label: this.$t('common.RelOr'), value: 'or' }, - { label: this.$t('common.RelNot'), value: 'not' } - ] - const attrNameOptions = this.attrs.map(attr => ({ label: attr.label, value: attr.name })) + + const strMatchValues = ['exact', 'not', 'in', 'contains', 'startswith', 'endswith', 'regex'] + const typeMatchMapper = { + str: strMatchValues, + bool: ['exact', 'not'], + m2m: ['m2m'], + ip: strMatchValues + ['ip_in'], + int: strMatchValues + ['gte', 'lte'], + select: ['in'] + } + attrMatchOptions.forEach((option) => { + option.hidden = !typeMatchMapper[this.attrs[0].type || 'str'].includes(option.value) + }) const tableFormatter = (colName) => { return (row, col, cellValue) => { const value = cellValue @@ -77,8 +88,6 @@ export default { return this.attrs.find(attr => attr.name === value)?.label || value case 'match': return attrMatchOptions.find(opt => opt.value === value).label || value - case 'rel': - return attrRelOptions.find(opt => opt.value === value)?.label || value case 'value': return Array.isArray(value) ? value.join(', ') : value default: @@ -98,8 +107,7 @@ export default { columns: [ { prop: 'name', label: this.$t('common.AttrName'), formatter: tableFormatter('name') }, { prop: 'match', label: this.$t('common.Match'), formatter: tableFormatter('match') }, - { prop: 'value', label: this.$t('common.AttrValue'), formatter: tableFormatter('value') }, - { prop: 'rel', label: this.$t('common.Relation'), formatter: tableFormatter('rel') }, + { prop: 'value', label: this.$t('common.AttrValue'), formatter: ValueFormatter, formatterArgs: { attrs: this.attrs }}, { prop: 'action', label: this.$t('common.Action'), formatter: (row, col, cellValue, index) => { return (
@@ -126,46 +134,48 @@ export default { }, formConfig: { // 为了方便更新,避免去取 fields 的索引 - attrNameOptions: attrNameOptions, hasSaveContinue: false, editRowIndex: -1, form: {}, fields: [ { id: 'name', - label: '属性', + label: this.$t('common.AttrName'), type: 'select', - options: attrNameOptions + options: this.attrs.map(attr => ({ label: attr.label, value: attr.name })), + on: { + change: ([val], updateForm) => { + const attr = this.attrs.find(attr => attr.name === val) + if (!attr) return + this.formConfig.fields[2].el.attr = attr + const attrType = attr.type || 'str' + const matchSupports = typeMatchMapper[attrType] + attrMatchOptions.forEach((option) => { + option.hidden = !matchSupports.includes(option.value) + }) + setTimeout(() => updateForm({ match: matchSupports[0] }), 0.1) + } + } }, { id: 'match', - label: '匹配方式', + label: this.$t('common.Match'), type: 'select', options: attrMatchOptions, on: { change: ([value], updateForm) => { this.formConfig.fields[2].el.match = value - if (['in', 'ip_in'].includes(value)) { - updateForm({ value: [] }) - } else { - updateForm({ value: '' }) - } } } }, { id: 'value', - label: '值', + label: this.$t('common.AttrValue'), component: ValueField, el: { - match: 'exact' + match: attrMatchOptions[0].value, + attr: this.attrs[0] } - }, - { - id: 'rel', - label: '关系', - type: 'radio-group', - options: attrRelOptions } ] }, @@ -209,9 +219,8 @@ export default { } }, setAttrNameOptionUsed() { - const options = this.formConfig.attrNameOptions + const options = this.formConfig.fields[0].options const used = this.tableConfig.totalData.map(attr => attr.name) - console.log('Used: ', used) options.forEach(opt => { if (used.includes(opt.value)) { opt.disabled = true @@ -219,11 +228,11 @@ export default { delete opt.disabled } }) - console.log('Options: ', options) }, handleAttrEdit({ row, index }) { return () => { this.formConfig.editRowIndex = index + this.formConfig.fields[2].el.attr = this.attrs.find(attr => attr.name === row.name) this.formConfig.form = Object.assign({ index }, row) this.setAttrNameOptionUsed() this.visible = true @@ -236,6 +245,7 @@ export default { }, handleAttrAdd() { this.formConfig.form = this.getDefaultAttrForm() + this.formConfig.fields[2].el.attr = this.attrs.find(attr => attr.name === this.formConfig.form.name) this.setAttrNameOptionUsed() this.visible = true }, diff --git a/src/i18n/langs/zh.json b/src/i18n/langs/zh.json index fa44d833b..a9fbeb85d 100644 --- a/src/i18n/langs/zh.json +++ b/src/i18n/langs/zh.json @@ -456,6 +456,11 @@ "ReLoginErr": "登录时长已超过 5 分钟,请重新登录" }, "common": { + "GreatEqualThan": "大于等于", + "LessEqualThan": "小于等于", + "BelongTo": "所属", + "Email": "邮箱", + "IsActive": "激活", "All": "所有", "Spec": "指定", "SelectByAttr": "属性筛选", diff --git a/src/views/assets/const.js b/src/views/assets/const.js index 5e83cedbd..88d3e58cc 100644 --- a/src/views/assets/const.js +++ b/src/views/assets/const.js @@ -140,6 +140,23 @@ export const assetFieldsMeta = (vm) => { } export const assetJSONSelectMeta = (vm) => { + const categories = [] + const types = [] + const protocols = [] + vm.$axios.get('/api/v1/assets/categories/').then((res) => { + const _types = [] + const _protocols = [] + for (const category of res) { + categories.push({ value: category.value, label: category.label }) + _types.push(...category.types.map(item => ({ value: item.value, label: item.label }))) + for (const type of category.types) { + _protocols.push(...type.constraints.protocols?.map(item => ({ value: item.name, label: item.name.toUpperCase() }))) + } + } + types.push(..._.uniqBy(_types, 'value')) + protocols.push(..._.uniqBy(_protocols, 'value')) + }) + return { component: JSONManyToManySelect, el: { @@ -160,7 +177,63 @@ export const assetJSONSelectMeta = (vm) => { }, { name: 'address', - label: vm.$t('assets.Address') + label: vm.$t('assets.Address'), + type: 'ip' + }, + { + name: 'nodes', + label: vm.$t('assets.Node'), + type: 'm2m', + el: { + url: '/api/v1/assets/nodes/', + ajax: { + transformOption: (item) => { + return { label: item.full_value, value: item.id } + } + } + } + }, + { + name: 'platform', + label: vm.$t('assets.Platform'), + type: 'm2m', + el: { + multiple: false, + url: '/api/v1/assets/platforms/' + } + }, + { + name: 'category', + label: vm.$t('assets.Category'), + type: 'select', + el: { + options: categories + } + }, + { + name: 'type', + label: vm.$t('assets.Type'), + type: 'select', + el: { + options: types + } + }, + { + name: 'protocols', + label: vm.$t('assets.Protocols'), + type: 'select', + el: { + options: protocols + } + }, + { + name: 'labels', + label: vm.$t('assets.Label'), + type: 'm2m', + el: { + multiple: true, + url: '/api/v1/assets/labels/' + } } ] } diff --git a/src/views/perms/AssetPermission/components/AccountFormatter.vue b/src/views/perms/AssetPermission/components/AccountFormatter.vue index 84cdd180c..77ad5b9b6 100644 --- a/src/views/perms/AssetPermission/components/AccountFormatter.vue +++ b/src/views/perms/AssetPermission/components/AccountFormatter.vue @@ -176,6 +176,7 @@ export default { }, mounted() { this.initDefaultChoice() + this.$emit('input', this.value) }, methods: { initDefaultChoice() { diff --git a/src/views/users/const.js b/src/views/users/const.js index 0a3a529f5..9c0fcb449 100644 --- a/src/views/users/const.js +++ b/src/views/users/const.js @@ -22,6 +22,55 @@ export const userJSONSelectMeta = (vm) => { { name: 'username', label: vm.$t('common.Username') + }, + { + name: 'email', + label: vm.$t('common.Email') + }, + { + name: 'comment', + label: vm.$t('common.Comment') + }, + { + name: 'is_active', + label: vm.$t('common.IsActive'), + type: 'bool' + }, + { + name: 'system_roles', + label: vm.$t('users.SystemRoles'), + type: 'm2m', + el: { + url: '/api/v1/rbac/system-roles/?fields_size=mini', + ajax: { + transformOption: (item) => { + return { label: item.display_name, value: item.id } + } + }, + displayField: 'display_name' + } + }, + { + name: 'org_roles', + label: vm.$t('users.OrgRoles'), + type: 'm2m', + el: { + url: '/api/v1/rbac/org-roles/', + ajax: { + transformOption: (item) => { + return { label: item.display_name, value: item.id } + } + }, + displayField: 'display_name' + } + }, + { + name: 'groups', + label: vm.$t('users.UserGroups'), + type: 'm2m', + el: { + url: '/api/v1/users/groups/?fields_size=mini' + } } ] }