Parcourir la source

Add email templates and fix Edit User modal

- Fixed Edit User modal to populate email field
- Added Reset Password button to Edit User modal (advanced auth only)
- Added "Welcome Email" template for user creation
- Added "Password Reset" email template
- Removed scrollbar from Settings menu tabs

Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
copilot-swe-agent[bot] il y a 3 mois
Parent
commit
df75cc8e63

+ 3 - 0
backend/app/api/routes/notification_templates.py

@@ -43,6 +43,9 @@ EVENT_NAMES = {
     "queue_job_skipped": "Queue Job Skipped",
     "queue_job_failed": "Queue Job Failed",
     "queue_completed": "Queue Completed",
+    # User management
+    "user_created": "Welcome Email",
+    "password_reset": "Password Reset",
 }
 
 

+ 12 - 0
backend/app/models/notification_template.py

@@ -146,4 +146,16 @@ DEFAULT_TEMPLATES = [
         "title_template": "Queue Complete",
         "body_template": "All {completed_count} queued jobs have finished",
     },
+    {
+        "event_type": "user_created",
+        "name": "Welcome Email",
+        "title_template": "Welcome to {app_name}",
+        "body_template": "Welcome {username}!\n\nYour account has been created.\nUsername: {username}\nPassword: {password}\n\nLogin at: {login_url}",
+    },
+    {
+        "event_type": "password_reset",
+        "name": "Password Reset",
+        "title_template": "{app_name} - Password Reset",
+        "body_template": "Hello {username},\n\nYour password has been reset.\nNew Password: {password}\n\nLogin at: {login_url}",
+    },
 ]

+ 18 - 0
backend/app/schemas/notification_template.py

@@ -53,6 +53,9 @@ EVENT_VARIABLES: dict[str, list[str]] = {
     "queue_job_skipped": ["printer", "job_name", "reason", "timestamp", "app_name"],
     "queue_job_failed": ["printer", "job_name", "reason", "timestamp", "app_name"],
     "queue_completed": ["completed_count", "timestamp", "app_name"],
+    # User management notifications
+    "user_created": ["username", "password", "login_url", "app_name", "timestamp"],
+    "password_reset": ["username", "password", "login_url", "app_name", "timestamp"],
 }
 
 # Sample data for previewing templates
@@ -191,6 +194,21 @@ SAMPLE_DATA: dict[str, dict[str, str]] = {
         "timestamp": "2024-01-15 18:30",
         "app_name": "Bambuddy",
     },
+    # User management notifications
+    "user_created": {
+        "username": "john_doe",
+        "password": "TempPass123!",
+        "login_url": "https://bambuddy.example.com/login",
+        "app_name": "Bambuddy",
+        "timestamp": "2024-01-15 14:30",
+    },
+    "password_reset": {
+        "username": "john_doe",
+        "password": "NewPass456!",
+        "login_url": "https://bambuddy.example.com/login",
+        "app_name": "Bambuddy",
+        "timestamp": "2024-01-15 14:30",
+    },
 }
 
 

+ 32 - 2
frontend/src/pages/SettingsPage.tsx

@@ -380,6 +380,16 @@ export function SettingsPage() {
     },
   });
 
+  const resetPasswordMutation = useMutation({
+    mutationFn: (userId: number) => api.resetUserPassword({ user_id: userId }),
+    onSuccess: (response) => {
+      showToast(response.message, 'success');
+    },
+    onError: (error: Error) => {
+      showToast(error.message, 'error');
+    },
+  });
+
   // Function to initiate user deletion with item count check
   const handleDeleteUserClick = async (userId: number) => {
     setDeleteUserId(userId);
@@ -501,6 +511,7 @@ export function SettingsPage() {
     setUserFormData({
       username: userToEdit.username,
       password: '',
+      email: userToEdit.email || '',
       confirmPassword: '',
       role: userToEdit.role,
       group_ids: userToEdit.groups?.map(g => g.id) || [],
@@ -946,7 +957,7 @@ export function SettingsPage() {
       </div>
 
       {/* Tab Navigation */}
-      <div className="flex flex-wrap gap-1 mb-6 border-b border-bambu-dark-tertiary overflow-y-auto max-h-32">
+      <div className="flex flex-wrap gap-1 mb-6 border-b border-bambu-dark-tertiary">
         <button
           onClick={() => handleTabChange('general')}
           className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
@@ -3952,10 +3963,29 @@ export function SettingsPage() {
 
                 {/* Info box about auto-generated password when Advanced Auth is enabled */}
                 {advancedAuthStatus?.advanced_auth_enabled && (
-                  <div className="bg-bambu-dark-secondary/50 border border-bambu-green/20 rounded-lg p-3">
+                  <div className="bg-bambu-dark-secondary/50 border border-bambu-green/20 rounded-lg p-3 space-y-3">
                     <p className="text-sm text-bambu-gray">
                       {t('users.form.passwordManagedByAdvancedAuth') || 'Password is managed by Advanced Authentication. Use "Reset Password" to send a new password to the user via email.'}
                     </p>
+                    <Button
+                      variant="secondary"
+                      size="sm"
+                      onClick={() => editingUserId && resetPasswordMutation.mutate(editingUserId)}
+                      disabled={resetPasswordMutation.isPending || !userFormData.email}
+                      className="w-full"
+                    >
+                      {resetPasswordMutation.isPending ? (
+                        <>
+                          <Loader2 className="w-4 h-4 animate-spin" />
+                          {t('users.form.resettingPassword') || 'Resetting Password...'}
+                        </>
+                      ) : (
+                        <>
+                          <RotateCcw className="w-4 h-4" />
+                          {t('users.form.resetPassword') || 'Reset Password'}
+                        </>
+                      )}
+                    </Button>
                   </div>
                 )}