浏览代码

feat: new recovery codes

Hintay 5 月之前
父节点
当前提交
0d1f56a43e

+ 11 - 6
api/user/2fa.go

@@ -2,6 +2,10 @@ package user
 
 
 import (
 import (
 	"encoding/base64"
 	"encoding/base64"
+	"net/http"
+	"strings"
+	"time"
+
 	"github.com/0xJacky/Nginx-UI/api"
 	"github.com/0xJacky/Nginx-UI/api"
 	"github.com/0xJacky/Nginx-UI/internal/cache"
 	"github.com/0xJacky/Nginx-UI/internal/cache"
 	"github.com/0xJacky/Nginx-UI/internal/passkey"
 	"github.com/0xJacky/Nginx-UI/internal/passkey"
@@ -12,15 +16,14 @@ import (
 	"github.com/go-webauthn/webauthn/webauthn"
 	"github.com/go-webauthn/webauthn/webauthn"
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/uozi-tech/cosy"
 	"github.com/uozi-tech/cosy"
-	"net/http"
-	"strings"
-	"time"
 )
 )
 
 
 type Status2FA struct {
 type Status2FA struct {
-	Enabled       bool `json:"enabled"`
-	OTPStatus     bool `json:"otp_status"`
-	PasskeyStatus bool `json:"passkey_status"`
+	Enabled                bool `json:"enabled"`
+	OTPStatus              bool `json:"otp_status"`
+	PasskeyStatus          bool `json:"passkey_status"`
+	RecoveryCodesGenerated bool `json:"recovery_codes_generated"`
+	RecoveryCodesViewed    bool `json:"recovery_codes_viewed"`
 }
 }
 
 
 func get2FAStatus(c *gin.Context) (status Status2FA) {
 func get2FAStatus(c *gin.Context) (status Status2FA) {
@@ -31,6 +34,8 @@ func get2FAStatus(c *gin.Context) (status Status2FA) {
 		status.OTPStatus = userPtr.EnabledOTP()
 		status.OTPStatus = userPtr.EnabledOTP()
 		status.PasskeyStatus = userPtr.EnabledPasskey() && passkey.Enabled()
 		status.PasskeyStatus = userPtr.EnabledPasskey() && passkey.Enabled()
 		status.Enabled = status.OTPStatus || status.PasskeyStatus
 		status.Enabled = status.OTPStatus || status.PasskeyStatus
+		status.RecoveryCodesGenerated = userPtr.RecoveryCodeGenerated()
+		status.RecoveryCodesViewed = userPtr.RecoveryCodeViewed()
 	}
 	}
 	return
 	return
 }
 }

+ 23 - 30
api/user/otp.go

