mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-29 21:28:52 +00:00
Merge pull request #3101 from jumpserver/pr@dev@perf_m2m_json_field
feat: ACL 中选择可以根据属性进行选择
This commit is contained in:
@@ -136,6 +136,8 @@ export default {
|
||||
const form = this.$refs['form']
|
||||
const values = form.getFormValue()
|
||||
callback(values, form, button)
|
||||
},
|
||||
getFormValue() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
ref="table"
|
||||
class="el-data-table"
|
||||
v-bind="tableConfig"
|
||||
@sizeChange="handleSizeChange"
|
||||
@update="onUpdate"
|
||||
v-on="iListeners"
|
||||
@sizeChange="handleSizeChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -168,57 +168,4 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-data-table > > > .el-table {
|
||||
.table {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
&.selected-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
& > td {
|
||||
line-height: 1.5;
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
border-right: none;
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
& > div > span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__header > thead > tr > th {
|
||||
padding: 6px 0;
|
||||
background-color: #ffffff;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
border-right: none;
|
||||
.cell {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
&:hover {
|
||||
border-right: 2px solid #EBEEF5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-data-table >>> .el-table .el-table__header > thead > tr .is-sortable {
|
||||
padding: 5px 0;
|
||||
.cell {
|
||||
padding-top: 3px!important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<slot />
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<slot name="footer">
|
||||
<el-button v-if="showCancel" @click="onCancel">{{ cancelTitle }}</el-button>
|
||||
<el-button v-if="showConfirm" :loading="loadingStatus" type="primary" @click="onConfirm">
|
||||
<el-button v-if="showCancel && showButtons" @click="onCancel">{{ cancelTitle }}</el-button>
|
||||
<el-button v-if="showConfirm && showButtons" :loading="loadingStatus" type="primary" @click="onConfirm">
|
||||
{{ confirmTitle }}
|
||||
</el-button>
|
||||
</slot>
|
||||
@@ -29,16 +29,6 @@ export default {
|
||||
type: String,
|
||||
default: 'Title'
|
||||
},
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
cancelTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('common.Cancel')
|
||||
}
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: '3vh'
|
||||
@@ -51,16 +41,30 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadingStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
confirmTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('common.Confirm')
|
||||
}
|
||||
},
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
cancelTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('common.Cancel')
|
||||
}
|
||||
},
|
||||
showButtons: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadingStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: '1200px'
|
||||
@@ -90,6 +94,10 @@ export default {
|
||||
border-radius: 0.3em;
|
||||
max-width: 1500px;
|
||||
|
||||
.el-icon-circle-check {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__header {
|
||||
box-sizing: border-box;
|
||||
padding: 15px 22px;
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:destroy-on-close="true"
|
||||
:show-buttons="false"
|
||||
:title="$tc('common.SelectAttrs')"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<div v-if="!loading">
|
||||
<DataForm
|
||||
:form="form"
|
||||
class="attr-form"
|
||||
v-bind="formConfig"
|
||||
@submit="onAttrDialogConfirm"
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataForm from '@/components/DataForm/index.vue'
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import ValueField from '@/components/FormFields/JSONManyToManySelect/ValueField.vue'
|
||||
import { attrMatchOptions, typeMatchMapper } from './const'
|
||||
|
||||
export default {
|
||||
name: 'AttrFormDialog',
|
||||
components: { Dialog, DataForm },
|
||||
props: {
|
||||
attrs: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
attrsAdded: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
formConfig: {
|
||||
// 为了方便更新,避免去取 fields 的索引
|
||||
hasSaveContinue: false,
|
||||
fields: [
|
||||
{
|
||||
id: 'name',
|
||||
label: this.$t('common.AttrName'),
|
||||
type: 'select',
|
||||
options: this.attrs.map(attr => {
|
||||
const disabled = this.attrsAdded.includes(attr.name) && this.form.name !== attr.name
|
||||
return { label: attr.label, value: attr.name, disabled: disabled }
|
||||
}),
|
||||
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)
|
||||
})
|
||||
let defaultValue = ''
|
||||
if (['m2m', 'select'].includes(attrType)) {
|
||||
defaultValue = []
|
||||
} else if (['bool'].includes(attrType)) {
|
||||
defaultValue = false
|
||||
}
|
||||
setTimeout(() => {
|
||||
updateForm({ match: matchSupports[0], value: defaultValue })
|
||||
}, 0.1)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'match',
|
||||
label: this.$t('common.Match'),
|
||||
type: 'select',
|
||||
options: attrMatchOptions,
|
||||
on: {
|
||||
change: ([value], updateForm) => {
|
||||
let defaultValue = ''
|
||||
if (['in', 'ip_in'].includes(value)) {
|
||||
defaultValue = []
|
||||
}
|
||||
updateForm({ value: defaultValue })
|
||||
this.formConfig.fields[2].el.match = value
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
label: this.$t('common.AttrValue'),
|
||||
component: ValueField,
|
||||
el: {
|
||||
match: attrMatchOptions[0].value,
|
||||
attr: this.attrs[0]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.form.index === undefined || this.form.index === -1) {
|
||||
Object.assign(this.form, this.getDefaultAttrForm())
|
||||
}
|
||||
this.formConfig.fields[2].el.attr = this.attrs.find(attr => attr.name === this.form.name)
|
||||
this.formConfig.fields[2].el.match = this.form.match
|
||||
this.$log.debug('Form config: ', this.formConfig)
|
||||
this.loading = false
|
||||
},
|
||||
methods: {
|
||||
getDefaultAttrForm() {
|
||||
const attrKeys = this.attrs.map(attr => attr.name)
|
||||
const diff = attrKeys.filter(attr => !this.attrsAdded.includes(attr))
|
||||
let name = this.attrs[0].name
|
||||
if (diff.length > 0) {
|
||||
name = diff[0]
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
match: 'exact',
|
||||
value: '',
|
||||
rel: 'and'
|
||||
}
|
||||
},
|
||||
onAttrDialogConfirm(form) {
|
||||
this.$emit('confirm', form)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.attr-form {
|
||||
>>> .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:destroy-on-close="true"
|
||||
:show-buttons="false"
|
||||
:title="$tc('common.MatchResult')"
|
||||
:v-bind="$attrs"
|
||||
:v-on="$listeners"
|
||||
:visible.sync="iVisible"
|
||||
>
|
||||
<ListTable v-bind="attrMatchTableConfig" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import ListTable from '@/components/ListTable/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'AttrMatchResultDialog',
|
||||
components: { ListTable, Dialog },
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
attrs: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attrMatchTableConfig: {
|
||||
headerActions: {
|
||||
hasCreate: false,
|
||||
hasImport: false,
|
||||
hasExport: false,
|
||||
hasMoreActions: false
|
||||
},
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
columns: this.attrs.filter(item => item.inTable).map(item => {
|
||||
return {
|
||||
prop: item.name,
|
||||
label: item.label,
|
||||
formatter: item.formatter
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iVisible: {
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
},
|
||||
get() {
|
||||
return this.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div v-if="!loading">
|
||||
<TagInput v-if="type === 'array'" :value="value" @input="handleInput" />
|
||||
<Select2 v-else-if="type === 'select'" :value="value" v-bind="attr.el" @change="handleInput" @input="handleInput" />
|
||||
<Switcher v-else-if="type === 'bool'" :value="value" @change="handleInput" @input="handleInput" />
|
||||
<el-input v-else :value="value" @input="handleInput" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TagInput from '@/components/FormFields/TagInput.vue'
|
||||
import Select2 from '@/components/FormFields/Select2.vue'
|
||||
import Switcher from '@/components/FormFields/Switcher.vue'
|
||||
|
||||
export default {
|
||||
name: 'ValueField',
|
||||
components: { Switcher, TagInput, Select2 },
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, Boolean, Array, Object],
|
||||
default: () => ''
|
||||
},
|
||||
match: {
|
||||
type: String,
|
||||
default: 'exact'
|
||||
},
|
||||
attr: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
match() {
|
||||
this.setTypeAndValue()
|
||||
},
|
||||
attr: {
|
||||
handler() {
|
||||
this.setTypeAndValue()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setTypeAndValue()
|
||||
},
|
||||
methods: {
|
||||
handleInput(value) {
|
||||
this.$emit('input', value)
|
||||
},
|
||||
setTypeAndValue() {
|
||||
this.loading = false
|
||||
this.type = this.getType()
|
||||
this.$log.debug('ValueField: Type: ', this.type, 'Value: ', this.value)
|
||||
if (['select', 'array'].includes(this.type) && typeof this.value === 'string') {
|
||||
const value = this.value ? this.value.split(',') : []
|
||||
this.handleInput(value)
|
||||
} else if (this.type === 'bool') {
|
||||
const value = !!this.value
|
||||
this.handleInput(value)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
getType() {
|
||||
const attrType = this.attr.type || 'str'
|
||||
this.$log.debug('Value field attr type: ', attrType, this.attr, this.match)
|
||||
if (attrType === 'm2m') {
|
||||
return 'select'
|
||||
} else if (attrType === 'bool') {
|
||||
return 'bool'
|
||||
} else if (attrType === 'select') {
|
||||
return 'select'
|
||||
}
|
||||
if (['in', 'ip_in'].includes(this.match)) {
|
||||
return 'array'
|
||||
} else {
|
||||
return 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<span v-if="attr.type === 'bool'">
|
||||
<i v-if="value" class="fa fa-check text-primary" />
|
||||
<i v-else class="fa fa-times text-danger" />
|
||||
</span>
|
||||
<span v-else :title="value">{{ value }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFormatter from '@/components/TableFormatters/base.vue'
|
||||
import { setUrlParam } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'ValueFormatter',
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
attrs: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
return {
|
||||
formatterArgs: formatterArgs,
|
||||
loading: true,
|
||||
attr: {},
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {
|
||||
cellValue: {
|
||||
handler(val) {
|
||||
this.getValue()
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getValue()
|
||||
},
|
||||
methods: {
|
||||
async getValue() {
|
||||
this.attr = this.formatterArgs.attrs.find(attr => attr.name === this.row.name)
|
||||
this.match = this.row.match
|
||||
this.$log.debug('ValueFormatter: ', this.attr, this.row.name)
|
||||
if (this.attr.type === 'm2m') {
|
||||
const url = setUrlParam(this.attr.el.url, 'ids', this.cellValue.join(','))
|
||||
const data = await this.$axios.get(url)
|
||||
if (data.length > 0) {
|
||||
const displayField = this.attr.el.displayField || 'name'
|
||||
this.value = data.map(item => item[displayField]).join(', ')
|
||||
}
|
||||
} else if (this.attr.type === 'select') {
|
||||
this.value = this.attr.el.options
|
||||
.filter(item => this.cellValue.includes(item.value))
|
||||
.map(item => item.label).join(',')
|
||||
} else if (['in', 'ip_in'].includes(this.match)) {
|
||||
this.value = this.cellValue.join(', ')
|
||||
} else {
|
||||
this.value = this.cellValue
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
25
src/components/FormFields/JSONManyToManySelect/const.js
Normal file
25
src/components/FormFields/JSONManyToManySelect/const.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export const strMatchValues = ['exact', 'not', 'in', 'contains', 'startswith', 'endswith', 'regex']
|
||||
export const typeMatchMapper = {
|
||||
str: strMatchValues,
|
||||
bool: ['exact', 'not'],
|
||||
m2m: ['m2m'],
|
||||
ip: strMatchValues + ['ip_in'],
|
||||
int: strMatchValues + ['gte', 'lte'],
|
||||
select: ['in']
|
||||
}
|
||||
|
||||
export const attrMatchOptions = [
|
||||
{ label: i18n.t('common.Equal'), value: 'exact' },
|
||||
{ label: i18n.t('common.NotEqual'), value: 'not' },
|
||||
{ label: i18n.t('common.MatchIn'), value: 'in' },
|
||||
{ label: i18n.t('common.Contains'), value: 'contains' },
|
||||
{ label: i18n.t('common.Startswith'), value: 'startswith' },
|
||||
{ label: i18n.t('common.Endswith'), value: 'endswith' },
|
||||
{ label: i18n.t('common.Regex'), value: 'regex' },
|
||||
{ label: i18n.t('common.BelongTo'), value: 'm2m' },
|
||||
{ label: i18n.t('common.IPMatch'), value: 'ip_in' },
|
||||
{ label: i18n.t('common.GreatEqualThan'), value: 'gte' },
|
||||
{ label: i18n.t('common.LessEqualThan'), value: 'lte' }
|
||||
]
|
||||
240
src/components/FormFields/JSONManyToManySelect/index.vue
Normal file
240
src/components/FormFields/JSONManyToManySelect/index.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-radio-group v-model="iValue.type" @input="handleTypeChange">
|
||||
<el-radio v-for="tp of types" :key="tp.name" :label="tp.name">
|
||||
{{ tp.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<Select2 v-if="iValue.type === 'ids'" v-model="ids" v-bind="select2" @change="onChangeEmit" />
|
||||
<div v-if="iValue.type === 'attrs'">
|
||||
<DataTable :config="tableConfig" class="attr-list" />
|
||||
<div class="actions">
|
||||
<el-button size="mini" type="primary" @click="handleAttrAdd">
|
||||
{{ $t('common.Add') }}
|
||||
</el-button>
|
||||
<span style="padding-left: 10px; font-size: 13px">
|
||||
<span class="help-tips; ">{{ $t('common.MatchedCount') }}:</span>
|
||||
<a class="text-link" style="padding: 0 5px;" @click="showAttrMatchTable">{{ attrMatchCount }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AttrFormDialog
|
||||
v-if="attrFormVisible"
|
||||
:attrs="attrs"
|
||||
:attrs-added="attrsAdded"
|
||||
:form="attrForm"
|
||||
:visible.sync="attrFormVisible"
|
||||
@confirm="handleAttrDialogConfirm"
|
||||
/>
|
||||
<AttrMatchResultDialog
|
||||
v-if="attrMatchTableVisible"
|
||||
:attrs="attrs"
|
||||
:url="attrMatchTableUrl"
|
||||
:visible.sync="attrMatchTableVisible"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Select2 from '../Select2.vue'
|
||||
import DataTable from '@/components/DataTable/index.vue'
|
||||
import ValueFormatter from './ValueFormatter.vue'
|
||||
import AttrFormDialog from './AttrFormDialog.vue'
|
||||
import AttrMatchResultDialog from './AttrMatchResultDialog.vue'
|
||||
import { setUrlParam } from '@/utils/common'
|
||||
import { attrMatchOptions } from './const'
|
||||
import { toM2MJsonParams } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
name: 'JSONManyToManySelect',
|
||||
components: { AttrFormDialog, DataTable, Select2, AttrMatchResultDialog },
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
type: 'all'
|
||||
}
|
||||
}
|
||||
},
|
||||
select2: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
attrs: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
resource: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
attrTableColumns: {
|
||||
type: Array,
|
||||
default: () => (['name'])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const tableFormatter = (colName) => {
|
||||
return (row, col, cellValue) => {
|
||||
const value = cellValue
|
||||
switch (colName) {
|
||||
case 'name':
|
||||
return this.attrs.find(attr => attr.name === value)?.label || value
|
||||
case 'match':
|
||||
return attrMatchOptions.find(opt => opt.value === value).label || value
|
||||
case 'value':
|
||||
return Array.isArray(value) ? value.join(', ') : value
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
iValue: Object.assign({ type: 'all' }, this.value),
|
||||
attrFormVisible: false,
|
||||
attrForm: {},
|
||||
attrMatchCount: 0,
|
||||
attrMatchTableVisible: false,
|
||||
attrMatchTableUrl: '',
|
||||
ids: this.value.ids || [],
|
||||
editIndex: -1,
|
||||
types: [
|
||||
{ name: 'all', label: this.$t('common.All') + this.resource },
|
||||
{ name: 'ids', label: this.$t('common.Spec') + this.resource },
|
||||
{ name: 'attrs', label: this.$t('common.SelectByAttr') }
|
||||
],
|
||||
tableConfig: {
|
||||
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: ValueFormatter, formatterArgs: { attrs: this.attrs }},
|
||||
{ prop: 'action', label: this.$t('common.Action'), align: 'center', width: '120px', formatter: (row, col, cellValue, index) => {
|
||||
return (
|
||||
<div className='input-button'>
|
||||
<el-button
|
||||
icon='el-icon-edit'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='primary'
|
||||
onClick={this.handleAttrEdit({ row, col, cellValue, index })}
|
||||
/>
|
||||
<el-button
|
||||
icon='el-icon-minus'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='danger'
|
||||
onClick={this.handleAttrDelete({ row, col, cellValue, index })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} }
|
||||
],
|
||||
totalData: this.value.attrs || [],
|
||||
hasPagination: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
attrsAdded() {
|
||||
return this.tableConfig.totalData.map(item => item.name)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
attrFormVisible(val) {
|
||||
if (!val) {
|
||||
this.getAttrsCount()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.value.type === 'attrs') {
|
||||
this.getAttrsCount()
|
||||
}
|
||||
this.$emit('input', this.iValue)
|
||||
},
|
||||
methods: {
|
||||
showAttrMatchTable() {
|
||||
const [key, value] = this.getAttrFilterKey()
|
||||
this.attrMatchTableUrl = setUrlParam(this.select2.url, key, value)
|
||||
this.attrMatchTableVisible = true
|
||||
},
|
||||
getAttrFilterKey() {
|
||||
if (this.tableConfig.totalData.length === 0) return ''
|
||||
let attrFilter = { type: 'attrs', attrs: this.tableConfig.totalData }
|
||||
attrFilter = toM2MJsonParams(attrFilter)
|
||||
return attrFilter
|
||||
},
|
||||
getAttrsCount() {
|
||||
const attrFilter = this.getAttrFilterKey()
|
||||
if (!attrFilter) {
|
||||
this.attrMatchCount = 0
|
||||
return
|
||||
}
|
||||
const [key, value] = attrFilter
|
||||
let url = setUrlParam(this.select2.url, key, value)
|
||||
url = setUrlParam(url, 'limit', 1)
|
||||
return this.$axios.get(url).then(res => {
|
||||
this.attrMatchCount = res.count
|
||||
})
|
||||
},
|
||||
handleAttrEdit({ row, index }) {
|
||||
return () => {
|
||||
this.attrForm = Object.assign({ index }, row)
|
||||
this.editIndex = index
|
||||
this.attrFormVisible = true
|
||||
}
|
||||
},
|
||||
handleAttrDelete({ index }) {
|
||||
return () => {
|
||||
this.tableConfig.totalData.splice(index, 1)
|
||||
this.getAttrsCount()
|
||||
}
|
||||
},
|
||||
handleAttrAdd() {
|
||||
this.attrForm = {}
|
||||
this.editIndex = -1
|
||||
this.attrFormVisible = true
|
||||
},
|
||||
onChangeEmit() {
|
||||
const tp = this.iValue.type
|
||||
this.handleTypeChange(tp)
|
||||
},
|
||||
handleTypeChange(val) {
|
||||
switch (val) {
|
||||
case 'ids':
|
||||
this.$emit('input', { type: 'ids', ids: this.ids })
|
||||
break
|
||||
case 'attrs':
|
||||
this.$emit('input', { type: 'attrs', attrs: this.tableConfig.totalData })
|
||||
break
|
||||
default:
|
||||
this.$emit('input', { type: 'all' })
|
||||
break
|
||||
}
|
||||
},
|
||||
handleAttrDialogConfirm(form) {
|
||||
if (this.editIndex > -1) {
|
||||
this.tableConfig.totalData.splice(this.editIndex, 1)
|
||||
}
|
||||
const allAttrs = this.tableConfig.totalData
|
||||
// 因为可能 attr 的 name 会重复,所以需要先删除再添加
|
||||
const setIndex = allAttrs.findIndex(attr => attr.name === form.name)
|
||||
if (setIndex === -1) {
|
||||
allAttrs.push(Object.assign({}, form))
|
||||
} else {
|
||||
allAttrs.splice(setIndex, 1, Object.assign({}, form))
|
||||
}
|
||||
this.attrFormVisible = false
|
||||
this.onChangeEmit()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.attr-list {
|
||||
width: 99%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<el-switch
|
||||
v-model="iValue"
|
||||
inactive-color="#dcdfe6"
|
||||
:class="type"
|
||||
inactive-color="#dcdfe6"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
@@ -17,7 +17,7 @@ export default {
|
||||
default: 'primary'
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
}
|
||||
},
|
||||
@@ -31,9 +31,14 @@ export default {
|
||||
this.$emit('input', newValue)
|
||||
},
|
||||
get: function() {
|
||||
return this.value
|
||||
return !!this.value
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.$log.debug('Switcher Value changed: ', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,7 @@ import UploadSecret from './UploadSecret'
|
||||
import WeekCronSelect from './WeekCronSelect'
|
||||
import NestedObjectSelect2 from './NestedObjectSelect2'
|
||||
import DatetimeRangePicker from './DatetimeRangePicker'
|
||||
import JSONManyToManySelect from './JSONManyToManySelect/index.vue'
|
||||
|
||||
export default {
|
||||
Text,
|
||||
@@ -33,7 +34,8 @@ export default {
|
||||
UploadSecret,
|
||||
WeekCronSelect,
|
||||
NestedObjectSelect2,
|
||||
DatetimeRangePicker
|
||||
DatetimeRangePicker,
|
||||
JSONManyToManySelect
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -53,5 +55,6 @@ export {
|
||||
UploadSecret,
|
||||
WeekCronSelect,
|
||||
NestedObjectSelect2,
|
||||
DatetimeRangePicker
|
||||
DatetimeRangePicker,
|
||||
JSONManyToManySelect
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ export default {
|
||||
hasTree: true,
|
||||
columnsExclude: ['spec_info'],
|
||||
columnShow: {
|
||||
min: ['name', 'address', 'accounts']
|
||||
min: ['name', 'address', 'accounts'],
|
||||
default: ['name', 'address', 'accounts', 'actions']
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
|
||||
53
src/components/ManyJsonTabs/AssetJsonTab.vue
Normal file
53
src/components/ManyJsonTabs/AssetJsonTab.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<el-row :gutter="24">
|
||||
<el-col :md="20" :sm="22">
|
||||
<ListTable v-bind="config" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/ListTable/index.vue'
|
||||
import { toM2MJsonParams } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
name: 'AssetJsonTab',
|
||||
components: {
|
||||
ListTable
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const [key, value] = toM2MJsonParams(this.object.assets)
|
||||
return {
|
||||
config: {
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasImport: false,
|
||||
hasExport: false
|
||||
},
|
||||
tableConfig: {
|
||||
url: `/api/v1/assets/assets/?${key}=${value}`,
|
||||
columns: ['name', 'address'],
|
||||
columnsShow: {
|
||||
min: ['id']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iUrl() {
|
||||
return `/api/v1/users/users/`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
53
src/components/ManyJsonTabs/UserJsonTab.vue
Normal file
53
src/components/ManyJsonTabs/UserJsonTab.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<el-row :gutter="24">
|
||||
<el-col :md="20" :sm="22">
|
||||
<ListTable v-bind="config" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/ListTable/index.vue'
|
||||
import { toM2MJsonParams } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
name: 'User',
|
||||
components: {
|
||||
ListTable
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const [key, value] = toM2MJsonParams(this.object.users)
|
||||
return {
|
||||
config: {
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasImport: false,
|
||||
hasExport: false
|
||||
},
|
||||
tableConfig: {
|
||||
url: `/api/v1/users/users/?${key}=${value}`,
|
||||
columns: ['name', 'username'],
|
||||
columnsShow: {
|
||||
min: ['id']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iUrl() {
|
||||
return `/api/v1/users/users/`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -53,7 +53,6 @@ export default {
|
||||
iGetTag() {
|
||||
let tag = this.formatterArgs.getTag({ row: this.row, cellValue: this.cellValue })
|
||||
if (tag) return tag
|
||||
console.log('Tag: ', tag)
|
||||
tag = {
|
||||
size: this.formatterArgs.getTagSize({ row: this.row, cellValue: this.cellValue }),
|
||||
type: this.formatterArgs.getTagType({ row: this.row, cellValue: this.cellValue }),
|
||||
|
||||
@@ -38,7 +38,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleNodeClick(data) {
|
||||
console.log(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"accounts": {
|
||||
"GenerateSuccessMsg": "Accounts generated successfully",
|
||||
"GenerateAccounts": "Regenerate accounts",
|
||||
"Accounts": "Accounts",
|
||||
"SelectAccount": "Select account",
|
||||
"UpdateSecret": "Update secret",
|
||||
"AccountPolicy": "Account policy",
|
||||
"BulkCreateStrategy": "When creating accounts that do not meet the requirements, such as key type non-compliance and unique key constraints, the above policies can be selected.",
|
||||
@@ -436,7 +438,11 @@
|
||||
"AssetTree": "Asset tree",
|
||||
"SSHPort": "SSH Port",
|
||||
"PrimaryProtocol": "The primary protocol, the most basic and commonly used protocol for assets, can only and must be set up with one.",
|
||||
"Primary": "Primary"
|
||||
"Primary": "Primary",
|
||||
"CreateCustom": "Create Custom",
|
||||
"CustomType": "Custom Type",
|
||||
"CustomHelpMessage": "The assets of custom types require applet support. Please ensure that the corresponding applet is installed.",
|
||||
"CustomFields": "Custom Fields"
|
||||
},
|
||||
"audits": {
|
||||
"ChangeField": "Change field",
|
||||
@@ -461,6 +467,7 @@
|
||||
"ReLoginErr": "Login time has exceeded 5 minutes, please login again"
|
||||
},
|
||||
"common": {
|
||||
"BatchProcessing": "Select {Number} items",
|
||||
"Generate": "Generate",
|
||||
"BatchProcessing": "Batch processing(select {Number} items)",
|
||||
"ServerError": "Server Error",
|
||||
@@ -811,7 +818,25 @@
|
||||
"Error": "Error",
|
||||
"Created": "Created",
|
||||
"Skipped": "Skipped",
|
||||
"Updated": "Updated"
|
||||
"Updated": "Updated",
|
||||
"NotEqual": "Not Equal",
|
||||
"Startswith": "Starts With",
|
||||
"AttrName": "Attribute Name",
|
||||
"SelectByAttr": "Select By Attribute",
|
||||
"IPMatch": "IP Match",
|
||||
"Regex": "Regex",
|
||||
"AttrValue": "Attribute Value",
|
||||
"Contains": "Contains",
|
||||
"Match": "Match",
|
||||
"Spec": "Specific",
|
||||
"All": "All",
|
||||
"Endswith": "Ends With",
|
||||
"RelAnd": "And",
|
||||
"Equal": "Equal",
|
||||
"MatchIn": "In ...",
|
||||
"RelNot": "Not",
|
||||
"RelOr": "Or",
|
||||
"Relation": "Relation"
|
||||
},
|
||||
"dashboard": {
|
||||
"ActiveAsset": "Asset active",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"": "",
|
||||
"accounts": {
|
||||
"Accounts": "アカウント",
|
||||
"SelectAccount": "アカウントを選択",
|
||||
"GenerateSuccessMsg": "アカウントの生成に成功しました",
|
||||
"GenerateAccounts": "アカウントを再生成する",
|
||||
"UpdateSecret": "機密の更新",
|
||||
@@ -436,7 +438,11 @@
|
||||
"Category": "カテゴリー",
|
||||
"SSHPort": "SSH ポート",
|
||||
"PrimaryProtocol": "主要協議は、資産にとって最も基本的で最も一般的に使用されるプロトコルであり、1つのみ設定でき、必ず設定する必要があります",
|
||||
"Primary": "主要な"
|
||||
"Primary": "主要な",
|
||||
"CreateCustom": "カスタムアセットを作成する",
|
||||
"CustomType": "カスタムタイプ",
|
||||
"CustomHelpMessage": "カスタムタイプのアセットにはアプレットのサポートが必要です。対応するアプレットがインストールされていることを確認してください。",
|
||||
"CustomFields": "カスタム属性"
|
||||
},
|
||||
"audits": {
|
||||
"ChangeField": "フィールドを変更します",
|
||||
@@ -461,6 +467,7 @@
|
||||
"ReLoginErr": "ログイン時間が 5 分を超えました。もう一度ログインしてください"
|
||||
},
|
||||
"common": {
|
||||
"BatchProcessing": "選択 {Number} 項目",
|
||||
"Generate": "生成",
|
||||
"BatchProcessing": "一括処理(選択 {Number} 項目)",
|
||||
"ServerError": "サーバーエラー",
|
||||
@@ -810,7 +817,25 @@
|
||||
"Product": "产品",
|
||||
"Created": "已创建",
|
||||
"Skipped": "已跳过",
|
||||
"Updated": "已更新"
|
||||
"Updated": "已更新",
|
||||
"NotEqual": "等しくない",
|
||||
"Startswith": "で始まる",
|
||||
"AttrName": "属性名",
|
||||
"SelectByAttr": "属性で選択",
|
||||
"IPMatch": "IPアドレスが一致する",
|
||||
"Regex": "正規表現",
|
||||
"AttrValue": "属性値",
|
||||
"Contains": "含む",
|
||||
"Match": "一致する",
|
||||
"Spec": "指定する",
|
||||
"All": "すべて",
|
||||
"Endswith": "で終わる",
|
||||
"RelAnd": "かつ",
|
||||
"Equal": "等しい",
|
||||
"MatchIn": "以下のいずれかに一致する",
|
||||
"RelNot": "でない",
|
||||
"RelOr": "または",
|
||||
"Relation": "関係"
|
||||
},
|
||||
"dashboard": {
|
||||
"TotalJobLog": "ジョブ実行総数",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"": "",
|
||||
"accounts": {
|
||||
"Accounts": "账号",
|
||||
"SelectAccount": "选择账号",
|
||||
"GenerateSuccessMsg": "账号生成成功",
|
||||
"GenerateAccounts": "重新生成账号",
|
||||
"UpdateSecret": "更新密文",
|
||||
@@ -459,6 +461,33 @@
|
||||
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
|
||||
},
|
||||
"common": {
|
||||
"MatchedCount": "匹配结果",
|
||||
"SelectAttrs": "选择属性",
|
||||
"MatchResult": "匹配结果",
|
||||
"GreatEqualThan": "大于等于",
|
||||
"LessEqualThan": "小于等于",
|
||||
"BelongTo": "所属",
|
||||
"Email": "邮箱",
|
||||
"IsActive": "激活",
|
||||
"All": "所有",
|
||||
"Spec": "指定",
|
||||
"SelectByAttr": "属性筛选",
|
||||
"AttrName": "属性名",
|
||||
"AttrValue": "属性值",
|
||||
"Match": "匹配",
|
||||
"Relation": "关系",
|
||||
"Equal": "等于",
|
||||
"NotEqual": "不等于",
|
||||
"MatchIn": "在...中",
|
||||
"Contains": "包含",
|
||||
"Startswith": "以...开头",
|
||||
"Endswith": "以...结尾",
|
||||
"Regex": "正则表达式",
|
||||
"IPMatch": "IP 匹配",
|
||||
"RelAnd": "与",
|
||||
"RelOr": "或",
|
||||
"RelNot": "非",
|
||||
"BatchProcessing": "选中 {Number} 项",
|
||||
"Generate": "生成",
|
||||
"BatchProcessing": "批量处理(选中 {Number} 项)",
|
||||
"Created": "已创建",
|
||||
|
||||
@@ -40,7 +40,7 @@ export default [
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'host-acls',
|
||||
path: 'login-asset-acls',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: {
|
||||
|
||||
@@ -218,3 +218,66 @@ input[type=file] {
|
||||
width: 100vw!important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-data-table .el-table {
|
||||
.table {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
&.selected-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
& > td {
|
||||
line-height: 1.5;
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
border-right: none;
|
||||
&:last-child {
|
||||
border-right: solid 1px #EBEEF5 !important;
|
||||
}
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
& > div > span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__header > thead > tr > th {
|
||||
padding: 6px 0;
|
||||
background-color: #ffffff;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
border-right: none;
|
||||
|
||||
&:last-child {
|
||||
border-right: solid 1px #EBEEF5 !important;
|
||||
}
|
||||
|
||||
.cell {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
&:hover {
|
||||
border-right: 2px solid #EBEEF5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-data-table >>> .el-table .el-table__header > thead > tr .is-sortable {
|
||||
padding: 5px 0;
|
||||
|
||||
.cell {
|
||||
padding-top: 3px!important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,3 +144,7 @@ export function getConstRouteName() {
|
||||
addRoutes(names, constRoutes)
|
||||
return names
|
||||
}
|
||||
|
||||
export function toM2MJsonParams(attrFilter) {
|
||||
return ['attr_rules', encodeURIComponent(btoa(JSON.stringify(attrFilter)))]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<script>
|
||||
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import { cleanFormValueForHandleUserAssetAccount } from '../common'
|
||||
import { userJSONSelectMeta } from '@/views/users/const'
|
||||
import { assetJSONSelectMeta } from '@/views/assets/const'
|
||||
import AccountFormatter from '@/views/perms/AssetPermission/components/AccountFormatter.vue'
|
||||
|
||||
export default {
|
||||
name: 'AclCreateUpdate',
|
||||
@@ -14,28 +16,30 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initial: {},
|
||||
initial: {
|
||||
accounts: ['@ALL']
|
||||
},
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'priority']],
|
||||
[this.$t('acl.users'), ['users']],
|
||||
[this.$t('acl.host'), ['assets']],
|
||||
[this.$t('acl.account'), ['accounts']],
|
||||
[this.$t('acl.action'), ['action', 'reviewers']],
|
||||
[this.$t('common.Other'), ['is_active', 'comment']]
|
||||
[this.$t('common.Basic'), ['name']],
|
||||
[this.$t('users.Users'), ['users']],
|
||||
[this.$t('assets.Asset'), ['assets']],
|
||||
[this.$t('accounts.Accounts'), ['accounts']],
|
||||
[this.$t('common.Action'), ['action', 'reviewers']],
|
||||
[this.$t('common.Other'), ['priority', 'is_active', 'comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
priority: {
|
||||
rules: [rules.Required]
|
||||
},
|
||||
assets: {
|
||||
fields: ['name_group', 'address_group']
|
||||
},
|
||||
users: {
|
||||
fields: ['username_group'],
|
||||
fieldsMeta: {}
|
||||
},
|
||||
assets: assetJSONSelectMeta(this),
|
||||
users: userJSONSelectMeta(this),
|
||||
accounts: {
|
||||
fields: ['username_group']
|
||||
component: AccountFormatter,
|
||||
el: {
|
||||
showAddTemplate: false,
|
||||
showVirtualAccount: false,
|
||||
value: ['@ALL']
|
||||
}
|
||||
},
|
||||
reviewers: {
|
||||
hidden: (item) => item.action !== 'review',
|
||||
@@ -51,8 +55,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
url: '/api/v1/acls/login-asset-acls/',
|
||||
cleanFormValue: cleanFormValueForHandleUserAssetAccount
|
||||
url: '/api/v1/acls/login-asset-acls/'
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="14" :sm="24">
|
||||
<AutoDetailCard :url="url" :fields="detailFields" :object="object" />
|
||||
<AutoDetailCard :fields="detailFields" :object="object" :url="url" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -23,21 +23,9 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: `/api/v1/acls/login-asset-acls/${this.object.id}`,
|
||||
url: `/api/v1/acls/login-asset-acls/${this.object.id}/`,
|
||||
detailFields: [
|
||||
'name',
|
||||
{
|
||||
key: this.$t('acl.UserUsername'),
|
||||
value: this.object.users.username_group.toString()
|
||||
},
|
||||
{
|
||||
key: this.$t('acl.AssetName'),
|
||||
value: this.object.assets.name_group.toString()
|
||||
},
|
||||
{
|
||||
key: this.$t('acl.AssetAddress'),
|
||||
value: this.object.accounts.username_group.toString()
|
||||
},
|
||||
{
|
||||
key: this.$t('acl.action'),
|
||||
value: this.object.action.label
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<GenericDetailPage :object.sync="TaskDetail" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
|
||||
<GenericDetailPage :active-menu.sync="config.activeMenu" :object.sync="TaskDetail" v-bind="config" v-on="$listeners">
|
||||
<keep-alive>
|
||||
<component :is="config.activeMenu" :object="TaskDetail" />
|
||||
</keep-alive>
|
||||
@@ -9,10 +9,15 @@
|
||||
<script>
|
||||
import { GenericDetailPage } from '@/layout/components'
|
||||
import Detail from './Detail.vue'
|
||||
import UserJsonTab from '@/components/ManyJsonTabs/UserJsonTab.vue'
|
||||
import AssetJsonTab from '@/components/ManyJsonTabs/AssetJsonTab.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericDetailPage,
|
||||
Detail
|
||||
Detail,
|
||||
UserJsonTab,
|
||||
AssetJsonTab
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -23,6 +28,14 @@ export default {
|
||||
{
|
||||
title: this.$t('acl.RuleDetail'),
|
||||
name: 'Detail'
|
||||
},
|
||||
{
|
||||
title: this.$t('users.Users'),
|
||||
name: 'UserJsonTab'
|
||||
},
|
||||
{
|
||||
title: this.$t('assets.Assets'),
|
||||
name: 'AssetJsonTab'
|
||||
}
|
||||
],
|
||||
hasRightSide: true,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<GenericCreateUpdatePage
|
||||
:fields="fields"
|
||||
:initial="initial"
|
||||
:fields-meta="fieldsMeta"
|
||||
:initial="initial"
|
||||
:url="url"
|
||||
v-bind="$data"
|
||||
/>
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import AccountFormatter from '@/views/perms/AssetPermission/components/AccountFormatter.vue'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import {
|
||||
cleanFormValueForHandleUserAssetAccount
|
||||
} from '../../common'
|
||||
import { userJSONSelectMeta } from '@/views/users/const'
|
||||
import { assetJSONSelectMeta } from '@/views/assets/const'
|
||||
|
||||
export default {
|
||||
name: 'CommandFilterAclCreateUpdate',
|
||||
@@ -23,7 +23,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
initial: {
|
||||
is_active: true
|
||||
is_active: true,
|
||||
accounts: ['@ALL']
|
||||
},
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name']],
|
||||
@@ -37,17 +38,16 @@ export default {
|
||||
url: '/api/v1/acls/command-filter-acls/',
|
||||
createSuccessNextRoute: { name: 'CommandFilterAclList' },
|
||||
fieldsMeta: {
|
||||
users: {
|
||||
fields: ['username_group']
|
||||
},
|
||||
assets: {
|
||||
fields: ['name_group', 'address_group']
|
||||
},
|
||||
users: userJSONSelectMeta(this),
|
||||
assets: assetJSONSelectMeta(this),
|
||||
accounts: {
|
||||
fields: ['username_group']
|
||||
},
|
||||
action: {
|
||||
component: AccountFormatter,
|
||||
el: {
|
||||
showAddTemplate: false,
|
||||
showVirtualAccount: false
|
||||
}
|
||||
},
|
||||
action: {},
|
||||
command_groups: {
|
||||
el: {
|
||||
value: [],
|
||||
@@ -75,8 +75,7 @@ export default {
|
||||
is_active: {
|
||||
type: 'checkbox'
|
||||
}
|
||||
},
|
||||
cleanFormValue: cleanFormValueForHandleUserAssetAccount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="14" :sm="24">
|
||||
<AutoDetailCard :url="url" :fields="detailFields" :object="object" />
|
||||
<AutoDetailCard :fields="detailFields" :object="object" :url="url" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -25,22 +25,6 @@ export default {
|
||||
url: `/api/v1/acls/command-filter-acls/${this.object.id}/`,
|
||||
detailFields: [
|
||||
'name',
|
||||
{
|
||||
key: this.$t('acl.UserUsername'),
|
||||
value: this.object.users.username_group.toString()
|
||||
},
|
||||
{
|
||||
key: this.$t('acl.AssetName'),
|
||||
value: this.object.assets.name_group.toString()
|
||||
},
|
||||
{
|
||||
key: this.$t('acl.AssetAddress'),
|
||||
value: this.object.assets.address_group.toString()
|
||||
},
|
||||
{
|
||||
key: this.$t('acl.AccountUsername'),
|
||||
value: this.object.accounts.username_group.toString()
|
||||
},
|
||||
{
|
||||
key: this.$t('acl.CommandGroup'),
|
||||
value: this.object.command_groups.map((item) => item.name).join(', ')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<GenericDetailPage
|
||||
:object.sync="CommandFilterAcl"
|
||||
:active-menu.sync="config.activeMenu"
|
||||
:object.sync="CommandFilterAcl"
|
||||
v-bind="config"
|
||||
v-on="$listeners"
|
||||
>
|
||||
@@ -13,13 +13,17 @@
|
||||
|
||||
<script>
|
||||
import { GenericDetailPage, TabPage } from '@/layout/components'
|
||||
import UserJsonTab from '@/components/ManyJsonTabs/UserJsonTab.vue'
|
||||
import AssetJsonTab from '@/components/ManyJsonTabs/AssetJsonTab.vue'
|
||||
import Detail from './Detail.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericDetailPage,
|
||||
TabPage,
|
||||
Detail
|
||||
Detail,
|
||||
UserJsonTab,
|
||||
AssetJsonTab
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -31,6 +35,14 @@ export default {
|
||||
{
|
||||
title: this.$t('common.BasicInfo'),
|
||||
name: 'Detail'
|
||||
},
|
||||
{
|
||||
title: this.$t('users.Users'),
|
||||
name: 'UserJsonTab'
|
||||
},
|
||||
{
|
||||
title: this.$t('assets.Assets'),
|
||||
name: 'AssetJsonTab'
|
||||
}
|
||||
],
|
||||
actions: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ListTable :table-config="tableConfig" :header-actions="headerActions" />
|
||||
<ListTable :header-actions="headerActions" :table-config="tableConfig" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -22,7 +22,8 @@ export default {
|
||||
columnsShow: {
|
||||
min: ['name', 'actions'],
|
||||
default: [
|
||||
'name', 'command_groups_amount', 'priority', 'is_active', 'comment', 'actions'
|
||||
'name', 'command_groups_amount', 'priority',
|
||||
'is_active', 'comment', 'actions'
|
||||
]
|
||||
},
|
||||
columnsMeta: {
|
||||
@@ -40,7 +41,6 @@ export default {
|
||||
hasImport: false,
|
||||
hasRefresh: true,
|
||||
hasSearch: true,
|
||||
hasMoreActions: false,
|
||||
createRoute: 'CommandFilterAclCreate',
|
||||
canCreate: () => {
|
||||
return this.$hasPerm('acls.add_commandfilteracl') && !this.$store.getters.currentOrgIsRoot
|
||||
|
||||
@@ -1,42 +1 @@
|
||||
export const UserAssetAccountFieldInitial = {
|
||||
users: {
|
||||
username_group: '*'
|
||||
},
|
||||
assets: {
|
||||
name_group: '*',
|
||||
address_group: '*'
|
||||
},
|
||||
accounts: {
|
||||
username_group: '*'
|
||||
}
|
||||
}
|
||||
|
||||
export function afterGetFormValueForHandleUserAssetAccount(formValue) {
|
||||
// users
|
||||
formValue.users.username_group = formValue.users.username_group.toString()
|
||||
// assets
|
||||
formValue.assets.name_group = formValue.assets.name_group.toString()
|
||||
formValue.assets.address_group = formValue.assets.address_group.toString()
|
||||
// accounts
|
||||
formValue.accounts.username_group = formValue.accounts.username_group.toString()
|
||||
return formValue
|
||||
}
|
||||
|
||||
export function cleanFormValueForHandleUserAssetAccount(value) {
|
||||
// users
|
||||
if (!Array.isArray(value.users.username_group)) {
|
||||
value.users.username_group = value.users.username_group ? value.users.username_group.split(',') : []
|
||||
}
|
||||
// assets
|
||||
if (!Array.isArray(value.assets.name_group)) {
|
||||
value.assets.name_group = value.assets.name_group ? value.assets.name_group.split(',') : []
|
||||
}
|
||||
if (!Array.isArray(value.assets.address_group)) {
|
||||
value.assets.address_group = value.assets.address_group ? value.assets.address_group.split(',') : []
|
||||
}
|
||||
// accounts
|
||||
if (!Array.isArray(value.accounts.username_group)) {
|
||||
value.accounts.username_group = value.accounts.username_group ? value.accounts.username_group.split(',') : []
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -3,21 +3,23 @@
|
||||
<el-link v-if="isUpdate(this)" :underline="false" type="default" @click="goToAssetAccountsPage()">
|
||||
{{ $t('assets.InAssetDetail') }}
|
||||
</el-link>
|
||||
<div v-else class="accounts">
|
||||
<el-table :data="accounts" style="width: 100%">
|
||||
<div v-else class="accounts el-data-table">
|
||||
<el-table :data="accounts" class="el-table--fit el-table--border">
|
||||
<el-table-column :label="$tc('assets.Name')" prop="name" />
|
||||
<el-table-column :label="$tc('assets.Username')" prop="username" />
|
||||
<el-table-column :label="$tc('assets.Privileged')" prop="privileged">
|
||||
<template v-slot="scope">
|
||||
<i :class="scope.row['privileged'] ? 'fa-check' : ''" class="fa text-primary" />
|
||||
<i v-if="scope.row['privileged']" class="fa fa-check text-primary" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$tc('common.TemplateAdd')" prop="template">
|
||||
<template v-slot="scope">
|
||||
<i :class="scope.row['template'] ? 'fa-check' : ''" class="fa text-primary" />
|
||||
<i v-if="scope.row['template']" class="fa fa-check text-primary" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$tc('common.Actions')" align="right" class-name="buttons" fixed="right" width="135">
|
||||
<el-table-column :label="$tc('common.Actions')" align="center" class-name="buttons" fixed="right" width="135">
|
||||
<template v-slot="scope">
|
||||
<el-button icon="el-icon-minus" size="mini" type="danger" @click="removeAccount(scope.row)" />
|
||||
<el-button :disabled="scope.row.template" icon="el-icon-edit" size="mini" type="primary" @click="onEditClick(scope.row)" />
|
||||
@@ -74,8 +76,9 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const accounts = this.value || []
|
||||
return {
|
||||
accounts: this.value || [],
|
||||
accounts: accounts,
|
||||
account: {},
|
||||
initial: false,
|
||||
addAccountDialogVisible: false,
|
||||
@@ -152,7 +155,57 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.accounts >>> .buttons .cell {
|
||||
padding-right: 2px;
|
||||
.el-data-table >>> .el-table {
|
||||
.table {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
&.selected-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
& > td {
|
||||
line-height: 1.5;
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
border-right: none;
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
& > div > span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__header > thead > tr > th {
|
||||
padding: 6px 0;
|
||||
background-color: #ffffff;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
border-right: none;
|
||||
.cell {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
&:hover {
|
||||
border-right: 2px solid #EBEEF5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-data-table >>> .el-table .el-table__header > thead > tr .is-sortable {
|
||||
padding: 5px 0;
|
||||
.cell {
|
||||
padding-top: 3px!important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@ import i18n from '@/i18n/i18n'
|
||||
import ProtocolSelector from '@/components/FormFields/ProtocolSelector'
|
||||
import AssetAccounts from '@/views/assets/Asset/AssetCreateUpdate/components/AssetAccounts'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import { Select2 } from '@/components/FormFields'
|
||||
import { JSONManyToManySelect, Select2 } from '@/components/FormFields'
|
||||
import { message } from '@/utils/message'
|
||||
|
||||
export const filterSelectValues = (values) => {
|
||||
@@ -138,3 +138,127 @@ 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: {
|
||||
value: [],
|
||||
resource: vm.$t('assets.Asset'),
|
||||
select2: {
|
||||
url: '/api/v1/assets/assets/',
|
||||
ajax: {
|
||||
transformOption: (item) => {
|
||||
return { label: item.name + '(' + item.address + ')', value: item.id }
|
||||
}
|
||||
}
|
||||
},
|
||||
attrs: [
|
||||
{
|
||||
name: 'name',
|
||||
label: vm.$t('common.Name'),
|
||||
inTable: true
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
label: vm.$t('assets.Address'),
|
||||
type: 'ip',
|
||||
inTable: true
|
||||
},
|
||||
{
|
||||
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',
|
||||
inTable: true,
|
||||
formatter: (row, column, cellValue) => cellValue.label,
|
||||
el: {
|
||||
options: categories
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
label: vm.$t('assets.Type'),
|
||||
type: 'select',
|
||||
inTable: true,
|
||||
formatter: (row, column, cellValue) => cellValue.label,
|
||||
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/'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getAssetSelect2Meta() {
|
||||
return {
|
||||
component: Select2,
|
||||
el: {
|
||||
value: [],
|
||||
select2: {
|
||||
ajax: {
|
||||
url: '/api/v1/assets/assets/?fields_size=mini',
|
||||
transformOption: (item) => {
|
||||
return { label: item.name + '(' + item.address + ')', value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
</el-tooltip>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="showSpecAccounts" class="spec-accounts">
|
||||
<el-form-item label="选择账号">
|
||||
<div v-if="showSpecAccounts" class="spec-accounts">
|
||||
<TagInput
|
||||
:autocomplete="autocomplete"
|
||||
:tag-type="getTagType"
|
||||
@@ -25,20 +23,20 @@
|
||||
@change="handleTagChange"
|
||||
/>
|
||||
<span v-if="showAddTemplate">
|
||||
<el-button size="small" type="primary" style="margin-left: 10px" @click="showAccountTemplateDialog=true">
|
||||
<el-button size="small" style="margin-left: 10px" type="primary" @click="showTemplateDialog=true">
|
||||
{{ $t('common.TemplateAdd') }}
|
||||
</el-button>
|
||||
{{ addTemplateHelpText }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<Dialog
|
||||
v-if="showAccountTemplateDialog"
|
||||
v-if="showTemplateDialog"
|
||||
:title="$tc('accounts.AccountTemplate')"
|
||||
:visible.sync="showAccountTemplateDialog"
|
||||
@confirm="handleAccountTemplateConfirm"
|
||||
:visible.sync="showTemplateDialog"
|
||||
@cancel="handleAccountTemplateCancel"
|
||||
@confirm="handleAccountTemplateConfirm"
|
||||
>
|
||||
<ListTable ref="templateTable" v-bind="accountTemplateTable" />
|
||||
</Dialog>
|
||||
@@ -59,7 +57,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [Array],
|
||||
type: [Array, String],
|
||||
default: () => []
|
||||
},
|
||||
assets: {
|
||||
@@ -78,6 +76,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showVirtualAccount: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
addTemplateHelpText: {
|
||||
type: String,
|
||||
default() {
|
||||
@@ -112,10 +114,12 @@ export default {
|
||||
return {
|
||||
ALL: AllAccount,
|
||||
SPEC: SpecAccount,
|
||||
showAccountTemplateDialog: false,
|
||||
choices: choices,
|
||||
choicesSelected: [],
|
||||
defaultChoices: [this.ALL],
|
||||
showTemplateDialog: false,
|
||||
choices: choices.filter(i => {
|
||||
const isVirtualAccount = [SameAccount, ManualAccount].includes(i.value)
|
||||
return !(isVirtualAccount && !this.showVirtualAccount)
|
||||
}),
|
||||
choicesSelected: [this.ALL],
|
||||
specAccountsInput: [],
|
||||
specAccountsTemplate: [],
|
||||
showSpecAccounts: false,
|
||||
@@ -156,19 +160,14 @@ export default {
|
||||
username: query,
|
||||
assets: this.assets.slice(0, 20).join(','),
|
||||
nodes: this.nodes.slice(0, 20).map(item => {
|
||||
if (typeof item === 'object') {
|
||||
return item.pk
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
return typeof item === 'object' ? item.pk : item
|
||||
}).join(','),
|
||||
oid: this.oid
|
||||
}
|
||||
}).then(res => {
|
||||
if (!res) {
|
||||
res = []
|
||||
}
|
||||
const data = res.filter(item => vm.value.indexOf(item) === -1)
|
||||
if (!res) res = []
|
||||
const data = res
|
||||
.filter(item => vm.value.indexOf(item) === -1)
|
||||
.map(v => ({ value: v, label: v }))
|
||||
cb(data)
|
||||
})
|
||||
@@ -176,10 +175,18 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
this.initDefaultChoice()
|
||||
setTimeout(() => {
|
||||
console.log('Account Value: ', this.value)
|
||||
if (this.value === '') {
|
||||
this.$emit('input', ['@ALL'])
|
||||
} else {
|
||||
this.$emit('input', this.value)
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
initDefaultChoice() {
|
||||
const choicesSelected = this.value.filter(i => i.startsWith('@'))
|
||||
const specAccountsInput = this.value.filter(i => !i.startsWith('@'))
|
||||
if (specAccountsInput.length > 0 && !choicesSelected.includes(this.ALL)) {
|
||||
@@ -189,11 +196,14 @@ export default {
|
||||
if (this.value.indexOf(this.SPEC) > -1) {
|
||||
this.showSpecAccounts = true
|
||||
}
|
||||
if (choicesSelected.length === 0) {
|
||||
choicesSelected.push(this.ALL)
|
||||
}
|
||||
this.choicesSelected = choicesSelected
|
||||
this.specAccountsInput = specAccountsInput
|
||||
},
|
||||
handleAccountTemplateCancel() {
|
||||
this.showAccountTemplateDialog = false
|
||||
this.showTemplateDialog = false
|
||||
},
|
||||
handleAccountTemplateConfirm() {
|
||||
this.specAccountsTemplate = this.$refs.templateTable.selectedRows
|
||||
@@ -201,7 +211,7 @@ export default {
|
||||
this.specAccountsInput = this.specAccountsInput.filter(i => !added.includes(i)).concat(added)
|
||||
this.outputValue()
|
||||
setTimeout(() => {
|
||||
this.showAccountTemplateDialog = false
|
||||
this.showTemplateDialog = false
|
||||
this.outputValue()
|
||||
}, 100)
|
||||
},
|
||||
@@ -242,19 +252,8 @@ export default {
|
||||
}
|
||||
|
||||
.spec-accounts {
|
||||
border: solid 1px #f3f3f4;
|
||||
padding: 10px 10px 0;
|
||||
|
||||
&>>> .el-form-item {
|
||||
display: flex;
|
||||
}
|
||||
&>>> .el-form-item__content {
|
||||
width: 80% !important;
|
||||
flex: 1;
|
||||
}
|
||||
&>>> .filter-field {
|
||||
width: calc(100% - 94px);
|
||||
display: inline-block;
|
||||
>>> .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,11 @@ export default {
|
||||
source: {
|
||||
width: '120px'
|
||||
},
|
||||
username: {
|
||||
formatter: (row) => {
|
||||
return row['username'].replace(' ', '*')
|
||||
}
|
||||
},
|
||||
system_roles: {
|
||||
width: '100px',
|
||||
label: this.$t('users.SystemRoles'),
|
||||
|
||||
81
src/views/users/const.js
Normal file
81
src/views/users/const.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { JSONManyToManySelect } from '@/components/FormFields'
|
||||
|
||||
export const userJSONSelectMeta = (vm) => {
|
||||
return {
|
||||
component: JSONManyToManySelect,
|
||||
el: {
|
||||
value: [],
|
||||
resource: vm.$t('users.Users'),
|
||||
select2: {
|
||||
url: '/api/v1/users/users/',
|
||||
ajax: {
|
||||
transformOption: (item) => {
|
||||
return { label: item.name + '(' + item.username + ')', value: item.id }
|
||||
}
|
||||
}
|
||||
},
|
||||
attrs: [
|
||||
{
|
||||
name: 'name',
|
||||
label: vm.$t('common.Name'),
|
||||
inTable: true
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
label: vm.$t('common.Username'),
|
||||
inTable: true
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: vm.$t('common.Email'),
|
||||
inTable: true
|
||||
},
|
||||
{
|
||||
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'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user