You've already forked nginx-proxy-manager
							
							
				mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-30 18:05:34 +03:00 
			
		
		
		
	Set password for users
This commit is contained in:
		| @@ -58,6 +58,9 @@ | ||||
|   "offline": "Offline", | ||||
|   "online": "Online", | ||||
|   "password": "Password", | ||||
|   "password.generate": "Generate random password", | ||||
|   "password.hide": "Hide Password", | ||||
|   "password.show": "Show Password", | ||||
|   "permissions.hidden": "Hidden", | ||||
|   "permissions.manage": "Manage", | ||||
|   "permissions.view": "View Only", | ||||
| @@ -101,6 +104,7 @@ | ||||
|   "user.new": "New User", | ||||
|   "user.new-password": "New Password", | ||||
|   "user.nickname": "Nickname", | ||||
|   "user.set-password": "Set Password", | ||||
|   "user.set-permissions": "Set Permissions for {name}", | ||||
|   "user.switch-dark": "Switch to Dark mode", | ||||
|   "user.switch-light": "Switch to Light mode", | ||||
|   | ||||
| @@ -176,6 +176,15 @@ | ||||
| 	"password": { | ||||
| 		"defaultMessage": "Password" | ||||
| 	}, | ||||
| 	"password.generate": { | ||||
| 		"defaultMessage": "Generate random password" | ||||
| 	}, | ||||
| 	"password.hide": { | ||||
| 		"defaultMessage": "Hide Password" | ||||
| 	}, | ||||
| 	"password.show": { | ||||
| 		"defaultMessage": "Show Password" | ||||
| 	}, | ||||
| 	"permissions.hidden": { | ||||
| 		"defaultMessage": "Hidden" | ||||
| 	}, | ||||
| @@ -305,6 +314,9 @@ | ||||
| 	"user.nickname": { | ||||
| 		"defaultMessage": "Nickname" | ||||
| 	}, | ||||
| 	"user.set-password": { | ||||
| 		"defaultMessage": "Set Password" | ||||
| 	}, | ||||
| 	"user.set-permissions": { | ||||
| 		"defaultMessage": "Set Permissions for {name}" | ||||
| 	}, | ||||
|   | ||||
							
								
								
									
										132
									
								
								frontend/src/modals/SetPasswordModal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								frontend/src/modals/SetPasswordModal.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| import { Field, Form, Formik } from "formik"; | ||||
