Skip to content

Commit

Permalink
[Improvement-14387][UI] Support to reset user's password. (apache#14498)
Browse files Browse the repository at this point in the history
  • Loading branch information
calvinjiang authored and biaoma-ty committed Aug 17, 2023
1 parent fbf41ef commit 6162655
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 12 deletions.
2 changes: 2 additions & 0 deletions dolphinscheduler-ui/src/locales/en_US/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions dolphinscheduler-ui/src/locales/zh_CN/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export default {
edit_user: '编辑用户',
delete_user: '删除用户',
delete_confirm: '确定删除吗?',
reset_password: '重新设置密码',
project: '项目',
resource: '资源',
file_resource: '文件资源',
Expand All @@ -162,6 +163,7 @@ export default {
username_tips: '请输入用户名',
user_password: '密码',
user_password_tips: '请输入包含字母和数字,长度在6~20之间的密码',
confirm_password_tips: '两次密码输入不一致',
user_type: '用户类型',
ordinary_user: '普通用户',
administrator: '管理员',
Expand Down
Original file line number Diff line number Diff line change
@@ -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<boolean>,
default: false
},
currentRecord: {
type: Object as PropType<IRecord | null>,
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() {
const { t } = this

return (
<Modal
show={this.show}
title={t('security.user.reset_password')}
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={150}
>
<NFormItem label={t('security.user.username')} path='userName'>
<NInput
allowInput={this.trim}
class='input-username'
v-model:value={this.formData.userName}
minlength={3}
maxlength={39}
disabled={true}
placeholder={t('security.user.username_tips')}
/>
</NFormItem>
<NFormItem
label={t('security.user.user_password')}
path='userPassword'
>
<NInput
allowInput={this.trim}
class='input-password'
type='password'
v-model:value={this.formData.userPassword}
placeholder={t('security.user.user_password_tips')}
/>
</NFormItem>
<NFormItem
label={t('password.confirm_password')}
path='confirmPassword'
>
<NInput
allowInput={this.trim}
type='password'
v-model:value={this.formData.confirmPassword}
placeholder={t('password.confirm_password_tips')}
/>
</NFormItem>
</NForm>
</Modal>
)
}
})

export default PasswordModal
Original file line number Diff line number Diff line change
@@ -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<boolean> => {
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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const UserModal = defineComponent({
rules={this.formRules}
labelPlacement='left'
labelAlign='left'
labelWidth={80}
labelWidth={120}
>
<NFormItem label={t('security.user.username')} path='userName'>
<NInput
Expand Down
11 changes: 11 additions & 0 deletions dolphinscheduler-ui/src/views/security/user-manage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useColumns } from './use-columns'
import { useTable } from './use-table'
import UserDetailModal from './components/user-detail-modal'
import AuthorizeModal from './components/authorize-modal'
import PasswordModal from './components/password-modal'
import Card from '@/components/card'
import Search from '@/components/input-search'

Expand All @@ -44,6 +45,10 @@ const UsersManage = defineComponent({
const onAuthorizeModalCancel = () => {
state.authorizeModalShow = false
}
const onPasswordModalCancel = () => {
state.passwordModalShow = false
}

const trim = getCurrentInstance()?.appContext.config.globalProperties.trim

return {
Expand All @@ -56,6 +61,7 @@ const UsersManage = defineComponent({
onUpdatedList: updateList,
onDetailModalCancel,
onAuthorizeModalCancel,
onPasswordModalCancel,
trim
}
},
Expand Down Expand Up @@ -120,6 +126,11 @@ const UsersManage = defineComponent({
userId={this.currentRecord?.id}
onCancel={this.onAuthorizeModalCancel}
/>
<PasswordModal
show={this.passwordModalShow}
currentRecord={this.currentRecord}
onCancel={this.onPasswordModalCancel}
/>
</NSpace>
)
}
Expand Down
2 changes: 2 additions & 0 deletions dolphinscheduler-ui/src/views/security/user-manage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ interface IRecord {
state: 0 | 1
createTime: string
updateTime: string
userPassword?: string
confirmPassword?: string
}

interface IResourceOption {
Expand Down
Loading

0 comments on commit 6162655

Please sign in to comment.