diff --git a/dolphinscheduler-ui/src/locales/en_US/security.ts b/dolphinscheduler-ui/src/locales/en_US/security.ts index c6dcb02892fe..44536982b253 100644 --- a/dolphinscheduler-ui/src/locales/en_US/security.ts +++ b/dolphinscheduler-ui/src/locales/en_US/security.ts @@ -144,6 +144,7 @@ export default { delete_confirm: 'Are you sure to delete?', delete_confirm_tip: 'Deleting user is a dangerous operation,please be careful', + reset_password: 'Reset Password', project: 'Project', resource: 'Resource', file_resource: 'File Resource', @@ -165,6 +166,7 @@ export default { user_password: 'Password', user_password_tips: 'Please enter a password containing letters and numbers with a length between 6 and 20', + confirm_password_tips: 'The both of password and confirm password are not same.', user_type: 'User Type', ordinary_user: 'Ordinary users', administrator: 'Administrator', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/security.ts b/dolphinscheduler-ui/src/locales/zh_CN/security.ts index cc86de550441..ffe4f7dff8a6 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/security.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/security.ts @@ -142,6 +142,7 @@ export default { edit_user: '编辑用户', delete_user: '删除用户', delete_confirm: '确定删除吗?', + reset_password: '重新设置密码', project: '项目', resource: '资源', file_resource: '文件资源', @@ -162,6 +163,7 @@ export default { username_tips: '请输入用户名', user_password: '密码', user_password_tips: '请输入包含字母和数字,长度在6~20之间的密码', + confirm_password_tips: '两次密码输入不一致', user_type: '用户类型', ordinary_user: '普通用户', administrator: '管理员', diff --git a/dolphinscheduler-ui/src/views/security/user-manage/components/password-modal.tsx b/dolphinscheduler-ui/src/views/security/user-manage/components/password-modal.tsx new file mode 100644 index 000000000000..257a19ce3308 --- /dev/null +++ b/dolphinscheduler-ui/src/views/security/user-manage/components/password-modal.tsx @@ -0,0 +1,146 @@ +/* + * 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, + getCurrentInstance, + PropType, + toRefs, + watch +} from 'vue' +import { useI18n } from 'vue-i18n' +import { NInput, NForm, NFormItem } from 'naive-ui' +import { usePassword } from './use-password' +import Modal from '@/components/modal' +import type { IRecord } from '../types' + +const props = { + show: { + type: Boolean as PropType, + default: false + }, + currentRecord: { + type: Object as PropType, + default: {} + } +} + +export const PasswordModal = defineComponent({ + name: 'password-modal', + props, + emits: ['cancel', 'update'], + setup(props, ctx) { + const { t } = useI18n() + const { state, IS_ADMIN, formRules, onReset, onSave, onSetValues } = + usePassword() + + const onCancel = () => { + onReset() + ctx.emit('cancel') + } + const onConfirm = async () => { + if (props.currentRecord?.id) { + const result = await onSave(props.currentRecord) + if (!result) return + } + onCancel() + ctx.emit('update') + } + + const trim = getCurrentInstance()?.appContext.config.globalProperties.trim + + watch( + () => props.show, + () => { + if (props.show && props.currentRecord?.id) { + onSetValues(props.currentRecord) + } + } + ) + + return { + t, + ...toRefs(state), + IS_ADMIN, + formRules, + onCancel, + onConfirm, + trim + } + }, + render(props: { currentRecord: IRecord }) { + const { t } = this + + return ( + + + + + + + + + + + + + + ) + } +}) + +export default PasswordModal diff --git a/dolphinscheduler-ui/src/views/security/user-manage/components/use-password.ts b/dolphinscheduler-ui/src/views/security/user-manage/components/use-password.ts new file mode 100644 index 000000000000..50345a6243dc --- /dev/null +++ b/dolphinscheduler-ui/src/views/security/user-manage/components/use-password.ts @@ -0,0 +1,134 @@ +/* + * 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, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { pick } from 'lodash' +import { useUserStore } from '@/store/user/user' +import { updateUser } from '@/service/modules/users' +import type { IRecord, UserInfoRes } from '../types' +import { FormItemRule } from 'naive-ui' +import { UserReq } from '../types' +import { IdReq } from '@/service/modules/users/types' + +export function usePassword() { + const { t } = useI18n() + const userStore = useUserStore() + const userInfo = userStore.getUserInfo as UserInfoRes + const IS_ADMIN = userInfo.userType === 'ADMIN_USER' + + const initialValues = { + userName: '', + userPassword: '', + confirmPassword: '' + } + + const state = reactive({ + formRef: ref(), + formData: { ...initialValues }, + saving: false, + loading: false + }) + + function validatePasswordStartWith( + rule: FormItemRule, + value: string + ): boolean { + return ( + !!state.formRef.model.userPassword && + state.formRef.model.userPassword.startsWith(value) && + state.formRef.model.userPassword.length >= value.length + ) + } + + function validatePasswordSame(rule: FormItemRule, value: string): boolean { + return value === state.formRef.model.userPassword + } + + const formRules = { + 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')) + } + } + }, + confirmPassword: [ + { + trigger: ['input', 'blur'], + required: true + }, + { + validator: validatePasswordStartWith, + message: t('security.user.confirm_password_tips'), + trigger: ['input'] + }, + { + validator: validatePasswordSame, + message: t('security.user.confirm_password_tips'), + trigger: ['blur', 'password-input'] + } + ] + } + + const onReset = () => { + state.formData = { ...initialValues } + } + const onSave = async (record: IRecord): Promise => { + try { + await state.formRef.validate() + if (state.saving) return false + state.saving = true + + const resetPasswordReq = { + ...pick(record, [ + 'id', + 'userName', + 'tenantId', + 'email', + 'queue', + 'phone', + 'state' + ]), + userPassword: state.formData.userPassword + } as IdReq & UserReq + + await updateUser(resetPasswordReq) + + state.saving = false + return true + } catch (err) { + state.saving = false + return false + } + } + const onSetValues = (record: IRecord) => { + state.formData = { + ...pick(record, ['userName']), + userPassword: '', + confirmPassword: '' + } + } + + return { state, formRules, IS_ADMIN, onReset, onSave, onSetValues } +} diff --git a/dolphinscheduler-ui/src/views/security/user-manage/components/user-detail-modal.tsx b/dolphinscheduler-ui/src/views/security/user-manage/components/user-detail-modal.tsx index c66a414c956d..751bd3c009d5 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/components/user-detail-modal.tsx +++ b/dolphinscheduler-ui/src/views/security/user-manage/components/user-detail-modal.tsx @@ -110,7 +110,7 @@ export const UserModal = defineComponent({ rules={this.formRules} labelPlacement='left' labelAlign='left' - labelWidth={80} + labelWidth={120} > { state.authorizeModalShow = false } + const onPasswordModalCancel = () => { + state.passwordModalShow = false + } + const trim = getCurrentInstance()?.appContext.config.globalProperties.trim return { @@ -56,6 +61,7 @@ const UsersManage = defineComponent({ onUpdatedList: updateList, onDetailModalCancel, onAuthorizeModalCancel, + onPasswordModalCancel, trim } }, @@ -120,6 +126,11 @@ const UsersManage = defineComponent({ userId={this.currentRecord?.id} onCancel={this.onAuthorizeModalCancel} /> + ) } diff --git a/dolphinscheduler-ui/src/views/security/user-manage/types.ts b/dolphinscheduler-ui/src/views/security/user-manage/types.ts index a91e07101ad2..bf51fe394728 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/types.ts +++ b/dolphinscheduler-ui/src/views/security/user-manage/types.ts @@ -41,6 +41,8 @@ interface IRecord { state: 0 | 1 createTime: string updateTime: string + userPassword?: string + confirmPassword?: string } interface IResourceOption { diff --git a/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts b/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts index 5e77865f732d..812e104e9d85 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts +++ b/dolphinscheduler-ui/src/views/security/user-manage/use-columns.ts @@ -26,17 +26,28 @@ import { NDropdown, NPopconfirm } from 'naive-ui' -import { EditOutlined, DeleteOutlined, UserOutlined } from '@vicons/antd' +import { + EditOutlined, + DeleteOutlined, + UserOutlined, + KeyOutlined +} from '@vicons/antd' import { COLUMN_WIDTH_CONFIG, calculateTableWidth, DefaultTableWidth } from '@/common/column-width-config' import type { TableColumns, InternalRowData } from './types' +import { useUserStore } from '@/store/user/user' +import { UserInfoRes } from './types' export function useColumns(onCallback: Function) { const { t } = useI18n() + const userStore = useUserStore() + const userInfo = userStore.getUserInfo as UserInfoRes + const IS_ADMIN = userInfo.userType === 'ADMIN_USER' + const columnsRef = ref({ columns: [] as TableColumns, tableWidth: DefaultTableWidth @@ -116,8 +127,8 @@ export function useColumns(onCallback: Function) { { title: t('security.user.operation'), key: 'operation', - ...COLUMN_WIDTH_CONFIG['operation'](3), - render: (rowData: any, unused: number) => { + ...COLUMN_WIDTH_CONFIG['operation'](4), + render: (rowData: InternalRowData, unused: number) => { return h(NSpace, null, { default: () => [ h( @@ -158,7 +169,7 @@ export function useColumns(onCallback: Function) { NButton, { circle: true, - type: 'warning', + type: 'info', size: 'small', class: 'authorize' }, @@ -189,6 +200,27 @@ export function useColumns(onCallback: Function) { default: () => t('security.user.edit') } ), + IS_ADMIN && + h( + NTooltip, + { trigger: 'hover' }, + { + trigger: () => + h( + NButton, + { + circle: true, + type: 'error', + size: 'small', + class: 'edit', + onClick: () => + void onCallback({ rowData }, 'resetPassword') + }, + () => h(NIcon, null, () => h(KeyOutlined)) + ), + default: () => t('security.user.reset_password') + } + ), h( NPopconfirm, { diff --git a/dolphinscheduler-ui/src/views/security/user-manage/use-table.ts b/dolphinscheduler-ui/src/views/security/user-manage/use-table.ts index 410c68618e48..2059c7a958d3 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/use-table.ts +++ b/dolphinscheduler-ui/src/views/security/user-manage/use-table.ts @@ -22,6 +22,7 @@ import { parseTime } from '@/common/common' import type { IRecord, TAuthType } from './types' export function useTable() { + const state = reactive({ page: 1, pageSize: 10, @@ -32,7 +33,8 @@ export function useTable() { currentRecord: {} as IRecord | null, authorizeType: 'authorize_project' as TAuthType, detailModalShow: false, - authorizeModalShow: false + authorizeModalShow: false, + passwordModalShow: false }) const getList = async () => { @@ -74,7 +76,7 @@ export function useTable() { const onOperationClick = ( data: { rowData: IRecord; key?: TAuthType }, - type: 'authorize' | 'edit' | 'delete' + type: 'authorize' | 'edit' | 'delete' | 'resetPassword' ) => { state.currentRecord = data.rowData if (type === 'edit') { @@ -87,13 +89,11 @@ export function useTable() { if (type === 'delete') { deleteUser(data.rowData.id) } + if (type === 'resetPassword') { + state.passwordModalShow = true + } } - // const deleteRecord = async (id: number) => { - // const ignored = await deleteAlertPluginInstance(id) - // updateList() - // } - const changePage = (page: number) => { state.page = page getList()