Merge pull request #537 from jumpserver/pr@dev@feat_custom_col_list

feat: 允许用户自定义表格列显示功能
This commit is contained in:
Orange 2021-01-12 02:10:29 +08:00 committed by GitHub
commit cec17bbef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 227 additions and 17 deletions

View File

@ -1,7 +1,7 @@
<template>
<div :class="grouped ? 'el-button-group' : ''">
<el-button v-for="item in iActions" :key="item.name" :size="size" v-bind="item" @click="handleClick(item.name)">
<el-tooltip v-if="['actionExport', 'actionImport', 'actionRefresh'].indexOf(item.name) !== -1" effect="dark" :content="item.tip" placement="top">
<el-tooltip v-if="['actionExport', 'actionImport', 'actionRefresh','actionColumnSetting'].indexOf(item.name) !== -1" effect="dark" :content="item.tip" placement="top">
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
</el-tooltip>
<span v-else>

View File

@ -178,6 +178,7 @@ export default {
}
config.columns = columns
this.iConfig = config
this.$eventBus.$emit('columnsChange', config)
},
filterChange(filters) {
const key = Object.keys(filters)[0]

View File

@ -0,0 +1,97 @@
<template>
<Dialog
v-if="showColumnSettingPopover"
:title="$t('common.CustomCol')"
:visible.sync="showColumnSettingPopover"
:destroy-on-close="true"
:show-cancel="false"
width="35%"
top="10%"
@confirm="handleColumnConfirm()"
>
<el-alert type="success">
{{ this.$t('common.TableColSettingInfo') }}
</el-alert>
<el-checkbox-group
v-model="columnList"
>
<el-row>
<el-col
v-for="item in totalColumns"
:key="item.prop"
:span="8"
style="margin-top:5px;"
>
<el-checkbox
:label="item.prop"
:disabled="item.prop==='id' ||item.prop==='actions' "
>
{{ item.label }}
</el-checkbox>
</el-col>
</el-row>
</el-checkbox-group>
</Dialog>
</template>
<script>
import { mapGetters } from 'vuex'
import Dialog from '@/components/Dialog/index'
export default {
name: 'ColumnSettingPopover',
components: {
Dialog
},
props: {
totalColumns: {
type: Array,
default: () => []
},
defaultColumn: {
type: Array,
default: () => []
}
},
data() {
return {
showColumnSettingPopover: false,
columnList: []
}
},
computed: {
...mapGetters([
'tableConfig'
])
},
mounted() {
this.$eventBus.$on('showColumnSettingPopover', () => {
this.showColumnSettingPopover = true
})
console.log(this.tableConfig, this.$route.name, this.defaultColumn)
this.columnList = _.get(this.tableConfig, this.$route.name, this.defaultColumn || [])
console.log(this.columnList)
console.log(this.totalColumns)
},
methods: {
handleColumnConfirm() {
const ACTIVE_COLUMN_KEY = this.$route.name
this.$store.commit('table/SET_TABLE_CONFIG',
{
key: ACTIVE_COLUMN_KEY,
value: this.columnList
}
)
this.showColumnSettingPopover = false
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@ -2,21 +2,25 @@
<div>
<ActionsGroup :is-fa="true" :actions="rightSideActions" class="right-side-actions right-side-item" />
<ImExportDialog :selected-rows="selectedRows" :url="tableUrl" v-bind="$attrs" />
<ColumnSettingPopover v-bind="$attrs" />
</div>
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup'
import ImExportDialog from './ImExportDialog'
import ColumnSettingPopover from './ColumnSettingPopover'
import { cleanActions } from './utils'
const defaultTrue = { type: Boolean, default: true }
const defaultFalse = { type: Boolean, default: false }
export default {
name: 'RightSide',
components: {
ActionsGroup,
ImExportDialog
ImExportDialog,
ColumnSettingPopover
},
props: {
tableUrl: {
@ -37,6 +41,13 @@ export default {
this.$eventBus.$emit('showImportDialog', { selectedRows })
}
},
hasColumnSetting: defaultFalse,
handleColumnConfig: {
type: Function,
default: function({ selectedRows }) {
this.$eventBus.$emit('showColumnSettingPopover')
}
},
hasRefresh: defaultTrue,
selectedRows: {
type: Array,
@ -54,6 +65,7 @@ export default {
data() {
return {
defaultRightSideActions: [
{ name: 'actionColumnSetting', fa: 'fa-cog', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleColumnConfig.bind(this) },
{ name: 'actionExport', fa: 'fa-download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExport.bind(this) },
{ name: 'actionImport', fa: 'fa-upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImport.bind(this) },
{ name: 'actionRefresh', fa: 'fa-refresh', tip: this.$t('common.Refresh'), has: this.hasRefresh, callback: this.handleRefresh }

View File

@ -1,6 +1,6 @@
<template>
<div>
<TableAction :table-url="iTableConfig.url" :search-table="search" :date-pick="handleDateChange" v-bind="headerActions" :selected-rows="selectedRows" :reload-table="reloadTable" />
<TableAction :table-url="iTableConfig.url" :search-table="search" :date-pick="handleDateChange" v-bind="iHeaderActions" :selected-rows="selectedRows" :reload-table="reloadTable" />
<IBox class="table-content">
<AutoDataTable ref="dataTable" :filter-table="filter" :config="iTableConfig" @selection-change="handleSelectionChange" v-on="$listeners" />
</IBox>
@ -13,6 +13,7 @@ import IBox from '../IBox'
import TableAction from './TableAction'
import Emitter from '@/mixins/emitter'
import deepmerge from 'deepmerge'
import { mapGetters } from 'vuex'
export default {
name: 'ListTable',
components: {
@ -41,6 +42,7 @@ export default {
}
},
computed: {
dataTable() {
return this.$refs.dataTable.$refs.dataTable
},
@ -65,13 +67,24 @@ export default {
}
return false
},
iHeaderActions() {
const config = this.headerActions
const hasColumnSetting = _.get(this.headerActions, 'hasColumnSetting', false)
if (hasColumnSetting) {
config.totalColumns = this.tableConfig.columns
}
return config
},
iTableConfig() {
const config = deepmerge(this.tableConfig, { extraQuery: this.extraQuery })
this.$log.debug('Header actions', this.headerActions)
_.set(config, 'columnsMeta.actions.formatterArgs.hasClone', this.hasCloneAction)
this.$log.debug('ListTable: iTableConfig change', config)
return config
}
return this.getActiveColumns(config)
},
...mapGetters({
tableColConfig: 'tableConfig'
})
},
watch: {
extraQuery: {
@ -79,9 +92,31 @@ export default {
this.$log.debug('ListTable: found extraQuery change')
},
deep: true
},
tableColConfig: {
handler() {
this.$log.debug('ListTable: found colConfig change')
},
deep: true
}
},
methods: {
getActiveColumns(config) {
const ACTIVE_COLUMN_KEY = this.$route.name
const hasColumnSetting = _.get(this.headerActions, 'hasColumnSetting', false)
const defaultColumn = _.get(this.headerActions, 'defaultColumn', [])
if (hasColumnSetting) {
const currentColumnSetting = _.get(this.tableColConfig, ACTIVE_COLUMN_KEY, defaultColumn || [])
const currentColumn = []
config.columns.forEach((v, k) => {
if (currentColumnSetting.indexOf(v.prop) !== -1 || v.prop === 'id') {
currentColumn.push(config.columns[k])
}
})
config.columns = currentColumn
}
return config
},
handleSelectionChange(val) {
this.selectedRows = val
},

View File

@ -58,6 +58,7 @@
"AdminUserDetail": "管理用户详情",
"AdminUserListHelpMessage": "管理用户是资产(被控服务器)上的 root或拥有 NOPASSWD: ALL sudo 权限的用户, JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。\n",
"Asset": "资产",
"HardwareInfo": "硬件信息",
"AssetDetail": "资产详情",
"AssetList": "资产列表",
"AssetListHelpMessage": "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产\n",
@ -184,11 +185,13 @@
"Action": "动作",
"RequestTickets": "申请工单",
"Actions": "操作",
"CustomCol":"自定义列表字段",
"Activate": "激活",
"NeedSpecifiedFile": "需上传指定格式文件",
"TestPortErrorMsg":"端口错误,请重新输入",
"Active": "激活中",
"actionsTips":"剪切板权限控制目前仅支持 RDP/VNC 协议的连接",
"TableColSettingInfo": "请选择您想显示的列表详细信息。",
"Add": "添加",
"UpdateAssetDetail": "配置更多信息",
"AddSuccessMsg": "添加成功",

View File

@ -58,6 +58,7 @@
"AdminUserDetail": "Admin user detail",
"AdminUserListHelpMessage": "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, JumpServer users of the system using the user to `push system user`, `get assets hardware information`, etc.\n",
"Asset": "Asset",
"HardwareInfo": "Hardware info",
"Hardware": "Hardware",
"AccountList": "Account list",
"AssetDetail": "Asset detail",
@ -182,6 +183,7 @@
"common": {
"Nothing": "Nothing",
"Action": "Action",
"CustomCol":"Custom table col",
"RequestTickets": "Request tickets",
"Actions": "Actions",
"NeedSpecifiedFile": "Required to upload the specified format file",
@ -189,6 +191,7 @@
"Activate": "Activate",
"actionsTips":"Clipboard's copy and paste control only support RDP/VNC protocol.",
"Active": "Active",
"TableColSettingInfo": "Please select the list details you want to display",
"Add": "Add",
"PleaseAgreeToTheTerms": "Please agree to the terms",
"PushSelected":"Push selected",

View File

@ -13,6 +13,7 @@ const getters = {
currentOrgRoles: state => state.users.roles,
currentOrgPerms: state => state.users.perms,
MFAVerifyAt: state => state.users.MFAVerifyAt,
MFA_TTl: state => state.settings.publicSettings.SECURITY_MFA_VERIFY_TTL
MFA_TTl: state => state.settings.publicSettings.SECURITY_MFA_VERIFY_TTL,
tableConfig: state => state.table.tableConfig
}
export default getters

View File

@ -0,0 +1,29 @@
import VueCookie from 'vue-cookie'
import Vue from 'vue'
function getTableConfigfromCookie() {
console.log(VueCookie.get('tableConfig') ? JSON.parse(VueCookie.get('tableConfig')) : {})
return VueCookie.get('tableConfig') ? JSON.parse(VueCookie.get('tableConfig')) : {}
}
const state = {
tableConfig: getTableConfigfromCookie()
}
const mutations = {
SET_TABLE_CONFIG: (state, tableConfig) => {
Vue.set(state.tableConfig, tableConfig.key, tableConfig.value)
VueCookie.set('tableConfig', JSON.stringify(state.tableConfig), 14)
}
}
const actions = {
}
export default {
namespaced: true,
state,
mutations,
actions
}

View File

@ -38,7 +38,6 @@ const mutations = {
state.orgs = orgs
},
MODIFY_ORG: (state, org) => {
// console.log(state.orgs)
state.orgs = state.orgs.map(oldOrg => {
if (oldOrg.id === org.id) {
oldOrg.name = org.name

View File

@ -90,6 +90,11 @@ export default {
}
}
},
{
prop: 'platform',
label: this.$t('assets.Platform'),
width: '120px'
},
{
prop: 'comment',
label: this.$t('assets.Comment'),
@ -146,7 +151,9 @@ export default {
hasExport: false,
hasImport: false,
hasLeftActions: false,
hasSearch: true
hasSearch: true,
hasColumnSetting: true,
defaultColumn: ['hostname', 'ip', 'SystemUsers', 'comment', 'id']
}
}
},

View File

@ -98,24 +98,40 @@ export default {
url: '/api/v1/assets/assets/',
hasTree: true,
columns: [
'hostname', 'ip', 'hardware_info', 'connectivity', 'actions'
],
columnsMeta: {
hostname: {
{
prop: 'hostname',
formatter: DetailFormatter,
label: this.$t('assets.Hostname'),
formatterArgs: {
route: 'AssetDetail'
},
showOverflowTooltip: true
},
ip: {
{
prop: 'ip',
sortable: 'custom',
label: `IP`,
width: '140px'
},
hardware_info: {
{
prop: 'domain_display',
label: this.$t('assets.Domain')
},
{
prop: 'hardware_info',
label: this.$t('assets.HardwareInfo'),
showOverflowTooltip: true
},
connectivity: {
{
prop: 'public_ip',
label: this.$t('assets.PublicIp')
},
{
prop: 'platform',
label: this.$t('assets.Platform')
},
{
prop: 'connectivity',
label: this.$t('assets.Reachable'),
formatter: BooleanFormatter,
formatterArgs: {
@ -132,8 +148,11 @@ export default {
width: '90px',
align: 'center'
},
actions: {
{
prop: 'actions',
formatter: ActionsFormatter,
label: this.$t('common.Actions'),
width: '140px',
formatterArgs: {
performDelete: ({ row, col }) => {
const id = row.id
@ -152,9 +171,11 @@ export default {
]
}
}
}
]
},
headerActions: {
hasColumnSetting: true,
defaultColumn: ['name', 'username', 'protocol', 'assets_amount', 'comment', 'actions'],
createRoute: {
name: 'AssetCreate',
query: this.$route.query

View File

@ -31,6 +31,8 @@ export default {
},
headerActions: {
hasMoreActions: false,
hasColumnSetting: true,
defaultColumn: ['name', 'username', 'protocol', 'assets_amount', 'comment', 'id'],
createRoute: 'SystemUserCreate'
},
helpMessage: this.$t('assets.SystemUserListHelpMessage')