[Feature][UI Next][V1.0.0-Alpha]: Refactor the user manage page unde… (#8839)
* [Feature][UI Next][V1.0.0-Alpha]: Refactor the user manage page under security. * [Feature][UI Next][V1.0.0-Alpha]: Add license into types file.slim
parent
5aeb753ae8
commit
e0ee6f1f2f
|
|
@ -1014,18 +1014,23 @@ const security = {
|
|||
authorize_udf: 'UDF Function Authorize',
|
||||
username: 'Username',
|
||||
username_exists: 'The username already exists',
|
||||
username_rule_msg: 'Please enter username',
|
||||
user_password: 'Please enter password',
|
||||
user_password_rule_msg:
|
||||
username_tips: 'Please enter username',
|
||||
user_password: 'Password',
|
||||
user_password_tips:
|
||||
'Please enter a password containing letters and numbers with a length between 6 and 20',
|
||||
user_type: 'User Type',
|
||||
ordinary_user: 'Ordinary users',
|
||||
administrator: 'Administrator',
|
||||
tenant_code: 'Tenant',
|
||||
tenant_id_rule_msg: 'Please select tenant',
|
||||
tenant_id_tips: 'Please select tenant',
|
||||
queue: 'Queue',
|
||||
queue_tips: 'Please select a queue',
|
||||
email: 'Email',
|
||||
email_rule_msg: 'Please enter valid email',
|
||||
email_empty_tips: 'Please enter email',
|
||||
emial_correct_tips: 'Please enter the correct email format',
|
||||
phone: 'Phone',
|
||||
phone_rule_msg: 'Please enter valid phone number',
|
||||
phone_empty_tips: 'Please enter phone number',
|
||||
phone_correct_tips: 'Please enter the correct mobile phone format',
|
||||
state: 'State',
|
||||
state_enabled: 'Enabled',
|
||||
state_disabled: 'Disabled',
|
||||
|
|
@ -1038,7 +1043,9 @@ const security = {
|
|||
save_error_msg: 'Failed to save, please retry',
|
||||
delete_error_msg: 'Failed to delete, please retry',
|
||||
auth_error_msg: 'Failed to authorize, please retry',
|
||||
auth_success_msg: 'Authorize succeeded'
|
||||
auth_success_msg: 'Authorize succeeded',
|
||||
enable: 'Enable',
|
||||
disable: 'Disable'
|
||||
},
|
||||
alarm_instance: {
|
||||
search_input_tips: 'Please input the keywords',
|
||||
|
|
|
|||
|
|
@ -990,7 +990,6 @@ const security = {
|
|||
update_user: '更新用户',
|
||||
delete_user: '删除用户',
|
||||
delete_confirm: '确定删除吗?',
|
||||
delete_confirm_tip: '删除用户属于危险操作,请谨慎操作!',
|
||||
project: '项目',
|
||||
resource: '资源',
|
||||
file_resource: '文件资源',
|
||||
|
|
@ -1003,17 +1002,22 @@ const security = {
|
|||
authorize_udf: 'UDF函数授权',
|
||||
username: '用户名',
|
||||
username_exists: '用户名已存在',
|
||||
username_rule_msg: '请输入用户名',
|
||||
username_tips: '请输入用户名',
|
||||
user_password: '密码',
|
||||
user_password_rule_msg: '请输入包含字母和数字,长度在6~20之间的密码',
|
||||
user_password_tips: '请输入包含字母和数字,长度在6~20之间的密码',
|
||||
user_type: '用户类型',
|
||||
ordinary_user: '普通用户',
|
||||
administrator: '管理员',
|
||||
tenant_code: '租户',
|
||||
tenant_id_rule_msg: '请选择租户',
|
||||
tenant_id_tips: '请选择租户',
|
||||
queue: '队列',
|
||||
queue_tips: '默认为租户关联队列',
|
||||
email: '邮件',
|
||||
email_rule_msg: '请输入正确的邮箱',
|
||||
email_empty_tips: '请输入邮箱',
|
||||
emial_correct_tips: '请输入正确的邮箱格式',
|
||||
phone: '手机',
|
||||
phone_rule_msg: '请输入正确的手机号',
|
||||
phone_empty_tips: '请输入手机号码',
|
||||
phone_correct_tips: '请输入正确的手机格式',
|
||||
state: '状态',
|
||||
state_enabled: '启用',
|
||||
state_disabled: '停用',
|
||||
|
|
@ -1026,7 +1030,9 @@ const security = {
|
|||
save_error_msg: '保存失败,请重试',
|
||||
delete_error_msg: '删除失败,请重试',
|
||||
auth_error_msg: '授权失败,请重试',
|
||||
auth_success_msg: '授权成功'
|
||||
auth_success_msg: '授权成功',
|
||||
enable: '启用',
|
||||
disable: '停用'
|
||||
},
|
||||
alarm_instance: {
|
||||
search_input_tips: '请输入关键字',
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export function createDataSource(data: IDataSource): any {
|
|||
})
|
||||
}
|
||||
|
||||
export function authedDatasource(params: UserIdReq) {
|
||||
export function authedDatasource(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/datasources/authed-datasource',
|
||||
method: 'get',
|
||||
|
|
@ -80,7 +80,7 @@ export function queryDataSourceList(params: TypeReq): any {
|
|||
})
|
||||
}
|
||||
|
||||
export function unAuthDatasource(params: UserIdReq) {
|
||||
export function unAuthDatasource(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/datasources/unauth-datasource',
|
||||
method: 'get',
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function createProject(data: ProjectsReq): any {
|
|||
})
|
||||
}
|
||||
|
||||
export function queryAuthorizedProject(params: UserIdReq) {
|
||||
export function queryAuthorizedProject(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/projects/authed-project',
|
||||
method: 'get',
|
||||
|
|
@ -56,7 +56,7 @@ export function queryAllProjectList(): any {
|
|||
})
|
||||
}
|
||||
|
||||
export function queryUnauthorizedProject(params: UserIdReq) {
|
||||
export function queryUnauthorizedProject(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/projects/unauth-project',
|
||||
method: 'get',
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export function createResource(
|
|||
})
|
||||
}
|
||||
|
||||
export function authorizedFile(params: UserIdReq) {
|
||||
export function authorizedFile(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/resources/authed-file',
|
||||
method: 'get',
|
||||
|
|
@ -73,7 +73,7 @@ export function authorizedFile(params: UserIdReq) {
|
|||
})
|
||||
}
|
||||
|
||||
export function authorizeResourceTree(params: UserIdReq) {
|
||||
export function authorizeResourceTree(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/resources/authed-resource-tree',
|
||||
method: 'get',
|
||||
|
|
@ -81,7 +81,7 @@ export function authorizeResourceTree(params: UserIdReq) {
|
|||
})
|
||||
}
|
||||
|
||||
export function authUDFFunc(params: UserIdReq) {
|
||||
export function authUDFFunc(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/resources/authed-udf-func',
|
||||
method: 'get',
|
||||
|
|
@ -158,7 +158,7 @@ export function deleteUdfFunc(id: number): any {
|
|||
})
|
||||
}
|
||||
|
||||
export function unAuthUDFFunc(params: UserIdReq) {
|
||||
export function unAuthUDFFunc(params: UserIdReq): any {
|
||||
return axios({
|
||||
url: '/resources/unauth-udf-func',
|
||||
method: 'get',
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export function listAll(params?: ListAllReq): any {
|
|||
})
|
||||
}
|
||||
|
||||
export function queryUserList(params: ListReq) {
|
||||
export function queryUserList(params: ListReq): any {
|
||||
return axios({
|
||||
url: '/users/list-paging',
|
||||
method: 'get',
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ interface AlertGroupIdReq {
|
|||
}
|
||||
|
||||
interface UserReq {
|
||||
email?: string
|
||||
tenantId?: number
|
||||
userName?: string
|
||||
userPassword?: string
|
||||
email: string
|
||||
tenantId: number
|
||||
userName: string
|
||||
userPassword: string
|
||||
phone?: string
|
||||
queue?: string
|
||||
state?: number
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function removeUselessChildren(
|
||||
list: { children?: []; dirctory?: boolean; disabled?: boolean }[]
|
||||
) {
|
||||
if (!list.length) return
|
||||
list.forEach((item) => {
|
||||
if (item.dirctory) item.disabled = true
|
||||
if (!item.children) return
|
||||
if (item.children.length === 0) {
|
||||
delete item.children
|
||||
return
|
||||
}
|
||||
removeUselessChildren(item.children)
|
||||
})
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { queryResourceByProgramType } from '@/service/modules/resources'
|
||||
import { removeUselessChildren } from './use-shell'
|
||||
import { removeUselessChildren } from '@/utils/tree-format'
|
||||
import { useCustomParams, useDeployMode } from '.'
|
||||
import type { IJsonItem, ProgramType } from '../types'
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { queryResourceByProgramType } from '@/service/modules/resources'
|
||||
import { removeUselessChildren } from './use-shell'
|
||||
import { removeUselessChildren } from '@/utils/tree-format'
|
||||
import { PROGRAM_TYPES } from './use-spark'
|
||||
import { useCustomParams } from '.'
|
||||
import type { IJsonItem, ProgramType } from '../types'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { ref, onMounted, watch, computed } from 'vue'
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import { queryResourceList } from '@/service/modules/resources'
|
||||
import { useDeployMode } from '.'
|
||||
import { removeUselessChildren } from '@/utils/tree-format'
|
||||
import type { IJsonItem } from '../types'
|
||||
|
||||
export function useSeaTunnel(model: { [field: string]: any }): IJsonItem[] {
|
||||
|
|
@ -62,23 +63,6 @@ export function useSeaTunnel(model: { [field: string]: any }): IJsonItem[] {
|
|||
loading.value = false
|
||||
}
|
||||
|
||||
function removeUselessChildren(
|
||||
list: { children?: []; fullName: string; id: number }[]
|
||||
) {
|
||||
if (!list.length) return
|
||||
list.forEach((item) => {
|
||||
if (!item.children) {
|
||||
return
|
||||
}
|
||||
if (item.children.length === 0) {
|
||||
model.resourceFiles.push({ id: item.id, fullName: item.fullName })
|
||||
delete item.children
|
||||
return
|
||||
}
|
||||
removeUselessChildren(item.children)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getResourceList()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { ref, onMounted } from 'vue'
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import { queryResourceList } from '@/service/modules/resources'
|
||||
import { useCustomParams } from './use-custom-params'
|
||||
import { removeUselessChildren } from '@/utils/tree-format'
|
||||
import type { IJsonItem } from '../types'
|
||||
|
||||
export function useShell(model: { [field: string]: any }): IJsonItem[] {
|
||||
|
|
@ -70,15 +71,3 @@ export function useShell(model: { [field: string]: any }): IJsonItem[] {
|
|||
...useCustomParams({ model, field: 'localParams', isSimple: true })
|
||||
]
|
||||
}
|
||||
|
||||
export function removeUselessChildren(list: { children?: [] }[]) {
|
||||
if (!list.length) return
|
||||
list.forEach((item) => {
|
||||
if (!item.children) return
|
||||
if (item.children.length === 0) {
|
||||
delete item.children
|
||||
return
|
||||
}
|
||||
removeUselessChildren(item.children)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { queryResourceByProgramType } from '@/service/modules/resources'
|
||||
import { removeUselessChildren } from './use-shell'
|
||||
import { removeUselessChildren } from '@/utils/tree-format'
|
||||
import {
|
||||
useCustomParams,
|
||||
useDeployMode,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { queryResourceList } from '@/service/modules/resources'
|
||||
import { removeUselessChildren } from '@/utils/tree-format'
|
||||
import type { IJsonItem } from '../types'
|
||||
|
||||
export function useSql(model: { [field: string]: any }): IJsonItem[] {
|
||||
|
|
@ -147,18 +148,6 @@ export function useSql(model: { [field: string]: any }): IJsonItem[] {
|
|||
]
|
||||
}
|
||||
|
||||
function removeUselessChildren(list: { children?: [] }[]) {
|
||||
if (!list.length) return
|
||||
list.forEach((item) => {
|
||||
if (!item.children) return
|
||||
if (item.children.length === 0) {
|
||||
delete item.children
|
||||
return
|
||||
}
|
||||
removeUselessChildren(item.children)
|
||||
})
|
||||
}
|
||||
|
||||
export const TYPE_LIST = [
|
||||
{
|
||||
value: 'VARCHAR',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { defineComponent, PropType, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
NTransfer,
|
||||
NSpace,
|
||||
NRadioGroup,
|
||||
NRadioButton,
|
||||
NTreeSelect
|
||||
} from 'naive-ui'
|
||||
import { useAuthorize } from './use-authorize'
|
||||
import Modal from '@/components/modal'
|
||||
import styles from '../index.module.scss'
|
||||
import type { TAuthType } from '../types'
|
||||
|
||||
const props = {
|
||||
show: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
userId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<TAuthType>,
|
||||
default: 'auth_project'
|
||||
}
|
||||
}
|
||||
|
||||
export const AuthorizeModal = defineComponent({
|
||||
name: 'authorize-project-modal',
|
||||
props,
|
||||
emits: ['cancel'],
|
||||
setup(props, ctx) {
|
||||
const { t } = useI18n()
|
||||
const { state, onInit, onSave } = useAuthorize()
|
||||
const onCancel = () => {
|
||||
ctx.emit('cancel')
|
||||
}
|
||||
const onConfirm = async () => {
|
||||
const result = await onSave(props.type, props.userId)
|
||||
if (result) onCancel()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
() => {
|
||||
if (props.show) {
|
||||
onInit(props.type, props.userId)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
t,
|
||||
...toRefs(state),
|
||||
onCancel,
|
||||
onConfirm
|
||||
}
|
||||
},
|
||||
render(props: { type: TAuthType }) {
|
||||
const { t } = this
|
||||
const { type } = props
|
||||
return (
|
||||
<Modal
|
||||
show={this.show}
|
||||
title={t(`security.user.${type}`)}
|
||||
onCancel={this.onCancel}
|
||||
confirmLoading={this.loading}
|
||||
onConfirm={this.onConfirm}
|
||||
confirmClassName='btn-submit'
|
||||
cancelClassName='btn-cancel'
|
||||
>
|
||||
{type === 'authorize_project' && (
|
||||
<NTransfer
|
||||
virtualScroll
|
||||
options={this.unauthorizedProjects}
|
||||
filterable
|
||||
v-model={[this.authorizedProjects, 'value']}
|
||||
class={styles.transfer}
|
||||
/>
|
||||
)}
|
||||
{type === 'authorize_datasource' && (
|
||||
<NTransfer
|
||||
virtualScroll
|
||||
options={this.unauthorizedDatasources}
|
||||
filterable
|
||||
v-model:value={this.authorizedDatasources}
|
||||
class={styles.transfer}
|
||||
/>
|
||||
)}
|
||||
{type === 'authorize_udf' && (
|
||||
<NTransfer
|
||||
virtualScroll
|
||||
options={this.unauthorizedUdfs}
|
||||
filterable
|
||||
v-model:value={this.authorizedUdfs}
|
||||
class={styles.transfer}
|
||||
/>
|
||||
)}
|
||||
{type === 'authorize_resource' && (
|
||||
<NSpace vertical>
|
||||
<NRadioGroup v-model:value={this.resourceType}>
|
||||
<NRadioButton key='file' value='file'>
|
||||
{t('security.user.file_resource')}
|
||||
</NRadioButton>
|
||||
<NRadioButton key='udf' value='udf'>
|
||||
{t('security.user.udf_resource')}
|
||||
</NRadioButton>
|
||||
</NRadioGroup>
|
||||
<NTreeSelect
|
||||
v-show={this.resourceType === 'file'}
|
||||
filterable
|
||||
multiple
|
||||
cascade
|
||||
checkable
|
||||
checkStrategy='child'
|
||||
key-field='id'
|
||||
label-field='fullName'
|
||||
options={this.fileResources}
|
||||
v-model:value={this.authorizedFileResources}
|
||||
/>
|
||||
<NTreeSelect
|
||||
v-show={this.resourceType === 'udf'}
|
||||
filterable
|
||||
multiple
|
||||
cascade
|
||||
checkable
|
||||
checkStrategy='child'
|
||||
key-field='id'
|
||||
label-field='fullName'
|
||||
options={this.udfResources}
|
||||
v-model:value={this.authorizedUdfResources}
|
||||
/>
|
||||
</NSpace>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default AuthorizeModal
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { reactive } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
queryAuthorizedProject,
|
||||
queryUnauthorizedProject
|
||||
} from '@/service/modules/projects'
|
||||
import {
|
||||
authedDatasource,
|
||||
unAuthDatasource
|
||||
} from '@/service/modules/data-source'
|
||||
import {
|
||||
authorizedFile,
|
||||
authorizeResourceTree,
|
||||
authUDFFunc,
|
||||
unAuthUDFFunc
|
||||
} from '@/service/modules/resources'
|
||||
import {
|
||||
grantProject,
|
||||
grantResource,
|
||||
grantDataSource,
|
||||
grantUDFFunc
|
||||
} from '@/service/modules/users'
|
||||
import { removeUselessChildren } from '@/utils/tree-format'
|
||||
import type { TAuthType, IResourceOption, IOption } from '../types'
|
||||
|
||||
export function useAuthorize() {
|
||||
const { t } = useI18n()
|
||||
|
||||
const state = reactive({
|
||||
saving: false,
|
||||
loading: false,
|
||||
authorizedProjects: [] as number[],
|
||||
unauthorizedProjects: [] as IOption[],
|
||||
authorizedDatasources: [] as number[],
|
||||
unauthorizedDatasources: [] as IOption[],
|
||||
authorizedUdfs: [] as number[],
|
||||
unauthorizedUdfs: [] as IOption[],
|
||||
resourceType: 'file',
|
||||
fileResources: [] as IResourceOption[],
|
||||
udfResources: [] as IResourceOption[],
|
||||
authorizedFileResources: [] as number[],
|
||||
authorizedUdfResources: [] as number[]
|
||||
})
|
||||
|
||||
const getProjects = async (userId: number) => {
|
||||
if (state.loading) return
|
||||
state.loading = true
|
||||
const projects = await Promise.all([
|
||||
queryAuthorizedProject({ userId }),
|
||||
queryUnauthorizedProject({ userId })
|
||||
])
|
||||
state.loading = false
|
||||
state.authorizedProjects = projects[0].map(
|
||||
(item: { name: string; id: number }) => item.id
|
||||
)
|
||||
state.unauthorizedProjects = [...projects[0], ...projects[1]].map(
|
||||
(item: { name: string; id: number }) => ({
|
||||
label: item.name,
|
||||
value: item.id
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getDatasources = async (userId: number) => {
|
||||
if (state.loading) return
|
||||
state.loading = true
|
||||
const datasources = await Promise.all([
|
||||
authedDatasource({ userId }),
|
||||
unAuthDatasource({ userId })
|
||||
])
|
||||
state.loading = false
|
||||
state.authorizedDatasources = datasources[0].map(
|
||||
(item: { name: string; id: number }) => item.id
|
||||
)
|
||||
state.unauthorizedDatasources = [...datasources[0], ...datasources[1]].map(
|
||||
(item: { name: string; id: number }) => ({
|
||||
label: item.name,
|
||||
value: item.id
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getUdfs = async (userId: number) => {
|
||||
if (state.loading) return
|
||||
state.loading = true
|
||||
const udfs = await Promise.all([
|
||||
authUDFFunc({ userId }),
|
||||
unAuthUDFFunc({ userId })
|
||||
])
|
||||
state.loading = false
|
||||
state.authorizedUdfs = udfs[0].map(
|
||||
(item: { name: string; id: number }) => item.id
|
||||
)
|
||||
state.unauthorizedUdfs = [...udfs[0], ...udfs[1]].map(
|
||||
(item: { name: string; id: number }) => ({
|
||||
label: item.name,
|
||||
value: item.id
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getResources = async (userId: number) => {
|
||||
if (state.loading) return
|
||||
state.loading = true
|
||||
const resources = await Promise.all([
|
||||
authorizeResourceTree({ userId }),
|
||||
authorizedFile({ userId })
|
||||
])
|
||||
state.loading = false
|
||||
removeUselessChildren(resources[0])
|
||||
let udfResources = [] as IResourceOption[]
|
||||
let fileResources = [] as IResourceOption[]
|
||||
resources[0].forEach((item: IResourceOption) => {
|
||||
item.type === 'FILE' ? fileResources.push(item) : udfResources.push(item)
|
||||
})
|
||||
let udfTargets = [] as number[]
|
||||
let fileTargets = [] as number[]
|
||||
resources[1].forEach((item: { type: string; id: number }) => {
|
||||
item.type === 'FILE'
|
||||
? fileTargets.push(item.id)
|
||||
: udfTargets.push(item.id)
|
||||
})
|
||||
state.fileResources = fileResources
|
||||
state.udfResources = udfResources
|
||||
console.log(fileResources)
|
||||
state.authorizedFileResources = fileTargets
|
||||
state.authorizedUdfResources = fileTargets
|
||||
}
|
||||
|
||||
const onInit = (type: TAuthType, userId: number) => {
|
||||
if (type === 'authorize_project') {
|
||||
getProjects(userId)
|
||||
}
|
||||
if (type === 'authorize_datasource') {
|
||||
getDatasources(userId)
|
||||
}
|
||||
if (type === 'authorize_udf') {
|
||||
getUdfs(userId)
|
||||
}
|
||||
if (type === 'authorize_resource') {
|
||||
getResources(userId)
|
||||
}
|
||||
}
|
||||
|
||||
const onSave = async (type: TAuthType, userId: number) => {
|
||||
if (state.saving) return false
|
||||
state.saving = true
|
||||
if (type === 'authorize_project') {
|
||||
await grantProject({
|
||||
userId,
|
||||
projectIds: state.authorizedProjects.join(',')
|
||||
})
|
||||
}
|
||||
if (type === 'authorize_datasource') {
|
||||
await grantDataSource({
|
||||
userId,
|
||||
datasourceIds: state.authorizedDatasources.join(',')
|
||||
})
|
||||
}
|
||||
if (type === 'authorize_udf') {
|
||||
await grantUDFFunc({
|
||||
userId,
|
||||
udfIds: state.authorizedUdfResources.join(',')
|
||||
})
|
||||
}
|
||||
if (type === 'authorize_resource') {
|
||||
await grantResource({
|
||||
userId,
|
||||
resourceIds:
|
||||
state.resourceType === 'file'
|
||||
? state.authorizedFileResources.join(',')
|
||||
: state.authorizedUdfResources.join(',')
|
||||
})
|
||||
}
|
||||
state.saving = false
|
||||
return true
|
||||
}
|
||||
|
||||
return { state, onInit, onSave }
|
||||
}
|
||||
|
|
@ -1,496 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ref, watch, computed, InjectionKey } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { queryTenantList } from '@/service/modules/tenants'
|
||||
import { queryList } from '@/service/modules/queues'
|
||||
import {
|
||||
createUser,
|
||||
updateUser,
|
||||
delUserById,
|
||||
verifyUserName,
|
||||
grantProject,
|
||||
grantResource,
|
||||
grantDataSource,
|
||||
grantUDFFunc
|
||||
} from '@/service/modules/users'
|
||||
import {
|
||||
queryAuthorizedProject,
|
||||
queryUnauthorizedProject
|
||||
} from '@/service/modules/projects'
|
||||
import {
|
||||
authorizedFile,
|
||||
authorizeResourceTree,
|
||||
authUDFFunc,
|
||||
unAuthUDFFunc
|
||||
} from '@/service/modules/resources'
|
||||
import {
|
||||
authedDatasource,
|
||||
unAuthDatasource
|
||||
} from '@/service/modules/data-source'
|
||||
import regexUtils from '@/utils/regex'
|
||||
export type Mode =
|
||||
| 'add'
|
||||
| 'edit'
|
||||
| 'delete'
|
||||
| 'auth_project'
|
||||
| 'auth_resource'
|
||||
| 'auth_datasource'
|
||||
| 'auth_udf'
|
||||
|
||||
export type UserModalSharedStateType = ReturnType<
|
||||
typeof useSharedUserModalState
|
||||
> & {
|
||||
onSuccess?: (mode: Mode) => void
|
||||
}
|
||||
|
||||
export const UserModalSharedStateKey: InjectionKey<UserModalSharedStateType> =
|
||||
Symbol()
|
||||
|
||||
export function useSharedUserModalState() {
|
||||
return {
|
||||
show: ref(false),
|
||||
mode: ref<Mode>('add'),
|
||||
user: ref()
|
||||
}
|
||||
}
|
||||
|
||||
export function useModal({
|
||||
onSuccess,
|
||||
show,
|
||||
mode,
|
||||
user
|
||||
}: UserModalSharedStateType) {
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
const formRef = ref()
|
||||
const formValues = ref({
|
||||
userName: '',
|
||||
userPassword: '',
|
||||
tenantId: 0,
|
||||
email: '',
|
||||
queue: '',
|
||||
phone: '',
|
||||
state: 1
|
||||
})
|
||||
const tenants = ref<any[]>([])
|
||||
const queues = ref<any[]>([])
|
||||
const authorizedProjects = ref<string[]>([])
|
||||
const projects = ref<any[]>([])
|
||||
const authorizedFiles = ref<string[]>([])
|
||||
const originResourceTree = ref<any[]>([])
|
||||
const resourceType = ref<'file' | 'udf'>()
|
||||
const authorizedUDF = ref<string[]>([])
|
||||
const UDFs = ref<any[]>([])
|
||||
const authorizedDatasource = ref<string[]>([])
|
||||
const datasource = ref<any[]>([])
|
||||
const optionsLoading = ref(false)
|
||||
const confirmLoading = ref(false)
|
||||
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
userName: {
|
||||
required: true,
|
||||
message: t('security.user.username_rule_msg'),
|
||||
trigger: 'blur'
|
||||
},
|
||||
userPassword: {
|
||||
required: mode.value === 'add',
|
||||
validator(rule: any, value?: string) {
|
||||
if (mode.value !== 'add' && !value) {
|
||||
return true
|
||||
}
|
||||
const msg = t('security.user.user_password_rule_msg')
|
||||
if (!value || !regexUtils.password.test(value)) {
|
||||
return new Error(msg)
|
||||
}
|
||||
return true
|
||||
},
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
tenantId: {
|
||||
required: true,
|
||||
validator(rule: any, value?: number) {
|
||||
const msg = t('security.user.tenant_id_rule_msg')
|
||||
if (typeof value === 'number') {
|
||||
return true
|
||||
}
|
||||
return new Error(msg)
|
||||
},
|
||||
trigger: 'blur'
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
validator(rule: any, value?: string) {
|
||||
const msg = t('security.user.email_rule_msg')
|
||||
if (!value || !regexUtils.email.test(value)) {
|
||||
return new Error(msg)
|
||||
}
|
||||
return true
|
||||
},
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
phone: {
|
||||
validator(rule: any, value?: string) {
|
||||
const msg = t('security.user.phone_rule_msg')
|
||||
if (value && !regexUtils.phone.test(value)) {
|
||||
return new Error(msg)
|
||||
}
|
||||
return true
|
||||
},
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const resourceTree = computed(() => {
|
||||
const loopTree = (arr: any[]): any[] =>
|
||||
arr
|
||||
.map((d) => {
|
||||
if (
|
||||
(resourceType.value &&
|
||||
`${d.type}`.toLowerCase() === resourceType.value) ||
|
||||
!resourceType.value
|
||||
) {
|
||||
const obj = { key: `${d.pid}-${d.id}`, label: d.name }
|
||||
const children = d.children
|
||||
if (children instanceof Array && children.length > 0) {
|
||||
return {
|
||||
...obj,
|
||||
children: loopTree(children)
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter((f) => f)
|
||||
const data = loopTree(originResourceTree.value)
|
||||
return data
|
||||
})
|
||||
|
||||
const titleMap = computed(() => {
|
||||
const titles: Record<Mode, string> = {
|
||||
add: t('security.user.create_user'),
|
||||
edit: t('security.user.update_user'),
|
||||
delete: t('security.user.delete_user'),
|
||||
auth_project: t('security.user.authorize_project'),
|
||||
auth_resource: t('security.user.authorize_resource'),
|
||||
auth_datasource: t('security.user.authorize_datasource'),
|
||||
auth_udf: t('security.user.authorize_udf')
|
||||
}
|
||||
return titles
|
||||
})
|
||||
|
||||
const setFormValues = () => {
|
||||
const defaultValues = {
|
||||
userName: '',
|
||||
userPassword: '',
|
||||
tenantId: tenants.value[0]?.value,
|
||||
email: '',
|
||||
queue: queues.value[0]?.value,
|
||||
phone: '',
|
||||
state: 1
|
||||
}
|
||||
if (!user.value) {
|
||||
formValues.value = defaultValues
|
||||
} else {
|
||||
const v: any = {}
|
||||
Object.keys(defaultValues).map((k) => {
|
||||
v[k] = user.value[k]
|
||||
})
|
||||
v.userPassword = ''
|
||||
formValues.value = v
|
||||
}
|
||||
}
|
||||
|
||||
const prepareOptions = async () => {
|
||||
optionsLoading.value = true
|
||||
Promise.all([queryTenantList(), queryList()])
|
||||
.then((res) => {
|
||||
tenants.value =
|
||||
res[0]?.map((d: any) => ({
|
||||
label: d.tenantCode,
|
||||
value: d.id
|
||||
})) || []
|
||||
queues.value =
|
||||
res[1]?.map((d: any) => ({
|
||||
label: d.queueName,
|
||||
value: d.queue
|
||||
})) || []
|
||||
})
|
||||
.finally(() => {
|
||||
optionsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const fetchProjects = async () => {
|
||||
optionsLoading.value = true
|
||||
Promise.all([
|
||||
queryAuthorizedProject({ userId: user.value.id }),
|
||||
queryUnauthorizedProject({ userId: user.value.id })
|
||||
])
|
||||
.then((res: any[]) => {
|
||||
const ids: string[] = []
|
||||
res[0]?.forEach((d: any) => {
|
||||
if (!ids.includes(d.id)) {
|
||||
ids.push(d.id)
|
||||
}
|
||||
})
|
||||
authorizedProjects.value = ids
|
||||
projects.value =
|
||||
res?.flat().map((d: any) => ({ label: d.name, value: d.id })) || []
|
||||
})
|
||||
.finally(() => {
|
||||
optionsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const fetchResources = async () => {
|
||||
optionsLoading.value = true
|
||||
Promise.all([
|
||||
authorizedFile({ userId: user.value.id }),
|
||||
authorizeResourceTree({ userId: user.value.id })
|
||||
])
|
||||
.then((res: any[]) => {
|
||||
const ids: string[] = []
|
||||
const getIds = (arr: any[]) => {
|
||||
arr.forEach((d) => {
|
||||
const children = d.children
|
||||
if (children instanceof Array && children.length > 0) {
|
||||
getIds(children)
|
||||
} else {
|
||||
ids.push(`${d.pid}-${d.id}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
getIds(res[0] || [])
|
||||
authorizedFiles.value = ids
|
||||
originResourceTree.value = res[1] || []
|
||||
})
|
||||
.finally(() => {
|
||||
optionsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const fetchDatasource = async () => {
|
||||
optionsLoading.value = true
|
||||
Promise.all([
|
||||
authedDatasource({ userId: user.value.id }),
|
||||
unAuthDatasource({ userId: user.value.id })
|
||||
])
|
||||
.then((res: any[]) => {
|
||||
const ids: string[] = []
|
||||
res[0]?.forEach((d: any) => {
|
||||
if (!ids.includes(d.id)) {
|
||||
ids.push(d.id)
|
||||
}
|
||||
})
|
||||
authorizedDatasource.value = ids
|
||||
datasource.value =
|
||||
res?.flat().map((d: any) => ({ label: d.name, value: d.id })) || []
|
||||
})
|
||||
.finally(() => {
|
||||
optionsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const fetchUDFs = async () => {
|
||||
optionsLoading.value = true
|
||||
Promise.all([
|
||||
authUDFFunc({ userId: user.value.id }),
|
||||
unAuthUDFFunc({ userId: user.value.id })
|
||||
])
|
||||
.then((res: any[]) => {
|
||||
const ids: string[] = []
|
||||
res[0]?.forEach((d: any) => {
|
||||
if (!ids.includes(d.id)) {
|
||||
ids.push(d.id)
|
||||
}
|
||||
})
|
||||
authorizedUDF.value = ids
|
||||
UDFs.value =
|
||||
res?.flat().map((d: any) => ({ label: d.name, value: d.id })) || []
|
||||
})
|
||||
.finally(() => {
|
||||
optionsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const onModalCancel = () => {
|
||||
show.value = false
|
||||
}
|
||||
|
||||
const onDelete = () => {
|
||||
confirmLoading.value = true
|
||||
delUserById({ id: user.value.id })
|
||||
.then(
|
||||
() => {
|
||||
onSuccess?.(mode.value)
|
||||
onModalCancel()
|
||||
},
|
||||
() => {
|
||||
message.error(t('security.user.delete_error_msg'))
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
confirmLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const onCreateUser = () => {
|
||||
confirmLoading.value = true
|
||||
verifyUserName({ userName: formValues.value.userName })
|
||||
.then(
|
||||
() => createUser(formValues.value),
|
||||
(error) => {
|
||||
if (`${error.message}`.includes('exists')) {
|
||||
message.error(t('security.user.username_exists'))
|
||||
}
|
||||
return false
|
||||
}
|
||||
)
|
||||
.then(
|
||||
(res) => {
|
||||
if (res) {
|
||||
onSuccess?.(mode.value)
|
||||
onModalCancel()
|
||||
}
|
||||
},
|
||||
() => {
|
||||
message.error(t('security.user.save_error_msg'))
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
confirmLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const onUpdateUser = () => {
|
||||
confirmLoading.value = true
|
||||
updateUser({ id: user.value.id, ...formValues.value })
|
||||
.then(
|
||||
() => {
|
||||
onSuccess?.(mode.value)
|
||||
onModalCancel()
|
||||
},
|
||||
() => {
|
||||
message.error(t('security.user.save_error_msg'))
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
confirmLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const onGrant = (grantReq: () => Promise<any>) => {
|
||||
confirmLoading.value = true
|
||||
grantReq()
|
||||
.then(
|
||||
() => {
|
||||
onSuccess?.(mode.value)
|
||||
onModalCancel()
|
||||
message.success(t('security.user.auth_success_msg'))
|
||||
},
|
||||
() => {
|
||||
message.error(t('security.user.auth_error_msg'))
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
confirmLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
if (mode.value === 'add' || mode.value === 'edit') {
|
||||
formRef.value.validate((errors: any) => {
|
||||
if (!errors) {
|
||||
user.value ? onUpdateUser() : onCreateUser()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
mode.value === 'delete' && onDelete()
|
||||
mode.value === 'auth_project' &&
|
||||
onGrant(() =>
|
||||
grantProject({
|
||||
userId: user.value.id,
|
||||
projectIds: authorizedProjects.value.join(',')
|
||||
})
|
||||
)
|
||||
mode.value === 'auth_resource' &&
|
||||
onGrant(() =>
|
||||
grantResource({
|
||||
userId: user.value.id,
|
||||
resourceIds: authorizedFiles.value.join(',')
|
||||
})
|
||||
)
|
||||
mode.value === 'auth_datasource' &&
|
||||
onGrant(() =>
|
||||
grantDataSource({
|
||||
userId: user.value.id,
|
||||
datasourceIds: authorizedDatasource.value.join(',')
|
||||
})
|
||||
)
|
||||
mode.value === 'auth_udf' &&
|
||||
onGrant(() =>
|
||||
grantUDFFunc({
|
||||
userId: user.value.id,
|
||||
udfIds: authorizedUDF.value.join(',')
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
watch([show, mode], () => {
|
||||
show.value && ['add', 'edit'].includes(mode.value) && prepareOptions()
|
||||
show.value && mode.value === 'auth_project' && fetchProjects()
|
||||
show.value && mode.value === 'auth_resource' && fetchResources()
|
||||
show.value && mode.value === 'auth_datasource' && fetchDatasource()
|
||||
show.value && mode.value === 'auth_udf' && fetchUDFs()
|
||||
})
|
||||
|
||||
watch([queues, tenants, user], () => {
|
||||
setFormValues()
|
||||
})
|
||||
|
||||
return {
|
||||
show,
|
||||
mode,
|
||||
user,
|
||||
titleMap,
|
||||
onModalCancel,
|
||||
formRef,
|
||||
formValues,
|
||||
formRules,
|
||||
tenants,
|
||||
queues,
|
||||
authorizedProjects,
|
||||
projects,
|
||||
authorizedDatasource,
|
||||
datasource,
|
||||
authorizedUDF,
|
||||
UDFs,
|
||||
authorizedFiles,
|
||||
resourceTree,
|
||||
resourceType,
|
||||
optionsLoading,
|
||||
onConfirm,
|
||||
confirmLoading
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { pick } from 'lodash'
|
||||
import { queryTenantList } from '@/service/modules/tenants'
|
||||
import { queryList } from '@/service/modules/queues'
|
||||
import { verifyUserName, createUser, updateUser } from '@/service/modules/users'
|
||||
import { useUserStore } from '@/store/user/user'
|
||||
import type { IRecord, UserReq, UserInfoRes } from '../types'
|
||||
|
||||
export function useUserDetail() {
|
||||
const { t } = useI18n()
|
||||
const userStore = useUserStore()
|
||||
const userInfo = userStore.getUserInfo as UserInfoRes
|
||||
const IS_ADMIN = userInfo.userType === 'ADMIN_USER'
|
||||
|
||||
const initialValues = {
|
||||
userName: '',
|
||||
userPassword: '',
|
||||
tenantId: 0,
|
||||
email: '',
|
||||
queue: '',
|
||||
phone: '',
|
||||
state: 1
|
||||
} as UserReq
|
||||
|
||||
let PREV_NAME: string
|
||||
|
||||
const state = reactive({
|
||||
formRef: ref(),
|
||||
formData: { ...initialValues },
|
||||
saving: false,
|
||||
loading: false,
|
||||
queues: [] as { label: string; value: string }[],
|
||||
tenants: [] as { label: string; value: number }[]
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
userName: {
|
||||
trigger: ['input', 'blur'],
|
||||
required: true,
|
||||
validator(validator: any, value: string) {
|
||||
if (!value.trim()) {
|
||||
return new Error(t('security.user.username_tips'))
|
||||
}
|
||||
}
|
||||
},
|
||||
userPassword: {
|
||||
trigger: ['input', 'blur'],
|
||||
required: true,
|
||||
validator(validator: any, value: string) {
|
||||
if (
|
||||
!value ||
|
||||
!/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?![`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]+$)[`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、0-9A-Za-z]{6,22}$/.test(
|
||||
value
|
||||
)
|
||||
) {
|
||||
return new Error(t('security.user.user_password_tips'))
|
||||
}
|
||||
}
|
||||
},
|
||||
tenantId: {
|
||||
trigger: ['input', 'blur'],
|
||||
required: true,
|
||||
validator(validator: any, value: string) {
|
||||
if (IS_ADMIN && !value) {
|
||||
return new Error(t('security.user.tenant_id_tips'))
|
||||
}
|
||||
}
|
||||
},
|
||||
email: {
|
||||
trigger: ['input', 'blur'],
|
||||
required: true,
|
||||
validator(validator: any, value: string) {
|
||||
if (!value) {
|
||||
return new Error(t('security.user.email_empty_tips'))
|
||||
}
|
||||
if (
|
||||
!/^([a-zA-Z0-9]+[_|\-|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\-|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/.test(
|
||||
value
|
||||
)
|
||||
) {
|
||||
return new Error(t('security.user.emial_correct_tips'))
|
||||
}
|
||||
}
|
||||
},
|
||||
phone: {
|
||||
trigger: ['input', 'blur'],
|
||||
validator(validator: any, value: string) {
|
||||
if (value && !/^1(3|4|5|6|7|8)\d{9}$/.test(value)) {
|
||||
return new Error(t('security.user.phone_correct_tips'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getQueues = async () => {
|
||||
const result = await queryList()
|
||||
state.queues = result.map((queue: { queueName: string; id: string }) => ({
|
||||
label: queue.queueName,
|
||||
value: queue.id
|
||||
}))
|
||||
if (state.queues.length) {
|
||||
initialValues.queue = state.queues[0].value
|
||||
state.formData.queue = state.queues[0].value
|
||||
}
|
||||
}
|
||||
const getTenants = async () => {
|
||||
const result = await queryTenantList()
|
||||
state.tenants = result.map(
|
||||
(tenant: { tenantCode: string; id: number }) => ({
|
||||
label: tenant.tenantCode,
|
||||
value: tenant.id
|
||||
})
|
||||
)
|
||||
if (state.tenants.length) {
|
||||
initialValues.tenantId = state.tenants[0].value
|
||||
state.formData.tenantId = state.tenants[0].value
|
||||
}
|
||||
}
|
||||
const onReset = () => {
|
||||
state.formData = { ...initialValues }
|
||||
}
|
||||
const onSave = async (id?: number): Promise<boolean> => {
|
||||
await state.formRef.validate()
|
||||
if (state.saving) return false
|
||||
state.saving = true
|
||||
if (PREV_NAME !== state.formData.userName) {
|
||||
await verifyUserName({ userName: state.formData.userName })
|
||||
}
|
||||
|
||||
id
|
||||
? await updateUser({ id, ...state.formData })
|
||||
: await createUser(state.formData)
|
||||
|
||||
state.saving = false
|
||||
return true
|
||||
}
|
||||
const onSetValues = (record: IRecord) => {
|
||||
state.formData = {
|
||||
...pick(record, [
|
||||
'userName',
|
||||
'tenantId',
|
||||
'email',
|
||||
'queue',
|
||||
'phone',
|
||||
'state'
|
||||
]),
|
||||
userPassword: ''
|
||||
} as UserReq
|
||||
PREV_NAME = state.formData.userName
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (IS_ADMIN) {
|
||||
getQueues()
|
||||
getTenants()
|
||||
}
|
||||
})
|
||||
|
||||
return { state, formRules, IS_ADMIN, onReset, onSave, onSetValues }
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { defineComponent, PropType, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
NInput,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NSelect,
|
||||
NRadio,
|
||||
NRadioGroup,
|
||||
NSpace
|
||||
} from 'naive-ui'
|
||||
import { useUserDetail } from './use-user-detail'
|
||||
import Modal from '@/components/modal'
|
||||
import type { IRecord } from '../types'
|
||||
|
||||
const props = {
|
||||
show: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
currentRecord: {
|
||||
type: Object as PropType<IRecord | null>,
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
|
||||
export const UserModal = defineComponent({
|
||||
name: 'user-modal',
|
||||
props,
|
||||
emits: ['cancel', 'update'],
|
||||
setup(props, ctx) {
|
||||
const { t } = useI18n()
|
||||
const { state, IS_ADMIN, formRules, onReset, onSave, onSetValues } =
|
||||
useUserDetail()
|
||||
const onCancel = () => {
|
||||
onReset()
|
||||
ctx.emit('cancel')
|
||||
}
|
||||
const onConfirm = async () => {
|
||||
const result = await onSave(props.currentRecord?.id)
|
||||
if (!result) return
|
||||
onCancel()
|
||||
ctx.emit('update')
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
() => {
|
||||
if (props.show && props.currentRecord?.id) {
|
||||
onSetValues(props.currentRecord)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
t,
|
||||
...toRefs(state),
|
||||
IS_ADMIN,
|
||||
formRules,
|
||||
onCancel,
|
||||
onConfirm
|
||||
}
|
||||
},
|
||||
render(props: { currentRecord: IRecord }) {
|
||||
const { t } = this
|
||||
const { currentRecord } = props
|
||||
return (
|
||||
<Modal
|
||||
show={this.show}
|
||||
title={`${t(
|
||||
currentRecord?.id
|
||||
? 'security.user.update_user'
|
||||
: 'security.user.create_user'
|
||||
)}`}
|
||||
onCancel={this.onCancel}
|
||||
confirmLoading={this.loading}
|
||||
onConfirm={this.onConfirm}
|
||||
confirmClassName='btn-submit'
|
||||
cancelClassName='btn-cancel'
|
||||
>
|
||||
<NForm
|
||||
ref='formRef'
|
||||
model={this.formData}
|
||||
rules={this.formRules}
|
||||
labelPlacement='left'
|
||||
labelAlign='left'
|
||||
labelWidth={80}
|
||||
>
|
||||
<NFormItem label={t('security.user.username')} path='userName'>
|
||||
<NInput
|
||||
class='input-username'
|
||||
v-model:value={this.formData.userName}
|
||||
minlength={3}
|
||||
maxlength={39}
|
||||
placeholder={t('security.user.username_tips')}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
label={t('security.user.user_password')}
|
||||
path='userPassword'
|
||||
>
|
||||
<NInput
|
||||
class='input-password'
|
||||
type='password'
|
||||
v-model:value={this.formData.userPassword}
|
||||
placeholder={t('security.user.user_password_tips')}
|
||||
/>
|
||||
</NFormItem>
|
||||
{this.IS_ADMIN && (
|
||||
<NFormItem label={t('security.user.tenant_code')} path='tenantId'>
|
||||
<NSelect
|
||||
class='select-tenant'
|
||||
options={this.tenants}
|
||||
v-model:value={this.formData.tenantId}
|
||||
/>
|
||||
</NFormItem>
|
||||
)}
|
||||
{this.IS_ADMIN && (
|
||||
<NFormItem label={t('security.user.queue')} path='queue'>
|
||||
<NSelect
|
||||
class='select-queue'
|
||||
options={this.queues}
|
||||
v-model:value={this.formData.queue}
|
||||
placeholder={t('security.user.queue_tips')}
|
||||
/>
|
||||
</NFormItem>
|
||||
)}
|
||||
<NFormItem label={t('security.user.email')} path='email'>
|
||||
<NInput
|
||||
class='input-email'
|
||||
v-model:value={this.formData.email}
|
||||
placeholder={t('security.user.email_empty_tips')}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label={t('security.user.phone')} path='phone'>
|
||||
<NInput
|
||||
class='input-phone'
|
||||
v-model:value={this.formData.phone}
|
||||
placeholder={t('security.user.phone_empty_tips')}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label={t('security.user.state')} path='state'>
|
||||
<NRadioGroup v-model:value={this.formData.state}>
|
||||
<NSpace>
|
||||
<NRadio value={1} class='radio-state-enable'>
|
||||
{this.t('security.user.enable')}
|
||||
</NRadio>
|
||||
<NRadio value={0} class='radio-state-disable'>
|
||||
{this.t('security.user.disable')}
|
||||
</NRadio>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default UserModal
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { defineComponent, inject } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
NInput,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NSelect,
|
||||
NRadio,
|
||||
NRadioGroup,
|
||||
NRadioButton,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NTransfer,
|
||||
NTreeSelect
|
||||
} from 'naive-ui'
|
||||
|
||||
import Modal from '@/components/modal'
|
||||
import {
|
||||
useModal,
|
||||
useSharedUserModalState,
|
||||
UserModalSharedStateKey
|
||||
} from './use-modal'
|
||||
|
||||
export const UserModal = defineComponent({
|
||||
name: 'user-modal',
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const sharedState =
|
||||
inject(UserModalSharedStateKey) || useSharedUserModalState()
|
||||
const modalState = useModal(sharedState)
|
||||
|
||||
return {
|
||||
t,
|
||||
...modalState
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { t } = this
|
||||
return (
|
||||
<Modal
|
||||
show={this.show}
|
||||
title={this.titleMap?.[this.mode || 'add']}
|
||||
onCancel={this.onModalCancel}
|
||||
confirmDisabled={this.optionsLoading}
|
||||
confirmLoading={this.confirmLoading}
|
||||
onConfirm={this.onConfirm}
|
||||
confirmClassName='btn-submit'
|
||||
cancelClassName='btn-cancel'
|
||||
>
|
||||
{{
|
||||
default: () => {
|
||||
if (this.mode === 'delete') {
|
||||
return (
|
||||
<NAlert type='error' title={t('security.user.delete_confirm')}>
|
||||
{t('security.user.delete_confirm_tip')}
|
||||
</NAlert>
|
||||
)
|
||||
}
|
||||
if (this.mode === 'auth_project') {
|
||||
return (
|
||||
<NTransfer
|
||||
virtualScroll
|
||||
options={this.projects}
|
||||
filterable
|
||||
v-model:value={this.authorizedProjects}
|
||||
style={{ margin: '0 auto' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (this.mode === 'auth_datasource') {
|
||||
return (
|
||||
<NTransfer
|
||||
virtualScroll
|
||||
options={this.datasource}
|
||||
filterable
|
||||
v-model:value={this.authorizedDatasource}
|
||||
style={{ margin: '0 auto' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (this.mode === 'auth_udf') {
|
||||
return (
|
||||
<NTransfer
|
||||
virtualScroll
|
||||
options={this.UDFs}
|
||||
filterable
|
||||
v-model:value={this.authorizedUDF}
|
||||
style={{ margin: '0 auto' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (this.mode === 'auth_resource') {
|
||||
return (
|
||||
<NSpace vertical>
|
||||
<NRadioGroup v-model:value={this.resourceType}>
|
||||
<NRadioButton key='file' value='file'>
|
||||
{t('security.user.file_resource')}
|
||||
</NRadioButton>
|
||||
<NRadioButton key='udf' value='udf'>
|
||||
{t('security.user.udf_resource')}
|
||||
</NRadioButton>
|
||||
</NRadioGroup>
|
||||
<NTreeSelect
|
||||
multiple
|
||||
cascade
|
||||
checkable
|
||||
checkStrategy='child'
|
||||
defaultExpandAll
|
||||
options={this.resourceTree}
|
||||
v-model:value={this.authorizedFiles}
|
||||
/>
|
||||
</NSpace>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<NForm
|
||||
ref='formRef'
|
||||
model={this.formValues}
|
||||
rules={this.formRules}
|
||||
labelPlacement='left'
|
||||
labelAlign='left'
|
||||
labelWidth={80}
|
||||
>
|
||||
<NFormItem label={t('security.user.username')} path='userName'>
|
||||
<NInput
|
||||
class='input-username'
|
||||
inputProps={{ autocomplete: 'off' }}
|
||||
v-model:value={this.formValues.userName}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
label={t('security.user.user_password')}
|
||||
path='userPassword'
|
||||
>
|
||||
<NInput
|
||||
class='input-password'
|
||||
inputProps={{ autocomplete: 'off' }}
|
||||
type='password'
|
||||
v-model:value={this.formValues.userPassword}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
label={t('security.user.tenant_code')}
|
||||
path='tenantId'
|
||||
>
|
||||
<NSelect
|
||||
class='select-tenant'
|
||||
options={this.tenants}
|
||||
v-model:value={this.formValues.tenantId}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label={t('security.user.queue')} path='queue'>
|
||||
<NSelect
|
||||
class='select-queue'
|
||||
options={this.queues}
|
||||
v-model:value={this.formValues.queue}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label={t('security.user.email')} path='email'>
|
||||
<NInput
|
||||
class='input-email'
|
||||
v-model:value={this.formValues.email}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label={t('security.user.phone')} path='phone'>
|
||||
<NInput
|
||||
class='input-phone'
|
||||
v-model:value={this.formValues.phone}
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label={t('security.user.state')} path='state'>
|
||||
<NRadioGroup v-model:value={this.formValues.state}>
|
||||
<NSpace>
|
||||
<NRadio value={1} class='radio-state-enable'>
|
||||
启用
|
||||
</NRadio>
|
||||
<NRadio value={0} class='radio-state-disable'>
|
||||
停用
|
||||
</NRadio>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default UserModal
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.transfer {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -15,132 +15,112 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { defineComponent, provide } from 'vue'
|
||||
import { defineComponent, toRefs, watch } from 'vue'
|
||||
import {
|
||||
NCard,
|
||||
NButton,
|
||||
NInputGroup,
|
||||
NInput,
|
||||
NIcon,
|
||||
NSpace,
|
||||
NGrid,
|
||||
NGridItem,
|
||||
NDataTable,
|
||||
NPagination,
|
||||
NSkeleton
|
||||
NPagination
|
||||
} from 'naive-ui'
|
||||
import Card from '@/components/card'
|
||||
import UserDetailModal from './components/user-detail-modal'
|
||||
import AuthorizeModal from './components/authorize-modal'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { SearchOutlined } from '@vicons/antd'
|
||||
import { useColumns } from './use-columns'
|
||||
import { useTable } from './use-table'
|
||||
import UserModal from './components/user-modal'
|
||||
import {
|
||||
useSharedUserModalState,
|
||||
UserModalSharedStateKey,
|
||||
Mode
|
||||
} from './components/use-modal'
|
||||
|
||||
const UsersManage = defineComponent({
|
||||
name: 'user-manage',
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const { show, mode, user } = useSharedUserModalState()
|
||||
const tableState = useTable({
|
||||
onEdit: (u, m: Mode) => {
|
||||
show.value = true
|
||||
mode.value = m
|
||||
user.value = u
|
||||
},
|
||||
onDelete: (u) => {
|
||||
show.value = true
|
||||
mode.value = 'delete'
|
||||
user.value = u
|
||||
}
|
||||
})
|
||||
|
||||
const onSuccess = (mode: Mode) => {
|
||||
if (!mode.startsWith('auth')) {
|
||||
mode === 'add' && tableState.resetPage()
|
||||
tableState.getUserList()
|
||||
}
|
||||
}
|
||||
const { state, changePage, changePageSize, updateList, onOperationClick } =
|
||||
useTable()
|
||||
const { columnsRef } = useColumns(onOperationClick)
|
||||
|
||||
const onAddUser = () => {
|
||||
show.value = true
|
||||
mode.value = 'add'
|
||||
user.value = undefined
|
||||
state.detailModalShow = true
|
||||
state.currentRecord = null
|
||||
}
|
||||
const onDetailModalCancel = () => {
|
||||
state.detailModalShow = false
|
||||
}
|
||||
const onAuthorizeModalCancel = () => {
|
||||
state.authorizeModalShow = false
|
||||
}
|
||||
|
||||
provide(UserModalSharedStateKey, { show, mode, user, onSuccess })
|
||||
|
||||
return {
|
||||
t,
|
||||
columnsRef,
|
||||
...toRefs(state),
|
||||
changePage,
|
||||
changePageSize,
|
||||
onAddUser,
|
||||
...tableState
|
||||
onUpdatedList: updateList,
|
||||
onDetailModalCancel,
|
||||
onAuthorizeModalCancel
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { t, onSearchValOk, onSearchValClear, userListLoading } = this
|
||||
return (
|
||||
<>
|
||||
<NGrid cols={1} yGap={16}>
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
<NSpace justify='space-between'>
|
||||
<NButton
|
||||
onClick={this.onAddUser}
|
||||
type='primary'
|
||||
class='btn-create-user'
|
||||
>
|
||||
{t('security.user.create_user')}
|
||||
<NSpace vertical>
|
||||
<Card>
|
||||
<NSpace justify='space-between'>
|
||||
<NButton
|
||||
onClick={this.onAddUser}
|
||||
type='primary'
|
||||
class='btn-create-user'
|
||||
>
|
||||
{this.t('security.user.create_user')}
|
||||
</NButton>
|
||||
<NSpace>
|
||||
<NInput v-model:value={this.searchVal} clearable />
|
||||
<NButton type='primary' onClick={this.onUpdatedList}>
|
||||
<NIcon>
|
||||
<SearchOutlined />
|
||||
</NIcon>
|
||||
</NButton>
|
||||
<NInputGroup>
|
||||
<NInput
|
||||
v-model:value={this.searchInputVal}
|
||||
clearable
|
||||
onClear={onSearchValClear}
|
||||
onKeyup={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
onSearchValOk()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<NButton type='primary' onClick={onSearchValOk}>
|
||||
<NIcon>
|
||||
<SearchOutlined />
|
||||
</NIcon>
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
{userListLoading ? (
|
||||
<NSkeleton text repeat={6} />
|
||||
) : (
|
||||
<NSpace v-show={!userListLoading} vertical size={20}>
|
||||
<NDataTable
|
||||
row-class-name='items'
|
||||
columns={this.columns}
|
||||
data={this.userList}
|
||||
scrollX={this.scrollX}
|
||||
bordered={false}
|
||||
/>
|
||||
<NSpace justify='center'>
|
||||
<NPagination
|
||||
v-model:page={this.page}
|
||||
v-model:page-size={this.pageSize}
|
||||
pageCount={this.pageCount}
|
||||
pageSizes={this.pageSizes}
|
||||
showSizePicker
|
||||
/>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
)}
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
<UserModal />
|
||||
</NSpace>
|
||||
</Card>
|
||||
<Card>
|
||||
<NSpace vertical>
|
||||
<NDataTable
|
||||
row-class-name='items'
|
||||
columns={this.columnsRef}
|
||||
data={this.list}
|
||||
loading={this.loading}
|
||||
/>
|
||||
<NSpace justify='center'>
|
||||
<NPagination
|
||||
v-model:page={this.page}
|
||||
v-model:page-size={this.pageSize}
|
||||
item-count={this.itemCount}
|
||||
show-size-picker
|
||||
page-sizes={[10, 30, 50]}
|
||||
show-quick-jumper
|
||||
on-update:page={this.changePage}
|
||||
on-update:page-size={this.changePageSize}
|
||||
/>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</Card>
|
||||
</NSpace>
|
||||
<UserDetailModal
|
||||
show={this.detailModalShow}
|
||||
currentRecord={this.currentRecord}
|
||||
onCancel={this.onDetailModalCancel}
|
||||
onUpdate={this.onUpdatedList}
|
||||
/>
|
||||
<AuthorizeModal
|
||||
show={this.authorizeModalShow}
|
||||
type={this.authorizeType}
|
||||
userId={this.currentRecord?.id}
|
||||
onCancel={this.onAuthorizeModalCancel}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import type {
|
||||
TableColumns,
|
||||
InternalRowData
|
||||
} from 'naive-ui/es/data-table/src/interface'
|
||||
import { UserReq } from '@/service/modules/users/types'
|
||||
export type { UserInfoRes } from '@/service/modules/users/types'
|
||||
|
||||
type TUserType = 'GENERAL_USER' | ''
|
||||
type TAuthType =
|
||||
| 'authorize_project'
|
||||
| 'authorize_resource'
|
||||
| 'authorize_datasource'
|
||||
| 'authorize_udf'
|
||||
|
||||
interface IRecord {
|
||||
id: number
|
||||
userName: string
|
||||
userType: TUserType
|
||||
tenantCode: string
|
||||
queueName: string
|
||||
email: string
|
||||
phone: string
|
||||
state: 0 | 1
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
|
||||
interface IResourceOption {
|
||||
id: number
|
||||
fullName: string
|
||||
type: string
|
||||
}
|
||||
|
||||
interface IOption {
|
||||
value: number
|
||||
label: string
|
||||
}
|
||||
|
||||
export {
|
||||
IRecord,
|
||||
IResourceOption,
|
||||
IOption,
|
||||
TAuthType,
|
||||
UserReq,
|
||||
TableColumns,
|
||||
InternalRowData
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { h, ref, watch, onMounted, Ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
NSpace,
|
||||
NTooltip,
|
||||
NButton,
|
||||
NIcon,
|
||||
NTag,
|
||||
NDropdown,
|
||||
NPopconfirm
|
||||
} from 'naive-ui'
|
||||
import { EditOutlined, DeleteOutlined, UserOutlined } from '@vicons/antd'
|
||||
import { TableColumns, InternalRowData } from './types'
|
||||
|
||||
export function useColumns(onCallback: Function) {
|
||||
const { t } = useI18n()
|
||||
|
||||
const columnsRef = ref([]) as Ref<TableColumns>
|
||||
|
||||
const createColumns = () => {
|
||||
columnsRef.value = [
|
||||
{
|
||||
title: '#',
|
||||
key: 'index',
|
||||
render: (rowData: InternalRowData, rowIndex: number) => rowIndex + 1
|
||||
},
|
||||
{
|
||||
title: t('security.user.username'),
|
||||
key: 'userName'
|
||||
},
|
||||
{
|
||||
title: t('security.user.user_type'),
|
||||
key: 'userType',
|
||||
render: (rowData: InternalRowData) =>
|
||||
rowData.userType === 'GENERAL_USER'
|
||||
? t('security.user.ordinary_user')
|
||||
: t('security.user.administrator')
|
||||
},
|
||||
{
|
||||
title: t('security.user.tenant_code'),
|
||||
key: 'tenantCode'
|
||||
},
|
||||
{
|
||||
title: t('security.user.queue'),
|
||||
key: 'queue'
|
||||
},
|
||||
{
|
||||
title: t('security.user.email'),
|
||||
key: 'email'
|
||||
},
|
||||
{
|
||||
title: t('security.user.phone'),
|
||||
key: 'phone'
|
||||
},
|
||||
{
|
||||
title: t('security.user.state'),
|
||||
key: 'state',
|
||||
render: (rowData: any, unused: number) =>
|
||||
h(
|
||||
NTag,
|
||||
{ type: rowData.state === 1 ? 'success' : 'error' },
|
||||
{
|
||||
default: () =>
|
||||
t(
|
||||
`security.user.state_${
|
||||
rowData.state === 1 ? 'enabled' : 'disabled'
|
||||
}`
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('security.user.create_time'),
|
||||
key: 'createTime'
|
||||
},
|
||||
{
|
||||
title: t('security.user.update_time'),
|
||||
key: 'updateTime'
|
||||
},
|
||||
{
|
||||
title: t('security.user.operation'),
|
||||
key: 'operation',
|
||||
render: (rowData: any, unused: number) => {
|
||||
return h(NSpace, null, {
|
||||
default: () => [
|
||||
h(
|
||||
NDropdown,
|
||||
{
|
||||
trigger: 'click',
|
||||
options: [
|
||||
{
|
||||
label: t('security.user.project'),
|
||||
key: 'authorize_project'
|
||||
},
|
||||
{
|
||||
label: t('security.user.resource'),
|
||||
key: 'authorize_resource'
|
||||
},
|
||||
{
|
||||
label: t('security.user.datasource'),
|
||||
key: 'authorize_datasource'
|
||||
},
|
||||
{ label: t('security.user.udf'), key: 'authorize_udf' }
|
||||
],
|
||||
onSelect: (key) =>
|
||||
void onCallback({ rowData, key }, 'authorize')
|
||||
},
|
||||
() =>
|
||||
h(
|
||||
NTooltip,
|
||||
{
|
||||
trigger: 'hover'
|
||||
},
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
circle: true,
|
||||
type: 'warning',
|
||||
size: 'small',
|
||||
class: 'authorize'
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, null, () => h(UserOutlined))
|
||||
}
|
||||
),
|
||||
default: () => t('security.user.authorize')
|
||||
}
|
||||
)
|
||||
),
|
||||
h(
|
||||
NTooltip,
|
||||
{ trigger: 'hover' },
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
circle: true,
|
||||
type: 'info',
|
||||
size: 'small',
|
||||
onClick: () => void onCallback({ rowData }, 'edit')
|
||||
},
|
||||
() => h(NIcon, null, () => h(EditOutlined))
|
||||
),
|
||||
default: () => t('security.user.edit')
|
||||
}
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => void onCallback({ rowData }, 'delete')
|
||||
},
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NTooltip,
|
||||
{},
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
circle: true,
|
||||
type: 'error',
|
||||
size: 'small',
|
||||
class: 'delete'
|
||||
},
|
||||
{
|
||||
icon: () =>
|
||||
h(NIcon, null, {
|
||||
default: () => h(DeleteOutlined)
|
||||
})
|
||||
}
|
||||
),
|
||||
default: () => t('security.user.delete')
|
||||
}
|
||||
),
|
||||
default: () => t('security.user.delete_confirm')
|
||||
}
|
||||
)
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
createColumns()
|
||||
})
|
||||
|
||||
watch(useI18n().locale, () => {
|
||||
createColumns()
|
||||
})
|
||||
|
||||
return {
|
||||
columnsRef,
|
||||
createColumns
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { queryUserList, delUserById } from '@/service/modules/users'
|
||||
import { format } from 'date-fns'
|
||||
import { parseTime } from '@/utils/common'
|
||||
import type { IRecord, TAuthType } from './types'
|
||||
|
||||
export function useTable() {
|
||||
const state = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
searchVal: '',
|
||||
list: [],
|
||||
loading: false,
|
||||
currentRecord: {} as IRecord | null,
|
||||
authorizeType: 'authorize_project' as TAuthType,
|
||||
detailModalShow: false,
|
||||
authorizeModalShow: false
|
||||
})
|
||||
|
||||
const getList = async () => {
|
||||
if (state.loading) return
|
||||
state.loading = true
|
||||
|
||||
const { totalList, total } = await queryUserList({
|
||||
pageNo: state.page,
|
||||
pageSize: state.pageSize,
|
||||
searchVal: state.searchVal
|
||||
})
|
||||
state.loading = false
|
||||
if (!totalList) throw Error()
|
||||
state.list = totalList.map((record: IRecord) => {
|
||||
record.createTime = record.createTime
|
||||
? format(parseTime(record.createTime), 'yyyy-MM-dd HH:mm:ss')
|
||||
: ''
|
||||
record.updateTime = record.updateTime
|
||||
? format(parseTime(record.updateTime), 'yyyy-MM-dd HH:mm:ss')
|
||||
: ''
|
||||
return record
|
||||
})
|
||||
|
||||
state.itemCount = total
|
||||
}
|
||||
|
||||
const updateList = () => {
|
||||
if (state.list.length === 1 && state.page > 1) {
|
||||
--state.page
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
const deleteUser = async (userId: number) => {
|
||||
await delUserById({ id: userId })
|
||||
updateList()
|
||||
}
|
||||
|
||||
const onOperationClick = (
|
||||
data: { rowData: IRecord; key?: TAuthType },
|
||||
type: 'authorize' | 'edit' | 'delete'
|
||||
) => {
|
||||
state.currentRecord = data.rowData
|
||||
if (type === 'edit') {
|
||||
state.detailModalShow = true
|
||||
}
|
||||
if (type === 'authorize' && data.key) {
|
||||
state.authorizeModalShow = true
|
||||
state.authorizeType = data.key
|
||||
}
|
||||
if (type === 'delete') {
|
||||
deleteUser(data.rowData.id)
|
||||
}
|
||||
}
|
||||
|
||||
// const deleteRecord = async (id: number) => {
|
||||
// const ignored = await deleteAlertPluginInstance(id)
|
||||
// updateList()
|
||||
// }
|
||||
|
||||
const changePage = (page: number) => {
|
||||
state.page = page
|
||||
getList()
|
||||
}
|
||||
|
||||
const changePageSize = (pageSize: number) => {
|
||||
state.page = 1
|
||||
state.pageSize = pageSize
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
return { state, changePage, changePageSize, updateList, onOperationClick }
|
||||
}
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ref, watch, onBeforeMount, computed } from 'vue'
|
||||
import { NSpace, NTooltip, NButton, NIcon, NTag, NDropdown } from 'naive-ui'
|
||||
import { EditOutlined, DeleteOutlined, UserOutlined } from '@vicons/antd'
|
||||
import { queryUserList } from '@/service/modules/users'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Mode } from './components/use-modal'
|
||||
|
||||
type UseTableProps = {
|
||||
onEdit: (user: any, mode: Mode) => void
|
||||
onDelete: (user: any) => void
|
||||
}
|
||||
|
||||
function useColumns({ onEdit, onDelete }: UseTableProps) {
|
||||
const { t } = useI18n()
|
||||
const columns = computed(() =>
|
||||
[
|
||||
{
|
||||
title: '#',
|
||||
key: 'index',
|
||||
width: 80,
|
||||
render: (rowData: any, rowIndex: number) => rowIndex + 1
|
||||
},
|
||||
{
|
||||
title: t('security.user.username'),
|
||||
key: 'userName',
|
||||
className: 'name'
|
||||
},
|
||||
{
|
||||
title: t('security.user.tenant_code'),
|
||||
key: 'tenantCode'
|
||||
},
|
||||
{
|
||||
title: t('security.user.queue'),
|
||||
key: 'queue'
|
||||
},
|
||||
{
|
||||
title: t('security.user.email'),
|
||||
key: 'email'
|
||||
},
|
||||
{
|
||||
title: t('security.user.phone'),
|
||||
key: 'phone'
|
||||
},
|
||||
{
|
||||
title: t('security.user.state'),
|
||||
key: 'state',
|
||||
render: (rowData: any, unused: number) => {
|
||||
return rowData.state === 1 ? (
|
||||
<NTag type='success'>{t('security.user.state_enabled')}</NTag>
|
||||
) : (
|
||||
<NTag type='error'>{t('security.user.state_disabled')}</NTag>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('security.user.create_time'),
|
||||
key: 'createTime',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: t('security.user.update_time'),
|
||||
key: 'updateTime',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: t('security.user.operation'),
|
||||
key: 'operation',
|
||||
fixed: 'right',
|
||||
width: 140,
|
||||
render: (rowData: any, unused: number) => {
|
||||
return (
|
||||
<NSpace>
|
||||
<NDropdown
|
||||
trigger='click'
|
||||
options={[
|
||||
{ label: t('security.user.project'), key: 'auth_project' },
|
||||
{ label: t('security.user.resource'), key: 'auth_resource' },
|
||||
{
|
||||
label: t('security.user.datasource'),
|
||||
key: 'auth_datasource'
|
||||
},
|
||||
{ label: t('security.user.udf'), key: 'auth_udf' }
|
||||
]}
|
||||
onSelect={(key) => {
|
||||
onEdit(rowData, key)
|
||||
}}
|
||||
>
|
||||
<NTooltip trigger='hover'>
|
||||
{{
|
||||
trigger: () => (
|
||||
<NButton
|
||||
circle
|
||||
type='warning'
|
||||
size='small'
|
||||
class='authorize'
|
||||
>
|
||||
{{
|
||||
icon: () => (
|
||||
<NIcon>
|
||||
<UserOutlined />
|
||||
</NIcon>
|
||||
)
|
||||
}}
|
||||
</NButton>
|
||||
),
|
||||
default: () => t('security.user.authorize')
|
||||
}}
|
||||
</NTooltip>
|
||||
</NDropdown>
|
||||
<NTooltip trigger='hover'>
|
||||
{{
|
||||
trigger: () => (
|
||||
<NButton
|
||||
circle
|
||||
type='info'
|
||||
size='small'
|
||||
class='edit'
|
||||
onClick={() => {
|
||||
onEdit(rowData, 'edit')
|
||||
}}
|
||||
>
|
||||
{{
|
||||
icon: () => (
|
||||
<NIcon>
|
||||
<EditOutlined />
|
||||
</NIcon>
|
||||
)
|
||||
}}
|
||||
</NButton>
|
||||
),
|
||||
default: () => t('security.user.edit')
|
||||
}}
|
||||
</NTooltip>
|
||||
<NTooltip trigger='hover'>
|
||||
{{
|
||||
trigger: () => (
|
||||
<NButton
|
||||
circle
|
||||
type='error'
|
||||
size='small'
|
||||
class='delete'
|
||||
onClick={() => {
|
||||
onDelete(rowData)
|
||||
}}
|
||||
>
|
||||
{{
|
||||
icon: () => (
|
||||
<NIcon>
|
||||
<DeleteOutlined />
|
||||
</NIcon>
|
||||
)
|
||||
}}
|
||||
</NButton>
|
||||
),
|
||||
default: () => t('security.user.delete')
|
||||
}}
|
||||
</NTooltip>
|
||||
</NSpace>
|
||||
)
|
||||
}
|
||||
}
|
||||
].map((d: any) => ({ ...d, width: d.width || 160 }))
|
||||
)
|
||||
|
||||
const scrollX = columns.value.reduce((p, c) => p + c.width, 0)
|
||||
|
||||
return {
|
||||
columns,
|
||||
scrollX
|
||||
}
|
||||
}
|
||||
|
||||
export function useTable(props: UseTableProps) {
|
||||
const page = ref(1)
|
||||
const pageCount = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const searchInputVal = ref()
|
||||
const searchVal = ref('')
|
||||
const pageSizes = [10, 30, 50]
|
||||
const userListLoading = ref(false)
|
||||
const userList = ref([])
|
||||
const { columns, scrollX } = useColumns(props)
|
||||
|
||||
const getUserList = () => {
|
||||
userListLoading.value = true
|
||||
queryUserList({
|
||||
pageNo: page.value,
|
||||
pageSize: pageSize.value,
|
||||
searchVal: searchVal.value
|
||||
})
|
||||
.then((res: any) => {
|
||||
userList.value = res?.totalList || []
|
||||
pageCount.value = res?.totalPage || 0
|
||||
})
|
||||
.finally(() => {
|
||||
userListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const resetPage = () => {
|
||||
page.value = 1
|
||||
}
|
||||
|
||||
const onSearchValOk = () => {
|
||||
resetPage()
|
||||
searchVal.value = searchInputVal.value
|
||||
}
|
||||
|
||||
const onSearchValClear = () => {
|
||||
resetPage()
|
||||
searchVal.value = ''
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
getUserList()
|
||||
})
|
||||
|
||||
watch([page, pageSize, searchVal], () => {
|
||||
getUserList()
|
||||
})
|
||||
|
||||
return {
|
||||
userList,
|
||||
userListLoading,
|
||||
getUserList,
|
||||
page,
|
||||
pageCount,
|
||||
pageSize,
|
||||
searchVal,
|
||||
searchInputVal,
|
||||
pageSizes,
|
||||
columns,
|
||||
scrollX,
|
||||
onSearchValOk,
|
||||
onSearchValClear,
|
||||
resetPage
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue