mirror of
https://github.com/jumpserver/lina.git
synced 2025-08-01 15:11:16 +00:00
Merge pull request #537 from jumpserver/pr@dev@feat_custom_col_list
feat: 允许用户自定义表格列显示功能
This commit is contained in:
commit
cec17bbef8
@ -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>
|
||||
|
@ -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]
|
||||
|
@ -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>
|
@ -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 }
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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": "添加成功",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
29
src/store/modules/table.js
Normal file
29
src/store/modules/table.js
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user