mirror of
https://gitee.com/dolphinscheduler/DolphinScheduler.git
synced 2024-12-02 20:28:03 +08:00
[Improvement-14387][UI] Support to reset user's password. (#14498)
This commit is contained in:
parent
0246327083
commit
c3c2dda861
@ -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',
|
||||
|
@ -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: '管理员',
|
||||
|
@ -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
|
@ -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 }
|
||||
}
|
@ -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
|
||||
|
@ -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'
|
||||
|
||||
@ -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 {
|
||||
@ -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}
|
||||
/>
|
||||
<PasswordModal
|
||||
show={this.passwordModalShow}
|
||||
currentRecord={this.currentRecord}
|
||||
onCancel={this.onPasswordModalCancel}
|
||||
/>
|
||||
</NSpace>
|
||||
)
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ interface IRecord {
|
||||
state: 0 | 1
|
||||
createTime: string
|
||||
updateTime: string
|
||||
userPassword?: string
|
||||
confirmPassword?: string
|
||||
}
|
||||
|
||||
interface IResourceOption {
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user