@@ -1,15 +1,15 @@
 package user
 package user
 
 
 import (
 import (
-	"bytes"
-	"crypto/sha1"
-	"encoding/hex"
+	"encoding/json"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
+	"time"
 
 
 	"github.com/0xJacky/Nginx-UI/api"
 	"github.com/0xJacky/Nginx-UI/api"
 	"github.com/0xJacky/Nginx-UI/internal/crypto"
 	"github.com/0xJacky/Nginx-UI/internal/crypto"
+	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/0xJacky/Nginx-UI/query"
 	"github.com/0xJacky/Nginx-UI/query"
 	"github.com/0xJacky/Nginx-UI/settings"
 	"github.com/0xJacky/Nginx-UI/settings"
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin"
@@ -59,22 +59,22 @@ func EnrollTOTP(c *gin.Context) {
 		return
 		return
 	}
 	}
 
 
-	var json struct {
+	var twoFA struct {
 		Secret   string `json:"secret" binding:"required"`
 		Secret   string `json:"secret" binding:"required"`
 		Passcode string `json:"passcode" binding:"required"`
 		Passcode string `json:"passcode" binding:"required"`
 	}
 	}
-	if !cosy.BindAndValid(c, &json) {
+	if !cosy.BindAndValid(c, &twoFA) {
 		return
 		return
 	}
 	}
 
 
-	if ok := totp.Validate(json.Passcode, json.Secret); !ok {
+	if ok := totp.Validate(twoFA.Passcode, twoFA.Secret); !ok {
 		c.JSON(http.StatusNotAcceptable, gin.H{
 		c.JSON(http.StatusNotAcceptable, gin.H{
 			"message": "Invalid passcode",
 			"message": "Invalid passcode",
 		})
 		})
 		return
 		return
 	}
 	}
 
 
-	ciphertext, err := crypto.AesEncrypt([]byte(json.Secret))
+	ciphertext, err := crypto.AesEncrypt([]byte(twoFA.Secret))
 	if err != nil {
 	if err != nil {
 		api.ErrHandler(c, err)
 		api.ErrHandler(c, err)
 		return
 		return
@@ -87,37 +87,30 @@ func EnrollTOTP(c *gin.Context) {
 		return
 		return
 	}
 	}
 
 
-	recoveryCode := sha1.Sum(ciphertext)
-
-	c.JSON(http.StatusOK, gin.H{
-		"message":       "ok",
-		"recovery_code": hex.EncodeToString(recoveryCode[:]),
-	})
-}
-
-func ResetOTP(c *gin.Context) {
-	var json struct {
-		RecoveryCode string `json:"recovery_code"`
-	}
-	if !cosy.BindAndValid(c, &json) {
-		return
-	}
-	recoverCode, err := hex.DecodeString(json.RecoveryCode)
+	t := time.Now()
+	recoveryCodes := model.RecoveryCodes{Codes: generateRecoveryCodes(16), LastViewed: &t}
+	codesJson, err := json.Marshal(&recoveryCodes)
 	if err != nil {
 	if err != nil {
 		api.ErrHandler(c, err)
 		api.ErrHandler(c, err)
 		return
 		return
 	}
 	}
-	cUser := api.CurrentUser(c)
-	k := sha1.Sum(cUser.OTPSecret)
-	if !bytes.Equal(k[:], recoverCode) {
-		c.JSON(http.StatusBadRequest, gin.H{
-			"message": "Invalid recovery code",
-		})
+
+	_, err = u.Where(u.ID.Eq(cUser.ID)).Update(u.RecoveryCodes, codesJson)
+	if err != nil {
+		api.ErrHandler(c, err)
 		return
 		return
 	}
 	}
 
 
+	c.JSON(http.StatusOK, RecoveryCodesResponse{
+		Message:       "ok",
+		RecoveryCodes: recoveryCodes,
+	})
+}
+
+func ResetOTP(c *gin.Context) {
+	cUser := api.CurrentUser(c)
 	u := query.User
 	u := query.User
-	_, err = u.Where(u.ID.Eq(cUser.ID)).UpdateSimple(u.OTPSecret.Null())
+	_, err := u.Where(u.ID.Eq(cUser.ID)).UpdateSimple(u.OTPSecret.Null(), u.RecoveryCodes.Null())
 	if err != nil {
 	if err != nil {
 		api.ErrHandler(c, err)
 		api.ErrHandler(c, err)
 		return
 		return

+ 81 - 0
api/user/recovery.go

@@ -0,0 +1,81 @@
+package user
+
+import (
+	"encoding/json"
+	"fmt"
+	"math/rand"
+	"net/http"
+	"time"
+
+	"github.com/0xJacky/Nginx-UI/api"
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/query"
+	"github.com/gin-gonic/gin"
+)
+
+type RecoveryCodesResponse struct {
+	Message string `json:"message"`
+	model.RecoveryCodes
+}
+
+func generateRecoveryCode() string {
+	// generate recovery code, 10 hex numbers
+	return fmt.Sprintf("%010x", rand.Intn(0x10000000000))
+}
+
+func generateRecoveryCodes(count int) []model.RecoveryCode {
+	recoveryCodes := make([]model.RecoveryCode, count)
+	for i := 0; i < count; i++ {
+		recoveryCodes[i].Code = generateRecoveryCode()
+	}
+	return recoveryCodes
+}
+
+func ViewRecoveryCodes(c *gin.Context) {
+	user := api.CurrentUser(c)
+
+	u := query.User
+	user, err := u.Where(u.ID.Eq(user.ID)).First()
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+
+	// update last viewed time
+	t := time.Now()
+	user.RecoveryCodes.LastViewed = &t
+	_, err = u.Where(u.ID.Eq(user.ID)).Updates(user)
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, RecoveryCodesResponse{
+		Message:       "ok",
+		RecoveryCodes: user.RecoveryCodes,
+	})
+}
+
+func GenerateRecoveryCodes(c *gin.Context) {
+	user := api.CurrentUser(c)
+
+	t := time.Now()
+	recoveryCodes := model.RecoveryCodes{Codes: generateRecoveryCodes(16), LastViewed: &t}
+	codesJson, err := json.Marshal(&recoveryCodes)
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+
+	u := query.User
+	_, err = u.Where(u.ID.Eq(user.ID)).Update(u.RecoveryCodes, codesJson)
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, RecoveryCodesResponse{
+		Message:       "ok",
+		RecoveryCodes: recoveryCodes,
+	})
+}

+ 9 - 1
api/user/router.go

@@ -1,6 +1,7 @@
 package user
 package user
 
 
 import (
 import (
+	"github.com/0xJacky/Nginx-UI/internal/middleware"
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin"
 )
 )
 
 
@@ -26,7 +27,6 @@ func InitUserRouter(r *gin.RouterGroup) {
 
 
 	r.GET("/otp_secret", GenerateTOTP)
 	r.GET("/otp_secret", GenerateTOTP)
 	r.POST("/otp_enroll", EnrollTOTP)
 	r.POST("/otp_enroll", EnrollTOTP)
-	r.POST("/otp_reset", ResetOTP)
 
 
 	r.GET("/begin_passkey_register", BeginPasskeyRegistration)
 	r.GET("/begin_passkey_register", BeginPasskeyRegistration)
 	r.POST("/finish_passkey_register", FinishPasskeyRegistration)
 	r.POST("/finish_passkey_register", FinishPasskeyRegistration)
@@ -34,4 +34,12 @@ func InitUserRouter(r *gin.RouterGroup) {
 	r.GET("/passkeys", GetPasskeyList)
 	r.GET("/passkeys", GetPasskeyList)
 	r.POST("/passkeys/:id", UpdatePasskey)
 	r.POST("/passkeys/:id", UpdatePasskey)
 	r.DELETE("/passkeys/:id", DeletePasskey)
 	r.DELETE("/passkeys/:id", DeletePasskey)
+
+	o := r.Group("", middleware.RequireSecureSession())
+	{
+		o.GET("/otp_reset", ResetOTP)
+
+		o.GET("/recovery_codes", ViewRecoveryCodes)
+		o.GET("/recovery_codes_generate", GenerateRecoveryCodes)
+	}
 }
 }

+ 4 - 2
app/src/api/2fa.ts

@@ -1,14 +1,16 @@
 import type { AuthenticationResponseJSON } from '@simplewebauthn/browser'
 import type { AuthenticationResponseJSON } from '@simplewebauthn/browser'
 import http from '@/lib/http'
 import http from '@/lib/http'
 
 
-export interface TwoFAStatusResponse {
+export interface TwoFAStatus {
   enabled: boolean
   enabled: boolean
   otp_status: boolean
   otp_status: boolean
   passkey_status: boolean
   passkey_status: boolean
+  recovery_codes_generated: boolean
+  recovery_codes_viewed: boolean
 }
 }
 
 
 const twoFA = {
 const twoFA = {
-  status(): Promise<TwoFAStatusResponse> {
+  status(): Promise<TwoFAStatus> {
     return http.get('/2fa_status')
     return http.get('/2fa_status')
   },
   },
   start_secure_session_by_otp(passcode: string, recovery_code: string): Promise<{ session_id: string }> {
   start_secure_session_by_otp(passcode: string, recovery_code: string): Promise<{ session_id: string }> {

+ 4 - 3
app/src/api/otp.ts

@@ -1,3 +1,4 @@
+import type { RecoveryCodesResponse } from '@/api/recovery'
 import http from '@/lib/http'
 import http from '@/lib/http'
 
 
 export interface OTPGenerateSecretResponse {
 export interface OTPGenerateSecretResponse {
@@ -9,11 +10,11 @@ const otp = {
   generate_secret(): Promise<OTPGenerateSecretResponse> {
   generate_secret(): Promise<OTPGenerateSecretResponse> {
     return http.get('/otp_secret')
     return http.get('/otp_secret')
   },
   },
-  enroll_otp(secret: string, passcode: string): Promise<{ recovery_code: string }> {
+  enroll_otp(secret: string, passcode: string): Promise<RecoveryCodesResponse> {
     return http.post('/otp_enroll', { secret, passcode })
     return http.post('/otp_enroll', { secret, passcode })
   },
   },
-  reset(recovery_code: string) {
-    return http.post('/otp_reset', { recovery_code })
+  reset() {
+    return http.get('/otp_reset')
   },
   },
 }
 }
 
 

+ 27 - 0
app/src/api/recovery.ts

@@ -0,0 +1,27 @@
+import http from '@/lib/http'
+
+export interface RecoveryCode {
+  code: string
+  used_time?: number
+}
+
+export interface RecoveryCodes {
+  codes: RecoveryCode[]
+  last_viewed?: number
+  last_downloaded?: number
+}
+
+export interface RecoveryCodesResponse extends RecoveryCodes {
+  message: string
+}
+
+const recovery = {
+  generate(): Promise<RecoveryCodesResponse> {
+    return http.get('/recovery_codes_generate')
+  },
+  view(): Promise<RecoveryCodesResponse> {
+    return http.get('/recovery_codes')
+  },
+}
+
+export default recovery

+ 2 - 2
app/src/components/TwoFA/Authorization.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import type { TwoFAStatusResponse } from '@/api/2fa'
+import type { TwoFAStatus } from '@/api/2fa'
 import twoFA from '@/api/2fa'
 import twoFA from '@/api/2fa'
 import OTPInput from '@/components/OTPInput/OTPInput.vue'
 import OTPInput from '@/components/OTPInput/OTPInput.vue'
 import { useUserStore } from '@/pinia'
 import { useUserStore } from '@/pinia'
@@ -7,7 +7,7 @@ import { KeyOutlined } from '@ant-design/icons-vue'
 import { startAuthentication } from '@simplewebauthn/browser'
 import { startAuthentication } from '@simplewebauthn/browser'
 
 
 defineProps<{
 defineProps<{
-  twoFAStatus: TwoFAStatusResponse
+  twoFAStatus: TwoFAStatus
 }>()
 }>()
 
 
 const emit = defineEmits(['submitOTP', 'submitSecureSessionID'])
 const emit = defineEmits(['submitOTP', 'submitSecureSessionID'])

+ 141 - 54
app/src/language/ar/app.po

@@ -17,7 +17,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr "المصادقة الثنائية"
 msgstr "المصادقة الثنائية"
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr "إعدادات المصادقة الثنائية"
 msgstr "إعدادات المصادقة الثنائية"
 
 
@@ -40,7 +40,7 @@ msgstr "مستخدم ACME"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -140,7 +140,7 @@ msgstr "تم التكرار بنجاح"
 msgid "Arch"
 msgid "Arch"
 msgstr "بنية"
 msgstr "بنية"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "هل أنت متأكد من حذف عنوان IP المحظور هذا على الفور؟"
 msgstr "هل أنت متأكد من حذف عنوان IP المحظور هذا على الفور؟"
 
 
@@ -148,6 +148,16 @@ msgstr "هل أنت متأكد من حذف عنوان IP المحظور هذا 
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "هل أنت متأكد من حذف مفتاح المرور هذا على الفور؟"
 msgstr "هل أنت متأكد من حذف مفتاح المرور هذا على الفور؟"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "هل أنت متأكد أنك تريد استرداد هذا العنصر؟"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "هل أنت متأكد أنك تريد الحذف؟"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -204,7 +214,7 @@ msgstr "المساعد"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr "محاولات"
 msgstr "محاولات"
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr "محاولات"
 msgstr "محاولات"
 
 
@@ -216,7 +226,7 @@ msgstr "مصادقة"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr "المصادقة باستخدام مفتاح المرور"
 msgstr "المصادقة باستخدام مفتاح المرور"
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr "إعدادات المصادقة"
 msgstr "إعدادات المصادقة"
 
 
@@ -253,15 +263,15 @@ msgstr "العودة إلى الصفحة الرئيسية"
 msgid "Back to list"
 msgid "Back to list"
 msgstr "العودة إلى القائمة"
 msgstr "العودة إلى القائمة"
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "دقائق حد الحظر"
 msgstr "دقائق حد الحظر"
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr "عناوين IP المحظورة"
 msgstr "عناوين IP المحظورة"
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr "محظور حتى"
 msgstr "محظور حتى"
 
 
@@ -457,7 +467,7 @@ msgstr "مسح"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "تم المسح بنجاح"
 msgstr "تم المسح بنجاح"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -508,6 +518,7 @@ msgstr "محتوى"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr "تم النسخ"
 msgstr "تم النسخ"
 
 
@@ -515,6 +526,11 @@ msgstr "تم النسخ"
 msgid "Copy"
 msgid "Copy"
 msgstr "نسخ"
 msgstr "نسخ"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+#, fuzzy
+msgid "Copy Codes"
+msgstr "رمز الاسترداد"
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "ترقية نواة"
 msgstr "ترقية نواة"
@@ -565,11 +581,11 @@ msgstr "بيان الاعتماد"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "بيانات الاعتماد"
 msgstr "بيانات الاعتماد"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr "TOTP مفعل للحساب الحالي."
 msgstr "TOTP مفعل للحساب الحالي."
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr "TOTP معطل للحساب الحالي."
 msgstr "TOTP معطل للحساب الحالي."
 
 
@@ -906,7 +922,7 @@ msgstr "فشل تفعيل %{conf_name} في %{node_name}"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "تم تفعيل %{conf_name} في %{node_name} بنجاح"
 msgstr "تم تفعيل %{conf_name} في %{node_name} بنجاح"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "تم تفعيل المصادقة الثنائية بنجاح"
 msgstr "تم تفعيل المصادقة الثنائية بنجاح"
 
 
@@ -942,7 +958,7 @@ msgstr "تم التفعيل بنجاح"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "تفعيل TLS"
 msgstr "تفعيل TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "تفعيل TOTP"
 msgstr "تفعيل TOTP"
 
 
@@ -1067,6 +1083,10 @@ msgstr "تصفيه"
 msgid "Finished"
 msgid "Finished"
 msgstr "انتهى"
 msgstr "انتهى"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1102,6 +1122,22 @@ msgstr "شهادة عامة"
 msgid "Generate"
 msgid "Generate"
 msgstr "توليد"
 msgstr "توليد"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate New Recovery Codes"
+msgstr "رمز الاسترداد"
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "رمز الاسترداد"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "تم الاسترداد بنجاح"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "توليد مفتاح خاص لتسجيل الحساب"
 msgstr "توليد مفتاح خاص لتسجيل الحساب"
@@ -1150,7 +1186,7 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "إذا تُرك فارغًا، سيتم استخدام دليل CA الافتراضي."
 msgstr "إذا تُرك فارغًا، سيتم استخدام دليل CA الافتراضي."
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
@@ -1158,14 +1194,6 @@ msgstr ""
 "إذا وصل عدد محاولات تسجيل الدخول الفاشلة من عنوان IP إلى الحد الأقصى "
 "إذا وصل عدد محاولات تسجيل الدخول الفاشلة من عنوان IP إلى الحد الأقصى "
 "للمحاولات في حد دقائق الحظر، سيتم حظر عنوان IP لفترة من الوقت."
 "للمحاولات في حد دقائق الحظر، سيتم حظر عنوان IP لفترة من الوقت."
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-"إذا فقدت هاتفك المحمول، يمكنك استخدام رمز الاسترداد لإعادة تعيين المصادقة "
-"الثنائية."
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr "إذا كان متصفحك يدعم WebAuthn Passkey، ستظهر نافذة حوار."
 msgstr "إذا كان متصفحك يدعم WebAuthn Passkey، ستظهر نافذة حوار."
@@ -1199,12 +1227,11 @@ msgstr "خطأ في ترقية النواة الأولية"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "بدء ترقية النواة"
 msgstr "بدء ترقية النواة"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr "أدخل الرمز من التطبيق:"
 msgstr "أدخل الرمز من التطبيق:"
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr "أدخل رمز الاسترداد:"
 msgstr "أدخل رمز الاسترداد:"
 
 
@@ -1247,7 +1274,7 @@ msgstr "رمز المرور أو رمز الاسترداد غير صالح"
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "رمز 2FA أو الاسترداد غير صالح"
 msgstr "رمز 2FA أو الاسترداد غير صالح"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr "IP"
 msgstr "IP"
 
 
@@ -1271,6 +1298,12 @@ msgstr "المُصدر: %{issuer}"
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "سر JWT"
 msgstr "سر JWT"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1412,7 +1445,7 @@ msgstr "إدارة المستخدمين"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "شهادة مُدارة"
 msgstr "شهادة مُدارة"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr "الحد الأقصى للمحاولات"
 msgstr "الحد الأقصى للمحاولات"
 
 
@@ -1598,7 +1631,7 @@ msgstr "تم إعادة تشغيل Nginx بنجاح"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1717,7 +1750,7 @@ msgstr "أوبن أي آي"
 msgid "Or"
 msgid "Or"
 msgstr "أو"
 msgstr "أو"
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -1960,17 +1993,19 @@ msgid "Recovered Successfully"
 msgstr "تم الاسترداد بنجاح"
 msgstr "تم الاسترداد بنجاح"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr "استرداد"
 msgstr "استرداد"
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
+#: src/views/preference/components/RecoveryCodes.vue:68
+#, fuzzy
+msgid "Recovery Codes"
 msgstr "رمز الاسترداد"
 msgstr "رمز الاسترداد"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
-msgstr "رمز الاسترداد:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
+msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
@@ -2035,7 +2070,7 @@ msgstr "إعادة التحميل"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "إعادة تحميل nginx"
 msgstr "إعادة تحميل nginx"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr "إزالة"
 msgstr "إزالة"
 
 
@@ -2047,7 +2082,7 @@ msgstr "خطأ في إزالة الموقع %{site} من %{node}، الاستج
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "تمت إزالة الموقع %{site} من %{node} بنجاح"
 msgstr "تمت إزالة الموقع %{site} من %{node} بنجاح"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 msgid "Remove successfully"
 msgid "Remove successfully"
 msgstr "إزالة بنجاح"
 msgstr "إزالة بنجاح"
@@ -2136,7 +2171,7 @@ msgstr "تم الطلب باستخدام عوامل خاطئة"
 msgid "Reset"
 msgid "Reset"
 msgstr "إعادة تعيين"
 msgstr "إعادة تعيين"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "إعادة تعيين التحقق بخطوتين"
 msgstr "إعادة تعيين التحقق بخطوتين"
 
 
@@ -2148,15 +2183,15 @@ msgstr "إعادة تشغيل"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "إعادة التشغيل"
 msgstr "إعادة التشغيل"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr "اسم العرض RP"
 msgstr "اسم العرض RP"
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr "أصول RP"
 msgstr "أصول RP"
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr "معرّف الجهاز عن بُعد"
 msgstr "معرّف الجهاز عن بُعد"
 
 
@@ -2223,7 +2258,7 @@ msgstr "تم الحفظ بنجاح"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "تم الحفظ بنجاح"
 msgstr "تم الحفظ بنجاح"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr "امسح رمز الاستجابة السريعة بهاتفك المحمول لإضافة الحساب إلى التطبيق."
 msgstr "امسح رمز الاستجابة السريعة بهاتفك المحمول لإضافة الحساب إلى التطبيق."
 
 
@@ -2231,7 +2266,7 @@ msgstr "امسح رمز الاستجابة السريعة بهاتفك المح
 msgid "SDK"
 msgid "SDK"
 msgstr "حزمة تطوير البرمجيات SDK"
 msgstr "حزمة تطوير البرمجيات SDK"
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr "تم نسخ السر"
 msgstr "تم نسخ السر"
 
 
@@ -2590,11 +2625,6 @@ msgstr ""
 "يجب أن يحتوي اسم العقدة على حروف وأحرف يونيكود وأرقام وشرطات وعلامات وصل "
 "يجب أن يحتوي اسم العقدة على حروف وأحرف يونيكود وأرقام وشرطات وعلامات وصل "
 "ونقاط فقط."
 "ونقاط فقط."
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr "رمز الاسترداد يُعرض مرة واحدة فقط، يرجى حفظه في مكان آمن."
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2626,6 +2656,13 @@ msgstr "عنوان URL غير صالح."
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "اسم المستخدم أو كلمة المرور غير صحيحة"
 msgstr "اسم المستخدم أو كلمة المرور غير صحيحة"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "هذا العنصر في الشهادة التلقائية غير صالح، يرجى إزالته."
 msgstr "هذا العنصر في الشهادة التلقائية غير صالح، يرجى إزالته."
@@ -2667,11 +2704,11 @@ msgid ""
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr "سيتم ترقية أو إعادة تثبيت Nginx UI على %{nodeNames} إلى %{version}."
 msgstr "سيتم ترقية أو إعادة تثبيت Nginx UI على %{nodeNames} إلى %{version}."
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr "كبح"
 msgstr "كبح"
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2681,7 +2718,7 @@ msgstr "نصائح"
 msgid "Title"
 msgid "Title"
 msgstr "عنوان"
 msgstr "عنوان"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2734,11 +2771,11 @@ msgstr[3] ""
 msgstr[4] ""
 msgstr[4] ""
 msgstr[5] ""
 msgstr[5] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr "كلمة مرور لمرة واحدة تعتمد على الوقت"
 msgstr "كلمة مرور لمرة واحدة تعتمد على الوقت"
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2864,6 +2901,16 @@ msgstr "عرض التفاصيل"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "وضع العرض"
 msgstr "وضع العرض"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "رمز الاسترداد"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "عرض"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2885,7 +2932,7 @@ msgstr ""
 "سنقوم بإزالة تكوين HTTPChallenge من هذا الملف وإعادة تحميل Nginx. هل أنت "
 "سنقوم بإزالة تكوين HTTPChallenge من هذا الملف وإعادة تحميل Nginx. هل أنت "
 "متأكد أنك تريد المتابعة؟"
 "متأكد أنك تريد المتابعة؟"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr "ويب أوثن"
 msgstr "ويب أوثن"
 
 
@@ -2911,6 +2958,12 @@ msgstr ""
 "عند تفعيل/تعطيل، حذف، أو حفظ هذا الموقع، سيتم مزامنة العقد المحددة في فئة "
 "عند تفعيل/تعطيل، حذف، أو حفظ هذا الموقع، سيتم مزامنة العقد المحددة في فئة "
 "الموقع والعقد المحددة أدناه."
 "الموقع والعقد المحددة أدناه."
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -2924,7 +2977,7 @@ msgstr "كتابة مفتاح الشهادة الخاص إلى القرص"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "كتابة الشهادة إلى القرص"
 msgstr "كتابة الشهادة إلى القرص"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -2945,10 +2998,44 @@ msgid ""
 "passkey."
 "passkey."
 msgstr "لم تقم بتكوين إعدادات Webauthn، لذا لا يمكنك إضافة مفتاح مرور."
 msgstr "لم تقم بتكوين إعدادات Webauthn، لذا لا يمكنك إضافة مفتاح مرور."
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "مفاتيح المرور الخاصة بك"
 msgstr "مفاتيح المرور الخاصة بك"
 
 
+#~ msgid ""
+#~ "If you lose your mobile phone, you can use the recovery code to reset "
+#~ "your 2FA."
+#~ msgstr ""
+#~ "إذا فقدت هاتفك المحمول، يمكنك استخدام رمز الاسترداد لإعادة تعيين المصادقة "
+#~ "الثنائية."
+
+#~ msgid "Recovery Code:"
+#~ msgstr "رمز الاسترداد:"
+
+#~ msgid ""
+#~ "The recovery code is only displayed once, please save it in a safe place."
+#~ msgstr "رمز الاسترداد يُعرض مرة واحدة فقط، يرجى حفظه في مكان آمن."
+
 #~ msgid "Can't scan? Use text key binding"
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "لا يمكن المسح؟ استخدم ربط مفتاح النص"
 #~ msgstr "لا يمكن المسح؟ استخدم ربط مفتاح النص"
 
 

+ 125 - 52
app/src/language/en/app.po

@@ -13,7 +13,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr ""
 msgstr ""
 
 
@@ -38,7 +38,7 @@ msgstr "Username"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -142,7 +142,7 @@ msgstr "Saved successfully"
 msgid "Arch"
 msgid "Arch"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 #, fuzzy
 #, fuzzy
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "Are you sure you want to remove this directive?"
 msgstr "Are you sure you want to remove this directive?"
@@ -152,6 +152,16 @@ msgstr "Are you sure you want to remove this directive?"
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "Are you sure you want to remove this directive?"
 msgstr "Are you sure you want to remove this directive?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "Are you sure you want to remove this directive?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "Are you sure you want to remove this directive?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -215,7 +225,7 @@ msgstr ""
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -227,7 +237,7 @@ msgstr ""
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr ""
 msgstr ""
 
 
@@ -265,15 +275,15 @@ msgstr "Back"
 msgid "Back to list"
 msgid "Back to list"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr ""
 msgstr ""
 
 
@@ -469,7 +479,7 @@ msgstr ""
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "Disabled successfully"
 msgstr "Disabled successfully"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -522,6 +532,7 @@ msgstr "Content"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr ""
 msgstr ""
 
 
@@ -529,6 +540,10 @@ msgstr ""
 msgid "Copy"
 msgid "Copy"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+msgid "Copy Codes"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr ""
 msgstr ""
@@ -583,11 +598,11 @@ msgstr ""
 msgid "Credentials"
 msgid "Credentials"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr ""
 msgstr ""
 
 
@@ -944,7 +959,7 @@ msgstr "Saved successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "Saved successfully"
 msgstr "Saved successfully"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 #, fuzzy
 #, fuzzy
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Enabled successfully"
 msgstr "Enabled successfully"
@@ -986,7 +1001,7 @@ msgstr "Enabled successfully"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "Enable TLS"
 msgstr "Enable TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 #, fuzzy
 #, fuzzy
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "Enable TLS"
 msgstr "Enable TLS"
@@ -1116,6 +1131,10 @@ msgstr ""
 msgid "Finished"
 msgid "Finished"
 msgstr "Finished"
 msgstr "Finished"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1154,6 +1173,21 @@ msgstr "Certificate is valid"
 msgid "Generate"
 msgid "Generate"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate New Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "Invalid E-mail!"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "Saved successfully"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr ""
 msgstr ""
@@ -1203,18 +1237,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1247,12 +1275,11 @@ msgstr ""
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr ""
 msgstr ""
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr ""
 msgstr ""
 
 
@@ -1299,7 +1326,7 @@ msgstr ""
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "Invalid E-mail!"
 msgstr "Invalid E-mail!"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr ""
 msgstr ""
 
 
@@ -1326,6 +1353,12 @@ msgstr ""
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1474,7 +1507,7 @@ msgstr "Manage Users"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "Certificate is valid"
 msgstr "Certificate is valid"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -1672,7 +1705,7 @@ msgstr "Saved successfully"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1793,7 +1826,7 @@ msgstr ""
 msgid "Or"
 msgid "Or"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -2036,16 +2069,18 @@ msgid "Recovered Successfully"
 msgstr "Saved successfully"
 msgstr "Saved successfully"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
-msgstr ""
+#: src/views/preference/components/RecoveryCodes.vue:68
+#, fuzzy
+msgid "Recovery Codes"
+msgstr "Invalid E-mail!"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
 msgstr ""
 msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
@@ -2117,7 +2152,7 @@ msgstr ""
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr ""
 msgstr ""
 
 
@@ -2131,7 +2166,7 @@ msgstr "Saved successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "Saved successfully"
 msgstr "Saved successfully"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 #, fuzzy
 #, fuzzy
 msgid "Remove successfully"
 msgid "Remove successfully"
@@ -2233,7 +2268,7 @@ msgstr ""
 msgid "Reset"
 msgid "Reset"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr ""
 msgstr ""
 
 
@@ -2245,15 +2280,15 @@ msgstr ""
 msgid "Restarting"
 msgid "Restarting"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2326,7 +2361,7 @@ msgstr "Saved successfully"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "Saved successfully"
 msgstr "Saved successfully"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr ""
 msgstr ""
 
 
@@ -2334,7 +2369,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr ""
 msgstr ""
 
 
@@ -2704,11 +2739,6 @@ msgid ""
 "hyphens, dashes, colons, and dots."
 "hyphens, dashes, colons, and dots."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2740,6 +2770,13 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "Password"
 msgstr "Password"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr ""
 msgstr ""
@@ -2782,11 +2819,11 @@ msgid ""
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr "Saved successfully"
 msgstr "Saved successfully"
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2796,7 +2833,7 @@ msgstr ""
 msgid "Title"
 msgid "Title"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2834,11 +2871,11 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2971,6 +3008,16 @@ msgstr ""
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Basic Mode"
 msgstr "Basic Mode"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "Invalid E-mail!"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "Basic Mode"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2990,7 +3037,7 @@ msgid ""
 "Nginx. Are you sure you want to continue?"
 "Nginx. Are you sure you want to continue?"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -3011,6 +3058,12 @@ msgid ""
 "site category and the nodes selected below will be synchronized."
 "site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -3025,7 +3078,7 @@ msgstr ""
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "Certificate is valid"
 msgstr "Certificate is valid"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -3046,6 +3099,26 @@ msgid ""
 "passkey."
 "passkey."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr ""
 msgstr ""

+ 143 - 56
app/src/language/es/app.po

@@ -20,7 +20,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr "2FA"
 msgstr "2FA"
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr "Configuración de 2FA"
 msgstr "Configuración de 2FA"
 
 
@@ -43,7 +43,7 @@ msgstr "Usuario ACME"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -145,7 +145,7 @@ msgstr "Duplicado con éxito"
 msgid "Arch"
 msgid "Arch"
 msgstr "Arquitectura"
 msgstr "Arquitectura"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "¿Está seguro de eliminar esta IP bloqueada inmediatamente?"
 msgstr "¿Está seguro de eliminar esta IP bloqueada inmediatamente?"
 
 
@@ -153,6 +153,16 @@ msgstr "¿Está seguro de eliminar esta IP bloqueada inmediatamente?"
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "¿Está seguro de eliminar esta llave de acceso inmediatamente?"
 msgstr "¿Está seguro de eliminar esta llave de acceso inmediatamente?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "¿Está seguro de que quiere recuperar este elemento?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "¿Está seguro de que quiere borrar?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -209,7 +219,7 @@ msgstr "Asistente"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr "Intentos"
 msgstr "Intentos"
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr "Intentos"
 msgstr "Intentos"
 
 
@@ -221,7 +231,7 @@ msgstr "Autenticación"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr "Autenticarse con una llave de acceso"
 msgstr "Autenticarse con una llave de acceso"
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr "Configuración de autenticación"
 msgstr "Configuración de autenticación"
 
 
@@ -258,15 +268,15 @@ msgstr "Volver al Inicio"
 msgid "Back to list"
 msgid "Back to list"
 msgstr "Volver a la lista"
 msgstr "Volver a la lista"
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "Umbral de Prohibición en Minutos"
 msgstr "Umbral de Prohibición en Minutos"
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr "IPs prohibidas"
 msgstr "IPs prohibidas"
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr "Bloqueado hasta"
 msgstr "Bloqueado hasta"
 
 
@@ -456,7 +466,7 @@ msgstr "Borrar"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "Limpiado exitoso"
 msgstr "Limpiado exitoso"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -507,6 +517,7 @@ msgstr "Contenido"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr "Copiado"
 msgstr "Copiado"
 
 
@@ -514,6 +525,11 @@ msgstr "Copiado"
 msgid "Copy"
 msgid "Copy"
 msgstr "Copiar"
 msgstr "Copiar"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+#, fuzzy
+msgid "Copy Codes"
+msgstr "Código de Recuperación"
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Actualización del kernel"
 msgstr "Actualización del kernel"
@@ -564,11 +580,11 @@ msgstr "Credencial"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Credenciales"
 msgstr "Credenciales"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr "La cuenta actual tiene habilitada TOTP."
 msgstr "La cuenta actual tiene habilitada TOTP."
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr "La cuenta actual no tiene habilitada TOTP."
 msgstr "La cuenta actual no tiene habilitada TOTP."
 
 
@@ -903,7 +919,7 @@ msgstr "Falló el habilitado de %{conf_name} en %{node_name}"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "Habilitado exitoso de %{conf_name} en %{node_name}"
 msgstr "Habilitado exitoso de %{conf_name} en %{node_name}"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Habilitar 2FA exitoso"
 msgstr "Habilitar 2FA exitoso"
 
 
@@ -943,7 +959,7 @@ msgstr "Habilitado con Éxito"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "Habilitar TLS"
 msgstr "Habilitar TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 #, fuzzy
 #, fuzzy
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "Habilitar TLS"
 msgstr "Habilitar TLS"
@@ -1070,6 +1086,10 @@ msgstr "Filtro"
 msgid "Finished"
 msgid "Finished"
 msgstr "Terminado"
 msgstr "Terminado"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1107,6 +1127,22 @@ msgstr "Certificado General"
 msgid "Generate"
 msgid "Generate"
 msgstr "Generar"
 msgstr "Generar"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate New Recovery Codes"
+msgstr "Código de Recuperación"
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "Código de Recuperación"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "Recuperado con éxito"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Generando clave privada para registrar cuenta"
 msgstr "Generando clave privada para registrar cuenta"
@@ -1155,7 +1191,7 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Si se deja en blanco, se utilizará el directorio CA predeterminado."
 msgstr "Si se deja en blanco, se utilizará el directorio CA predeterminado."
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
@@ -1164,14 +1200,6 @@ msgstr ""
 "el máximo de intentos en los minutos del umbral de prohibición, la IP será "
 "el máximo de intentos en los minutos del umbral de prohibición, la IP será "
 "bloqueada por un período de tiempo."
 "bloqueada por un período de tiempo."
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-"Si pierde su teléfono móvil, puede usar el código de recuperación para "
-"restablecer su 2FA."
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1206,12 +1234,11 @@ msgstr "Error de actualización de kernel inicial"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "Inicializando la actualización del kernel"
 msgstr "Inicializando la actualización del kernel"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr "Ingrese el código de la aplicación:"
 msgstr "Ingrese el código de la aplicación:"
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr "Ingrese el código de recuperación:"
 msgstr "Ingrese el código de recuperación:"
 
 
@@ -1254,7 +1281,7 @@ msgstr "Código de acceso o código de recuperación inválido"
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "Código 2FA o de recuperación inválido"
 msgstr "Código 2FA o de recuperación inválido"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr "IP"
 msgstr "IP"
 
 
@@ -1278,6 +1305,12 @@ msgstr "Emisor: %{issuer}"
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "Secreto Jwt"
 msgstr "Secreto Jwt"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1422,7 +1455,7 @@ msgstr "Administrar usuarios"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "Certificado Administrado"
 msgstr "Certificado Administrado"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr "Intentos máximos"
 msgstr "Intentos máximos"
 
 
@@ -1611,7 +1644,7 @@ msgstr "Nginx reiniciado con éxito"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1732,7 +1765,7 @@ msgstr "OpenAI"
 msgid "Or"
 msgid "Or"
 msgstr "O"
 msgstr "O"
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -1992,17 +2025,19 @@ msgid "Recovered Successfully"
 msgstr "Recuperado con éxito"
 msgstr "Recuperado con éxito"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr "Recuperación"
 msgstr "Recuperación"
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
+#: src/views/preference/components/RecoveryCodes.vue:68
+#, fuzzy
+msgid "Recovery Codes"
 msgstr "Código de Recuperación"
 msgstr "Código de Recuperación"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
-msgstr "Código de Recuperación:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
+msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
@@ -2071,7 +2106,7 @@ msgstr "Recargando"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "Recargando Nginx"
 msgstr "Recargando Nginx"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr "Eliminar"
 msgstr "Eliminar"
 
 
@@ -2085,7 +2120,7 @@ msgstr "Eliminar sitio: %{site_name}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "Duplicado con éxito de %{conf_name} a %{node_name}"
 msgstr "Duplicado con éxito de %{conf_name} a %{node_name}"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 msgid "Remove successfully"
 msgid "Remove successfully"
 msgstr "Eliminado con éxito"
 msgstr "Eliminado con éxito"
@@ -2176,7 +2211,7 @@ msgstr "Pedido con parámetros incorrectos"
 msgid "Reset"
 msgid "Reset"
 msgstr "Limpiar"
 msgstr "Limpiar"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "Restablecer 2FA"
 msgstr "Restablecer 2FA"
 
 
@@ -2188,15 +2223,15 @@ msgstr "Reiniciar"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "Reiniciando"
 msgstr "Reiniciando"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr "Nombre RP"
 msgstr "Nombre RP"
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr "Orígenes RP"
 msgstr "Orígenes RP"
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr "RPID"
 msgstr "RPID"
 
 
@@ -2269,7 +2304,7 @@ msgstr "Guardado con éxito"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "Guardado con éxito"
 msgstr "Guardado con éxito"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr ""
 msgstr ""
 "Escanee el código QR con su teléfono móvil para agregar la cuenta a la "
 "Escanee el código QR con su teléfono móvil para agregar la cuenta a la "
@@ -2279,7 +2314,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr "SDK"
 msgstr "SDK"
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr "El secreto ha sido copiado"
 msgstr "El secreto ha sido copiado"
 
 
@@ -2648,13 +2683,6 @@ msgstr ""
 "El nombre del servidor solo debe contener letras, Unicode, números, guiones, "
 "El nombre del servidor solo debe contener letras, Unicode, números, guiones, "
 "rayas y puntos."
 "rayas y puntos."
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-"El código de recuperación se muestra solo una vez, por favor guárdalo en un "
-"lugar seguro."
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2687,6 +2715,13 @@ msgstr "La URL no es válida."
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "El nombre de usuario o contraseña son incorrectos"
 msgstr "El nombre de usuario o contraseña son incorrectos"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "Este elemento de Auto Cert es inválido, elimínelo por favor."
 msgstr "Este elemento de Auto Cert es inválido, elimínelo por favor."
@@ -2735,11 +2770,11 @@ msgstr ""
 "Esto actualizará o reinstalará la interfaz de usuario de Nginx en "
 "Esto actualizará o reinstalará la interfaz de usuario de Nginx en "
 "%{nodeNames} a %{version}."
 "%{nodeNames} a %{version}."
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr "Acelerador"
 msgstr "Acelerador"
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2749,7 +2784,7 @@ msgstr "Consejos"
 msgid "Title"
 msgid "Title"
 msgstr "Título"
 msgstr "Título"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2800,11 +2835,11 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr "TOTP"
 msgstr "TOTP"
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2931,6 +2966,16 @@ msgstr "Ver detalles"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Modo de vista"
 msgstr "Modo de vista"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "Código de Recuperación"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "Ver"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2954,7 +2999,7 @@ msgstr ""
 "Eliminaremos la configuración de HTTPChallenge de este archivo y "
 "Eliminaremos la configuración de HTTPChallenge de este archivo y "
 "recargaremos Nginx. ¿Estás seguro de que quieres continuar?"
 "recargaremos Nginx. ¿Estás seguro de que quieres continuar?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr "Webauthn"
 msgstr "Webauthn"
 
 
@@ -2981,6 +3026,12 @@ msgstr ""
 "configurados en la categoría del sitio y los nodos seleccionados a "
 "configurados en la categoría del sitio y los nodos seleccionados a "
 "continuación se sincronizarán."
 "continuación se sincronizarán."
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -2994,7 +3045,7 @@ msgstr "Escribir la clave privada del certificado a disco"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "Escribir certificado a disco"
 msgstr "Escribir certificado a disco"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -3017,10 +3068,46 @@ msgstr ""
 "No ha configurado los ajustes de Webauthn, por lo que no puede agregar una "
 "No ha configurado los ajustes de Webauthn, por lo que no puede agregar una "
 "llave de acceso."
 "llave de acceso."
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Sus llaves de acceso"
 msgstr "Sus llaves de acceso"
 
 
+#~ msgid ""
+#~ "If you lose your mobile phone, you can use the recovery code to reset "
+#~ "your 2FA."
+#~ msgstr ""
+#~ "Si pierde su teléfono móvil, puede usar el código de recuperación para "
+#~ "restablecer su 2FA."
+
+#~ msgid "Recovery Code:"
+#~ msgstr "Código de Recuperación:"
+
+#~ msgid ""
+#~ "The recovery code is only displayed once, please save it in a safe place."
+#~ msgstr ""
+#~ "El código de recuperación se muestra solo una vez, por favor guárdalo en "
+#~ "un lugar seguro."
+
 #~ msgid "Can't scan? Use text key binding"
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "¿No puede escanear? Utilice la vinculación con una llave de texto"
 #~ msgstr "¿No puede escanear? Utilice la vinculación con una llave de texto"
 
 

+ 121 - 51
app/src/language/fr_FR/app.po

@@ -15,7 +15,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr ""
 msgstr ""
 
 
@@ -39,7 +39,7 @@ msgstr "Nom d'utilisateur"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -145,7 +145,7 @@ msgstr "Dupliqué avec succès"
 msgid "Arch"
 msgid "Arch"
 msgstr "Arch"
 msgstr "Arch"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 #, fuzzy
 #, fuzzy
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
@@ -155,6 +155,16 @@ msgstr "Etes-vous sûr que vous voulez supprimer ?"
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "Voulez-vous vraiment supprimer cette directive ?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "Etes-vous sûr que vous voulez supprimer ?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -216,7 +226,7 @@ msgstr ""
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -229,7 +239,7 @@ msgstr "Autheur"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr ""
 msgstr ""
 
 
@@ -266,15 +276,15 @@ msgstr "Retour au menu principal"
 msgid "Back to list"
 msgid "Back to list"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr ""
 msgstr ""
 
 
@@ -471,7 +481,7 @@ msgstr "Effacer"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "Désactivé avec succès"
 msgstr "Désactivé avec succès"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -523,6 +533,7 @@ msgstr "Contenu"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr ""
 msgstr ""
 
 
@@ -530,6 +541,10 @@ msgstr ""
 msgid "Copy"
 msgid "Copy"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+msgid "Copy Codes"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Mise à jour du core"
 msgstr "Mise à jour du core"
@@ -584,11 +599,11 @@ msgstr "Identifiant"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Identifiants"
 msgstr "Identifiants"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr ""
 msgstr ""
 
 
@@ -943,7 +958,7 @@ msgstr "Dupliqué avec succès"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "Dupliqué avec succès"
 msgstr "Dupliqué avec succès"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 #, fuzzy
 #, fuzzy
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Activé avec succès"
 msgstr "Activé avec succès"
@@ -985,7 +1000,7 @@ msgstr "Activé avec succès"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "Activer TLS"
 msgstr "Activer TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 #, fuzzy
 #, fuzzy
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "Activer TLS"
 msgstr "Activer TLS"
@@ -1117,6 +1132,10 @@ msgstr "Filtrer"
 msgid "Finished"
 msgid "Finished"
 msgstr "Finie"
 msgstr "Finie"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1154,6 +1173,20 @@ msgstr "Changer de certificat"
 msgid "Generate"
 msgid "Generate"
 msgstr "Générer"
 msgstr "Générer"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate New Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "Enregistré avec succès"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Génération de clé privée pour l'enregistrement du compte"
 msgstr "Génération de clé privée pour l'enregistrement du compte"
@@ -1202,18 +1235,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1247,12 +1274,11 @@ msgstr "Erreur du programme de mise à niveau initial du core"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "Initialisation du programme de mise à niveau du core"
 msgstr "Initialisation du programme de mise à niveau du core"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr ""
 msgstr ""
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr ""
 msgstr ""
 
 
@@ -1294,7 +1320,7 @@ msgstr ""
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr ""
 msgstr ""
 
 
@@ -1320,6 +1346,12 @@ msgstr ""
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "Secret Jwt"
 msgstr "Secret Jwt"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #, fuzzy
 #, fuzzy
@@ -1470,7 +1502,7 @@ msgstr "Gérer les utilisateurs"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "Changer de certificat"
 msgstr "Changer de certificat"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -1667,7 +1699,7 @@ msgstr "Nginx a redémarré avec succès"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1786,7 +1818,7 @@ msgstr "OpenAI"
 msgid "Or"
 msgid "Or"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -2040,16 +2072,17 @@ msgid "Recovered Successfully"
 msgstr "Enregistré avec succès"
 msgstr "Enregistré avec succès"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
+#: src/views/preference/components/RecoveryCodes.vue:68
+msgid "Recovery Codes"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
 msgstr ""
 msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
@@ -2121,7 +2154,7 @@ msgstr "Rechargement"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "Rechargement de nginx"
 msgstr "Rechargement de nginx"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr ""
 msgstr ""
 
 
@@ -2135,7 +2168,7 @@ msgstr "Supprimer le site : %{site_name}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "Dupliqué avec succès"
 msgstr "Dupliqué avec succès"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 #, fuzzy
 #, fuzzy
 msgid "Remove successfully"
 msgid "Remove successfully"
@@ -2237,7 +2270,7 @@ msgstr ""
 msgid "Reset"
 msgid "Reset"
 msgstr "Réinitialiser"
 msgstr "Réinitialiser"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 #, fuzzy
 #, fuzzy
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "Réinitialiser"
 msgstr "Réinitialiser"
@@ -2250,15 +2283,15 @@ msgstr "Redémarrer"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "Redémarrage"
 msgstr "Redémarrage"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2329,7 +2362,7 @@ msgstr "Sauvegarde réussie"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "Enregistré avec succès"
 msgstr "Enregistré avec succès"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr ""
 msgstr ""
 
 
@@ -2337,7 +2370,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr ""
 msgstr ""
 
 
@@ -2706,11 +2739,6 @@ msgid ""
 "hyphens, dashes, colons, and dots."
 "hyphens, dashes, colons, and dots."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2742,6 +2770,13 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "Le pseudo ou mot de passe est incorect"
 msgstr "Le pseudo ou mot de passe est incorect"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 #, fuzzy
 #, fuzzy
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
@@ -2787,11 +2822,11 @@ msgid ""
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr "Dupliqué avec succès"
 msgstr "Dupliqué avec succès"
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2801,7 +2836,7 @@ msgstr ""
 msgid "Title"
 msgid "Title"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2843,11 +2878,11 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2977,6 +3012,15 @@ msgstr ""
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Mode simple"
 msgstr "Mode simple"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+msgid "View Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "Voir"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2998,7 +3042,7 @@ msgstr ""
 "Nous allons supprimer la configuration HTTPChallenge de ce fichier et "
 "Nous allons supprimer la configuration HTTPChallenge de ce fichier et "
 "recharger le Nginx. Êtes-vous sûr de vouloir continuer?"
 "recharger le Nginx. Êtes-vous sûr de vouloir continuer?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -3019,6 +3063,12 @@ msgid ""
 "site category and the nodes selected below will be synchronized."
 "site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -3032,7 +3082,7 @@ msgstr "Écriture de la clé privée du certificat sur le disque"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "Écriture du certificat sur le disque"
 msgstr "Écriture du certificat sur le disque"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -3053,6 +3103,26 @@ msgid ""
 "passkey."
 "passkey."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr ""
 msgstr ""

+ 125 - 52
app/src/language/ko_KR/app.po

@@ -18,7 +18,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr "2FA"
 msgstr "2FA"
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr "2FA 설정"
 msgstr "2FA 설정"
 
 
@@ -41,7 +41,7 @@ msgstr "ACME 사용자"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -141,7 +141,7 @@ msgstr "성공적으로 복제됨"
 msgid "Arch"
 msgid "Arch"
 msgstr "아키텍처"
 msgstr "아키텍처"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "차단된 IP를 즉시 삭제하시겠습니까?"
 msgstr "차단된 IP를 즉시 삭제하시겠습니까?"
 
 
@@ -150,6 +150,16 @@ msgstr "차단된 IP를 즉시 삭제하시겠습니까?"
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "차단된 IP를 즉시 삭제하시겠습니까?"
 msgstr "차단된 IP를 즉시 삭제하시겠습니까?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "이 항목을 복구하시겠습니까?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "정말 삭제하시겠습니까?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -206,7 +216,7 @@ msgstr "조수"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr "시도 횟수"
 msgstr "시도 횟수"
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr "시도 횟수"
 msgstr "시도 횟수"
 
 
@@ -218,7 +228,7 @@ msgstr "인증"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr ""
 msgstr ""
 
 
@@ -255,15 +265,15 @@ msgstr "홈으로"
 msgid "Back to list"
 msgid "Back to list"
 msgstr "목록으로 돌아가기"
 msgstr "목록으로 돌아가기"
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "차단 시간(분)"
 msgstr "차단 시간(분)"
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr "차단된 IP"
 msgstr "차단된 IP"
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr "차단될 시간"
 msgstr "차단될 시간"
 
 
@@ -453,7 +463,7 @@ msgstr "클리어"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "성공적으로 제거됨"
 msgstr "성공적으로 제거됨"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -504,6 +514,7 @@ msgstr "내용"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr ""
 msgstr ""
 
 
@@ -511,6 +522,10 @@ msgstr ""
 msgid "Copy"
 msgid "Copy"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+msgid "Copy Codes"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "코어 업그레이드"
 msgstr "코어 업그레이드"
@@ -564,11 +579,11 @@ msgstr "인증 정보"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "인증 정보들"
 msgstr "인증 정보들"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr ""
 msgstr ""
 
 
@@ -904,7 +919,7 @@ msgstr "%{node_name}에서 %{conf_name} 활성화 실패"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "%{node_name}에서 %{conf_name} 성공적으로 활성화됨"
 msgstr "%{node_name}에서 %{conf_name} 성공적으로 활성화됨"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 #, fuzzy
 #, fuzzy
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "성공적으로 활성화"
 msgstr "성공적으로 활성화"
@@ -945,7 +960,7 @@ msgstr "성공적으로 활성화"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "TLS 활성화"
 msgstr "TLS 활성화"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 #, fuzzy
 #, fuzzy
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "TLS 활성화"
 msgstr "TLS 활성화"
@@ -1074,6 +1089,10 @@ msgstr "필터"
 msgid "Finished"
 msgid "Finished"
 msgstr "완료됨"
 msgstr "완료됨"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1112,6 +1131,21 @@ msgstr "일반 인증서"
 msgid "Generate"
 msgid "Generate"
 msgstr "생성"
 msgstr "생성"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate New Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "유효함"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "성공적으로 제거됨"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "계정 등록을 위한 개인 키 생성 중"
 msgstr "계정 등록을 위한 개인 키 생성 중"
@@ -1161,18 +1195,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1205,12 +1233,11 @@ msgstr "초기 코어 업그레이더 오류"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "코어 업그레이더 초기화"
 msgstr "코어 업그레이더 초기화"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr ""
 msgstr ""
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr ""
 msgstr ""
 
 
@@ -1257,7 +1284,7 @@ msgstr ""
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "유효함"
 msgstr "유효함"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr ""
 msgstr ""
 
 
@@ -1284,6 +1311,12 @@ msgstr ""
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "Jwt 토큰"
 msgstr "Jwt 토큰"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1436,7 +1469,7 @@ msgstr "사용자 관리"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "인증서 유효"
 msgstr "인증서 유효"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 #, fuzzy
 #, fuzzy
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr "시도 횟수"
 msgstr "시도 횟수"
@@ -1638,7 +1671,7 @@ msgstr "Nginx가 성공적으로 재시작됨"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1757,7 +1790,7 @@ msgstr "오픈AI"
 msgid "Or"
 msgid "Or"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -2004,16 +2037,18 @@ msgid "Recovered Successfully"
 msgstr "성공적으로 제거됨"
 msgstr "성공적으로 제거됨"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
-msgstr ""
+#: src/views/preference/components/RecoveryCodes.vue:68
+#, fuzzy
+msgid "Recovery Codes"
+msgstr "유효함"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
 msgstr ""
 msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
@@ -2086,7 +2121,7 @@ msgstr "리로딩 중"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "Nginx 리로딩 중"
 msgstr "Nginx 리로딩 중"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr ""
 msgstr ""
 
 
@@ -2100,7 +2135,7 @@ msgstr "사이트 삭제: %{site_name}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "%{conf_name}을(를) %{node_name}(으)로 성공적으로 복제함"
 msgstr "%{conf_name}을(를) %{node_name}(으)로 성공적으로 복제함"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 #, fuzzy
 #, fuzzy
 msgid "Remove successfully"
 msgid "Remove successfully"
@@ -2202,7 +2237,7 @@ msgstr "잘못된 매개변수로 요청됨"
 msgid "Reset"
 msgid "Reset"
 msgstr "재설정"
 msgstr "재설정"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 #, fuzzy
 #, fuzzy
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "재설정"
 msgstr "재설정"
@@ -2215,15 +2250,15 @@ msgstr "재시작"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "재시작 중"
 msgstr "재시작 중"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2296,7 +2331,7 @@ msgstr "성공적으로 저장됨"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "성공적으로 저장됨"
 msgstr "성공적으로 저장됨"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr ""
 msgstr ""
 
 
@@ -2304,7 +2339,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr ""
 msgstr ""
 
 
@@ -2670,11 +2705,6 @@ msgid ""
 "hyphens, dashes, colons, and dots."
 "hyphens, dashes, colons, and dots."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2707,6 +2737,13 @@ msgstr "유효한 URL이 아닙니다"
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "사용자 이름 또는 비밀번호가 올바르지 않습니다"
 msgstr "사용자 이름 또는 비밀번호가 올바르지 않습니다"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "이 자동 인증 항목이 유효하지 않습니다. 제거해주세요."
 msgstr "이 자동 인증 항목이 유효하지 않습니다. 제거해주세요."
@@ -2751,11 +2788,11 @@ msgid ""
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr "%{conf_name}을(를) %{node_name}(으)로 성공적으로 복제함"
 msgstr "%{conf_name}을(를) %{node_name}(으)로 성공적으로 복제함"
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2765,7 +2802,7 @@ msgstr "팁"
 msgid "Title"
 msgid "Title"
 msgstr "제목"
 msgstr "제목"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2806,11 +2843,11 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2943,6 +2980,16 @@ msgstr "세부 사항"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "기본 모드"
 msgstr "기본 모드"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "유효함"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "보기"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2966,7 +3013,7 @@ msgstr ""
 "이 파일에서 HTTPChallenge 구성을 제거하고 Nginx를 다시 로드할 예정입니다. 계"
 "이 파일에서 HTTPChallenge 구성을 제거하고 Nginx를 다시 로드할 예정입니다. 계"
 "속하시겠습니까?"
 "속하시겠습니까?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -2987,6 +3034,12 @@ msgid ""
 "site category and the nodes selected below will be synchronized."
 "site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -3000,7 +3053,7 @@ msgstr "인증서 개인 키를 디스크에 쓰기"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "인증서를 디스크에 쓰기"
 msgstr "인증서를 디스크에 쓰기"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -3021,6 +3074,26 @@ msgid ""
 "passkey."
 "passkey."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr ""
 msgstr ""

+ 105 - 48
app/src/language/messages.pot

@@ -6,7 +6,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr ""
 msgstr ""
 
 
@@ -31,7 +31,7 @@ msgstr ""
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76
 #: src/views/site/site_list/columns.tsx:76
 #: src/views/stream/StreamList.vue:49
 #: src/views/stream/StreamList.vue:49
@@ -132,7 +132,7 @@ msgstr ""
 msgid "Arch"
 msgid "Arch"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr ""
 msgstr ""
 
 
@@ -140,6 +140,14 @@ msgstr ""
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+msgid "Are you sure to generate new recovery codes?"
+msgstr ""
+
+#: src/views/preference/components/TOTP.vue:85
+msgid "Are you sure to reset 2FA?"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
 msgstr ""
 msgstr ""
@@ -194,7 +202,7 @@ msgstr ""
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -206,7 +214,7 @@ msgstr ""
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr ""
 msgstr ""
 
 
@@ -245,15 +253,15 @@ msgstr ""
 msgid "Back to list"
 msgid "Back to list"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr ""
 msgstr ""
 
 
@@ -433,7 +441,7 @@ msgstr ""
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -484,6 +492,7 @@ msgstr ""
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr ""
 msgstr ""
 
 
@@ -491,6 +500,10 @@ msgstr ""
 msgid "Copy"
 msgid "Copy"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+msgid "Copy Codes"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr ""
 msgstr ""
@@ -543,11 +556,11 @@ msgstr ""
 msgid "Credentials"
 msgid "Credentials"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr ""
 msgstr ""
 
 
@@ -875,7 +888,7 @@ msgstr ""
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr ""
 msgstr ""
 
 
@@ -911,7 +924,7 @@ msgstr ""
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr ""
 msgstr ""
 
 
@@ -1040,6 +1053,10 @@ msgstr ""
 msgid "Finished"
 msgid "Finished"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid "Follow the instructions in the dialog to complete the passkey registration process."
 msgid "Follow the instructions in the dialog to complete the passkey registration process."
 msgstr ""
 msgstr ""
@@ -1073,6 +1090,19 @@ msgstr ""
 msgid "Generate"
 msgid "Generate"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate New Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+msgid "Generate recovery codes successfully"
+msgstr ""
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr ""
 msgstr ""
@@ -1122,14 +1152,10 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid "If the number of login failed attempts from a ip reach the max attempts in ban threshold minutes, the ip will be banned for a period of time."
 msgid "If the number of login failed attempts from a ip reach the max attempts in ban threshold minutes, the ip will be banned for a period of time."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid "If you lose your mobile phone, you can use the recovery code to reset your 2FA."
-msgstr ""
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1161,12 +1187,11 @@ msgstr ""
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr ""
 msgstr ""
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr ""
 msgstr ""
 
 
@@ -1208,7 +1233,7 @@ msgstr ""
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr ""
 msgstr ""
 
 
@@ -1232,6 +1257,10 @@ msgstr ""
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid "Keep your recovery codes as safe as your password. We recommend saving them with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1364,7 +1393,7 @@ msgstr ""
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -1554,7 +1583,7 @@ msgstr ""
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1667,7 +1696,7 @@ msgstr ""
 msgid "Or"
 msgid "Or"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -1894,16 +1923,15 @@ msgid "Recovered Successfully"
 msgstr ""
 msgstr ""
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
+#: src/views/preference/components/RecoveryCodes.vue:68
+msgid "Recovery Codes"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid "Recovery codes are used to access your account when you lose access to your 2FA device. Each code can only be used once."
 msgstr ""
 msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
@@ -1967,7 +1995,7 @@ msgstr ""
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr ""
 msgstr ""
 
 
@@ -1979,7 +2007,7 @@ msgstr ""
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 msgid "Remove successfully"
 msgid "Remove successfully"
 msgstr ""
 msgstr ""
@@ -2063,7 +2091,7 @@ msgstr ""
 msgid "Reset"
 msgid "Reset"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr ""
 msgstr ""
 
 
@@ -2075,15 +2103,15 @@ msgstr ""
 msgid "Restarting"
 msgid "Restarting"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2151,7 +2179,7 @@ msgstr ""
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr ""
 msgstr ""
 
 
@@ -2159,7 +2187,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr ""
 msgstr ""
 
 
@@ -2474,10 +2502,6 @@ msgstr ""
 msgid "The Public Security Number should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots."
 msgid "The Public Security Number should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid "The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid "The remote Nginx UI version is not compatible with the local Nginx UI version. To avoid potential errors, please upgrade the remote Nginx UI to match the local version."
 msgid "The remote Nginx UI version is not compatible with the local Nginx UI version. To avoid potential errors, please upgrade the remote Nginx UI to match the local version."
 msgstr ""
 msgstr ""
@@ -2500,6 +2524,10 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid "These codes are the last resort for accessing your account in case you lose your password and second factors. If you cannot find these codes, you will lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr ""
 msgstr ""
@@ -2539,11 +2567,11 @@ msgstr ""
 msgid "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgid "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2553,7 +2581,7 @@ msgstr ""
 msgid "Title"
 msgid "Title"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid "To enable it, you need to install the Google or Microsoft Authenticator app on your mobile phone."
 msgid "To enable it, you need to install the Google or Microsoft Authenticator app on your mobile phone."
 msgstr ""
 msgstr ""
 
 
@@ -2579,11 +2607,11 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid "TOTP is a two-factor authentication method that uses a time-based one-time password algorithm."
 msgid "TOTP is a two-factor authentication method that uses a time-based one-time password algorithm."
 msgstr ""
 msgstr ""
 
 
@@ -2710,6 +2738,14 @@ msgstr ""
 msgid "View Mode"
 msgid "View Mode"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+msgid "View Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "Viewed"
+msgstr ""
+
 #: src/constants/index.ts:17
 #: src/constants/index.ts:17
 #: src/views/config/InspectConfig.vue:33
 #: src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
@@ -2726,7 +2762,7 @@ msgstr ""
 msgid "We will remove the HTTPChallenge configuration from this file and reload the Nginx. Are you sure you want to continue?"
 msgid "We will remove the HTTPChallenge configuration from this file and reload the Nginx. Are you sure you want to continue?"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -2742,6 +2778,10 @@ msgstr ""
 msgid "When you enable/disable, delete, or save this site, the nodes set in the site category and the nodes selected below will be synchronized."
 msgid "When you enable/disable, delete, or save this site, the nodes set in the site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid "When you generate new recovery codes, you must download or print the new codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -2755,7 +2795,7 @@ msgstr ""
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -2774,6 +2814,23 @@ msgstr ""
 msgid "You have not configured the settings of Webauthn, so you cannot add a passkey."
 msgid "You have not configured the settings of Webauthn, so you cannot add a passkey."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid "You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid "Your current recovery code might be outdated and insecure. Please generate new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr ""
 msgstr ""

+ 143 - 56
app/src/language/ru_RU/app.po

@@ -19,7 +19,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr "2FA"
 msgstr "2FA"
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr "Настройки 2FA"
 msgstr "Настройки 2FA"
 
 
@@ -42,7 +42,7 @@ msgstr "Пользователь ACME"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -142,7 +142,7 @@ msgstr "Продублированно"
 msgid "Arch"
 msgid "Arch"
 msgstr "Архитектура"
 msgstr "Архитектура"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "Вы уверены, что хотите немедленно удалить этот заблокированный IP?"
 msgstr "Вы уверены, что хотите немедленно удалить этот заблокированный IP?"
 
 
@@ -151,6 +151,16 @@ msgstr "Вы уверены, что хотите немедленно удали
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "Вы уверены, что хотите немедленно удалить этот заблокированный IP?"
 msgstr "Вы уверены, что хотите немедленно удалить этот заблокированный IP?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "Вы уверены, что хотите восстановить этот элемент?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "Вы уверены, что хотите удалить?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -208,7 +218,7 @@ msgstr "Ассистент"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr "Попытки"
 msgstr "Попытки"
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr "Попытки"
 msgstr "Попытки"
 
 
@@ -220,7 +230,7 @@ msgstr "Авторизация"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr "Аутентификация с помощью ключа доступа"
 msgstr "Аутентификация с помощью ключа доступа"
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr "Настройки аутентификации"
 msgstr "Настройки аутентификации"
 
 
@@ -257,15 +267,15 @@ msgstr "Вернуться на главную"
 msgid "Back to list"
 msgid "Back to list"
 msgstr "Возврат к списку"
 msgstr "Возврат к списку"
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "Порог блокировки в минутах"
 msgstr "Порог блокировки в минутах"
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr "Заблокированные IP-адреса"
 msgstr "Заблокированные IP-адреса"
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr "Заблокирован до"
 msgstr "Заблокирован до"
 
 
@@ -455,7 +465,7 @@ msgstr "Очистить"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "Очищено успешно"
 msgstr "Очищено успешно"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -506,6 +516,7 @@ msgstr "Содержание"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr "Скопировано"
 msgstr "Скопировано"
 
 
@@ -513,6 +524,11 @@ msgstr "Скопировано"
 msgid "Copy"
 msgid "Copy"
 msgstr "Копировать"
 msgstr "Копировать"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+#, fuzzy
+msgid "Copy Codes"
+msgstr "Код восстановления"
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Обновление ядра"
 msgstr "Обновление ядра"
@@ -563,12 +579,12 @@ msgstr "Учетные данные"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Учетные данные"
 msgstr "Учетные данные"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 #, fuzzy
 #, fuzzy
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr "Текущая учетная запись имеет включенную 2ФА."
 msgstr "Текущая учетная запись имеет включенную 2ФА."
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 #, fuzzy
 #, fuzzy
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr ""
 msgstr ""
@@ -911,7 +927,7 @@ msgstr "Включение %{conf_name} in %{node_name} нипалучилася
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "Включение %{conf_name} in %{node_name} успешно"
 msgstr "Включение %{conf_name} in %{node_name} успешно"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Двухфакторная аутентификация успешно включена"
 msgstr "Двухфакторная аутентификация успешно включена"
 
 
@@ -951,7 +967,7 @@ msgstr "Включено успешно"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "Включить TLS"
 msgstr "Включить TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 #, fuzzy
 #, fuzzy
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "Включить TLS"
 msgstr "Включить TLS"
@@ -1077,6 +1093,10 @@ msgstr "Фильтр"
 msgid "Finished"
 msgid "Finished"
 msgstr "Готово"
 msgstr "Готово"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1114,6 +1134,22 @@ msgstr "Общий сертификат"
 msgid "Generate"
 msgid "Generate"
 msgstr "Сгенерировать"
 msgstr "Сгенерировать"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate New Recovery Codes"
+msgstr "Код восстановления"
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "Код восстановления"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "Восстановлено успешно"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Генерация приватного ключа для регистрации учетной записи"
 msgstr "Генерация приватного ключа для регистрации учетной записи"
@@ -1162,7 +1198,7 @@ msgstr "ICP номер"
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Если оставить пустым, будет использоваться каталог CA по умолчанию."
 msgstr "Если оставить пустым, будет использоваться каталог CA по умолчанию."
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
@@ -1171,14 +1207,6 @@ msgstr ""
 "количества попыток в течение пороговых минут блокировки, IP будет "
 "количества попыток в течение пороговых минут блокировки, IP будет "
 "заблокирован на определенный период времени."
 "заблокирован на определенный период времени."
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-"Если вы потеряете свой мобильный телефон, вы можете использовать код "
-"восстановления для сброса 2FA."
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1213,12 +1241,11 @@ msgstr "Ошибка первоначального обновления ядр
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "Инициализация программы обновления ядра"
 msgstr "Инициализация программы обновления ядра"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr "Введите код из приложения:"
 msgstr "Введите код из приложения:"
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr "Введите код восстановления:"
 msgstr "Введите код восстановления:"
 
 
@@ -1261,7 +1288,7 @@ msgstr "Неверный пароль или код восстановления
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "Неверный 2FA или код восстановления"
 msgstr "Неверный 2FA или код восстановления"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr "IP"
 msgstr "IP"
 
 
@@ -1285,6 +1312,12 @@ msgstr "Издатель: %{issuer}"
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "Jwt секрет"
 msgstr "Jwt секрет"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1428,7 +1461,7 @@ msgstr "Пользователи"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "Управление сертификатом"
 msgstr "Управление сертификатом"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr "Максимальное количество попыток"
 msgstr "Максимальное количество попыток"
 
 
@@ -1617,7 +1650,7 @@ msgstr "Nginx успешно перезапущен"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1736,7 +1769,7 @@ msgstr "OpenAI"
 msgid "Or"
 msgid "Or"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -1990,17 +2023,19 @@ msgid "Recovered Successfully"
 msgstr "Восстановлено успешно"
 msgstr "Восстановлено успешно"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr "Восстановление"
 msgstr "Восстановление"
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
+#: src/views/preference/components/RecoveryCodes.vue:68
+#, fuzzy
+msgid "Recovery Codes"
 msgstr "Код восстановления"
 msgstr "Код восстановления"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
-msgstr "Код восстановления:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
+msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
@@ -2067,7 +2102,7 @@ msgstr "Перезагружается"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "Перезагружается nginx"
 msgstr "Перезагружается nginx"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr "Удалить"
 msgstr "Удалить"
 
 
@@ -2081,7 +2116,7 @@ msgstr "Удалить сайт: %{site_name}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "Продублированно %{conf_name} в %{node_name}"
 msgstr "Продублированно %{conf_name} в %{node_name}"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 msgid "Remove successfully"
 msgid "Remove successfully"
 msgstr "Удалено успешно"
 msgstr "Удалено успешно"
@@ -2173,7 +2208,7 @@ msgstr "Запрос с неправильными параметрами"
 msgid "Reset"
 msgid "Reset"
 msgstr "Сброс"
 msgstr "Сброс"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "Сброс 2FA"
 msgstr "Сброс 2FA"
 
 
@@ -2185,15 +2220,15 @@ msgstr "Перезапуск"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "Перезапускается"
 msgstr "Перезапускается"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2266,7 +2301,7 @@ msgstr "Сохранено успешно"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "Успешно сохранено"
 msgstr "Успешно сохранено"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr ""
 msgstr ""
 "Отсканируйте QR-код с помощью мобильного телефона, чтобы добавить учетную "
 "Отсканируйте QR-код с помощью мобильного телефона, чтобы добавить учетную "
@@ -2276,7 +2311,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr "SDK"
 msgstr "SDK"
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr ""
 msgstr ""
 
 
@@ -2646,13 +2681,6 @@ msgstr ""
 "Имя сервера должно содержать только буквы, юникод, цифры, дефисы, тире и "
 "Имя сервера должно содержать только буквы, юникод, цифры, дефисы, тире и "
 "точки."
 "точки."
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-"Код восстановления отображается только один раз, пожалуйста, сохраните его в "
-"безопасном месте."
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2685,6 +2713,13 @@ msgstr "URL недействителен."
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "Имя пользователя или пароль неверны"
 msgstr "Имя пользователя или пароль неверны"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "Этот элемент автосертификата недействителен, удалите его.."
 msgstr "Этот элемент автосертификата недействителен, удалите его.."
@@ -2733,11 +2768,11 @@ msgstr ""
 "Это обновит или переустановит интерфейс Nginx на %{nodeNames} до версии "
 "Это обновит или переустановит интерфейс Nginx на %{nodeNames} до версии "
 "%{version}."
 "%{version}."
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2747,7 +2782,7 @@ msgstr "Советы"
 msgid "Title"
 msgid "Title"
 msgstr "Заголовок"
 msgstr "Заголовок"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2791,11 +2826,11 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2923,6 +2958,16 @@ msgstr "Подробно"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Простой режим"
 msgstr "Простой режим"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "Код восстановления"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "Просмотр"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2946,7 +2991,7 @@ msgstr ""
 "Мы удалим конфигурацию HTTPChallenge из этого файла и перезагрузим Nginx. Вы "
 "Мы удалим конфигурацию HTTPChallenge из этого файла и перезагрузим Nginx. Вы "
 "уверены, что хотите продолжить?"
 "уверены, что хотите продолжить?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -2967,6 +3012,12 @@ msgid ""
 "site category and the nodes selected below will be synchronized."
 "site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -2980,7 +3031,7 @@ msgstr "Запись закрытого ключа сертификата на 
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "Запись сертификата на диск"
 msgstr "Запись сертификата на диск"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -3001,11 +3052,47 @@ msgid ""
 "passkey."
 "passkey."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 #, fuzzy
 #, fuzzy
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Добавить ключ доступа"
 msgstr "Добавить ключ доступа"
 
 
+#~ msgid ""
+#~ "If you lose your mobile phone, you can use the recovery code to reset "
+#~ "your 2FA."
+#~ msgstr ""
+#~ "Если вы потеряете свой мобильный телефон, вы можете использовать код "
+#~ "восстановления для сброса 2FA."
+
+#~ msgid "Recovery Code:"
+#~ msgstr "Код восстановления:"
+
+#~ msgid ""
+#~ "The recovery code is only displayed once, please save it in a safe place."
+#~ msgstr ""
+#~ "Код восстановления отображается только один раз, пожалуйста, сохраните "
+#~ "его в безопасном месте."
+
 #~ msgid "Directory"
 #~ msgid "Directory"
 #~ msgstr "Каталог"
 #~ msgstr "Каталог"
 
 

+ 144 - 58
app/src/language/tr_TR/app.po

@@ -16,7 +16,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr "İki aşamalı kimlik doğrulaması(2FA)"
 msgstr "İki aşamalı kimlik doğrulaması(2FA)"
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr "2FA Ayarları"
 msgstr "2FA Ayarları"
 
 
@@ -39,7 +39,7 @@ msgstr "ACME Kullanıcısı"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -140,7 +140,7 @@ msgstr "Başarıyla kopyalandı"
 msgid "Arch"
 msgid "Arch"
 msgstr "Mimari"
 msgstr "Mimari"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "Bu yasaklı IP'yi hemen sileceğinizden emin misiniz?"
 msgstr "Bu yasaklı IP'yi hemen sileceğinizden emin misiniz?"
 
 
@@ -148,6 +148,16 @@ msgstr "Bu yasaklı IP'yi hemen sileceğinizden emin misiniz?"
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "Bu geçiş anahtarını hemen silmek istediğinizden emin misiniz?"
 msgstr "Bu geçiş anahtarını hemen silmek istediğinizden emin misiniz?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "Bu öğeyi kurtarmak istediğinizden emin misiniz?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "Silmek istediğine emin misin?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -204,7 +214,7 @@ msgstr "Asistan"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr "Girişimler"
 msgstr "Girişimler"
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr "Girişimler"
 msgstr "Girişimler"
 
 
@@ -216,7 +226,7 @@ msgstr "Kimlik Doğrulama"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr "Geçiş anahtarıyla kimlik doğrulama"
 msgstr "Geçiş anahtarıyla kimlik doğrulama"
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr "Kimlik Doğrulama Ayarları"
 msgstr "Kimlik Doğrulama Ayarları"
 
 
@@ -253,15 +263,15 @@ msgstr "Ana Sayfaya Dön"
 msgid "Back to list"
 msgid "Back to list"
 msgstr "Listeye geri dön"
 msgstr "Listeye geri dön"
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "Yasaklama Eşiği Süresi (Dakika)"
 msgstr "Yasaklama Eşiği Süresi (Dakika)"
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr "Yasaklı IP'ler"
 msgstr "Yasaklı IP'ler"
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr "Şu Zamana Kadar Yasaklı"
 msgstr "Şu Zamana Kadar Yasaklı"
 
 
@@ -449,7 +459,7 @@ msgstr "Temizle"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "Başarıyla temizlendi"
 msgstr "Başarıyla temizlendi"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -500,6 +510,7 @@ msgstr "İçerik"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr "Kopyalandı"
 msgstr "Kopyalandı"
 
 
@@ -507,6 +518,11 @@ msgstr "Kopyalandı"
 msgid "Copy"
 msgid "Copy"
 msgstr "Kopya"
 msgstr "Kopya"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+#, fuzzy
+msgid "Copy Codes"
+msgstr "Kurtarma Kodu"
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Çekirdek Yükseltme"
 msgstr "Çekirdek Yükseltme"
@@ -557,11 +573,11 @@ msgstr "Kimlik bilgisi"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Kimlik bilgileri"
 msgstr "Kimlik bilgileri"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr "Mevcut hesap için TOTP etkinleştirildi."
 msgstr "Mevcut hesap için TOTP etkinleştirildi."
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr "Mevcut hesap için TOTP etkin değil."
 msgstr "Mevcut hesap için TOTP etkin değil."
 
 
@@ -909,7 +925,7 @@ msgstr ""
 "%{conf_name} yapılandırmasını %{node_name} düğümünde etkinleştirme başarılı "
 "%{conf_name} yapılandırmasını %{node_name} düğümünde etkinleştirme başarılı "
 "oldu"
 "oldu"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "2FA'yı başarıyla etkinleştirildi"
 msgstr "2FA'yı başarıyla etkinleştirildi"
 
 
@@ -953,7 +969,7 @@ msgstr "Başarıyla etkinleştirildi"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "TLS'yi Etkinleştir"
 msgstr "TLS'yi Etkinleştir"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "TOTP'yi Etkinleştir"
 msgstr "TOTP'yi Etkinleştir"
 
 
@@ -1078,6 +1094,10 @@ msgstr "Filtre"
 msgid "Finished"
 msgid "Finished"
 msgstr "Bitmiş"
 msgstr "Bitmiş"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1115,6 +1135,22 @@ msgstr "Genel Sertifika"
 msgid "Generate"
 msgid "Generate"
 msgstr "Oluştur"
 msgstr "Oluştur"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate New Recovery Codes"
+msgstr "Kurtarma Kodu"
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "Kurtarma Kodu"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "Başarıyla Kurtarıldı"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Hesap kaydı için özel anahtar oluşturuluyor"
 msgstr "Hesap kaydı için özel anahtar oluşturuluyor"
@@ -1163,7 +1199,7 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Boş bırakılırsa, varsayılan CA Dir kullanılır."
 msgstr "Boş bırakılırsa, varsayılan CA Dir kullanılır."
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
@@ -1172,14 +1208,6 @@ msgstr ""
 "yasaklama eşiği dakikaları içinde maksimum deneme sayısına ulaşırsa, IP "
 "yasaklama eşiği dakikaları içinde maksimum deneme sayısına ulaşırsa, IP "
 "adresi belirli bir süre için yasaklanacaktır."
 "adresi belirli bir süre için yasaklanacaktır."
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-"Cep telefonunuzu kaybederseniz, 2FA'nızı sıfırlamak için kurtarma kodunu "
-"kullanabilirsiniz."
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1215,12 +1243,11 @@ msgstr "İlk çekirdek yükseltici hatası"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "Çekirdek yükseltici başlatılıyor"
 msgstr "Çekirdek yükseltici başlatılıyor"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr "Uygulamadan kodu girin:"
 msgstr "Uygulamadan kodu girin:"
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr "Kurtarma kodunu girin:"
 msgstr "Kurtarma kodunu girin:"
 
 
@@ -1263,7 +1290,7 @@ msgstr "Geçersiz parola veya kurtarma kodu"
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "Geçersiz 2FA veya kurtarma kodu"
 msgstr "Geçersiz 2FA veya kurtarma kodu"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr "IP"
 msgstr "IP"
 
 
@@ -1287,6 +1314,12 @@ msgstr "Düzenleyen: %{issuer}"
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "Jwt Secret"
 msgstr "Jwt Secret"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1435,7 +1468,7 @@ msgstr "Kullanıcıları Yönet"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "Yönetilen Sertifika"
 msgstr "Yönetilen Sertifika"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 #, fuzzy
 #, fuzzy
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr "Maksimum Deneme"
 msgstr "Maksimum Deneme"
@@ -1654,7 +1687,7 @@ msgstr "Nginx başarıyla yeniden başlatıldı"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1791,7 +1824,7 @@ msgstr "OpenAI"
 msgid "Or"
 msgid "Or"
 msgstr "Veya"
 msgstr "Veya"
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -2086,20 +2119,20 @@ msgid "Recovered Successfully"
 msgstr "Başarıyla Kurtarıldı"
 msgstr "Başarıyla Kurtarıldı"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 #, fuzzy
 #, fuzzy
 msgid "Recovery"
 msgid "Recovery"
 msgstr "Kurtarma"
 msgstr "Kurtarma"
 
 
-#: src/views/preference/components/TOTP.vue:80
+#: src/views/preference/components/RecoveryCodes.vue:68
 #, fuzzy
 #, fuzzy
-msgid "Recovery Code"
+msgid "Recovery Codes"
 msgstr "Kurtarma Kodu"
 msgstr "Kurtarma Kodu"
 
 
-#: src/views/preference/components/TOTP.vue:89
-#, fuzzy
-msgid "Recovery Code:"
-msgstr "Kurtarma Kodu:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
+msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
 #, fuzzy
 #, fuzzy
@@ -2181,7 +2214,7 @@ msgstr "Yeniden Yükleme"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "Nginx'i yeniden yükleme"
 msgstr "Nginx'i yeniden yükleme"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 #, fuzzy
 #, fuzzy
 msgid "Remove"
 msgid "Remove"
 msgstr "Kaldır"
 msgstr "Kaldır"
@@ -2196,7 +2229,7 @@ msgstr "Siteyi sil: %{site_name}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "%{conf_name} başarıyla %{node_name} düğümüne kopyalandı"
 msgstr "%{conf_name} başarıyla %{node_name} düğümüne kopyalandı"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 #, fuzzy
 #, fuzzy
 msgid "Remove successfully"
 msgid "Remove successfully"
@@ -2305,7 +2338,7 @@ msgstr "Yanlış parametrelerle talep edildi"
 msgid "Reset"
 msgid "Reset"
 msgstr "Sıfırla"
 msgstr "Sıfırla"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 #, fuzzy
 #, fuzzy
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "2FA'yı Sıfırla"
 msgstr "2FA'yı Sıfırla"
@@ -2320,15 +2353,15 @@ msgstr "Yeniden başlat"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "Yeniden Başlatma"
 msgstr "Yeniden Başlatma"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2408,7 +2441,7 @@ msgstr "Başarıyla kaydedin"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "Başarıyla Kaydedildi"
 msgstr "Başarıyla Kaydedildi"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 #, fuzzy
 #, fuzzy
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr "Hesabı uygulamaya eklemek için QR kodunu cep telefonunuzla tarayın."
 msgstr "Hesabı uygulamaya eklemek için QR kodunu cep telefonunuzla tarayın."
@@ -2418,7 +2451,7 @@ msgstr "Hesabı uygulamaya eklemek için QR kodunu cep telefonunuzla tarayın."
 msgid "SDK"
 msgid "SDK"
 msgstr "SDK"
 msgstr "SDK"
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 #, fuzzy
 #, fuzzy
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr "Sır kopyalandı"
 msgstr "Sır kopyalandı"
@@ -2834,14 +2867,6 @@ msgstr ""
 "Sunucu adı yalnızca harf, unicode, sayı, kısa çizgi, tire ve nokta "
 "Sunucu adı yalnızca harf, unicode, sayı, kısa çizgi, tire ve nokta "
 "içermelidir."
 "içermelidir."
 
 
-#: src/views/preference/components/TOTP.vue:88
-#, fuzzy
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-"Kurtarma kodu yalnızca bir kez görüntülenir, lütfen güvenli bir yere "
-"kaydedin."
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 #, fuzzy
 #, fuzzy
 msgid ""
 msgid ""
@@ -2879,6 +2904,13 @@ msgstr "URL geçersiz."
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "Kullanıcı adı veya şifre yanlış"
 msgstr "Kullanıcı adı veya şifre yanlış"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 #, fuzzy
 #, fuzzy
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
@@ -2931,11 +2963,11 @@ msgstr ""
 "Bu, %{nodeNames} üzerindeki Nginx kullanıcı arayüzünü %{version}'e "
 "Bu, %{nodeNames} üzerindeki Nginx kullanıcı arayüzünü %{version}'e "
 "yükseltecek veya yeniden yükleyecektir."
 "yükseltecek veya yeniden yükleyecektir."
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 #, fuzzy
 #, fuzzy
@@ -2947,7 +2979,7 @@ msgstr "İpuçları"
 msgid "Title"
 msgid "Title"
 msgstr "Başlık"
 msgstr "Başlık"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 #, fuzzy
 #, fuzzy
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
@@ -3002,12 +3034,12 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 #, fuzzy
 #, fuzzy
 msgid "TOTP"
 msgid "TOTP"
 msgstr "TOTP"
 msgstr "TOTP"
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 #, fuzzy
 #, fuzzy
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
@@ -3158,6 +3190,16 @@ msgstr "Detayları göster"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Görünüm Modu"
 msgstr "Görünüm Modu"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "Kurtarma Kodu"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "Görünüm"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -3184,7 +3226,7 @@ msgstr ""
 "HTTPChallenge yapılandırmasını bu dosyadan kaldıracağız ve Nginx'i yeniden "
 "HTTPChallenge yapılandırmasını bu dosyadan kaldıracağız ve Nginx'i yeniden "
 "yükleyeceğiz. Devam etmek istediğinizden emin misiniz?"
 "yükleyeceğiz. Devam etmek istediğinizden emin misiniz?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -3209,6 +3251,12 @@ msgid ""
 "site category and the nodes selected below will be synchronized."
 "site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 #, fuzzy
 #, fuzzy
@@ -3225,7 +3273,7 @@ msgstr "Sertifika özel anahtarını diske yazma"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "Sertifikayı diske yazma"
 msgstr "Sertifikayı diske yazma"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -3252,11 +3300,49 @@ msgstr ""
 "Webauthn ayarlarını yapılandırmadınız, bu nedenle bir geçiş anahtarı "
 "Webauthn ayarlarını yapılandırmadınız, bu nedenle bir geçiş anahtarı "
 "ekleyemezsiniz."
 "ekleyemezsiniz."
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 #, fuzzy
 #, fuzzy
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Geçiş anahtarlarınız"
 msgstr "Geçiş anahtarlarınız"
 
 
+#~ msgid ""
+#~ "If you lose your mobile phone, you can use the recovery code to reset "
+#~ "your 2FA."
+#~ msgstr ""
+#~ "Cep telefonunuzu kaybederseniz, 2FA'nızı sıfırlamak için kurtarma kodunu "
+#~ "kullanabilirsiniz."
+
+#, fuzzy
+#~ msgid "Recovery Code:"
+#~ msgstr "Kurtarma Kodu:"
+
+#, fuzzy
+#~ msgid ""
+#~ "The recovery code is only displayed once, please save it in a safe place."
+#~ msgstr ""
+#~ "Kurtarma kodu yalnızca bir kez görüntülenir, lütfen güvenli bir yere "
+#~ "kaydedin."
+
 #~ msgid "Can't scan? Use text key binding"
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "Tarayamıyor musunuz? Metin anahtar bağlamasını kullanın"
 #~ msgstr "Tarayamıyor musunuz? Metin anahtar bağlamasını kullanın"
 
 

+ 125 - 52
app/src/language/vi_VN/app.po

@@ -13,7 +13,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr ""
 msgstr ""
 
 
@@ -37,7 +37,7 @@ msgstr "Người dùng"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -143,7 +143,7 @@ msgstr "Nhân bản thành công"
 msgid "Arch"
 msgid "Arch"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 #, fuzzy
 #, fuzzy
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "Bạn chắc chắn muốn xóa nó "
 msgstr "Bạn chắc chắn muốn xóa nó "
@@ -153,6 +153,16 @@ msgstr "Bạn chắc chắn muốn xóa nó "
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "Bạn chắc chắn muốn xóa nó "
 msgstr "Bạn chắc chắn muốn xóa nó "
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "Bạn chắc chắn muốn xoá directive này ?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "Bạn chắc chắn muốn xóa nó "
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -216,7 +226,7 @@ msgstr "Trợ lý"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -229,7 +239,7 @@ msgstr "Tác giả"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr ""
 msgstr ""
 
 
@@ -267,15 +277,15 @@ msgstr "Quay lại"
 msgid "Back to list"
 msgid "Back to list"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr ""
 msgstr ""
 
 
@@ -474,7 +484,7 @@ msgstr "Xoá"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "Đã xóa thành công"
 msgstr "Đã xóa thành công"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -527,6 +537,7 @@ msgstr "Nội dung"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr ""
 msgstr ""
 
 
@@ -534,6 +545,10 @@ msgstr ""
 msgid "Copy"
 msgid "Copy"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+msgid "Copy Codes"
+msgstr ""
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "Cập nhật core"
 msgstr "Cập nhật core"
@@ -588,11 +603,11 @@ msgstr "Chứng chỉ"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "Chứng chỉ"
 msgstr "Chứng chỉ"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr ""
 msgstr ""
 
 
@@ -946,7 +961,7 @@ msgstr "Không thể bật %{conf_name} trên %{node_name}"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "Đã bật %{conf_name} trên %{node_name}"
 msgstr "Đã bật %{conf_name} trên %{node_name}"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 #, fuzzy
 #, fuzzy
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Đã bật"
 msgstr "Đã bật"
@@ -988,7 +1003,7 @@ msgstr "Đã bật"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "Bật TLS"
 msgstr "Bật TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 #, fuzzy
 #, fuzzy
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "Bật TLS"
 msgstr "Bật TLS"
@@ -1118,6 +1133,10 @@ msgstr "Lọc"
 msgid "Finished"
 msgid "Finished"
 msgstr "Đã hoàn thành"
 msgstr "Đã hoàn thành"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1157,6 +1176,21 @@ msgstr "Chứng chỉ chung"
 msgid "Generate"
 msgid "Generate"
 msgstr "Tạo"
 msgstr "Tạo"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate New Recovery Codes"
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "Hợp lệ"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "Xoá thành công"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "Tạo khóa riêng để đăng ký tài khoản"
 msgstr "Tạo khóa riêng để đăng ký tài khoản"
@@ -1207,18 +1241,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr ""
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1252,12 +1280,11 @@ msgstr "Không thể khởi tạo trình nâng cấp"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "Đang khởi tạo trình nâng cấp"
 msgstr "Đang khởi tạo trình nâng cấp"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr ""
 msgstr ""
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr ""
 msgstr ""
 
 
@@ -1304,7 +1331,7 @@ msgstr ""
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "Hợp lệ"
 msgstr "Hợp lệ"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr ""
 msgstr ""
 
 
@@ -1331,6 +1358,12 @@ msgstr ""
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #, fuzzy
 #, fuzzy
@@ -1479,7 +1512,7 @@ msgstr "Người dùng"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "Thay đổi chứng chỉ"
 msgstr "Thay đổi chứng chỉ"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr ""
 msgstr ""
 
 
@@ -1679,7 +1712,7 @@ msgstr "Restart Nginx thành công"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1799,7 +1832,7 @@ msgstr ""
 msgid "Or"
 msgid "Or"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -2047,16 +2080,18 @@ msgid "Recovered Successfully"
 msgstr "Xoá thành công"
 msgstr "Xoá thành công"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
-msgstr ""
+#: src/views/preference/components/RecoveryCodes.vue:68
+#, fuzzy
+msgid "Recovery Codes"
+msgstr "Hợp lệ"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
 msgstr ""
 msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
@@ -2129,7 +2164,7 @@ msgstr "Đang tải lại"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "Tải lại nginx"
 msgstr "Tải lại nginx"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr ""
 msgstr ""
 
 
@@ -2143,7 +2178,7 @@ msgstr "Xoá trang web: %{site_name}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "Nhân bản %{conf_name} thành %{node_name} thành công"
 msgstr "Nhân bản %{conf_name} thành %{node_name} thành công"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 #, fuzzy
 #, fuzzy
 msgid "Remove successfully"
 msgid "Remove successfully"
@@ -2245,7 +2280,7 @@ msgstr "Yêu cầu có chứa tham số sai"
 msgid "Reset"
 msgid "Reset"
 msgstr "Đặt lại"
 msgstr "Đặt lại"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 #, fuzzy
 #, fuzzy
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "Đặt lại"
 msgstr "Đặt lại"
@@ -2258,15 +2293,15 @@ msgstr "Khởi động lại"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "Đang khởi động lại"
 msgstr "Đang khởi động lại"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2339,7 +2374,7 @@ msgstr "Lưu thành công"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "Lưu thành công"
 msgstr "Lưu thành công"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr ""
 msgstr ""
 
 
@@ -2347,7 +2382,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr ""
 msgstr ""
 
 
@@ -2713,11 +2748,6 @@ msgid ""
 "hyphens, dashes, colons, and dots."
 "hyphens, dashes, colons, and dots."
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr ""
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2747,6 +2777,13 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "Tên người dùng hoặc mật khẩu không chính xác"
 msgstr "Tên người dùng hoặc mật khẩu không chính xác"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "Mục Chứng chỉ tự động này không hợp lệ, vui lòng xóa nó"
 msgstr "Mục Chứng chỉ tự động này không hợp lệ, vui lòng xóa nó"
@@ -2791,11 +2828,11 @@ msgid ""
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr "Nhân bản %{conf_name} thành %{node_name} thành công"
 msgstr "Nhân bản %{conf_name} thành %{node_name} thành công"
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2805,7 +2842,7 @@ msgstr ""
 msgid "Title"
 msgid "Title"
 msgstr "Tiêu đề"
 msgstr "Tiêu đề"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2847,11 +2884,11 @@ msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 msgstr[1] ""
 msgstr[1] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2984,6 +3021,16 @@ msgstr "Chi tiết"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "Cơ bản"
 msgstr "Cơ bản"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "Hợp lệ"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "Xem"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -3007,7 +3054,7 @@ msgstr ""
 "Chúng tôi sẽ xóa cấu hình HTTPChallenge khỏi tệp này và tải lại Nginx. Bạn "
 "Chúng tôi sẽ xóa cấu hình HTTPChallenge khỏi tệp này và tải lại Nginx. Bạn "
 "có muốn tiếp tục không?"
 "có muốn tiếp tục không?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -3028,6 +3075,12 @@ msgid ""
 "site category and the nodes selected below will be synchronized."
 "site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -3041,7 +3094,7 @@ msgstr "Ghi Private Key vào disk"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "Ghi chứng chỉ vào disk"
 msgstr "Ghi chứng chỉ vào disk"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -3062,6 +3115,26 @@ msgid ""
 "passkey."
 "passkey."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr ""
 msgstr ""

+ 135 - 55
app/src/language/zh_CN/app.po

@@ -17,7 +17,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr "2FA"
 msgstr "2FA"
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr "2FA 设置"
 msgstr "2FA 设置"
 
 
@@ -40,7 +40,7 @@ msgstr "ACME 用户"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -138,7 +138,7 @@ msgstr "批量操作应用成功"
 msgid "Arch"
 msgid "Arch"
 msgstr "架构"
 msgstr "架构"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "您确定要立即删除这个被禁用的 IP 吗?"
 msgstr "您确定要立即删除这个被禁用的 IP 吗?"
 
 
@@ -146,6 +146,14 @@ msgstr "您确定要立即删除这个被禁用的 IP 吗?"
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "您确定要立即删除这个 Passkey 吗?"
 msgstr "您确定要立即删除这个 Passkey 吗?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+msgid "Are you sure to generate new recovery codes?"
+msgstr "您确定要生成新的恢复代码吗?"
+
+#: src/views/preference/components/TOTP.vue:85
+msgid "Are you sure to reset 2FA?"
+msgstr "您确定要重设双重身份验证?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
 msgstr "您确定要应用于所有选定的对象吗?"
 msgstr "您确定要应用于所有选定的对象吗?"
@@ -200,7 +208,7 @@ msgstr "助手"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr "尝试修复"
 msgstr "尝试修复"
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr "尝试次数"
 msgstr "尝试次数"
 
 
@@ -212,7 +220,7 @@ msgstr "认证"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr "通过 Passkey 认证"
 msgstr "通过 Passkey 认证"
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr "认证设置"
 msgstr "认证设置"
 
 
@@ -249,15 +257,15 @@ msgstr "返回首页"
 msgid "Back to list"
 msgid "Back to list"
 msgstr "返回列表"
 msgstr "返回列表"
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "禁止阈值(分钟)"
 msgstr "禁止阈值(分钟)"
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr "禁止 IP 列表"
 msgstr "禁止 IP 列表"
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr "禁用至"
 msgstr "禁用至"
 
 
@@ -439,7 +447,7 @@ msgstr "清空"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "清除成功"
 msgstr "清除成功"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr "点击复制"
 msgstr "点击复制"
 
 
@@ -490,6 +498,7 @@ msgstr "内容"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr "已拷贝"
 msgstr "已拷贝"
 
 
@@ -497,6 +506,10 @@ msgstr "已拷贝"
 msgid "Copy"
 msgid "Copy"
 msgstr "拷贝"
 msgstr "拷贝"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+msgid "Copy Codes"
+msgstr "复制代码"
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "核心升级"
 msgstr "核心升级"
@@ -547,11 +560,11 @@ msgstr "DNS 凭证"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "凭证"
 msgstr "凭证"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr "当前账户已启用 TOTP 验证。"
 msgstr "当前账户已启用 TOTP 验证。"
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr "当前用户未启用 TOTP 验证。"
 msgstr "当前用户未启用 TOTP 验证。"
 
 
@@ -876,7 +889,7 @@ msgstr "在%{node_name}中启用%{conf_name}失败"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "成功启用%{node_name}中的%{conf_name}"
 msgstr "成功启用%{node_name}中的%{conf_name}"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "二步验证启用成功"
 msgstr "二步验证启用成功"
 
 
@@ -912,7 +925,7 @@ msgstr "启用成功"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "启用 TLS"
 msgstr "启用 TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "启用 TOTP"
 msgstr "启用 TOTP"
 
 
@@ -1035,6 +1048,10 @@ msgstr "过滤"
 msgid "Finished"
 msgid "Finished"
 msgstr "完成"
 msgstr "完成"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr "首次查看"
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1070,6 +1087,19 @@ msgstr "普通证书"
 msgid "Generate"
 msgid "Generate"
 msgstr "生成"
 msgstr "生成"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate New Recovery Codes"
+msgstr "生成新的恢复代码"
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+msgid "Generate Recovery Codes"
+msgstr "生成恢复代码"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+msgid "Generate recovery codes successfully"
+msgstr "成功生成恢复代码"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "正在生成私钥用于注册账户"
 msgstr "正在生成私钥用于注册账户"
@@ -1118,7 +1148,7 @@ msgstr "ICP备案号"
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "如果留空,则使用默认 CA Dir。"
 msgstr "如果留空,则使用默认 CA Dir。"
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
@@ -1126,12 +1156,6 @@ msgstr ""
 "如果某个 IP 的登录失败次数达到禁用阈值分钟内的最大尝试次数,该 IP 将被禁止登"
 "如果某个 IP 的登录失败次数达到禁用阈值分钟内的最大尝试次数,该 IP 将被禁止登"
 "录一段时间。"
 "录一段时间。"
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr "如果丢失了手机,可以使用恢复代码重置二步验证。"
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr "如果您的浏览器支持 WebAuthn Passkey,则会出现一个对话框。"
 msgstr "如果您的浏览器支持 WebAuthn Passkey,则会出现一个对话框。"
@@ -1163,12 +1187,11 @@ msgstr "初始化核心升级程序错误"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "初始化核心升级器"
 msgstr "初始化核心升级器"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr "输入应用程序中的代码:"
 msgstr "输入应用程序中的代码:"
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr "输入恢复代码:"
 msgstr "输入恢复代码:"
 
 
@@ -1209,7 +1232,7 @@ msgstr "二次验证码或恢复代码无效"
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "无效的恢复代码"
 msgstr "无效的恢复代码"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr "IP"
 msgstr "IP"
 
 
@@ -1233,6 +1256,13 @@ msgstr "颁发者:%{issuer}"
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "Jwt 密钥"
 msgstr "Jwt 密钥"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+"请像保护密码一样安全地保管您的恢复代码。我们建议使用密码管理器保存它们。"
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1372,7 +1402,7 @@ msgstr "用户管理"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "托管证书"
 msgstr "托管证书"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr "最大尝试次数"
 msgstr "最大尝试次数"
 
 
@@ -1558,7 +1588,7 @@ msgstr "Nginx 重启成功"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1673,7 +1703,7 @@ msgstr "OpenAI"
 msgid "Or"
 msgid "Or"
 msgstr "或"
 msgstr "或"
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr "或输入密钥:%{secret}"
 msgstr "或输入密钥:%{secret}"
 
 
@@ -1912,17 +1942,20 @@ msgid "Recovered Successfully"
 msgstr "恢复成功"
 msgstr "恢复成功"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr "恢复"
 msgstr "恢复"
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
+#: src/views/preference/components/RecoveryCodes.vue:68
+msgid "Recovery Codes"
 msgstr "恢复代码"
 msgstr "恢复代码"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
-msgstr "恢复代码:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
+msgstr ""
+"恢复代码用于在您无法访问双重身份验证设备时登录您的账户。每个代码只能使用一"
+"次。"
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
@@ -1987,7 +2020,7 @@ msgstr "重载中"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "正在重载 Nginx"
 msgstr "正在重载 Nginx"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr "删除"
 msgstr "删除"
 
 
@@ -1999,7 +2032,7 @@ msgstr "从 %{node} 中删除站点 %{site} 错误,响应:%{resp}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "成功从 %{node} 中删除站点 %{site}"
 msgstr "成功从 %{node} 中删除站点 %{site}"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 msgid "Remove successfully"
 msgid "Remove successfully"
 msgstr "移除成功"
 msgstr "移除成功"
@@ -2085,7 +2118,7 @@ msgstr "请求参数错误"
 msgid "Reset"
 msgid "Reset"
 msgstr "重置"
 msgstr "重置"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "重置二步验证"
 msgstr "重置二步验证"
 
 
@@ -2097,15 +2130,15 @@ msgstr "重启"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "重启中"
 msgstr "重启中"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr "依赖方显示名称"
 msgstr "依赖方显示名称"
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr "依赖方的源"
 msgstr "依赖方的源"
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr "依赖方 ID"
 msgstr "依赖方 ID"
 
 
@@ -2172,7 +2205,7 @@ msgstr "保存成功"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "保存成功"
 msgstr "保存成功"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr "用手机扫描二维码,将账户添加到应用程序中。"
 msgstr "用手机扫描二维码,将账户添加到应用程序中。"
 
 
@@ -2180,7 +2213,7 @@ msgstr "用手机扫描二维码,将账户添加到应用程序中。"
 msgid "SDK"
 msgid "SDK"
 msgstr "SDK"
 msgstr "SDK"
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr "密钥已复制"
 msgstr "密钥已复制"
 
 
@@ -2523,11 +2556,6 @@ msgid ""
 "hyphens, dashes, colons, and dots."
 "hyphens, dashes, colons, and dots."
 msgstr "公安备案号只能包含字母、单码、数字、连字符、破折号、冒号和点。"
 msgstr "公安备案号只能包含字母、单码、数字、连字符、破折号、冒号和点。"
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr "恢复密码只会显示一次,请妥善保存。"
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2557,6 +2585,15 @@ msgstr "URL 无效."
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "用户名或密码错误"
 msgstr "用户名或密码错误"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+"这些代码是在您丢失密码和双重身份验证方式时,访问账户的最后手段。如果找不到这"
+"些代码,您将无法再访问您的账户。"
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "这个证书自动续期项目是无效的,请删除。"
 msgstr "这个证书自动续期项目是无效的,请删除。"
@@ -2598,11 +2635,11 @@ msgid ""
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr "将 %{nodeNames} 上的 Nginx UI 升级或重新安装到 %{version} 版本。"
 msgstr "将 %{nodeNames} 上的 Nginx UI 升级或重新安装到 %{version} 版本。"
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr "限流"
 msgstr "限流"
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2612,7 +2649,7 @@ msgstr "提示"
 msgid "Title"
 msgid "Title"
 msgstr "标题"
 msgstr "标题"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2656,11 +2693,11 @@ msgid "Total %{total} item"
 msgid_plural "Total %{total} items"
 msgid_plural "Total %{total} items"
 msgstr[0] "共 %{total} 个项目"
 msgstr[0] "共 %{total} 个项目"
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr "TOTP"
 msgstr "TOTP"
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2783,6 +2820,14 @@ msgstr "查看详情"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "预览模式"
 msgstr "预览模式"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+msgid "View Recovery Codes"
+msgstr "查看恢复代码"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "Viewed"
+msgstr "已查看"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2803,7 +2848,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "我们将从这个文件中删除HTTPChallenge的配置,并重新加载Nginx。你确定要继续吗?"
 "我们将从这个文件中删除HTTPChallenge的配置,并重新加载Nginx。你确定要继续吗?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr "Webauthn"
 msgstr "Webauthn"
 
 
@@ -2828,6 +2873,12 @@ msgstr ""
 "启用/禁用、删除或保存此站点时,网站分类中设置的节点和下面选择的节点将同步执行"
 "启用/禁用、删除或保存此站点时,网站分类中设置的节点和下面选择的节点将同步执行"
 "操作。"
 "操作。"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr "当您生成新的恢复代码时,必须下载或打印新的代码。"
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -2841,7 +2892,7 @@ msgstr "正在将证书私钥写入磁盘"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "正在将证书写入磁盘"
 msgstr "正在将证书写入磁盘"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -2862,10 +2913,42 @@ msgid ""
 "passkey."
 "passkey."
 msgstr "您尚未配置 Webauthn 的设置,因此无法添加 Passkey。"
 msgstr "您尚未配置 Webauthn 的设置,因此无法添加 Passkey。"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr "您尚未启用双重身份验证。请启用双重身份验证以生成恢复代码。"
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr "您尚未生成恢复代码。"
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr "您的当前恢复代码可能已过期且不安全。请尽快生成新的恢复代码以确保安全。"
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr "您的旧代码将不再有效。"
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "你的 Passkeys"
 msgstr "你的 Passkeys"
 
 
+#~ msgid ""
+#~ "If you lose your mobile phone, you can use the recovery code to reset "
+#~ "your 2FA."
+#~ msgstr "如果丢失了手机,可以使用恢复代码重置二步验证。"
+
+#~ msgid "Recovery Code:"
+#~ msgstr "恢复代码:"
+
+#~ msgid ""
+#~ "The recovery code is only displayed once, please save it in a safe place."
+#~ msgstr "恢复密码只会显示一次,请妥善保存。"
+
 #~ msgid "Can't scan? Use text key binding"
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "无法扫描?使用文本密钥绑定"
 #~ msgstr "无法扫描?使用文本密钥绑定"
 
 
@@ -3047,9 +3130,6 @@ msgstr "你的 Passkeys"
 #~ msgid "404 Not Found"
 #~ msgid "404 Not Found"
 #~ msgstr "404 未找到页面"
 #~ msgstr "404 未找到页面"
 
 
-#~ msgid "Are you sure you want to restore?"
-#~ msgstr "您确定要反删除?"
-
 #~ msgid "Destroy"
 #~ msgid "Destroy"
 #~ msgstr "删除"
 #~ msgstr "删除"
 
 

+ 139 - 55
app/src/language/zh_TW/app.po

@@ -22,7 +22,7 @@ msgstr ""
 msgid "2FA"
 msgid "2FA"
 msgstr "多重要素驗證"
 msgstr "多重要素驗證"
 
 
-#: src/views/preference/AuthSettings.vue:55
+#: src/views/preference/AuthSettings.vue:70
 msgid "2FA Settings"
 msgid "2FA Settings"
 msgstr "多重要素驗證設定"
 msgstr "多重要素驗證設定"
 
 
@@ -45,7 +45,7 @@ msgstr "ACME 用戶"
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
 #: src/views/notification/notificationColumns.tsx:65
 #: src/views/notification/notificationColumns.tsx:65
-#: src/views/preference/AuthSettings.vue:26
+#: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_category/columns.ts:29
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/user/userColumns.tsx:60
 #: src/views/user/userColumns.tsx:60
@@ -145,7 +145,7 @@ msgstr "複製成功"
 msgid "Arch"
 msgid "Arch"
 msgstr "架構"
 msgstr "架構"
 
 
-#: src/views/preference/AuthSettings.vue:134
+#: src/views/preference/AuthSettings.vue:162
 msgid "Are you sure to delete this banned IP immediately?"
 msgid "Are you sure to delete this banned IP immediately?"
 msgstr "您確定要刪除這個被禁用的 IP 嗎?"
 msgstr "您確定要刪除這個被禁用的 IP 嗎?"
 
 
@@ -153,6 +153,16 @@ msgstr "您確定要刪除這個被禁用的 IP 嗎?"
 msgid "Are you sure to delete this passkey immediately?"
 msgid "Are you sure to delete this passkey immediately?"
 msgstr "您確定要刪除這個被禁用的通行密鑰嗎?"
 msgstr "您確定要刪除這個被禁用的通行密鑰嗎?"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:154
+#, fuzzy
+msgid "Are you sure to generate new recovery codes?"
+msgstr "您確定要恢復此項目嗎?"
+
+#: src/views/preference/components/TOTP.vue:85
+#, fuzzy
+msgid "Are you sure to reset 2FA?"
+msgstr "您確定要恢復?"
+
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:96
 #, fuzzy
 #, fuzzy
 msgid "Are you sure you want to apply to all selected?"
 msgid "Are you sure you want to apply to all selected?"
@@ -209,7 +219,7 @@ msgstr "助理"
 msgid "Attempt to fix"
 msgid "Attempt to fix"
 msgstr "嘗試次數"
 msgstr "嘗試次數"
 
 
-#: src/views/preference/AuthSettings.vue:17
+#: src/views/preference/AuthSettings.vue:21
 msgid "Attempts"
 msgid "Attempts"
 msgstr "嘗試次數"
 msgstr "嘗試次數"
 
 
@@ -221,7 +231,7 @@ msgstr "身份驗證"
 msgid "Authenticate with a passkey"
 msgid "Authenticate with a passkey"
 msgstr "使用通行密鑰認證"
 msgstr "使用通行密鑰認證"
 
 
-#: src/views/preference/AuthSettings.vue:60
+#: src/views/preference/AuthSettings.vue:88
 msgid "Authentication Settings"
 msgid "Authentication Settings"
 msgstr "認證設定"
 msgstr "認證設定"
 
 
@@ -258,15 +268,15 @@ msgstr "返回首頁"
 msgid "Back to list"
 msgid "Back to list"
 msgstr "返回列表"
 msgstr "返回列表"
 
 
-#: src/views/preference/AuthSettings.vue:101
+#: src/views/preference/AuthSettings.vue:129
 msgid "Ban Threshold Minutes"
 msgid "Ban Threshold Minutes"
 msgstr "封禁閾值分鐘數"
 msgstr "封禁閾值分鐘數"
 
 
-#: src/views/preference/AuthSettings.vue:122
+#: src/views/preference/AuthSettings.vue:150
 msgid "Banned IPs"
 msgid "Banned IPs"
 msgstr "被禁止的 IP"
 msgstr "被禁止的 IP"
 
 
-#: src/views/preference/AuthSettings.vue:20
+#: src/views/preference/AuthSettings.vue:24
 msgid "Banned Until"
 msgid "Banned Until"
 msgstr "禁止至"
 msgstr "禁止至"
 
 
@@ -452,7 +462,7 @@ msgstr "清除"
 msgid "Cleared successfully"
 msgid "Cleared successfully"
 msgstr "清除成功"
 msgstr "清除成功"
 
 
-#: src/views/preference/components/TOTP.vue:125
+#: src/views/preference/components/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
 
 
@@ -503,6 +513,7 @@ msgstr "內容"
 
 
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/SensitiveString/SensitiveString.vue:37
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
 #: src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx:150
+#: src/views/preference/components/RecoveryCodes.vue:121
 msgid "Copied"
 msgid "Copied"
 msgstr "已複製"
 msgstr "已複製"
 
 
@@ -510,6 +521,11 @@ msgstr "已複製"
 msgid "Copy"
 msgid "Copy"
 msgstr "複製"
 msgstr "複製"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:121
+#, fuzzy
+msgid "Copy Codes"
+msgstr "恢復碼"
+
 #: src/views/system/Upgrade.vue:146
 #: src/views/system/Upgrade.vue:146
 msgid "Core Upgrade"
 msgid "Core Upgrade"
 msgstr "核心升級"
 msgstr "核心升級"
@@ -560,12 +576,12 @@ msgstr "認證"
 msgid "Credentials"
 msgid "Credentials"
 msgstr "認證資訊"
 msgstr "認證資訊"
 
 
-#: src/views/preference/components/TOTP.vue:75
+#: src/views/preference/components/TOTP.vue:72
 #, fuzzy
 #, fuzzy
 msgid "Current account is enabled TOTP."
 msgid "Current account is enabled TOTP."
 msgstr "當前帳戶已啟用多因素身份驗證。"
 msgstr "當前帳戶已啟用多因素身份驗證。"
 
 
-#: src/views/preference/components/TOTP.vue:73
+#: src/views/preference/components/TOTP.vue:70
 #, fuzzy
 #, fuzzy
 msgid "Current account is not enabled TOTP."
 msgid "Current account is not enabled TOTP."
 msgstr "當前帳戶未啟用多因素身份驗證。"
 msgstr "當前帳戶未啟用多因素身份驗證。"
@@ -900,7 +916,7 @@ msgstr "在 %{node_name} 啟用 %{conf_name} 失敗"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgid "Enable %{conf_name} in %{node_name} successfully"
 msgstr "成功在 %{node_name} 啟用 %{conf_name}"
 msgstr "成功在 %{node_name} 啟用 %{conf_name}"
 
 
-#: src/views/preference/components/TOTP.vue:38
+#: src/views/preference/components/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "啟用多因素身份驗證成功"
 msgstr "啟用多因素身份驗證成功"
 
 
@@ -940,7 +956,7 @@ msgstr "啟用成功"
 msgid "Enable TLS"
 msgid "Enable TLS"
 msgstr "啟用 TLS"
 msgstr "啟用 TLS"
 
 
-#: src/views/preference/components/TOTP.vue:101
+#: src/views/preference/components/TOTP.vue:81
 #, fuzzy
 #, fuzzy
 msgid "Enable TOTP"
 msgid "Enable TOTP"
 msgstr "啟用 TLS"
 msgstr "啟用 TLS"
@@ -1066,6 +1082,10 @@ msgstr "篩選"
 msgid "Finished"
 msgid "Finished"
 msgstr "完成"
 msgstr "完成"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:70
+msgid "First View"
+msgstr ""
+
 #: src/views/preference/components/AddPasskey.vue:71
 #: src/views/preference/components/AddPasskey.vue:71
 msgid ""
 msgid ""
 "Follow the instructions in the dialog to complete the passkey registration "
 "Follow the instructions in the dialog to complete the passkey registration "
@@ -1101,6 +1121,22 @@ msgstr "普通憑證"
 msgid "Generate"
 msgid "Generate"
 msgstr "產生"
 msgstr "產生"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:138
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate New Recovery Codes"
+msgstr "恢復碼"
+
+#: src/views/preference/components/RecoveryCodes.vue:161
+#, fuzzy
+msgid "Generate Recovery Codes"
+msgstr "恢復碼"
+
+#: src/views/preference/components/RecoveryCodes.vue:32
+#, fuzzy
+msgid "Generate recovery codes successfully"
+msgstr "恢復成功"
+
 #: src/language/constants.ts:7
 #: src/language/constants.ts:7
 msgid "Generating private key for registering account"
 msgid "Generating private key for registering account"
 msgstr "產生註冊帳號的私鑰"
 msgstr "產生註冊帳號的私鑰"
@@ -1149,7 +1185,7 @@ msgstr "ICP 編號"
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "如果留空,將使用默認的 CA Dir。"
 msgstr "如果留空,將使用默認的 CA Dir。"
 
 
-#: src/views/preference/AuthSettings.vue:117
+#: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
 "ban threshold minutes, the ip will be banned for a period of time."
 "ban threshold minutes, the ip will be banned for a period of time."
@@ -1157,12 +1193,6 @@ msgstr ""
 "如果來自某個 IP 的登錄失敗次數在禁止閾值分鐘內達到最大嘗試次數,該 IP 將被禁"
 "如果來自某個 IP 的登錄失敗次數在禁止閾值分鐘內達到最大嘗試次數,該 IP 將被禁"
 "止一段時間。"
 "止一段時間。"
 
 
-#: src/views/preference/components/TOTP.vue:87
-msgid ""
-"If you lose your mobile phone, you can use the recovery code to reset your "
-"2FA."
-msgstr "如果您丟失了手機,可以使用恢復碼重置您的多重因素驗證驗證。"
-
 #: src/views/preference/components/AddPasskey.vue:70
 #: src/views/preference/components/AddPasskey.vue:70
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgid "If your browser supports WebAuthn Passkey, a dialog box will appear."
 msgstr ""
 msgstr ""
@@ -1194,12 +1224,11 @@ msgstr "初始化核心升級程式錯誤"
 msgid "Initialing core upgrader"
 msgid "Initialing core upgrader"
 msgstr "正在初始化核心升級程式"
 msgstr "正在初始化核心升級程式"
 
 
-#: src/views/preference/components/TOTP.vue:134
+#: src/views/preference/components/TOTP.vue:119
 msgid "Input the code from the app:"
 msgid "Input the code from the app:"
 msgstr "請輸入應用程式中的代碼:"
 msgstr "請輸入應用程式中的代碼:"
 
 
 #: src/components/TwoFA/Authorization.vue:82
 #: src/components/TwoFA/Authorization.vue:82
-#: src/views/preference/components/TOTP.vue:148
 msgid "Input the recovery code:"
 msgid "Input the recovery code:"
 msgstr "輸入恢復碼:"
 msgstr "輸入恢復碼:"
 
 
@@ -1242,7 +1271,7 @@ msgstr "無效的密碼或恢復碼"
 msgid "Invalid recovery code"
 msgid "Invalid recovery code"
 msgstr "無效的多重因素驗證或恢復碼"
 msgstr "無效的多重因素驗證或恢復碼"
 
 
-#: src/views/preference/AuthSettings.vue:14
+#: src/views/preference/AuthSettings.vue:18
 msgid "IP"
 msgid "IP"
 msgstr "IP"
 msgstr "IP"
 
 
@@ -1266,6 +1295,12 @@ msgstr "發行者:%{issuer}"
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr "Jwt Secret"
 msgstr "Jwt Secret"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:74
+msgid ""
+"Keep your recovery codes as safe as your password. We recommend saving them "
+"with a password manager."
+msgstr ""
+
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/certificate/CertificateList/certColumns.tsx:62
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 #: src/views/site/cert/components/AutoCertStepOne.vue:77
 msgid "Key Type"
 msgid "Key Type"
@@ -1407,7 +1442,7 @@ msgstr "管理使用者"
 msgid "Managed Certificate"
 msgid "Managed Certificate"
 msgstr "受管理的憑證"
 msgstr "受管理的憑證"
 
 
-#: src/views/preference/AuthSettings.vue:107
+#: src/views/preference/AuthSettings.vue:135
 msgid "Max Attempts"
 msgid "Max Attempts"
 msgstr "最大嘗試次數"
 msgstr "最大嘗試次數"
 
 
@@ -1598,7 +1633,7 @@ msgstr "Nginx 重啟成功"
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:524
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:538
 #: src/views/notification/Notification.vue:37
 #: src/views/notification/Notification.vue:37
-#: src/views/preference/AuthSettings.vue:136
+#: src/views/preference/AuthSettings.vue:164
 #: src/views/preference/CertSettings.vue:70
 #: src/views/preference/CertSettings.vue:70
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/site/ngx_conf/LocationEditor.vue:88
 #: src/views/site/ngx_conf/LocationEditor.vue:88
@@ -1714,7 +1749,7 @@ msgstr "OpenAI"
 msgid "Or"
 msgid "Or"
 msgstr "或"
 msgstr "或"
 
 
-#: src/views/preference/components/TOTP.vue:127
+#: src/views/preference/components/TOTP.vue:112
 msgid "Or enter the secret: %{secret}"
 msgid "Or enter the secret: %{secret}"
 msgstr ""
 msgstr ""
 
 
@@ -1961,17 +1996,19 @@ msgid "Recovered Successfully"
 msgstr "恢復成功"
 msgstr "恢復成功"
 
 
 #: src/components/TwoFA/Authorization.vue:89
 #: src/components/TwoFA/Authorization.vue:89
-#: src/views/preference/components/TOTP.vue:155
 msgid "Recovery"
 msgid "Recovery"
 msgstr "恢復"
 msgstr "恢復"
 
 
-#: src/views/preference/components/TOTP.vue:80
-msgid "Recovery Code"
+#: src/views/preference/components/RecoveryCodes.vue:68
+#, fuzzy
+msgid "Recovery Codes"
 msgstr "恢復碼"
 msgstr "恢復碼"
 
 
-#: src/views/preference/components/TOTP.vue:89
-msgid "Recovery Code:"
-msgstr "恢復碼:"
+#: src/views/preference/components/RecoveryCodes.vue:73
+msgid ""
+"Recovery codes are used to access your account when you lose access to your "
+"2FA device. Each code can only be used once."
+msgstr ""
 
 
 #: src/views/preference/CertSettings.vue:37
 #: src/views/preference/CertSettings.vue:37
 msgid "Recursive Nameservers"
 msgid "Recursive Nameservers"
@@ -2038,7 +2075,7 @@ msgstr "重新載入中"
 msgid "Reloading nginx"
 msgid "Reloading nginx"
 msgstr "正在重新載入 Nginx"
 msgstr "正在重新載入 Nginx"
 
 
-#: src/views/preference/AuthSettings.vue:141
+#: src/views/preference/AuthSettings.vue:169
 msgid "Remove"
 msgid "Remove"
 msgstr "移除"
 msgstr "移除"
 
 
@@ -2052,7 +2089,7 @@ msgstr "刪除網站:%{site_name}"
 msgid "Remove Site %{site} from %{node} successfully"
 msgid "Remove Site %{site} from %{node} successfully"
 msgstr "成功複製 %{conf_name} 到 %{node_name}"
 msgstr "成功複製 %{conf_name} 到 %{node_name}"
 
 
-#: src/views/preference/AuthSettings.vue:47
+#: src/views/preference/AuthSettings.vue:51
 #: src/views/preference/components/Passkey.vue:46
 #: src/views/preference/components/Passkey.vue:46
 msgid "Remove successfully"
 msgid "Remove successfully"
 msgstr "移除成功"
 msgstr "移除成功"
@@ -2143,7 +2180,7 @@ msgstr "請求參數錯誤"
 msgid "Reset"
 msgid "Reset"
 msgstr "重設"
 msgstr "重設"
 
 
-#: src/views/preference/components/TOTP.vue:109
+#: src/views/preference/components/TOTP.vue:93
 msgid "Reset 2FA"
 msgid "Reset 2FA"
 msgstr "重置多重因素驗證"
 msgstr "重置多重因素驗證"
 
 
@@ -2155,15 +2192,15 @@ msgstr "重新啟動"
 msgid "Restarting"
 msgid "Restarting"
 msgstr "正在重新啟動"
 msgstr "正在重新啟動"
 
 
-#: src/views/preference/AuthSettings.vue:79
+#: src/views/preference/AuthSettings.vue:107
 msgid "RP Display Name"
 msgid "RP Display Name"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:85
+#: src/views/preference/AuthSettings.vue:113
 msgid "RP Origins"
 msgid "RP Origins"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:73
+#: src/views/preference/AuthSettings.vue:101
 msgid "RPID"
 msgid "RPID"
 msgstr ""
 msgstr ""
 
 
@@ -2234,7 +2271,7 @@ msgstr "儲存成功"
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr "儲存成功"
 msgstr "儲存成功"
 
 
-#: src/views/preference/components/TOTP.vue:72
+#: src/views/preference/components/TOTP.vue:69
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgid "Scan the QR code with your mobile phone to add the account to the app."
 msgstr "用手機掃描二維碼將賬戶添加到應用程序中。"
 msgstr "用手機掃描二維碼將賬戶添加到應用程序中。"
 
 
@@ -2242,7 +2279,7 @@ msgstr "用手機掃描二維碼將賬戶添加到應用程序中。"
 msgid "SDK"
 msgid "SDK"
 msgstr "SDK"
 msgstr "SDK"
 
 
-#: src/views/preference/components/TOTP.vue:124
+#: src/views/preference/components/TOTP.vue:109
 msgid "Secret has been copied"
 msgid "Secret has been copied"
 msgstr ""
 msgstr ""
 
 
@@ -2596,11 +2633,6 @@ msgid ""
 "hyphens, dashes, colons, and dots."
 "hyphens, dashes, colons, and dots."
 msgstr "伺服器名稱應僅包含字母、Unicode、數字、連字符、破折號和點。"
 msgstr "伺服器名稱應僅包含字母、Unicode、數字、連字符、破折號和點。"
 
 
-#: src/views/preference/components/TOTP.vue:88
-msgid ""
-"The recovery code is only displayed once, please save it in a safe place."
-msgstr "恢復碼僅顯示一次,請將其保存在安全的地方。"
-
 #: src/views/dashboard/Environments.vue:148
 #: src/views/dashboard/Environments.vue:148
 msgid ""
 msgid ""
 "The remote Nginx UI version is not compatible with the local Nginx UI "
 "The remote Nginx UI version is not compatible with the local Nginx UI "
@@ -2630,6 +2662,13 @@ msgstr "網址無效。"
 msgid "The username or password is incorrect"
 msgid "The username or password is incorrect"
 msgstr "使用者名稱或密碼不正確"
 msgstr "使用者名稱或密碼不正確"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:104
+msgid ""
+"These codes are the last resort for accessing your account in case you lose "
+"your password and second factors. If you cannot find these codes, you will "
+"lose access to your account."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:102
 #: src/views/certificate/CertificateEditor.vue:102
 msgid "This Auto Cert item is invalid, please remove it."
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "此自動憑證項目無效,請將其移除。"
 msgstr "此自動憑證項目無效,請將其移除。"
@@ -2674,11 +2713,11 @@ msgid ""
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 "This will upgrade or reinstall the Nginx UI on %{nodeNames} to %{version}."
 msgstr "這將在 %{nodeNames} 上升級或重新安裝 Nginx UI 到 %{version}。"
 msgstr "這將在 %{nodeNames} 上升級或重新安裝 Nginx UI 到 %{version}。"
 
 
-#: src/views/preference/AuthSettings.vue:96
+#: src/views/preference/AuthSettings.vue:124
 msgid "Throttle"
 msgid "Throttle"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/AuthSettings.vue:116
+#: src/views/preference/AuthSettings.vue:144
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/components/AddPasskey.vue:65
 #: src/views/preference/LogrotateSettings.vue:11
 #: src/views/preference/LogrotateSettings.vue:11
 msgid "Tips"
 msgid "Tips"
@@ -2688,7 +2727,7 @@ msgstr "提示"
 msgid "Title"
 msgid "Title"
 msgstr "標題"
 msgstr "標題"
 
 
-#: src/views/preference/components/TOTP.vue:71
+#: src/views/preference/components/TOTP.vue:68
 msgid ""
 msgid ""
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "To enable it, you need to install the Google or Microsoft Authenticator app "
 "on your mobile phone."
 "on your mobile phone."
@@ -2728,11 +2767,11 @@ msgid "Total %{total} item"
 msgid_plural "Total %{total} items"
 msgid_plural "Total %{total} items"
 msgstr[0] ""
 msgstr[0] ""
 
 
-#: src/views/preference/components/TOTP.vue:69
+#: src/views/preference/components/TOTP.vue:66
 msgid "TOTP"
 msgid "TOTP"
 msgstr ""
 msgstr ""
 
 
-#: src/views/preference/components/TOTP.vue:70
+#: src/views/preference/components/TOTP.vue:67
 msgid ""
 msgid ""
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "TOTP is a two-factor authentication method that uses a time-based one-time "
 "password algorithm."
 "password algorithm."
@@ -2857,6 +2896,16 @@ msgstr "查看詳情"
 msgid "View Mode"
 msgid "View Mode"
 msgstr "查看模式"
 msgstr "查看模式"
 
 
+#: src/views/preference/components/RecoveryCodes.vue:134
+#, fuzzy
+msgid "View Recovery Codes"
+msgstr "恢復碼"
+
+#: src/views/preference/components/RecoveryCodes.vue:70
+#, fuzzy
+msgid "Viewed"
+msgstr "檢視"
+
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/constants/index.ts:17 src/views/config/InspectConfig.vue:33
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/notification/notificationColumns.tsx:22
 #: src/views/preference/components/AddPasskey.vue:82
 #: src/views/preference/components/AddPasskey.vue:82
@@ -2878,7 +2927,7 @@ msgstr ""
 "我們將從該檔案中刪除 HTTPChallenge 設定並重新載入 Nginx 設定檔案。你確定你要"
 "我們將從該檔案中刪除 HTTPChallenge 設定並重新載入 Nginx 設定檔案。你確定你要"
 "繼續嗎?"
 "繼續嗎?"
 
 
-#: src/views/preference/AuthSettings.vue:69
+#: src/views/preference/AuthSettings.vue:97
 msgid "Webauthn"
 msgid "Webauthn"
 msgstr ""
 msgstr ""
 
 
@@ -2899,6 +2948,12 @@ msgid ""
 "site category and the nodes selected below will be synchronized."
 "site category and the nodes selected below will be synchronized."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:140
+msgid ""
+"When you generate new recovery codes, you must download or print the new "
+"codes."
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:37
 #: src/views/dashboard/ServerAnalytic.vue:373
 #: src/views/dashboard/ServerAnalytic.vue:373
 msgid "Writes"
 msgid "Writes"
@@ -2912,7 +2967,7 @@ msgstr "將憑證私鑰寫入磁碟"
 msgid "Writing certificate to disk"
 msgid "Writing certificate to disk"
 msgstr "將憑證寫入磁碟"
 msgstr "將憑證寫入磁碟"
 
 
-#: src/views/preference/AuthSettings.vue:135
+#: src/views/preference/AuthSettings.vue:163
 #: src/views/preference/CertSettings.vue:69
 #: src/views/preference/CertSettings.vue:69
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/directive/DirectiveEditorItem.vue:96
 #: src/views/site/ngx_conf/LocationEditor.vue:87
 #: src/views/site/ngx_conf/LocationEditor.vue:87
@@ -2933,11 +2988,43 @@ msgid ""
 "passkey."
 "passkey."
 msgstr ""
 msgstr ""
 
 
+#: src/views/preference/components/RecoveryCodes.vue:81
+msgid ""
+"You have not enabled 2FA yet. Please enable 2FA to generate recovery codes."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:94
+msgid "You have not generated recovery codes yet."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:91
+msgid ""
+"Your current recovery code might be outdated and insecure. Please generate "
+"new recovery codes at your earliest convenience to ensure security."
+msgstr ""
+
+#: src/views/preference/components/RecoveryCodes.vue:142
+#: src/views/preference/components/RecoveryCodes.vue:155
+msgid "Your old codes won't work anymore."
+msgstr ""
+
 #: src/views/preference/components/Passkey.vue:75
 #: src/views/preference/components/Passkey.vue:75
 #, fuzzy
 #, fuzzy
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "新增通行密鑰"
 msgstr "新增通行密鑰"
 
 
+#~ msgid ""
+#~ "If you lose your mobile phone, you can use the recovery code to reset "
+#~ "your 2FA."
+#~ msgstr "如果您丟失了手機,可以使用恢復碼重置您的多重因素驗證驗證。"
+
+#~ msgid "Recovery Code:"
+#~ msgstr "恢復碼:"
+
+#~ msgid ""
+#~ "The recovery code is only displayed once, please save it in a safe place."
+#~ msgstr "恢復碼僅顯示一次,請將其保存在安全的地方。"
+
 #~ msgid "Directory"
 #~ msgid "Directory"
 #~ msgstr "目錄"
 #~ msgstr "目錄"
 
 
@@ -3077,9 +3164,6 @@ msgstr "新增通行密鑰"
 #~ msgid "404 Not Found"
 #~ msgid "404 Not Found"
 #~ msgstr "404 未找到頁面"
 #~ msgstr "404 未找到頁面"
 
 
-#~ msgid "Are you sure you want to restore?"
-#~ msgstr "您確定要恢復?"
-
 #~ msgid "Destroy"
 #~ msgid "Destroy"
 #~ msgstr "刪除"
 #~ msgstr "刪除"
 
 

+ 29 - 1
app/src/views/preference/AuthSettings.vue

@@ -1,8 +1,12 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
+import type { TwoFAStatus } from '@/api/2fa'
+import type { RecoveryCode } from '@/api/recovery'
 import type { BannedIP, Settings } from '@/api/settings'
 import type { BannedIP, Settings } from '@/api/settings'
 import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { Ref } from 'vue'
 import type { Ref } from 'vue'
+import twoFA from '@/api/2fa'
 import setting from '@/api/settings'
 import setting from '@/api/settings'
+import RecoveryCodes from '@/views/preference/components/RecoveryCodes.vue'
 import TOTP from '@/views/preference/components/TOTP.vue'
 import TOTP from '@/views/preference/components/TOTP.vue'
 import { message } from 'ant-design-vue'
 import { message } from 'ant-design-vue'
 import dayjs from 'dayjs'
 import dayjs from 'dayjs'
@@ -47,6 +51,17 @@ function removeBannedIP(ip: string) {
     message.success($gettext('Remove successfully'))
     message.success($gettext('Remove successfully'))
   })
   })
 }
 }
+
+const twoFAStatus = ref<TwoFAStatus>({} as TwoFAStatus)
+const recoveryCodes = ref<RecoveryCode[]>()
+
+function get2FAStatus() {
+  twoFA.status().then(r => {
+    twoFAStatus.value = r
+  })
+}
+
+get2FAStatus()
 </script>
 </script>
 
 
 <template>
 <template>
@@ -54,7 +69,20 @@ function removeBannedIP(ip: string) {
     <div>
     <div>
       <h2>{{ $gettext('2FA Settings') }}</h2>
       <h2>{{ $gettext('2FA Settings') }}</h2>
       <PasskeyRegistration class="mb-4" />
       <PasskeyRegistration class="mb-4" />
-      <TOTP class="mb-4" />
+
+      <TOTP
+        v-model:recovery-codes="recoveryCodes"
+        class="mb-4"
+        :status="twoFAStatus?.otp_status"
+        @refresh="get2FAStatus"
+      />
+
+      <RecoveryCodes
+        class="mb-4"
+        :two-f-a-status="twoFAStatus"
+        :recovery-codes="recoveryCodes"
+        @refresh="get2FAStatus"
+      />
 
 
       <h2>
       <h2>
         {{ $gettext('Authentication Settings') }}
         {{ $gettext('Authentication Settings') }}

+ 172 - 0
app/src/views/preference/components/RecoveryCodes.vue

@@ -0,0 +1,172 @@
+<script setup lang="ts">
+import type { TwoFAStatus } from '@/api/2fa'
+import type { RecoveryCode } from '@/api/recovery'
+import recovery from '@/api/recovery'
+import use2FAModal from '@/components/TwoFA/use2FAModal'
+import { CopyOutlined, WarningOutlined } from '@ant-design/icons-vue'
+import { UseClipboard } from '@vueuse/components'
+import { message } from 'ant-design-vue'
+
+const props = defineProps<{
+  recoveryCodes?: RecoveryCode[]
+  twoFAStatus?: TwoFAStatus
+}>()
+
+const emit = defineEmits<{
+  refresh: [void]
+}>()
+
+const _codes = ref<RecoveryCode[]>()
+const codes = computed(() => props.recoveryCodes ?? _codes.value)
+const newGenerated = ref(false)
+
+const codeSource = computed(() => codes.value?.map(code => code.code).join('\n'))
+
+function clickGenerateRecoveryCodes() {
+  const otpModal = use2FAModal()
+  otpModal.open().then(() => {
+    recovery.generate().then(r => {
+      _codes.value = r.codes
+      newGenerated.value = true
+      emit('refresh')
+      message.success($gettext('Generate recovery codes successfully'))
+    })
+  })
+}
+
+function clickViewRecoveryCodes() {
+  const otpModal = use2FAModal()
+  otpModal.open().then(() => {
+    recovery.view().then(r => {
+      _codes.value = r.codes
+    })
+  })
+}
+
+const popOpen = ref(false)
+
+function popConfirm() {
+  popOpen.value = false
+  clickGenerateRecoveryCodes()
+}
+
+function handlePopOpenChange(visible: boolean) {
+  popOpen.value = visible
+  if (!visible)
+    return
+
+  if (props.twoFAStatus?.recovery_codes_generated)
+    popOpen.value = true
+  else
+    popConfirm()
+}
+</script>
+
+<template>
+  <div>
+    <h3>
+      {{ $gettext('Recovery Codes') }}
+      <ATag v-if="recoveryCodes || twoFAStatus?.recovery_codes_viewed" :color="newGenerated || recoveryCodes ? 'success' : 'processing'">
+        {{ newGenerated || recoveryCodes ? $gettext('First View') : $gettext('Viewed') }}
+      </ATag>
+    </h3>
+    <p>{{ $gettext('Recovery codes are used to access your account when you lose access to your 2FA device. Each code can only be used once.') }}</p>
+    <p>{{ $gettext('Keep your recovery codes as safe as your password. We recommend saving them with a password manager.') }}</p>
+
+    <AAlert
+      v-if="!twoFAStatus?.enabled"
+      class="mb-4"
+      type="info"
+      show-icon
+      :message="$gettext('You have not enabled 2FA yet. Please enable 2FA to generate recovery codes.')"
+    />
+    <AAlert
+      v-else-if="!twoFAStatus?.recovery_codes_generated"
+      class="mb-4"
+      type="warning"
+      show-icon
+    >
+      <template #message>
+        <template v-if="twoFAStatus?.otp_status">
+          {{ $gettext('Your current recovery code might be outdated and insecure. Please generate new recovery codes at your earliest convenience to ensure security.') }}
+        </template>
+        <template v-else>
+          {{ $gettext('You have not generated recovery codes yet.') }}
+        </template>
+      </template>
+    </AAlert>
+
+    <ACard v-if="codes" class="codes-card mb-4">
+      <template #title>
+        <AAlert class="whitespace-normal px-6 py-4 rounded-t-[8px]" type="warning" banner :show-icon="false">
+          <template #message>
+            <WarningOutlined class="ant-alert-icon text-lg" />
+            {{ $gettext('These codes are the last resort for accessing your account in case you lose your password and second factors. If you cannot find these codes, you will lose access to your account.') }}
+          </template>
+        </AAlert>
+      </template>
+      <ul class="grid grid-cols-2 gap-2 text-lg">
+        <li v-for="(code, index) in codes" :key="index">
+          <span :class="{ 'line-through': code.used_time }">
+            {{ `${code.code.slice(0, 5)}-${code.code.slice(5)}` }}
+          </span>
+        </li>
+      </ul>
+      <div class="mt-4 flex space-x-2">
+        <UseClipboard v-slot="{ copy, copied }" :source="codeSource">
+          <AButton @click="copy()">
+            <template #icon>
+              <CopyOutlined />
+            </template>
+            {{ !copied ? $gettext('Copy Codes') : $gettext('Copied') }}
+          </AButton>
+        </UseClipboard>
+      </div>
+    </ACard>
+
+    <template v-if="twoFAStatus?.enabled">
+      <AButton
+        v-if="twoFAStatus?.recovery_codes_generated && !codes"
+        type="primary"
+        ghost
+        @click="clickViewRecoveryCodes"
+      >
+        {{ $gettext('View Recovery Codes') }}
+      </AButton>
+
+      <div v-if="twoFAStatus?.recovery_codes_generated" class="mt-4">
+        <h3>{{ $gettext('Generate New Recovery Codes') }}</h3>
+        <p>
+          {{ $gettext('When you generate new recovery codes, you must download or print the new codes.') }}
+          <b>
+            {{ $gettext('Your old codes won\'t work anymore.') }}
+          </b>
+        </p>
+      </div>
+
+      <APopconfirm
+        :open="popOpen"
+        @open-change="handlePopOpenChange"
+        @confirm="popConfirm"
+        @cancel="() => popOpen = false"
+      >
+        <template #title>
+          {{ $gettext('Are you sure to generate new recovery codes?') }}<br>
+          <b>{{ $gettext('Your old codes won\'t work anymore.') }}</b>
+        </template>
+        <AButton
+          type="primary"
+          ghost
+        >
+          {{ twoFAStatus?.recovery_codes_generated ? $gettext('Generate New Recovery Codes') : $gettext('Generate Recovery Codes') }}
+        </AButton>
+      </APopconfirm>
+    </template>
+  </div>
+</template>
+
+<style scoped lang="less">
+.codes-card :deep(.ant-card-head) {
+  padding: 0;
+}
+</style>

+ 33 - 64
app/src/views/preference/components/TOTP.vue

@@ -1,21 +1,28 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import twoFA from '@/api/2fa'
+import type { RecoveryCode } from '@/api/recovery'
 import otp from '@/api/otp'
 import otp from '@/api/otp'
 import OTPInput from '@/components/OTPInput/OTPInput.vue'
 import OTPInput from '@/components/OTPInput/OTPInput.vue'
+import use2FAModal from '@/components/TwoFA/use2FAModal'
 import { CheckCircleOutlined } from '@ant-design/icons-vue'
 import { CheckCircleOutlined } from '@ant-design/icons-vue'
 import { UseClipboard } from '@vueuse/components'
 import { UseClipboard } from '@vueuse/components'
-
 import { message } from 'ant-design-vue'
 import { message } from 'ant-design-vue'
 
 
-const status = ref(false)
+const { status = false } = defineProps<{
+  status?: boolean
+}>()
+
+const emit = defineEmits<{
+  refresh: [void]
+}>()
+
+const recoveryCodes = defineModel<RecoveryCode[]>('recoveryCodes')
+
 const enrolling = ref(false)
 const enrolling = ref(false)
 const resetting = ref(false)
 const resetting = ref(false)
 const generatedUrl = ref('')
 const generatedUrl = ref('')
 const secret = ref('')
 const secret = ref('')
 const passcode = ref('')
 const passcode = ref('')
 const refOtp = useTemplateRef('refOtp')
 const refOtp = useTemplateRef('refOtp')
-const recoveryCode = ref('')
-const inputRecoveryCode = ref('')
 
 
 function clickEnable2FA() {
 function clickEnable2FA() {
   enrolling.value = true
   enrolling.value = true
@@ -33,33 +40,23 @@ function generateSecret() {
 function enroll(code: string) {
 function enroll(code: string) {
   otp.enroll_otp(secret.value, code).then(r => {
   otp.enroll_otp(secret.value, code).then(r => {
     enrolling.value = false
     enrolling.value = false
-    recoveryCode.value = r.recovery_code
-    get2FAStatus()
+    recoveryCodes.value = r.codes
+    emit('refresh')
     message.success($gettext('Enable 2FA successfully'))
     message.success($gettext('Enable 2FA successfully'))
   }).catch(() => {
   }).catch(() => {
     refOtp.value?.clearInput()
     refOtp.value?.clearInput()
   })
   })
 }
 }
 
 
-function get2FAStatus() {
-  twoFA.status().then(r => {
-    status.value = r.otp_status
-  })
-}
-
-get2FAStatus()
-
-function clickReset2FA() {
-  resetting.value = true
-  inputRecoveryCode.value = ''
-}
-
 function reset2FA() {
 function reset2FA() {
-  otp.reset(inputRecoveryCode.value).then(() => {
-    resetting.value = false
-    recoveryCode.value = ''
-    get2FAStatus()
-    clickEnable2FA()
+  const otpModal = use2FAModal()
+  otpModal.open().then(() => {
+    otp.reset().then(() => {
+      resetting.value = false
+      recoveryCodes.value = undefined
+      emit('refresh')
+      clickEnable2FA()
+    })
   })
   })
 }
 }
 </script>
 </script>
@@ -75,23 +72,6 @@ function reset2FA() {
       <p><CheckCircleOutlined class="mr-2 text-green-600" />{{ $gettext('Current account is enabled TOTP.') }}</p>
       <p><CheckCircleOutlined class="mr-2 text-green-600" />{{ $gettext('Current account is enabled TOTP.') }}</p>
     </div>
     </div>
 
 
-    <AAlert
-      v-if="recoveryCode"
-      :message="$gettext('Recovery Code')"
-      class="mb-4"
-      type="info"
-      show-icon
-    >
-      <template #description>
-        <div>
-          <p>{{ $gettext('If you lose your mobile phone, you can use the recovery code to reset your 2FA.') }}</p>
-          <p>{{ $gettext('The recovery code is only displayed once, please save it in a safe place.') }}</p>
-          <p>{{ $gettext('Recovery Code:') }}</p>
-          <span class="ml-2">{{ recoveryCode }}</span>
-        </div>
-      </template>
-    </AAlert>
-
     <AButton
     <AButton
       v-if="!status && !enrolling"
       v-if="!status && !enrolling"
       type="primary"
       type="primary"
@@ -100,14 +80,19 @@ function reset2FA() {
     >
     >
       {{ $gettext('Enable TOTP') }}
       {{ $gettext('Enable TOTP') }}
     </AButton>
     </AButton>
-    <AButton
+    <APopconfirm
       v-if="status && !resetting"
       v-if="status && !resetting"
-      type="primary"
-      ghost
-      @click="clickReset2FA"
+      :title="$gettext('Are you sure to reset 2FA?')"
+      @confirm="reset2FA"
     >
     >
-      {{ $gettext('Reset 2FA') }}
-    </AButton>
+      <AButton
+        v-if="status && !resetting"
+        type="primary"
+        ghost
+      >
+        {{ $gettext('Reset 2FA') }}
+      </AButton>
+    </APopconfirm>
 
 
     <template v-if="enrolling">
     <template v-if="enrolling">
       <div class="flex flex-col items-center">
       <div class="flex flex-col items-center">
@@ -140,22 +125,6 @@ function reset2FA() {
         </div>
         </div>
       </div>
       </div>
     </template>
     </template>
-
-    <div
-      v-if="resetting"
-      class="mt-2"
-    >
-      <p>{{ $gettext('Input the recovery code:') }}</p>
-      <AInputGroup compact>
-        <AInput v-model:value="inputRecoveryCode" />
-        <AButton
-          type="primary"
-          @click="reset2FA"
-        >
-          {{ $gettext('Recovery') }}
-        </AButton>
-      </AInputGroup>
-    </div>
   </div>
   </div>
 </template>
 </template>
 
 

+ 27 - 5
model/user.go

@@ -1,19 +1,33 @@
 package model
 package model
 
 
 import (
 import (
+	"time"
+
 	"github.com/go-webauthn/webauthn/webauthn"
 	"github.com/go-webauthn/webauthn/webauthn"
 	"github.com/spf13/cast"
 	"github.com/spf13/cast"
 	"gorm.io/gorm"
 	"gorm.io/gorm"
 )
 )
 
 
+type RecoveryCode struct {
+	Code     string     `json:"code"`
+	UsedTime *time.Time `json:"used_time,omitempty"  gorm:"type:datetime;default:null"`
+}
+
+type RecoveryCodes struct {
+	Codes          []RecoveryCode `json:"codes"`
+	LastViewed     *time.Time     `json:"last_viewed,omitempty" gorm:"type:datetime;default:null"`
+	LastDownloaded *time.Time     `json:"last_downloaded,omitempty" gorm:"type:datetime;default:null"`
+}
+
 type User struct {
 type User struct {
 	Model
 	Model
 
 
-	Name         string `json:"name" cosy:"add:max=20;update:omitempty,max=20;list:fussy;db_unique"`
-	Password     string `json:"-" cosy:"json:password;add:required,max=20;update:omitempty,max=20"`
-	Status       bool   `json:"status" gorm:"default:1"`
-	OTPSecret    []byte `json:"-" gorm:"type:blob"`
-	EnabledTwoFA bool   `json:"enabled_2fa" gorm:"-"`
+	Name          string        `json:"name" cosy:"add:max=20;update:omitempty,max=20;list:fussy;db_unique"`
+	Password      string        `json:"-" cosy:"json:password;add:required,max=20;update:omitempty,max=20"`
+	Status        bool          `json:"status" gorm:"default:1"`
+	OTPSecret     []byte        `json:"-" gorm:"type:blob"`
+	RecoveryCodes RecoveryCodes `json:"-" gorm:"serializer:json"`
+	EnabledTwoFA  bool          `json:"enabled_2fa" gorm:"-"`
 }
 }
 
 
 type AuthToken struct {
 type AuthToken struct {
@@ -35,6 +49,14 @@ func (u *User) EnabledOTP() bool {
 	return len(u.OTPSecret) != 0
 	return len(u.OTPSecret) != 0
 }
 }
 
 
+func (u *User) RecoveryCodeGenerated() bool {
+	return len(u.RecoveryCodes.Codes) > 0
+}
+
+func (u *User) RecoveryCodeViewed() bool {
+	return u.RecoveryCodes.LastViewed != nil
+}
+
 func (u *User) EnabledPasskey() bool {
 func (u *User) EnabledPasskey() bool {
 	var passkeys Passkey
 	var passkeys Passkey
 	db.Where("user_id", u.ID).Limit(1).Find(&passkeys)
 	db.Where("user_id", u.ID).Limit(1).Find(&passkeys)

+ 14 - 10
query/auths.gen.go

@@ -36,6 +36,7 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user {
 	_user.Password = field.NewString(tableName, "password")
 	_user.Password = field.NewString(tableName, "password")
 	_user.Status = field.NewBool(tableName, "status")
 	_user.Status = field.NewBool(tableName, "status")
 	_user.OTPSecret = field.NewBytes(tableName, "otp_secret")
 	_user.OTPSecret = field.NewBytes(tableName, "otp_secret")
+	_user.RecoveryCodes = field.NewField(tableName, "recovery_codes")
 
 
 	_user.fillFieldMap()
 	_user.fillFieldMap()
 
 
@@ -45,15 +46,16 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user {
 type user struct {
 type user struct {
 	userDo
 	userDo
 
 
-	ALL       field.Asterisk
-	ID        field.Uint64
-	CreatedAt field.Time
-	UpdatedAt field.Time
-	DeletedAt field.Field
-	Name      field.String
-	Password  field.String
-	Status    field.Bool
-	OTPSecret field.Bytes
+	ALL           field.Asterisk
+	ID            field.Uint64
+	CreatedAt     field.Time
+	UpdatedAt     field.Time
+	DeletedAt     field.Field
+	Name          field.String
+	Password      field.String
+	Status        field.Bool
+	OTPSecret     field.Bytes
+	RecoveryCodes field.Field
 
 
 	fieldMap map[string]field.Expr
 	fieldMap map[string]field.Expr
 }
 }
@@ -78,6 +80,7 @@ func (u *user) updateTableName(table string) *user {
 	u.Password = field.NewString(table, "password")
 	u.Password = field.NewString(table, "password")
 	u.Status = field.NewBool(table, "status")
 	u.Status = field.NewBool(table, "status")
 	u.OTPSecret = field.NewBytes(table, "otp_secret")
 	u.OTPSecret = field.NewBytes(table, "otp_secret")
+	u.RecoveryCodes = field.NewField(table, "recovery_codes")
 
 
 	u.fillFieldMap()
 	u.fillFieldMap()
 
 
@@ -94,7 +97,7 @@ func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
 }
 }
 
 
 func (u *user) fillFieldMap() {
 func (u *user) fillFieldMap() {
-	u.fieldMap = make(map[string]field.Expr, 8)
+	u.fieldMap = make(map[string]field.Expr, 9)
 	u.fieldMap["id"] = u.ID
 	u.fieldMap["id"] = u.ID
 	u.fieldMap["created_at"] = u.CreatedAt
 	u.fieldMap["created_at"] = u.CreatedAt
 	u.fieldMap["updated_at"] = u.UpdatedAt
 	u.fieldMap["updated_at"] = u.UpdatedAt
@@ -103,6 +106,7 @@ func (u *user) fillFieldMap() {
 	u.fieldMap["password"] = u.Password
 	u.fieldMap["password"] = u.Password
 	u.fieldMap["status"] = u.Status
 	u.fieldMap["status"] = u.Status
 	u.fieldMap["otp_secret"] = u.OTPSecret
 	u.fieldMap["otp_secret"] = u.OTPSecret
+	u.fieldMap["recovery_codes"] = u.RecoveryCodes
 }
 }
 
 
 func (u user) clone(db *gorm.DB) user {
 func (u user) clone(db *gorm.DB) user {