This is an automated email from the ASF dual-hosted git repository.

kerwin pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new c3c2dda861 [Improvement-14387][UI] Support to reset user's password. 
(#14498)
c3c2dda861 is described below

commit c3c2dda861e2472b8f282ee889e5d609d0d6b36c
Author: calvin <[email protected]>
AuthorDate: Tue Jul 11 12:01:49 2023 +0800

    [Improvement-14387][UI] Support to reset user's password. (#14498)
---
 dolphinscheduler-ui/src/locales/en_US/security.ts  |   2 +
 dolphinscheduler-ui/src/locales/zh_CN/security.ts  |   2 +
 .../{user-detail-modal.tsx => password-modal.tsx}  | 108 +++++------------
 .../user-manage/components/use-password.ts         | 134 +++++++++++++++++++++
 .../user-manage/components/user-detail-modal.tsx   |   2 +-
 .../src/views/security/user-manage/index.tsx       |  11 ++
 .../src/views/security/user-manage/types.ts        |   2 +
 .../src/views/security/user-manage/use-columns.ts  |  40 +++++-
 .../src/views/security/user-manage/use-table.ts    |  14 +--
 9 files changed, 226 insertions(+), 89 deletions(-)

diff --git a/dolphinscheduler-ui/src/locales/en_US/security.ts 
b/dolphinscheduler-ui/src/locales/en_US/security.ts
index c6dcb02892..44536982b2 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 cc86de5504..ffe4f7dff8 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/user-detail-modal.tsx
 
b/dolphinscheduler-ui/src/views/security/user-manage/components/password-modal.tsx
similarity index 52%
copy from 
dolphinscheduler-ui/src/views/security/user-manage/components/user-detail-modal.tsx
copy to 
dolphinscheduler-ui/src/views/security/user-manage/components/password-modal.tsx
index c66a414c95..146db6c7d6 100644
--- 
a/dolphinscheduler-ui/src/views/security/user-manage/components/user-detail-modal.tsx
+++ 
b/dolphinscheduler-ui/src/views/security/user-manage/components/password-modal.tsx
@@ -23,16 +23,8 @@ import {
   watch
 } from 'vue'
 import { useI18n } from 'vue-i18n'
-import {
-  NInput,
-  NForm,
-  NFormItem,
-  NSelect,
-  NRadio,
-  NRadioGroup,
-  NSpace
-} from 'naive-ui'
-import { useUserDetail } from './use-user-detail'
+import { NInput, NForm, NFormItem } from 'naive-ui'
+import { usePassword } from './use-password'
 import Modal from '@/components/modal'
 import type { IRecord } from '../types'
 
@@ -47,21 +39,24 @@ const props = {
   }
 }
 
-export const UserModal = defineComponent({
-  name: 'user-modal',
+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 } =
-      useUserDetail()
+      usePassword()
+
     const onCancel = () => {
       onReset()
       ctx.emit('cancel')
     }
     const onConfirm = async () => {
-      const result = await onSave(props.currentRecord?.id)
-      if (!result) return
+      if (props.currentRecord?.id) {
+        const result = await onSave(props.currentRecord)
+        if (!result) return
+      }
       onCancel()
       ctx.emit('update')
     }
@@ -87,17 +82,13 @@ export const UserModal = defineComponent({
       trim
     }
   },
-  render(props: { currentRecord: IRecord }) {
+  render() {
     const { t } = this
-    const { currentRecord } = props
+
     return (
       <Modal
         show={this.show}
-        title={`${t(
-          currentRecord?.id
-            ? 'security.user.edit_user'
-            : 'security.user.create_user'
-        )}`}
+        title={t('security.user.reset_password')}
         onCancel={this.onCancel}
         confirmLoading={this.loading}
         onConfirm={this.onConfirm}
@@ -110,7 +101,7 @@ export const UserModal = defineComponent({
           rules={this.formRules}
           labelPlacement='left'
           labelAlign='left'
-          labelWidth={80}
+          labelWidth={150}
         >
           <NFormItem label={t('security.user.username')} path='userName'>
             <NInput
@@ -119,74 +110,37 @@ export const UserModal = defineComponent({
               v-model:value={this.formData.userName}
               minlength={3}
               maxlength={39}
+              disabled={true}
               placeholder={t('security.user.username_tips')}
             />
           </NFormItem>
-          {!this.currentRecord?.id && (
-            <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>
-          )}
-          {this.IS_ADMIN && (
-            <NFormItem label={t('security.user.tenant_code')} path='tenantId'>
-              <NSelect
-                class='select-tenant'
-                options={this.tenants}
-                v-model:value={this.formData.tenantId}
-              />
-            </NFormItem>
-          )}
-          {this.IS_ADMIN && (
-            <NFormItem label={t('security.user.queue')} path='queue'>
-              <NSelect
-                class='select-queue'
-                options={this.queues}
-                v-model:value={this.formData.queue}
-                placeholder={t('security.user.queue_tips')}
-              />
-            </NFormItem>
-          )}
-          <NFormItem label={t('security.user.email')} path='email'>
+          <NFormItem
+            label={t('security.user.user_password')}
+            path='userPassword'
+          >
             <NInput
               allowInput={this.trim}
-              class='input-email'
-              v-model:value={this.formData.email}
-              placeholder={t('security.user.email_empty_tips')}
+              class='input-password'
+              type='password'
+              v-model:value={this.formData.userPassword}
+              placeholder={t('security.user.user_password_tips')}
             />
           </NFormItem>
-          <NFormItem label={t('security.user.phone')} path='phone'>
+          <NFormItem
+            label={t('password.confirm_password')}
+            path='confirmPassword'
+          >
             <NInput
               allowInput={this.trim}
-              class='input-phone'
-              v-model:value={this.formData.phone}
-              placeholder={t('security.user.phone_empty_tips')}
+              type='password'
+              v-model:value={this.formData.confirmPassword}
+              placeholder={t('password.confirm_password_tips')}
             />
           </NFormItem>
-          <NFormItem label={t('security.user.state')} path='state'>
-            <NRadioGroup v-model:value={this.formData.state}>
-              <NSpace>
-                <NRadio value={1} class='radio-state-enable'>
-                  {this.t('security.user.enable')}
-                </NRadio>
-                <NRadio value={0} class='radio-state-disable'>
-                  {this.t('security.user.disable')}
-                </NRadio>
-              </NSpace>
-            </NRadioGroup>
-          </NFormItem>
         </NForm>
       </Modal>
     )
   }
 })
 
-export default UserModal
+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 0000000000..50345a6243
--- /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<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 }
+}
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 c66a414c95..751bd3c009 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}
         >
           <NFormItem label={t('security.user.username')} path='userName'>
             <NInput
diff --git a/dolphinscheduler-ui/src/views/security/user-manage/index.tsx 
b/dolphinscheduler-ui/src/views/security/user-manage/index.tsx
index 92bb7d29f5..980cc8884e 100644
--- a/dolphinscheduler-ui/src/views/security/user-manage/index.tsx
+++ b/dolphinscheduler-ui/src/views/security/user-manage/index.tsx
@@ -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>
     )
   }
diff --git a/dolphinscheduler-ui/src/views/security/user-manage/types.ts 
b/dolphinscheduler-ui/src/views/security/user-manage/types.ts
index a91e07101a..bf51fe3947 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 5e77865f73..812e104e9d 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 410c68618e..2059c7a958 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()

Reply via email to