mirror of
https://github.com/veops/cmdb.git
synced 2025-08-08 16:37:03 +08:00
前后端全面升级
This commit is contained in:
72
cmdb-ui/src/modules/acl/api/app.js
Normal file
72
cmdb-ui/src/modules/acl/api/app.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function searchRole(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addRole(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/roles',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRoleById(id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRoleById(id) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function searchApp(params = {}) {
|
||||
return axios({
|
||||
url: urlPrefix + '/apps',
|
||||
method: 'GET',
|
||||
params: { ...params, page_size: 9999 }
|
||||
})
|
||||
}
|
||||
|
||||
export function addApp(data) {
|
||||
return axios({
|
||||
url: urlPrefix + '/apps',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateApp(aid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/apps/${aid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function getApp(aid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/apps/${aid}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteApp(aid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/apps/${aid}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
35
cmdb-ui/src/modules/acl/api/history.js
Normal file
35
cmdb-ui/src/modules/acl/api/history.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function searchPermissonHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/permission`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchRoleHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/role`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchResourceHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/resource`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchTriggerHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/trigger`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
136
cmdb-ui/src/modules/acl/api/permission.js
Normal file
136
cmdb-ui/src/modules/acl/api/permission.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function getResourcePerms(resourceID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources/${resourceID}/permissions`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getResourceTypePerms(typeID) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_types/${typeID}/perms`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getResourceGroupPerms(resourceGroupID) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_groups/${resourceGroupID}/permissions`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function setRoleResourcePerm(rid, resourceID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/${resourceID}/grant2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function setRoleResourceGroupPerm(rid, resourceGroupID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/${resourceGroupID}/grant`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRoleResourcePerm(rid, resourceID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/${resourceID}/revoke2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRoleResourceGroupPerm(rid, resourceGroupID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/${resourceGroupID}/revoke`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源组 清空按钮使用
|
||||
export function deleteRoleResourceGroupPerm2(rid, resourceGroupID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/${resourceGroupID}/revoke2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchPermResourceByRoleId(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function roleHasPermissionToGrant(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/roles/has_perm',
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源批量授权
|
||||
export function setBatchRoleResourcePerm(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/grant`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源组批量授权
|
||||
export function setBatchRoleResourceGroupPerm(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/batch/grant`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源批量权限回收
|
||||
export function setBatchRoleResourceRevoke(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/revoke`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源组批量授权
|
||||
export function setBatchRoleResourceGroupRevoke(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/batch/revoke`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 按资源名批量授权
|
||||
export function setBatchRoleResourceByResourceName(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/grant2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源名批量回收
|
||||
export function setBatchRoleResourceRevokeByResourceName(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/revoke2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
104
cmdb-ui/src/modules/acl/api/resource.js
Normal file
104
cmdb-ui/src/modules/acl/api/resource.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function searchResource (params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addResource (params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/resources',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateResourceById (id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteResourceById (id) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function searchResourceType (params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_types`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addResourceType (params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/resource_types',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateResourceTypeById (id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_types/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteResourceTypeById (id) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_types/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
// add resource group
|
||||
export function getResourceGroups (params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addResourceGroup (data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateResourceGroup (_id, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups/${_id}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteResourceGroup (_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups/${_id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function getResourceGroupItems (_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups/${_id}/items`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
83
cmdb-ui/src/modules/acl/api/role.js
Normal file
83
cmdb-ui/src/modules/acl/api/role.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function searchRole(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addRole(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/roles',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRoleById(id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRoleById(id, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${id}`,
|
||||
method: 'DELETE',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function addParentRole(id, otherID, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${id}/parents`,
|
||||
method: 'POST',
|
||||
data: { ...data, parent_id: otherID }
|
||||
})
|
||||
}
|
||||
|
||||
// export function addChildRole (id, otherID, data) {
|
||||
// return axios({
|
||||
// url: urlPrefix + `/roles/${otherID}/parents`,
|
||||
// method: 'POST',
|
||||
// data: {...data, parent_id: id }
|
||||
// })
|
||||
// }
|
||||
|
||||
export function delParentRole(cid, pid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${cid}/parents`,
|
||||
method: 'DELETE',
|
||||
data: { ...data, parent_id: pid }
|
||||
})
|
||||
}
|
||||
|
||||
// export function delChildRole (pid, cid, data) {
|
||||
// return axios({
|
||||
// url: urlPrefix + `/roles/${cid}/parents`,
|
||||
// method: 'DELETE',
|
||||
// data: { data, parent_id: pid }
|
||||
// })
|
||||
// }
|
||||
|
||||
export function getUsersUnderRole(rid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/users`,
|
||||
method: 'GET',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
export function addBatchParentRole(parent_id, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${parent_id}/children`,
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
16
cmdb-ui/src/modules/acl/api/secretKey.js
Normal file
16
cmdb-ui/src/modules/acl/api/secretKey.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getSecret() {
|
||||
return axios({
|
||||
url: '/v1/acl/users/secret',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function updateSecret(data) {
|
||||
return axios({
|
||||
url: '/v1/acl/users/reset_key_secret',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
56
cmdb-ui/src/modules/acl/api/trigger.js
Normal file
56
cmdb-ui/src/modules/acl/api/trigger.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function getTriggers(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/triggers',
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addTrigger(data) {
|
||||
return axios({
|
||||
url: urlPrefix + '/triggers',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTrigger(tid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTrigger(tid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function applyTrigger(tid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}/apply`,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
export function cancelTrigger(tid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}/cancel`,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
export function patternResults(params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/triggers/resources`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
49
cmdb-ui/src/modules/acl/api/user.js
Normal file
49
cmdb-ui/src/modules/acl/api/user.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable */
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function currentUser() {
|
||||
return axios({
|
||||
url: urlPrefix + `/users/info`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getOnDutyUser() {
|
||||
return axios({
|
||||
url: urlPrefix + '/users/employee',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function searchUser(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/users`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addUser(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/users',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateUserById(id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/users/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteUserById(id) {
|
||||
return axios({
|
||||
url: urlPrefix + `/users/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
9
cmdb-ui/src/modules/acl/constants/constants.js
Normal file
9
cmdb-ui/src/modules/acl/constants/constants.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const valueTypeMap = {
|
||||
'0': '整数',
|
||||
'1': '浮点数',
|
||||
'2': '文本',
|
||||
'3': 'datetime',
|
||||
'4': 'date',
|
||||
'5': 'time',
|
||||
'6': 'json'
|
||||
}
|
6
cmdb-ui/src/modules/acl/index.js
Normal file
6
cmdb-ui/src/modules/acl/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import genAclRoutes from './router'
|
||||
|
||||
export default {
|
||||
name: 'acl',
|
||||
route: genAclRoutes
|
||||
}
|
100
cmdb-ui/src/modules/acl/router/index.js
Normal file
100
cmdb-ui/src/modules/acl/router/index.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { BasicLayout, RouteView } from '@/layouts/index'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
|
||||
const genAppRoute = ({ name }) => {
|
||||
return {
|
||||
path: `/acl/${name}`,
|
||||
component: RouteView,
|
||||
meta: { title: name, icon: 'solution', permission: [`${name}_admin`, 'acl_admin'] },
|
||||
children: [
|
||||
{
|
||||
path: `/acl/${name}/roles`,
|
||||
name: `${name}_roles_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/roles'),
|
||||
meta: { title: '角色管理', icon: 'team', keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/resources`,
|
||||
name: `${name}_resources_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/resources'),
|
||||
meta: { title: '资源管理', icon: 'credit-card', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/resource_types`,
|
||||
name: `${name}_resource_types_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/resource_types'),
|
||||
meta: { title: '资源类型', icon: 'file-text', keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/trigger`,
|
||||
name: `${name}_trigger_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/trigger'),
|
||||
meta: { title: '触发器', icon: 'clock-circle', keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/history`,
|
||||
name: `${name}_history_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/history'),
|
||||
meta: { title: '操作审计', icon: 'search', keepAlive: false }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const genAclRoutes = async () => {
|
||||
const aclApps = await searchApp()
|
||||
const routes = {
|
||||
path: '/acl',
|
||||
name: 'acl',
|
||||
component: BasicLayout,
|
||||
meta: { title: 'ACL', keepAlive: true },
|
||||
redirect: '/acl/secret_key',
|
||||
children: [
|
||||
{
|
||||
path: `/acl/secret_key`,
|
||||
name: 'acl_secret_key',
|
||||
component: () => import('../views/secretKey'),
|
||||
meta: { title: '用户密钥', icon: 'key' }
|
||||
},
|
||||
{
|
||||
path: `/acl/operate_history`,
|
||||
name: 'acl_operate_history',
|
||||
component: () => import('../views/operation_history/index.vue'),
|
||||
// meta: { title: '操作审计', icon: 'search', permission: ['acl_admin'] },
|
||||
meta: { title: '操作审计', icon: 'search' }
|
||||
},
|
||||
{
|
||||
path: `/acl/user`,
|
||||
name: 'acl_user',
|
||||
component: () => import('../views/users'),
|
||||
meta: { title: '用户管理', icon: 'user', permission: ['acl_admin'] }
|
||||
},
|
||||
{
|
||||
path: `/acl/roles`,
|
||||
name: `acl_roles`,
|
||||
component: () => import('../views/roles'),
|
||||
meta: { title: '角色管理', icon: 'team', keepAlive: true, permission: ['acl_admin'] }
|
||||
},
|
||||
{
|
||||
path: `/acl/apps`,
|
||||
name: 'acl_apps',
|
||||
component: () => import('../views/apps'),
|
||||
meta: { title: '应用管理', icon: 'appstore', permission: ['acl_admin'] }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
aclApps.apps.forEach(app => {
|
||||
if (app.name !== 'acl') {
|
||||
routes.children.push(genAppRoute(app))
|
||||
}
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
export default genAclRoutes
|
0
cmdb-ui/src/modules/acl/store/index.js
Normal file
0
cmdb-ui/src/modules/acl/store/index.js
Normal file
0
cmdb-ui/src/modules/acl/style/index.css
Normal file
0
cmdb-ui/src/modules/acl/style/index.css
Normal file
0
cmdb-ui/src/modules/acl/style/index.less
Normal file
0
cmdb-ui/src/modules/acl/style/index.less
Normal file
118
cmdb-ui/src/modules/acl/views/apps.vue
Normal file
118
cmdb-ui/src/modules/acl/views/apps.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-card title="应用管理" class="acl-app-wrapper" :style="{ '--ant-body-height': `${windowHeight - 150}px` }">
|
||||
<a-row :guttr="24">
|
||||
<a-col v-for="app in apps" :key="app.id" :span="8">
|
||||
<div style="margin: 15px">
|
||||
<a-card class="acl-app-single-card">
|
||||
<a-card-meta :title="app.name">
|
||||
<div slot="description" :title="app.description || ''">{{ app.description || '无' }}</div>
|
||||
<a-avatar style="background-color: #5dc2f1" slot="avatar">{{ app.name[0].toUpperCase() }}</a-avatar>
|
||||
</a-card-meta>
|
||||
<template class="ant-card-actions" slot="actions">
|
||||
<a-icon type="edit" @click="handleEditApp(app)" />
|
||||
<a-icon type="delete" @click="handleDeleteApp(app)" />
|
||||
</template>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<div class="acl-app-add" @click="handleCreateApp">
|
||||
<span class="acl-app-add-icon" style="">+</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<app-form ref="appForm" @refresh="loadApps"></app-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import { mapState } from 'vuex'
|
||||
import { searchApp, addApp, updateApp, deleteApp } from '@/modules/acl/api/app'
|
||||
import AppForm from './module/appForm'
|
||||
|
||||
export default {
|
||||
name: 'AclApps',
|
||||
data() {
|
||||
return {
|
||||
apps: [],
|
||||
selected: null,
|
||||
modalVisible: false,
|
||||
allUsers: [],
|
||||
permissionRoles: [],
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
components: { AppForm },
|
||||
mounted() {
|
||||
this.loadApps()
|
||||
},
|
||||
methods: {
|
||||
loadApps() {
|
||||
searchApp().then((res) => {
|
||||
this.apps = res.apps
|
||||
})
|
||||
},
|
||||
handleCreateApp() {
|
||||
this.selected = null
|
||||
this.$refs.appForm.handleEdit()
|
||||
},
|
||||
handleDeleteApp(app) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '危险操作',
|
||||
content: '确定要删除该App吗?',
|
||||
onOk() {
|
||||
deleteApp(app.id).then((res) => {
|
||||
that.$message.success(`删除成功:${app.name}`)
|
||||
that.loadApps()
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
handleEditApp(app) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.appForm.handleEdit(app)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.acl-app-wrapper {
|
||||
> .ant-card-body {
|
||||
overflow-y: auto;
|
||||
height: var(--ant-body-height);
|
||||
cursor: default;
|
||||
}
|
||||
.acl-app-single-card {
|
||||
.ant-card-meta-description > div {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px #0000004d;
|
||||
}
|
||||
}
|
||||
.acl-app-add {
|
||||
height: 141px;
|
||||
margin: 15px;
|
||||
border: 1px #e8e8e8 solid;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px #0000004d;
|
||||
}
|
||||
.acl-app-add-icon {
|
||||
font-size: 70px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: #5dc2f1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
237
cmdb-ui/src/modules/acl/views/history.vue
Normal file
237
cmdb-ui/src/modules/acl/views/history.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-card :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="权限变更">
|
||||
<permisson-history-table
|
||||
v-if="isloaded"
|
||||
:allResourceTypes="allResourceTypes"
|
||||
:allResources="allResources"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allResourceTypesMap="allResourceTypesMap"
|
||||
@loadMoreResources="loadMoreResources"
|
||||
@reloadResources="reloadResources"
|
||||
@fetchResources="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></permisson-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="角色变更">
|
||||
<role-history-table
|
||||
v-if="isloaded"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
></role-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="资源变更">
|
||||
<resource-history-table
|
||||
v-if="isloaded"
|
||||
:allResources="allResources"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allResourcesMap="allResourcesMap"
|
||||
@loadMoreResources="loadMoreResources"
|
||||
@reloadResources="reloadResources"
|
||||
@fetchResources="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></resource-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab="资源类型变更">
|
||||
<resource-type-history-table
|
||||
v-if="isloaded"
|
||||
:allResourceTypes="allResourceTypes"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allResourceTypesMap="allResourceTypesMap"
|
||||
></resource-type-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab="触发器变更">
|
||||
<trigger-history-table
|
||||
v-if="isloaded"
|
||||
:allTriggers="allTriggers"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allTriggersMap="allTriggersMap"
|
||||
:allResourceTypesMap="allResourceTypesMap"
|
||||
></trigger-history-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import permissonHistoryTable from './module/permissionHistoryTable.vue'
|
||||
import roleHistoryTable from './module/roleHistoryTable.vue'
|
||||
import resourceHistoryTable from './module/resourceHistoryTable.vue'
|
||||
import triggerHistoryTable from './module/triggerHistoryTable.vue'
|
||||
import resourceTypeHistoryTable from './module/resourceTypeHistoryTable.vue'
|
||||
import { getTriggers } from '@/modules/acl/api/trigger'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResource, searchResourceType } from '@/modules/acl/api/resource'
|
||||
export default {
|
||||
components: {
|
||||
permissonHistoryTable,
|
||||
roleHistoryTable,
|
||||
resourceHistoryTable,
|
||||
triggerHistoryTable,
|
||||
resourceTypeHistoryTable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
isloaded: false,
|
||||
resourcesNum: 0,
|
||||
resourcesPage: 1,
|
||||
allResourceTypes: [],
|
||||
allResources: [],
|
||||
allUsers: [],
|
||||
allRoles: [],
|
||||
allTriggers: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
allResourceTypesMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
allTriggersMap: new Map(),
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.initData()
|
||||
this.isloaded = true
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
try {
|
||||
this.isloaded = false
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
await this.initData()
|
||||
} finally {
|
||||
this.isloaded = true
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async initData() {
|
||||
await Promise.all([
|
||||
this.getAllRoles(),
|
||||
this.getAllUsers(),
|
||||
this.getAllResourceTypes(),
|
||||
this.getAllResources(1),
|
||||
this.getTriggers(),
|
||||
])
|
||||
},
|
||||
async getAllRoles() {
|
||||
const { roles } = await searchRole({ app_id: this.app_id, page_size: 9999 })
|
||||
const allRoles = []
|
||||
const allRolesMap = new Map()
|
||||
roles.forEach((item) => {
|
||||
allRoles.push({ [item.name]: item.id })
|
||||
allRolesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allRoles = allRoles
|
||||
this.allRolesMap = allRolesMap
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000 })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach((item) => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
},
|
||||
async getAllResourceTypes() {
|
||||
const { groups } = await searchResourceType({ app_id: this.app_id, page_size: 9999, page: 1 })
|
||||
const allResourceTypes = []
|
||||
const allResourceTypesMap = new Map()
|
||||
groups.forEach((item) => {
|
||||
allResourceTypes.push({ [item.name]: item.id })
|
||||
allResourceTypesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allResourceTypes = allResourceTypes
|
||||
this.allResourceTypesMap = allResourceTypesMap
|
||||
},
|
||||
async getAllResources(page = 1, value = undefined) {
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: page,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
const allResources = this.allResources
|
||||
const allResourcesMap = this.allResourcesMap
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
allResourcesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.allResourcesMap = allResourcesMap
|
||||
},
|
||||
reloadResources() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.allResourcesMap = new Map()
|
||||
this.getAllResources()
|
||||
},
|
||||
loadMoreResources(value) {
|
||||
if (this.allResources.length < this.resourcesNum) {
|
||||
let currentPage = this.resourcesPage
|
||||
this.getAllResources(++currentPage, value)
|
||||
this.resourcesPage = currentPage
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.getAllResources()
|
||||
},
|
||||
async fetchResources(value) {
|
||||
const allResources = []
|
||||
const allResourcesMap = new Map()
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
this.resourcesPage = 1
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
allResourcesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.allResourcesMap = allResourcesMap
|
||||
},
|
||||
async getTriggers() {
|
||||
const res = await getTriggers({ app_id: this.app_id })
|
||||
const allTriggers = []
|
||||
const allTriggersMap = new Map()
|
||||
res.forEach((item) => {
|
||||
allTriggers.push({ [item.name]: item.id })
|
||||
allTriggersMap.set(item.id, item.name)
|
||||
})
|
||||
this.allTriggers = allTriggers
|
||||
this.allTriggersMap = allTriggersMap
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
85
cmdb-ui/src/modules/acl/views/module/appForm.vue
Normal file
85
cmdb-ui/src/modules/acl/views/module/appForm.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<CustomDrawer @close="handleClose" width="500" :title="title" :visible="visible" :closable="false">
|
||||
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="应用名称">
|
||||
<a-input v-decorator="['name', { rules: [{ required: true, message: '请输入应用名称' }] }]"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述">
|
||||
<a-input v-decorator="['description', { rules: [{ required: true, message: '请输入描述' }] }]"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="AppId">
|
||||
<a-input v-decorator="['app_id', { rules: [{ required: false }] }]" :disabled="mode === 'update'"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="SecretKey">
|
||||
<a-input v-decorator="['secret_key', { rules: [{ required: false }] }]" :disabled="mode === 'update'">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input v-decorator="['id']" type="hidden" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">提交</a-button>
|
||||
</div>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { addApp, updateApp, getApp } from '@/modules/acl/api/app'
|
||||
|
||||
export default {
|
||||
name: 'AppForm',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
title: '创建应用',
|
||||
mode: 'create',
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
methods: {
|
||||
handleEdit(ele) {
|
||||
this.visible = true
|
||||
if (ele) {
|
||||
this.title = '修改应用'
|
||||
this.mode = 'update'
|
||||
console.log(ele)
|
||||
const { name, description } = ele
|
||||
getApp(ele.id).then((res) => {
|
||||
const { app_id, secret_key } = res
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({ name, description, app_id, secret_key, id: ele.id })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.mode = 'create'
|
||||
this.title = '创建应用'
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
this.form.resetFields()
|
||||
},
|
||||
async handleSubmit() {
|
||||
this.form.validateFields(async (err, values) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
if (values.id) {
|
||||
await updateApp(values.id, values).then((res) => {
|
||||
this.$message.success('修改成功!')
|
||||
})
|
||||
} else {
|
||||
await addApp(values).then((res) => {
|
||||
this.$message.success('创建成功!')
|
||||
})
|
||||
}
|
||||
this.handleClose()
|
||||
this.$emit('refresh')
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
116
cmdb-ui/src/modules/acl/views/module/pager.vue
Normal file
116
cmdb-ui/src/modules/acl/views/module/pager.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button>
|
||||
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
|
||||
<a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button>
|
||||
<a-dropdown class="dropdown" size="small" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ size }}条/页
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small"> {{ pageSize }}条/页 <a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropdownIsDisabled: false,
|
||||
prevIsDisabled: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nextIsDisabled() {
|
||||
return this.isLoading || this.total < this.pageSize
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isLoading: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === true) {
|
||||
this.dropdownIsDisabled = true
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.dropdownIsDisabled = false
|
||||
if (this.currentPage === 1) {
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.prevIsDisabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
currentPage: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === 1) {
|
||||
this.prevIsDisabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(size) {
|
||||
this.$emit('showSizeChange', size)
|
||||
},
|
||||
nextPage() {
|
||||
const pageNum = this.currentPage + 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
prevPage() {
|
||||
const pageNum = this.currentPage - 1
|
||||
this.$emit('change', pageNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row{
|
||||
margin-top: 5px;
|
||||
.left-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.right-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.page-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
268
cmdb-ui/src/modules/acl/views/module/permCollectForm.vue
Normal file
268
cmdb-ui/src/modules/acl/views/module/permCollectForm.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:title="`权限汇总: ${user.nickname}`"
|
||||
:visible="visible"
|
||||
placement="left"
|
||||
width="100%"
|
||||
@close="visible = false"
|
||||
:hasFooter="false"
|
||||
>
|
||||
<a-tabs @change="handleSwitchApp">
|
||||
<a-tab-pane
|
||||
v-for="app in displayApps"
|
||||
:key="app.id"
|
||||
:tab="app.name.slice(0, 1).toUpperCase() + app.name.slice(1)"
|
||||
>
|
||||
<a-tabs
|
||||
v-if="resourceTypes && resourceTypes.length"
|
||||
:animated="false"
|
||||
v-model="currentResourceId"
|
||||
@change="loadResource()"
|
||||
>
|
||||
<a-tab-pane v-for="rType in resourceTypes" :key="rType.id" :tab="rType.name">
|
||||
<a-spin :spinning="spinning">
|
||||
<vxe-table :max-height="`${windowHeight - 230}px`" :data="resources" ref="rTable">
|
||||
<vxe-column
|
||||
field="name"
|
||||
title="资源名"
|
||||
width="30%"
|
||||
:filters="[{ data: '' }]"
|
||||
:filter-method="filterNameMethod"
|
||||
:filter-recover-method="filterNameRecoverMethod"
|
||||
>
|
||||
<template #filter="{ $panel, column }">
|
||||
<template v-for="(option, index) in column.filters">
|
||||
<input
|
||||
class="my-input"
|
||||
type="type"
|
||||
:key="index"
|
||||
v-model="option.data"
|
||||
@input="$panel.changeOption($event, !!option.data, option)"
|
||||
@keyup.enter="$panel.confirmFilter()"
|
||||
placeholder="按回车确认筛选"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="permissions" title="权限列表" width="70%">
|
||||
<template #default="{row}">
|
||||
<a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<!-- <a-table
|
||||
:columns="tableColumns"
|
||||
:dataSource="resources"
|
||||
:rowKey="record=>record.id"
|
||||
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录` }"
|
||||
showPagination="auto"
|
||||
ref="rTable"
|
||||
size="middle">
|
||||
<div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
|
||||
<a-input
|
||||
v-ant-ref="c => searchInput = c"
|
||||
:placeholder="` ${column.title}`"
|
||||
:value="selectedKeys[0]"
|
||||
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
|
||||
@pressEnter="() => handleSearch(selectedKeys, confirm, column)"
|
||||
style="width: 188px; margin-bottom: 8px; display: block;"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="() => handleSearch(selectedKeys, confirm, column)"
|
||||
icon="search"
|
||||
size="small"
|
||||
style="width: 90px; margin-right: 8px"
|
||||
>搜索</a-button>
|
||||
<a-button
|
||||
@click="() => handleReset(clearFilters, column)"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
>重置</a-button>
|
||||
</div>
|
||||
<a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
|
||||
|
||||
<template slot="nameSearchRender" slot-scope="text">
|
||||
<span v-if="columnSearchText.name">
|
||||
<template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
|
||||
<mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
|
||||
<template v-else>{{ fragment }}</template>
|
||||
</template>
|
||||
</span>
|
||||
<template v-else>{{ text }}</template>
|
||||
</template>
|
||||
<template slot="permissions" slot-scope="record">
|
||||
<a-tag color="cyan" v-for="(r, index) in record" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</a-table> -->
|
||||
</a-spin>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { searchApp, searchRole } from '@/modules/acl/api/app'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'PermCollectForm',
|
||||
data() {
|
||||
return {
|
||||
spinning: false,
|
||||
visible: false,
|
||||
user: {},
|
||||
roles: [],
|
||||
apps: [],
|
||||
currentAppId: 0,
|
||||
currentResourceId: 0,
|
||||
resourceTypes: [],
|
||||
resourceTypePerms: [],
|
||||
resources: [],
|
||||
// tableColumns: [
|
||||
// {
|
||||
// title: '资源名',
|
||||
// dataIndex: 'name',
|
||||
// sorter: false,
|
||||
// width: 150,
|
||||
// // scopedSlots: {
|
||||
// // customRender: 'nameSearchRender',
|
||||
// // filterDropdown: 'filterDropdown',
|
||||
// // filterIcon: 'filterIcon'
|
||||
// // },
|
||||
// // onFilter: (value, record) => record.name && record.name.toLowerCase().includes(value.toLowerCase()),
|
||||
// // onFilterDropdownVisibleChange: (visible) => {
|
||||
// // if (visible) {
|
||||
// // setTimeout(() => {
|
||||
// // this.searchInput.focus()
|
||||
// // }, 0)
|
||||
// // }
|
||||
// // }
|
||||
// },
|
||||
// {
|
||||
// title: '权限列表',
|
||||
// dataIndex: 'permissions',
|
||||
// width: 300,
|
||||
// scopedSlots: { customRender: 'permissions' },
|
||||
// },
|
||||
// ],
|
||||
columnSearchText: {
|
||||
name: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
displayApps() {
|
||||
const roles = this.$store.state.user.roles.permissions
|
||||
if (roles.includes('acl_admin')) {
|
||||
return this.apps
|
||||
}
|
||||
return this.apps.filter(item => {
|
||||
if (roles.includes(`${item.name}_admin`)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadApps()
|
||||
},
|
||||
methods: {
|
||||
collect(user) {
|
||||
this.user = user
|
||||
this.visible = true
|
||||
setTimeout(() => {
|
||||
this.loadResource()
|
||||
}, 500)
|
||||
},
|
||||
handleSearch(selectedKeys, confirm, column) {
|
||||
confirm()
|
||||
this.columnSearchText[column.dataIndex] = selectedKeys[0]
|
||||
},
|
||||
handleReset(clearFilters, column) {
|
||||
clearFilters()
|
||||
this.columnSearchText[column.dataIndex] = ''
|
||||
},
|
||||
async loadRoles(_appId) {
|
||||
const res = await searchRole({ app_id: _appId, page_size: 9999, is_all: true })
|
||||
this.roles = res.roles.filter(item => item.uid)
|
||||
},
|
||||
async handleSwitchApp(appId) {
|
||||
this.currentAppId = appId
|
||||
await this.loadResourceTypes(appId)
|
||||
},
|
||||
async loadApps() {
|
||||
const res = await searchApp()
|
||||
this.apps = res.apps
|
||||
if (this.displayApps[0]) {
|
||||
this.currentAppId = this.displayApps[0].id
|
||||
await this.loadRoles(this.apps[0].id)
|
||||
await this.loadResourceTypes(this.displayApps[0].id)
|
||||
} else {
|
||||
this.$message.info('No apps!')
|
||||
}
|
||||
},
|
||||
async loadResourceTypes(appId) {
|
||||
const res = await searchResourceType({ app_id: appId, page_size: 9999 })
|
||||
this.resourceTypes = res['groups']
|
||||
this.resourceTypePerms = res['id2perms'][`${appId}`]
|
||||
this.currentResourceId = res['groups'][0] && res['groups'][0]['id']
|
||||
await this.loadResource()
|
||||
},
|
||||
async loadResource() {
|
||||
this.spinning = true
|
||||
const fil = this.roles.filter(role => role.uid === this.user.uid)
|
||||
if (!fil[0]) {
|
||||
return
|
||||
}
|
||||
const res = await searchPermResourceByRoleId(fil[0].id, {
|
||||
resource_type_id: this.currentResourceId,
|
||||
app_id: this.currentAppId,
|
||||
})
|
||||
this.resources = res['resources']
|
||||
this.spinning = false
|
||||
},
|
||||
filterNameMethod({ option, row }) {
|
||||
return row.name.toLowerCase().includes(option.data.toLowerCase())
|
||||
},
|
||||
filterNameRecoverMethod({ option }) {
|
||||
option.data = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.custom-filter-dropdown {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: rgb(255, 192, 105);
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
199
cmdb-ui/src/modules/acl/views/module/permissionForm.vue
Normal file
199
cmdb-ui/src/modules/acl/views/module/permissionForm.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="30%"
|
||||
>
|
||||
<a-form :form="form" :layout="formLayout" @submit="handleSubmit">
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="类型名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder=""
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入资源名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="描述">
|
||||
<a-textarea placeholder="请输入描述信息..." name="description" :rows="4" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="权限">
|
||||
<div :style="{ borderBottom: '1px solid #E9E9E9' }">
|
||||
<a-checkbox :indeterminate="indeterminate" @change="onCheckAllChange" :checked="checkAll">
|
||||
全选
|
||||
</a-checkbox>
|
||||
</div>
|
||||
<br />
|
||||
<a-checkbox-group :options="plainOptions" v-model="perms" @change="onPermChange" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addResourceType, updateResourceTypeById } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增资源类型',
|
||||
drawerVisible: false,
|
||||
formLayout: 'vertical',
|
||||
perms: ['1'],
|
||||
indeterminate: true,
|
||||
checkAll: false,
|
||||
plainOptions: ['1', '2'],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {
|
||||
formItemLayout() {
|
||||
const { formLayout } = this
|
||||
return formLayout === 'horizontal'
|
||||
? {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 14 },
|
||||
}
|
||||
: {}
|
||||
},
|
||||
|
||||
horizontalFormItemLayout() {
|
||||
return {
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 12 },
|
||||
}
|
||||
},
|
||||
buttonItemLayout() {
|
||||
const { formLayout } = this
|
||||
return formLayout === 'horizontal'
|
||||
? {
|
||||
wrapperCol: { span: 14, offset: 4 },
|
||||
}
|
||||
: {}
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
onPermChange(perms) {
|
||||
this.indeterminate = !!perms.length && perms.length < this.plainOptions.length
|
||||
this.checkAll = perms.length === this.plainOptions.length
|
||||
},
|
||||
onCheckAllChange(e) {
|
||||
Object.assign(this, {
|
||||
perms: e.target.checked ? this.plainOptions : [],
|
||||
indeterminate: false,
|
||||
checkAll: e.target.checked,
|
||||
})
|
||||
},
|
||||
handleCreate() {
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerVisible = true
|
||||
console.log(record)
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
values.perms = this.perms
|
||||
if (values.id) {
|
||||
this.updateResourceType(values.id, values)
|
||||
} else {
|
||||
this.createResourceType(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateResourceType(id, data) {
|
||||
updateResourceTypeById(id, data).then(res => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createResourceType(data) {
|
||||
addResourceType(data).then(res => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
// requestFailed (err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// }
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
308
cmdb-ui/src/modules/acl/views/module/permissionHistoryTable.vue
Normal file
308
cmdb-ui/src/modules/acl/views/module/permissionHistoryTable.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="permissionTableAttrList"
|
||||
@search="handleSearch"
|
||||
@expandChange="handleExpandChange"
|
||||
@searchFormReset="searchFormReset"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
:scroll-y="{ enabled: false }"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.deleted_at || row.updated_at || row.created_at }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{
|
||||
operateTypeMap.get(row.operate_type)
|
||||
}}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="rid" title="用户"></vxe-column>
|
||||
<vxe-column field="resource_type_id" title="资源类型"></vxe-column>
|
||||
<vxe-column field="resources" title="资源">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.resource_ids.length > 0">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
<span>{{ row.resource_ids[0] }}</span>
|
||||
</template>
|
||||
<a-tag color="blue" v-for="(resource, index) in row.resource_ids" :key="'resources_' + resource + index">
|
||||
{{ resource }}
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="row.group_ids.length > 0">
|
||||
<a-tag color="blue" v-for="(group, index) in row.group_ids" :key="'groups_' + group + index">
|
||||
{{ group }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="权限">
|
||||
<template #default="{ row }">
|
||||
<a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index">
|
||||
{{ perm }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchPermissonHistory } from '@/modules/acl/api/history'
|
||||
import debounce from 'lodash/debounce'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allResourceTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allResources: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allResourceTypesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
this.fetchResources = debounce(this.fetchResources, 800)
|
||||
return {
|
||||
isExpand: false,
|
||||
loading: true,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
tableData: [],
|
||||
operateTypeMap: new Map([
|
||||
['grant', '授权'],
|
||||
['revoke', '撤销'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
permissionTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers,
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'rid',
|
||||
value_type: '2',
|
||||
choice_value: this.allRoles,
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'resource_type_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResourceTypes,
|
||||
},
|
||||
{
|
||||
alias: '资源',
|
||||
is_choice: true,
|
||||
name: 'resource_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResources,
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 授权: 'grant' }, { 撤销: 'revoke' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
allResources: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.permissionTableAttrList[4].choice_value = val
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2groups, id2perms, id2resources, id2roles } = await searchPermissonHistory(
|
||||
this.handleQueryParams(queryParams)
|
||||
)
|
||||
data.forEach((item) => {
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
item.rid = id2roles[item.rid].name
|
||||
item.resource_type_id = this.allResourceTypesMap.get(item.resource_type_id)
|
||||
item.resource_ids.forEach((subItem, index) => {
|
||||
item.resource_ids[index] = id2resources[subItem].name
|
||||
})
|
||||
item.group_ids.forEach((subItem, index) => {
|
||||
item.group_ids[index] = id2groups[subItem].name
|
||||
})
|
||||
item.permission_ids.forEach((subItem, index) => {
|
||||
item.permission_ids[index] = id2perms[subItem].name
|
||||
})
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'resource_id') {
|
||||
this.$emit('loadMoreResources', value)
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.$emit('resourceClear')
|
||||
},
|
||||
fetchResources(value) {
|
||||
if (value === '') {
|
||||
this.$emit('reloadResources')
|
||||
return
|
||||
}
|
||||
this.$emit('fetchResources', value)
|
||||
},
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
148
cmdb-ui/src/modules/acl/views/module/resourceBatchPerm.vue
Normal file
148
cmdb-ui/src/modules/acl/views/module/resourceBatchPerm.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
title="便捷授权"
|
||||
width="500px"
|
||||
:maskClosable="false"
|
||||
:closable="true"
|
||||
:visible="visible"
|
||||
@close="handleClose"
|
||||
>
|
||||
<a-form :form="form">
|
||||
<a-form-item>
|
||||
<div slot="label" style="display: inline-block">
|
||||
<span>角色列表</span>
|
||||
<a-divider type="vertical" />
|
||||
<a-switch
|
||||
style="display: inline-block"
|
||||
checked-children="用户"
|
||||
un-checked-children="虚拟"
|
||||
@change="handleRoleTypeChange"
|
||||
v-model="roleType"
|
||||
/>
|
||||
</div>
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
v-decorator="['roleIdList', { rules: [{ required: true, message: '请选择角色名称' }] }]"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="请选择角色名称,可多选!"
|
||||
>
|
||||
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="权限列表">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
name="permName"
|
||||
v-decorator="['permName', { rules: [{ required: true, message: '请选择权限' }] }]"
|
||||
multiple
|
||||
placeholder="请选择权限,可多选!"
|
||||
>
|
||||
<el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源名">
|
||||
<a-textarea
|
||||
v-decorator="['resource_names', { rules: [{ required: true, message: '请输入资源名,换行分隔' }] }]"
|
||||
:autoSize="{ minRows: 4 }"
|
||||
placeholder="请输入资源名,换行分隔"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleRevoke" type="danger" ghost>权限回收</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">授权</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import {
|
||||
getResourceTypePerms,
|
||||
setBatchRoleResourceByResourceName,
|
||||
setBatchRoleResourceRevokeByResourceName,
|
||||
} from '@/modules/acl/api/permission'
|
||||
export default {
|
||||
name: 'ResourceBatchPerm',
|
||||
components: { ElSelect: Select, ElOption: Option },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
resource_type_id: null,
|
||||
roleType: true,
|
||||
allRoles: [],
|
||||
allPerms: [],
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
methods: {
|
||||
open(currentTypeId) {
|
||||
this.visible = true
|
||||
this.resource_type_id = currentTypeId
|
||||
this.loadRoles(1)
|
||||
this.loadPerm(currentTypeId)
|
||||
},
|
||||
handleClose() {
|
||||
this.form.resetFields()
|
||||
this.roleType = true
|
||||
this.visible = false
|
||||
},
|
||||
handleRoleTypeChange(target) {
|
||||
this.loadRoles(Number(target))
|
||||
},
|
||||
loadRoles(isUserRole) {
|
||||
searchRole({ page_size: 9999, app_id: this.$route.name.split('_')[0], user_role: isUserRole }).then(res => {
|
||||
this.allRoles = res.roles
|
||||
})
|
||||
},
|
||||
loadPerm(resourceTypeId) {
|
||||
getResourceTypePerms(resourceTypeId).then(res => {
|
||||
this.allPerms = res
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log(values)
|
||||
values.roleIdList.forEach(roleId => {
|
||||
setBatchRoleResourceByResourceName(roleId, {
|
||||
resource_names: values.resource_names.split('\n'),
|
||||
perms: values.permName,
|
||||
resource_type_id: this.resource_type_id,
|
||||
}).then(res => {
|
||||
this.$message.success('授权成功')
|
||||
this.form.resetFields()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleRevoke() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log(values)
|
||||
values.roleIdList.forEach(roleId => {
|
||||
setBatchRoleResourceRevokeByResourceName(roleId, {
|
||||
resource_names: values.resource_names.split('\n'),
|
||||
perms: values.permName,
|
||||
resource_type_id: this.resource_type_id,
|
||||
}).then(res => {
|
||||
this.$message.success('权限回收成功')
|
||||
this.form.resetFields()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
151
cmdb-ui/src/modules/acl/views/module/resourceForm.vue
Normal file
151
cmdb-ui/src/modules/acl/views/module/resourceForm.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="30%"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="资源名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder=""
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入资源名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="资源类型">
|
||||
<a-select v-model="selectedTypeId">
|
||||
<a-select-option v-for="type in allTypes" :key="type.id">{{ type.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="是否组">
|
||||
<a-radio-group v-model="isGroup">
|
||||
<a-radio :value="true"> 是 </a-radio>
|
||||
<a-radio :value="false"> 否 </a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addResource, searchResourceType, addResourceGroup } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增资源',
|
||||
drawerVisible: false,
|
||||
allTypes: [],
|
||||
isGroup: false,
|
||||
selectedTypeId: 0,
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getAllResourceTypes()
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
getAllResourceTypes() {
|
||||
searchResourceType({ page_size: 9999, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.allTypes = res.groups
|
||||
})
|
||||
},
|
||||
handleCreate(defaultType) {
|
||||
this.drawerVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.selectedTypeId = defaultType.id
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
this.$emit('fresh')
|
||||
},
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
values.type_id = this.selectedTypeId
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
if (values.id) {
|
||||
this.$message.error('错误提示')
|
||||
} else {
|
||||
this.createResource(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
createResource(data) {
|
||||
if (!this.isGroup) {
|
||||
addResource(data).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
} else {
|
||||
addResourceGroup(data).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
},
|
||||
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function (newValue, oldValue) {
|
||||
this.getAllResourceTypes()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
35
cmdb-ui/src/modules/acl/views/module/resourceGroupMember.vue
Normal file
35
cmdb-ui/src/modules/acl/views/module/resourceGroupMember.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<a-modal v-model="visible" :title="`组成员:${editRecord.name}`" :width="800" :footer="null">
|
||||
<div :style="{ maxHeight: '500px', overflow: 'auto' }">
|
||||
<a-tag :style="{ marginBottom: '5px' }" v-for="mem in members" :key="mem.name">
|
||||
{{ mem.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import { getResourceGroupItems } from '@/modules/acl/api/resource'
|
||||
export default {
|
||||
name: 'ResourceGroupMember',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
editRecord: {},
|
||||
members: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEdit(record) {
|
||||
this.visible = true
|
||||
this.editRecord = record
|
||||
this.loadMembers(record.id)
|
||||
},
|
||||
loadMembers(_id) {
|
||||
getResourceGroupItems(_id).then((res) => {
|
||||
this.members = res
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
241
cmdb-ui/src/modules/acl/views/module/resourceGroupModal.vue
Normal file
241
cmdb-ui/src/modules/acl/views/module/resourceGroupModal.vue
Normal file
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<a-modal v-model="visible" :title="`成员管理:${editRecord.name}`" @ok="handleSubmit" :width="690">
|
||||
<!-- <CustomTransfer
|
||||
ref="customTransfer"
|
||||
:show-search="true"
|
||||
:data-source="resources"
|
||||
:filter-option="filterOption"
|
||||
:target-keys="targetKeys"
|
||||
:render="(item) => item.title"
|
||||
@change="handleChange"
|
||||
@search="handleSearch"
|
||||
@selectChange="selectChange"
|
||||
>
|
||||
</CustomTransfer> -->
|
||||
<a-transfer
|
||||
:dataSource="resources"
|
||||
:showSearch="true"
|
||||
:listStyle="{
|
||||
width: '300px',
|
||||
height: '450px',
|
||||
}"
|
||||
:titles="[]"
|
||||
:render="(item) => item.title"
|
||||
:targetKeys="targetKeys"
|
||||
@change="handleChange"
|
||||
@selectChange="selectChange"
|
||||
:selectedKeys="selectedKeys"
|
||||
>
|
||||
<span slot="notFoundContent">暂无数据</span>
|
||||
<template slot="children" slot-scope="{ props: { direction, filteredItems } }">
|
||||
<div class="ant-transfer-list-content" v-if="direction === 'right'">
|
||||
<div
|
||||
@dblclick="(e) => changeSingleItem(e, item)"
|
||||
v-for="item in filteredItems"
|
||||
:key="item.key"
|
||||
:style="{ height: '32px' }"
|
||||
>
|
||||
<li
|
||||
:class="
|
||||
`ant-transfer-list-content-item ${
|
||||
selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
|
||||
}`
|
||||
"
|
||||
style="padding:0 12px 0 12px;position:relative"
|
||||
@click="setSelectedKeys(item)"
|
||||
>
|
||||
<div :class="`ant-transfer-list-content-item-text`" style="display:inline">
|
||||
{{ item.title }}
|
||||
<div class="ant-transfer-list-icon" @click="(e) => changeSingleItem(e, item)">
|
||||
<a-icon type="left" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="direction === 'left'" class="ant-transfer-list-content">
|
||||
<div
|
||||
@dblclick="(e) => changeSingleItem(e, item)"
|
||||
v-for="item in filteredItems"
|
||||
:key="item.key"
|
||||
:style="{ height: '32px' }"
|
||||
>
|
||||
<li
|
||||
:class="
|
||||
`ant-transfer-list-content-item ${
|
||||
selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
|
||||
}`
|
||||
"
|
||||
style="padding:0 12px 0 12px;position:relative"
|
||||
@click="setSelectedKeys(item)"
|
||||
>
|
||||
<div class="ant-transfer-list-content-item-text" style="display:inline">
|
||||
{{ item.title }}
|
||||
<div @click="(e) => changeSingleItem(e, item)" class="ant-transfer-list-icon">
|
||||
<a-icon type="right" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-transfer>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { updateResourceGroup, searchResource, getResourceGroupItems } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceGroupModal',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
editRecord: {},
|
||||
resources: [],
|
||||
targetKeys: [],
|
||||
selectedKeys: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
const items = this.targetKeys.map((key) => {
|
||||
return this.resources.filter((item) => item.name === key)[0]['id']
|
||||
})
|
||||
updateResourceGroup(this.editRecord['id'], { items: items.join(',') }).then(() => {
|
||||
this.visible = false
|
||||
this.$message.success('更新成功!')
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
// filterOption(inputValue, option) {
|
||||
// return option.description.indexOf(inputValue) > -1
|
||||
// },
|
||||
// handleChange(targetKeys, direction, moveKeys) {
|
||||
// console.log(JSON.parse(JSON.stringify(targetKeys)))
|
||||
// this.targetKeys = targetKeys
|
||||
// },
|
||||
// handleSearch(dir, value) {
|
||||
// console.log('search:', dir, value)
|
||||
// },
|
||||
async handleEdit(record) {
|
||||
this.editRecord = record
|
||||
this.visible = true
|
||||
this.selectedKeys = []
|
||||
await this.loadChildren(record.id)
|
||||
await this.loadResource()
|
||||
},
|
||||
loadChildren(_id) {
|
||||
getResourceGroupItems(_id).then((res) => {
|
||||
this.targetKeys = res.map((item) => item.name)
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
loadResource() {
|
||||
const params = {
|
||||
app_id: this.editRecord['app_id'],
|
||||
resource_type_id: this.editRecord['resource_type_id'],
|
||||
page_size: 9999,
|
||||
}
|
||||
searchResource(params).then((res) => {
|
||||
this.resources = res['resources'].map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
key: item.name,
|
||||
description: item.name,
|
||||
title: item.name,
|
||||
}
|
||||
})
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
setSelectedKeys(item) {
|
||||
const idx = this.selectedKeys.findIndex((key) => key === item.key)
|
||||
if (idx > -1) {
|
||||
this.selectedKeys.splice(idx, 1)
|
||||
} else {
|
||||
this.selectedKeys.push(item.key)
|
||||
}
|
||||
},
|
||||
changeSingleItem(e, item) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
const idx = this.targetKeys.findIndex((key) => key === item.key)
|
||||
if (idx > -1) {
|
||||
this.targetKeys.splice(idx, 1)
|
||||
} else {
|
||||
this.targetKeys.push(item.key)
|
||||
}
|
||||
},
|
||||
handleChange(targetKeys, direction, moveKeys) {
|
||||
const _selectedKeys = _.cloneDeep(this.selectedKeys)
|
||||
moveKeys.forEach((key) => {
|
||||
const idx = _selectedKeys.findIndex((selected) => selected === key)
|
||||
if (idx > -1) {
|
||||
_selectedKeys.splice(idx, 1)
|
||||
}
|
||||
})
|
||||
this.selectedKeys = _.cloneDeep(_selectedKeys)
|
||||
this.targetKeys = targetKeys
|
||||
},
|
||||
selectChange(sourceSelectedKeys, targetSelectedKeys) {
|
||||
const _selectedKeys = _.cloneDeep(this.selectedKeys)
|
||||
const list = [
|
||||
{ data: sourceSelectedKeys, name: 'source' },
|
||||
{ data: targetSelectedKeys, name: 'target' },
|
||||
]
|
||||
list.forEach((item) => {
|
||||
if (!item.data.__ob__) {
|
||||
if (item.data.length) {
|
||||
item.data.forEach((key) => {
|
||||
const idx = _selectedKeys.findIndex((selected) => selected === key)
|
||||
if (idx > -1) {
|
||||
} else {
|
||||
_selectedKeys.push(key)
|
||||
}
|
||||
})
|
||||
this.selectedKeys = _.cloneDeep(_selectedKeys)
|
||||
} else {
|
||||
let _list = []
|
||||
if (item.name === 'source') {
|
||||
_list = _selectedKeys.filter((key) => this.targetKeys.includes(key))
|
||||
} else {
|
||||
_list = _selectedKeys.filter((key) => !this.targetKeys.includes(key))
|
||||
}
|
||||
this.selectedKeys = _list
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ant-transfer-list-body-customize-wrapper {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
max-height: calc(100% - 44px);
|
||||
}
|
||||
.ant-transfer-list-content-item {
|
||||
transition: all 0.3s;
|
||||
.ant-transfer-list-icon {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
display: none;
|
||||
&:hover {
|
||||
color: #1f78d1;
|
||||
}
|
||||
}
|
||||
&:hover .ant-transfer-list-icon {
|
||||
display: inline;
|
||||
background-color: #c0eaff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
</style>
|
326
cmdb-ui/src/modules/acl/views/module/resourceHistoryTable.vue
Normal file
326
cmdb-ui/src/modules/acl/views/module/resourceHistoryTable.vue
Normal file
@@ -0,0 +1,326 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="组"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
size="small"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
resizable
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" title="资源名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allResources: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allResourcesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
tableData: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers,
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
|
||||
},
|
||||
{
|
||||
alias: '资源名',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResources,
|
||||
},
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'resource',
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
allResources: {
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.resourceTableAttrList[3].choice_value = val
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach((item) => {
|
||||
item.originResource_ids = item?.extra?.resource_ids?.origin.map((subItem) =>
|
||||
this.allResourcesMap.get(Number(subItem))
|
||||
)
|
||||
item.currentResource_ids = item?.extra?.resource_ids?.current.map((subItem) =>
|
||||
this.allResourcesMap.get(Number(subItem))
|
||||
)
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'link_id') {
|
||||
this.$emit('loadMoreResources', value)
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.$emit('resourceClear')
|
||||
},
|
||||
fetchResources(value) {
|
||||
if (value === '') {
|
||||
this.$emit('reloadResources')
|
||||
return
|
||||
}
|
||||
this.$emit('fetchResources', value)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: this.checked ? 'resource_group' : 'resource' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: this.checked ? 'resource_group' : 'resource',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'resource_group' : 'resource'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建资源:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
item.description += ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
} else {
|
||||
item.description += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
const originResource_ids = item.originResource_ids
|
||||
const currentResource_ids = item.currentResource_ids
|
||||
if (!_.isEqual(originResource_ids, currentResource_ids)) {
|
||||
if (originResource_ids.length === 0) {
|
||||
const str = ` 【 resource_ids : 新增 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 resource_ids : 由 ${originResource_ids} 改为 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
item.description = `删除资源:${item.origin.name}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
272
cmdb-ui/src/modules/acl/views/module/resourcePermForm.vue
Normal file
272
cmdb-ui/src/modules/acl/views/module/resourcePermForm.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
width="80%"
|
||||
placement="left"
|
||||
@close="() => (drawerVisible = false)"
|
||||
:hasFooter="false"
|
||||
>
|
||||
<vxe-table :max-height="`${windowHeight - 150}px`" :data="resPerms" ref="rTable">
|
||||
<vxe-column
|
||||
field="name"
|
||||
title="角色名"
|
||||
width="20%"
|
||||
:filters="[{ data: '' }]"
|
||||
:filter-method="filterNameMethod"
|
||||
:filter-recover-method="filterNameRecoverMethod"
|
||||
>
|
||||
<template #filter="{ $panel, column }">
|
||||
<template v-for="(option, index) in column.filters">
|
||||
<input
|
||||
class="my-input"
|
||||
type="type"
|
||||
:key="index"
|
||||
v-model="option.data"
|
||||
@input="$panel.changeOption($event, !!option.data, option)"
|
||||
@keyup.enter="$panel.confirmFilter()"
|
||||
placeholder="按回车确认筛选"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="users" title="下属用户" width="35%">
|
||||
<template #default="{row}">
|
||||
<a-tag color="green" v-for="user in row.users" :key="user.nickname">
|
||||
{{ user.nickname }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="perms" title="权限列表" width="35%">
|
||||
<template #default="{row}">
|
||||
<a-tag
|
||||
closable
|
||||
color="cyan"
|
||||
v-for="perm in row.perms"
|
||||
:key="perm.name"
|
||||
@close="deletePerm(perm.rid, perm.name)"
|
||||
>
|
||||
{{ perm.name }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate" title="批量操作">
|
||||
<template #default="{row}">
|
||||
<a-button size="small" type="danger" @click="handleClearAll(row)">
|
||||
清空
|
||||
</a-button>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<!-- <a-table
|
||||
:columns="columns"
|
||||
:dataSource="resPerms"
|
||||
:rowKey="record => record.name"
|
||||
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录` }"
|
||||
showPagination="auto"
|
||||
ref="rTable"
|
||||
size="middle"
|
||||
>
|
||||
<div slot="perms" slot-scope="text">
|
||||
<a-tag closable color="cyan" v-for="perm in text" :key="perm.name" @close="deletePerm(perm.rid, perm.name)">
|
||||
{{ perm.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div slot="users" slot-scope="text">
|
||||
<a-tag color="green" v-for="user in text" :key="user.nickname">
|
||||
{{ user.nickname }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div slot="operate" slot-scope="text">
|
||||
<a-button size="small" type="danger" @click="handleClearAll(text)">
|
||||
清空
|
||||
</a-button>
|
||||
</div>
|
||||
</a-table> -->
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {
|
||||
getResourceTypePerms,
|
||||
getResourcePerms,
|
||||
deleteRoleResourcePerm,
|
||||
getResourceGroupPerms,
|
||||
deleteRoleResourceGroupPerm,
|
||||
deleteRoleResourceGroupPerm2,
|
||||
} from '@/modules/acl/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
data() {
|
||||
return {
|
||||
isGroup: false,
|
||||
drawerTitle: '权限列表',
|
||||
drawerVisible: false,
|
||||
record: null,
|
||||
allPerms: [],
|
||||
resPerms: [],
|
||||
roleID: null,
|
||||
// columns: [
|
||||
// {
|
||||
// title: '角色名',
|
||||
// dataIndex: 'name',
|
||||
// sorter: false,
|
||||
// width: 50,
|
||||
// },
|
||||
// {
|
||||
// title: '下属用户',
|
||||
// dataIndex: 'users',
|
||||
// sorter: false,
|
||||
// width: 150,
|
||||
// scopedSlots: { customRender: 'users' },
|
||||
// },
|
||||
// {
|
||||
// title: '权限列表',
|
||||
// dataIndex: 'perms',
|
||||
// sorter: false,
|
||||
// width: 150,
|
||||
// scopedSlots: { customRender: 'perms' },
|
||||
// },
|
||||
// {
|
||||
// title: '批量操作',
|
||||
// sorter: false,
|
||||
// width: 50,
|
||||
// scopedSlots: { customRender: 'operate' },
|
||||
// },
|
||||
// ],
|
||||
childrenDrawer: false,
|
||||
allRoles: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
methods: {
|
||||
handlePerm(record, isGroup) {
|
||||
this.isGroup = isGroup
|
||||
this.drawerVisible = true
|
||||
this.record = record
|
||||
this.getResPerms(record.id)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.getAllPerms(record.resource_type_id)
|
||||
})
|
||||
},
|
||||
handleClearAll(text) {
|
||||
if (this.isGroup) {
|
||||
deleteRoleResourceGroupPerm2(text.perms[0].rid, this.record.id, {
|
||||
perms: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success('删除成功')
|
||||
this.$nextTick(() => {
|
||||
this.getResPerms(this.record.id)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
deleteRoleResourcePerm(text.perms[0].rid, this.record.id, {
|
||||
perms: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success('删除成功')
|
||||
// for (let i = 0; i < this.resPerms.length; i++) {
|
||||
// if (this.resPerms[i].name === text.name) {
|
||||
// this.resPerms[i].perms = []
|
||||
// }
|
||||
// }
|
||||
this.$nextTick(() => {
|
||||
this.getResPerms(this.record.id)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
getResPerms(resId) {
|
||||
if (!this.isGroup) {
|
||||
getResourcePerms(resId).then((res) => {
|
||||
const perms = []
|
||||
for (const key in res) {
|
||||
perms.push({ name: key, perms: res[key].perms, users: res[key].users })
|
||||
}
|
||||
this.resPerms = perms
|
||||
})
|
||||
} else {
|
||||
getResourceGroupPerms(resId).then((res) => {
|
||||
const perms = []
|
||||
for (const key in res) {
|
||||
perms.push({ name: key, perms: res[key].perms, users: res[key].users })
|
||||
}
|
||||
this.resPerms = perms
|
||||
})
|
||||
}
|
||||
},
|
||||
getAllPerms(resTypeId) {
|
||||
getResourceTypePerms(resTypeId).then((res) => {
|
||||
this.allPerms = res
|
||||
})
|
||||
},
|
||||
deletePerm(roleID, permName) {
|
||||
if (!this.isGroup) {
|
||||
deleteRoleResourcePerm(roleID, this.record.id, {
|
||||
perms: [permName],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
} else {
|
||||
deleteRoleResourceGroupPerm(roleID, this.record.id, {
|
||||
perms: [permName],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
},
|
||||
handleCancel(e) {
|
||||
this.drawerVisible = false
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
filterNameMethod({ option, row }) {
|
||||
return row.name.toLowerCase().includes(option.data.toLowerCase())
|
||||
},
|
||||
filterNameRecoverMethod({ option }) {
|
||||
option.data = ''
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
216
cmdb-ui/src/modules/acl/views/module/resourcePermManageForm.vue
Normal file
216
cmdb-ui/src/modules/acl/views/module/resourcePermManageForm.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<CustomDrawer :title="title" width="500px" :closable="false" :visible="visible" @close="closeForm">
|
||||
<a-form :form="form">
|
||||
<a-form-item>
|
||||
<div slot="label" style="display: inline-block">
|
||||
<span>角色列表</span>
|
||||
<a-divider type="vertical" />
|
||||
<a-switch
|
||||
style="display: inline-block"
|
||||
checked-children="用户"
|
||||
un-checked-children="虚拟"
|
||||
@change="handleRoleTypeChange"
|
||||
/>
|
||||
</div>
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
v-decorator="['roleIdList', { rules: [{ required: true, message: '请选择角色名称' }] }]"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="请选择角色名称,可多选!"
|
||||
>
|
||||
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="权限列表">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
name="permName"
|
||||
v-decorator="['permName', { rules: [{ required: true, message: '请选择权限' }] }]"
|
||||
multiple
|
||||
placeholder="请选择权限,可多选!"
|
||||
>
|
||||
<el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="closeForm">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import {
|
||||
getResourceTypePerms,
|
||||
setRoleResourcePerm,
|
||||
setRoleResourceGroupPerm,
|
||||
setBatchRoleResourcePerm,
|
||||
setBatchRoleResourceGroupPerm,
|
||||
setBatchRoleResourceRevoke,
|
||||
setBatchRoleResourceGroupRevoke,
|
||||
} from '@/modules/acl/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'ResourcePermManageForm',
|
||||
components: { ElSelect: Select, ElOption: Option },
|
||||
data() {
|
||||
return {
|
||||
isGroup: false,
|
||||
allRoles: [],
|
||||
allPerms: [],
|
||||
visible: false,
|
||||
instance: {} || [], // 当前对象or批量授权的数组
|
||||
type: 'grant', // grant or revoke
|
||||
title: '',
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
groupTypeMessage: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadRoles(1)
|
||||
},
|
||||
methods: {
|
||||
handleRoleTypeChange(target) {
|
||||
if (!target) {
|
||||
this.loadRoles(1)
|
||||
} else {
|
||||
this.loadRoles(0)
|
||||
}
|
||||
},
|
||||
loadRoles(isUserRole) {
|
||||
searchRole({ page_size: 9999, app_id: this.$route.name.split('_')[0], user_role: isUserRole }).then((res) => {
|
||||
this.allRoles = res.roles
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
loadPerm(resourceTypeId) {
|
||||
getResourceTypePerms(resourceTypeId).then((res) => {
|
||||
this.allPerms = res
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
closeForm() {
|
||||
this.visible = false
|
||||
this.form.resetFields()
|
||||
this.$emit('close')
|
||||
},
|
||||
editPerm(record, isGroup, type = 'grant') {
|
||||
this.isGroup = isGroup
|
||||
this.visible = true
|
||||
this.instance = record
|
||||
this.type = type
|
||||
if (Array.isArray(record)) {
|
||||
this.loadPerm(record[0]['resource_type_id'])
|
||||
this.title = `${type === 'grant' ? '批量授权' : '批量权限回收'}`
|
||||
} else {
|
||||
this.title = `添加授权:${record.name}`
|
||||
this.loadPerm(record['resource_type_id'])
|
||||
}
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
values.roleIdList.forEach((roleId) => {
|
||||
const params = { perms: values.permName, app_id: this.$route.name.split('_')[0] }
|
||||
this.loading = true
|
||||
if (!this.isGroup) {
|
||||
if (Array.isArray(this.instance)) {
|
||||
// const promises = this.instance.map(item => {
|
||||
// return setRoleResourcePerm(roleId, item.id, params)
|
||||
// })
|
||||
// Promise.all(promises).then(() => {
|
||||
// this.$message.success('添加授权成功')
|
||||
// })
|
||||
if (this.type === 'grant') {
|
||||
setBatchRoleResourcePerm(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) })
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
setBatchRoleResourceRevoke(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) })
|
||||
.then((res) => {
|
||||
this.$message.success('批量权限回收成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setRoleResourcePerm(roleId, this.instance.id, params)
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(this.instance)) {
|
||||
// const promises = this.instance.map(item => {
|
||||
// return setRoleResourceGroupPerm(roleId, item.id, params)
|
||||
// })
|
||||
// Promise.all(promises).then(() => {
|
||||
// this.$message.success('添加授权成功')
|
||||
// })
|
||||
if (this.type === 'grant') {
|
||||
setBatchRoleResourceGroupPerm(roleId, { ...params, group_ids: this.instance.map((a) => a.id) })
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
setBatchRoleResourceGroupRevoke(roleId, {
|
||||
...params,
|
||||
group_ids: this.instance.map((a) => a.id),
|
||||
})
|
||||
.then((res) => {
|
||||
this.$message.success('批量权限回收成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setRoleResourceGroupPerm(roleId, this.instance.id, params)
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
160
cmdb-ui/src/modules/acl/views/module/resourceTypeForm.vue
Normal file
160
cmdb-ui/src/modules/acl/views/module/resourceTypeForm.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="500px"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="类型名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder="类型名称"
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入类型名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="描述">
|
||||
<a-textarea
|
||||
placeholder="请输入描述信息..."
|
||||
name="description"
|
||||
:rows="4"
|
||||
v-decorator="['description', { rules: [] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="权限">
|
||||
<a-select mode="tags" v-model="perms" style="width: 100%" placeholder="请输入权限名..."> </a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addResourceType, updateResourceTypeById } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增资源类型',
|
||||
drawerVisible: false,
|
||||
perms: [],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
handleCreate() {
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.perms = []
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerVisible = true
|
||||
console.log(record)
|
||||
this.perms = record.perms
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
values.perms = this.perms
|
||||
if (values.id) {
|
||||
this.updateResourceType(values.id, values)
|
||||
} else {
|
||||
this.createResourceType(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateResourceType(id, data) {
|
||||
updateResourceTypeById(id, data).then(res => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createResourceType(data) {
|
||||
addResourceType(data).then(res => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
// requestFailed (err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// }
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
size="small"
|
||||
:data="tableData"
|
||||
resizable
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" title="资源类型名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allResourceTypes: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allResourceTypesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
tableData: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
]
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResourceTypes
|
||||
}
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除']
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'resource_type',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: 'resource_type' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'resource_type',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.changeDescription = `新增资源类型:${item.current.name}\n描述:${item.current.description}\n权限:${item.extra.permission_ids.current}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null || oldVal === '') {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
}
|
||||
}
|
||||
}
|
||||
const currentPerms = item.extra.permission_ids.current
|
||||
const originPerms = item.extra.permission_ids.origin
|
||||
if (!_.isEqual(currentPerms, originPerms)) item.changeDescription += ` 【 permission_ids : 由 ${originPerms} 改为 ${currentPerms} 】 `
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
item.changeDescription = `删除资源类型:${item.origin.name}\n描述:${item.origin.description}\n权限:${item.extra.permission_ids.origin}`
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
292
cmdb-ui/src/modules/acl/views/module/resourceUserForm.vue
Normal file
292
cmdb-ui/src/modules/acl/views/module/resourceUserForm.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="800px"
|
||||
placement="left"
|
||||
title="资源列表"
|
||||
@close="handleCancel"
|
||||
:visible="visible"
|
||||
:hasFooter="false"
|
||||
>
|
||||
<a-form-item label="资源类型" :label-col="{ span: 2 }" :wrapper-col="{ span: 14 }">
|
||||
<a-select v-model="typeSelected" style="width:100%" @change="refresh">
|
||||
<a-select-option v-for="type in resourceTypes" :value="type.id" :key="type.id">{{ type.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<vxe-table :max-height="`${windowHeight - 180}px`" :data="records" ref="rTable">
|
||||
<vxe-column
|
||||
field="name"
|
||||
title="资源名"
|
||||
width="30%"
|
||||
:filters="[{ data: '' }]"
|
||||
:filter-method="filterNameMethod"
|
||||
:filter-recover-method="filterNameRecoverMethod"
|
||||
>
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-tooltip title="复制资源名">
|
||||
<a-icon @click="copyResourceName" class="resource-user-form-copy" theme="filled" type="copy" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #filter="{ $panel, column }">
|
||||
<template v-for="(option, index) in column.filters">
|
||||
<input
|
||||
class="my-input"
|
||||
type="type"
|
||||
:key="index"
|
||||
v-model="option.data"
|
||||
@input="$panel.changeOption($event, !!option.data, option)"
|
||||
@keyup.enter="$panel.confirmFilter()"
|
||||
placeholder="按回车确认筛选"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="permissions" title="权限列表" width="70%">
|
||||
<template #default="{row}">
|
||||
<a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<!-- <a-table
|
||||
:columns="columns"
|
||||
:dataSource="records"
|
||||
:rowKey="record => record.id"
|
||||
:pagination="false"
|
||||
ref="rTable"
|
||||
size="middle"
|
||||
:scroll="{ y: 300 }"
|
||||
> -->
|
||||
<!-- <div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
|
||||
<a-input
|
||||
v-ant-ref="c => searchInput = c"
|
||||
:placeholder="` ${column.title}`"
|
||||
:value="selectedKeys[0]"
|
||||
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
|
||||
@pressEnter="() => handleSearch(selectedKeys, confirm, column)"
|
||||
style="width: 188px; margin-bottom: 8px; display: block;"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="() => handleSearch(selectedKeys, confirm, column)"
|
||||
icon="search"
|
||||
size="small"
|
||||
style="width: 90px; margin-right: 8px"
|
||||
>搜索</a-button>
|
||||
<a-button
|
||||
@click="() => handleReset(clearFilters, column)"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
>重置</a-button>
|
||||
</div>
|
||||
<a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
|
||||
|
||||
<template slot="nameSearchRender" slot-scope="text">
|
||||
<span v-if="columnSearchText.name">
|
||||
<template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
|
||||
<mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
|
||||
<template v-else>{{ fragment }}</template>
|
||||
</template>
|
||||
</span>
|
||||
<template v-else>{{ text }}</template>
|
||||
</template> -->
|
||||
<!-- <template slot="permissions" slot-scope="record">
|
||||
<a-tag color="cyan" v-for="(r, index) in record" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</a-table> -->
|
||||
<!-- <div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
borderTop: '1px solid #e9e9e9',
|
||||
padding: '10px 16px',
|
||||
background: '#fff',
|
||||
textAlign: 'right',
|
||||
}"
|
||||
>
|
||||
<a-button :style="{marginRight: '8px'}" @click="handleCancel">
|
||||
取消
|
||||
</a-button>
|
||||
<a-button @click="handleOk" type="primary">确定</a-button>
|
||||
</div> -->
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceUserForm',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
rid: 0,
|
||||
records: [],
|
||||
resourceTypes: [],
|
||||
typeSelected: null,
|
||||
columnSearchText: {
|
||||
name: '',
|
||||
},
|
||||
filterName: '',
|
||||
// columns: [
|
||||
// {
|
||||
// title: '资源名',
|
||||
// field: 'name',
|
||||
// sorter: false,
|
||||
// width: '30%',
|
||||
// // scopedSlots: {
|
||||
// // customRender: 'nameSearchRender',
|
||||
// // filterDropdown: 'filterDropdown',
|
||||
// // filterIcon: 'filterIcon'
|
||||
// // },
|
||||
// // onFilter: (value, record) => record.name && record.name.toLowerCase().includes(value.toLowerCase()),
|
||||
// // onFilterDropdownVisibleChange: (visible) => {
|
||||
// // if (visible) {
|
||||
// // setTimeout(() => {
|
||||
// // this.searchInput.focus()
|
||||
// // }, 0)
|
||||
// // }
|
||||
// // }
|
||||
// },
|
||||
// {
|
||||
// title: '权限列表',
|
||||
// field: 'permissions',
|
||||
// width: '70%',
|
||||
// slots: { default: 'permissions_default' },
|
||||
// },
|
||||
// ],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.loadResourceTypes()
|
||||
},
|
||||
methods: {
|
||||
loadUserResource(record) {
|
||||
this.visible = true
|
||||
this.rid = record.id
|
||||
this.refresh()
|
||||
},
|
||||
// handleSearch(selectedKeys, confirm, column) {
|
||||
// confirm()
|
||||
// this.columnSearchText[column.dataIndex] = selectedKeys[0]
|
||||
// },
|
||||
// handleReset(clearFilters, column) {
|
||||
// clearFilters()
|
||||
// this.columnSearchText[column.dataIndex] = ''
|
||||
// },
|
||||
loadResourceTypes() {
|
||||
this.resourceTypes = []
|
||||
const appId = this.$route.name.split('_')[0]
|
||||
searchResourceType({ app_id: appId }).then(res => {
|
||||
this.resourceTypes = res.groups
|
||||
if (res.groups && res.groups.length > 0) {
|
||||
this.typeSelected = res.groups[0].id
|
||||
console.log(this.typeSelected)
|
||||
} else {
|
||||
this.typeSelected = null
|
||||
}
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
handleOk() {
|
||||
this.visible = false
|
||||
},
|
||||
refresh() {
|
||||
if (this.typeSelected) {
|
||||
searchPermResourceByRoleId(this.rid, {
|
||||
resource_type_id: this.typeSelected,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then(res => {
|
||||
this.records = res.resources
|
||||
})
|
||||
// .catch(err=>this.$httpError(err))
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
filterNameMethod({ option, row }) {
|
||||
this.filterName = option.data
|
||||
return row.name.toLowerCase().includes(option.data.toLowerCase())
|
||||
},
|
||||
filterNameRecoverMethod({ option }) {
|
||||
this.filterName = ''
|
||||
option.data = ''
|
||||
},
|
||||
copyResourceName() {
|
||||
const val = this.records
|
||||
.filter(item => item.name.toLowerCase().includes(this.filterName.toLowerCase()))
|
||||
.map(item => item.name)
|
||||
.join('\n')
|
||||
|
||||
this.copy(val, () => {
|
||||
this.$message.success('复制成功')
|
||||
})
|
||||
},
|
||||
copy(value, cb) {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.readOnly = 'readonly'
|
||||
textarea.value = value
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
textarea.setSelectionRange(0, textarea.value.length)
|
||||
document.execCommand('Copy')
|
||||
document.body.removeChild(textarea)
|
||||
if (cb && Object.prototype.toString.call(cb) === '[object Function]') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.resourceTypes = []
|
||||
this.loadResourceTypes()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.custom-filter-dropdown {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: rgb(255, 192, 105);
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resource-user-form-copy {
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
225
cmdb-ui/src/modules/acl/views/module/roleForm.vue
Normal file
225
cmdb-ui/src/modules/acl/views/module/roleForm.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="500px"
|
||||
>
|
||||
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }" @submit="handleSubmit">
|
||||
<a-form-item label="角色名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder="角色名"
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入角色名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="$route.name.split('_')[0] !== 'acl'" label="密码">
|
||||
<a-input name="password" placeholder="密码" v-decorator="['password', { rules: [{ required: false }] }]" />
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="继承自">
|
||||
<a-select
|
||||
showSearch
|
||||
v-model="selectedParents"
|
||||
:filterOption="false"
|
||||
placeholder="可选择继承角色"
|
||||
mode="multiple"
|
||||
>
|
||||
<a-select-option v-for="role in scrollData" :key="role.id">{{ role.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item> -->
|
||||
<a-form-item label="继承自">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
v-model="selectedParents"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="可选择继承角色"
|
||||
>
|
||||
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否应用管理员">
|
||||
<a-switch
|
||||
@change="onChange"
|
||||
name="is_app_admin"
|
||||
v-decorator="['is_app_admin', { rules: [], valuePropName: 'checked' }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { addRole, updateRoleById, delParentRole, addBatchParentRole } from '@/modules/acl/api/role'
|
||||
|
||||
export default {
|
||||
name: 'RoleForm',
|
||||
components: {
|
||||
ElSelect: Select,
|
||||
ElOption: Option,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '',
|
||||
current_id: 0,
|
||||
drawerVisible: false,
|
||||
selectedParents: [],
|
||||
oldParents: [],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {
|
||||
console.log(this.$route)
|
||||
},
|
||||
methods: {
|
||||
// filterOption(input, option) {
|
||||
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// },
|
||||
handleCreate() {
|
||||
this.drawerTitle = '新增角色'
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.selectedParents = []
|
||||
this.oldParents = []
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerTitle = `编辑:${record.name}`
|
||||
this.drawerVisible = true
|
||||
this.current_id = record.id
|
||||
const _parents = this.id2parents[record.id]
|
||||
if (_parents) {
|
||||
_parents.forEach((item) => {
|
||||
this.selectedParents.push(item.id)
|
||||
this.oldParents.push(item.id)
|
||||
})
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
is_app_admin: record.is_app_admin,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
if (values.id) {
|
||||
this.updateRole(values.id, values)
|
||||
} else {
|
||||
this.createRole(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateRole(id, data) {
|
||||
this.updateParents(id)
|
||||
updateRoleById(id, { ...data, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createRole(data) {
|
||||
addRole({ ...data, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.updateParents(res.id)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
updateParents(id) {
|
||||
this.oldParents.forEach((item) => {
|
||||
if (!this.selectedParents.includes(item)) {
|
||||
delParentRole(id, item, { app_id: this.$route.name.split('_')[0] })
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
})
|
||||
this.selectedParents.forEach((item) => {
|
||||
if (!this.oldParents.includes(item)) {
|
||||
addBatchParentRole(item, {
|
||||
child_ids: [id],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
})
|
||||
// addParentRole(id, item, { app_id: this.$route.name.split('_')[0] })
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
})
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// console.log(err)
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
id2parents: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
314
cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue
Normal file
314
cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="roleTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="角色关系"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
:data="tableData"
|
||||
size="small"
|
||||
:loading="loading"
|
||||
resizable
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" title="操作员" width="130px"></vxe-column>
|
||||
<vxe-column field="operate_type" title="操作" width="112px">
|
||||
<template #default="{ row }">
|
||||
<template>
|
||||
<a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '角色' : '角色'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.child_ids" :key="'child_ids_' + id + index" color="blue">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '继承自' : '管理员'" :width="checked ? '350px' : '80px'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-icon type="check" v-if="row.current.is_app_admin"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.parent_ids" :key="'parent_ids_' + id + index" color="cyan">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述" v-if="!checked">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" title="来源" width="100px"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchRoleHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
tableData: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['delete', '删除'],
|
||||
['update', '修改'],
|
||||
['role_relation_add', '添加角色关系'],
|
||||
['role_relation_delete', '删除角色关系'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['role_relation_add', 'green'],
|
||||
['role_relation_delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'role',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
roleTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '添加角色关系': 'role_relation_add' },
|
||||
{ '删除角色关系': 'role_relation_delete' },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2roles, id2perms, id2resources } = await searchRoleHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
if (item.operate_type === 'role_relation_add' || item.operate_type === 'role_relation_delete') {
|
||||
item.extra.child_ids.forEach((subItem, index) => { item.extra.child_ids[index] = id2roles[subItem].name })
|
||||
item.extra.parent_ids.forEach((subItem, index) => { item.extra.parent_ids[index] = id2roles[subItem].name })
|
||||
} else {
|
||||
this.handleChangeDescription(item, item.operate_type, id2roles, id2perms, id2resources)
|
||||
}
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'role_relation' : 'role'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: this.checked ? 'role_relation' : 'role' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: this.checked ? 'role_relation' : 'role'
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
// 处理tag颜色
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type, id2roles, id2perms, id2resources) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建角色:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const { extra: { child_ids, parent_ids, role_permissions } } = item
|
||||
child_ids.forEach((subItem, index) => { child_ids[index] = id2roles[subItem].name })
|
||||
parent_ids.forEach((subItem, index) => { parent_ids[index] = id2roles[subItem].name })
|
||||
|
||||
const resourceMap = new Map()
|
||||
const permsArr = []
|
||||
role_permissions.forEach(subItem => {
|
||||
const resource_id = subItem.resource_id
|
||||
const perm_id = subItem.perm_id
|
||||
if (resourceMap.has(resource_id)) {
|
||||
let resource_perms = resourceMap.get(resource_id)
|
||||
resource_perms += `,${id2perms[perm_id].name}`
|
||||
resourceMap.set(resource_id, resource_perms)
|
||||
} else {
|
||||
resourceMap.set(resource_id, String(id2perms[perm_id].name))
|
||||
}
|
||||
})
|
||||
resourceMap.forEach((value, key) => {
|
||||
permsArr.push(`${id2resources[key].name}:${value}`)
|
||||
})
|
||||
item.description = `继承者:${child_ids}\n继承自:${parent_ids}\n涉及资源及权限:\n${permsArr.join(`\n`)}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
236
cmdb-ui/src/modules/acl/views/module/searchForm.vue
Normal file
236
cmdb-ui/src/modules/acl/views/module/searchForm.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-form :colon="false">
|
||||
<a-row :gutter="24">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="12"
|
||||
:xl="6"
|
||||
v-for="attr in attrList.slice(0, 4)"
|
||||
:key="attr.name">
|
||||
<a-form-item
|
||||
:label="attr.alias || attr.name"
|
||||
:labelCol="{ span: 4 }"
|
||||
:wrapperCol="{ span: 20 }"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
@popupScroll="loadMoreData(attr.name, $event)"
|
||||
@search="(value) => fetchData(value, attr.name)"
|
||||
v-model="queryParams[attr.name]"
|
||||
placeholder="请选择"
|
||||
v-if="attr.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
v-for="(choice, index) in attr.choice_value"
|
||||
:key="'Search_' + attr.name + index"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
v-model="date"
|
||||
@change="onChange"
|
||||
:style="{ width: '100%' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
|
||||
}"
|
||||
v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'"
|
||||
/>
|
||||
<a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<template v-if="expand && attrList.length >= 4">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
:key="'expand_' + item.name"
|
||||
v-for="item in attrList.slice(4)">
|
||||
<a-form-item
|
||||
:label="item.alias || item.name"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
@popupScroll="loadMoreData(item.name, $event)"
|
||||
@search="(value) => fetchData(value, item.name)"
|
||||
v-model="queryParams[item.name]"
|
||||
placeholder="请选择"
|
||||
v-if="item.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="'Search_' + item.name + index"
|
||||
v-for="(choice, index) in item.choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
:style="{ width: '100%' }"
|
||||
@change="onChange"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
|
||||
}"
|
||||
/>
|
||||
<a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="24" :style="{ textAlign: 'right', marginBottom: '10px' }">
|
||||
<a-switch
|
||||
ref="switch"
|
||||
v-if="hasSwitch"
|
||||
:un-checked-children="switchValue"
|
||||
@change="onSwitchChange"
|
||||
v-model="checked"
|
||||
/>
|
||||
<a-button :style="{ marginLeft: '8px' }" type="primary" html-type="submit" @click="handleSearch">
|
||||
查询
|
||||
</a-button>
|
||||
<a-button :style="{ marginLeft: '8px' }" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
<a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 5">
|
||||
{{ expand ? '隐藏' : '展开' }} <a-icon :type="expand ? 'up' : 'down'" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { valueTypeMap } from '../../constants/constants'
|
||||
export default {
|
||||
name: 'SearchForm',
|
||||
props: {
|
||||
attrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
hasSwitch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
switchValue: {
|
||||
default: '',
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueTypeMap,
|
||||
expand: false,
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
},
|
||||
date: undefined,
|
||||
checked: false,
|
||||
scrollPage: 1,
|
||||
searchValue: undefined,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryParams: {
|
||||
deep: true,
|
||||
handler: function(val) {
|
||||
this.preProcessData()
|
||||
this.$emit('searchFormChange', val)
|
||||
},
|
||||
},
|
||||
'queryParams.resource_id': {
|
||||
handler(val) {
|
||||
if (val === undefined) {
|
||||
this.$emit('resourceClear')
|
||||
}
|
||||
},
|
||||
},
|
||||
'queryParams.link_id': {
|
||||
handler(val) {
|
||||
if (val === undefined) {
|
||||
this.$emit('resourceClear')
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleSearch() {
|
||||
this.queryParams.page = 1
|
||||
this.$emit('search', this.queryParams)
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
this.date = undefined
|
||||
this.$emit('searchFormReset')
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.expand = !this.expand
|
||||
this.$emit('expandChange', this.expand)
|
||||
},
|
||||
|
||||
onChange(date, dateString) {
|
||||
this.queryParams.start = dateString[0]
|
||||
this.queryParams.end = dateString[1]
|
||||
},
|
||||
onSwitchChange(checked) {
|
||||
this.$emit('onSwitchChange', checked)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
},
|
||||
preProcessData() {
|
||||
Object.keys(this.queryParams).forEach((item) => {
|
||||
if (this.queryParams[item] === '' || this.queryParams[item] === undefined) {
|
||||
delete this.queryParams[item]
|
||||
}
|
||||
})
|
||||
return this.queryParams
|
||||
},
|
||||
loadMoreData(name, e, value) {
|
||||
const { target } = e
|
||||
if (target.scrollTop + target.clientHeight === target.scrollHeight) {
|
||||
this.$emit('loadMoreData', name, this.searchValue)
|
||||
}
|
||||
},
|
||||
fetchData(value, name) {
|
||||
this.searchValue = value
|
||||
if (name === 'link_id' || name === 'resource_id') {
|
||||
this.$emit('fetchData', value)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
197
cmdb-ui/src/modules/acl/views/module/triggerForm.vue
Normal file
197
cmdb-ui/src/modules/acl/views/module/triggerForm.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
@close="handleClose"
|
||||
width="500"
|
||||
:title="`${triggerId ? '修改' : '新建'}触发器`"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
>
|
||||
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }">
|
||||
<a-form-item label="触发器名">
|
||||
<a-input size="large" v-decorator="['name', { rules: [{ required: true, message: '请输入触发器名' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源名">
|
||||
<a-input size="large" v-decorator="['wildcard']" placeholder="优先正则模式(次通配符)"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建人">
|
||||
<el-select :style="{ width: '100%' }" filterable multiple v-decorator="['uid']">
|
||||
<template v-for="role in roles">
|
||||
<el-option v-if="role.uid" :key="role.id" :value="role.uid" :label="role.name">{{ role.name }}</el-option>
|
||||
</template>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源类型">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
@change="handleRTChange"
|
||||
v-decorator="['resource_type_id', { rules: [{ required: true, message: '请选择资源类型' }] }]"
|
||||
>
|
||||
<el-option
|
||||
v-for="resourceType in resourceTypeList"
|
||||
:key="resourceType.id"
|
||||
:value="resourceType.id"
|
||||
:label="resourceType.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<a-tooltip title="查看正则匹配结果">
|
||||
<a class="trigger-form-pattern" @click="handlePattern"><a-icon type="eye"/></a>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
filterable
|
||||
multiple
|
||||
v-decorator="['roles', { rules: [{ required: true, message: '请选择角色' }] }]"
|
||||
>
|
||||
<el-option v-for="role in roles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="权限">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
multiple
|
||||
v-decorator="['permissions', { rules: [{ required: true, message: '请选择权限' }] }]"
|
||||
>
|
||||
<el-option
|
||||
v-for="perm in selectResourceTypePerms"
|
||||
:key="perm.id"
|
||||
:value="perm.name"
|
||||
:label="perm.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="启用/禁用">
|
||||
<a-switch v-decorator="['enabled', { rules: [], valuePropName: 'checked', initialValue: true }]" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">提交</a-button>
|
||||
</div>
|
||||
<TriggerPattern ref="triggerPattern" :roles="roles" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { Select, Option, Input } from 'element-ui'
|
||||
import { addTrigger, updateTrigger } from '@/modules/acl/api/trigger'
|
||||
import TagSelectOption from '@/components/TagSelect/TagSelectOption'
|
||||
import TriggerPattern from '../module/triggerPattern'
|
||||
|
||||
export default {
|
||||
name: 'TriggerForm',
|
||||
components: {
|
||||
TagSelectOption,
|
||||
TriggerPattern,
|
||||
ElSelect: Select,
|
||||
ElOption: Option,
|
||||
ElInput: Input,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
triggerId: null,
|
||||
selectResourceTypePerms: [],
|
||||
form: this.$form.createForm(this),
|
||||
}
|
||||
},
|
||||
props: {
|
||||
roles: {
|
||||
required: true,
|
||||
type: Array,
|
||||
},
|
||||
resourceTypeList: {
|
||||
required: true,
|
||||
type: Array,
|
||||
},
|
||||
id2perms: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
app_id: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
beforeCreate() {},
|
||||
methods: {
|
||||
handleEdit(ele) {
|
||||
this.form.resetFields()
|
||||
this.visible = true
|
||||
if (ele) {
|
||||
this.triggerId = ele.id
|
||||
this.$nextTick(() => {
|
||||
this.selectResourceTypePerms = this.id2perms[ele.resource_type_id]
|
||||
const { name, wildcard, uid, resource_type_id, roles, permissions, enabled } = ele
|
||||
this.form.setFieldsValue({
|
||||
name,
|
||||
wildcard,
|
||||
uid,
|
||||
resource_type_id,
|
||||
permissions,
|
||||
enabled,
|
||||
roles: roles.map((x) => Number(x)),
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.triggerId = null
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.$nextTick(() => {
|
||||
this.visible = false
|
||||
})
|
||||
},
|
||||
handleRTChange(value) {
|
||||
this.selectResourceTypePerms = this.id2perms[value]
|
||||
},
|
||||
// filterOption(input, option) {
|
||||
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// },
|
||||
handleSubmit() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.triggerId) {
|
||||
updateTrigger(this.triggerId, { ...values, app_id: this.app_id }).then((res) => {
|
||||
this.visible = false
|
||||
this.$message.success('修改成功!')
|
||||
this.$emit('refresh')
|
||||
})
|
||||
} else {
|
||||
addTrigger({ ...values, app_id: this.app_id }).then((res) => {
|
||||
this.visible = false
|
||||
this.$message.success('创建成功!')
|
||||
this.$emit('refresh')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handlePattern() {
|
||||
this.form.validateFields(['wildcard', 'uid', 'resource_type_id'], (err, values) => {
|
||||
if (!err) {
|
||||
const { wildcard, uid, resource_type_id } = values
|
||||
console.log(values)
|
||||
this.$refs.triggerPattern.open({
|
||||
resource_type_id,
|
||||
app_id: this.app_id,
|
||||
owner: uid,
|
||||
pattern: wildcard,
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.trigger-form-pattern {
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
}
|
||||
</style>
|
295
cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue
Normal file
295
cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="triggerTableAttrList"
|
||||
@searchFormReset="searchFormReset"
|
||||
@search="handleSearch"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
size="small"
|
||||
resizable
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="trigger_id" width="250px" title="触发器">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchTriggerHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allTriggers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allTriggersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allResourceTypesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
loading: true,
|
||||
tableData: [],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
['trigger_apply', '应用'],
|
||||
['trigger_cancel', '取消'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['trigger_apply', 'green'],
|
||||
['trigger_cancel', 'red'],
|
||||
]),
|
||||
triggerTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '应用': 'trigger_apply' },
|
||||
{ '取消': 'trigger_cancel' },
|
||||
]
|
||||
},
|
||||
{
|
||||
alias: '触发器',
|
||||
is_choice: true,
|
||||
name: 'trigger_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allTriggers
|
||||
}
|
||||
],
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0]
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await searchTriggerHistory(this.handleQueryParams(queryParams))
|
||||
res.data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.trigger_id = this.allTriggersMap.get(item.trigger_id)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = res.data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.queryParams.app_id = this.app_id
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0]
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `新增触发器:${item.current.name}\n资源类型:${this.allResourceTypesMap.get(item.current.resource_type_id)},资源名:${item.current.wildcard},角色:[${newStr}]\n权限:${item.current.permissions}\n状态:${item.current.enabled}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const str = item.origin.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `删除触发器:${item.origin.name}\n资源类型:${this.allResourceTypesMap.get(item.origin.resource_type_id)},资源名:${item.origin.wildcard},角色:[${newStr}]\n权限:${item.origin.permissions}\n状态:${item.origin.enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_apply': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `应用触发器:${item.current.name}\n资源类型:${this.allResourceTypesMap.get(item.current.resource_type_id)},资源名:${item.current.wildcard},角色:[${newStr}]\n权限:${item.current.permissions}\n状态:${item.current.enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_cancel': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `取消触发器:${item.current.name}\n资源类型:${this.allResourceTypesMap.get(item.current.resource_type_id)},资源名:${item.current.wildcard},角色:[${newStr}]\n权限:${item.current.permissions}\n状态:${item.current.enabled}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
if (q) {
|
||||
q += `,${key}:${queryParams[key]}`
|
||||
} else {
|
||||
q += `${key}:${queryParams[key]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
</style>
|
68
cmdb-ui/src/modules/acl/views/module/triggerPattern.vue
Normal file
68
cmdb-ui/src/modules/acl/views/module/triggerPattern.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:hasFooter="false"
|
||||
title="正则匹配结果"
|
||||
:visible="patternVisible"
|
||||
width="500"
|
||||
@close="
|
||||
() => {
|
||||
patternVisible = false
|
||||
}
|
||||
"
|
||||
>
|
||||
<vxe-table :data="tableData" :max-height="`${windowHeight - 110}px`">
|
||||
<vxe-table-column field="name" title="资源名"></vxe-table-column>
|
||||
<vxe-table-column field="uid" title="创建人">
|
||||
<template #default="{row}">
|
||||
{{ getRoleName(row.uid) }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="created_at" title="创建时间"></vxe-table-column>
|
||||
</vxe-table>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { patternResults } from '@/modules/acl/api/trigger'
|
||||
export default {
|
||||
name: 'TriggerPattern',
|
||||
props: {
|
||||
roles: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
patternVisible: false,
|
||||
tableData: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
open(params) {
|
||||
patternResults(params).then(res => {
|
||||
this.patternVisible = true
|
||||
this.tableData = res
|
||||
})
|
||||
},
|
||||
getRoleName(uid) {
|
||||
if (uid) {
|
||||
const _find = this.roles.find(item => item.uid === uid)
|
||||
if (_find) {
|
||||
return _find.name
|
||||
}
|
||||
return ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
198
cmdb-ui/src/modules/acl/views/module/userForm.vue
Normal file
198
cmdb-ui/src/modules/acl/views/module/userForm.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="500px"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="用户名(英文)">
|
||||
<a-input
|
||||
name="username"
|
||||
placeholder="英文名"
|
||||
v-decorator="['username', { rules: [{ required: true, message: '请输入用户名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="中文名">
|
||||
<a-input name="nickname" v-decorator="['nickname', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input
|
||||
type="password"
|
||||
name="password"
|
||||
v-decorator="['password', { rules: [{ required: true, message: '请输入密码' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="部门">
|
||||
<a-input name="department" v-decorator="['department', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="小组">
|
||||
<a-input name="catalog" v-decorator="['catalog', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="邮箱">
|
||||
<a-input
|
||||
name="email"
|
||||
v-decorator="[
|
||||
'email',
|
||||
{
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
message: '请输入正确的邮箱!',
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
message: '请输入邮箱',
|
||||
},
|
||||
],
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="手机号码">
|
||||
<a-input
|
||||
name="mobile"
|
||||
v-decorator="['mobile', { rules: [{ message: '请输入正确的手机号码', pattern: /^1\d{10}$/ }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="是否锁定">
|
||||
<a-switch @change="onChange" name="block" v-decorator="['block', { rules: [], valuePropName: 'checked' }]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addUser, updateUserById } from '@/modules/acl/api/user'
|
||||
|
||||
export default {
|
||||
name: 'AttributeForm',
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增用户',
|
||||
drawerVisible: false,
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
handleCreate() {
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerVisible = true
|
||||
console.log(record)
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.uid,
|
||||
username: record.username,
|
||||
nickname: record.nickname,
|
||||
password: record.password,
|
||||
department: record.department,
|
||||
catalog: record.catalog,
|
||||
email: record.email,
|
||||
mobile: record.mobile,
|
||||
block: record.block,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
|
||||
if (values.id) {
|
||||
this.updateUser(values.id, values)
|
||||
} else {
|
||||
this.createUser(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateUser(attrId, data) {
|
||||
updateUserById(attrId, data).then((res) => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createUser(data) {
|
||||
addUser(data).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
// requestFailed (err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// }
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
102
cmdb-ui/src/modules/acl/views/module/usersUnderRoleForm.vue
Normal file
102
cmdb-ui/src/modules/acl/views/module/usersUnderRoleForm.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<CustomDrawer :closable="true" :visible="visible" width="500px" @close="handleClose" title="组用户">
|
||||
<a-form-item label="添加用户" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<a-row>
|
||||
<a-col span="15">
|
||||
<el-select v-model="selectedChildrenRole" multiple collapse-tags size="small" filterable>
|
||||
<el-option
|
||||
class="drop-down-render"
|
||||
v-for="role in allRoles"
|
||||
:key="role.id"
|
||||
:value="role.id"
|
||||
:label="role.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</a-col>
|
||||
<a-col span="5" offset="1">
|
||||
<a-button style="display: inline-block" @click="handleAddRole">确定</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-card>
|
||||
<a-row :gutter="24" v-for="(record, index) in records" :key="record.id" :style="{ marginBottom: '5px' }">
|
||||
<a-col :span="20">{{ index + 1 }}、{{ record.nickname }}</a-col>
|
||||
<a-col :span="4"><a-button type="danger" size="small" @click="handleRevokeUser(record)">移除</a-button></a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUsersUnderRole, delParentRole, addBatchParentRole } from '@/modules/acl/api/role'
|
||||
import { Select, Option } from 'element-ui'
|
||||
export default {
|
||||
name: 'UsersUnderRoleForm',
|
||||
components: { ElSelect: Select, ElOption: Option },
|
||||
props: {
|
||||
allRoles: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
records: [],
|
||||
roleId: 0,
|
||||
selectedChildrenRole: [],
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
},
|
||||
loadRecords(roleId) {
|
||||
getUsersUnderRole(roleId, { app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.records = res['users']
|
||||
})
|
||||
// .catch(err=> this.$httpError(err))
|
||||
},
|
||||
handleProcessRole(roleId) {
|
||||
this.roleId = roleId
|
||||
this.visible = true
|
||||
this.loadRecords(roleId)
|
||||
},
|
||||
// filterOption(input, option) {
|
||||
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// },
|
||||
async handleAddRole() {
|
||||
// await this.selectedChildrenRole.forEach(item => {
|
||||
// addParentRole(item, this.roleId, { app_id: this.$route.name.split('_')[0] }).then(res => {
|
||||
// this.$message.success('添加成功')
|
||||
// })
|
||||
// // .catch(err=>{
|
||||
// // this.$httpError(err)
|
||||
// // })
|
||||
|
||||
// })
|
||||
await addBatchParentRole(this.roleId, {
|
||||
child_ids: this.selectedChildrenRole,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
})
|
||||
this.loadRecords(this.roleId)
|
||||
this.$message.success('添加完成')
|
||||
this.selectedChildrenRole = []
|
||||
},
|
||||
handleRevokeUser(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
content: '是否确定要移除该用户',
|
||||
onOk() {
|
||||
delParentRole(record.role.id, that.roleId, { app_id: that.$route.name.split('_')[0] }).then((res) => {
|
||||
that.$message.success('删除成功!')
|
||||
that.loadRecords(that.roleId)
|
||||
})
|
||||
// .catch(err=>that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
34
cmdb-ui/src/modules/acl/views/operation_history/index.vue
Normal file
34
cmdb-ui/src/modules/acl/views/operation_history/index.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="权限变更">
|
||||
<permisson-table></permisson-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="角色变更">
|
||||
<role-history-table></role-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="资源变更">
|
||||
<resource-history-table></resource-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab="资源类型变更">
|
||||
<resource-type-history-table></resource-type-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab="触发器变更">
|
||||
<trigger-history-table></trigger-history-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import permissonTable from './modules/permissionTable.vue'
|
||||
import resourceHistoryTable from './modules/resourceHistoryTable.vue'
|
||||
import resourceTypeHistoryTable from './modules/resourceTypeHistoryTable.vue'
|
||||
import roleHistoryTable from './modules/roleHistoryTable.vue'
|
||||
import triggerHistoryTable from './modules/triggerHistoryTable.vue'
|
||||
export default {
|
||||
components: { permissonTable, resourceHistoryTable, resourceTypeHistoryTable, roleHistoryTable, triggerHistoryTable },
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="permissionTableAttrList"
|
||||
@search="handleSearch"
|
||||
@expandChange="handleExpandChange"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
:scroll-y="{ enabled: false }"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.deleted_at || row.updated_at || row.created_at }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{
|
||||
operateTypeMap.get(row.operate_type)
|
||||
}}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="rid" title="用户"></vxe-column>
|
||||
<vxe-column field="resource_type_id" title="资源类型"></vxe-column>
|
||||
<vxe-column field="resources" title="资源">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.resource_ids.length > 0">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
<span>{{ row.resource_ids[0] }}</span>
|
||||
</template>
|
||||
<a-tag color="blue" v-for="(resource, index) in row.resource_ids" :key="'resources_' + resource + index">
|
||||
{{ resource }}
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="row.group_ids.length > 0">
|
||||
<a-tag color="blue" v-for="(group, index) in row.group_ids" :key="'groups_' + group + index">
|
||||
{{ group }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="权限">
|
||||
<template #default="{ row }">
|
||||
<a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index">
|
||||
{{ perm }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
import { searchPermissonHistory } from '@/modules/acl/api/history'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResource, searchResourceType } from '@/modules/acl/api/resource'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
this.fetchResources = debounce(this.fetchResources, 800)
|
||||
return {
|
||||
app_id: undefined,
|
||||
resource_id: undefined,
|
||||
isExpand: false,
|
||||
loading: true,
|
||||
resourcesPage: 1,
|
||||
resourcesNum: 0,
|
||||
tableData: [],
|
||||
allRoles: [],
|
||||
allUsers: [],
|
||||
allResourceTypes: [],
|
||||
allResources: [],
|
||||
allApps: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
allResourceTypesMap: new Map(),
|
||||
operateTypeMap: new Map([
|
||||
['grant', '授权'],
|
||||
['revoke', '撤销'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
permissionTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'rid',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'resource_type_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '资源',
|
||||
is_choice: true,
|
||||
name: 'resource_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 授权: 'grant' }, { 撤销: 'revoke' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function() {
|
||||
return this.permissionTableAttrList[3].choice_value
|
||||
},
|
||||
function() {
|
||||
delete this.$refs.child.queryParams.rid
|
||||
delete this.$refs.child.queryParams.resource_type_id
|
||||
delete this.$refs.child.queryParams.resource_id
|
||||
}
|
||||
)
|
||||
await Promise.all([this.getAllApps(), this.getAllUsers()])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2groups, id2perms, id2resources, id2roles, id2resource_types } = await searchPermissonHistory(
|
||||
this.handleQueryParams(queryParams)
|
||||
)
|
||||
data.forEach((item) => {
|
||||
item.rid = id2roles[item.rid].name
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
// 脏数据清除后可删
|
||||
if (id2resource_types[item.resource_type_id]) {
|
||||
item.resource_type_id = id2resource_types[item.resource_type_id].name
|
||||
}
|
||||
item.resource_ids.forEach((subItem, index) => {
|
||||
item.resource_ids[index] = id2resources[subItem].name
|
||||
})
|
||||
item.group_ids.forEach((subItem, index) => {
|
||||
item.group_ids[index] = id2groups[subItem].name
|
||||
})
|
||||
item.permission_ids.forEach((subItem, index) => {
|
||||
item.permission_ids[index] = id2perms[subItem].name
|
||||
})
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const res = await searchApp()
|
||||
const allApps = []
|
||||
res.apps.forEach((item) => {
|
||||
allApps.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allApps = allApps
|
||||
this.permissionTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllRoles(app_id) {
|
||||
if (!app_id) {
|
||||
this.permissionTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const { roles } = await searchRole({
|
||||
page_size: 9999,
|
||||
app_id: app_id,
|
||||
})
|
||||
const allRoles = []
|
||||
roles.forEach((item) => {
|
||||
allRoles.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allRoles = allRoles
|
||||
this.permissionTableAttrList[3].choice_value = this.allRoles
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach((item) => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.permissionTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getAllResourceTypes(app_id) {
|
||||
if (!app_id) {
|
||||
this.permissionTableAttrList[4].choice_value = []
|
||||
return
|
||||
}
|
||||
const { groups } = await searchResourceType({
|
||||
page_size: 9999,
|
||||
page: 1,
|
||||
app_id: app_id,
|
||||
})
|
||||
const allResourceTypes = []
|
||||
groups.forEach((item) => {
|
||||
allResourceTypes.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResourceTypes = allResourceTypes
|
||||
this.permissionTableAttrList[4].choice_value = this.allResourceTypes
|
||||
},
|
||||
async getAllResources(app_id, page, value = undefined) {
|
||||
if (!app_id) {
|
||||
this.permissionTableAttrList[5].choice_value = []
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: page,
|
||||
page_size: 50,
|
||||
app_id: app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
const allResources = this.allResources
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.permissionTableAttrList[5].choice_value = this.allResources
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'resource_id' && this.allResources.length < this.resourcesNum) {
|
||||
let currentPage = this.resourcesPage
|
||||
this.getAllResources(this.app_id, ++currentPage, value)
|
||||
this.resourcesPage = currentPage
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.getAllResources(this.app_id, 1)
|
||||
},
|
||||
async fetchResources(value) {
|
||||
this.allResources = []
|
||||
const allResources = []
|
||||
if (!this.app_id) {
|
||||
this.permissionTableAttrList[5].choice_value = []
|
||||
return
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
if (value === '') {
|
||||
this.getAllResources(this.app_id, 1)
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.permissionTableAttrList[5].choice_value = this.allResources
|
||||
},
|
||||
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
await Promise.all([
|
||||
this.getAllRoles(this.app_id),
|
||||
this.getAllResourceTypes(this.app_id),
|
||||
this.getAllResources(this.app_id, this.resourcesPage),
|
||||
])
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.rid = undefined
|
||||
this.$refs.child.queryParams.resource_type_id = undefined
|
||||
this.$refs.child.queryParams.resource_id = undefined
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
}
|
||||
},
|
||||
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="组"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" title="资源名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResource } from '@/modules/acl/api/resource'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
this.fetchResources = _.debounce(this.fetchResources, 800)
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
isExpand: false,
|
||||
app_id: undefined,
|
||||
resourcesPage: 1,
|
||||
resourcesNum: 0,
|
||||
tableData: [],
|
||||
allResources: [],
|
||||
allUsers: [],
|
||||
allApps: [],
|
||||
allUsersMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '资源名',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
|
||||
},
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'resource',
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function() {
|
||||
return this.resourceTableAttrList[3].choice_value
|
||||
},
|
||||
function() {
|
||||
delete this.$refs.child.queryParams.link_id
|
||||
}
|
||||
)
|
||||
await Promise.all([this.getAllApps(), this.getAllUsers()])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach((item) => {
|
||||
item.originResource_ids = item?.extra?.resource_ids?.origin
|
||||
item.currentResource_ids = item?.extra?.resource_ids?.current
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach((item) => {
|
||||
allApps.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allApps = allApps
|
||||
this.resourceTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach((item) => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.resourceTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getAllResources(app_id, page, value = undefined) {
|
||||
if (!app_id) {
|
||||
this.resourceTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: page,
|
||||
page_size: 50,
|
||||
app_id: app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
const allResources = this.allResources
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.resourceTableAttrList[3].choice_value = this.allResources
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'link_id' && this.allResources.length < this.resourcesNum) {
|
||||
let currentPage = this.resourcesPage
|
||||
this.getAllResources(this.app_id, ++currentPage, value)
|
||||
this.resourcesPage = currentPage
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.getAllResources(this.app_id, 1)
|
||||
},
|
||||
async fetchResources(value) {
|
||||
this.allResources = []
|
||||
const allResources = []
|
||||
if (!this.app_id) {
|
||||
this.resourceTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
if (value === '') {
|
||||
this.getAllResources(this.app_id, 1)
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.resourceTableAttrList[3].choice_value = this.allResources
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: this.checked ? 'resource_group' : 'resource' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: this.checked ? 'resource_group' : 'resource',
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'resource_group' : 'resource'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
await this.getAllResources(this.app_id, this.resourcesPage)
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.link_id = undefined
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建资源:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
}
|
||||
const originResource_ids = item.originResource_ids
|
||||
const currentResource_ids = item.currentResource_ids
|
||||
if (!_.isEqual(originResource_ids, currentResource_ids)) {
|
||||
if (originResource_ids.length === 0) {
|
||||
const str = ` 【 resource_ids : 新增 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 resource_ids : 由 ${originResource_ids} 改为 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
item.description = `删除资源:${item.origin.name}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
@expandChange="handleExpandChange"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" width="159px" title="资源类型名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
isExpand: false,
|
||||
app_id: undefined,
|
||||
tableData: [],
|
||||
allResourceTypes: [],
|
||||
allUsers: [],
|
||||
allApps: [],
|
||||
allUsersMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
]
|
||||
}
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除']
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'resource_type',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function () {
|
||||
return this.resourceTableAttrList[3].choice_value
|
||||
},
|
||||
function () {
|
||||
delete this.$refs.child.queryParams.link_id
|
||||
}
|
||||
)
|
||||
await Promise.all([ this.getAllApps(), this.getAllUsers() ])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach(item => { allApps.push({ [item.name]: item.id }) })
|
||||
this.allApps = allApps
|
||||
this.resourceTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach(item => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.resourceTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getAllResourceTypes(app_id) {
|
||||
if (!app_id) {
|
||||
this.resourceTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const { groups } = await searchResourceType({
|
||||
page: 1,
|
||||
page_size: 9999,
|
||||
app_id: app_id
|
||||
})
|
||||
const allResourceTypes = []
|
||||
groups.forEach(item => { allResourceTypes.push({ [item.name]: item.id }) })
|
||||
this.allResourceTypes = allResourceTypes
|
||||
this.resourceTableAttrList[3].choice_value = this.allResourceTypes
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: 'resource_type' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'resource_type',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
await this.getAllResourceTypes(this.app_id)
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.link_id = undefined
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
const description = item.current?.description === undefined ? '无' : item.current?.description
|
||||
const permission = item.extra.permission_ids?.current === undefined ? '无' : item.extra.permission_ids?.current
|
||||
item.changeDescription = `新增资源类型:${item.current.name}\n描述:${description}\n权限:${permission}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null || oldVal === '') {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
}
|
||||
}
|
||||
}
|
||||
const currentPerms = item.extra.permission_ids?.current === undefined ? '无' : item.extra.permission_ids?.current
|
||||
const originPerms = item.extra.permission_ids?.origin === undefined ? '无' : item.extra.permission_ids?.origin
|
||||
if (!_.isEqual(currentPerms, originPerms)) item.changeDescription += ` 【 permission_ids : 由 ${originPerms} 改为 ${currentPerms} 】 `
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const description = item.origin?.description === undefined ? '无' : item.origin?.description
|
||||
const permission = item.extra.permission_ids?.origin === undefined ? '无' : item.extra.permission_ids?.origin
|
||||
item.changeDescription = `删除资源类型:${item.origin.name}\n描述:${description}\n权限:${permission}`
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="roleTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="角色关系"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="112px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<template>
|
||||
<a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '角色' : '角色'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.child_ids" :key="'child_ids_' + id + index" color="blue">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '继承自' : '管理员'" :width="checked ? '350px' : '80px'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-icon type="check" v-if="row.current.is_app_admin"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.parent_ids" :key="'parent_ids_' + id + index" color="cyan">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述" v-if="!checked">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchRoleHistory } from '@/modules/acl/api/history'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
app_id: undefined,
|
||||
tableData: [],
|
||||
allApps: [],
|
||||
allUsers: [],
|
||||
allRoles: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['delete', '删除'],
|
||||
['update', '修改'],
|
||||
['role_relation_add', '添加角色关系'],
|
||||
['role_relation_delete', '删除角色关系'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['role_relation_add', 'green'],
|
||||
['role_relation_delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'role',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
roleTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '添加角色关系': 'role_relation_add' },
|
||||
{ '删除角色关系': 'role_relation_delete' },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await Promise.all([ this.getAllApps(), this.getAllUsers() ])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2roles, id2perms, id2resources } = await searchRoleHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
if (item.operate_type === 'role_relation_add' || item.operate_type === 'role_relation_delete') {
|
||||
item.extra.child_ids.forEach((subItem, index) => { item.extra.child_ids[index] = id2roles[subItem].name })
|
||||
item.extra.parent_ids.forEach((subItem, index) => { item.extra.parent_ids[index] = id2roles[subItem].name })
|
||||
} else {
|
||||
this.handleChangeDescription(item, item.operate_type, id2roles, id2perms, id2resources)
|
||||
}
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach(item => { allApps.push({ [item.name]: item.id }) })
|
||||
this.allApps = allApps
|
||||
this.roleTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach(item => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.roleTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'role_relation' : 'role'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: this.checked ? 'role_relation' : 'role' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: this.checked ? 'role_relation' : 'role'
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
// 处理tag颜色
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type, id2roles, id2perms, id2resources) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建角色:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const { extra: { child_ids, parent_ids, role_permissions } } = item
|
||||
child_ids.forEach((subItem, index) => { child_ids[index] = id2roles[subItem].name })
|
||||
parent_ids.forEach((subItem, index) => { parent_ids[index] = id2roles[subItem].name })
|
||||
|
||||
const resourceMap = new Map()
|
||||
const permsArr = []
|
||||
role_permissions.forEach(subItem => {
|
||||
const resource_id = subItem.resource_id
|
||||
const perm_id = subItem.perm_id
|
||||
if (resourceMap.has(resource_id)) {
|
||||
let resource_perms = resourceMap.get(resource_id)
|
||||
resource_perms += `,${id2perms[perm_id].name}`
|
||||
resourceMap.set(resource_id, resource_perms)
|
||||
} else {
|
||||
resourceMap.set(resource_id, String(id2perms[perm_id].name))
|
||||
}
|
||||
})
|
||||
resourceMap.forEach((value, key) => {
|
||||
permsArr.push(`${id2resources[key].name}:${value}`)
|
||||
})
|
||||
item.description = `继承者:${child_ids}\n继承自:${parent_ids}\n涉及资源及权限:\n${permsArr.join(`\n`)}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="triggerTableAttrList"
|
||||
@searchFormReset="searchFormReset"
|
||||
@search="handleSearch"
|
||||
@expandChange="handleExpandChange"
|
||||
@searchFormChange="searchFormChange"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="trigger_id" width="250px" title="触发器">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchTriggerHistory } from '@/modules/acl/api/history'
|
||||
import { getTriggers } from '@/modules/acl/api/trigger'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
app_id: undefined,
|
||||
loading: true,
|
||||
isExpand: false,
|
||||
tableData: [],
|
||||
allResourceTypes: [],
|
||||
allResources: [],
|
||||
allUsers: [],
|
||||
allRoles: [],
|
||||
allTriggers: [],
|
||||
allApps: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
allResourceTypesMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
allTriggersMap: new Map(),
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
['trigger_apply', '应用'],
|
||||
['trigger_cancel', '取消'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['trigger_apply', 'green'],
|
||||
['trigger_cancel', 'red'],
|
||||
]),
|
||||
triggerTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '触发器',
|
||||
is_choice: true,
|
||||
name: 'trigger_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '应用': 'trigger_apply' },
|
||||
{ '取消': 'trigger_cancel' },
|
||||
]
|
||||
},
|
||||
],
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function () {
|
||||
return this.triggerTableAttrList[3].choice_value
|
||||
},
|
||||
function () {
|
||||
delete this.$refs.child.queryParams.trigger_id
|
||||
}
|
||||
)
|
||||
await Promise.all([ this.getAllApps(), this.getAllUsers() ])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2resource_types, id2roles } = await searchTriggerHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type, id2resource_types, id2roles)
|
||||
item.trigger_id = this.allTriggersMap.get(item.trigger_id)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach(item => { allApps.push({ [item.name]: item.id }) })
|
||||
this.allApps = allApps
|
||||
this.triggerTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach(item => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.triggerTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getTriggers(app_id) {
|
||||
if (!app_id) {
|
||||
this.triggerTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const res = await getTriggers({ app_id: app_id })
|
||||
const allTriggers = []
|
||||
const allTriggersMap = new Map()
|
||||
res.forEach(item => {
|
||||
allTriggers.push({ [item.name]: item.id })
|
||||
allTriggersMap.set(item.id, item.name)
|
||||
})
|
||||
this.allTriggers = allTriggers
|
||||
this.allTriggersMap = allTriggersMap
|
||||
this.triggerTableAttrList[3].choice_value = this.allTriggers
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
await this.getTriggers(this.app_id)
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.trigger_id = undefined
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeDescription(item, operate_type, id2resource_types, id2roles) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.current
|
||||
item.changeDescription = `新增触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const str = item.origin.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.origin
|
||||
item.changeDescription = `删除触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_apply': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.current
|
||||
item.changeDescription = `应用触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_cancel': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.current
|
||||
item.changeDescription = `取消触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
if (q) {
|
||||
q += `,${key}:${queryParams[key]}`
|
||||
} else {
|
||||
q += `${key}:${queryParams[key]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
</style>
|
197
cmdb-ui/src/modules/acl/views/resource_types.vue
Normal file
197
cmdb-ui/src/modules/acl/views/resource_types.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<div class="resource-types-action-btn">
|
||||
<a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem;">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
|
||||
placeholder="搜索 | 资源类型名"
|
||||
v-model="searchName"
|
||||
allowClear
|
||||
@search="
|
||||
() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<a-spin
|
||||
:spinning="loading"
|
||||
><vxe-grid :columns="tableColumns" :data="groups" :max-height="`${windowHeight - 185}px`" highlight-hover-row>
|
||||
<template #id_default="{row}">
|
||||
<a-tag color="cyan" v-for="perm in id2perms[row.id]" :key="perm.id">{{ perm.name }}</a-tag>
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<a @click="handleEdit(row)"><a-icon type="edit"/></a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" okText="是" cancelText="否">
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<template #pager>
|
||||
<vxe-pager
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
</vxe-pager>
|
||||
</template> </vxe-grid
|
||||
></a-spin>
|
||||
|
||||
<resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import resourceTypeForm from './module/resourceTypeForm'
|
||||
import { deleteResourceTypeById, searchResourceType } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceType',
|
||||
components: {
|
||||
resourceTypeForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
groups: [],
|
||||
id2perms: {},
|
||||
btnName: '新增资源类型',
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
},
|
||||
tableColumns: [
|
||||
{
|
||||
title: '资源类型名',
|
||||
field: 'name',
|
||||
minWidth: '175px',
|
||||
fixed: 'left',
|
||||
showOverflow: 'tooltip',
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
field: 'description',
|
||||
minWidth: '175px',
|
||||
},
|
||||
{
|
||||
title: '权限',
|
||||
field: 'id',
|
||||
minWidth: '300px',
|
||||
slots: {
|
||||
default: 'id_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
minWidth: '175px',
|
||||
slots: { default: 'action_default' },
|
||||
fixed: 'right',
|
||||
},
|
||||
],
|
||||
searchName: '',
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {},
|
||||
mounted() {
|
||||
this.searchData()
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.tablePage = {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
}
|
||||
this.searchData()
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
if (!newVal) {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inject: ['reload'],
|
||||
|
||||
methods: {
|
||||
searchData() {
|
||||
const { currentPage, pageSize } = this.tablePage
|
||||
this.loading = true
|
||||
const param = {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
page_size: pageSize,
|
||||
page: currentPage,
|
||||
q: this.searchName,
|
||||
}
|
||||
searchResourceType(param).then((res) => {
|
||||
this.tablePage = { ...this.tablePage, total: res.numfound, currentPage: res.page }
|
||||
this.groups = res.groups
|
||||
this.id2perms = res.id2perms
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleEdit(record) {
|
||||
var perms = []
|
||||
var permList = this.id2perms[record.id]
|
||||
if (permList) {
|
||||
for (var i = 0; i < permList.length; i++) {
|
||||
perms.push(permList[i].name)
|
||||
}
|
||||
}
|
||||
record.perms = perms
|
||||
this.$refs.resourceTypeForm.handleEdit(record)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteResourceType(record.id)
|
||||
},
|
||||
handleOk() {
|
||||
this.searchData()
|
||||
},
|
||||
handleCreate() {
|
||||
this.$refs.resourceTypeForm.handleCreate()
|
||||
},
|
||||
deleteResourceType(id) {
|
||||
deleteResourceTypeById(id).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resource-types-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
378
cmdb-ui/src/modules/acl/views/resources.vue
Normal file
378
cmdb-ui/src/modules/acl/views/resources.vue
Normal file
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<div v-if="allResourceTypes.length > 0">
|
||||
<a-tabs default-active-key="1" @change="loadCurrentType">
|
||||
<a-tab-pane v-for="rtype in allResourceTypes" :key="rtype.id" :tab="rtype.name"> </a-tab-pane>
|
||||
</a-tabs>
|
||||
<div class="resources-action-btn">
|
||||
<a-space>
|
||||
<a-button @click="handleCreate" type="primary">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
placeholder="搜索 | 资源名"
|
||||
v-model="searchName"
|
||||
@search="
|
||||
() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
"
|
||||
></a-input-search>
|
||||
<a-dropdown v-if="selectedRows && selectedRows.length">
|
||||
<a-button type="primary" ghost>
|
||||
批量操作
|
||||
</a-button>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item @click="handleBatchPerm">
|
||||
<a href="javascript:;">授权</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleBatchRevoke">
|
||||
<a href="javascript:;">权限回收</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
|
||||
<span
|
||||
v-if="selectedRows && selectedRows.length"
|
||||
>已选择<strong>{{ selectedRows.length }}</strong
|
||||
>项</span
|
||||
>
|
||||
</a-space>
|
||||
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
@click="
|
||||
() => {
|
||||
$refs.resourceBatchPerm.open(currentType.id)
|
||||
}
|
||||
"
|
||||
>便捷授权</a-button
|
||||
>
|
||||
<a-switch
|
||||
v-model="isGroup"
|
||||
@change="
|
||||
() => {
|
||||
searchName = ''
|
||||
tablePage.currentPage = 1
|
||||
searchData()
|
||||
selectedRows = []
|
||||
$refs.xTable && $refs.xTable.clearCheckboxRow()
|
||||
$refs.xTable && $refs.xTable.clearCheckboxReserve()
|
||||
}
|
||||
"
|
||||
un-checked-children="组"
|
||||
></a-switch>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-spin
|
||||
:spinning="loading"
|
||||
><vxe-grid
|
||||
:columns="tableColumns"
|
||||
:data="tableData"
|
||||
highlight-hover-row
|
||||
:max-height="`${windowHeight - 245}px`"
|
||||
:checkbox-config="{ reserve: true }"
|
||||
@checkbox-change="changeCheckbox"
|
||||
@checkbox-all="changeCheckbox"
|
||||
ref="xTable"
|
||||
row-id="id"
|
||||
show-overflow
|
||||
>
|
||||
<template #name_header>
|
||||
{{ isGroup ? '资源组名' : '资源名' }}
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<span v-show="isGroup">
|
||||
<a @click="handleDisplayMember(row)">成员</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="handleGroupEdit(row)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
</span>
|
||||
<a-tooltip title="查看授权">
|
||||
<a @click="handlePerm(row)"><a-icon type="eye"/></a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<a-tooltip title="授权">
|
||||
<a :style="{ color: '#4bbb13' }" @click="handlePermManage(row)">
|
||||
<a-icon type="usergroup-add" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" @cancel="cancel" okText="是" cancelText="否">
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<template #pager>
|
||||
<vxe-pager
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
</vxe-pager>
|
||||
</template> </vxe-grid
|
||||
></a-spin>
|
||||
</div>
|
||||
<div v-else style="text-align: center">
|
||||
<a-icon style="font-size:50px; margin-bottom: 20px; color: orange" type="info-circle" />
|
||||
<h3>暂无类型信息,请先添加资源类型!</h3>
|
||||
</div>
|
||||
<resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm>
|
||||
<resourcePermForm ref="resourcePermForm"> </resourcePermForm>
|
||||
<ResourcePermManageForm
|
||||
ref="resourcePermManageForm"
|
||||
:groupTypeMessage="currentType"
|
||||
@close="closePerm"
|
||||
></ResourcePermManageForm>
|
||||
<resource-group-modal ref="resourceGroupModal"></resource-group-modal>
|
||||
<resource-group-member ref="resourceGroupMember"></resource-group-member>
|
||||
<ResourceBatchPerm ref="resourceBatchPerm" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import resourceForm from './module/resourceForm'
|
||||
import resourcePermForm from './module/resourcePermForm'
|
||||
import ResourceGroupModal from './module/resourceGroupModal'
|
||||
import ResourceGroupMember from './module/resourceGroupMember'
|
||||
import ResourcePermManageForm from './module/resourcePermManageForm'
|
||||
import ResourceBatchPerm from './module/resourceBatchPerm.vue'
|
||||
import {
|
||||
deleteResourceById,
|
||||
searchResource,
|
||||
searchResourceType,
|
||||
getResourceGroups,
|
||||
deleteResourceGroup,
|
||||
} from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'Resources',
|
||||
components: {
|
||||
resourceForm,
|
||||
resourcePermForm,
|
||||
ResourceGroupModal,
|
||||
ResourceGroupMember,
|
||||
ResourcePermManageForm,
|
||||
ResourceBatchPerm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
},
|
||||
tableData: [],
|
||||
tableColumns: [
|
||||
{
|
||||
type: 'checkbox',
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '资源名',
|
||||
field: 'name',
|
||||
minWidth: '150px',
|
||||
showOverflow: 'tooltip',
|
||||
fixed: 'left',
|
||||
slots: {
|
||||
header: 'name_header',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建者',
|
||||
minWidth: '100px',
|
||||
field: 'user',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
minWidth: '220px',
|
||||
field: 'created_at',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '最后修改时间',
|
||||
minWidth: '220px',
|
||||
field: 'updated_at',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
width: '200px',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
},
|
||||
],
|
||||
btnName: '新增资源',
|
||||
isGroup: false,
|
||||
allResourceTypes: [],
|
||||
currentType: { id: 0 },
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
searchName: '',
|
||||
selectedRows: [],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
computed: mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
mounted() {
|
||||
// this.getAllResourceTypes()
|
||||
},
|
||||
inject: ['reload'],
|
||||
beforeRouteEnter(to, from, next) {
|
||||
next((vm) => {
|
||||
vm.getAllResourceTypes()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async searchData() {
|
||||
const { currentPage, pageSize } = this.tablePage
|
||||
const param = {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
resource_type_id: this.currentType.id,
|
||||
page_size: pageSize,
|
||||
page: currentPage,
|
||||
q: this.searchName,
|
||||
}
|
||||
this.loading = true
|
||||
if (!this.isGroup) {
|
||||
await searchResource(param).then((res) => {
|
||||
this.tablePage = { ...this.tablePage, total: res.numfound, currentPage: res.page }
|
||||
this.tableData = res.resources
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
await getResourceGroups(param).then((res) => {
|
||||
this.tablePage = { ...this.tablePage, total: res.numfound, currentPage: res.page }
|
||||
this.tableData = res.groups
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
handleDisplayMember(record) {
|
||||
this.$refs['resourceGroupMember'].handleEdit(record)
|
||||
},
|
||||
handleGroupEdit(record) {
|
||||
this.$refs['resourceGroupModal'].handleEdit(record)
|
||||
},
|
||||
async getAllResourceTypes() {
|
||||
await searchResourceType({ page_size: 9999, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.allResourceTypes = res.groups
|
||||
if (res.groups && res.groups.length) {
|
||||
this.loadCurrentType(res.groups[0].id)
|
||||
}
|
||||
})
|
||||
},
|
||||
handlePermManage(record) {
|
||||
this.$refs['resourcePermManageForm'].editPerm(record, this.isGroup) // todo fix
|
||||
},
|
||||
loadCurrentType(rtypeId) {
|
||||
this.searchName = ''
|
||||
this.selectedRows = []
|
||||
this.tablePage.currentPage = 1
|
||||
this.$refs.xTable && this.$refs.xTable.clearCheckboxRow()
|
||||
this.$refs.xTable && this.$refs.xTable.clearCheckboxReserve()
|
||||
if (rtypeId) {
|
||||
this.currentType = this.allResourceTypes.find((item) => item.id === rtypeId)
|
||||
}
|
||||
this.searchData()
|
||||
},
|
||||
handlePerm(record) {
|
||||
this.$refs.resourcePermForm.handlePerm(record, this.isGroup)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteResource(record.id)
|
||||
},
|
||||
handleOk() {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
},
|
||||
handleCreate() {
|
||||
this.$refs.resourceForm.handleCreate(this.currentType)
|
||||
},
|
||||
deleteResource(id) {
|
||||
if (!this.isGroup) {
|
||||
deleteResourceById(id).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
} else {
|
||||
deleteResourceGroup(id).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
cancel() {},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
},
|
||||
changeCheckbox({ records }) {
|
||||
// console.log(records)
|
||||
this.selectedRows = this.$refs.xTable.getCheckboxRecords().concat(this.$refs.xTable.getCheckboxReserveRecords())
|
||||
},
|
||||
handleBatchPerm() {
|
||||
this.$refs['resourcePermManageForm'].editPerm(this.selectedRows, this.isGroup)
|
||||
},
|
||||
handleBatchRevoke() {
|
||||
this.$refs['resourcePermManageForm'].editPerm(this.selectedRows, this.isGroup, 'revoke')
|
||||
},
|
||||
closePerm() {
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.selectedRows = []
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.isGroup = false
|
||||
this.tablePage = {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
}
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (!newVal) {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resources-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.ant-switch {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
311
cmdb-ui/src/modules/acl/views/roles.vue
Normal file
311
cmdb-ui/src/modules/acl/views/roles.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<div class="acl-role">
|
||||
<div class="roles-action-btn">
|
||||
<a-button @click="handleCreate" type="primary">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
allowClear
|
||||
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
|
||||
placeholder="搜索 | 角色名"
|
||||
v-model="searchName"
|
||||
@search="
|
||||
() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
}
|
||||
"
|
||||
></a-input-search>
|
||||
<a-checkbox :checked="is_all" @click="handleClickBoxChange">所有角色</a-checkbox>
|
||||
</div>
|
||||
<a-spin :spinning="loading">
|
||||
<vxe-grid
|
||||
:columns="tableColumns"
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - 185}px`"
|
||||
highlight-hover-row
|
||||
min-height="300px"
|
||||
:filter-config="{ remote: true }"
|
||||
@filter-change="filterTableMethod"
|
||||
>
|
||||
<template #is_app_admin_default="{row}">
|
||||
<a-icon type="check" v-if="row.is_app_admin" />
|
||||
</template>
|
||||
<template #inherit_default="{row}">
|
||||
<a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag>
|
||||
</template>
|
||||
<template #isVisualRole_default="{row}">
|
||||
{{ row.uid ? '否' : '是' }}
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<div style="width:300px">
|
||||
<span>
|
||||
<a-tooltip title="资源列表">
|
||||
<a
|
||||
v-if="$route.name !== 'acl_roles'"
|
||||
@click="handleDisplayUserResource(row)"
|
||||
><a-icon
|
||||
type="file-search"
|
||||
/></a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<div v-if="!row.uid" style="display: inline-block">
|
||||
<a-tooltip
|
||||
title="用户列表"
|
||||
><a @click="handleDisplayUserUnderRole(row)"><a-icon type="team"/></a
|
||||
></a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
</div>
|
||||
<a @click="handleEdit(row)"><a-icon type="edit"/></a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" @cancel="cancel" okText="是" cancelText="否">
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #pager>
|
||||
<vxe-pager
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
</vxe-pager>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</a-spin>
|
||||
|
||||
<roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm>
|
||||
<resource-user-form ref="ruf"></resource-user-form>
|
||||
<users-under-role-form ref="usersUnderRoleForm" :allRoles="allRoles"></users-under-role-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import roleForm from './module/roleForm'
|
||||
import resourceUserForm from './module/resourceUserForm'
|
||||
import usersUnderRoleForm from './module/usersUnderRoleForm'
|
||||
import { deleteRoleById, searchRole } from '@/modules/acl/api/role'
|
||||
|
||||
export default {
|
||||
name: 'Roles',
|
||||
components: {
|
||||
roleForm,
|
||||
resourceUserForm,
|
||||
usersUnderRoleForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
},
|
||||
tableColumns: [
|
||||
{
|
||||
title: '角色名',
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
minWidth: '150px',
|
||||
fixed: 'left',
|
||||
showOverflow: 'tooltip',
|
||||
},
|
||||
{
|
||||
title: '管理员',
|
||||
field: 'is_app_admin',
|
||||
minWidth: '100px',
|
||||
align: 'center',
|
||||
slots: { default: 'is_app_admin_default' },
|
||||
},
|
||||
{
|
||||
title: '继承自',
|
||||
field: 'id',
|
||||
minWidth: '150px',
|
||||
slots: { default: 'inherit_default' },
|
||||
},
|
||||
{
|
||||
title: '虚拟角色',
|
||||
field: 'uid',
|
||||
minWidth: '100px',
|
||||
align: 'center',
|
||||
filters: [
|
||||
{ label: '是', value: 1 },
|
||||
{ label: '否', value: 0 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
filterMethod: ({ value, row }) => {
|
||||
return value === !row.uid
|
||||
},
|
||||
slots: { default: 'isVisualRole_default' },
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
minWidth: '280px',
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action_default' },
|
||||
},
|
||||
],
|
||||
btnName: '新增虚拟角色',
|
||||
is_all: this.$route.name === 'acl_roles',
|
||||
tableData: [],
|
||||
allRoles: [],
|
||||
id2parents: {},
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
searchName: '',
|
||||
filterTableValue: { user_role: 1, user_only: 0 },
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.tablePage = {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
}
|
||||
this.tableData = []
|
||||
this.allRoles = []
|
||||
this.loadData()
|
||||
this.initData()
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (!newVal) {
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
handleClickBoxChange() {
|
||||
this.is_all = !this.is_all
|
||||
this.$nextTick(() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
initData() {
|
||||
console.log(111)
|
||||
searchRole({
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
page_size: 9999,
|
||||
}).then((res) => {
|
||||
this.allRoles = res.roles
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
console.log(222)
|
||||
this.loading = true
|
||||
const { currentPage, pageSize } = this.tablePage
|
||||
searchRole({
|
||||
...this.filterTableValue,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
page_size: pageSize,
|
||||
page: currentPage,
|
||||
is_all: this.is_all,
|
||||
q: this.searchName,
|
||||
})
|
||||
.then((res) => {
|
||||
this.tableData = res.roles
|
||||
this.id2parents = res.id2parents
|
||||
this.tablePage.total = res.numfound
|
||||
this.loading = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleDisplayUserResource(record) {
|
||||
this.$refs.ruf.loadUserResource(record)
|
||||
},
|
||||
handleDisplayUserUnderRole(record) {
|
||||
this.$refs.usersUnderRoleForm.handleProcessRole(record.id)
|
||||
},
|
||||
handleEdit(record) {
|
||||
this.$refs.roleForm.handleEdit(record)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteRole(record.id)
|
||||
},
|
||||
handleOk() {
|
||||
this.loadData()
|
||||
this.initData()
|
||||
},
|
||||
|
||||
handleCreate() {
|
||||
this.$refs.roleForm.handleCreate()
|
||||
},
|
||||
|
||||
deleteRole(id) {
|
||||
deleteRoleById(id, { app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
cancel(e) {
|
||||
return false
|
||||
},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.loadData()
|
||||
},
|
||||
filterTableMethod({ column, property, values, datas, filterList, $event }) {
|
||||
if (property === 'uid') {
|
||||
if (values[0] === 1) {
|
||||
this.filterTableValue = { user_role: 0 }
|
||||
} else if (values[0] === 0) {
|
||||
this.filterTableValue = {
|
||||
user_only: 1,
|
||||
}
|
||||
} else {
|
||||
this.filterTableValue = {
|
||||
user_role: 1,
|
||||
user_only: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.acl-role {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
.roles-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
.ant-checkbox-wrapper {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
.vxe-table--filter-body {
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
98
cmdb-ui/src/modules/acl/views/secretKey.vue
Normal file
98
cmdb-ui/src/modules/acl/views/secretKey.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="acl-secret-key">
|
||||
<a-form-model
|
||||
ref="secretKeyForm"
|
||||
:model="displayForm"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-form-model-item label="Key" prop="key">
|
||||
<a-input disabled v-model="displayForm.key" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="Secret" prop="secret">
|
||||
<a-input disabled v-model="displayForm.secret" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label=" " :colon="false">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="changeVisible">{{ !visible ? '查看' : '隐藏' }}</a-button>
|
||||
<a-button type="danger" ghost @click="handleSumbit">重置</a-button>
|
||||
<!-- <a-button @click="handleCancel">取消</a-button> -->
|
||||
</a-space>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSecret, updateSecret } from '../api/secretKey'
|
||||
export default {
|
||||
name: 'SecretKey',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
key: '',
|
||||
secret: '',
|
||||
},
|
||||
rules: {
|
||||
key: [{ required: true, message: 'key is required' }],
|
||||
secret: [{ required: true, message: 'secret is required' }],
|
||||
},
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayForm() {
|
||||
return {
|
||||
key: this.visible ? this.form.key : this.form.key.replace(/./g, '*'),
|
||||
secret: this.visible ? this.form.secret : this.form.secret.replace(/./g, '*'),
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getOriginSecret()
|
||||
},
|
||||
methods: {
|
||||
getOriginSecret() {
|
||||
getSecret().then((res) => {
|
||||
const { key, secret } = res
|
||||
this.form = { key, secret }
|
||||
})
|
||||
},
|
||||
handleSumbit() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '重置',
|
||||
content: '确定重置用户密钥?',
|
||||
onOk() {
|
||||
that.$refs.secretKeyForm.validate((valid) => {
|
||||
if (valid) {
|
||||
updateSecret().then((res) => {
|
||||
that.$message.success('重置成功')
|
||||
const { key, secret } = res
|
||||
that.form = { key, secret }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
handleCancel() {
|
||||
this.getOriginSecret()
|
||||
},
|
||||
changeVisible() {
|
||||
this.visible = !this.visible
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.acl-secret-key {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
.ant-input[disabled] {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
318
cmdb-ui/src/modules/acl/views/trigger.vue
Normal file
318
cmdb-ui/src/modules/acl/views/trigger.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<div class="trigger-action-btn">
|
||||
<a-button type="primary" @click="handleCreateTrigger">新增触发器</a-button>
|
||||
<a-input-search
|
||||
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
|
||||
placeholder="搜索 | 名称"
|
||||
v-model="searchName"
|
||||
allowClear
|
||||
@search="filter"
|
||||
></a-input-search>
|
||||
</div>
|
||||
|
||||
<vxe-grid
|
||||
:columns="tableColumns"
|
||||
:data="filterTriggers"
|
||||
:max-height="`${windowHeight - 185}px`"
|
||||
highlight-hover-row
|
||||
>
|
||||
<template #wildcard_default="{row}">
|
||||
<div style="word-break: break-word">
|
||||
<span>{{ row.wildcard }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #resourceTypeRender_default="{row}">
|
||||
{{
|
||||
resourceTypeList.filter((item) => item.id === row.resource_type_id)[0]
|
||||
? resourceTypeList.filter((item) => item.id === row.resource_type_id)[0].name
|
||||
: 'unkown'
|
||||
}}
|
||||
</template>
|
||||
<template #users_default="{row}">
|
||||
<span
|
||||
v-for="(u, index) in row.users"
|
||||
:key="index"
|
||||
>{{ u }}<a-divider
|
||||
v-if="index < row.users.length - 1"
|
||||
type="vertical"
|
||||
/></span>
|
||||
</template>
|
||||
<template #permissions_default="{row}">
|
||||
<a-tag v-for="(p, index) in row.permissions" :key="index">{{ p }}</a-tag>
|
||||
</template>
|
||||
<template #enabled_default="{row}">
|
||||
<a-tag v-if="row.enabled" color="#2db7f5">启用</a-tag>
|
||||
<a-tag v-else color="grey">禁用</a-tag>
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<a-space>
|
||||
<a-tooltip title="应用">
|
||||
<a @click="handleApplyTrigger(row)" :style="{ color: '#0f9d58' }"><a-icon type="appstore"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="取消">
|
||||
<a @click="handleCancelTrigger(row)" :style="{ color: 'orange' }"><a-icon type="stop"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="查看正则匹配结果">
|
||||
<a @click="handlePattern(row)" :style="{ color: 'purple' }"><a-icon type="eye"/></a>
|
||||
</a-tooltip>
|
||||
<a @click="handleEditTrigger(row)"><a-icon type="edit"/></a>
|
||||
<a @click="handleDeleteTrigger(row)" :style="{ color: 'red' }"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
<trigger-form
|
||||
ref="triggerForm"
|
||||
:roles="roles"
|
||||
:resourceTypeList="resourceTypeList"
|
||||
:id2perms="id2perms"
|
||||
@refresh="loadTriggers"
|
||||
:app_id="app_id"
|
||||
></trigger-form>
|
||||
<TriggerPattern ref="triggerPattern" :roles="roles" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import TriggerForm from './module/triggerForm'
|
||||
import TriggerPattern from './module/triggerPattern'
|
||||
import { getTriggers, deleteTrigger, applyTrigger, cancelTrigger } from '@/modules/acl/api/trigger'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ACLTrigger',
|
||||
components: { TriggerForm, TriggerPattern },
|
||||
data() {
|
||||
return {
|
||||
roles: [],
|
||||
searchName: '',
|
||||
resourceTypeList: [],
|
||||
filterTriggers: [],
|
||||
triggers: [],
|
||||
id2parents: [],
|
||||
id2perms: {},
|
||||
tableColumns: [
|
||||
{
|
||||
title: '名称',
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
minWidth: '150px',
|
||||
fixed: 'left',
|
||||
showOverflow: 'tooltip',
|
||||
},
|
||||
{
|
||||
title: '资源名',
|
||||
field: 'wildcard',
|
||||
minWidth: '250px',
|
||||
showOverflow: 'tooltip',
|
||||
slots: {
|
||||
default: 'wildcard_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '资源类型',
|
||||
field: 'resource_type_id',
|
||||
minWidth: '100px',
|
||||
slots: {
|
||||
default: 'resourceTypeRender_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
field: 'users',
|
||||
minWidth: '150px',
|
||||
showOverflow: 'tooltip',
|
||||
slots: {
|
||||
default: 'users_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
field: 'roles',
|
||||
minWidth: '150px',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
const roleNameList = row.roles.map((item) => {
|
||||
const temp = this.roles.find((a) => a.id === item)
|
||||
if (temp) {
|
||||
return temp.name
|
||||
}
|
||||
return 'unknown'
|
||||
})
|
||||
return [
|
||||
<div>
|
||||
{roleNameList.length <= 1 && roleNameList.map((item) => <span key={item}>{item}</span>)}
|
||||
{roleNameList.length > 1 && (
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{roleNameList.map((item, idx) => (
|
||||
<span>
|
||||
<span key={item}>{item}</span>
|
||||
{idx !== roleNameList.length - 1 && <a-divider type="vertical" />}
|
||||
</span>
|
||||
))}
|
||||
</template>
|
||||
<span>{roleNameList[0]}...</span>
|
||||
</a-tooltip>
|
||||
)}
|
||||
</div>,
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '权限',
|
||||
field: 'permissions',
|
||||
minWidth: '250px',
|
||||
slots: {
|
||||
default: 'permissions_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
field: 'enabled',
|
||||
minWidth: '100px',
|
||||
slots: {
|
||||
default: 'enabled_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
minWidth: '200px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadRoles()
|
||||
this.loadResourceTypeList()
|
||||
},
|
||||
beforeMount() {
|
||||
this.loadTriggers()
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
app_id() {
|
||||
return this.$route.name.split('_')[0]
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadTriggers() {
|
||||
this.searchName = ''
|
||||
getTriggers({ app_id: this.app_id }).then((res) => {
|
||||
this.triggers = res
|
||||
this.filterTriggers = res
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
loadRoles() {
|
||||
searchRole({ app_id: this.app_id, page_size: 9999 }).then((res) => {
|
||||
this.roles = res.roles
|
||||
this.id2parents = res.id2parents
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
loadResourceTypeList() {
|
||||
searchResourceType({ app_id: this.app_id, page_size: 9999 }).then((res) => {
|
||||
this.resourceTypeList = res['groups']
|
||||
this.id2perms = res['id2perms']
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
handleCreateTrigger() {
|
||||
this.$refs.triggerForm.handleEdit(null)
|
||||
},
|
||||
handleDeleteTrigger(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '删除',
|
||||
content: '确认删除该触发器吗?',
|
||||
onOk() {
|
||||
deleteTrigger(record.id).then((res) => {
|
||||
that.$message.success('删除成功')
|
||||
that.loadTriggers()
|
||||
})
|
||||
// .catch(err => that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
handleApplyTrigger(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '规则应用',
|
||||
content: '是否确定应用该触发器?',
|
||||
onOk() {
|
||||
applyTrigger(record.id).then((res) => {
|
||||
that.$message.success('提交成功!')
|
||||
})
|
||||
// .catch(err => that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
handleCancelTrigger(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '规则应用',
|
||||
content: '是否取消应用该触发器?',
|
||||
onOk() {
|
||||
cancelTrigger(record.id).then((res) => {
|
||||
that.$message.success('提交成功!')
|
||||
})
|
||||
// .catch(err => that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
handleEditTrigger(record) {
|
||||
this.$refs.triggerForm.handleEdit(record)
|
||||
},
|
||||
filter() {
|
||||
if (this.searchName) {
|
||||
this.filterTriggers = this.triggers.filter((item) =>
|
||||
item.name.toLowerCase().includes(this.searchName.toLowerCase())
|
||||
)
|
||||
} else {
|
||||
this.filterTriggers = this.triggers
|
||||
}
|
||||
},
|
||||
handlePattern(row) {
|
||||
const { wildcard, uid, resource_type_id } = row
|
||||
this.$refs.triggerPattern.open({
|
||||
resource_type_id,
|
||||
app_id: this.app_id,
|
||||
owner: uid,
|
||||
pattern: wildcard,
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(oldName, newName) {
|
||||
this.loadTriggers()
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (!newVal) {
|
||||
this.filter()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.trigger-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
181
cmdb-ui/src/modules/acl/views/users.vue
Normal file
181
cmdb-ui/src/modules/acl/views/users.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<div class="user-action-btn">
|
||||
<a-button v-if="isAclAdmin" @click="handleCreate" type="primary">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
allowClear
|
||||
:style="{ display: 'inline', marginLeft: '10px' }"
|
||||
placeholder="搜索 | 用户名、中文名"
|
||||
v-model="searchName"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<a-spin :spinning="loading">
|
||||
<vxe-grid :columns="tableColumns" :data="tableData" highlight-hover-row :max-height="`${windowHeight - 185}px`">
|
||||
<template #block_default="{row}">
|
||||
<a-icon type="lock" v-if="row.block" />
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<template>
|
||||
<a :disabled="isAclAdmin ? false : true" @click="handleEdit(row)">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-tooltip title="权限汇总">
|
||||
<a @click="handlePermCollect(row)"><a-icon type="solution"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</a-spin>
|
||||
<userForm ref="userForm" :handleOk="handleOk"> </userForm>
|
||||
<perm-collect-form ref="permCollectForm"></perm-collect-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import userForm from './module/userForm'
|
||||
import PermCollectForm from './module/permCollectForm'
|
||||
import { deleteUserById, searchUser, getOnDutyUser } from '@/modules/acl/api/user'
|
||||
|
||||
export default {
|
||||
name: 'Users',
|
||||
components: {
|
||||
userForm,
|
||||
PermCollectForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
tableColumns: [
|
||||
{
|
||||
title: '用户名',
|
||||
field: 'username',
|
||||
sortable: true,
|
||||
minWidth: '100px',
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '中文名',
|
||||
field: 'nickname',
|
||||
minWidth: '100px',
|
||||
},
|
||||
{
|
||||
title: '加入时间',
|
||||
field: 'date_joined',
|
||||
minWidth: '160px',
|
||||
},
|
||||
{
|
||||
title: '锁定',
|
||||
field: 'block',
|
||||
minWidth: '100px',
|
||||
slots: {
|
||||
default: 'block_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
minWidth: '180px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
},
|
||||
],
|
||||
onDutuUids: [],
|
||||
btnName: '新增用户',
|
||||
allUsers: [],
|
||||
tableData: [],
|
||||
searchName: '',
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
async beforeMount() {
|
||||
this.loading = true
|
||||
await getOnDutyUser().then(res => {
|
||||
this.onDutuUids = res.map(i => i.uid)
|
||||
this.search()
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
isAclAdmin: function() {
|
||||
if (this.$store.state.user.roles.permissions.filter(item => item === 'acl_admin').length > 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
inject: ['reload'],
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
searchUser({ page_size: 10000 }).then(res => {
|
||||
const ret = res.users.filter(u => this.onDutuUids.includes(u.uid))
|
||||
this.allUsers = ret
|
||||
this.tableData = ret
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handlePermCollect(record) {
|
||||
this.$refs['permCollectForm'].collect(record)
|
||||
},
|
||||
handleEdit(record) {
|
||||
this.$refs.userForm.handleEdit(record)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteUser(record.uid)
|
||||
},
|
||||
handleOk() {
|
||||
this.searchName = ''
|
||||
this.search()
|
||||
},
|
||||
|
||||
handleCreate() {
|
||||
this.$refs.userForm.handleCreate()
|
||||
},
|
||||
|
||||
deleteUser(attrId) {
|
||||
deleteUserById(attrId).then(res => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
},
|
||||
watch: {
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
if (newVal) {
|
||||
this.tableData = this.allUsers.filter(
|
||||
item =>
|
||||
item.username.toLowerCase().includes(newVal.toLowerCase()) ||
|
||||
item.nickname.toLowerCase().includes(newVal.toLowerCase())
|
||||
)
|
||||
} else {
|
||||
this.tableData = this.allUsers
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-action-btn {
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user