Browse Source

[Fix][UI Next] fix user-page lacks authorization button (#8491)

* [Fix][UI Next] Fix Security User Manage route was outdated

* [Fix][UI Next] Fix table of user-manage did not switch language correctly

* [Fix][UI Next] fix user-page lacks authorization button
3.0.0/version-upgrade
lilyzhou 3 years ago committed by GitHub
parent
commit
0124aa092b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      dolphinscheduler-ui-next/src/locales/modules/en_US.ts
  2. 15
      dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
  3. 4
      dolphinscheduler-ui-next/src/service/modules/data-source/index.ts
  4. 4
      dolphinscheduler-ui-next/src/service/modules/projects/index.ts
  5. 8
      dolphinscheduler-ui-next/src/service/modules/resources/index.ts
  6. 8
      dolphinscheduler-ui-next/src/service/modules/users/index.ts
  7. 249
      dolphinscheduler-ui-next/src/views/security/user-manage/components/use-modal.ts
  8. 62
      dolphinscheduler-ui-next/src/views/security/user-manage/components/user-modal.tsx
  9. 10
      dolphinscheduler-ui-next/src/views/security/user-manage/index.tsx
  10. 48
      dolphinscheduler-ui-next/src/views/security/user-manage/use-table.tsx

15
dolphinscheduler-ui-next/src/locales/modules/en_US.ts

@ -801,6 +801,16 @@ const security = {
delete_confirm: 'Are you sure to delete?', delete_confirm: 'Are you sure to delete?',
delete_confirm_tip: delete_confirm_tip:
'Deleting user is a dangerous operation,please be careful', 'Deleting user is a dangerous operation,please be careful',
project: 'Project',
resource: 'Resource',
file_resource: 'File Resource',
udf_resource: 'UDF Resource',
datasource: 'Datasource',
udf: 'UDF Function',
authorize_project: 'Project Authorize',
authorize_resource: 'Resource Authorize',
authorize_datasource: 'Datasource Authorize',
authorize_udf: 'UDF Function Authorize',
index: 'Index', index: 'Index',
username: 'Username', username: 'Username',
username_exists: 'The username already exists', username_exists: 'The username already exists',
@ -824,8 +834,11 @@ const security = {
operation: 'Operation', operation: 'Operation',
edit: 'Edit', edit: 'Edit',
delete: 'Delete', delete: 'Delete',
authorize: 'Authorize',
save_error_msg: 'Failed to save, please retry', save_error_msg: 'Failed to save, please retry',
delete_error_msg: 'Failed to delete, please retry' delete_error_msg: 'Failed to delete, please retry',
auth_error_msg: 'Failed to authorize, please retry',
auth_success_msg: 'Authorize succeeded'
}, },
alarm_instance: { alarm_instance: {
search_input_tips: 'Please input the keywords', search_input_tips: 'Please input the keywords',

15
dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts

@ -792,6 +792,16 @@ const security = {
delete_user: '删除用户', delete_user: '删除用户',
delete_confirm: '确定删除吗?', delete_confirm: '确定删除吗?',
delete_confirm_tip: '删除用户属于危险操作,请谨慎操作!', delete_confirm_tip: '删除用户属于危险操作,请谨慎操作!',
project: '项目',
resource: '资源',
file_resource: '文件资源',
udf_resource: 'UDF资源',
datasource: '数据源',
udf: 'UDF函数',
authorize_project: '项目授权',
authorize_resource: '资源授权',
authorize_datasource: '数据源授权',
authorize_udf: 'UDF函数授权',
index: '序号', index: '序号',
username: '用户名', username: '用户名',
username_exists: '用户名已存在', username_exists: '用户名已存在',
@ -814,8 +824,11 @@ const security = {
operation: '操作', operation: '操作',
edit: '编辑', edit: '编辑',
delete: '删除', delete: '删除',
authorize: '授权',
save_error_msg: '保存失败,请重试', save_error_msg: '保存失败,请重试',
delete_error_msg: '删除失败,请重试' delete_error_msg: '删除失败,请重试',
auth_error_msg: '授权失败,请重试',
auth_success_msg: '授权成功'
}, },
alarm_instance: { alarm_instance: {
search_input_tips: '请输入关键字', search_input_tips: '请输入关键字',

4
dolphinscheduler-ui-next/src/service/modules/data-source/index.ts

@ -45,7 +45,7 @@ export function createDataSource(data: IDataSource): any {
}) })
} }
export function authedDatasource(params: UserIdReq): any { export function authedDatasource(params: UserIdReq) {
return axios({ return axios({
url: '/datasources/authed-datasource', url: '/datasources/authed-datasource',
method: 'get', method: 'get',
@ -80,7 +80,7 @@ export function queryDataSourceList(params: TypeReq): any {
}) })
} }
export function unAuthDatasource(params: UserIdReq): any { export function unAuthDatasource(params: UserIdReq) {
return axios({ return axios({
url: '/datasources/unauth-datasource', url: '/datasources/unauth-datasource',
method: 'get', method: 'get',

4
dolphinscheduler-ui-next/src/service/modules/projects/index.ts

@ -34,7 +34,7 @@ export function createProject(data: ProjectsReq): any {
}) })
} }
export function queryAuthorizedProject(params: UserIdReq): any { export function queryAuthorizedProject(params: UserIdReq) {
return axios({ return axios({
url: '/projects/authed-project', url: '/projects/authed-project',
method: 'get', method: 'get',
@ -56,7 +56,7 @@ export function queryAllProjectList(): any {
}) })
} }
export function queryUnauthorizedProject(params: UserIdReq): any { export function queryUnauthorizedProject(params: UserIdReq) {
return axios({ return axios({
url: '/projects/unauth-project', url: '/projects/unauth-project',
method: 'get', method: 'get',

8
dolphinscheduler-ui-next/src/service/modules/resources/index.ts

@ -66,7 +66,7 @@ export function createResource(
}) })
} }
export function authorizedFile(params: UserIdReq): any { export function authorizedFile(params: UserIdReq) {
return axios({ return axios({
url: '/resources/authed-file', url: '/resources/authed-file',
method: 'get', method: 'get',
@ -74,7 +74,7 @@ export function authorizedFile(params: UserIdReq): any {
}) })
} }
export function authorizeResourceTree(params: UserIdReq): any { export function authorizeResourceTree(params: UserIdReq) {
return axios({ return axios({
url: '/resources/authed-resource-tree', url: '/resources/authed-resource-tree',
method: 'get', method: 'get',
@ -82,7 +82,7 @@ export function authorizeResourceTree(params: UserIdReq): any {
}) })
} }
export function authUDFFunc(params: UserIdReq): any { export function authUDFFunc(params: UserIdReq) {
return axios({ return axios({
url: '/resources/authed-udf-func', url: '/resources/authed-udf-func',
method: 'get', method: 'get',
@ -159,7 +159,7 @@ export function deleteUdfFunc(id: number): any {
}) })
} }
export function unAuthUDFFunc(params: UserIdReq): any { export function unAuthUDFFunc(params: UserIdReq) {
return axios({ return axios({
url: '/resources/unauth-udf-func', url: '/resources/unauth-udf-func',
method: 'get', method: 'get',

8
dolphinscheduler-ui-next/src/service/modules/users/index.ts

@ -80,7 +80,7 @@ export function getUserInfo(): any {
}) })
} }
export function grantDataSource(data: GrantDataSourceReq): any { export function grantDataSource(data: GrantDataSourceReq) {
return axios({ return axios({
url: '/users/grant-datasource', url: '/users/grant-datasource',
method: 'post', method: 'post',
@ -88,7 +88,7 @@ export function grantDataSource(data: GrantDataSourceReq): any {
}) })
} }
export function grantResource(data: GrantResourceReq): any { export function grantResource(data: GrantResourceReq) {
return axios({ return axios({
url: '/users/grant-file', url: '/users/grant-file',
method: 'post', method: 'post',
@ -96,7 +96,7 @@ export function grantResource(data: GrantResourceReq): any {
}) })
} }
export function grantProject(data: GrantProject): any { export function grantProject(data: GrantProject) {
return axios({ return axios({
url: '/users/grant-project', url: '/users/grant-project',
method: 'post', method: 'post',
@ -112,7 +112,7 @@ export function grantProjectByCode(data: ProjectCodeReq & UserIdReq): any {
}) })
} }
export function grantUDFFunc(data: GrantUDFReq & UserIdReq): any { export function grantUDFFunc(data: GrantUDFReq & UserIdReq) {
return axios({ return axios({
url: '/users/grant-udf-func', url: '/users/grant-udf-func',
method: 'post', method: 'post',

249
dolphinscheduler-ui-next/src/views/security/user-manage/components/use-modal.ts

@ -24,10 +24,35 @@ import {
createUser, createUser,
updateUser, updateUser,
delUserById, delUserById,
verifyUserName verifyUserName,
grantProject,
grantResource,
grantDataSource,
grantUDFFunc
} from '@/service/modules/users' } 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' import regexUtils from '@/utils/regex'
export type Mode = 'add' | 'edit' | 'delete' export type Mode =
| 'add'
| 'edit'
| 'delete'
| 'auth_project'
| 'auth_resource'
| 'auth_datasource'
| 'auth_udf'
export type UserModalSharedStateType = ReturnType< export type UserModalSharedStateType = ReturnType<
typeof useSharedUserModalState typeof useSharedUserModalState
@ -66,6 +91,15 @@ export function useModal({
}) })
const tenants = ref<any[]>([]) const tenants = ref<any[]>([])
const queues = 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 optionsLoading = ref(false)
const confirmLoading = ref(false) const confirmLoading = ref(false)
@ -125,11 +159,44 @@ export function useModal({
} }
}) })
const titleMap: Record<Mode, string> = { const resourceTree = computed(() => {
add: t('security.user.create_user'), const loopTree = (arr: any[]): any[] =>
edit: t('security.user.update_user'), arr
delete: t('security.user.delete_user') .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 setFormValues = () => {
const defaultValues = { const defaultValues = {
@ -173,6 +240,103 @@ export function useModal({
}) })
} }
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 = () => { const onDelete = () => {
confirmLoading.value = true confirmLoading.value = true
delUserById({ id: user.value.id }) delUserById({ id: user.value.id })
@ -235,24 +399,70 @@ export function useModal({
}) })
} }
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 = () => { const onConfirm = () => {
if (mode.value === 'delete') { if (mode.value === 'add' || mode.value === 'edit') {
onDelete()
} else {
formRef.value.validate((errors: any) => { formRef.value.validate((errors: any) => {
if (!errors) { if (!errors) {
user.value ? onUpdateUser() : onCreateUser() 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(',')
})
)
} }
} }
const onModalCancel = () => {
show.value = false
}
watch([show, mode], () => { watch([show, mode], () => {
show.value && mode.value !== 'delete' && prepareOptions() 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], () => { watch([queues, tenants, user], () => {
@ -270,6 +480,15 @@ export function useModal({
formRules, formRules,
tenants, tenants,
queues, queues,
authorizedProjects,
projects,
authorizedDatasource,
datasource,
authorizedUDF,
UDFs,
authorizedFiles,
resourceTree,
resourceType,
optionsLoading, optionsLoading,
onConfirm, onConfirm,
confirmLoading confirmLoading

62
dolphinscheduler-ui-next/src/views/security/user-manage/components/user-modal.tsx

@ -24,8 +24,11 @@ import {
NSelect, NSelect,
NRadio, NRadio,
NRadioGroup, NRadioGroup,
NRadioButton,
NSpace, NSpace,
NAlert NAlert,
NTransfer,
NTreeSelect
} from 'naive-ui' } from 'naive-ui'
import Modal from '@/components/modal' import Modal from '@/components/modal'
@ -55,6 +58,7 @@ export const UserModal = defineComponent({
show={this.show} show={this.show}
title={this.titleMap?.[this.mode || 'add']} title={this.titleMap?.[this.mode || 'add']}
onCancel={this.onModalCancel} onCancel={this.onModalCancel}
confirmDisabled={this.optionsLoading}
confirmLoading={this.confirmLoading} confirmLoading={this.confirmLoading}
onConfirm={this.onConfirm} onConfirm={this.onConfirm}
confirmClassName='btn-submit' confirmClassName='btn-submit'
@ -69,6 +73,62 @@ export const UserModal = defineComponent({
</NAlert> </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 ( return (
<NForm <NForm
ref='formRef' ref='formRef'

10
dolphinscheduler-ui-next/src/views/security/user-manage/index.tsx

@ -45,9 +45,9 @@ const UsersManage = defineComponent({
const { t } = useI18n() const { t } = useI18n()
const { show, mode, user } = useSharedUserModalState() const { show, mode, user } = useSharedUserModalState()
const tableState = useTable({ const tableState = useTable({
onEdit: (u) => { onEdit: (u, m: Mode) => {
show.value = true show.value = true
mode.value = 'edit' mode.value = m
user.value = u user.value = u
}, },
onDelete: (u) => { onDelete: (u) => {
@ -58,10 +58,10 @@ const UsersManage = defineComponent({
}) })
const onSuccess = (mode: Mode) => { const onSuccess = (mode: Mode) => {
if (mode === 'add') { if (!mode.startsWith('auth')) {
tableState.resetPage() mode === 'add' && tableState.resetPage()
tableState.getUserList()
} }
tableState.getUserList()
} }
const onAddUser = () => { const onAddUser = () => {

48
dolphinscheduler-ui-next/src/views/security/user-manage/use-table.tsx

@ -16,13 +16,14 @@
*/ */
import { ref, watch, onBeforeMount, computed } from 'vue' import { ref, watch, onBeforeMount, computed } from 'vue'
import { NSpace, NTooltip, NButton, NIcon, NTag } from 'naive-ui' import { NSpace, NTooltip, NButton, NIcon, NTag, NDropdown } from 'naive-ui'
import { EditOutlined, DeleteOutlined } from '@vicons/antd' import { EditOutlined, DeleteOutlined, UserOutlined } from '@vicons/antd'
import { queryUserList } from '@/service/modules/users' import { queryUserList } from '@/service/modules/users'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Mode } from './components/use-modal'
type UseTableProps = { type UseTableProps = {
onEdit: (user: any) => void onEdit: (user: any, mode: Mode) => void
onDelete: (user: any) => void onDelete: (user: any) => void
} }
@ -82,10 +83,47 @@ function useColumns({ onEdit, onDelete }: UseTableProps) {
title: t('security.user.operation'), title: t('security.user.operation'),
key: 'operation', key: 'operation',
fixed: 'right', fixed: 'right',
width: 120, width: 140,
render: (rowData: any, rowIndex: number) => { render: (rowData: any, rowIndex: number) => {
return ( return (
<NSpace> <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'> <NTooltip trigger='hover'>
{{ {{
trigger: () => ( trigger: () => (
@ -95,7 +133,7 @@ function useColumns({ onEdit, onDelete }: UseTableProps) {
size='small' size='small'
class='edit' class='edit'
onClick={() => { onClick={() => {
onEdit(rowData) onEdit(rowData, 'edit')
}} }}
> >
{{ {{

Loading…
Cancel
Save