This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit 9dc56720b4ebcc66c6f9ed6944008330efaa2144
Author: Qian Xia <lauraxiaq...@gmail.com>
AuthorDate: Wed Jul 5 14:41:52 2023 +0800

    KYLIN-5594 data permission
---
 kystudio/package.json                              |    2 +-
 kystudio/src/components/admin/Diagnostic/index.vue |    5 +-
 kystudio/src/components/admin/Diagnostic/store.js  |   26 +-
 .../User/UserDataPermission/UserDataPermission.vue |   96 ++
 .../admin/User/UserDataPermission/locales.js       |    7 +
 .../admin/User/UserDataPermission/store.js         |   45 +
 kystudio/src/components/admin/User/index.vue       |   69 +-
 kystudio/src/components/admin/User/locales.js      |    3 +-
 .../EditExcludeColumnsDialog.vue                   |    4 +-
 .../src/components/project/project_authority.vue   |   66 +-
 kystudio/src/components/project/user_access.vue    | 1228 ++++++++++++++++++++
 kystudio/src/router/routerGuard.js                 |    5 +-
 kystudio/src/service/project.js                    |    3 +
 kystudio/src/service/system.js                     |    4 +-
 kystudio/src/service/user.js                       |    8 +-
 kystudio/src/store/config.js                       |    3 +-
 kystudio/src/store/project.js                      |    3 +
 kystudio/src/store/types.js                        |    3 +
 kystudio/src/store/user.js                         |   15 +-
 19 files changed, 1545 insertions(+), 50 deletions(-)

diff --git a/kystudio/package.json b/kystudio/package.json
index 952b2aa7b2..92b45374a8 100644
--- a/kystudio/package.json
+++ b/kystudio/package.json
@@ -27,7 +27,7 @@
     "js-beautify": "1.6.14",
     "jsplumb": "2.6.9",
     "konva": "4.2.2",
-    "kyligence-kylin-ui": "5.0.3",
+    "kyligence-kylin-ui": "5.0.4",
     "less": "2.7.2",
     "less-loader": "2.2.3",
     "moment-timezone": "0.5.14",