| import { generate } from "generate-password-browser"; | ||||
| import { useState } from "react"; | ||||
| import { Alert } from "react-bootstrap"; | ||||
| import Modal from "react-bootstrap/Modal"; | ||||
| import { updateAuth } from "src/api/backend"; | ||||
| import { Button } from "src/components"; | ||||
| import { intl } from "src/locale"; | ||||
| import { validateString } from "src/modules/Validations"; | ||||
|  | ||||
| interface Props { | ||||
| 	userId: number; | ||||
| 	onClose: () => void; | ||||
| } | ||||
| export function SetPasswordModal({ userId, onClose }: Props) { | ||||
| 	const [error, setError] = useState<string | null>(null); | ||||
| 	const [showPassword, setShowPassword] = useState(false); | ||||
|  | ||||
| 	const onSubmit = async (values: any, { setSubmitting }: any) => { | ||||
| 		setError(null); | ||||
| 		try { | ||||
| 			await updateAuth(userId, values.new); | ||||
| 			onClose(); | ||||
| 		} catch (err: any) { | ||||
| 			setError(intl.formatMessage({ id: err.message })); | ||||
| 		} | ||||
| 		setSubmitting(false); | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<Modal show onHide={onClose} animation={false}> | ||||
| 			<Formik | ||||
| 				initialValues={ | ||||
| 					{ | ||||
| 						new: "", | ||||
| 					} as any | ||||
| 				} | ||||
| 				onSubmit={onSubmit} | ||||
| 			> | ||||
| 				{({ isSubmitting }) => ( | ||||
| 					<Form> | ||||
| 						<Modal.Header closeButton> | ||||
| 							<Modal.Title>{intl.formatMessage({ id: "user.set-password" })}</Modal.Title> | ||||
| 						</Modal.Header> | ||||
| 						<Modal.Body> | ||||
| 							<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible> | ||||
| 								{error} | ||||
| 							</Alert> | ||||
| 							<div className="mb-3"> | ||||
| 								<Field name="new" validate={validateString(8, 100)}> | ||||
| 									{({ field, form }: any) => ( | ||||
| 										<> | ||||
| 											<p className="text-end"> | ||||
| 												<small> | ||||
| 													<a | ||||
| 														href="#" | ||||
| 														onClick={(e) => { | ||||
| 															e.preventDefault(); | ||||
| 															form.setFieldValue( | ||||
| 																field.name, | ||||
| 																generate({ | ||||
| 																	length: 12, | ||||
| 																	numbers: true, | ||||
| 																}), | ||||
| 															); | ||||
| 															setShowPassword(true); | ||||
| 														}} | ||||
| 													> | ||||
| 														{intl.formatMessage({ | ||||
| 															id: "password.generate", | ||||
| 														})} | ||||
| 													</a>{" "} | ||||
| 													—{" "} | ||||
| 													<a | ||||
| 														href="#" | ||||
| 														className="text-xs" | ||||
| 														onClick={(e) => { | ||||
| 															e.preventDefault(); | ||||
| 															setShowPassword(!showPassword); | ||||
| 														}} | ||||
| 													> | ||||
| 														{intl.formatMessage({ | ||||
| 															id: showPassword ? "password.hide" : "password.show", | ||||
| 														})} | ||||
| 													</a> | ||||
| 												</small> | ||||
| 											</p> | ||||
| 											<div className="form-floating mb-3"> | ||||
| 												<input | ||||
| 													id="new" | ||||
| 													type={showPassword ? "text" : "password"} | ||||
| 													required | ||||
| 													className={`form-control ${form.errors.new && form.touched.new ? "is-invalid" : ""}`} | ||||
| 													placeholder={intl.formatMessage({ id: "user.new-password" })} | ||||
| 													{...field} | ||||
| 												/> | ||||
| 												<label htmlFor="new"> | ||||
| 													{intl.formatMessage({ id: "user.new-password" })} | ||||
| 												</label> | ||||
|  | ||||
| 												{form.errors.new ? ( | ||||
| 													<div className="invalid-feedback"> | ||||
| 														{form.errors.new && form.touched.new ? form.errors.new : null} | ||||
| 													</div> | ||||
| 												) : null} | ||||
| 											</div> | ||||
| 										</> | ||||
| 									)} | ||||
| 								</Field> | ||||
| 							</div> | ||||
| 						</Modal.Body> | ||||
| 						<Modal.Footer> | ||||
| 							<Button data-bs-dismiss="modal" onClick={onClose} disabled={isSubmitting}> | ||||
| 								{intl.formatMessage({ id: "cancel" })} | ||||
| 							</Button> | ||||
| 							<Button | ||||
| 								type="submit" | ||||
| 								actionType="primary" | ||||
| 								className="ms-auto" | ||||
| 								data-bs-dismiss="modal" | ||||
| 								isLoading={isSubmitting} | ||||
| 								disabled={isSubmitting} | ||||
| 							> | ||||
| 								{intl.formatMessage({ id: "save" })} | ||||
| 							</Button> | ||||
| 						</Modal.Footer> | ||||
| 					</Form> | ||||
| 				)} | ||||
| 			</Formik> | ||||
| 		</Modal> | ||||
| 	); | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| export * from "./ChangePasswordModal"; | ||||
| export * from "./DeleteConfirmModal"; | ||||
| export * from "./PermissionsModal"; | ||||
| export * from "./SetPasswordModal"; | ||||
| export * from "./UserModal"; | ||||
|   | ||||
| @@ -134,6 +134,8 @@ More for api, then implement here: | ||||
| - Properly implement refresh tokens | ||||
| - Add error message_18n for all backend errors | ||||
| - minor: certificates expand with hosts needs to omit 'is_deleted' | ||||
| - properly wrap all logger.debug called in isDebug check | ||||
|  | ||||
| `}</code> | ||||
| 			</pre> | ||||
| 		</div> | ||||
|   | ||||
| @@ -125,7 +125,7 @@ export default function Table({ | ||||
| 											}} | ||||
| 										> | ||||
| 											<IconLock size={16} /> | ||||
| 											{intl.formatMessage({ id: "user.change-password" })} | ||||
| 											{intl.formatMessage({ id: "user.set-password" })} | ||||
| 										</a> | ||||
| 										<div className="dropdown-divider" /> | ||||
| 										<a | ||||
|   | ||||
| @@ -5,13 +5,14 @@ import { deleteUser } from "src/api/backend"; | ||||
| import { Button, LoadingPage } from "src/components"; | ||||
| import { useUser, useUsers } from "src/hooks"; | ||||
| import { intl } from "src/locale"; | ||||
| import { DeleteConfirmModal, PermissionsModal, UserModal } from "src/modals"; | ||||
| import { DeleteConfirmModal, PermissionsModal, SetPasswordModal, UserModal } from "src/modals"; | ||||
| import { showSuccess } from "src/notifications"; | ||||
| import Table from "./Table"; | ||||
|  | ||||
| export default function TableWrapper() { | ||||
| 	const [editUserId, setEditUserId] = useState(0 as number | "new"); | ||||
| 	const [editUserPermissionsId, setEditUserPermissionsId] = useState(0); | ||||
| 	const [editUserPasswordId, setEditUserPasswordId] = useState(0); | ||||
| 	const [deleteUserId, setDeleteUserId] = useState(0); | ||||
| 	const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]); | ||||
| 	const { data: currentUser } = useUser("me"); | ||||
| @@ -64,6 +65,7 @@ export default function TableWrapper() { | ||||
| 					currentUserId={currentUser?.id} | ||||
| 					onEditUser={(id: number) => setEditUserId(id)} | ||||
| 					onEditPermissions={(id: number) => setEditUserPermissionsId(id)} | ||||
| 					onSetPassword={(id: number) => setEditUserPasswordId(id)} | ||||
| 					onDeleteUser={(id: number) => setDeleteUserId(id)} | ||||
| 					onNewUser={() => setEditUserId("new")} | ||||
| 				/> | ||||
| @@ -81,6 +83,9 @@ export default function TableWrapper() { | ||||
| 						{intl.formatMessage({ id: "user.delete.content" })} | ||||
| 					</DeleteConfirmModal> | ||||
| 				) : null} | ||||
| 				{editUserPasswordId ? ( | ||||
| 					<SetPasswordModal userId={editUserPasswordId} onClose={() => setEditUserPasswordId(0)} /> | ||||
| 				) : null} | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user