diff --git a/agent/app/api/v2/task.go b/agent/app/api/v2/task.go index 69e0bcff5265..6b0db3680f8f 100644 --- a/agent/app/api/v2/task.go +++ b/agent/app/api/v2/task.go @@ -3,6 +3,8 @@ package v2 import ( "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" "github.com/gin-gonic/gin" ) @@ -30,6 +32,31 @@ func (b *BaseApi) PageTasks(c *gin.Context) { }) } +// @Tags TaskLog +// @Summary Read task log by Line +// @Param request body request.TaskLogReadReq true "request" +// @Success 200 {object} response.FileLineContent +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /logs/tasks/read [post] +func (b *BaseApi) ReadTaskLogByLine(c *gin.Context) { + var req request.TaskLogReadReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + var res *response.FileLineContent + res, err := taskService.ReadByLine(req) + if err != nil { + helper.InternalServer(c, err) + return + } + if res.TotalLines > 100 { + helper.SuccessWithDataGzipped(c, res) + } else { + helper.SuccessWithData(c, res) + } +} + // @Tags TaskLog // @Summary Get the number of executing tasks // @Success 200 {integer} int64 diff --git a/agent/app/dto/request/file.go b/agent/app/dto/request/file.go index 6496645b5215..600ebb9dc321 100644 --- a/agent/app/dto/request/file.go +++ b/agent/app/dto/request/file.go @@ -164,6 +164,13 @@ type FileReadByLineReq struct { TaskReq } +type TaskLogReadReq struct { + Page int `json:"page" validate:"required,min=1"` + PageSize int `json:"pageSize" validate:"required,min=1"` + Latest bool `json:"latest"` + TaskReq +} + type TaskReq struct { TaskID string `json:"taskID"` TaskType string `json:"taskType"` diff --git a/agent/app/service/task.go b/agent/app/service/task.go index 03ad53c52e56..413fab60b482 100644 --- a/agent/app/service/task.go +++ b/agent/app/service/task.go @@ -1,14 +1,20 @@ package service import ( + "os" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/utils/files" ) type TaskLogService struct{} type ITaskLogService interface { Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) + ReadByLine(req request.TaskLogReadReq) (*response.FileLineContent, error) SyncForRestart() error CountExecutingTask() (int64, error) } @@ -46,6 +52,62 @@ func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, e return total, items, err } +func (u *TaskLogService) ReadByLine(req request.TaskLogReadReq) (*response.FileLineContent, error) { + opts := []repo.DBOption{} + if req.TaskID != "" { + opts = append(opts, taskRepo.WithByID(req.TaskID)) + } else { + opts = append(opts, repo.WithOrderRuleBy("created_at", "desc"), repo.WithByType(req.TaskType), taskRepo.WithOperate(req.TaskOperate), taskRepo.WithResourceID(req.ResourceID)) + } + taskModel, err := taskRepo.GetFirst(opts...) + if err != nil { + return nil, err + } + + file, err := os.Open(taskModel.LogFile) + if err != nil { + return nil, err + } + defer file.Close() + stat, err := file.Stat() + if err != nil { + return nil, err + } + + var ( + lines []string + isEndOfFile bool + scope string + logFileRes *dto.LogFileRes + ) + if stat.Size() > files.MaxReadFileSize { + lines, _ = files.TailFromEnd(taskModel.LogFile, req.PageSize) + isEndOfFile = true + scope = "tail" + } else { + logFileRes, err = files.ReadFileByLine(taskModel.LogFile, req.Page, req.PageSize, req.Latest) + if err != nil { + return nil, err + } + scope = "page" + lines = logFileRes.Lines + } + + res := &response.FileLineContent{ + End: isEndOfFile, + Path: taskModel.LogFile, + TaskStatus: taskModel.Status, + Lines: lines, + Scope: scope, + } + if logFileRes != nil { + res.TotalLines = logFileRes.TotalLines + res.Total = logFileRes.TotalPages + res.End = logFileRes.IsEndOfFile + } + return res, nil +} + func (u *TaskLogService) SyncForRestart() error { return taskRepo.UpdateRunningTaskToFailed() } diff --git a/agent/router/ro_log.go b/agent/router/ro_log.go index e6be0ffdf531..b3f9ee14b4e8 100644 --- a/agent/router/ro_log.go +++ b/agent/router/ro_log.go @@ -13,6 +13,7 @@ func (s *LogRouter) InitRouter(Router *gin.RouterGroup) { { operationRouter.GET("/system/files", baseApi.GetSystemFiles) operationRouter.POST("/tasks/search", baseApi.PageTasks) + operationRouter.POST("/tasks/read", baseApi.ReadTaskLogByLine) operationRouter.GET("/tasks/executing/count", baseApi.CountExecutingTasks) } } diff --git a/core/app/api/v2/auth.go b/core/app/api/v2/auth.go index 7f4e9de992cb..dde64a2db9d9 100644 --- a/core/app/api/v2/auth.go +++ b/core/app/api/v2/auth.go @@ -377,6 +377,23 @@ func (b *BaseApi) MFABind(c *gin.Context) { helper.Success(c) } +// @Tags System Setting +// @Summary Close mfa +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/auth/mfa/close [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"mfa 关闭","formatEN":"close mfa"} +func (b *BaseApi) MFAClose(c *gin.Context) { + if err := xpack.AuthProvider.MFAClose(c); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + // @Tags Auth // @Summary generate api key // @Accept json diff --git a/core/app/auth/auth.go b/core/app/auth/auth.go index 2c0f859b850b..7a386fec8a45 100644 --- a/core/app/auth/auth.go +++ b/core/app/auth/auth.go @@ -215,6 +215,11 @@ func MFABind(req dto.MfaCredential) error { } return nil } + +func MFAClose() error { + return repo.NewISettingRepo().Update("MFAStatus", constant.StatusDisable) +} + func GetCurrentUserInfo() (*dto.CurrentUserInfo, error) { setting, err := repo.NewISettingRepo().List() if err != nil { @@ -249,6 +254,11 @@ func GetCurrentUserInfo() (*dto.CurrentUserInfo, error) { } func UpdateCurrentUserInfo(c *gin.Context, req dto.CurrentUserUpdate) error { settingRepo := repo.NewISettingRepo() + currentName, err := settingRepo.GetValueByKey("UserName") + if err != nil { + return err + } + shouldDeleteSession := len(req.Password) != 0 || req.Name != currentName if len(req.Password) != 0 { if len(req.OldPassword) == 0 { return buserr.New("ErrInitialPassword") @@ -282,7 +292,9 @@ func UpdateCurrentUserInfo(c *gin.Context, req dto.CurrentUserUpdate) error { if err := settingRepo.Update("ExpirationTime", expirationTime); err != nil { return err } - deleteCurrentSession(c) + if shouldDeleteSession { + deleteCurrentSession(c) + } return nil } diff --git a/core/init/migration/helper/menu.go b/core/init/migration/helper/menu.go index 69e7b11436c3..141b0f09677d 100644 --- a/core/init/migration/helper/menu.go +++ b/core/init/migration/helper/menu.go @@ -104,7 +104,7 @@ func LoadMenus() string { item[i].Children = UpsertMenuByLabel(item[i].Children, dto.ShowMenu{ ID: "121", Disabled: false, - Title: "xpack.user.accessControl", + Title: "xpack.user.userManage", IsShow: true, Label: "UserManagement", Path: "/enterprise/users", diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index 7fd79eaffe7a..219a3a276692 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -1075,7 +1075,7 @@ var AddUserManagementMenu = &gormigrate.Migration{ newItem := dto.ShowMenu{ ID: "121", Disabled: false, - Title: "xpack.user.accessControl", + Title: "xpack.user.userManage", IsShow: true, Label: "UserManagement", Path: "/enterprise/users", diff --git a/core/init/validator/validator.go b/core/init/validator/validator.go index a9c5883e78e5..b0f426fc5320 100644 --- a/core/init/validator/validator.go +++ b/core/init/validator/validator.go @@ -41,7 +41,6 @@ var baseSettingKeys = map[string]struct{}{ "NoAuthSetting": {}, "DashboardMemoVisible": {}, "DashboardSimpleNodeVisible": {}, - "MFAStatus": {}, "Edition": {}, "DocSource": {}, "IsOffline": {}, diff --git a/core/router/ro_base.go b/core/router/ro_base.go index d69f50ec5bf6..4abfe020e9ce 100644 --- a/core/router/ro_base.go +++ b/core/router/ro_base.go @@ -26,6 +26,7 @@ func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) { authRouter.POST("/mfa", baseApi.LoadMFA) authRouter.POST("/mfa/bind", baseApi.MFABind) + authRouter.POST("/mfa/close", baseApi.MFAClose) authRouter.POST("/passkey/register/begin", baseApi.PasskeyRegisterBegin) authRouter.POST("/passkey/register/finish", baseApi.PasskeyRegisterFinish) diff --git a/core/utils/xpack/helper/auth_helper.go b/core/utils/xpack/helper/auth_helper.go index e2624cf04cc2..cd45d19b07ea 100644 --- a/core/utils/xpack/helper/auth_helper.go +++ b/core/utils/xpack/helper/auth_helper.go @@ -73,6 +73,9 @@ func (a *authHelper) LoadMFA(_ *gin.Context, req baseDto.MfaRequest) (mfa.Otp, e func (a *authHelper) MFABind(_ *gin.Context, req baseDto.MfaCredential) error { return auth.MFABind(req) } +func (a *authHelper) MFAClose(_ *gin.Context) error { + return auth.MFAClose() +} func (a *authHelper) GenerateApiKey(_ *gin.Context) (string, error) { return auth.GenerateApiKey() } diff --git a/core/utils/xpack/providers/auth.go b/core/utils/xpack/providers/auth.go index bf72202679c2..efca16e29cf1 100644 --- a/core/utils/xpack/providers/auth.go +++ b/core/utils/xpack/providers/auth.go @@ -20,6 +20,7 @@ type AuthProvider interface { LoadMFA(c *gin.Context, req dto.MfaRequest) (mfa.Otp, error) MFABind(c *gin.Context, req dto.MfaCredential) error + MFAClose(c *gin.Context) error GenerateApiKey(c *gin.Context) (string, error) UpdateApiConfig(c *gin.Context, req dto.ApiInterfaceConfig) error diff --git a/frontend/src/api/interface/log.ts b/frontend/src/api/interface/log.ts index 81c6afde4c71..a39114ddcff3 100644 --- a/frontend/src/api/interface/log.ts +++ b/frontend/src/api/interface/log.ts @@ -48,6 +48,16 @@ export namespace Log { taskID?: string; } + export interface TaskLogReadReq { + page: number; + pageSize: number; + latest?: boolean; + taskID?: string; + taskType?: string; + taskOperate?: string; + resourceID?: number; + } + export interface Task { id: string; name: string; diff --git a/frontend/src/api/modules/auth.ts b/frontend/src/api/modules/auth.ts index 1b2d8c9382b9..14aed2022afb 100644 --- a/frontend/src/api/modules/auth.ts +++ b/frontend/src/api/modules/auth.ts @@ -55,6 +55,9 @@ export const loadMFA = (params: Login.MFARequest) => { export const bindMFA = (params: Login.MFABind) => { return http.post(`/core/auth/mfa/bind`, params); }; +export const closeMFA = () => { + return http.post(`/core/auth/mfa/close`); +}; export const generateApiKey = () => { return http.post(`/core/auth/api/generate`); }; diff --git a/frontend/src/api/modules/log.ts b/frontend/src/api/modules/log.ts index 311e14fed549..354ff68af567 100644 --- a/frontend/src/api/modules/log.ts +++ b/frontend/src/api/modules/log.ts @@ -1,6 +1,7 @@ import http from '@/api'; import { ResPage } from '../interface'; import { Log } from '../interface/log'; +import { TimeoutEnum } from '@/enums/http-enum'; export const getOperationLogs = (info: Log.SearchOpLog) => { return http.post>(`/core/logs/operation`, info); @@ -24,6 +25,11 @@ export const searchTasks = (req: Log.SearchTaskReq, node?: string) => { return http.post>(`/logs/tasks/search${params}`, req); }; +export const readTaskLogByLine = (req: Log.TaskLogReadReq, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.post(`/logs/tasks/read${params}`, req, TimeoutEnum.T_40S); +}; + export const countExecutingTask = () => { return http.get(`/logs/tasks/executing/count`); }; diff --git a/frontend/src/components/log/file/index.vue b/frontend/src/components/log/file/index.vue index b01b00ef4ac4..f8f0ae4ca69d 100644 --- a/frontend/src/components/log/file/index.vue +++ b/frontend/src/components/log/file/index.vue @@ -52,6 +52,7 @@ import { nextTick, onMounted, onUnmounted, reactive, ref, computed } from 'vue'; import { downloadFile } from '@/utils/file'; import { readByLine } from '@/api/modules/files'; +import { readTaskLogByLine } from '@/api/modules/log'; import { GlobalStore } from '@/store'; import bus from '@/global/bus'; import Highlight from '@/components/log/custom-highlight/index.vue'; @@ -268,7 +269,12 @@ const getContent = async (pre: boolean) => { let res; try { - res = await readByLine(readReq, props.config.operateNode || globalStore.currentNode); + const operateNode = props.config.operateNode || globalStore.currentNode; + if (readReq.type === 'task') { + res = await readTaskLogByLine(readReq, operateNode); + } else { + res = await readByLine(readReq, operateNode); + } } catch (error) { isLoading.value = false; firstLoading.value = false; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 05f47bde9d3e..c9a1c64c82ef 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -29,6 +29,7 @@ const message = { conn: 'Connect', disConn: 'Disconnect', clean: 'Clear', + selectAll: 'Select all', login: 'Sign in', close: 'Close', stop: 'Stop', @@ -243,7 +244,7 @@ const message = { composeName: 'Supports non-special characters at the beginning, lowercase letters, numbers, - and _, length 1-256', complexityPassword: - 'This field must consist of English, numbers with a length of 8-30 and contain at least two special characters.', + 'Supports password combinations with a length of 8-30 that contain at least two of letters, numbers, and special characters.', commonPassword: 'This field length must be more than 6.', linuxName: 'This field length must be between 1 and 128. The field mustn\'t contain these special characters: "{0}".', @@ -2223,6 +2224,10 @@ const message = { language: 'Language', runtimeEnv: 'Runtime environment', offlineEnv: 'Offline environment', + offlineEnvHelper: + 'After it is enabled, resources such as the App Store are read from the local server directory by default.\nSystem upgrades and agent upgrades require manually downloaded offline packages.', + offlineEnvOpenHelper: 'Are you sure you want to enable the offline environment?', + offlineEnvCloseHelper: 'Are you sure you want to disable the offline environment?', docSource: 'Documentation Source', withByRegion: 'Match Region Setting (Default)', withByLang: 'Match System Language', @@ -2454,14 +2459,13 @@ const message = { unSetting: 'Not set', noneSetting: 'Set the expiration time for the panel password. After the expiration, you need to reset the password', - expirationHelper: 'If the password expiration time is [0] days, the password expiration function is disabled', - days: 'Expiration Days', + expirationHelper: 'If the password expiration time is 0 days, the password expiration function is disabled', + days: 'Password expiration days', expiredHelper: 'The current password has expired. Please change the password again.', - timeoutHelper: - '[ {0} days ] The panel password is about to expire. After the expiration, you need to reset the password', + timeoutHelper: 'The panel password will expire in {0} days. After it expires, you need to reset the password', complexity: 'Complexity validation', complexityHelper: - 'After you enable it, the password validation rule will be: 8-30 characters, including English, numbers, and at least two special characters.', + 'After it is enabled, the password must be 8-30 characters long and contain at least two of letters, numbers, and special characters.', bindDomain: 'Bind domain', unBindDomain: 'Unbind domain', panelSSL: 'Panel SSL', @@ -3737,15 +3741,16 @@ const message = { upage: 'AI Website Builder', proAlert: 'Upgrade to Pro to use this feature', user: { - accessControl: 'Permission Management', user: 'User', userInfo: 'User Info', userManage: 'User Management', superAdmin: 'Super Admin', + superAdminDesc: 'Has full system management permissions and can manage all resources and configurations.', nodeAdmin: 'Node Admin', + nodeAdminDesc: + 'Has management permissions for specified nodes and can manage resources and configurations within those nodes.', bindNode: 'Bind Node', role: 'Role', - roleManage: 'Role Management', roleName: 'Name', permission: 'Permissions', permissionDuplicate: 'Only one role can be assigned to each node', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index b65469d60a6f..41c577df088c 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -29,6 +29,7 @@ const message = { conn: 'Conectar', disconn: 'Desconectar', clean: 'Limpiar', + selectAll: 'Seleccionar todo', login: 'Iniciar sesión', close: 'Cerrar', stop: 'Detener', @@ -245,7 +246,7 @@ const message = { composeName: 'Debe comenzar con un carácter no especial, permite minúsculas, números, - y _, longitud de 1 a 256', complexityPassword: - 'Este campo debe estar compuesto por letras, números, con una longitud de 8 a 30 y contener al menos dos caracteres especiales.', + 'Admite combinaciones de contraseña de 8 a 30 caracteres que contengan al menos dos de estos tipos: letras, números y caracteres especiales.', commonPassword: 'La longitud de este campo debe ser mayor a 6.', linuxName: 'La longitud de este campo debe estar entre 1 y 128. El campo no debe contener los siguientes caracteres especiales: "{0}".', @@ -2221,6 +2222,10 @@ const message = { language: 'Idioma', runtimeEnv: 'Entorno de ejecución', offlineEnv: 'Entorno sin conexión', + offlineEnvHelper: + 'Después de habilitarlo, recursos como la Tienda de aplicaciones se leerán de forma predeterminada desde el directorio local del servidor.\nLas actualizaciones del sistema y de agentes requieren paquetes sin conexión descargados manualmente.', + offlineEnvOpenHelper: '¿Seguro que quieres habilitar el entorno sin conexión?', + offlineEnvCloseHelper: '¿Seguro que quieres deshabilitar el entorno sin conexión?', docSource: 'Fuente de documentación', withByRegion: 'Seguir región de operación (Predeterminado)', withByLang: 'Seguir idioma del sistema', @@ -2465,14 +2470,14 @@ const message = { noneSetting: 'Establece el tiempo de expiración para la contraseña del panel. Tras expirar, deberás restablecer la contraseña', expirationHelper: - 'Si el tiempo de expiración de la contraseña es [0] días, la función de expiración estará desactivada', - days: 'Días de expiración', + 'Si el tiempo de expiración de la contraseña es 0 días, la función de expiración estará desactivada', + days: 'Días de expiración de contraseña', expiredHelper: 'La contraseña actual ha expirado. Por favor cámbiala de nuevo.', timeoutHelper: - '[ {0} días ] La contraseña del panel está a punto de expirar. Tras expirar, deberás restablecerla', + 'La contraseña del panel expirará en {0} días. Tras expirar, deberás restablecerla', complexity: 'Validación de complejidad', complexityHelper: - 'Tras habilitarlo, la regla de validación de contraseña será: 8-30 caracteres, incluyendo letras, números y al menos dos caracteres especiales.', + 'Tras habilitarlo, la contraseña debe tener entre 8 y 30 caracteres y contener al menos dos de estos tipos: letras, números y caracteres especiales.', bindDomain: 'Vincular dominio', unBindDomain: 'Desvincular dominio', panelSSL: 'SSL del panel', @@ -3737,15 +3742,17 @@ const message = { upage: 'Constructor Web con IA', proAlert: 'Actualiza a Pro para usar esta función', user: { - accessControl: 'Gestión de permisos', user: 'Usuario', userInfo: 'Información de usuario', userManage: 'Gestión de usuarios', superAdmin: 'Superadministrador', + superAdminDesc: + 'Tiene permisos completos de administración del sistema y puede gestionar todos los recursos y configuraciones.', nodeAdmin: 'Administrador de nodo', + nodeAdminDesc: + 'Tiene permisos de administración para nodos especificados y puede gestionar recursos y configuraciones dentro de esos nodos.', bindNode: 'Vincular nodo', role: 'Rol', - roleManage: 'Gestión de roles', roleName: 'Nombre', permission: 'Permisos', permissionDuplicate: 'Solo se puede asignar un rol a cada nodo', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 810a335de0ed..3cc40f5205af 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -26,6 +26,7 @@ const message = { conn: '接続', disConn: '切断', clean: 'クリア', + selectAll: 'すべて選択', login: 'サインイン', close: '閉じる', off: '閉じる', @@ -241,7 +242,7 @@ const message = { supervisorName: 'このフィールドは、特別な文字以外の文字から開始する必要があり、英語、数字、「 - 」、および「_」文字が1〜128の文字で構成されている必要があります。', complexityPassword: - 'このフィールドは、英語で構成され、長さは8〜30で、少なくとも2つの特殊文字が含まれている必要があります。', + '長さ8〜30文字で、英字、数字、特殊文字のうち少なくとも2種類を含むパスワードの組み合わせをサポートします。', commonPassword: 'このフィールドの長さは6を超える必要があります。', linuxName: 'このフィールドの長さは1〜128でなければなりません。フィールドには、これらの特殊文字を含めてはなりません。「{0}」。', @@ -2187,6 +2188,10 @@ const message = { language: '言語', runtimeEnv: '実行環境', offlineEnv: 'オフライン環境', + offlineEnvHelper: + '有効にすると、アプリストアなどのリソースはデフォルトでサーバーのローカルディレクトリから読み込まれます。\nシステムアップグレードとエージェントアップグレードには、手動でダウンロードしたオフラインパッケージが必要です。', + offlineEnvOpenHelper: 'オフライン環境を有効にしてもよろしいですか?', + offlineEnvCloseHelper: 'オフライン環境を無効にしてもよろしいですか?', docSource: 'ドキュメントの参照先', withByRegion: '運用リージョンに従う(デフォルト)', withByLang: 'システム言語に従う', @@ -2401,14 +2406,14 @@ const message = { unSetting: '解き放つ', noneSetting: 'パネルパスワードの有効期限を設定します。有効期限が切れた後、パスワードをリセットする必要があります', - expirationHelper: 'パスワードの有効期限が[0]日の場合、パスワードの有効期限機能が無効になっています', - days: '有効期限', + expirationHelper: 'パスワードの有効期限が0日の場合、パスワード有効期限機能は無効になります', + days: 'パスワード有効期限日数', expiredHelper: '現在のパスワードの有効期限が切れています。もう一度パスワードを変更してください。', timeoutHelper: - '[{0}日]パネルパスワードの有効期限が切れようとしています。有効期限が切れた後、パスワードをリセットする必要があります', + 'パネルパスワードは{0}日後に期限切れになります。期限切れ後はパスワードをリセットする必要があります', complexity: '複雑さの検証', complexityHelper: - '有効にした後、パスワード検証ルールは次のとおりです。英語、数字、少なくとも2つの特殊文字を含む8〜30文字です。', + '有効にすると、パスワードは8〜30文字で、英字、数字、特殊文字のうち少なくとも2種類を含む必要があります。', bindDomain: 'バインドドメイン', unBindDomain: 'バインドドメイン', panelSSL: 'パネルSSL', @@ -3723,15 +3728,15 @@ const message = { upage: 'AIウェブサイトビルダー', proAlert: 'この機能を使用するにはProにアップグレードしてください', user: { - accessControl: '権限管理', user: 'ユーザー', userInfo: 'ユーザー情報', userManage: 'ユーザー管理', superAdmin: 'スーパー管理者', + superAdminDesc: 'システム全体の管理権限を持ち、すべてのリソースと設定を管理できます。', nodeAdmin: 'ノード管理者', + nodeAdminDesc: '指定されたノードの管理権限を持ち、ノード内のリソースと設定を管理できます。', bindNode: 'ノードをバインド', role: 'ロール', - roleManage: 'ロール管理', roleName: '名前', permission: '権限', permissionDuplicate: '各ノードには1つのロールのみ割り当てられます', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 01b08a3a69ad..7e052f95adcc 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -26,6 +26,7 @@ const message = { conn: '연결', disConn: '연결 해제', clean: '지우기', + selectAll: '전체 선택', login: '로그인', close: '닫기', off: '꺼짐', @@ -238,7 +239,7 @@ const message = { supervisorName: '이 필드는 특수 문자로 시작할 수 없으며, 영어, 숫자, "-", "_" 문자로 구성되어야 하며 길이는 1-128 자여야 합니다.', complexityPassword: - '이 필드는 영어와 숫자로 구성되어야 하며 길이는 8-30 자이고 최소 두 개의 특수 문자가 포함되어야 합니다.', + '길이 8-30 자이며 영문, 숫자, 특수 문자 중 최소 두 가지를 포함하는 비밀번호 조합을 지원합니다.', commonPassword: '이 필드 길이는 6 자 이상이어야 합니다.', linuxName: '이 필드 길이는 1-128 자 사이여야 하며, 다음 특수 문자를 포함할 수 없습니다: "{0}".', email: '이 필드는 유효한 이메일 주소여야 합니다.', @@ -2146,6 +2147,10 @@ const message = { language: '언어', runtimeEnv: '실행 환경', offlineEnv: '오프라인 환경', + offlineEnvHelper: + '활성화하면 앱 스토어 등의 리소스를 기본적으로 서버 로컬 디렉터리에서 읽습니다.\n시스템 업그레이드와 에이전트 업그레이드는 오프라인 패키지를 수동으로 다운로드해야 합니다.', + offlineEnvOpenHelper: '오프라인 환경을 활성화하시겠습니까?', + offlineEnvCloseHelper: '오프라인 환경을 비활성화하시겠습니까?', docSource: '문서 출처', withByRegion: '운영 지역 따름(기본값)', withByLang: '시스템 언어 따름', @@ -2351,13 +2356,13 @@ const message = { expirationTime: '만료 날짜', unSetting: '미설정', noneSetting: '패널 비밀번호의 만료 시간을 설정합니다. 만료 후 비밀번호를 재설정해야 합니다.', - expirationHelper: '비밀번호 만료 시간이 [0]일인 경우 비밀번호 만료 기능이 비활성화됩니다.', - days: '만료 일수', + expirationHelper: '비밀번호 만료 시간이 0일인 경우 비밀번호 만료 기능이 비활성화됩니다.', + days: '비밀번호 만료 일수', expiredHelper: '현재 비밀번호가 만료되었습니다. 비밀번호를 다시 변경하십시오.', - timeoutHelper: '[ {0}일 ] 패널 비밀번호가 곧 만료됩니다. 만료 후 비밀번호를 재설정해야 합니다.', + timeoutHelper: '패널 비밀번호가 {0}일 후 만료됩니다. 만료 후 비밀번호를 재설정해야 합니다.', complexity: '복잡성 검증', complexityHelper: - '활성화하면 비밀번호 검증 규칙이 8-30 자, 영어, 숫자 및 최소 두 개의 특수 문자 포함으로 설정됩니다.', + '활성화하면 비밀번호는 8-30 자이며 영문, 숫자, 특수 문자 중 최소 두 가지를 포함해야 합니다.', bindDomain: '도메인 바인딩', unBindDomain: '도메인 바인딩 해제', panelSSL: '패널 SSL', @@ -2586,8 +2591,7 @@ const message = { importHelper: '라이센스 파일을 여기에 클릭하거나 드래그하세요', licenseRequiredTip: '아직 라이선스를 가져오지 않았습니다. 장치 ID를 복사해 라이선스를 받은 뒤 가져와야 로그인할 수 있습니다.', - licenseRequiredShortTip: - '먼저 장치 ID를 복사해 라이선스를 받은 뒤, 라이선스를 가져오면 로그인할 수 있습니다.', + licenseRequiredShortTip: '먼저 장치 ID를 복사해 라이선스를 받은 뒤, 라이선스를 가져오면 로그인할 수 있습니다.', licenseRequiredUserTip: 'Enterprise Edition 라이선스가 바인딩되지 않았습니다. 슈퍼 관리자에게 라이선스 가져오기를 요청하세요.', technicalAdvice: '기술 상담', @@ -3640,15 +3644,15 @@ const message = { upage: 'AI 웹사이트 빌더', proAlert: '이 기능을 사용하려면 Pro로 업그레이드하세요', user: { - accessControl: '권한 관리', user: '사용자', userInfo: '사용자 정보', userManage: '사용자 관리', superAdmin: '최고 관리자', + superAdminDesc: '시스템 전체 관리 권한을 보유하며 모든 리소스와 설정을 관리할 수 있습니다.', nodeAdmin: '노드 관리자', + nodeAdminDesc: '지정된 노드의 관리 권한을 보유하며 노드 내 리소스와 설정을 관리할 수 있습니다.', bindNode: '노드 연결', role: '역할', - roleManage: '역할 관리', roleName: '이름', permission: '권한', permissionDuplicate: '각 노드에는 하나의 역할만 지정할 수 있습니다', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index e4d29c313b90..c8b5ddeb2af2 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -26,6 +26,7 @@ const message = { conn: 'Sambung', disConn: 'Putus sambungan', clean: 'Kosongkan', + selectAll: 'Pilih semua', login: 'Log masuk', close: 'Tutup', off: 'Tutup', @@ -244,7 +245,7 @@ const message = { supervisorName: 'Ruangan ini mesti bermula dengan aksara bukan khas dan mesti terdiri daripada aksara rumi, nombor, "-", dan "_" dengan panjang 1-128 aksara.', complexityPassword: - 'Ruangan ini mesti terdiri daripada aksara rumi, nombor dengan panjang 8-30 aksara dan mengandungi sekurang-kurangnya dua aksara khas.', + 'Menyokong gabungan kata laluan dengan panjang 8-30 aksara yang mengandungi sekurang-kurangnya dua daripada huruf, nombor, dan aksara khas.', commonPassword: 'Panjang ruangan ini mesti melebihi 6 aksara.', linuxName: 'Panjang ruangan ini mesti antara 1 hingga 128 aksara. Ruangan ini tidak boleh mengandungi aksara khas berikut: "{0}".', @@ -2226,6 +2227,10 @@ const message = { language: 'Bahasa', runtimeEnv: 'Persekitaran operasi', offlineEnv: 'Persekitaran luar talian', + offlineEnvHelper: + 'Selepas diaktifkan, sumber seperti App Store akan dibaca secara lalai daripada direktori setempat pelayan.\nNaik taraf sistem dan naik taraf ejen memerlukan pakej luar talian yang dimuat turun secara manual.', + offlineEnvOpenHelper: 'Adakah anda pasti mahu mengaktifkan persekitaran luar talian?', + offlineEnvCloseHelper: 'Adakah anda pasti mahu menyahaktifkan persekitaran luar talian?', docSource: 'Sumber dokumentasi', withByRegion: 'Ikut wilayah operasi (Lalai)', withByLang: 'Ikut bahasa sistem', @@ -2449,14 +2454,14 @@ const message = { noneSetting: 'Tetapkan masa tamat tempoh untuk kata laluan panel. Selepas tamat tempoh, anda perlu menetapkan semula kata laluan', expirationHelper: - 'Jika masa tamat tempoh kata laluan ialah [0] hari, fungsi tamat tempoh kata laluan dilumpuhkan', - days: 'Hari Tamat Tempoh', + 'Jika masa tamat tempoh kata laluan ialah 0 hari, fungsi tamat tempoh kata laluan dilumpuhkan', + days: 'Hari tamat tempoh kata laluan', expiredHelper: 'Kata laluan semasa telah tamat tempoh. Sila tukar kata laluan lagi.', timeoutHelper: - '[ {0} hari ] Kata laluan panel akan tamat tempoh. Selepas tamat tempoh, anda perlu menetapkan semula kata laluan', + 'Kata laluan panel akan tamat tempoh dalam {0} hari. Selepas tamat tempoh, anda perlu menetapkan semula kata laluan', complexity: 'Pengesahan kerumitan', complexityHelper: - 'Selepas anda mengaktifkannya, peraturan pengesahan kata laluan akan menjadi: 8-30 aksara, termasuk bahasa Inggeris, nombor, dan sekurang-kurangnya dua aksara khas.', + 'Selepas diaktifkan, kata laluan mestilah 8-30 aksara dan mengandungi sekurang-kurangnya dua daripada huruf, nombor, dan aksara khas.', bindDomain: 'Ikatan domain', unBindDomain: 'Buka ikatan domain', panelSSL: 'Panel SSL', @@ -3775,15 +3780,17 @@ const message = { upage: 'Pembina Laman Web AI', proAlert: 'Tingkatkan ke Pro untuk menggunakan ciri ini', user: { - accessControl: 'Pengurusan Kebenaran', user: 'Pengguna', userInfo: 'Maklumat Pengguna', userManage: 'Pengurusan Pengguna', superAdmin: 'Pentadbir Super', + superAdminDesc: + 'Mempunyai kebenaran pengurusan sistem penuh dan boleh mengurus semua sumber serta konfigurasi.', nodeAdmin: 'Pentadbir Nod', + nodeAdminDesc: + 'Mempunyai kebenaran pengurusan untuk nod yang ditentukan dan boleh mengurus sumber serta konfigurasi dalam nod tersebut.', bindNode: 'Ikat Nod', role: 'Peranan', - roleManage: 'Pengurusan Peranan', roleName: 'Nama', permission: 'Kebenaran', permissionDuplicate: 'Setiap nod hanya boleh diberikan satu peranan', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 8191d64f70c8..deebbd5ba618 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -26,6 +26,7 @@ const message = { conn: 'Conectar', disConn: 'Desconectar', clean: 'Limpar', + selectAll: 'Selecionar tudo', login: 'Entrar', close: 'Fechar', off: 'Fechar', @@ -240,7 +241,7 @@ const message = { supervisorName: 'Este campo deve começar com caracteres não especiais e conter letras, números, "-" e "_" com comprimento de 1-128.', complexityPassword: - 'Este campo deve conter letras, números com comprimento de 8-30 e pelo menos dois caracteres especiais.', + 'Suporta combinações de senha com comprimento de 8 a 30 que contenham pelo menos dois destes tipos: letras, números e caracteres especiais.', commonPassword: 'O comprimento deste campo deve ser maior que 6.', linuxName: 'O comprimento deste campo deve estar entre 1 e 128. Não pode conter os seguintes caracteres especiais: "{0}".', @@ -2339,6 +2340,10 @@ const message = { language: 'Idioma', runtimeEnv: 'Ambiente de execução', offlineEnv: 'Ambiente offline', + offlineEnvHelper: + 'Após ativar, recursos como a Loja de Aplicativos serão lidos por padrão do diretório local do servidor.\nAtualizações do sistema e de agentes exigem pacotes offline baixados manualmente.', + offlineEnvOpenHelper: 'Tem certeza de que deseja ativar o ambiente offline?', + offlineEnvCloseHelper: 'Tem certeza de que deseja desativar o ambiente offline?', docSource: 'Fonte da documentação', withByRegion: 'Seguir região de operação (Padrão)', withByLang: 'Seguir idioma do sistema', @@ -2562,14 +2567,14 @@ const message = { noneSetting: 'Defina o tempo de expiração da senha do painel. Após a expiração, será necessário redefinir a senha.', expirationHelper: - 'Se o tempo de expiração da senha for [0] dias, a função de expiração da senha estará desativada.', - days: 'Dias de Expiração', + 'Se o tempo de expiração da senha for 0 dias, a função de expiração da senha estará desativada.', + days: 'Dias de expiração da senha', expiredHelper: 'A senha atual expirou. Por favor, altere a senha novamente.', timeoutHelper: - '[ {0} dias ] A senha do painel está prestes a expirar. Após a expiração, será necessário redefinir a senha.', + 'A senha do painel expirará em {0} dias. Após a expiração, será necessário redefinir a senha.', complexity: 'Validação de Complexidade', complexityHelper: - 'Após ativar, a regra de validação de senha será: 8-30 caracteres, incluindo letras, números e pelo menos dois caracteres especiais.', + 'Após ativar, a senha deve ter de 8 a 30 caracteres e conter pelo menos dois destes tipos: letras, números e caracteres especiais.', bindDomain: 'Vincular domínio', unBindDomain: 'Desvincular domínio', panelSSL: 'SSL do Painel', @@ -3916,15 +3921,17 @@ const message = { upage: 'Construtor de Sites com IA', proAlert: 'Atualize para Pro para usar este recurso', user: { - accessControl: 'Gerenciamento de permissões', user: 'Usuário', userInfo: 'Informações do Usuário', userManage: 'Gerenciamento de Usuários', superAdmin: 'Super Administrador', + superAdminDesc: + 'Possui permissões completas de gerenciamento do sistema e pode gerenciar todos os recursos e configurações.', nodeAdmin: 'Administrador de Nó', + nodeAdminDesc: + 'Possui permissões de gerenciamento para nós especificados e pode gerenciar recursos e configurações dentro desses nós.', bindNode: 'Vincular Nó', role: 'Função', - roleManage: 'Gerenciamento de funções', roleName: 'Nome', permission: 'Permissões', permissionDuplicate: 'Apenas uma função pode ser atribuída a cada nó', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 76595214efad..6b53ef5241fd 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -26,6 +26,7 @@ const message = { conn: 'Подключить', disConn: 'Отключить', clean: 'Очистить', + selectAll: 'Выбрать все', login: 'Войти', close: 'Закрыть', off: 'Закрыть', @@ -239,7 +240,7 @@ const message = { supervisorName: 'Это поле должно начинаться с неспециальных символов и должно состоять из английских букв, цифр, "-" и "_" длиной 1-128.', complexityPassword: - 'Это поле должно состоять из английских букв, цифр длиной 8-30 и содержать как минимум два специальных символа.', + 'Поддерживаются комбинации пароля длиной 8-30 символов, содержащие как минимум два типа из следующих: буквы, цифры и специальные символы.', commonPassword: 'Длина этого поля должна быть больше 6.', linuxName: 'Длина этого поля должна быть от 1 до 128. Поле не должно содержать эти специальные символы: "{0}".', @@ -2203,6 +2204,10 @@ const message = { language: 'Язык', runtimeEnv: 'Среда выполнения', offlineEnv: 'Офлайн-среда', + offlineEnvHelper: + 'После включения такие ресурсы, как магазин приложений, по умолчанию считываются из локального каталога сервера.\nДля обновления системы и агентов необходимо вручную загрузить офлайн-пакеты.', + offlineEnvOpenHelper: 'Вы уверены, что хотите включить офлайн-среду?', + offlineEnvCloseHelper: 'Вы уверены, что хотите отключить офлайн-среду?', docSource: 'Источник документации', withByRegion: 'Следовать региону работы (по умолчанию)', withByLang: 'Следовать языку системы', @@ -2424,14 +2429,14 @@ const message = { expirationTime: 'Дата истечения', unSetting: 'Не задано', noneSetting: 'Установите срок действия пароля панели. После истечения срока необходимо сбросить пароль', - expirationHelper: 'Если срок действия пароля [0] дней, функция истечения срока действия пароля отключена', - days: 'Дней до истечения', + expirationHelper: 'Если срок действия пароля равен 0 дням, функция истечения срока действия пароля отключена', + days: 'Дней до истечения пароля', expiredHelper: 'Текущий пароль истек. Пожалуйста, измените пароль снова.', timeoutHelper: - '[ {0} дней ] Срок действия пароля панели скоро истечет. После истечения срока необходимо сбросить пароль', + 'Срок действия пароля панели истечет через {0} дней. После истечения срока необходимо сбросить пароль', complexity: 'Проверка сложности', complexityHelper: - 'После включения правило проверки пароля будет: 8-30 символов, включая английские буквы, цифры и как минимум два специальных символа.', + 'После включения пароль должен быть длиной 8-30 символов и содержать как минимум два типа из следующих: буквы, цифры и специальные символы.', bindDomain: 'Привязать домен', unBindDomain: 'Отвязать домен', panelSSL: 'SSL панели', @@ -3768,15 +3773,17 @@ const message = { upage: 'AI Конструктор сайтов', proAlert: 'Обновитесь до Pro, чтобы использовать эту функцию', user: { - accessControl: 'Управление разрешениями', user: 'Пользователь', userInfo: 'Информация о пользователе', userManage: 'Управление пользователями', superAdmin: 'Суперадминистратор', + superAdminDesc: + 'Имеет полные права управления системой и может управлять всеми ресурсами и конфигурациями.', nodeAdmin: 'Администратор узла', + nodeAdminDesc: + 'Имеет права управления указанными узлами и может управлять ресурсами и конфигурациями внутри этих узлов.', bindNode: 'Привязать узел', role: 'Роль', - roleManage: 'Управление ролями', roleName: 'Название', permission: 'Разрешения', permissionDuplicate: 'Каждому узлу можно назначить только одну роль', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index a28c6455206f..6be1e40773cd 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -29,6 +29,7 @@ const message = { conn: 'Bağlan', disconn: 'Bağlantıyı Kes', clean: 'Temizle', + selectAll: 'Tümünü seç', login: 'Oturum Aç', close: 'Kapat', stop: 'Durdur', @@ -245,7 +246,7 @@ const message = { composeName: 'Başlangıçta özel olmayan karakterleri, küçük harfleri, rakamları, - ve _ destekler, uzunluk 1-256', complexityPassword: - 'Bu alan İngilizce, rakamlardan oluşmalı, uzunluk 8-30 olmalı ve en az iki özel karakter içermelidir.', + '8-30 uzunluğunda, harf, rakam ve özel karakterlerden en az iki tür içeren parola kombinasyonlarını destekler.', commonPassword: 'Bu alanın uzunluğu 6dan fazla olmalıdır.', linuxName: 'Bu alanın uzunluğu 1 ile 128 arasında olmalıdır. Alan şu özel karakterleri içermemelidir: "{0}".', @@ -2218,6 +2219,10 @@ const message = { language: 'Dil', runtimeEnv: 'Çalışma ortamı', offlineEnv: 'Çevrimdışı ortam', + offlineEnvHelper: + 'Etkinleştirildikten sonra App Store gibi kaynaklar varsayılan olarak sunucunun yerel dizininden okunur.\nSistem yükseltmeleri ve aracı yükseltmeleri için çevrimdışı paketlerin manuel olarak indirilmesi gerekir.', + offlineEnvOpenHelper: 'Çevrimdışı ortamı etkinleştirmek istediğinizden emin misiniz?', + offlineEnvCloseHelper: 'Çevrimdışı ortamı devre dışı bırakmak istediğinizden emin misiniz?', docSource: 'Dokümantasyon kaynağı', withByRegion: 'Çalışma bölgesini takip et (Varsayılan)', withByLang: 'Sistem dilini takip et', @@ -2455,14 +2460,14 @@ const message = { unSetting: 'Ayarlanmadı', noneSetting: 'Panel parolasının son kullanma tarihini ayarlayın. Son kullanma tarihinden sonra parolayı sıfırlamanız gerekir', - expirationHelper: 'Parola son kullanma süresi [0] gün ise, parola son kullanma işlevi devre dışı bırakılır', - days: 'Son Kullanım Günleri', + expirationHelper: 'Parola son kullanma süresi 0 gün ise, parola son kullanma işlevi devre dışı bırakılır', + days: 'Parola son kullanma günleri', expiredHelper: 'Geçerli parola süresi doldu. Lütfen parolayı tekrar değiştirin.', timeoutHelper: - '[ {0} gün ] Panel parolası süresi dolmak üzere. Süre dolduktan sonra parolayı sıfırlamanız gerekir', + 'Panel parolasının süresi {0} gün sonra dolacak. Süre dolduktan sonra parolayı sıfırlamanız gerekir', complexity: 'Karmaşıklık doğrulaması', complexityHelper: - 'Etkinleştirildiğinde, parola doğrulama kuralı şu olacaktır: 8-30 karakter, İngilizce, sayılar ve en az iki özel karakter içerir.', + 'Etkinleştirildiğinde parola 8-30 karakter uzunluğunda olmalı ve harf, rakam ve özel karakterlerden en az iki tür içermelidir.', bindDomain: 'Alan adı bağla', unBindDomain: 'Alan adı bağlamasını kaldır', panelSSL: 'Panel SSL', @@ -3770,15 +3775,16 @@ const message = { upage: 'AI Web Sitesi Oluşturucu', proAlert: 'Bu özelliği kullanmak için Proya yükseltin', user: { - accessControl: 'İzin Yönetimi', user: 'Kullanıcı', userInfo: 'Kullanıcı Bilgileri', userManage: 'Kullanıcı Yönetimi', superAdmin: 'Süper Yönetici', + superAdminDesc: 'Tam sistem yönetim izinlerine sahiptir ve tüm kaynakları ve yapılandırmaları yönetebilir.', nodeAdmin: 'Düğüm Yöneticisi', + nodeAdminDesc: + 'Belirtilen düğümler için yönetim izinlerine sahiptir ve bu düğümlerdeki kaynakları ve yapılandırmaları yönetebilir.', bindNode: 'Düğüm Bağla', role: 'Rol', - roleManage: 'Rol Yönetimi', roleName: 'Ad', permission: 'İzinler', permissionDuplicate: 'Her düğüme yalnızca bir rol atanabilir', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 0d6395b61164..55f6322c12bd 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -29,6 +29,7 @@ const message = { conn: '連接', disConn: '斷開', clean: '清除', + selectAll: '全選', login: '登入', close: '關閉', stop: '停止', @@ -231,7 +232,7 @@ const message = { imageName: '支援非特殊字元開頭、英文、數字、:@/.-_,長度1-256', volumeName: '支援英文、數字、.-和_,長度2-30', supervisorName: '支援非特殊字元開頭,英文、數字、-和_,長度1-128', - complexityPassword: '請輸入長度為 8-30 位,並包含字母、數字、至少兩種特殊字元的密碼組合', + complexityPassword: '支援長度為 8-30 位,並包含字母、數字、特殊字元中至少兩項的密碼組合', commonPassword: '請輸入 6 位以上長度密碼', linuxName: '長度1-128,名稱不能含有{0}等符號', email: '請輸入正確的信箱', @@ -2050,6 +2051,10 @@ const message = { language: '系統語言', runtimeEnv: '運行環境', offlineEnv: '離線環境', + offlineEnvHelper: + '開啟後,應用商店等資源將預設從伺服器本機目錄讀取。\n系統升級和從節點升級需手動下載離線包完成。', + offlineEnvOpenHelper: '是否確認開啟離線環境?', + offlineEnvCloseHelper: '是否確認關閉離線環境?', docSource: '文件來源', withByRegion: '跟隨運行區域(預設)', withByLang: '跟隨系統語言', @@ -2064,7 +2069,7 @@ const message = { regionTip: '運行區域影響系統更新源以及安裝包下載地址。', docSourceTip: '文件來源決定文件與更新日誌的跳轉語言。', languageHelper: '預設跟隨瀏覽器語言,設定後只對目前瀏覽器生效,更換瀏覽器後需要重新設定', - sessionTimeout: '逾時時間', + sessionTimeout: '面板逾時時間', sessionTimeoutError: '最小逾時時間為 300 秒', sessionTimeoutHelper: '如果使用者超過 {0} 秒未操作面板,面板將自動登出', systemIP: '預設存取地址', @@ -2330,12 +2335,12 @@ const message = { expirationTime: '密碼過期時間', unSetting: '未設定', noneSetting: '為面板密碼設定過期時間,過期後需要重新設定密碼', - expirationHelper: '密碼過期時間為 [0] 天時,則關閉密碼過期功能', - days: '過期天數', + expirationHelper: '密碼過期時間為 0 天時,則關閉密碼過期功能', + days: '密碼過期天數', expiredHelper: '目前密碼已過期,請重新修改密碼:', - timeoutHelper: '【 {0} 天後 】面板密碼即將過期,過期後需要重新設定密碼', + timeoutHelper: '{0} 天後面板密碼即將過期,過期後需要重新設定密碼', complexity: '密碼複雜度驗證', - complexityHelper: '開啟後密碼必須滿足長度為 8-30 位,並包含字母、數字、至少兩種特殊字元', + complexityHelper: '開啟後密碼必須滿足長度為 8-30 位,並包含字母、數字、特殊字元中至少兩項', bindDomain: '網域綁定', unBindDomain: '域名解綁', panelSSL: '面板 SSL', @@ -3425,15 +3430,15 @@ const message = { upage: 'AI 建站', proAlert: '升級專業版以使用此功能', user: { - accessControl: '權限管理', user: '使用者', userInfo: '使用者資訊', userManage: '使用者管理', superAdmin: '超級管理員', + superAdminDesc: '擁有系統全部管理權限,可管理所有資源和設定。', nodeAdmin: '節點管理員', + nodeAdminDesc: '擁有指定節點的管理權限,可管理節點內資源和設定。', bindNode: '綁定節點', role: '角色', - roleManage: '角色管理', roleName: '名稱', permission: '權限', permissionDuplicate: '每個節點只能新增一種角色', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 72b470343823..df20e96d0a56 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -29,6 +29,7 @@ const message = { conn: '连接', disConn: '断开', clean: '清空', + selectAll: '全选', login: '登录', close: '关闭', stop: '关闭', @@ -219,7 +220,7 @@ const message = { imageName: '支持非特殊字符开头、英文、数字、:@/.-_,长度1-256', volumeName: '支持英文、数字、.-和_,长度2-30', supervisorName: '支持非特殊字符开头,英文、数字、-和_,长度1-128', - complexityPassword: '请输入长度为 8-30 位且包含字母、数字、特殊字符至少两项的密码组合', + complexityPassword: '支持长度为 8-30 位且包含字母、数字、特殊字符中至少两项的密码组合', linuxName: '长度1-128,名称不能含有{0}等符号', email: '请输入正确的邮箱', number: '请输入正确的数字', @@ -2090,6 +2091,10 @@ const message = { withByLang: '跟随系统语言', runtimeEnv: '运行环境', offlineEnv: '离线环境', + offlineEnvHelper: + '开启后,应用商店等资源将默认从服务器本地目录读取。\n系统升级和从节点升级需手动下载离线包完成。', + offlineEnvOpenHelper: '是否确认开启离线环境?', + offlineEnvCloseHelper: '是否确认关闭离线环境?', region: '运行区域', cn: '中国大陆', intl: '全球', @@ -2097,7 +2102,7 @@ const message = { regionTip: '运行区域影响系统更新源以及安装包下载地址。', docSourceTip: '文档来源决定文档与更新日志的跳转语言。', languageHelper: '默认跟随浏览器语言,设置后只对当前浏览器生效,更换浏览器后需要重新设置', - sessionTimeout: '超时时间', + sessionTimeout: '面板超时时间', sessionTimeoutError: '最小超时时间为 300 秒', sessionTimeoutHelper: '如果用户超过 {0} 秒未操作面板,面板将自动退出登录', systemIP: '默认访问地址', @@ -2334,12 +2339,12 @@ const message = { expirationTime: '密码过期时间', unSetting: '未设置', noneSetting: '为面板密码设置过期时间,过期后需要重新设置密码', - expirationHelper: '密码过期时间为 [0] 天时,则关闭密码过期功能', - days: '过期天数', + expirationHelper: '密码过期时间为 0 天时,则关闭密码过期功能', + days: '密码过期天数', expiredHelper: '当前密码已过期,请重新修改密码:', - timeoutHelper: '【 {0} 天后 】面板密码即将过期,过期后需要重新设置密码', + timeoutHelper: '{0} 天后面板密码即将过期,过期后需要重新设置密码', complexity: '密码复杂度验证', - complexityHelper: '开启后密码必须满足长度为 8-30 位且包含字母、数字、特殊字符至少两项', + complexityHelper: '开启后密码必须满足长度为 8-30 位,且包含字母、数字、特殊字符中至少两项', bindDomain: '域名绑定', unBindDomain: '域名解绑', panelSSL: '面板 SSL', @@ -4048,15 +4053,15 @@ const message = { appUpgradeHelper: '有 {0} 个应用需要升级', }, user: { - accessControl: '权限管理', user: '用户', userInfo: '用户信息', userManage: '用户管理', superAdmin: '超级管理员', + superAdminDesc: '拥有系统全部管理权限,可管理所有资源和配置。', nodeAdmin: '节点管理员', + nodeAdminDesc: '拥有指定节点的管理权限,可管理节点内资源和配置。', bindNode: '绑定节点', role: '角色', - roleManage: '角色管理', roleName: '名称', permission: '权限', permissionDuplicate: '每个节点只能添加一种角色', diff --git a/frontend/src/layout/components/Sidebar/components/Collapse.vue b/frontend/src/layout/components/Sidebar/components/Collapse.vue index dba5de50d322..d30a81dc173e 100644 --- a/frontend/src/layout/components/Sidebar/components/Collapse.vue +++ b/frontend/src/layout/components/Sidebar/components/Collapse.vue @@ -52,21 +52,17 @@ v-for="item in nodeOptions" :key="item.name" > -
- - {{ item.name === 'local' ? globalStore.getMasterAlias() : item.name }} - - - - - -
+ + {{ item.name === 'local' ? globalStore.getMasterAlias() : item.name }} + + + + + @@ -340,7 +336,8 @@ onMounted(() => { .dropdown-item { display: flex; align-items: center; - padding: 2px 8px; + gap: 6px; + padding: 5px 8px; cursor: pointer; line-height: 26px; transition: background 0.3s; @@ -349,21 +346,6 @@ onMounted(() => { } .icon-status { font-size: 16px; - margin-left: auto; - } - .node { - display: flex; - align-items: center; - gap: 6px; - padding: 3px 0; - width: 100%; - } - .node-name { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } .msg-tag { margin-top: 3px; diff --git a/frontend/src/layout/components/Sidebar/components/user-info/index.vue b/frontend/src/layout/components/Sidebar/components/user-info/index.vue index d2a9b975c925..da92de4519ef 100644 --- a/frontend/src/layout/components/Sidebar/components/user-info/index.vue +++ b/frontend/src/layout/components/Sidebar/components/user-info/index.vue @@ -40,13 +40,6 @@ autocomplete="current-password" /> - - - - {{ $t('setting.sessionTimeoutHelper', [form.sessionTimeout]) }} - - - {{ $t('setting.expirationHelper') }} + + + + {{ $t('setting.sessionTimeoutHelper', [form.sessionTimeout]) }} + + @@ -395,6 +394,7 @@ import DialogPro from '@/components/dialog-pro/index.vue'; import { Login } from '@/api/interface/auth'; import { bindMFA, + closeMFA, generateApiKey, loadMFA, passkeyDelete, @@ -846,6 +846,17 @@ const onSubmit = async (formEl: FormInstance | undefined) => { if (!formEl) return; const valid = await formEl.validateField(getUserFormFields(), () => {}); if (!valid) return; + const needReLogin = form.name !== props.currentUser?.name || !!form.password; + if (needReLogin) { + try { + await ElMessageBox.confirm(i18n.global.t('setting.userChangeHelper'), i18n.global.t('setting.userChange'), { + confirmButtonText: i18n.global.t('commons.button.confirm'), + cancelButtonText: i18n.global.t('commons.button.cancel'), + }); + } catch { + return; + } + } const param: Login.AuthInfoUpdate = { id: form.id, name: form.name, @@ -861,9 +872,13 @@ const onSubmit = async (formEl: FormInstance | undefined) => { loading.value = false; open.value = false; MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - globalStore.setLogStatus(false); - globalStore.clearAuthInfo(); - router.push({ name: 'entrance', params: { code: globalStore.entrance } }); + if (needReLogin) { + globalStore.setLogStatus(false); + globalStore.clearAuthInfo(); + router.push({ name: 'entrance', params: { code: globalStore.entrance } }); + return; + } + emit('search'); }) .catch(() => { loading.value = false; @@ -959,7 +974,7 @@ const handleMFA = async () => { }) .then(async () => { loading.value = true; - await updateSetting({ key: 'MFAStatus', value: 'Disable' }) + await closeMFA() .then(async () => { loading.value = false; mfaDialogOpen.value = false; diff --git a/frontend/src/routers/modules/database.ts b/frontend/src/routers/modules/database.ts index eef12edef835..a65ddafed9d2 100644 --- a/frontend/src/routers/modules/database.ts +++ b/frontend/src/routers/modules/database.ts @@ -127,6 +127,7 @@ const databaseRouter = { requiresAuth: false, parent: 'menu.database', title: 'MongoDB', + permission: 'database_view', }, }, { @@ -140,6 +141,7 @@ const databaseRouter = { parent: 'menu.database', title: 'MongoDB', detail: 'database.remote', + permission: 'database_view', }, }, ], diff --git a/frontend/src/views/setting/panel/hidemenu/index.vue b/frontend/src/views/setting/panel/hidemenu/index.vue index 0a8dee6e16a0..6a0e19a4c244 100644 --- a/frontend/src/views/setting/panel/hidemenu/index.vue +++ b/frontend/src/views/setting/panel/hidemenu/index.vue @@ -69,7 +69,7 @@ const acceptParams = (params: DialogProps): void => { let hideMenu = JSON.parse(params.hideMenu); sortMenu(hideMenu); treeData.hideMenu = hideMenu; - if (globalStore.isIntl) { + if (globalStore.isIntl || globalStore.isEE()) { treeData.hideMenu = removeUpage(treeData.hideMenu); } }; diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue index b5e51d5a617d..ef16a50e7c97 100644 --- a/frontend/src/views/setting/panel/index.vue +++ b/frontend/src/views/setting/panel/index.vue @@ -165,8 +165,9 @@ v-model="form.isOffline" active-value="Enable" inactive-value="Disable" - @change="onSave('IsOffline', form.isOffline)" + @change="onChangeOfflineEnv" /> + {{ $t('setting.offlineEnvHelper') }} @@ -391,6 +392,27 @@ const onChangeWatermark = async () => { }); }; +const onChangeOfflineEnv = async (val: string | number | boolean) => { + const value = val + ''; + const oldValue = value === 'Enable' ? 'Disable' : 'Enable'; + const message = + value === 'Enable' + ? i18n.global.t('setting.offlineEnvOpenHelper') + : i18n.global.t('setting.offlineEnvCloseHelper'); + try { + await ElMessageBox.confirm(message, i18n.global.t('setting.offlineEnv'), { + confirmButtonText: i18n.global.t('commons.button.confirm'), + cancelButtonText: i18n.global.t('commons.button.cancel'), + }); + const success = await onSave('IsOffline', value); + if (!success) { + form.isOffline = oldValue; + } + } catch { + form.isOffline = oldValue; + } +}; + const handleThemeChange = async (val: string) => { globalStore.themeConfig.theme = val; switchTheme(); @@ -435,9 +457,10 @@ const onSave = async (key: string, val: any) => { search(); } catch (error) { loading.value = false; - return; + return false; } loading.value = false; + return true; }; onMounted(() => {