diff --git a/kystudio/src/components/admin/Diagnostic/index.vue 
b/kystudio/src/components/admin/Diagnostic/index.vue
index 7f3dbd3468..c79454ffbe 100644
--- a/kystudio/src/components/admin/Diagnostic/index.vue
+++ b/kystudio/src/components/admin/Diagnostic/index.vue
@@ -393,7 +393,8 @@ export default class Diagnostic extends Vue {
     let data = {}
     if (this.isJobDiagnosis) {
       data = {
-        job_id: this.jobId
+        job_id: this.jobId,
+        project: this.currentSelectedProject
       }
     } else if (this.isQueryHistory) {
       data = {
@@ -440,7 +441,7 @@ export default class Diagnostic extends Vue {
   retryJob (item) {
     const { host, start, end, id } = item
     this.delDumpid(id)
-    this.getDumpRemote({ host, start, end, job_id: this.jobId || '', tm: 
this.getTimes() })
+    this.getDumpRemote({ host, start, end, job_id: this.jobId || '', project: 
this.currentSelectedProject, tm: this.getTimes() })
   }
   changeCheckAllType (val) {
     this.indeterminate = false
diff --git a/kystudio/src/components/admin/Diagnostic/store.js 
b/kystudio/src/components/admin/Diagnostic/store.js
index edfacda84f..ad5dacd409 100644
--- a/kystudio/src/components/admin/Diagnostic/store.js
+++ b/kystudio/src/components/admin/Diagnostic/store.js
@@ -125,7 +125,7 @@ export default {
           if (state.isReset) return
           const { data } = res.data
           await commit(types.UPDATE_DUMP_IDS, { host, start, end, id: data, tm 
})
-          dispatch(types.POLLING_STATUS_MSG, { host, id: data })
+          dispatch(types.POLLING_STATUS_MSG, { host, id: data, project })
           resolve(data)
         }).catch((err) => {
           handleError(err)
@@ -134,10 +134,10 @@ export default {
       })
     },
     // 生成诊断包
-    [types.GET_DUMP_REMOTE] ({ state, commit, dispatch }, { host = '', start = 
'', end = '', job_id = '', tm }) {
+    [types.GET_DUMP_REMOTE] ({ state, commit, dispatch }, { host = '', start = 
'', end = '', job_id = '', project, tm }) {
       if (!host) return
       return new Promise((resolve, reject) => {
-        api.system.getDumpRemote({ host, start, end, job_id }).then(async 
(res) => {
+        api.system.getDumpRemote({ host, start, end, job_id, project 
}).then(async (res) => {
           if (state.isReset) return
           const { data } = res.data
           await commit(types.UPDATE_DUMP_IDS, { host, start, end, id: data, tm 
})
@@ -167,9 +167,9 @@ export default {
       })
     },
     // 获取诊断报生成进度
-    [types.GET_STATUS_REMOTE] ({ commit }, { host, id }) {
+    [types.GET_STATUS_REMOTE] ({ commit }, { host, id, project }) {
       return new Promise((resolve, reject) => {
-        api.system.getStatusRemote({ host, id }).then(res => {
+        api.system.getStatusRemote({ host, id, project }).then(res => {
           const { data } = res.data
           commit(types.SET_DUMP_PROGRESS, {...data, id})
           resolve(res)
@@ -179,16 +179,16 @@ export default {
       })
     },
     // 轮询接口获取信息
-    [types.POLLING_STATUS_MSG] ({ state, commit, dispatch }, { host, id }) {
+    [types.POLLING_STATUS_MSG] ({ state, commit, dispatch }, { host, id, 
project }) {
       if (state.isReset) return
-      dispatch(types.GET_STATUS_REMOTE, { host, id }).then((res) => {
+      dispatch(types.GET_STATUS_REMOTE, { host, id, project }).then((res) => {
         timer[id] = setTimeout(() => {
-          dispatch(types.POLLING_STATUS_MSG, { host, id })
+          dispatch(types.POLLING_STATUS_MSG, { host, id, project })
         }, pollingTime)
         const { data } = res.data
         if (data.status === '000' && data.stage === 'DONE') {
           clearTimeout(timer[id])
-          dispatch(types.DOWNLOAD_DUMP_DIAG, {host, id})
+          dispatch(types.DOWNLOAD_DUMP_DIAG, {host, id, project})
         } else if (['001', '002', '999'].includes(data.status)) {
           clearTimeout(timer[id])
         }
@@ -199,11 +199,15 @@ export default {
       })
     },
     // 下载诊断包
-    [types.DOWNLOAD_DUMP_DIAG] (_, { host, id }) {
+    [types.DOWNLOAD_DUMP_DIAG] (_, { host, id, project }) {
       let dom = document.createElement('a')
       dom.download = true
       // 兼容IE 10以下 无origin属性问题,此处用protocol和host拼接
-      dom.href = 
`${location.protocol}//${location.host}${apiUrl}system/diag?host=${host}&id=${id}`
+      let href = 
`${location.protocol}//${location.host}${apiUrl}system/diag?host=${host}&id=${id}`
+      if (project) {
+        href = href + `&project=${project}`
+      }
+      dom.href = href
       document.body.appendChild(dom)
       dom.click()
       document.body.removeChild(dom)
diff --git 
a/kystudio/src/components/admin/User/UserDataPermission/UserDataPermission.vue 
b/kystudio/src/components/admin/User/UserDataPermission/UserDataPermission.vue
new file mode 100644
index 0000000000..860fd0e66f
--- /dev/null
+++ 
b/kystudio/src/components/admin/User/UserDataPermission/UserDataPermission.vue
@@ -0,0 +1,96 @@
+<template>
+    <el-dialog class="user-data-permission" width="400px"
+      :title="$t('dataPermission')"
+      :visible="isShow"
+      :close-on-press-escape="false"
+      :close-on-click-modal="false"
+      @close="isShow && closeHandler()">
+      <span>{{$t('kylinLang.common.userName')}}</span>
+      <el-input class="ksd-mt-8" v-model="userName" 
:disabled="true"></el-input>
+      <div class="ksd-mt-8 flex">
+        <span>{{$t('dataPermission')}}</span>
+        <el-tooltip class="item" effect="dark" 
:content="$t('dataPermissionTips')" placement="bottom">
+          <i class="el-icon-ksd-info ksd-fs-14 ksd-ml-5"></i>
+        </el-tooltip>
+        <el-switch
+          :value="dataPermission"
+          @change="handleChangePermission"
+          class="ksd-ml-8"
+          :active-text="$t('kylinLang.common.OFF')"
+          :inactive-text="$t('kylinLang.common.ON')">
+        </el-switch>
+      </div>
+      <div slot="footer" class="dialog-footer ky-no-br-space">
+        <el-button size="medium" 
@click="closeHandler()">{{$t('kylinLang.common.cancel')}}</el-button>
+        <el-button type="primary" size="medium" @click="submit" 
:loading="isLoading">{{$t('kylinLang.common.submit')}}</el-button>
+      </div>
+    </el-dialog>
+  </template>
+  
+  <script>
+  import Vue from 'vue'
+  import { Component } from 'vue-property-decorator'
+  import { mapActions, mapMutations, mapState } from 'vuex'
+  import vuex from '../../../../store'
+  import locales from './locales'
+  import store, { types } from './store'
+  import { handleError } from 'util/business'
+  vuex.registerModule(['modals', 'UserDataPermission'], store)
+  @Component({
+    computed: {
+      // Store数据注入
+      ...mapState('UserDataPermission', {
+        isShow: state => state.isShow,
+        dataPermission: state => state.dataPermission,
+        userName: state => state.userName,
+        callback: state => state.callback
+      })
+    },
+    methods: {
+      // Store方法注入
+      ...mapMutations('UserDataPermission', {
+        setModal: types.SET_MODAL,
+        hideModal: types.HIDE_MODAL
+      }),
+      // 后台接口请求
+      ...mapActions({
+        updataUserDataPermission: 'UPDATE_USER_DATA_PERMISSION'
+      })
+    },
+    locales
+  })
+  export default class UserDataPermission extends Vue {
+    isLoading = false
+    closeHandler () {
+      this.hideModal()
+    }
+    handleChangePermission (val) {
+      this.setModal({ dataPermission: val })
+    }
+    async submit () {
+      this.isLoading = true
+      try {
+        await this.updataUserDataPermission({ username: this.userName, 
enabled: this.dataPermission })
+        this.isLoading = false
+        this.callback(true)
+      } catch (e) {
+        handleError(e)
+        this.isLoading = false
+      }
+      this.hideModal()
+    }
+  }
+  </script>
+  <style lang="less">
+    @import '../../../../assets/styles/variables.less';
+    .user-data-permission {
+      .flex {
+        display: flex;
+        align-items: center;
+      }
+      .el-icon-ksd-info {
+        color: @text-placeholder-color;
+      }
+    }
+  </style>
+  
\ No newline at end of file
diff --git a/kystudio/src/components/admin/User/UserDataPermission/locales.js 
b/kystudio/src/components/admin/User/UserDataPermission/locales.js
new file mode 100644
index 0000000000..8babfe6739
--- /dev/null
+++ b/kystudio/src/components/admin/User/UserDataPermission/locales.js
@@ -0,0 +1,7 @@
+
+export default {
+  'en': {
+    dataPermission: 'data Permission',
+    dataPermissionTips: 'Allow users to access data, including viewing sample 
data and querying with SQL'
+  }
+}
diff --git a/kystudio/src/components/admin/User/UserDataPermission/store.js 
b/kystudio/src/components/admin/User/UserDataPermission/store.js
new file mode 100644
index 0000000000..331f9c1022
--- /dev/null
+++ b/kystudio/src/components/admin/User/UserDataPermission/store.js
@@ -0,0 +1,45 @@
+const types = {
+  SHOW_MODAL: 'SHOW_MODAL',
+  HIDE_MODAL: 'HIDE_MODAL',
+  SET_MODAL: 'SET_MODAL',
+  CALL_MODAL: 'CALL_MODAL'
+}
+// 声明:初始state状态
+const initialState = JSON.stringify({
+  isShow: false,
+  callback: null,
+  dataPermission: false,
+  userName: ''
+})
+
+export default {
+  // state深拷贝
+  state: JSON.parse(initialState),
+  mutations: {
+    // 显示Modal弹窗
+    [types.SHOW_MODAL]: (state) => {
+      state.isShow = true
+    },
+    // 隐藏Modal弹窗
+    [types.HIDE_MODAL]: (state) => {
+      state.isShow = false
+    },
+    // 设置Modal中的值
+    [types.SET_MODAL]: (state, payload) => {
+      for (const key in payload) {
+        state[key] = payload[key]
+      }
+    }
+  },
+  actions: {
+    [types.CALL_MODAL] ({ commit }, { dataPermission, userName }) {
+      return new Promise(resolve => {
+        commit(types.SET_MODAL, { dataPermission, userName, callback: resolve 
})
+        commit(types.SHOW_MODAL)
+      })
+    }
+  },
+  namespaced: true
+}
+
+export { types }
diff --git a/kystudio/src/components/admin/User/index.vue 
b/kystudio/src/components/admin/User/index.vue
index e4f926de97..79b0816a5f 100644
--- a/kystudio/src/components/admin/User/index.vue
+++ b/kystudio/src/components/admin/User/index.vue
@@ -14,8 +14,7 @@
           type="primary"
           size="medium"
           icon="el-ksd-icon-add_22"
-          v-if="userActions.includes('addUser')"
-          :disabled="!isTestingSecurityProfile"
+          v-if="userActions.includes('addUser')&&isTestingSecurityProfile"
           @click="editUser('new')">
           {{$t('user')}}
         </el-button>
@@ -55,6 +54,12 @@
           </common-tip>
         </template>
       </el-table-column>
+      <!-- 表:是否有数据权限 -->
+      <el-table-column :label="$t('dataPermission')" align="center" 
:width="120">
+        <template slot-scope="scope">
+          <i class="el-icon-ksd-good_health admin-svg" 
v-if="scope.row.hasQueryPermission"></i>
+        </template>
+      </el-table-column>
       <!-- 表:是否系统管理员列 -->
       <el-table-column :label="$t('admin')" align="center" :width="120">
         <template slot-scope="scope">
@@ -69,26 +74,34 @@
         </template>
       </el-table-column>
       <!-- 表:action列 -->
-      <el-table-column v-if="isActionShow" :label="$t('action')" :width="87">
+      <el-table-column v-if="isActionShow&&isTestingSecurityProfile" 
:label="$t('action')" :width="87">
         <template slot-scope="scope">
           <el-tooltip :content="$t('resetPassword')" effect="dark" 
placement="top">
-            <i class="el-icon-ksd-table_reset_password ksd-fs-14 ksd-mr-10" 
:class="{'is-disabled': !isTestingSecurityProfile}" 
v-if="userActions.includes('changePassword') || scope.row.uuid === 
currentUser.uuid" @click="editUser(scope.row.uuid === currentUser.uuid ? 
'password' : 'resetUserPassword', scope.row)"></i>
+            <i class="el-icon-ksd-table_reset_password ksd-fs-14 ksd-mr-10" 
v-if="userActions.includes('changePassword') || scope.row.uuid === 
currentUser.uuid" @click="editUser(scope.row.uuid === currentUser.uuid ? 
'password' : 'resetUserPassword', scope.row)"></i>
           </el-tooltip><span>
           </span><el-tooltip :content="$t('groupMembership')" effect="dark" 
placement="top">
-            <i class="el-icon-ksd-table_group ksd-fs-14 ksd-mr-10" 
:class="{'is-disabled': !isTestingSecurityProfile}" 
v-if="userActions.includes('assignGroup')" @click="editUser('group', 
scope.row)"></i>
+            <i class="el-icon-ksd-table_group ksd-fs-14 ksd-mr-10" 
v-if="userActions.includes('assignGroup')" @click="editUser('group', 
scope.row)"></i>
           </el-tooltip><span>
           </span><common-tip :content="$t('kylinLang.common.moreActions')" 
v-if="isMoreActionShow"><el-dropdown trigger="click">
-            <i class="el-icon-ksd-table_others" :class="{'is-disabled': 
!isTestingSecurityProfile}"></i>
+            <i class="el-icon-ksd-table_others"></i>
             <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item :disabled="!isTestingSecurityProfile" 
v-if="userActions.includes('editUser')&&scope.row.uuid !== currentUser.uuid" 
@click.native="editUser('edit', 
scope.row)">{{$t('editRole')}}</el-dropdown-item>
-              <el-dropdown-item :disabled="!isTestingSecurityProfile" 
v-if="userActions.includes('deleteUser')" 
@click.native="dropUser(scope.row)">{{$t('drop')}}</el-dropdown-item>
-              <el-dropdown-item :disabled="!isTestingSecurityProfile" 
v-if="userActions.includes('disableUser') && scope.row.disabled" 
@click.native="changeStatus(scope.row)">{{$t('enable')}}</el-dropdown-item>
-              <el-dropdown-item :disabled="!isTestingSecurityProfile" 
v-if="userActions.includes('disableUser') && !scope.row.disabled" 
@click.native="changeStatus(scope.row)">{{$t('disable')}}</el-dropdown-item>
+              <el-dropdown-item 
v-if="userActions.includes('editUser')&&scope.row.uuid !== currentUser.uuid" 
@click.native="editUser('edit', 
scope.row)">{{$t('editRole')}}</el-dropdown-item>
+              <el-dropdown-item :disabled="!hasQueryPermission" 
v-if="userActions.includes('editUserDataPermission')&&scope.row.uuid !== 
currentUser.uuid&&scope.row.admin&&!scope.row.isSuperAdmin" 
@click.native="editUserDataPermission(scope.row)">{{$t('dataPermission')}}</el-dropdown-item>
+              <el-dropdown-item v-if="userActions.includes('deleteUser')" 
@click.native="dropUser(scope.row)">{{$t('drop')}}</el-dropdown-item>
+              <el-dropdown-item v-if="userActions.includes('disableUser') && 
scope.row.disabled" 
@click.native="changeStatus(scope.row)">{{$t('enable')}}</el-dropdown-item>
+              <el-dropdown-item v-if="userActions.includes('disableUser') && 
!scope.row.disabled" 
@click.native="changeStatus(scope.row)">{{$t('disable')}}</el-dropdown-item>
             </el-dropdown-menu>
           </el-dropdown>
           </common-tip>
         </template>
       </el-table-column>
+      <el-table-column v-if="isActionShow&&!isTestingSecurityProfile" 
:label="$t('action')" :width="87">
+        <template slot-scope="scope">
+          <el-tooltip :content="$t('dataPermission')" effect="dark" 
placement="top">
+            <i class="el-ksd-n-icon-acl-data-outlined ksd-fs-14 ksd-mr-10" 
v-if="userActions.includes('editUserDataPermission')&&scope.row.uuid !== 
currentUser.uuid&&scope.row.admin&&!scope.row.isSuperAdmin" 
@click="editUserDataPermission(scope.row)"></i>
+          </el-tooltip>
+        </template>
+      </el-table-column>
     </el-table>
 
     <kylin-pager
@@ -99,6 +112,7 @@
       :curPage="pagination.page_offset+1"
       @handleCurrentChange="handleCurrentChange">
     </kylin-pager>
+    <UserDataPermission/>
   </div>
 </template>
 
@@ -109,7 +123,8 @@ import { Component } from 'vue-property-decorator'
 
 import locales from './locales'
 import { pageRefTags, bigPageCount } from 'config'
-import { handleError, kylinConfirm } from '../../../util'
+import { handleError, handleSuccessAsync, kylinConfirm } from '../../../util'
+import UserDataPermission from './UserDataPermission/UserDataPermission'
 
 @Component({
   computed: {
@@ -127,12 +142,19 @@ import { handleError, kylinConfirm } from '../../../util'
       removeUser: 'REMOVE_USER',
       loadUsersList: 'LOAD_USERS_LIST',
       loadUserListByGroupName: 'GET_USERS_BY_GROUPNAME',
-      updateStatus: 'UPDATE_STATUS'
+      updateStatus: 'UPDATE_STATUS',
+      getCurrentUserDataPermission: 'GET_CURRENT_USER_DATA_PERMISSION'
     }),
     ...mapActions('UserEditModal', {
       callUserEditModal: 'CALL_MODAL'
+    }),
+    ...mapActions('UserDataPermission', {
+      callDataPermission: 'CALL_MODAL'
     })
   },
+  components: {
+    UserDataPermission
+  },
   locales,
   beforeRouteEnter: (to, from, next) => {
     if (from.name === 'GroupDetail') {
@@ -157,6 +179,7 @@ export default class SecurityUser extends Vue {
     page_offset: 0
   }
   isLoadingUsers = false
+  hasQueryPermission = false
   get currentGroup () {
     const current = this.$store.state.user.usersGroupList.filter((g) => {
       return g.group_name === this.$route.params.groupName
@@ -178,6 +201,8 @@ export default class SecurityUser extends Vue {
       analyst: user.authorities.some(role => role.authority === 
'ROLE_ANALYST'),
       default_password: user.default_password,
       authorities: user.authorities,
+      hasQueryPermission: user.has_query_permission,
+      isSuperAdmin: user.is_super_admin,
       groups: user.authorities.map(role => role.authority),
       uuid: user.uuid
     }))
@@ -232,6 +257,13 @@ export default class SecurityUser extends Vue {
     isSubmit && this.loadUsers(this.filterName)
   }
 
+  async editUserDataPermission (row) {
+    const isSubmit = await this.callDataPermission({ dataPermission: 
row.hasQueryPermission, userName: row.username })
+    if (isSubmit) {
+      this.loadUsers()
+    }
+  }
+
   async dropUser (userDetail) {
     if (!this.isTestingSecurityProfile) return
     try {
@@ -263,8 +295,15 @@ export default class SecurityUser extends Vue {
     }
   }
 
-  mounted () {
+  async mounted () {
     this.loadUsers()
+    try {
+      const res = await this.getCurrentUserDataPermission({ username: 
this.currentUser.username })
+      const { enabled } = await handleSuccessAsync(res)
+      this.hasQueryPermission = enabled
+    } catch (e) {
+      handleError(e)
+    }
   }
 }
 </script>
@@ -279,6 +318,10 @@ export default class SecurityUser extends Vue {
     cursor: default;
   }
   .user-table {
+    i {
+      cursor: pointer;
+    }
+    .el-ksd-n-icon-acl-data-outlined:hover,
     .el-icon-ksd-table_reset_password:hover,
     .el-icon-ksd-table_group:hover,
     .el-icon-ksd-table_others:hover {
diff --git a/kystudio/src/components/admin/User/locales.js 
b/kystudio/src/components/admin/User/locales.js
index 978bf09f07..ee8282de7f 100644
--- a/kystudio/src/components/admin/User/locales.js
+++ b/kystudio/src/components/admin/User/locales.js
@@ -16,6 +16,7 @@ export default {
     cofirmDelUser: 'Are you sure you want to delete the user {userName} ?',
     delUserTitle: 'Delete User',
     userList: 'User List',
-    changeUserTips: 'Are you sure you want to {status} the user {userName} ?'
+    changeUserTips: 'Are you sure you want to {status} the user {userName} ?',
+    dataPermission: 'Data Permission'
   }
 }
diff --git 
a/kystudio/src/components/common/EditExcludeColumnsDialog/EditExcludeColumnsDialog.vue
 
b/kystudio/src/components/common/EditExcludeColumnsDialog/EditExcludeColumnsDialog.vue
index 7f96397026..188a7675ae 100644
--- 
a/kystudio/src/components/common/EditExcludeColumnsDialog/EditExcludeColumnsDialog.vue
+++ 
b/kystudio/src/components/common/EditExcludeColumnsDialog/EditExcludeColumnsDialog.vue
@@ -75,14 +75,14 @@
         </template>
       </el-table-column>
     </el-table>
-    <kap-pager
+    <kylin-pager
       class="ksd-center ksd-mt-16" ref="columnPager"
       :refTag="pageRefTags.exclusionColumnsPager"
       :totalSize="columnsTotalSize"
       :curPage="pagination.page_offset + 1"
       :perPageSize="pagination.page_size"
       @handleCurrentChange="handleCurrentChange">
-    </kap-pager>
+    </kylin-pager>
     <div slot="footer" class="dialog-footer">
       <el-popover
         ref="popover"
diff --git a/kystudio/src/components/project/project_authority.vue 
b/kystudio/src/components/project/project_authority.vue
index 2f8a10e464..7978a3179e 100644
--- a/kystudio/src/components/project/project_authority.vue
+++ b/kystudio/src/components/project/project_authority.vue
@@ -29,7 +29,12 @@
       </el-col>
     </el-row>
     <div>
-      <el-table :data="userAccessList" :empty-text="emptyText" 
class="user-access-table" key="user">
+      <el-table ref="userAccessTable" @expand-change="expandChange" 
:data="userAccessList" :empty-text="emptyText" class="user-access-table" 
key="user">
+        <el-table-column type="expand" :width="36">
+          <template slot-scope="props">
+            <user_access @reload="loadAccess" :projectName="currentProject" 
:row="props.row"></user_access>
+          </template>
+        </el-table-column>
         <el-table-column :label="$t('userOrGroup')" prop="role_or_name" 
class-name="role-name-cell" show-overflow-tooltip>
           <template slot-scope="props">
             <i :class="{'el-icon-ksd-table_admin': props.row.type === 'User', 
'el-icon-ksd-table_group': props.row.type === 'Group'}"></i>
@@ -66,9 +71,9 @@
     <el-dialog :title="authorTitle" width="960px" class="user-access-dialog" 
:close-on-press-escape="false" :close-on-click-modal="false" 
:visible.sync="authorizationVisible" @close="initAccessData">
       <div class="content-container">
         <div class="author-tips">
-          <div class="item-point">{{$t('authorTips')}}</div>
-          <div class="item-point">{{$t('authorTips1')}}</div>
           <div class="item-point" v-html="$t('authorTips2')"></div>
+          <div class="item-point ksd-mt-16">{{$t('authorTips')}}</div>
+          <div class="item-point">{{$t('authorTips1')}}</div>
         </div>
         <div class="ksd-title-label-small">{{$t('selectUserAccess')}}</div>
         <div v-for="(accessMeta, index) in accessMetas" :key="index" 
class="user-group-select ksd-mt-10 ky-no-br-space">
@@ -138,10 +143,11 @@
 <script>
 import Vue from 'vue'
 import { Component } from 'vue-property-decorator'
-import { objectClone } from '../../util'
+import { objectClone, indexOfObjWithSomeKey } from '../../util'
 import { handleSuccess, handleError, kylinConfirm, hasRole, 
hasPermissionOfProjectAccess } from '../../util/business'
 import { mapActions, mapGetters } from 'vuex'
 import { permissions, pageRefTags, pageCount } from 'config'
+import userAccess from './user_access'
 @Component({
   methods: {
     ...mapActions({
@@ -162,6 +168,9 @@ import { permissions, pageRefTags, pageCount } from 'config'
       'projectActions'
     ])
   },
+  components: {
+    'user_access': userAccess
+  },
   locales: {
     'en': {
       projectTitle: 'Authorization (Project: {projectName})',
@@ -184,7 +193,7 @@ import { permissions, pageRefTags, pageCount } from 'config'
       Group: 'User Group',
       User: 'User',
       Query: 'Query',
-      Admin: 'Admin',
+      Admin: 'Project Admin',
       Management: 'Management',
       Operation: 'Operation',
       tableName: 'Table Name',
@@ -193,14 +202,13 @@ import { permissions, pageRefTags, pageCount } from 
'config'
       deleteAccessTip: 'Are you sure you want to delete the authorization of 
"{userName}" in this project?',
       access: 'Role',
       deleteAccessTitle: 'Delete Authorization',
-      authorTips: 'Can\'t add system admin to the list, as this role already 
has full access to all projects.',
+      authorTips: 'Can\'t add System Admin to the list, as this role already 
has full access to all projects.',
       authorTips1: 'By default, the added user/user group would be granted 
full access to all the tables in the project.',
-      authorTips2: `What roles does Kylin provide?<br>
-      The relationship of each role is: Admin > Management > Operation > 
Query. For example, Admin includes all the permissions of the other three 
roles. Management includes all the permissions of Operation and Query. 
Operation includes all the permissions of Query.<br>
-      1. Query: For business analyst who would need permissions to query 
tables or indexes.<br>
-      2. Operation: For the operator who need permissions to build indexes and 
monitor job status.<br>
-      3. Management: For the model designer who would need permissions to load 
tables and design models.<br>
-      4. Admin: For the project admin who would need all permissions and could 
manage and maintain this project, including loading tables, authorizing user 
access permissions, etc.`,
+      authorTips2: `<div class="ksd-mb-8">What roles does Kyligence Enterprise 
provide?</div>
+      <p><span>Project Admin</span><span>For the project admin who needs all 
permission and could manage and maintain this project, including loading 
tables, authorizing user access permission, etc.</span></p>
+      <p><span>Management</span><span>For the model designer who needs 
permission to load tables, design models, build indexes and monitor job 
status.</span></p>
+      <p><span>Operation</span><span>For the operator who needs permission to 
build indexes and monitor job status.</span></p>
+      <p><span>Query</span><span>For the business analyst who needs permission 
to query tables or indexes.</span></p>`,
       noAuthorityTip: 'Access denied. Please try again after logging in.'
     }
   }
@@ -223,6 +231,7 @@ export default class ProjectAuthority extends Vue {
   authorizationVisible = false
   authorForm = {name: [], editName: '', role: 'Admin'}
   isEditAuthor = false
+  expandedRows = []
   showMask = {
     1: 'Query',
     16: 'Admin',
@@ -316,6 +325,14 @@ export default class ProjectAuthority extends Vue {
       return flag
     }
   }
+  expandChange (row) {
+    const index = indexOfObjWithSomeKey(this.expandedRows, 'id', row.id)
+    if (index !== -1) {
+      this.expandedRows.splice(index, 1)
+    } else {
+      this.expandedRows.push(row)
+    }
+  }
   showLimitTips (val) {
     return val ? this.userTotalSize > 100 : this.groupTotalSize > 100
   }
@@ -500,6 +517,18 @@ export default class ProjectAuthority extends Vue {
           access.accessDetails = []
           return access
         }) || []
+        if (this.expandedRows.length && this.$refs.userAccessTable) {
+          const expandedRows = objectClone(this.expandedRows)
+          this.expandedRows = []
+          expandedRows.forEach((item, i) => {
+            const index = indexOfObjWithSomeKey(this.userAccessList, 'id', 
item.id)
+            if (index !== -1) {
+              this.$nextTick(() => {
+                
this.$refs.userAccessTable.toggleRowExpansion(this.userAccessList[index], true)
+              })
+            }
+          })
+        }
       })
     }, (res) => {
       handleError(res)
@@ -586,6 +615,19 @@ export default class ProjectAuthority extends Vue {
           top: 8px;
           left: -10px;
         }
+        p {
+          margin-left: -8px;
+          border-bottom: 1px solid @ke-color-secondary;
+          span {
+            padding: 8px;
+            display: table-cell;
+            &:first-child {
+              width: 130px;
+              box-sizing: border-box;
+              vertical-align: middle;
+            }
+          }
+        }
       }
     }
     .user-group-select {
diff --git a/kystudio/src/components/project/user_access.vue 
b/kystudio/src/components/project/user_access.vue
new file mode 100644
index 0000000000..69e2226a9a
--- /dev/null
+++ b/kystudio/src/components/project/user_access.vue
@@ -0,0 +1,1228 @@
+<template>
+    <div class="user-access-block" v-loading="loading">
+      <div class="data-permission clearfix">
+        <div class="flex ksd-fleft">
+          <span class="ksd-title-label">{{$t('dataPermission')}}</span>
+          <el-tooltip class="item" effect="dark" 
:content="$t('dataPermissionTips')" placement="bottom">
+            <i class="el-icon-ksd-info ksd-fs-14 ksd-ml-5"></i>
+          </el-tooltip>
+          <el-switch
+            :value="ext_permissions"
+            class="ksd-ml-8"
+            @change="handleChangePermission"
+            :disabled="!isDataPermission"
+            :active-text="$t('kylinLang.common.OFF')"
+            :inactive-text="$t('kylinLang.common.ON')">
+          </el-switch>
+        </div>
+      </div>
+      <el-button type="primary" plain size="small" 
icon="el-ksd-n-icon-edit-outlined" class="ksd-mt-10" @click="editAccess" 
v-if="!isEdit && isAuthority && ext_permissions">{{$t('editACL')}}</el-button>
+      <el-row class="ksd-mt-10" v-if="ext_permissions">
+        <el-col :span="8">
+          <div class="access-card">
+            <div class="access-title">
+              <span v-if="!isEdit">{{$t('accessTables')}} 
({{tableAuthorizedNum}})</span>
+              <el-checkbox v-model="isAllTablesAccess" 
@change="checkAllTables" :indeterminate="tableAuthorizedNum !== totalNum && 
tableAuthorizedNum>0" :disabled="!tables.length" v-else>{{$t('accessTables')}} 
({{tableAuthorizedNum}}/{{totalNum}})</el-checkbox>
+            </div>
+            <div class="access-search">
+              <el-input size="mini" :placeholder="$t('searchKey')" 
v-model="tableFilter">
+                <i slot="prefix" class="el-input__icon 
el-ksd-icon-search_16"></i>
+              </el-input>
+            </div>
+            <div class="access-tips" v-if="isAllTablesAccess&&!isEdit">
+              <i class="el-icon-ksd-info ksd-fs-14"></i>
+              <span class="ksd-fs-12">{{$t('accessTips')}}</span>
+            </div>
+            <div class="access-content tree-content" :class="{'all-tips': 
isAllTablesAccess&&!isEdit}">
+              <el-tree
+                v-if="filterTableData.length&&isRerender"
+                show-overflow-tooltip
+                node-key="id"
+                ref="tableTree"
+                class="acl-tree"
+                :data="filterTableData"
+                :show-checkbox="isEdit"
+                :props="defaultProps"
+                :render-after-expand="false"
+                :highlight-current="true"
+                :default-expanded-keys="defaultExpandedKeys"
+                :default-checked-keys="defaultCheckedKeys"
+                @check="checkChange"
+                @node-expand="pushTableId"
+                @node-collapse="removeTableId"
+                @node-click="handleNodeClick">
+                <span class="custom-tree-node" slot-scope="{ node, data }">
+                  <i class="ksd-mr-2" :class="data.icon"></i>
+                  <span class="ky-ellipsis" :class="data.class" 
:title="node.label">{{ node.label }}</span>
+                </span>
+              </el-tree>
+              <kylin-nodata :content="emptyText" v-else>
+              </kylin-nodata>
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="8">
+          <div class="access-card column-card">
+            <div class="access-title">
+              <span v-if="!isEdit">{{$t('accessColumns')}} 
({{colAuthorizedNum}})</span>
+              <el-checkbox v-model="selectAllColumns" 
@change="checkAllColumns" :disabled="!isCurrentTableChecked || !columns.length" 
:indeterminate="colAuthorizedNum !== columns.length && colAuthorizedNum>0" 
v-else>{{$t('accessColumns')}} 
({{colAuthorizedNum}}/{{columns.length}})</el-checkbox>
+            </div>
+            <div class="access-search">
+              <el-input size="mini" :placeholder="$t('searchKey')" 
v-model="columnFilter">
+                <i slot="prefix" class="el-input__icon 
el-ksd-icon-search_16"></i>
+              </el-input>
+            </div>
+            <div class="access-tips" v-if="isAllColAccess&&!isEdit">
+              <i class="el-icon-ksd-info ksd-fs-14"></i>
+              <span class="ksd-fs-12">{{$t('accessColsTips')}}</span>
+            </div>
+            <div class="access-content" :class="{'all-tips': 
isAllColAccess&&!isEdit}">
+              <div v-if="pagedFilterColumns.length">
+                <ul>
+                  <li v-for="col in pagedFilterColumns" :key="col.name">
+                    <el-checkbox @change="val => selectColumn(val, col.name)" 
:disabled="!isCurrentTableChecked" size="medium" v-if="isEdit" 
:value="col.authorized">{{col.name}}</el-checkbox>
+                    <span v-else>{{col.name}}</span>
+                  </li>
+                </ul>
+                <div class="list-load-more" @click="loadMoreCols" 
v-if="pagedFilterColumns.length<filterCols.length">{{$t('loadMore')}}</div>
+              </div>
+              <kylin-nodata :content="emptyText2" v-else>
+              </kylin-nodata>
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="8">
+          <div class="access-card row-card">
+            <div class="access-title">
+              <span>{{$t('accessRows')}}</span>
+              <!-- <el-button type="primary" plain size="small" 
icon="el-ksd-icon-add_16" class="ksd-fright ksd-mt-5" @click="addRowAccess" 
v-if="isEdit" 
:disabled="!isCurrentTableChecked">{{$t('addRowAccess')}}</el-button> -->
+            </div>
+            <div class="access-search">
+              <el-input size="mini" :placeholder="$t('searchKey')" 
v-model="rowSearch">
+                <i slot="prefix" class="el-input__icon 
el-ksd-icon-search_16"></i>
+              </el-input>
+            </div>
+            <div class="access-tips" 
v-if="isCurrentTableChecked&&row_filter&&!row_filter.filter_groups.length">
+              <i class="el-icon-ksd-info ksd-fs-14"></i>
+              <span class="ksd-fs-12">{{$t('accessRowsTips')}}</span>
+            </div>
+            <div class="access-content">
+              <el-select class="ksd-mt-10 ksd-mb-5 ksd-ml-10 join-type" 
size="small" v-model="row_filter.type" v-if="isEdit && row_filter && 
getSearchFilterdGroup(row_filter.filter_groups).length" 
@change="changeRowFilterType">
+                <el-option label="AND" value="AND"></el-option>
+                <el-option label="OR" value="OR"></el-option>
+              </el-select>
+              <div v-for="(fg, fgIndex) in 
getSearchFilterdGroup(row_filter.filter_groups)" :key="fgIndex">
+                <div class="filter-groups" :class="{'is-group': fg.is_group}">
+                  <div class="filter-group-block">
+                    <div class="clearfix">
+                      <el-select class="join-type ksd-fleft" size="small" 
v-model="fg.type" @change="changeFilterGroupTpye(fgIndex)" 
v-if="fg.is_group&&fg.filters.length&&isEdit">
+                        <el-option label="AND" value="AND"></el-option>
+                        <el-option label="OR" value="OR"></el-option>
+                      </el-select>
+                    </div>
+                      <el-dropdown class="group-action-btn" size="small" 
v-if="fg.is_group&&isEdit">
+                        <span class="el-dropdown-link">
+                          <i class="el-icon-ksd-table_others"></i>
+                        </span>
+                        <el-dropdown-menu slot="dropdown">
+                          <el-dropdown-item 
@click.native="addRowAccess(fgIndex)">
+                            <i class="el-ksd-icon-add_16"></i>
+                            {{$t('filters')}}
+                          </el-dropdown-item>
+                          <el-dropdown-item @click.native="deleteFG(fgIndex)">
+                            <i class="el-icon-ksd-table_delete"></i>
+                            {{$t('kylinLang.common.delete')}}
+                          </el-dropdown-item>
+                        </el-dropdown-menu>
+                      </el-dropdown>
+                    <!-- getFilterFilters 搜索后的filters, getlimitFilters 
是搜索后length 大于3时要收拢filterGroup -->
+                    <ul v-if="getFilterFilters(fg.filters).length" 
:key="fg.isExpand">
+                      <div v-for="(row, key) in getlimitFilters(fg)" 
:key="key" >
+                        <li class="row-list">
+                          <el-row>
+                            <el-col :span="isEdit ? 23 : 24">
+                              <span>{{row.column_name}}</span><span 
v-if="row.in_items.length"> IN </span><span v-if="row.in_items.length" 
class="row-values">({{row.in_items.toString()}})</span><span 
v-if="row.like_items.length">&nbsp;LIKE </span><span class="row-values" 
v-if="row.like_items.length">({{row.like_items.toString()}})</span>
+                            </el-col>
+                            <el-col :span="1" class="ky-no-br-space btn-icons" 
v-if="isEdit">
+                              <!-- <i class="el-icon-ksd-table_edit ksd-fs-16" 
@click="editRowAccess(key, row)"></i>
+                              <i class="el-icon-ksd-table_delete ksd-fs-16 
ksd-ml-10" @click="deleteRowAccess(key, row)"></i> -->
+                              <el-dropdown size="small" v-if="isEdit">
+                                <span class="el-dropdown-link">
+                                  <i class="el-icon-ksd-table_others"></i>
+                                </span>
+                                <el-dropdown-menu slot="dropdown">
+                                  <el-dropdown-item 
@click.native="editRowAccess(fgIndex, key, 
row)">{{$t('kylinLang.common.edit')}}</el-dropdown-item>
+                                  <el-dropdown-item 
@click.native="deleteRowAccess(fgIndex, key, 
row)">{{$t('kylinLang.common.delete')}}</el-dropdown-item>
+                                </el-dropdown-menu>
+                              </el-dropdown>
+                            </el-col>
+                          </el-row>
+                        </li>
+                        <div v-if="key !== getlimitFilters(fg).length - 1" 
class="join-type-label">{{fg.type}}</div>
+                      </div>
+                    </ul>
+                    <div class="center ksd-mt-10" 
v-if="getFilterFilters(fg.filters).length > 3">
+                      <el-button type="primary" size="small" 
@click="toggleExpandFG(fg)" text>
+                        {{fg.isExpand ? $t('collapse') : $t('expandAll')}} 
{{fg.isExpand ? '' : `(${fg.filters.length})`}}
+                      </el-button>
+                    </div>
+                    <el-button type="primary" size="small" 
v-if="fg.is_group&&isEdit" icon="el-ksd-icon-add_16" 
@click="addRowAccess(fgIndex)" text>{{$t('filters')}}</el-button>
+                  </div>
+                </div>
+                <div v-if="fgIndex !== 
getSearchFilterdGroup(row_filter.filter_groups).length - 1" 
class="join-type-label">{{row_filter.type}}</div>
+              </div>
+              <el-dropdown size="small" class="ksd-ml-10 ksd-mb-10" 
v-if="isEdit&&row_filter&&getSearchFilterdGroup(row_filter.filter_groups).length">
+                <span class="el-dropdown-link">
+                  <el-button type="primary" size="small" 
:disabled="!isCurrentTableChecked" icon="el-ksd-icon-add_16" 
text>{{$t('add')}}</el-button>
+                </span>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item 
@click.native="addRowAccess(-1)">{{$t('filters')}}</el-dropdown-item>
+                  <el-dropdown-item 
@click.native="addFilterGroups">{{$t('filterGroups')}}</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+              <kylin-nodata :content="emptyText3" 
v-if="isCurrentTableChecked&&row_filter&&!getSearchFilterdGroup(row_filter.filter_groups).length&&row_filter.filter_groups.length">
+              </kylin-nodata>
+              <div class="view-all-tips" 
v-if="isCurrentTableChecked&&row_filter&&!row_filter.filter_groups.length">
+                <div><i class="point">•</i> {{$t('viewAllDataTips')}}</div>
+                <div><i class="point">•</i> {{$t('viewAllDataTips1')}}</div>
+                <div class="add-rows-btns">
+                  <el-dropdown size="small" v-if="isEdit">
+                    <span class="el-dropdown-link">
+                      <el-button type="primary" size="small" 
:disabled="!isCurrentTableChecked" icon="el-ksd-icon-add_16" 
text>{{$t('add')}}</el-button>
+                    </span>
+                    <el-dropdown-menu slot="dropdown">
+                      <el-dropdown-item 
@click.native="addRowAccess(-1)">{{$t('filters')}}</el-dropdown-item>
+                      <el-dropdown-item 
@click.native="addFilterGroups">{{$t('filterGroups')}}</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </el-dropdown>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+      <div class="expand-footer ky-no-br-space ksd-right" v-if="isEdit">
+        <el-button plain size="small" 
@click="cancelAccess">{{$t('kylinLang.common.cancel')}}</el-button>
+        <el-button type="primary" :disabled="disabledSubmitBtn" size="small" 
class="ksd-ml-10" :loading="submitLoading" 
@click="submitAccess">{{$t('kylinLang.common.submit')}}</el-button>
+      </div>
+      <el-dialog :title="rowAuthorTitle" width="960px" class="author_dialog" 
:close-on-press-escape="false" :close-on-click-modal="false" 
:visible.sync="rowAccessVisible" @close="resetRowAccess">
+        <div v-if="filterTotalLength+newFiltersLenth>maxFilterAndFilterValues 
|| overedRowValueFilters.length>0">
+          <el-alert class="ksd-mb-10" type="error" show-icon :closable="false">
+            <span slot="title">{{$t('overFilterMaxTips')}}
+              <a class="a-like" @click="showErrorDetails = 
!showErrorDetails">{{$t('details')}}
+                <i :class="[showErrorDetails ? 'el-icon-ksd-more_01-copy' : 
'el-icon-ksd-more_02', 'arrow']"></i>
+              </a>
+            </span>
+          </el-alert>
+          <div class="ksd-mtb-10 detail-content" v-if="showErrorDetails">
+            <div 
v-if="filterTotalLength+newFiltersLenth>maxFilterAndFilterValues">{{$t('filterTotal')}}{{filterTotalLength+newFiltersLenth}}/{{maxFilterAndFilterValues}}</div>
+            <div v-if="overedRowValueFilters.length>0">
+              {{$t('filterValuesTotal')}}{{overedRowValueFilters.toString()}}
+            </div>
+          </div>
+        </div>
+        <div class="like-tips-block ksd-mb-10">
+          <div class="ksd-mb-5">{{$t('tipsTitle')}}<span 
class="review-details" @click="showDetails = 
!showDetails">{{$t('viewDetail')}}<i :class="[showDetails ? 
'el-icon-ksd-more_01-copy' : 'el-icon-ksd-more_02', 'arrow']"></i></span></div>
+          <div class="detail-content" v-if="showDetails">
+            <p>{{$t('rules1')}}</p>
+            <p>{{$t('rules2')}}</p>
+            <p>{{$t('rules3')}}</p>
+          </div>
+        </div>
+        <div v-for="(row, key) in rowLists" :key="key" class="ksd-mb-10">
+          <el-select v-model="row.column_name" class="row-column" 
:placeholder="$t('kylinLang.common.pleaseSelectOrSearch')" filterable 
:disabled="isRowAuthorEdit" @change="isUnCharColumn(row.column_name, key)">
+            <i slot="prefix" class="el-input__icon el-icon-search" 
v-if="!row.column_name"></i>
+            <el-option v-for="c in checkedColumns" 
:disabled="c.datatype.indexOf('char') === -1 && c.datatype.indexOf('varchar') 
=== -1 && row.joinType === 'LIKE'" :key="c.name" :label="c.name" 
:value="c.name">
+              <el-tooltip :content="c.name" effect="dark" 
placement="top"><span>{{c.name | omit(30, '...')}}</span></el-tooltip>
+              <span 
class="ky-option-sub-info">{{c.datatype.toLocaleLowerCase()}}</span>
+            </el-option>
+          </el-select>
+          <el-select
+            :placeholder="$t('kylinLang.common.pleaseSelect')"
+            style="width:75px;"
+            class="link-type"
+            popper-class="js_like-type"
+            :disabled="isRowAuthorEdit"
+            v-model="row.joinType">
+            <el-option :disabled="row.isNeedDisableLike && key === 'LIKE'" 
:value="key" v-for="(key, i) in linkKind" :key="i">{{key}}</el-option>
+          </el-select>
+          <el-select
+            v-model="row.items"
+            multiple
+            filterable
+            clearable
+            remote
+            allow-create
+            default-first-option
+            :class="{'row-values-edit': isRowAuthorEdit, 'row-values-add': 
!isRowAuthorEdit}"
+            @change="setRowValues(row.items, key)"
+            :placeholder="$t('pleaseInput')">
+          </el-select>
+          <span class="ky-no-br-space ksd-ml-10" v-if="!isRowAuthorEdit">
+            <el-button type="primary" icon="el-ksd-icon-add_16" plain circle 
size="mini" @click="addRow" v-if="key==0"></el-button>
+            <el-button icon="el-icon-minus" plain circle size="mini" 
@click="removeRow(key)"></el-button>
+          </span>
+        </div>
+        <span slot="footer" class="dialog-footer ky-no-br-space">
+          <div class="ksd-fleft">
+            <el-alert
+              :title="$t('filterTips')"
+              type="info"
+              class="ksd-ptb-5"
+              :show-background="false"
+              :closable="false"
+              show-icon>
+            </el-alert>
+          </div>
+          <el-button plain @click="cancelRowAccess" 
size="medium">{{$t('kylinLang.common.cancel')}}</el-button>
+          <el-button type="primary" @click="submitRowAccess" 
size="medium">{{$t('kylinLang.common.submit')}}</el-button>
+        </span>
+      </el-dialog>
+    </div>
+  </template>
+  
+  <script>
+  import Vue from 'vue'
+  import { Component } from 'vue-property-decorator'
+  import { handleSuccessAsync, indexOfObjWithSomeKey, objectClone, 
kylinConfirm } from '../../util'
+  import { handleSuccess, handleError } from '../../util/business'
+  import { mapActions, mapGetters } from 'vuex'
+  import { pageSizeMapping, maxFilterAndFilterValues } from '../../config'
+  @Component({
+    props: ['row', 'projectName'],
+    computed: {
+      ...mapGetters([
+        'isDataPermission'
+      ])
+    },
+    methods: {
+      ...mapActions({
+        getAccessDetailsByUser: 'GET_ACCESS_DETAILS_BY_USER',
+        submitAccessData: 'SUBMIT_ACCESS_DATA',
+        getAclPermission: 'GET_ACL_PERMISSION',
+        changeProjectUserDataPermission: 'CHANGE_PROJECT_USER_DATA_PERMISSION'
+      })
+    },
+    locales: {
+      'en': {
+        accessTables: 'Table Access List',
+        accessColumns: 'Column Access List',
+        accessRows: 'Row Access List',
+        searchKey: 'Search by table or column name',
+        accessTips: 'All tables in current datasource are accessible.',
+        accessColsTips: 'All columns in current table are accessible.',
+        accessRowsTips: 'All rows in current table are accessible.',
+        viewAllDataTips: 'After the row ACL was set, the user/user group could 
only access the data that match the specified filters.',
+        viewAllDataTips1: 'For the columns without conditions set, user/user 
group could access all the data.',
+        addRowAccess: 'Add Row ACL',
+        addRowAccess1: 'Add Row ACL (Table: {tableName})',
+        editRowAccess: 'Edit Row ACL (Table: {tableName})',
+        pleaseInput: 'Confirm by pressing "enter" key and separate multiple 
values by comma.',
+        loadMore: 'Load More',
+        tipsTitle: 'The row ACL provides IN and LIKE operators. The LIKE 
operator could only be used for char or varchar data type, and needs to be used 
with wildcards. ',
+        viewDetail: 'View Rules',
+        details: 'Details',
+        rules1: '_ (underscore) wildcard characters, matches any single 
character. ',
+        rules2: '% (percent) wildcard characters, matches with zero or more 
characters.',
+        rules3: '\\ (backslash) escape character. The characters following 
"\\" won\'t be regarded as any special characters.',
+        add: 'Add',
+        filters: 'Filter',
+        filterGroups: 'Filter Group',
+        filterTips: 'The relation between different values ​​of the same 
filter is "OR"',
+        deleteFilterGroupTips: 'Are you sure you want to delete the filter 
group? All the included filters would be deleted.',
+        deleteFilterGroupTitle: 'Delete Filter Group',
+        expandAll: 'Expand All',
+        collapse: 'Collapse',
+        overFilterMaxTips: 'The number of filters or the included values of a 
single filter exceeds the upper limit. Please modify.',
+        filterTotal: 'Total number of filters: ',
+        filterValuesTotal: 'The filter(s) including excess values: ',
+        dataPermission: 'Data Permission',
+        dataPermissionTips: 'Allow users to access data, including viewing 
sample data and querying with SQL',
+        confirmOpen: 'Turn Open',
+        confirmOff: 'Turn Off',
+        Group: 'User Group',
+        User: 'User',
+        editACL: 'Manage Table Column, or Row Access',
+        openDataPermissionConfirm: 'Do you want to turn ON the data 
permissions of {type} "{name}"? This {type} will be able to view sample data 
and query.',
+        closeDataPermissionConfirm: 'Are you sure to turn OFF the data 
permissions of {type} "{name}"? This {type} will not be able to view sample 
data and query.'
+      },
+      'zh-cn': {
+        accessTables: '表级访问列表',
+        accessColumns: '列级访问列表',
+        accessRows: '行级访问列表',
+        searchKey: '搜索表名或列名',
+        accessTips: '当前数据源上所有表均可访问',
+        accessColsTips: '当前表上所有列均可访问',
+        accessRowsTips: '当前表上所有行均可访问',
+        viewAllDataTips: '设置行级权限后,用户/用户组仅能查看到表中符合筛选条件的数据。',
+        viewAllDataTips1: '对于没有设置权限的列,用户/用户组仍能够查看该列所有数据。',
+        addRowAccess: '添加行级权限',
+        addRowAccess1: '添加行级权限(表: {tableName})',
+        editRowAccess: '编辑行级权限(表: {tableName})',
+        pleaseInput: '请用回车进行输入确认并用逗号进行多个值分割',
+        loadMore: '加载更多',
+        tipsTitle: '行级权限支持 IN 和 LIKE 操作符。其中,LIKE 仅支持 char 和 varchar 
类型的列,需配合通配符使用。',
+        viewDetail: '查看规则',
+        details: '详情',
+        rules1: '_(下划线)通配符,匹配任意单个字符。',
+        rules2: '%(百分号)通配符,匹配空白字符或任意多个字符。',
+        rules3: '\\(反斜杠)转义符,转义符后的通配符或转义符将不被识别为特殊字符。',
+        add: '添加',
+        filters: '过滤器',
+        filterGroups: '过滤组',
+        filterTips: '同一过滤器的不同值的关系为 “或”(OR)',
+        deleteFilterGroupTips: '确定要删除过滤组吗?过滤组中的过滤器会被一并删除。',
+        deleteFilterGroupTitle: '删除过滤组',
+        expandAll: '展开全部',
+        collapse: '收起',
+        overFilterMaxTips: '过滤器包含的值或过滤器总数超过上限,请修改。',
+        filterTotal: '过滤器总数:',
+        filterValuesTotal: '包含值超额的过滤器:',
+        dataPermission: '数据权限',
+        dataPermissionTips: '允许用户进行数据访问,包括查看样例数据和 SQL 查询',
+        confirmOpen: '开启',
+        confirmOff: '关闭',
+        Group: '用户组',
+        User: '用户',
+        editACL: '编辑表列行级访问权限',
+        openDataPermissionConfirm: '确定要打开{type} “{name}” 
的数据权限吗?这个{type}将能查看样例数据和查询。',
+        closeDataPermissionConfirm: '确定要关闭{type} “{name}” 
的数据权限吗?这个{type}将不能查看样例数据和查询。'
+      }
+    }
+  })
+  export default class UserAccess extends Vue {
+    maxFilterAndFilterValues = maxFilterAndFilterValues
+    defaultProps = {
+      children: 'children',
+      label: 'label'
+    }
+    tables = []
+    filterOriginDatas = []
+    isEdit = false
+    isRerender = true
+    isRowAuthorEdit = false
+    rowAccessVisible = false
+    tableFilter = ''
+    columnFilter = ''
+    rowSearch = ''
+    columns = []
+    filterCols = []
+    rows = []
+    rowLists = [{column_name: '', joinType: 'IN', items: []}]
+    row_filter = {
+      type: 'AND',
+      filter_groups: []
+    }
+    linkKind = ['IN', 'LIKE']
+    isSelectTable = false
+    tableAuthorizedNum = 0
+    totalNum = 0
+    defaultCheckedKeys = []
+    defaultExpandedKeys = ['0']
+    catchDefaultExpandedKeys = ['0']
+    allTables = []
+    copyOriginTables = []
+    databaseIndex = -1
+    tableIndex = -1
+    editFilterGroupIndex = -1
+    editRowIndex = -1
+    currentTable = ''
+    isAllTablesAccess = false
+    isAllColAccess = false
+    colAuthorizedNum = 0
+    submitLoading = false
+    isCurrentTableChecked = false
+    selectAllColumns = false
+    currentTableId = ''
+    loading = false
+    columnPageSize = 100
+    columnCurrentPage = 1
+    isAuthority = false
+    showDetails = false
+    showErrorDetails = false
+    isAddFilterForGroup = false
+    filterTotalLength = 0
+    newFiltersLenth = 0
+    overedRowValueFilters = []
+    ext_permissions = !!this.row.ext_permissions && 
this.row.ext_permissions.length > 0
+    get emptyText () {
+      return this.tableFilter ? this.$t('kylinLang.common.noResults') : 
this.$t('kylinLang.common.noData')
+    }
+    get emptyText2 () {
+      return this.columnFilter ? this.$t('kylinLang.common.noResults') : 
this.$t('kylinLang.common.noData')
+    }
+    get emptyText3 () {
+      return this.rowSearch ? this.$t('kylinLang.common.noResults') : 
this.$t('kylinLang.common.noData')
+    }
+    get disabledSubmitBtn () {
+      return JSON.stringify(this.copyOriginTables) === 
JSON.stringify(this.allTables)
+    }
+    get currentProjectId () {
+      return this.$route.query.projectId
+    }
+    showLoading () {
+      this.loading = true
+    }
+    hideLoading () {
+      this.loading = false
+    }
+    async handleChangePermission (val) {
+      try {
+        const option = { type: this.$t(this.row.type), name: 
this.row.role_or_name }
+        const msg = val ? this.$t('openDataPermissionConfirm', option) : 
this.$t('closeDataPermissionConfirm', option)
+        const confirmBtnText = val ? this.$t('confirmOpen') : 
this.$t('confirmOff')
+        await kylinConfirm(msg, {confirmButtonText: confirmBtnText, 
centerButton: true}, this.$t('dataPermission'))
+        const reqsdata = { access_entry_id: this.row.id, permissions: val ? 
['DATA_QUERY'] : [], principal: this.row.type === 'User', sid: 
this.row.role_or_name }
+        const res = await this.changeProjectUserDataPermission({data: 
reqsdata, projectId: this.currentProjectId})
+        const { data } = await handleSuccessAsync(res)
+        this.ext_permissions = data
+        this.$emit('reload')
+      } catch (e) {
+        handleError(e)
+      }
+    }
+    pushTableId (data) {
+      const index = this.catchDefaultExpandedKeys.indexOf(data.id)
+      if (index === -1) {
+        this.catchDefaultExpandedKeys.push(data.id)
+      }
+    }
+    removeTableId (data) {
+      const index = this.catchDefaultExpandedKeys.indexOf(data.id)
+      if (index !== -1) {
+        this.catchDefaultExpandedKeys.splice(index, 1)
+      }
+    }
+    handleNodeClick (data, node) {
+      if (!data.children && !data.isMore) { // tables data 中‘非加载更多’的node
+        this.isSelectTable = true
+        this.currentTable = data.database + '.' + data.label
+        this.isCurrentTableChecked = data.authorized
+        this.currentTableId = data.id
+        this.$refs.tableTree && 
this.$refs.tableTree.setCurrentKey(this.currentTableId)
+        const indexs = data.id.split('_')
+        this.databaseIndex = indexs[0]
+        this.tableIndex = indexs[1]
+        this.initColsAndRows(data.columns, data.row_filter, data.totalColNum)
+      } else if (!data.children && data.isMore) {
+        node.parent.data.currentIndex++
+        const renderNums = node.parent.data.currentIndex * 
pageSizeMapping.TABLE_TREE
+        const renderMoreTables = node.parent.data.originTables.slice(0, 
renderNums)
+        node.parent.data.children = []
+        node.parent.data.children = renderMoreTables
+        if (renderNums < node.parent.data.originTables.length) {
+          renderMoreTables.push(data)
+        }
+        this.reRenderTree(true)
+      }
+    }
+    initColsAndRows (columns, row_filter, totalColNum) {
+      this.columnCurrentPage = 1
+      this.colAuthorizedNum = 0
+      this.columns = columns.map((col) => {
+        if (col.authorized) {
+          this.colAuthorizedNum++
+        }
+        return {name: col.column_name, authorized: col.authorized, datatype: 
col.datatype}
+      })
+      this.isAllColAccess = this.colAuthorizedNum === totalColNum
+      this.selectAllColumns = this.isAllColAccess
+      this.row_filter = objectClone(row_filter)
+    }
+    getColumns (type) {
+      let columns = this.columns
+      if (type === 'LIKE') {
+        columns = columns.filter((c) => {
+          return c.datatype.indexOf('char') !== -1 || 
c.datatype.indexOf('varchar') !== -1
+        })
+      }
+      return columns
+    }
+    isUnCharColumn (columnName, key) {
+      if (columnName) {
+        const index = indexOfObjWithSomeKey(this.columns, 'name', columnName)
+        let datatype = ''
+        if (index !== -1) {
+          datatype = this.columns[index].datatype
+        }
+        const isNeedDisableLike = datatype.indexOf('char') === -1 && 
datatype.indexOf('varchar') === -1
+        this.$set(this.rowLists[key], 'isNeedDisableLike', isNeedDisableLike)
+      } else {
+        this.$set(this.rowLists[key], 'isNeedDisableLike', false)
+      }
+    }
+    get checkedColumns () {
+      return this.columns.filter(c => c.authorized)
+    }
+    get pagedFilterColumns () {
+      const filterCols = objectClone(this.columns)
+      this.filterCols = filterCols.filter((col) => {
+        return 
col.name.toLowerCase().indexOf(this.columnFilter.trim().toLowerCase()) !== -1
+      })
+      return this.filterCols.slice(0, this.columnCurrentPage * 
this.columnPageSize)
+    }
+    loadMoreCols () {
+      this.columnCurrentPage++
+    }
+    checkAllTables (val) {
+      this.showLoading()
+      setTimeout(() => {
+        for (let i = this.tables.length - 1; i >= 0; i--) {
+          this.tables[i].originTables.forEach((d) => {
+            if (!d.isMore) {
+              this.handleTableData(d, val)
+              this.setCurrentTable(d, val)
+            }
+          })
+        }
+        this.setCurrentTable(this.tables[0].children[0], val)
+        this.reRenderTree()
+        this.hideLoading()
+      }, 500)
+    }
+    checkAllColumns (val) {
+      this.colAuthorizedNum = 0
+      this.columns.forEach((col) => {
+        col.authorized = val
+        if (col.authorized) {
+          this.colAuthorizedNum++
+        }
+      })
+      const columns = 
this.allTables[this.databaseIndex].tables[this.tableIndex].columns
+      columns.forEach((col) => {
+        col.authorized = val
+      })
+      this.tables[this.databaseIndex].originTables[this.tableIndex].columns = 
columns
+    }
+    handleTableData (data, isChecked) {
+      const indexs = data.id.split('_')
+      this.allTables[indexs[0]].tables[indexs[1]].authorized = isChecked
+      let database = objectClone(this.tables[indexs[0]])
+      let defaultCheckedKeys = objectClone(this.defaultCheckedKeys)
+      if (isChecked && !data.authorized) {
+        this.tableAuthorizedNum++
+        database.authorizedNum++
+        database.label = database.databaseName + ` 
(${database.authorizedNum}/${database.totalNum})`
+        defaultCheckedKeys.push(data.id)
+        if (database.authorizedNum && database.authorizedNum === 
database.totalNum) {
+          defaultCheckedKeys.push(database.id)
+        }
+      } else if (!isChecked && data.authorized) {
+        this.tableAuthorizedNum--
+        database.authorizedNum--
+        database.label = database.databaseName + ` 
(${database.authorizedNum}/${database.totalNum})`
+        const removeKeyIndex = defaultCheckedKeys.indexOf(data.id)
+        defaultCheckedKeys.splice(removeKeyIndex, 1)
+        const removeDatabaseKeyIndex = defaultCheckedKeys.indexOf(database.id)
+        if (removeDatabaseKeyIndex !== -1) {
+          defaultCheckedKeys.splice(removeDatabaseKeyIndex, 1)
+        }
+      }
+      data.authorized = isChecked
+      data.columns.forEach((col) => {
+        col.authorized = isChecked
+      })
+      database.originTables[indexs[1]] = data
+      this.tables[indexs[0]] = database
+      this.defaultCheckedKeys = defaultCheckedKeys
+      this.isAllTablesAccess = this.tableAuthorizedNum === this.totalNum
+    }
+    setCurrentTable (data, isChecked) {
+      const indexs = data.id.split('_')
+      this.databaseIndex = indexs[0]
+      this.tableIndex = indexs[1]
+      this.currentTable = data.database + '.' + data.label
+      this.currentTableId = data.id
+      this.isCurrentTableChecked = isChecked
+      this.selectAllColumns = isChecked
+      this.allTables[indexs[0]].tables[indexs[1]].columns.forEach((col) => {
+        col.authorized = isChecked
+      })
+      this.allTables[indexs[0]].tables[indexs[1]].row_filter = { type: 'AND', 
filter_groups: [] }
+      
this.initColsAndRows(this.allTables[indexs[0]].tables[indexs[1]].columns, 
this.allTables[indexs[0]].tables[indexs[1]].row_filter, data.totalColNum)
+    }
+    checkChange (data, checkNode, node) {
+      this.showLoading()
+      setTimeout(() => {
+        const isChecked = node.checked
+        if (!data.children && !data.isMore) { // tables data 中‘非加载更多’的node
+          this.handleTableData(data, isChecked)
+          this.setCurrentTable(data, isChecked)
+          this.reRenderTree()
+        } else if (data.children && data.children.length) {
+          data.originTables.forEach((d) => {
+            if (!d.isMore) {
+              this.handleTableData(d, isChecked)
+              this.setCurrentTable(d, isChecked)
+            }
+          })
+          this.setCurrentTable(data.children[0], isChecked)
+          this.reRenderTree()
+        }
+        this.hideLoading()
+      }, 100)
+    }
+    reRenderTree (isLoadMoreRender) { // isLoadMoreRender 为 true 时,不重置数据
+      this.isRerender = false
+      if (!isLoadMoreRender) {
+        this.tables = [...this.tables]
+      }
+      this.defaultExpandedKeys = objectClone(this.catchDefaultExpandedKeys)
+      this.$nextTick(() => {
+        this.isRerender = true
+        this.handleLoadMoreStyle()
+        setTimeout(() => {
+          this.$refs.tableTree.setCurrentKey(this.currentTableId)
+        })
+      })
+    }
+    get rowAuthorTitle () {
+      return !this.isRowAuthorEdit ? this.$t('addRowAccess1', {tableName: 
this.currentTable}) : this.$t('editRowAccess', {tableName: this.currentTable})
+    }
+    handleLoadMoreStyle () {
+      this.$nextTick(() => {
+        const loadMore = this.$el.querySelectorAll('.acl-tree .load-more') || 
this.$el.getElementsByClassName('.acl-tree .load-more')
+        const indeterminateNodes = this.$el.querySelectorAll('.acl-tree 
.indeterminate-node')
+        if (loadMore.length) {
+          Array.prototype.forEach.call(loadMore, (m) => {
+            const targetCheckbox = 
m.parentNode.parentNode.querySelector('.el-checkbox')
+            if (targetCheckbox) {
+              targetCheckbox.style.display = 'none'
+            }
+          })
+        }
+        if (indeterminateNodes.length) {
+          Array.prototype.forEach.call(indeterminateNodes, (n) => {
+            const indeterminateCheckbox = 
n.parentNode.parentNode.querySelector('.el-checkbox .el-checkbox__input')
+            if (indeterminateCheckbox) {
+              indeterminateCheckbox.className = 'el-checkbox__input 
is-indeterminate'
+            }
+          })
+        }
+      })
+    }
+    get filterTableData () {
+      let filterOriginDatas = objectClone(this.tables)
+      filterOriginDatas = filterOriginDatas.filter((data) => {
+        const originFilterTables = data.originTables.filter((t) => {
+          return 
t.label.toLowerCase().indexOf(this.tableFilter.trim().toLowerCase()) !== -1
+        })
+        const pagedFilterTables = originFilterTables.slice(0, 
pageSizeMapping.TABLE_TREE * data.currentIndex)
+        if (pageSizeMapping.TABLE_TREE < originFilterTables.length) {
+          pagedFilterTables.push({
+            id: data.id + '_more',
+            label: this.$t('loadMore'),
+            class: 'load-more ksd-fs-12',
+            isMore: true
+          })
+        }
+        data.children = pagedFilterTables
+        return pagedFilterTables.length > 0
+      })
+      this.defaultExpandedKeys = objectClone(this.catchDefaultExpandedKeys)
+      this.$nextTick(() => {
+        this.handleLoadMoreStyle()
+        if (this.$refs.tableTree) {
+          this.$refs.tableTree.setCurrentKey(this.currentTableId)
+        }
+      })
+      return filterOriginDatas
+    }
+    isShowRow (row) {
+      let isShow = false
+      if 
(row.column_name.toLowerCase().indexOf(this.rowSearch.trim().toLowerCase()) !== 
-1) {
+        isShow = true
+      }
+      for (let i = 0; i < row.in_items.length; i++) {
+        if 
(row.in_items[i].toLowerCase().indexOf(this.rowSearch.trim().toLowerCase()) !== 
-1) {
+          isShow = true
+          break
+        }
+      }
+      for (let k = 0; k < row.like_items.length; k++) {
+        if 
(row.like_items[k].toLowerCase().indexOf(this.rowSearch.trim().toLowerCase()) 
!== -1) {
+          isShow = true
+          break
+        }
+      }
+      return isShow
+    }
+    editAccess () {
+      this.isEdit = true
+      this.loadAccessDetails(false)
+    }
+    cancelAccess () {
+      this.isEdit = false
+      this.loadAccessDetails(true)
+    }
+    submitAccess () {
+      this.submitLoading = true
+      this.submitAccessData({projectName: this.projectName, userType: 
this.row.type, roleOrName: this.row.role_or_name, accessData: 
this.allTables}).then((res) => {
+        handleSuccess(res, () => {
+          this.$message({
+            type: 'success',
+            message: this.$t('kylinLang.common.submitSuccess')
+          })
+          this.submitLoading = false
+          this.isEdit = false
+          this.loadAccessDetails(true)
+        })
+      }, (res) => {
+        handleError(res)
+        this.submitLoading = false
+      })
+    }
+    selectColumn (val, col) {
+      let columns = 
this.allTables[this.databaseIndex].tables[this.tableIndex].columns
+      const index = indexOfObjWithSomeKey(columns, 'column_name', col)
+      columns[index].authorized = val
+      this.tables[this.databaseIndex].originTables[this.tableIndex].columns = 
columns
+      this.columns[index].authorized = val
+      if (val) {
+        this.colAuthorizedNum++
+      } else {
+        this.colAuthorizedNum--
+      }
+      this.selectAllColumns = this.colAuthorizedNum === this.columns.length
+    }
+    setRowValues (values, key) {
+      let formatValues = []
+      values.forEach((v) => {
+        const val = v.trim().split(/[,,]/g)
+        formatValues = [...formatValues, ...val]
+      })
+      this.rowLists[key].items = formatValues.filter(it => !!it)
+    }
+    addRowAccess (fgIndex) {
+      if (fgIndex !== -1) {
+        this.editFilterGroupIndex = fgIndex
+        this.isAddFilterForGroup = true // 在指定筛选组里添加筛选器
+      } else {
+        this.editFilterGroupIndex = fgIndex
+        this.isAddFilterForGroup = false
+      }
+      this.filterTotalLength = 0
+      this.overedRowValueFilters = []
+      this.isRowAuthorEdit = false
+      this.rowAccessVisible = true
+    }
+    addFilterGroups () {
+      this.row_filter.filter_groups.push({is_group: true, type: 'AND', 
filters: []})
+    }
+    getSearchFilterdGroup (filterGroups) { // 有搜索关键时,如果没有符合搜索的过滤器内容,去掉空的过滤组
+      if (this.rowSearch) {
+        return filterGroups.filter(fg => {
+          return this.getFilterFilters(fg.filters).length > 0
+        })
+      } else {
+        return filterGroups
+      }
+    }
+    getFilterFilters (filters) {
+      return filters.filter((f) => {
+        return this.isShowRow(f)
+      })
+    }
+    getlimitFilters (fg) {
+      const filterFilters = this.getFilterFilters(fg.filters)
+      if (filterFilters.length <= 3 || (fg.isExpand && filterFilters.length > 
3)) {
+        return filterFilters
+      } else {
+        return filterFilters.slice(0, 3)
+      }
+    }
+    toggleExpandFG (fg) {
+      this.$set(fg, 'isExpand', !fg.isExpand)
+    }
+    changeRowFilterType () {
+      
this.allTables[this.databaseIndex].tables[this.tableIndex].row_filter.type = 
this.row_filter.type
+      
this.tables[this.databaseIndex].originTables[this.tableIndex].row_filter.type = 
this.row_filter.type
+    }
+    changeFilterGroupTpye (fgIndex) {
+      
this.allTables[this.databaseIndex].tables[this.tableIndex].row_filter.filter_groups[fgIndex].type
 = this.row_filter.filter_groups[fgIndex].type
+      
this.tables[this.databaseIndex].originTables[this.tableIndex].row_filter.filter_groups[fgIndex].type
 = this.row_filter.filter_groups[fgIndex].type
+    }
+    editRowAccess (fgIndex, index, row) {
+      this.isRowAuthorEdit = true
+      this.editFilterGroupIndex = fgIndex
+      this.editRowIndex = index
+      this.rowLists = []
+      if (row.in_items.length > 0) {
+        this.rowLists.push({column_name: row.column_name, joinType: 'IN', 
items: row.in_items})
+      }
+      if (row.like_items.length > 0) {
+        this.rowLists.push({column_name: row.column_name, joinType: 'LIKE', 
items: row.like_items})
+      }
+      this.filterTotalLength = 0
+      this.overedRowValueFilters = []
+      this.rowAccessVisible = true
+    }
+    deleteRowAccess (fgIndex, index, row) {
+      let idx = index
+      this.row_filter.filter_groups[fgIndex].filters.splice(idx, 1)
+      
this.allTables[this.databaseIndex].tables[this.tableIndex].row_filter.filter_groups[fgIndex].filters.splice(idx,
 1)
+      
this.tables[this.databaseIndex].originTables[this.tableIndex].row_filter.filter_groups[fgIndex].filters.splice(idx,
 1)
+      if (!this.row_filter.filter_groups[fgIndex].filters.length) {
+        if (!this.row_filter.filter_groups[fgIndex].is_group) {
+          this.row_filter.filter_groups.splice(fgIndex, 1)
+        }
+        
this.allTables[this.databaseIndex].tables[this.tableIndex].row_filter.filter_groups.splice(fgIndex,
 1)
+        
this.tables[this.databaseIndex].originTables[this.tableIndex].row_filter.filter_groups.splice(fgIndex,
 1)
+      }
+    }
+    async deleteFG (fgIndex) {
+      await kylinConfirm(this.$t('deleteFilterGroupTips'), {confirmButtonText: 
this.$t('kylinLang.common.delete'), centerButton: true}, 
this.$t('deleteFilterGroupTitle'))
+      this.row_filter.filter_groups.splice(fgIndex, 1)
+      
this.allTables[this.databaseIndex].tables[this.tableIndex].row_filter.filter_groups.splice(fgIndex,
 1)
+      
this.tables[this.databaseIndex].originTables[this.tableIndex].row_filter.filter_groups.splice(fgIndex,
 1)
+    }
+    cancelRowAccess () {
+      this.rowAccessVisible = false
+      this.filterTotalLength = 0
+      this.overedRowValueFilters = []
+    }
+    fromRowArrToObj (rowsList, key) {
+      var len = rowsList && rowsList.length || 0
+      var obj = {}
+      for (var k = 0; k < len; k++) {
+        if (rowsList[k].items.length) {
+          obj[rowsList[k][key]] = obj[rowsList[k][key]] || []
+          obj[rowsList[k][key]] = [...obj[rowsList[k][key]], 
...rowsList[k].items]
+        }
+      }
+      return obj
+    }
+    checkFilterValues (filters) {
+      const overedRowValueFilters = []
+      filters.forEach(f => {
+        let copyRowList = objectClone(this.rowLists)
+        let index = indexOfObjWithSomeKey(copyRowList, 'column_name', 
f.column_name)
+        let newLenth = 0
+        while (index !== -1) {
+          newLenth = newLenth + copyRowList[index].items.length
+          copyRowList.splice(index, 1)
+          index = indexOfObjWithSomeKey(copyRowList, 'column_name', 
f.column_name)
+        }
+        if (f.in_items.length + f.like_items.length + newLenth > 
maxFilterAndFilterValues) { // 单个 filter 中的值的数量最多支持 100 个 (包含LIKE)
+          overedRowValueFilters.push(f.column_name + `(${f.in_items.length + 
f.like_items.length + newLenth}/${maxFilterAndFilterValues})`)
+        }
+      })
+      return overedRowValueFilters
+    }
+    checkMaxRowAccess () {
+      this.filterTotalLength = 0 // filter 总数不超过 100 个 (包含 group 中的 filter)
+      this.newFiltersLenth = 0
+      this.overedRowValueFilters = []
+      if (!this.isRowAuthorEdit) { // 新添加过滤器的入口
+        if (!this.isAddFilterForGroup) {
+          for (let fgKey in this.row_filter.filter_groups) {
+            const fg = this.row_filter.filter_groups[fgKey]
+            if (fg.is_group) continue // 添加外层过滤器,只在外层过滤器里比对列名值是否超过max
+            this.overedRowValueFilters = this.checkFilterValues(fg.filters)
+          }
+        } else {
+          const fg = this.row_filter.filter_groups[this.editFilterGroupIndex]
+          this.overedRowValueFilters = this.checkFilterValues(fg.filters)
+        }
+        if (!this.overedRowValueFilters.length) { // overedRowValueFilters 
为空说明已存在的filter没有超过max的值
+          this.row_filter.filter_groups.forEach(fg => {
+            this.filterTotalLength = this.filterTotalLength + fg.filters.length
+          })
+          const rowsObject = this.fromRowArrToObj(this.rowLists, 'column_name')
+          this.newFiltersLenth = Object.keys(rowsObject).length
+          for (let key in rowsObject) {
+            if (rowsObject[key].length > maxFilterAndFilterValues) { // 
新增的单条过滤值超过max
+              this.overedRowValueFilters.push(key + 
`(${rowsObject[key].length}/${maxFilterAndFilterValues})`)
+            }
+          }
+        }
+        return (this.filterTotalLength + this.newFiltersLenth > 
maxFilterAndFilterValues) || this.overedRowValueFilters.length > 0
+      } else { // 编辑是同一列的in 和 like 值已合并
+        const rowsObject = this.fromRowArrToObj(this.rowLists, 'column_name')
+        for (let key in rowsObject) {
+          if (rowsObject[key].length > maxFilterAndFilterValues) { // 
新增的单条过滤值超过max
+            this.overedRowValueFilters.push(key + 
`(${rowsObject[key].length}/${maxFilterAndFilterValues})`)
+          }
+        }
+        return this.overedRowValueFilters.length > 0
+      }
+    }
+    submitRowAccess () {
+      if (this.checkMaxRowAccess()) return
+      if (!this.isRowAuthorEdit) {
+        this.rowLists.forEach((row) => {
+          if (row.column_name && row.items.length) {
+            if (this.isAddFilterForGroup) { // 只在指定筛选组里合并相同维度
+              const filters = 
this.row_filter.filter_groups[this.editFilterGroupIndex].filters
+              const index = indexOfObjWithSomeKey(filters, 'column_name', 
row.column_name)
+              if (index !== -1) {
+                if (row.joinType === 'IN') {
+                  filters[index].in_items = [...filters[index].in_items, 
...row.items]
+                } else if (row.joinType === 'LIKE') {
+                  filters[index].like_items = [...filters[index].like_items, 
...row.items]
+                }
+              } else {
+                const rowObj = {column_name: row.column_name, in_items: 
row.joinType === 'IN' ? row.items : [], like_items: row.joinType === 'LIKE' ? 
row.items : []}
+                filters.push(rowObj)
+              }
+            } else { // 在筛选组外的所有筛选器合并相同维度
+              let isExistedColumn = false
+              for (let fg in this.row_filter.filter_groups) {
+                if (this.row_filter.filter_groups[fg].is_group) continue
+                const index = 
indexOfObjWithSomeKey(this.row_filter.filter_groups[fg].filters, 'column_name', 
row.column_name)
+                if (index !== -1) {
+                  if (row.joinType === 'IN') {
+                    this.row_filter.filter_groups[fg].filters[index].in_items 
= [...this.row_filter.filter_groups[fg].filters[index].in_items, ...row.items]
+                  } else if (row.joinType === 'LIKE') {
+                    
this.row_filter.filter_groups[fg].filters[index].like_items = 
[...this.row_filter.filter_groups[fg].filters[index].like_items, ...row.items]
+                  }
+                  isExistedColumn = true
+                  break
+                }
+              }
+              if (!isExistedColumn) { // 新增的列,需要新增一个filterGroups, is_group 
为false
+                const rowObj = {column_name: row.column_name, in_items: 
row.joinType === 'IN' ? row.items : [], like_items: row.joinType === 'LIKE' ? 
row.items : []}
+                this.row_filter.filter_groups.push({is_group: false, type: 
'AND', filters: [rowObj]})
+              }
+            }
+          }
+        })
+      } else {
+        this.rowLists.forEach((r) => {
+          if (r.joinType === 'IN') {
+            
this.row_filter.filter_groups[this.editFilterGroupIndex].filters[this.editRowIndex].in_items
 = r.items
+          } else if (r.joinType === 'LIKE') {
+            
this.row_filter.filter_groups[this.editFilterGroupIndex].filters[this.editRowIndex].like_items
 = r.items
+          }
+        })
+        if 
(this.row_filter.filter_groups[this.editFilterGroupIndex].filters[this.editRowIndex].in_items.length
 === 0 && 
this.row_filter.filter_groups[this.editFilterGroupIndex].filters[this.editRowIndex].like_items.length
 === 0) {
+          
this.row_filter.filter_groups[this.editFilterGroupIndex].filters.splice(this.editRowIndex,
 1)
+          if 
(!this.row_filter.filter_groups[this.editFilterGroupIndex].filters.length) {
+            if 
(!this.row_filter.filter_groups[this.editFilterGroupIndex].is_group) {
+              this.row_filter.filter_groups.splice(this.editFilterGroupIndex, 
1)
+            }
+            
this.allTables[this.databaseIndex].tables[this.tableIndex].row_filter.filter_groups.splice(this.editFilterGroupIndex,
 1)
+            
this.tables[this.databaseIndex].originTables[this.tableIndex].row_filter.filter_groups.splice(this.editFilterGroupIndex,
 1)
+          }
+        }
+      }
+      this.allTables[this.databaseIndex].tables[this.tableIndex].row_filter = 
objectClone(this.row_filter)
+      this.tables[this.databaseIndex].originTables[this.tableIndex].row_filter 
= objectClone(this.row_filter)
+      this.rowAccessVisible = false
+    }
+    addRow () {
+      this.rowLists.unshift({column_name: '', joinType: 'IN', items: []})
+    }
+    removeRow (index) {
+      this.rowLists.splice(index, 1)
+    }
+    resetRowAccess () {
+      this.showErrorDetails = false
+      this.showDetails = false
+      this.rowLists = [{column_name: '', joinType: 'IN', items: []}]
+    }
+    async loadAccessDetails (authorizedOnly) {
+      this.defaultCheckedKeys = []
+      this.allTables = []
+      this.tables = []
+      this.rows = []
+      this.columns = []
+      const response = await this.getAccessDetailsByUser({data: 
{authorized_only: authorizedOnly, project: this.projectName}, roleOrName: 
this.row.role_or_name, type: this.row.type, projectName: this.projectName})
+      const result = await handleSuccessAsync(response)
+      if (result.length) {
+        this.allTables = objectClone(result)
+        this.copyOriginTables = objectClone(result)
+        this.tableAuthorizedNum = 0
+        this.totalNum = 0
+        this.currentTableId = ''
+        this.tables = result.map((database, key) => {
+          this.tableAuthorizedNum = this.tableAuthorizedNum + 
database.authorized_table_num
+          this.totalNum = this.totalNum + database.total_table_num
+          const labelNum = this.authorizedOnly ? ` 
(${database.authorized_table_num})` : ` 
(${database.authorized_table_num}/${database.total_table_num})`
+          const originTableDatas = database.tables.map((t, i) => {
+            const id = key + '_' + i
+            if (t.authorized && !authorizedOnly) {
+              this.defaultCheckedKeys.push(id)
+            }
+            if (database.database_name + '.' + t.table_name === 
this.currentTable) {
+              this.currentTableId = id
+            }
+            return {id: id, label: t.table_name, database: 
database.database_name, authorized: t.authorized, columns: t.columns, 
row_filter: t.row_filter, totalColNum: t.total_column_num}
+          })
+          const pegedTableDatas = originTableDatas.slice(0, 
pageSizeMapping.TABLE_TREE)
+          if (pageSizeMapping.TABLE_TREE < originTableDatas.length) {
+            pegedTableDatas.push({
+              id: key + '_more',
+              label: this.$t('loadMore'),
+              class: 'load-more ksd-fs-12',
+              isMore: true
+            })
+          }
+          if (database.authorized_table_num && database.authorized_table_num 
=== database.total_table_num) {
+            this.defaultCheckedKeys.push(key + '')
+          }
+          return {
+            id: key + '',
+            label: database.database_name + labelNum,
+            class: database.authorized_table_num && 
database.authorized_table_num < database.total_table_num ? 'indeterminate-node' 
: '',
+            databaseName: database.database_name,
+            authorizedNum: database.authorized_table_num,
+            totalNum: database.total_table_num,
+            originTables: originTableDatas,
+            currentIndex: 1,
+            children: pegedTableDatas
+          }
+        })
+        this.isAllTablesAccess = this.tableAuthorizedNum === this.totalNum
+        this.$nextTick(() => {
+          if (this.currentTableId) {
+            const indexs = this.currentTableId.split('_')
+            if (indexs[1] <= pageSizeMapping.TABLE_TREE) {
+              this.handleNodeClick(this.tables[indexs[0]].children[indexs[1]])
+            } else {
+              this.handleNodeClick(this.tables[0].children[0])
+            }
+          } else {
+            this.handleNodeClick(this.tables[0].children[0])
+          }
+          this.handleLoadMoreStyle()
+        })
+      }
+    }
+    created () {
+      this.loadAccessDetails(true)
+      this.getAclPermission({project: this.projectName}).then((res) => {
+        const { data } = res.data
+        handleSuccess(res, () => {
+          this.isAuthority = data
+        })
+      }, (err) => {
+        handleError(err)
+      })
+    }
+  }
+  </script>
+  
+  <style lang="less">
+    @import '../../assets/styles/variables.less';
+    .data-permission {
+      .flex {
+        display: flex;
+        align-items: center;
+      }
+      .el-icon-ksd-info {
+        color: @text-placeholder-color;
+      }
+    }
+    .access-content {
+      .join-type {
+        width: 70px;
+      }
+      .row-list {
+        .el-col {
+          display: block !important;
+          word-break: break-all;
+          line-height: 1.4;
+          padding: 5px;
+        }
+      }
+    }
+    .row-column {
+      width: 210px;
+    }
+    .row-values-edit {
+      width: calc(~'100% - 295px');
+    }
+    .row-values-add {
+      width: calc(~'100% - 365px');
+    }
+    .row-values-edit,
+    .row-values-add {
+      .el-select__input {
+        margin-left: 12px;
+      }
+      .el-select__tags > span {
+        max-width: 100%;
+        .el-tag {
+          max-width: 100%;
+          position: relative;
+          padding-right: 16px;
+          white-space: normal;
+          word-break: break-word;
+          .el-select__tags-text {
+            max-width: 100%;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            display: inline-block;
+          }
+          .el-tag__close {
+            position: absolute;
+            right: 1px;
+            // top: 5px;
+          }
+        }
+      }
+    }
+    .author_dialog {
+      .el-dialog {
+        position: absolute;
+        left: 0;
+        right: 0;
+        margin: auto;
+        max-height: 70%;
+        overflow: hidden;
+        display: flex;
+        flex-direction: column;
+        .el-dialog__header {
+          min-height: 47px;
+        }
+        .el-dialog__body {
+          overflow: auto;
+        }
+      }
+    }
+    .a-like {
+      color: @base-color;
+      position: relative;
+      .arrow {
+        transform: rotate(90deg) scale(0.8);
+        margin-left: 3px;
+        font-size: 7px;
+        color: @base-color;
+        position: absolute;
+        top: 2px;
+      }
+    }
+    .error-msg {
+      color: @text-normal-color;
+      word-break: break-all;
+    }
+    .like-tips-block {
+      color: @text-normal-color;
+      .review-details {
+        color: @base-color;
+        cursor: pointer;
+        position: relative;
+      }
+      .el-icon-ksd-more_01-copy {
+        transform: scale(0.8);
+      }
+      .arrow {
+        transform: rotate(90deg) scale(0.8);
+        margin-left: 3px;
+        font-size: 7px;
+        color: @base-color;
+        position: absolute;
+        top: 4px;
+      }
+    }
+    .author_dialog {
+      .detail-content {
+        background-color: @base-background-color-1;
+        padding: 10px 15px;
+        box-sizing: border-box;
+        font-size: 12px;
+        color: @text-normal-color;
+      }
+    }
+  </style>
diff --git a/kystudio/src/router/routerGuard.js 
b/kystudio/src/router/routerGuard.js
index c256905682..21761a4767 100644
--- a/kystudio/src/router/routerGuard.js
+++ b/kystudio/src/router/routerGuard.js
@@ -55,8 +55,9 @@ export function bindRouterGuard (router) {
       // 判断用户是否有当前所要进入的页面权限
       let getRouteAuthority = (source) => {
         // 获取该用户所有有权限的菜单
-        const defaultMenus = getAvailableOptions('menu', { groupRole: 
store.getters.userAuthorities, projectRole: store.state.user.currentUserAccess, 
menu: 'dashboard' })
-        const adminMenus = getAvailableOptions('menu', { groupRole: 
store.getters.userAuthorities, projectRole: store.state.user.currentUserAccess, 
menu: 'project' })
+        const dataPermission = store.state.user.ext_permissions ? 
'dataPermission' : 'noDataPermission'
+        const defaultMenus = getAvailableOptions('menu', { groupRole: 
store.getters.userAuthorities, projectRole: store.state.user.currentUserAccess, 
dataPermission, menu: 'dashboard' })
+        const adminMenus = getAvailableOptions('menu', { groupRole: 
store.getters.userAuthorities, projectRole: store.state.user.currentUserAccess, 
dataPermission, menu: 'project' })
         let availableMenus = [...defaultMenus, ...adminMenus]
         let auth = to.name && availableMenus.includes(to.name.toLowerCase())
 
diff --git a/kystudio/src/service/project.js b/kystudio/src/service/project.js
index 9ba40a48bc..7c71495818 100644
--- a/kystudio/src/service/project.js
+++ b/kystudio/src/service/project.js
@@ -149,6 +149,9 @@ export default {
   getDefaultConfig () {
     return Vue.resource(apiUrl + 'projects/default_configs').get()
   },
+  changeProjectUserDataPermission (params) {
+    return Vue.resource(apiUrl + 
`access/extension/ProjectInstance/${params.projectId}`).update(params.data)
+  },
   loadExcludeTables (params) {
     return Vue.resource(apiUrl + `tables/excluded_tables`).get(params)
   },
diff --git a/kystudio/src/service/system.js b/kystudio/src/service/system.js
index ffb1ac73f3..98a5adbb61 100644
--- a/kystudio/src/service/system.js
+++ b/kystudio/src/service/system.js
@@ -38,8 +38,8 @@ export default {
   },
   // 生成诊断包相关api接口
   getDumpRemote: (para) => {
-    const { host, start, end, job_id } = para
-    return Vue.http.post(apiUrl + `system/diag?host=${host}`, { start, end, 
job_id })
+    const { host, start, end, job_id, project } = para
+    return Vue.http.post(apiUrl + `system/diag?host=${host}`, { start, end, 
job_id, project })
   },
   // 获取诊断包生成进度
   getStatusRemote: (para) => {
diff --git a/kystudio/src/service/user.js b/kystudio/src/service/user.js
index 6d59ae2bd1..8d8cd15461 100644
--- a/kystudio/src/service/user.js
+++ b/kystudio/src/service/user.js
@@ -34,7 +34,7 @@ export default {
     return Vue.resource(apiUrl + 'user/authentication').get()
   },
   userAccess: (para) => {
-    return Vue.resource(apiUrl + 
'access/permission/project_permission').get(para)
+    return Vue.resource(apiUrl + 
'access/permission/project_ext_permission').get(para)
   },
   // user goup
   addGroupsToUser: (para) => {
@@ -60,5 +60,11 @@ export default {
   },
   getAccessDetailsByUser: (projectName, roleOrName, data, type) => {
     return Vue.resource(apiUrl + `acl/${type}/${roleOrName}`).get(data)
+  },
+  getCurrentUserDataPermission: (para) => {
+    return Vue.resource(apiUrl + 
`access/global/permission/data_query/${para.username}`).get()
+  },
+  updataUserDataPermission: (para) => {
+    return Vue.resource(apiUrl + 
'access/global/permission/data_query').update(para)
   }
 }
diff --git a/kystudio/src/store/config.js b/kystudio/src/store/config.js
index 019f3371e4..3f3c378bfd 100644
--- a/kystudio/src/store/config.js
+++ b/kystudio/src/store/config.js
@@ -85,8 +85,9 @@ export default {
         const groupRole = rootGetters.userAuthorities
         const projectRole = rootState.user.currentUserAccess
         const menu = rootState.route.name.toLowerCase()
+        const dataPermission = rootState.user.ext_permissions ? 
'dataPermission' : 'noDataPermission'
 
-        return getAvailableOptions('menu', { groupRole, projectRole, menu })
+        return getAvailableOptions('menu', { groupRole, projectRole, 
dataPermission, menu })
       } else {
         return []
       }
diff --git a/kystudio/src/store/project.js b/kystudio/src/store/project.js
index a5b9a8e7d8..8241593a2b 100644
--- a/kystudio/src/store/project.js
+++ b/kystudio/src/store/project.js
@@ -322,6 +322,9 @@ export default {
     },
     [types.UPDATE_EXCLUDE_COLUMN_CONFIG]: function ({ commit }, para) {
       return api.project.updateExcludeColumnConfig(para)
+    },
+    [types.CHANGE_PROJECT_USER_DATA_PERMISSION]: function ({ commit }, para) {
+      return api.project.changeProjectUserDataPermission(para)
     }
   },
   getters: {
diff --git a/kystudio/src/store/types.js b/kystudio/src/store/types.js
index d852acf269..7ec5311e22 100644
--- a/kystudio/src/store/types.js
+++ b/kystudio/src/store/types.js
@@ -71,6 +71,7 @@ export const UPDATE_PROJECT_CONFIG = 'UPDATE_PROJECT_CONFIG'
 export const DELETE_PROJECT_CONFIG = 'DELETE_PROJECT_CONFIG'
 export const GET_DEFAULT_CONFIG = 'GET_DEFAULT_CONFIG'
 export const SAVE_DEFAULT_CONFIG_LIST = 'SAVE_DEFAULT_CONFIG_LIST'
+export const CHANGE_PROJECT_USER_DATA_PERMISSION = 
'CHANGE_PROJECT_USER_DATA_PERMISSION'
 // datasource actions mutations
 // csv 数据源
 export const VERIFY_CSV_CONN = 'VERIFY_CSV_CONN'
@@ -364,6 +365,8 @@ export const GET_CANARY_REPORT = 'GET_CANARY_REPORT'
 export const SAVE_CANARY_REPORT = 'SAVE_CANARY_REPORT'
 export const INIT_SPEC = 'INIT_SPEC'
 export const COLLECT_MESSAGE_DIRECTIVES = 'COLLECT_MESSAGE_DIRECTIVES'
+export const GET_CURRENT_USER_DATA_PERMISSION = 
'GET_CURRENT_USER_DATA_PERMISSION'
+export const UPDATE_USER_DATA_PERMISSION = 'UPDATE_USER_DATA_PERMISSION'
 // monitor actions mutations
 export const SUGGEST_MODEL = 'SUGGEST_MODEL'
 export const SAVE_SUGGEST_MODELS = 'SAVE_SUGGEST_MODELS'
diff --git a/kystudio/src/store/user.js b/kystudio/src/store/user.js
index 0f5b00bef0..fbba4e3fd4 100644
--- a/kystudio/src/store/user.js
+++ b/kystudio/src/store/user.js
@@ -11,6 +11,7 @@ export default {
     usersGroupSize: 0,
     currentUser: null,
     currentUserAccess: 'DEFAULT',
+    ext_permissions: false,
     userDetail: null,
     isShowAdminTips: !cacheLocalStorage('isHideAdminTips')
   },
@@ -27,7 +28,8 @@ export default {
       state.currentUser = result.user
     },
     [types.SAVE_CURRENT_USER_ACCESS]: function (state, result) {
-      state.currentUserAccess = result.access
+      state.currentUserAccess = result.data.permission
+      state.ext_permissions = result.data.ext_permissions[0] === 'DATA_QUERY'
     },
     [types.RESET_CURRENT_USER]: function (state) {
       state.currentUser = null
@@ -110,7 +112,7 @@ export default {
       return new Promise((resolve, reject) => {
         api.user.userAccess({project: para.project}).then((res) => {
           if (!para.not_cache) {
-            commit(types.SAVE_CURRENT_USER_ACCESS, {access: res.data.data})
+            commit(types.SAVE_CURRENT_USER_ACCESS, {data: res.data.data})
           }
           resolve(res)
         }, () => {
@@ -120,6 +122,12 @@ export default {
     },
     [types.GET_ACCESS_DETAILS_BY_USER]: function ({ commit }, para) {
       return api.user.getAccessDetailsByUser(para.projectName, 
para.roleOrName, para.data, para.type)
+    },
+    [types.GET_CURRENT_USER_DATA_PERMISSION]: function ({ commit }, para) {
+      return api.user.getCurrentUserDataPermission(para)
+    },
+    [types.UPDATE_USER_DATA_PERMISSION]: function ({ commit }, para) {
+      return api.user.updataUserDataPermission(para)
     }
   },
   getters: {
@@ -159,6 +167,9 @@ export default {
         permissions.ADMINISTRATION.value,
         permissions.MANAGEMENT.value
       ].includes(state.currentUserAccess)
+    },
+    isDataPermission (state) {
+      return state.ext_permissions
     }
   }
 }


Reply via email to