diff --git a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts b/dolphinscheduler-ui-next/src/locales/modules/en_US.ts index 7597e4a30f..c9d6e64c19 100644 --- a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts +++ b/dolphinscheduler-ui-next/src/locales/modules/en_US.ts @@ -801,6 +801,16 @@ const security = { delete_confirm: 'Are you sure to delete?', delete_confirm_tip: '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', username: 'Username', username_exists: 'The username already exists', @@ -824,8 +834,11 @@ const security = { operation: 'Operation', edit: 'Edit', delete: 'Delete', + authorize: 'Authorize', 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: { search_input_tips: 'Please input the keywords', diff --git a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts b/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts index 10b5a39627..6e58969d88 100644 --- a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts +++ b/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts @@ -792,6 +792,16 @@ const security = { delete_user: '删除用户', delete_confirm: '确定删除吗?', delete_confirm_tip: '删除用户属于危险操作,请谨慎操作!', + project: '项目', + resource: '资源', + file_resource: '文件资源', + udf_resource: 'UDF资源', + datasource: '数据源', + udf: 'UDF函数', + authorize_project: '项目授权', + authorize_resource: '资源授权', + authorize_datasource: '数据源授权', + authorize_udf: 'UDF函数授权', index: '序号', username: '用户名', username_exists: '用户名已存在', @@ -814,8 +824,11 @@ const security = { operation: '操作', edit: '编辑', delete: '删除', + authorize: '授权', save_error_msg: '保存失败,请重试', - delete_error_msg: '删除失败,请重试' + delete_error_msg: '删除失败,请重试', + auth_error_msg: '授权失败,请重试', + auth_success_msg: '授权成功' }, alarm_instance: { search_input_tips: '请输入关键字', diff --git a/dolphinscheduler-ui-next/src/service/modules/data-source/index.ts b/dolphinscheduler-ui-next/src/service/modules/data-source/index.ts index b3aae9b624..c24fa821cc 100644 --- a/dolphinscheduler-ui-next/src/service/modules/data-source/index.ts +++ b/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({ url: '/datasources/authed-datasource', 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({ url: '/datasources/unauth-datasource', method: 'get', diff --git a/dolphinscheduler-ui-next/src/service/modules/projects/index.ts b/dolphinscheduler-ui-next/src/service/modules/projects/index.ts index 555fec1ab6..6a8de0e073 100644 --- a/dolphinscheduler-ui-next/src/service/modules/projects/index.ts +++ b/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({ url: '/projects/authed-project', method: 'get', @@ -56,7 +56,7 @@ export function queryAllProjectList(): any { }) } -export function queryUnauthorizedProject(params: UserIdReq): any { +export function queryUnauthorizedProject(params: UserIdReq) { return axios({ url: '/projects/unauth-project', method: 'get', diff --git a/dolphinscheduler-ui-next/src/service/modules/resources/index.ts b/dolphinscheduler-ui-next/src/service/modules/resources/index.ts index 62bb592448..061033d4ee 100644 --- a/dolphinscheduler-ui-next/src/service/modules/resources/index.ts +++ b/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({ url: '/resources/authed-file', 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({ url: '/resources/authed-resource-tree', 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({ url: '/resources/authed-udf-func', 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({ url: '/resources/unauth-udf-func', method: 'get', diff --git a/dolphinscheduler-ui-next/src/service/modules/users/index.ts b/dolphinscheduler-ui-next/src/service/modules/users/index.ts index 7d56619783..2a9c4c44ac 100644 --- a/dolphinscheduler-ui-next/src/service/modules/users/index.ts +++ b/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({ url: '/users/grant-datasource', 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({ url: '/users/grant-file', 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({ url: '/users/grant-project', 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({ url: '/users/grant-udf-func', method: 'post', diff --git a/dolphinscheduler-ui-next/src/views/security/user-manage/components/use-modal.ts b/dolphinscheduler-ui-next/src/views/security/user-manage/components/use-modal.ts index e5f50aee6d..fe9507e2cc 100644 --- a/dolphinscheduler-ui-next/src/views/security/user-manage/components/use-modal.ts +++ b/dolphinscheduler-ui-next/src/views/security/user-manage/components/use-modal.ts @@ -24,10 +24,35 @@ import { createUser, updateUser, delUserById, - verifyUserName + 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' +export type Mode = + | 'add' + | 'edit' + | 'delete' + | 'auth_project' + | 'auth_resource' + | 'auth_datasource' + | 'auth_udf' export type UserModalSharedStateType = ReturnType< typeof useSharedUserModalState @@ -66,6 +91,15 @@ export function useModal({ }) const tenants = ref([]) const queues = ref([]) + const authorizedProjects = ref([]) + const projects = ref([]) + const authorizedFiles = ref([]) + const originResourceTree = ref([]) + const resourceType = ref<'file' | 'udf'>() + const authorizedUDF = ref([]) + const UDFs = ref([]) + const authorizedDatasource = ref([]) + const datasource = ref([]) const optionsLoading = ref(false) const confirmLoading = ref(false) @@ -125,11 +159,44 @@ export function useModal({ } }) - const titleMap: Record = { - add: t('security.user.create_user'), - edit: t('security.user.update_user'), - delete: t('security.user.delete_user') - } + 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 = { + 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 = { @@ -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 = () => { confirmLoading.value = true delUserById({ id: user.value.id }) @@ -235,24 +399,70 @@ export function useModal({ }) } + const onGrant = (grantReq: () => Promise) => { + 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 === 'delete') { - onDelete() - } else { + 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(',') + }) + ) } } - const onModalCancel = () => { - show.value = false - } - 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], () => { @@ -270,6 +480,15 @@ export function useModal({ formRules, tenants, queues, + authorizedProjects, + projects, + authorizedDatasource, + datasource, + authorizedUDF, + UDFs, + authorizedFiles, + resourceTree, + resourceType, optionsLoading, onConfirm, confirmLoading diff --git a/dolphinscheduler-ui-next/src/views/security/user-manage/components/user-modal.tsx b/dolphinscheduler-ui-next/src/views/security/user-manage/components/user-modal.tsx index 77fd93b576..4578f3b84c 100644 --- a/dolphinscheduler-ui-next/src/views/security/user-manage/components/user-modal.tsx +++ b/dolphinscheduler-ui-next/src/views/security/user-manage/components/user-modal.tsx @@ -24,8 +24,11 @@ import { NSelect, NRadio, NRadioGroup, + NRadioButton, NSpace, - NAlert + NAlert, + NTransfer, + NTreeSelect } from 'naive-ui' import Modal from '@/components/modal' @@ -55,6 +58,7 @@ export const UserModal = defineComponent({ show={this.show} title={this.titleMap?.[this.mode || 'add']} onCancel={this.onModalCancel} + confirmDisabled={this.optionsLoading} confirmLoading={this.confirmLoading} onConfirm={this.onConfirm} confirmClassName='btn-submit' @@ -69,6 +73,62 @@ export const UserModal = defineComponent({ ) } + if (this.mode === 'auth_project') { + return ( + + ) + } + if (this.mode === 'auth_datasource') { + return ( + + ) + } + if (this.mode === 'auth_udf') { + return ( + + ) + } + if (this.mode === 'auth_resource') { + return ( + + + + {t('security.user.file_resource')} + + + {t('security.user.udf_resource')} + + + + + ) + } return ( { + onEdit: (u, m: Mode) => { show.value = true - mode.value = 'edit' + mode.value = m user.value = u }, onDelete: (u) => { @@ -58,10 +58,10 @@ const UsersManage = defineComponent({ }) const onSuccess = (mode: Mode) => { - if (mode === 'add') { - tableState.resetPage() + if (!mode.startsWith('auth')) { + mode === 'add' && tableState.resetPage() + tableState.getUserList() } - tableState.getUserList() } const onAddUser = () => { diff --git a/dolphinscheduler-ui-next/src/views/security/user-manage/use-table.tsx b/dolphinscheduler-ui-next/src/views/security/user-manage/use-table.tsx index 4e9a18e43c..7075540b2d 100644 --- a/dolphinscheduler-ui-next/src/views/security/user-manage/use-table.tsx +++ b/dolphinscheduler-ui-next/src/views/security/user-manage/use-table.tsx @@ -16,13 +16,14 @@ */ import { ref, watch, onBeforeMount, computed } from 'vue' -import { NSpace, NTooltip, NButton, NIcon, NTag } from 'naive-ui' -import { EditOutlined, DeleteOutlined } from '@vicons/antd' +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) => void + onEdit: (user: any, mode: Mode) => void onDelete: (user: any) => void } @@ -82,10 +83,47 @@ function useColumns({ onEdit, onDelete }: UseTableProps) { title: t('security.user.operation'), key: 'operation', fixed: 'right', - width: 120, + width: 140, render: (rowData: any, rowIndex: number) => { return ( + { + onEdit(rowData, key) + }} + > + + {{ + trigger: () => ( + + {{ + icon: () => ( + + + + ) + }} + + ), + default: () => t('security.user.authorize') + }} + + {{ trigger: () => ( @@ -95,7 +133,7 @@ function useColumns({ onEdit, onDelete }: UseTableProps) { size='small' class='edit' onClick={() => { - onEdit(rowData) + onEdit(rowData, 'edit') }} > {{