diff --git a/agent/app/service/file.go b/agent/app/service/file.go index a375129b8..7d665ce81 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -1116,7 +1116,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi } logFilePath = path.Join(global.Dir.LogDir, "ai", safeName) default: - return nil, buserr.New("ErrNotSupportType") + return nil, buserr.WithName("ErrNotSupportType", req.Type) } file, err := os.Open(logFilePath) diff --git a/agent/cmd/server/docs/x-log.json b/agent/cmd/server/docs/x-log.json index 9a0059220..f3807ea82 100644 --- a/agent/cmd/server/docs/x-log.json +++ b/agent/cmd/server/docs/x-log.json @@ -1578,6 +1578,255 @@ "formatZH": "更新 [name]", "formatEN": "update user [name]" }, + "/core/enterprise/vms": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机 [name]", + "formatEN": "create VM [name]" + }, + "/core/enterprise/vms/del": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除虚拟机 [name]", + "formatEN": "delete VM [name]" + }, + "/core/enterprise/vms/iso": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机镜像 [name] [type]", + "formatEN": "create VM ISO [name] [type]" + }, + "/core/enterprise/vms/iso/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_isos", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机镜像 [name]", + "formatEN": "delete VM ISO [name]" + }, + "/core/enterprise/vms/iso/update": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新虚拟机镜像 [name] [type]", + "formatEN": "update VM ISO [name] [type]" + }, + "/core/enterprise/vms/networks": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机网络 [name] [type]", + "formatEN": "create VM network [name] [type]" + }, + "/core/enterprise/vms/networks/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_networks", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机网络 [name]", + "formatEN": "delete VM network [name]" + }, + "/core/enterprise/vms/networks/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步虚拟机网络", + "formatEN": "sync VM networks" + }, + "/core/enterprise/vms/networks/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_networks", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新虚拟机网络 [name]", + "formatEN": "update VM network [name]" + }, + "/core/enterprise/vms/operate": { + "bodyKeys": [ + "name", + "operate" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "操作虚拟机 [name] [operate]", + "formatEN": "operate VM [name] [operate]" + }, + "/core/enterprise/vms/rename": { + "bodyKeys": [ + "name", + "newName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "重命名虚拟机 [name] 为 [newName]", + "formatEN": "rename VM [name] to [newName]" + }, + "/core/enterprise/vms/snapshots": { + "bodyKeys": [ + "name", + "snapshotName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机 [name] 快照 [snapshotName]", + "formatEN": "create VM [name] snapshot [snapshotName]" + }, + "/core/enterprise/vms/snapshots/del": { + "bodyKeys": [ + "name", + "snapshotName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除虚拟机 [name] 快照 [snapshotName]", + "formatEN": "delete VM [name] snapshot [snapshotName]" + }, + "/core/enterprise/vms/snapshots/recover": { + "bodyKeys": [ + "name", + "snapshotName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "恢复虚拟机 [name] 快照 [snapshotName]", + "formatEN": "recover VM [name] snapshot [snapshotName]" + }, + "/core/enterprise/vms/storages": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机存储 [name] [type]", + "formatEN": "create VM storage [name] [type]" + }, + "/core/enterprise/vms/storages/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_storages", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机存储 [name]", + "formatEN": "delete VM storage [name]" + }, + "/core/enterprise/vms/storages/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步虚拟机存储", + "formatEN": "sync VM storages" + }, + "/core/enterprise/vms/storages/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_storages", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新虚拟机存储 [name]", + "formatEN": "update VM storage [name]" + }, + "/core/enterprise/vms/templates": { + "bodyKeys": [ + "name", + "templateName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机 [name] 模板 [templateName]", + "formatEN": "create VM [name] template [templateName]" + }, + "/core/enterprise/vms/templates/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_templates", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机模板 [name]", + "formatEN": "delete VM template [name]" + }, + "/core/enterprise/vms/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新虚拟机 [name]", + "formatEN": "update VM [name]" + }, "/core/groups": { "bodyKeys": [ "name", @@ -1900,13 +2149,6 @@ "formatZH": "删除集群 [clusterName] 节点 [clusterNodeID]", "formatEN": "delete node [clusterNodeID] from cluster [clusterName]" }, - "/core/xpack/exchange/app": { - "bodyKeys": [], - "paramKeys": [], - "beforeFunctions": [], - "formatZH": "同步节点应用", - "formatEN": "sync app" - }, "/core/xpack/exchange/file": { "bodyKeys": [ "sourceNode", @@ -2262,6 +2504,13 @@ "formatZH": "更新专业版设置 [key]", "formatEN": "update xpack setting [key]" }, + "/core/xpack/sync/app": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步节点应用", + "formatEN": "sync app" + }, "/core/xpack/sync/ssl": { "bodyKeys": [ "primaryDomain" diff --git a/core/app/repo/common.go b/core/app/repo/common.go index ccf3a095d..fcd919051 100644 --- a/core/app/repo/common.go +++ b/core/app/repo/common.go @@ -43,6 +43,14 @@ func WithByName(name string) global.DBOption { return g.Where("`name` = ?", name) } } +func WithByLikeName(name string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(name) == 0 { + return g + } + return g.Where("name like ?", "%"+name+"%") + } +} func WithByUserID(userID string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("user_id = ?", userID) diff --git a/core/app/service/setting.go b/core/app/service/setting.go index 5e2147c13..41b6c0dc6 100644 --- a/core/app/service/setting.go +++ b/core/app/service/setting.go @@ -766,7 +766,7 @@ func checkProxy(req dto.ProxyUpdate) error { case "", "close": return nil default: - return buserr.WithDetail("ErrNotSupportType", req.ProxyType, nil) + return buserr.WithName("ErrNotSupportType", req.ProxyType) } defer func() { if r := recover(); r != nil { diff --git a/core/app/task/task.go b/core/app/task/task.go index b1af9705e..179e88c58 100644 --- a/core/app/task/task.go +++ b/core/app/task/task.go @@ -49,6 +49,9 @@ type SubTask struct { const ( TaskInstall = "TaskInstall" + TaskCreate = "TaskCreate" + TaskUpdate = "TaskUpdate" + TaskDelete = "TaskDelete" TaskUpgrade = "TaskUpgrade" TaskAddNode = "TaskAddNode" TaskSync = "TaskSync" @@ -70,6 +73,7 @@ const ( TaskScopeCluster = "Cluster" TaskScopeAppInstall = "AppInstallTask" TaskScopeAI = "AI" + TaskScopeVm = "VirtualMachine" ) func GetTaskName(resourceName, operate, scope string) string { diff --git a/core/cmd/server/docs/docs.go b/core/cmd/server/docs/docs.go index dd75825d2..c4a9d5030 100644 --- a/core/cmd/server/docs/docs.go +++ b/core/cmd/server/docs/docs.go @@ -74,6 +74,7 @@ const docTemplate = `{ "description": "OK", "schema": { "additionalProperties": { + "format": "int64", "type": "integer" }, "type": "object" @@ -40549,6 +40550,9 @@ const docTemplate = `{ "streamableHttpPath": { "type": "string" }, + "taskID": { + "type": "string" + }, "type": { "type": "string" }, @@ -40681,6 +40685,9 @@ const docTemplate = `{ "streamableHttpPath": { "type": "string" }, + "taskID": { + "type": "string" + }, "type": { "type": "string" }, @@ -45494,4 +45501,4 @@ var SwaggerInfo = &swag.Spec{ func init() { swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) -} +} \ No newline at end of file diff --git a/core/cmd/server/docs/swagger.json b/core/cmd/server/docs/swagger.json index ea3608cf0..99c0b3c69 100644 --- a/core/cmd/server/docs/swagger.json +++ b/core/cmd/server/docs/swagger.json @@ -70,6 +70,7 @@ "description": "OK", "schema": { "additionalProperties": { + "format": "int64", "type": "integer" }, "type": "object" @@ -31916,7 +31917,8 @@ "restart", "stop", "down", - "delete" + "delete", + "rebuild" ], "type": "string" }, @@ -40544,6 +40546,9 @@ "streamableHttpPath": { "type": "string" }, + "taskID": { + "type": "string" + }, "type": { "type": "string" }, @@ -40676,6 +40681,9 @@ "streamableHttpPath": { "type": "string" }, + "taskID": { + "type": "string" + }, "type": { "type": "string" }, diff --git a/core/cmd/server/docs/x-log.json b/core/cmd/server/docs/x-log.json index 9a0059220..f3807ea82 100644 --- a/core/cmd/server/docs/x-log.json +++ b/core/cmd/server/docs/x-log.json @@ -1578,6 +1578,255 @@ "formatZH": "更新 [name]", "formatEN": "update user [name]" }, + "/core/enterprise/vms": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机 [name]", + "formatEN": "create VM [name]" + }, + "/core/enterprise/vms/del": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除虚拟机 [name]", + "formatEN": "delete VM [name]" + }, + "/core/enterprise/vms/iso": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机镜像 [name] [type]", + "formatEN": "create VM ISO [name] [type]" + }, + "/core/enterprise/vms/iso/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_isos", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机镜像 [name]", + "formatEN": "delete VM ISO [name]" + }, + "/core/enterprise/vms/iso/update": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新虚拟机镜像 [name] [type]", + "formatEN": "update VM ISO [name] [type]" + }, + "/core/enterprise/vms/networks": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机网络 [name] [type]", + "formatEN": "create VM network [name] [type]" + }, + "/core/enterprise/vms/networks/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_networks", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机网络 [name]", + "formatEN": "delete VM network [name]" + }, + "/core/enterprise/vms/networks/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步虚拟机网络", + "formatEN": "sync VM networks" + }, + "/core/enterprise/vms/networks/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_networks", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新虚拟机网络 [name]", + "formatEN": "update VM network [name]" + }, + "/core/enterprise/vms/operate": { + "bodyKeys": [ + "name", + "operate" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "操作虚拟机 [name] [operate]", + "formatEN": "operate VM [name] [operate]" + }, + "/core/enterprise/vms/rename": { + "bodyKeys": [ + "name", + "newName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "重命名虚拟机 [name] 为 [newName]", + "formatEN": "rename VM [name] to [newName]" + }, + "/core/enterprise/vms/snapshots": { + "bodyKeys": [ + "name", + "snapshotName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机 [name] 快照 [snapshotName]", + "formatEN": "create VM [name] snapshot [snapshotName]" + }, + "/core/enterprise/vms/snapshots/del": { + "bodyKeys": [ + "name", + "snapshotName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除虚拟机 [name] 快照 [snapshotName]", + "formatEN": "delete VM [name] snapshot [snapshotName]" + }, + "/core/enterprise/vms/snapshots/recover": { + "bodyKeys": [ + "name", + "snapshotName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "恢复虚拟机 [name] 快照 [snapshotName]", + "formatEN": "recover VM [name] snapshot [snapshotName]" + }, + "/core/enterprise/vms/storages": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机存储 [name] [type]", + "formatEN": "create VM storage [name] [type]" + }, + "/core/enterprise/vms/storages/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_storages", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机存储 [name]", + "formatEN": "delete VM storage [name]" + }, + "/core/enterprise/vms/storages/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步虚拟机存储", + "formatEN": "sync VM storages" + }, + "/core/enterprise/vms/storages/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_storages", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新虚拟机存储 [name]", + "formatEN": "update VM storage [name]" + }, + "/core/enterprise/vms/templates": { + "bodyKeys": [ + "name", + "templateName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建虚拟机 [name] 模板 [templateName]", + "formatEN": "create VM [name] template [templateName]" + }, + "/core/enterprise/vms/templates/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "vm_templates", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除虚拟机模板 [name]", + "formatEN": "delete VM template [name]" + }, + "/core/enterprise/vms/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新虚拟机 [name]", + "formatEN": "update VM [name]" + }, "/core/groups": { "bodyKeys": [ "name", @@ -1900,13 +2149,6 @@ "formatZH": "删除集群 [clusterName] 节点 [clusterNodeID]", "formatEN": "delete node [clusterNodeID] from cluster [clusterName]" }, - "/core/xpack/exchange/app": { - "bodyKeys": [], - "paramKeys": [], - "beforeFunctions": [], - "formatZH": "同步节点应用", - "formatEN": "sync app" - }, "/core/xpack/exchange/file": { "bodyKeys": [ "sourceNode", @@ -2262,6 +2504,13 @@ "formatZH": "更新专业版设置 [key]", "formatEN": "update xpack setting [key]" }, + "/core/xpack/sync/app": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步节点应用", + "formatEN": "sync app" + }, "/core/xpack/sync/ssl": { "bodyKeys": [ "primaryDomain" diff --git a/core/constant/common.go b/core/constant/common.go index 1b30051b9..7795b45f8 100644 --- a/core/constant/common.go +++ b/core/constant/common.go @@ -208,6 +208,11 @@ var WebUrlMap = map[string]struct{}{ "/enterprise/ops-report/alert": {}, "/enterprise/ops-report/history": {}, "/enterprise/ops-report/settings": {}, + "/enterprise/vm/list": {}, + "/enterprise/vm/iso": {}, + "/enterprise/vm/templates": {}, + "/enterprise/vm/networks": {}, + "/enterprise/vm/storage-pools": {}, } var DynamicRoutes = []string{ diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index 8da9c9f85..844fef120 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -9,7 +9,7 @@ ErrNotLogin: "User not logged in: {{ .detail }}" ErrSessionDataNotFound: "Session expired" ErrSessionDataFormat: "Invalid session data format" ErrPasswordExpired: "Password expired: {{ .detail }}" -ErrNotSupportType: "Unsupported type: {{ .detail }}" +ErrNotSupportType: "Unsupported type: {{ .name }}" ErrProxy: "Request failed; check this node status: {{ .detail }}" ErrApiConfigStatusInvalid: "API access disabled: {{ .detail }}" ErrApiConfigKeyInvalid: "Invalid API key: {{ .detail }}" @@ -36,6 +36,21 @@ ErrEntranceFormat: "Security entry {{ .name }} is not supported. Check and try a # common ErrDemoEnvironment: "Demo server, this operation is prohibited!" ErrCmdTimeout: "Command execution timeout!" +ErrVMNotFound: "Virtual machine does not exist" +ErrVMAlreadyExists: "Virtual machine already exists" +ErrVMRunning: "Virtual machine is running" +ErrVMLibvirtConnect: "Failed to connect to libvirt" +ErrVMPermission: "No permission to operate virtual machine: {{ .detail }}" +ErrVMNetworkNotFound: "Virtual machine network does not exist" +ErrVMInvalidPath: "Invalid virtual machine path" +ErrVMInvalidInput: "Invalid virtual machine parameter" +ErrVMCommandFailed: "Virtual machine command failed: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "Security entrance information error, check and try again!" ErrGroupIsDefault: "Default group, unable to delete" ErrGroupIsInUse: "The group is in use and cannot be deleted." diff --git a/core/i18n/lang/es-ES.yaml b/core/i18n/lang/es-ES.yaml index 024ad76e0..05fa3b1a0 100644 --- a/core/i18n/lang/es-ES.yaml +++ b/core/i18n/lang/es-ES.yaml @@ -35,6 +35,21 @@ ErrEntranceFormat: "La entrada de seguridad {{ .name }} no está actualmente sop # common ErrDemoEnvironment: 'No disponible en demo' ErrCmdTimeout: 'Comando agotó tiempo' +ErrVMNotFound: "La máquina virtual no existe" +ErrVMAlreadyExists: "La máquina virtual ya existe" +ErrVMRunning: "La máquina virtual está en ejecución" +ErrVMLibvirtConnect: "Error al conectar con libvirt: {{ .detail }}" +ErrVMPermission: "Sin permiso para operar la máquina virtual: {{ .detail }}" +ErrVMNetworkNotFound: "La red de la máquina virtual no existe: {{ .detail }}" +ErrVMInvalidPath: "Ruta de máquina virtual no válida: {{ .detail }}" +ErrVMInvalidInput: "Parámetro de máquina virtual no válido: {{ .detail }}" +ErrVMCommandFailed: "Error al ejecutar el comando de la máquina virtual: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "Error en la información de entrada de seguridad, por favor revise e intente de nuevo" ErrGroupIsDefault: "Grupo predeterminado, no se puede eliminar" ErrGroupIsInUse: "El grupo está en uso y no se puede eliminar." diff --git a/core/i18n/lang/fa.yaml b/core/i18n/lang/fa.yaml index 91587fa03..acf09b5a1 100644 --- a/core/i18n/lang/fa.yaml +++ b/core/i18n/lang/fa.yaml @@ -9,7 +9,7 @@ ErrNotLogin: "کاربر وارد نشده است: {{ .detail }}" ErrSessionDataNotFound: "جلسه منقضی شده است" ErrSessionDataFormat: "فرمت داده جلسه نامعتبر است" ErrPasswordExpired: "رمز عبور منقضی شده است: {{ .detail }}" -ErrNotSupportType: "نوع پشتیبانی نمی‌شود: {{ .detail }}" +ErrNotSupportType: "نوع پشتیبانی نمی‌شود: {{ .name }}" ErrProxy: "درخواست ناموفق بود؛ وضعیت این گره را بررسی کنید: {{ .detail }}" ErrApiConfigStatusInvalid: "دسترسی API غیرفعال است: {{ .detail }}" ErrApiConfigKeyInvalid: "کلید API نامعتبر است: {{ .detail }}" diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml index 8633164fa..ffddcf429 100644 --- a/core/i18n/lang/ja.yaml +++ b/core/i18n/lang/ja.yaml @@ -30,6 +30,21 @@ ErrEntranceFormat: "セキュリティエントランス {{ .name }} は現在 # common ErrDemoEnvironment: 'デモ環境では利用できません' ErrCmdTimeout: 'コマンドがタイムアウトしました' +ErrVMNotFound: "仮想マシンが存在しません" +ErrVMAlreadyExists: "仮想マシンは既に存在します" +ErrVMRunning: "仮想マシンは実行中です" +ErrVMLibvirtConnect: "libvirt への接続に失敗しました: {{ .detail }}" +ErrVMPermission: "仮想マシンを操作する権限がありません: {{ .detail }}" +ErrVMNetworkNotFound: "仮想マシンネットワークが存在しません: {{ .detail }}" +ErrVMInvalidPath: "仮想マシンのパスが無効です: {{ .detail }}" +ErrVMInvalidInput: "仮想マシンのパラメータが無効です: {{ .detail }}" +ErrVMCommandFailed: "仮想マシンコマンドの実行に失敗しました: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "セキュリティ情報エラー、再確認してください!" ErrGroupIsDefault: "デフォルトグループの削除はできません" ErrGroupIsInUse: "グループは使用中のため、削除できません。" diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml index ef566d601..61d17f18a 100644 --- a/core/i18n/lang/ko.yaml +++ b/core/i18n/lang/ko.yaml @@ -30,6 +30,21 @@ ErrEntranceFormat: "보안 입구 {{ .name }}은(는) 현재 지원되지 않습 # common ErrDemoEnvironment: '데모 환경에서는 불가' ErrCmdTimeout: '명령이 시간 초과했습니다' +ErrVMNotFound: "가상 머신이 존재하지 않습니다" +ErrVMAlreadyExists: "가상 머신이 이미 존재합니다" +ErrVMRunning: "가상 머신이 실행 중입니다" +ErrVMLibvirtConnect: "libvirt 연결 실패: {{ .detail }}" +ErrVMPermission: "가상 머신 작업 권한이 없습니다: {{ .detail }}" +ErrVMNetworkNotFound: "가상 머신 네트워크가 존재하지 않습니다: {{ .detail }}" +ErrVMInvalidPath: "가상 머신 경로가 유효하지 않습니다: {{ .detail }}" +ErrVMInvalidInput: "가상 머신 매개변수가 유효하지 않습니다: {{ .detail }}" +ErrVMCommandFailed: "가상 머신 명령 실행 실패: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "보안 정보 오류입니다. 확인 후 다시 시도하십시오" ErrGroupIsDefault: "기본 그룹은 삭제할 수 없습니다" ErrGroupIsInUse: "그룹이 사용 중이므로 삭제할 수 없습니다." diff --git a/core/i18n/lang/ms.yaml b/core/i18n/lang/ms.yaml index 4e27a47ee..7a8003ccc 100644 --- a/core/i18n/lang/ms.yaml +++ b/core/i18n/lang/ms.yaml @@ -30,6 +30,21 @@ ErrEntranceFormat: "Pintu masuk keselamatan {{ .name }} tidak disokong buat masa # common ErrDemoEnvironment: 'Tidak tersedia dalam demo' ErrCmdTimeout: 'Arahan tamat masa' +ErrVMNotFound: "Mesin maya tidak wujud" +ErrVMAlreadyExists: "Mesin maya sudah wujud" +ErrVMRunning: "Mesin maya sedang berjalan" +ErrVMLibvirtConnect: "Gagal menyambung ke libvirt: {{ .detail }}" +ErrVMPermission: "Tiada kebenaran untuk mengendalikan mesin maya: {{ .detail }}" +ErrVMNetworkNotFound: "Rangkaian mesin maya tidak wujud: {{ .detail }}" +ErrVMInvalidPath: "Laluan mesin maya tidak sah: {{ .detail }}" +ErrVMInvalidInput: "Parameter mesin maya tidak sah: {{ .detail }}" +ErrVMCommandFailed: "Arahan mesin maya gagal: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "Maklumat pintu masuk keselamatan salah, sila periksa dan cuba lagi" ErrGroupIsDefault: "Kumpulan lalai tidak boleh dihapuskan" ErrGroupIsInUse: "Kumpulan sedang digunakan dan tidak boleh dipadam." diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml index e917fa1f0..44440dce4 100644 --- a/core/i18n/lang/pt-BR.yaml +++ b/core/i18n/lang/pt-BR.yaml @@ -30,6 +30,21 @@ ErrEntranceFormat: "A entrada de segurança {{ .name }} não é suportada atualm # common ErrDemoEnvironment: 'Indisponível em demo' ErrCmdTimeout: 'Comando expirou' +ErrVMNotFound: "Máquina virtual não existe" +ErrVMAlreadyExists: "Máquina virtual já existe" +ErrVMRunning: "Máquina virtual está em execução" +ErrVMLibvirtConnect: "Falha ao conectar ao libvirt: {{ .detail }}" +ErrVMPermission: "Sem permissão para operar a máquina virtual: {{ .detail }}" +ErrVMNetworkNotFound: "Rede da máquina virtual não existe: {{ .detail }}" +ErrVMInvalidPath: "Caminho da máquina virtual inválido: {{ .detail }}" +ErrVMInvalidInput: "Parâmetro da máquina virtual inválido: {{ .detail }}" +ErrVMCommandFailed: "Falha no comando da máquina virtual: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "Erro nas informações de entrada de segurança, por favor, verifique e tente novamente" ErrGroupIsDefault: "Grupo padrão não pode ser excluído" ErrGroupIsInUse: "O grupo está em uso e não pode ser excluído." diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml index a50a63b68..ec59cbb10 100644 --- a/core/i18n/lang/ru.yaml +++ b/core/i18n/lang/ru.yaml @@ -30,6 +30,21 @@ ErrEntranceFormat: "Защищенный вход {{ .name }} в настоящ # common ErrDemoEnvironment: 'Недоступно в демо' ErrCmdTimeout: 'Команда завершилась по тайм-ауту' +ErrVMNotFound: "Виртуальная машина не существует" +ErrVMAlreadyExists: "Виртуальная машина уже существует" +ErrVMRunning: "Виртуальная машина запущена" +ErrVMLibvirtConnect: "Не удалось подключиться к libvirt: {{ .detail }}" +ErrVMPermission: "Нет разрешения на операцию с виртуальной машиной: {{ .detail }}" +ErrVMNetworkNotFound: "Сеть виртуальной машины не существует: {{ .detail }}" +ErrVMInvalidPath: "Недопустимый путь виртуальной машины: {{ .detail }}" +ErrVMInvalidInput: "Недопустимые параметры виртуальной машины: {{ .detail }}" +ErrVMCommandFailed: "Ошибка выполнения команды виртуальной машины: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "Ошибка информации о безопасном входе, проверьте и повторите попытку" ErrGroupIsDefault: "Группу по умолчанию нельзя удалить" ErrGroupIsInUse: "Группа используется и не может быть удалена." diff --git a/core/i18n/lang/tr.yaml b/core/i18n/lang/tr.yaml index 7a1506d2a..3afa825b4 100644 --- a/core/i18n/lang/tr.yaml +++ b/core/i18n/lang/tr.yaml @@ -30,6 +30,21 @@ ErrEntranceFormat: "{{ .name }} güvenlik girişi şu anda desteklenmiyor. Lütf # common ErrDemoEnvironment: 'Demo modunda yok' ErrCmdTimeout: 'Komut zaman aşımına uğradı' +ErrVMNotFound: "Sanal makine mevcut değil" +ErrVMAlreadyExists: "Sanal makine zaten mevcut" +ErrVMRunning: "Sanal makine çalışıyor" +ErrVMLibvirtConnect: "libvirt bağlantısı başarısız: {{ .detail }}" +ErrVMPermission: "Sanal makine işlemi için izin yok: {{ .detail }}" +ErrVMNetworkNotFound: "Sanal makine ağı mevcut değil: {{ .detail }}" +ErrVMInvalidPath: "Geçersiz sanal makine yolu: {{ .detail }}" +ErrVMInvalidInput: "Geçersiz sanal makine parametresi: {{ .detail }}" +ErrVMCommandFailed: "Sanal makine komutu başarısız: {{ .err }}" +VMSnapshotCreateTask: "Create snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotCreateStep: "Create virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" +VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" +VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" ErrEntrance: "Güvenlik girişi bilgi hatası, lütfen kontrol edip tekrar deneyin" ErrGroupIsDefault: "Varsayılan grup, silinemez" ErrGroupIsInUse: "Grup kullanımda ve silinemez." diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index 2c16e6ce8..93e3a0769 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -9,7 +9,7 @@ ErrNotLogin: "使用者未登入: {{ .detail }}" ErrSessionDataNotFound: "目前工作階段已過期!" ErrSessionDataFormat: "目前工作階段資料格式異常!" ErrPasswordExpired: "目前密碼已過期: {{ .detail }}" -ErrNotSupportType: "系統暫不支援目前類型: {{ .detail }}" +ErrNotSupportType: "系統暫不支援目前類型: {{ .name }}" ErrProxy: "請求錯誤,請檢查該節點狀態: {{ .detail }}" ErrApiConfigStatusInvalid: "API 介面禁止存取: {{ .detail }}" ErrApiConfigKeyInvalid: "API 金鑰錯誤: {{ .detail }}" @@ -30,6 +30,21 @@ ErrEntranceFormat: "暫不支援安全入口 {{ .name }} ,請檢查後重試 #common ErrDemoEnvironment: "示範伺服器,禁止此操作!" ErrCmdTimeout: "指令執行逾時!" +ErrVMNotFound: "虛擬機不存在" +ErrVMAlreadyExists: "虛擬機已存在" +ErrVMRunning: "虛擬機正在執行" +ErrVMLibvirtConnect: "連接 libvirt 失敗: {{ .detail }}" +ErrVMPermission: "沒有虛擬機操作權限: {{ .detail }}" +ErrVMNetworkNotFound: "虛擬機網路不存在: {{ .detail }}" +ErrVMInvalidPath: "虛擬機路徑非法: {{ .detail }}" +ErrVMInvalidInput: "虛擬機參數非法: {{ .detail }}" +ErrVMCommandFailed: "虛擬機指令執行失敗: {{ .err }}" +VMSnapshotCreateTask: "建立虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]" +VMSnapshotCreateStep: "建立虛擬機快照 [{{ .snapshot }}]" +VMSnapshotRecoverTask: "復原虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]" +VMSnapshotRecoverStep: "復原虛擬機快照 [{{ .snapshot }}]" +VMSnapshotDeleteTask: "刪除虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]" +VMSnapshotDeleteStep: "刪除虛擬機快照 [{{ .snapshot }}]" ErrEntrance: "安全入口資訊錯誤,請檢查後再試。" ErrGroupIsDefault: "預設分組無法刪除" ErrGroupIsInUse: "分組正被使用,無法刪除。" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index a8b696e5b..ea87dd25c 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -9,7 +9,7 @@ ErrNotLogin: "用户未登录: {{ .detail }}" ErrSessionDataNotFound: "当前会话已过期!" ErrSessionDataFormat: "当前会话数据格式异常!" ErrPasswordExpired: "当前密码已过期: {{ .detail }}" -ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}" +ErrNotSupportType: "系统暂不支持当前类型: {{ .name }}" ErrProxy: "请求错误,请检查该节点状态: {{ .detail }}" ErrApiConfigStatusInvalid: "API 接口禁止访问: {{ .detail }}" ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}" @@ -36,6 +36,21 @@ ErrEntranceFormat: "暂不支持安全入口 {{ .name }} ,请检查后重试 #common ErrDemoEnvironment: "演示服务器,禁止此操作!" ErrCmdTimeout: "命令执行超时!" +ErrVMNotFound: "虚拟机不存在" +ErrVMAlreadyExists: "虚拟机已存在" +ErrVMRunning: "虚拟机正在运行" +ErrVMLibvirtConnect: "连接 libvirt 失败: {{ .detail }}" +ErrVMPermission: "没有虚拟机操作权限: {{ .detail }}" +ErrVMNetworkNotFound: "虚拟机网络不存在: {{ .detail }}" +ErrVMInvalidPath: "虚拟机路径非法: {{ .detail }}" +ErrVMInvalidInput: "虚拟机参数非法: {{ .detail }}" +ErrVMCommandFailed: "虚拟机命令执行失败: {{ .err }}" +VMSnapshotCreateTask: "创建虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]" +VMSnapshotCreateStep: "创建虚拟机快照 [{{ .snapshot }}]" +VMSnapshotRecoverTask: "恢复虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]" +VMSnapshotRecoverStep: "恢复虚拟机快照 [{{ .snapshot }}]" +VMSnapshotDeleteTask: "删除虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]" +VMSnapshotDeleteStep: "删除虚拟机快照 [{{ .snapshot }}]" ErrEntrance: "安全入口信息错误,请检查后重试!" ErrGroupIsDefault: "默认分组,无法删除" ErrGroupIsInUse: "分组正被使用,无法删除" diff --git a/core/init/migration/helper/menu.go b/core/init/migration/helper/menu.go index f6de356e5..80dba0607 100644 --- a/core/init/migration/helper/menu.go +++ b/core/init/migration/helper/menu.go @@ -137,6 +137,15 @@ func LoadMenus() string { Path: "/enterprise/ops-report", Sort: 360, }, "UserManagement") + item[i].Children = UpsertMenuByLabel(item[i].Children, dto.ShowMenu{ + ID: "123", + Disabled: false, + Title: "xpack.vm.title", + IsShow: true, + Label: "VirtualMachine", + Path: "/enterprise/vm", + Sort: 550, + }, "MonitorDashboard") break } } @@ -181,6 +190,7 @@ func MenuSort() []dto.MenuLabelSort { {Label: "OpsReport", Sort: 360}, {Label: "Upage", Sort: 400}, {Label: "MonitorDashboard", Sort: 500}, + {Label: "VirtualMachine", Sort: 550}, {Label: "Tamper", Sort: 600}, {Label: "Cluster", Sort: 700}, {Label: "Sync", Sort: 800}, diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index 894e4a8d7..304aabe5f 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -45,6 +45,7 @@ func Init() { migrations.AddAIProxyMenu, migrations.AddSkillsHubMenu, migrations.UpdateXpackSyncMenu, + migrations.AddVirtualMachineMenu, migrations.AddOperationLogUser, migrations.AddLoginLogUser, migrations.AddAlertAuditUser, diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index d91347897..de5fa2e00 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -1326,6 +1326,55 @@ var AddOpsReportMenu = &gormigrate.Migration{ }, } +var AddVirtualMachineMenu = &gormigrate.Migration{ + ID: "20260623-add-virtual-machine-menu", + Migrate: func(tx *gorm.DB) error { + if !global.CONF.Base.IsEnterprise { + return nil + } + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + if menuJSON == "" { + menuJSON = helper.LoadMenus() + } + + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + newItem := dto.ShowMenu{ + ID: "123", + Disabled: false, + Title: "xpack.vm.title", + IsShow: true, + Label: "VirtualMachine", + Path: "/enterprise/vm", + Sort: 550, + } + + for i := range menus { + if menus[i].Label != "Xpack-Menu" { + continue + } + menus[i].Children = helper.UpsertMenuByLabel(menus[i].Children, newItem, "MonitorDashboard") + break + } + + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error + }, +} + var AddOperationLogUser = &gormigrate.Migration{ ID: "20260424-add-operation-log-user", Migrate: func(tx *gorm.DB) error { diff --git a/core/init/validator/validator.go b/core/init/validator/validator.go index 413968b29..ede93b395 100644 --- a/core/init/validator/validator.go +++ b/core/init/validator/validator.go @@ -5,7 +5,7 @@ import ( "unicode" "github.com/1Panel-dev/1Panel/core/global" - + "github.com/1Panel-dev/1Panel/core/utils/re" "github.com/go-playground/validator/v10" ) @@ -23,6 +23,12 @@ func Init() { if err := validator.RegisterValidation("base_setting_key", checkBaseSettingKey); err != nil { panic(err) } + if err := validator.RegisterValidation("vm_name", checkVMNamePattern); err != nil { + panic(err) + } + if err := validator.RegisterValidation("vm_common", checkVMCommonPattern); err != nil { + panic(err) + } global.VALID = validator } @@ -100,3 +106,12 @@ func checkBaseSettingKey(fl validator.FieldLevel) bool { _, ok := baseSettingKeys[fl.Field().String()] return ok } + +func checkVMNamePattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + return re.GetRegex(re.VMNameValidationPattern).MatchString(value) +} +func checkVMCommonPattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + return re.GetRegex(re.VMCommonPattern).MatchString(value) +} diff --git a/core/utils/re/re.go b/core/utils/re/re.go index ea8db84ea..9ac4f08aa 100644 --- a/core/utils/re/re.go +++ b/core/utils/re/re.go @@ -7,6 +7,8 @@ import ( const ( OrderByValidationPattern = `^[a-zA-Z_][a-zA-Z0-9_]*$` + VMNameValidationPattern = `^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$` + VMCommonPattern = `^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,63}$` ) var regexMap = make(map[string]*regexp.Regexp) @@ -14,6 +16,8 @@ var regexMap = make(map[string]*regexp.Regexp) func Init() { patterns := []string{ OrderByValidationPattern, + VMNameValidationPattern, + VMCommonPattern, } for _, pattern := range patterns { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7c4575490..92e8fbeb0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "@codemirror/legacy-modes": "^6.5.3", "@codemirror/theme-one-dark": "^6.1.3", "@element-plus/icons-vue": "^1.1.4", + "@novnc/novnc": "^1.7.0", "@vue-office/docx": "^1.6.2", "@vue-office/excel": "^1.7.8", "@vueuse/core": "^14.3.0", @@ -2002,6 +2003,12 @@ "@emnapi/runtime": "^1.7.1" } }, + "node_modules/@novnc/novnc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@novnc/novnc/-/novnc-1.7.0.tgz", + "integrity": "sha512-ucEJOx4T2avIRCleodk7YobZj5O2Ga2AeLfQ69A/yjG9HHba2+PDgwSkN3FttrmG+70ZGx21sElNFouK13RzyA==", + "license": "MPL-2.0" + }, "node_modules/@oxc-project/types": { "version": "0.133.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index f7a789043..da1578244 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "@codemirror/legacy-modes": "^6.5.3", "@codemirror/theme-one-dark": "^6.1.3", "@element-plus/icons-vue": "^1.1.4", + "@novnc/novnc": "^1.7.0", "@vue-office/docx": "^1.6.2", "@vue-office/excel": "^1.7.8", "@vueuse/core": "^14.3.0", diff --git a/frontend/src/api/interface/index.ts b/frontend/src/api/interface/index.ts index 26edf4d53..2ed1ebd4f 100644 --- a/frontend/src/api/interface/index.ts +++ b/frontend/src/api/interface/index.ts @@ -25,6 +25,7 @@ export interface SearchWithPage { orderBy?: string; order?: string; name?: string; + type?: string; } export interface CommonModel { id: number; diff --git a/frontend/src/components/v-charts/components/Line.vue b/frontend/src/components/v-charts/components/Line.vue index e82a49a0c..7709017a5 100644 --- a/frontend/src/components/v-charts/components/Line.vue +++ b/frontend/src/components/v-charts/components/Line.vue @@ -112,10 +112,16 @@ function initChart() { } let itemChart = echarts?.getInstanceByDom(chartDom); const optionItem = itemChart?.getOption(); - const itemSelect = optionItem?.legend; + const itemSelect = Array.isArray(optionItem?.legend) ? optionItem?.legend?.[0] : optionItem?.legend; if (itemChart == null) { itemChart = echarts.init(chartDom); } + const style = getComputedStyle(document.documentElement); + const primaryTextColor = style.getPropertyValue('--el-text-color-primary').trim() || '#303133'; + const regularTextColor = style.getPropertyValue('--el-text-color-regular').trim() || '#606266'; + const secondaryTextColor = style.getPropertyValue('--el-text-color-secondary').trim() || '#909399'; + const borderColor = style.getPropertyValue('--el-border-color-light').trim() || '#e4e7ed'; + const tooltipBackgroundColor = style.getPropertyValue('--el-bg-color-overlay').trim() || '#ffffff'; const series = []; if (props.option?.yData?.length) { @@ -135,14 +141,25 @@ function initChart() { if (props.option.yAxis && props.option.yAxis.length > 0) { props.option.yAxis.forEach((item: any) => { yAxis.push({ + ...item, splitLine: { + ...item.splitLine, show: true, lineStyle: { + ...item.splitLine?.lineStyle, type: 'dashed', opacity: isDarkTheme.value ? 0.1 : 1, + color: borderColor, }, }, - ...item, + axisLabel: { + color: secondaryTextColor, + ...item.axisLabel, + }, + nameTextStyle: { + color: secondaryTextColor, + ...item.nameTextStyle, + }, }); }); } @@ -209,34 +226,74 @@ function initChart() { left: 'center', text: props.option.title, show: props.option.title, + textStyle: { + color: primaryTextColor, + fontWeight: 500, + }, }, ], zlevel: 1, z: 1, tooltip: { appendToBody: true, + backgroundColor: tooltipBackgroundColor, + borderColor, + textStyle: { + color: regularTextColor, + }, ...tooltip, extraCssText: `${tooltip.extraCssText || ''}; z-index: 3000;`, }, grid, - legend: itemSelect || { + legend: { + ...itemSelect, right: grid.right || 10, itemWidth: 8, textStyle: { - color: '#646A73', + ...itemSelect?.textStyle, + color: regularTextColor, }, icon: 'circle', }, - xAxis: { data: props.option.xData, boundaryGap: false }, + xAxis: { + ...props.option.xAxis, + data: props.option.xData, + boundaryGap: false, + axisLabel: { + ...props.option.xAxis?.axisLabel, + color: secondaryTextColor, + }, + axisLine: { + ...props.option.xAxis?.axisLine, + lineStyle: { + ...props.option.xAxis?.axisLine?.lineStyle, + color: borderColor, + }, + }, + axisTick: { + ...props.option.xAxis?.axisTick, + lineStyle: { + ...props.option.xAxis?.axisTick?.lineStyle, + color: borderColor, + }, + }, + }, yAxis: props.option.yAxis ? yAxis : { name: '( ' + props.option.formatStr + ' )', + nameTextStyle: { + color: secondaryTextColor, + }, + axisLabel: { + color: secondaryTextColor, + }, splitLine: { //分隔辅助线 lineStyle: { type: 'dashed', //线的类型 虚线0 opacity: isDarkTheme.value ? 0.1 : 1, //透明度 + color: borderColor, }, }, }, diff --git a/frontend/src/global/form-rules.ts b/frontend/src/global/form-rules.ts index 5e84a2d8f..f24c50d81 100644 --- a/frontend/src/global/form-rules.ts +++ b/frontend/src/global/form-rules.ts @@ -346,6 +346,32 @@ const checkAlias: RuleValidator = (_rule, value, callback) => { } }; +const checkVMName: RuleValidator = (_rule, value, callback) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.vmName'))); + } else { + const reg = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/; + if (!reg.test(value)) { + callback(new Error(i18n.global.t('commons.rule.vmName'))); + } else { + callback(); + } + } +}; + +const checkVMNetwork: RuleValidator = (_rule, value, callback) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.vmNetwork'))); + } else { + const reg = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,63}$/; + if (!reg.test(value)) { + callback(new Error(i18n.global.t('commons.rule.vmNetwork'))); + } else { + callback(); + } + } +}; + const checkDomain: RuleValidator = (_rule, value, callback) => { if (value === '' || typeof value === 'undefined' || value == null) { callback(new Error(i18n.global.t('commons.rule.domain'))); @@ -697,6 +723,8 @@ interface CommonRule { paramSimple: FormItemRule; paramHttp: FormItemRule; phone: FormItemRule; + vmName: FormItemRule; + vmNetwork: FormItemRule; } export const Rules: CommonRule = { @@ -945,4 +973,14 @@ export const Rules: CommonRule = { validator: checkAlias, trigger: 'blur', }, + vmName: { + required: true, + validator: checkVMName, + trigger: 'blur', + }, + vmNetwork: { + required: true, + validator: checkVMNetwork, + trigger: ['blur', 'change'], + }, }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index de051e99b..6c813802c 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -56,6 +56,7 @@ const message = { power: 'Submit', search: 'Search', refresh: 'Refresh', + reconnect: 'Reconnect', get: 'Get', upgrade: 'Upgrade', update: 'Update', @@ -114,6 +115,8 @@ const message = { total: 'Total {0}', name: 'Name', type: 'Type', + card: 'Card', + table: 'Table', status: 'Status', statusSuccess: 'Success', statusFailed: 'Failed', @@ -285,6 +288,9 @@ const message = { length128Err: 'Length cannot exceed 128 characters', maxLength: 'Length cannot exceed {0} characters', alias: 'Supports English, numbers, - and _, length 1-128, and cannot start or end with -_.', + vmName: 'Supports English letters, numbers, ".", "-", and "_"; length: 1-64; must start with a letter or number.', + vmNetwork: + 'Supports English letters, numbers, ".", ":", "-", and "_"; length: 1-64; must start with a letter or number.', }, res: { paramError: 'Request failed. Try again later.', @@ -2059,6 +2065,7 @@ const message = { sync: 'Resource Sync', exchange: 'Resource Sync', xapp: 'App', + vm: 'Virtual Machines', }, websiteLog: 'Website logs', runLog: 'Run logs', @@ -5561,6 +5568,134 @@ const message = { selectTargetFirst: 'Please select at least one target node.', submitSuccess: 'Sync task submitted.', }, + vm: { + vm: 'VM', + title: 'Virtual Machines', + dependencies: 'Dependencies', + dependencyPurpose: 'Purpose', + dependencyPurposeMap: { + kvm: 'Provides hardware virtualization support for accelerated VM execution.', + libvirt: 'Provides VM management services for lifecycle control and resource scheduling.', + storage: 'Provides the default storage directory or pool for VM disk files.', + }, + dependencyNotReady: 'VM dependency checks failed. Fix the dependencies before using virtual machines.', + helperUnavailable: + 'VM capability initialization failed. Check core logs or upgrade again before using VMs.', + helperNotStart: + 'The VM runtime environment is not ready. Fix the host virtualization service before using VMs.', + cpu: 'CPU', + memory: 'Memory', + disk: 'Disk', + memoryLimitHelper: 'Maximum allocatable memory: {0}', + memoryLimitExceeded: + 'VM memory allocation will exceed allocatable host memory. Reduce memory and try again.', + storageLimitHelper: 'Current free space in the target storage pool: {0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: 'Disk Size', + diskPath: 'Disk Path', + diskPathHelper: + 'Optional. If empty, the system creates .qcow2 in the selected storage pool directory. Custom values must be absolute paths.', + imagePath: 'Image Path', + imagePathHelper: 'Optional. If set, use an existing qcow2 image absolute path.', + isoPath: 'ISO Path', + isoPathHelper: 'Optional. If set, use an existing ISO absolute path.', + storagePool: 'Storage Pool', + storagePoolHelper: 'Uses the default storage pool by default to create and manage VM disk volumes.', + network: 'Network', + networkHelper: + 'Uses the default NAT network by default. You can also select or enter an existing libvirt network name.', + bridgeName: 'Bridge Name', + natNetworkHelper: + 'NAT creates a libvirt virtual network for VM outbound access. The internal bridge name is generated automatically and no host bridge is required.', + bridgeNetworkHelper: + 'Bridge connects VMs to an existing host Linux bridge so they can join the same Layer 2 network as the host.', + bridgeNameHelper: + 'Select an existing Linux bridge on the host, such as br0. Physical NICs are not bridges, and docker/libvirt managed bridges cannot be selected here.', + natCIDRHelper: 'CIDR, gateway, and DHCP range are used by the new NAT virtual network.', + networkCIDR: 'Network CIDR', + gateway: 'Gateway', + dhcpStart: 'DHCP Start', + dhcpEnd: 'DHCP End', + bootOrder: 'Boot Order', + bootOrderHelper: + 'HD boots from disk first and then tries the ISO if the disk is not bootable. CD-ROM boots from ISO first.', + startAfterCreate: 'Start after creation', + autoStart: 'Autostart', + resourceAutoStart: 'Autostart', + capacity: 'Capacity', + available: 'Available', + vmCount: 'Linked VMs', + usedIPs: 'Used IPs', + syncFromServer: 'Sync from Server', + notFound: 'Not found', + resourceStatus: { + running: 'Running', + inactive: 'Inactive', + building: 'Building', + degraded: 'Degraded', + inaccessible: 'Inaccessible', + unknown: 'Unknown', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: 'New name', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: 'Console', + consoleNotReady: 'The VNC console is unavailable. Start the VM and check its VNC graphics configuration.', + consoleConnecting: 'Connecting to the VNC console...', + consoleDisconnected: 'The VNC console has been disconnected.', + consoleConnectFailed: 'Failed to connect to the VNC console. Check the console proxy service.', + consoleCredentialsRequired: 'The VNC console requires credentials. Check the backend proxy configuration.', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: 'Network Interface', + device: 'Device', + path: 'Path', + start: 'Start', + shutdown: 'Shutdown', + reboot: 'Reboot', + destroy: 'Force Stop', + suspend: 'Suspend', + resume: 'Resume', + operateConfirm: 'Confirm {0} virtual machine [{1}]?', + forceDeleteConfirm: 'The virtual machine is running. It will be force stopped before deletion. Continue?', + createResource: 'Create {0}', + isoSource: 'ISO Source', + isoUploadDirHelper: 'The file will be uploaded to: {0}', + isoSize: 'ISO Size', + dir: 'Local Directory', + }, cluster: { cluster: 'Application High Availability', name: 'Cluster Name', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index a53c38944..c9c85d7f2 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -115,6 +115,8 @@ const message = { total: 'Total {0}', name: 'Nombre', type: 'Tipo', + card: 'Tarjeta', + table: 'Tabla', status: 'Estado', statusSuccess: 'Éxito', statusFailed: 'Fallido', @@ -2102,6 +2104,7 @@ const message = { sync: 'Sincronización de Recursos', exchange: 'Sincronización de Recursos', xapp: 'App', + vm: 'Gestión de máquinas virtuales', }, websiteLog: 'Logs de sitio web', runLog: 'Logs de ejecución', @@ -5611,6 +5614,140 @@ const message = { selectTargetFirst: 'Seleccione al menos un nodo de destino.', submitSuccess: 'La tarea de sincronización se ha enviado correctamente.', }, + vm: { + vm: 'Máquina virtual', + title: 'Gestión de máquinas virtuales', + dependencies: 'Dependencias', + dependencyPurpose: 'Propósito', + dependencyPurposeMap: { + kvm: 'Proporciona virtualización por hardware para acelerar la ejecución de las VM.', + libvirt: 'Proporciona servicios de gestión de VM para el ciclo de vida y la planificación de recursos.', + storage: + 'Proporciona el directorio o pool de almacenamiento predeterminado para los archivos de disco de la VM.', + }, + dependencyNotReady: + 'Las comprobaciones de dependencias de la VM han fallado. Corrija las dependencias antes de usar máquinas virtuales.', + helperUnavailable: + 'La inicialización de la capacidad de VM falló. Revise los registros de Core o actualice de nuevo antes de usar máquinas virtuales.', + helperNotStart: + 'El servicio de VM no se está ejecutando. Inicie el servicio antes de usar máquinas virtuales.', + cpu: 'CPU', + memory: 'Memoria', + disk: 'Disco', + memoryLimitHelper: 'Memoria máxima asignable: {0}', + memoryLimitExceeded: + 'La asignación de memoria de la VM superará la memoria asignable del host. Reduzca la memoria e inténtelo de nuevo.', + storageLimitHelper: 'Espacio libre actual en el pool de almacenamiento de destino: {0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: 'Tamaño del disco', + diskPath: 'Ruta del disco', + diskPathHelper: + 'Opcional. Si está vacío, el sistema crea .qcow2 en el directorio del pool de almacenamiento seleccionado. Los valores personalizados deben ser rutas absolutas.', + imagePath: 'Ruta de la imagen', + imagePathHelper: 'Opcional. Si se define, use la ruta absoluta de una imagen qcow2 existente.', + isoPath: 'Ruta ISO', + isoPathHelper: 'Opcional. Si se define, use la ruta absoluta de un ISO existente.', + storagePool: 'Pool de almacenamiento', + storagePoolHelper: + 'Usa el pool de almacenamiento default de forma predeterminada para crear y gestionar volúmenes de disco de VM.', + network: 'Red', + networkHelper: + 'Usa la red NAT default de forma predeterminada. También puede seleccionar o introducir un nombre de red libvirt existente.', + bridgeName: 'Nombre del bridge', + natNetworkHelper: + 'NAT crea una red virtual libvirt para el acceso saliente de la VM. El nombre del bridge interno se genera automáticamente y no se requiere un bridge del host.', + bridgeNetworkHelper: + 'Bridge conecta las VM a un Linux bridge existente del host para que entren en la misma red de capa 2 que el host.', + bridgeNameHelper: + 'Seleccione un Linux bridge existente en el host, como br0. Las NIC físicas no son bridges, y los bridges gestionados por docker/libvirt no pueden seleccionarse aquí.', + natCIDRHelper: 'CIDR, puerta de enlace y rango DHCP se usarán en la nueva red virtual NAT.', + networkCIDR: 'CIDR de red', + gateway: 'Puerta de enlace', + dhcpStart: 'Inicio DHCP', + dhcpEnd: 'Fin DHCP', + bootOrder: 'Orden de arranque', + bootOrderHelper: + 'HD arranca primero desde el disco y prueba el ISO si el disco no es arrancable. CD-ROM arranca primero desde el ISO.', + startAfterCreate: 'Iniciar después de crear', + autoStart: 'Inicio automático', + resourceAutoStart: 'Inicio automático', + capacity: 'Capacidad', + available: 'Disponible', + vmCount: 'VM vinculadas', + usedIPs: 'IP usadas', + syncFromServer: 'Sincronizar desde servidor', + notFound: 'No encontrado', + resourceStatus: { + running: 'En ejecución', + inactive: 'Inactivo', + building: 'Creando', + degraded: 'Degradado', + inaccessible: 'Inaccesible', + unknown: 'Desconocido', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: 'Nuevo nombre', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: 'Consola', + consoleNotReady: + 'La consola VNC no está disponible. Inicie la VM y compruebe su configuración gráfica VNC.', + consoleConnecting: 'Conectando a la consola VNC...', + consoleDisconnected: 'La consola VNC se ha desconectado.', + consoleConnectFailed: 'No se pudo conectar a la consola VNC. Compruebe el servicio proxy de consola.', + consoleCredentialsRequired: + 'La consola VNC requiere credenciales. Compruebe la configuración del proxy backend.', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: 'Interfaz de red', + device: 'Dispositivo', + path: 'Ruta', + start: 'Iniciar', + shutdown: 'Apagar', + reboot: 'Reiniciar', + destroy: 'Forzar detención', + suspend: 'Suspender', + resume: 'Reanudar', + operateConfirm: '¿Confirmar {0} máquina virtual [{1}]?', + forceDeleteConfirm: + 'La máquina virtual está en ejecución. Se detendrá forzosamente antes de eliminarla. ¿Continuar?', + createResource: 'Crear {0}', + isoSource: 'Origen ISO', + isoUploadDirHelper: 'El archivo se subirá a: {0}', + isoSize: 'Tamaño ISO', + dir: 'Directorio local', + }, cluster: { cluster: 'Alta disponibilidad de aplicaciones', name: 'Nombre del clúster', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index b4d31472c..8911eb48f 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -115,6 +115,8 @@ const message = { total: '合計{0}', name: '名前', type: 'タイプ', + card: 'カード', + table: 'テーブル', status: '状態', records: '記録', group: 'グループ', @@ -2070,6 +2072,7 @@ const message = { sync: 'リソース同期', exchange: 'リソース同期', xapp: 'App', + vm: '仮想マシン管理', }, websiteLog: 'ウェブサイトログ', runLog: 'ログを実行します', @@ -5579,6 +5582,137 @@ const message = { selectTargetFirst: '少なくとも1つのターゲットノードを選択してください。', submitSuccess: '同期タスクが送信されました。', }, + vm: { + vm: '仮想マシン', + title: '仮想マシン管理', + dependencies: '依存関係', + dependencyPurpose: '用途', + dependencyPurposeMap: { + kvm: 'ハードウェア仮想化機能を提供し、仮想マシンを高速に実行する基盤です。', + libvirt: '仮想マシン管理サービスを提供し、ライフサイクルとリソーススケジューリングを担当します。', + storage: '仮想マシンのディスクファイルを保存するデフォルトの保存先またはストレージプールを提供します。', + }, + dependencyNotReady: '仮想マシンの依存関係チェックに失敗しました。依存関係を処理してから使用してください。', + helperUnavailable: + '仮想マシン機能の初期化に失敗しました。Core ログを確認するか、再度アップグレードしてから使用してください。', + helperNotStart: + '仮想マシンの実行環境が準備できていません。ホストの仮想化サービスを処理してから使用してください。', + cpu: 'CPU', + memory: 'メモリ', + disk: 'ディスク', + memoryLimitHelper: '割り当て可能な最大メモリ:{0}', + memoryLimitExceeded: + 'VM のメモリ割り当てがホストの割り当て可能メモリを超えます。メモリを減らして再試行してください。', + storageLimitHelper: '対象ストレージプールの現在の空き容量:{0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: 'ディスク容量', + diskPath: 'ディスクパス', + diskPathHelper: + '空欄にできます。システムは選択したストレージプールディレクトリに 仮想マシン名.qcow2 を自動作成します。入力する場合は絶対パスである必要があります。', + imagePath: 'イメージパス', + imagePathHelper: '空欄にできます。入力する場合は既存の qcow2 イメージの絶対パスを指定してください。', + isoPath: 'ISO パス', + isoPathHelper: '空欄にできます。入力する場合は既存の ISO の絶対パスを指定してください。', + storagePool: 'ストレージプール', + storagePoolHelper: + 'デフォルトでは default ストレージプールを使用し、VM ディスクボリュームを作成・管理します。', + network: 'ネットワーク', + networkHelper: + 'デフォルトでは default NAT ネットワークを使用します。既存の libvirt ネットワーク名を選択または入力することもできます。', + bridgeName: 'ブリッジ名', + natNetworkHelper: + 'NAT は VM の外部通信に使用する libvirt 仮想ネットワークを作成します。内部ブリッジ名は自動生成されるため、ホストのブリッジ NIC を選択する必要はありません。', + bridgeNetworkHelper: + 'Bridge は VM をホスト上の既存 Linux bridge に接続し、ホストと同じ L2 ネットワークに参加させます。', + bridgeNameHelper: + 'br0 など、ホスト上に既に存在する Linux bridge を選択してください。物理 NIC は bridge ではなく、docker/libvirt 管理の bridge はここでは選択できません。', + natCIDRHelper: 'CIDR、ゲートウェイ、DHCP アドレス範囲は新しい NAT 仮想ネットワークに使用されます。', + networkCIDR: 'ネットワーク CIDR', + gateway: 'ゲートウェイ', + dhcpStart: 'DHCP 開始アドレス', + dhcpEnd: 'DHCP 終了アドレス', + bootOrder: '起動順序', + bootOrderHelper: + 'HD はディスクから優先起動し、起動できない場合に ISO を試します。CD-ROM は ISO から優先起動します。', + startAfterCreate: '作成後に起動', + autoStart: '自動起動', + resourceAutoStart: '自動起動', + capacity: '容量', + available: '利用可能', + vmCount: '関連 VM', + usedIPs: '使用済み IP', + syncFromServer: 'サーバーから同期', + notFound: '存在しません', + resourceStatus: { + running: '実行中', + inactive: '未起動', + building: '構築中', + degraded: '低下', + inaccessible: 'アクセス不可', + unknown: '不明', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: '新しい名前', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: 'コンソール', + consoleNotReady: 'VNC コンソールを使用できません。VM を起動し、VNC グラフィック設定を確認してください。', + consoleConnecting: 'VNC コンソールに接続中...', + consoleDisconnected: 'VNC コンソールの接続が切断されました。', + consoleConnectFailed: + 'VNC コンソールへの接続に失敗しました。コンソールプロキシサービスを確認してください。', + consoleCredentialsRequired: + 'VNC コンソールには認証情報が必要です。バックエンドプロキシ設定を確認してください。', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: 'ネットワークインターフェース', + device: 'デバイス', + path: 'パス', + start: '起動', + shutdown: 'シャットダウン', + reboot: '再起動', + destroy: '強制停止', + suspend: '一時停止', + resume: '再開', + operateConfirm: '{0} 仮想マシン [{1}] を実行しますか?', + forceDeleteConfirm: '仮想マシンは実行中です。削除前に強制停止します。続行しますか?', + createResource: '{0} を作成', + isoSource: 'ISO ソース', + isoUploadDirHelper: 'ファイルのアップロード先:{0}', + isoSize: 'ISO サイズ', + dir: 'ローカルディレクトリ', + }, cluster: { cluster: 'アプリケーションの高可用性', name: 'クラスタ名', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 9b6de6c44..99f726a3b 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -115,6 +115,8 @@ const message = { total: '총 {0}', name: '이름', type: '유형', + card: '카드', + table: '표', status: '상태', records: '기록', group: '그룹', @@ -2028,6 +2030,7 @@ const message = { sync: '리소스 동기화', exchange: '리소스 동기화', xapp: 'App', + vm: '가상 머신 관리', }, websiteLog: '웹사이트 로그', runLog: '실행 로그', @@ -5465,6 +5468,133 @@ const message = { selectTargetFirst: '최소 하나의 대상 노드를 선택하세요.', submitSuccess: '동기화 작업이 제출되었습니다.', }, + vm: { + vm: '가상 머신', + title: '가상 머신 관리', + dependencies: '종속성', + dependencyPurpose: '용도', + dependencyPurposeMap: { + kvm: '하드웨어 가상화 기능을 제공하며 가상 머신 가속 실행의 기반입니다.', + libvirt: '가상 머신 관리 서비스를 제공하며 수명 주기와 리소스 스케줄링을 담당합니다.', + storage: '가상 머신 디스크 파일을 저장하기 위한 기본 저장소 디렉터리 또는 스토리지 풀을 제공합니다.', + }, + dependencyNotReady: '가상 머신 종속성 검사를 통과하지 못했습니다. 종속성을 처리한 후 사용하세요.', + helperUnavailable: + '가상 머신 기능 초기화에 실패했습니다. Core 로그를 확인하거나 다시 업그레이드한 후 사용하세요.', + helperNotStart: '가상 머신 실행 환경이 준비되지 않았습니다. 호스트 가상화 서비스를 처리한 후 사용하세요.', + cpu: 'CPU', + memory: '메모리', + disk: '디스크', + memoryLimitHelper: '최대 할당 가능 메모리: {0}', + memoryLimitExceeded: + 'VM 메모리 할당이 호스트의 할당 가능 메모리를 초과합니다. 메모리를 줄인 후 다시 시도하세요.', + storageLimitHelper: '대상 스토리지 풀의 현재 사용 가능 공간: {0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: '디스크 용량', + diskPath: '디스크 경로', + diskPathHelper: + '비워둘 수 있습니다. 시스템은 선택한 스토리지 풀 디렉터리에 가상머신이름.qcow2를 자동 생성합니다. 입력하는 경우 절대 경로여야 합니다.', + imagePath: '이미지 경로', + imagePathHelper: '비워둘 수 있습니다. 입력하는 경우 기존 qcow2 이미지의 절대 경로여야 합니다.', + isoPath: 'ISO 경로', + isoPathHelper: '비워둘 수 있습니다. 입력하는 경우 기존 ISO의 절대 경로여야 합니다.', + storagePool: '스토리지 풀', + storagePoolHelper: '기본적으로 default 스토리지 풀을 사용하여 VM 디스크 볼륨을 생성하고 관리합니다.', + network: '네트워크', + networkHelper: + '기본적으로 default NAT 네트워크를 사용합니다. 기존 libvirt 네트워크 이름을 선택하거나 입력할 수도 있습니다.', + bridgeName: '브리지 이름', + natNetworkHelper: + 'NAT는 VM 외부 연결을 위한 libvirt 가상 네트워크를 생성합니다. 내부 브리지 이름은 자동 생성되며 호스트 브리지 NIC를 선택할 필요가 없습니다.', + bridgeNetworkHelper: + 'Bridge는 VM을 호스트의 기존 Linux bridge에 연결하여 호스트와 같은 L2 네트워크에 참여시킵니다.', + bridgeNameHelper: + 'br0와 같이 호스트에 이미 존재하는 Linux bridge를 선택하세요. 물리 NIC는 bridge가 아니며 docker/libvirt가 관리하는 bridge는 여기서 선택할 수 없습니다.', + natCIDRHelper: 'CIDR, 게이트웨이, DHCP 주소 범위는 새 NAT 가상 네트워크에 사용됩니다.', + networkCIDR: '네트워크 CIDR', + gateway: '게이트웨이', + dhcpStart: 'DHCP 시작 주소', + dhcpEnd: 'DHCP 종료 주소', + bootOrder: '부팅 순서', + bootOrderHelper: + 'HD는 디스크에서 먼저 부팅하고 디스크가 부팅 불가능하면 ISO를 시도합니다. CD-ROM은 ISO에서 먼저 부팅합니다.', + startAfterCreate: '생성 후 시작', + autoStart: '자동 시작', + resourceAutoStart: '자동 시작', + capacity: '용량', + available: '사용 가능', + vmCount: '연결된 VM', + usedIPs: '사용된 IP', + syncFromServer: '서버에서 동기화', + notFound: '존재하지 않음', + resourceStatus: { + running: '실행 중', + inactive: '시작되지 않음', + building: '빌드 중', + degraded: '저하됨', + inaccessible: '접근 불가', + unknown: '알 수 없음', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: '새 이름', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: '콘솔', + consoleNotReady: 'VNC 콘솔을 사용할 수 없습니다. VM을 시작하고 VNC 그래픽 구성을 확인하세요.', + consoleConnecting: 'VNC 콘솔에 연결 중...', + consoleDisconnected: 'VNC 콘솔 연결이 끊어졌습니다.', + consoleConnectFailed: 'VNC 콘솔 연결에 실패했습니다. 콘솔 프록시 서비스를 확인하세요.', + consoleCredentialsRequired: 'VNC 콘솔에 인증 정보가 필요합니다. 백엔드 프록시 구성을 확인하세요.', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: '네트워크 인터페이스', + device: '장치', + path: '경로', + start: '시작', + shutdown: '종료', + reboot: '재부팅', + destroy: '강제 중지', + suspend: '일시 중지', + resume: '재개', + operateConfirm: '{0} 가상 머신 [{1}]을(를) 실행하시겠습니까?', + forceDeleteConfirm: '가상 머신이 실행 중입니다. 삭제 전에 강제 중지됩니다. 계속하시겠습니까?', + createResource: '{0} 생성', + isoSource: 'ISO 출처', + isoUploadDirHelper: '파일 업로드 위치: {0}', + isoSize: 'ISO 크기', + dir: '로컬 디렉터리', + }, cluster: { cluster: '애플리케이션 고가용성', name: '클러스터 이름', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 0ef08afa2..b6c64316d 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -115,6 +115,8 @@ const message = { total: 'Jumlah {0}', name: 'Nama', type: 'Jenis', + card: 'Kad', + table: 'Jadual', status: 'Status', records: 'Rekod', group: 'Kumpulan', @@ -2096,6 +2098,7 @@ const message = { sync: 'Penyegerakan Sumber', exchange: 'Penyegerakan Sumber', xapp: 'App', + vm: 'Pengurusan Mesin Maya', }, websiteLog: 'Log Laman Web', runLog: 'Log Jalankan', @@ -5654,6 +5657,136 @@ const message = { selectTargetFirst: 'Sila pilih sekurang-kurangnya satu nod sasaran.', submitSuccess: 'Tugas penyegerakan berjaya dihantar.', }, + vm: { + vm: 'Mesin Maya', + title: 'Pengurusan Mesin Maya', + dependencies: 'Kebergantungan', + dependencyPurpose: 'Tujuan', + dependencyPurposeMap: { + kvm: 'Menyediakan sokongan virtualisasi perkakasan untuk mempercepat pelaksanaan VM.', + libvirt: 'Menyediakan perkhidmatan pengurusan VM untuk kitar hayat dan penjadualan sumber.', + storage: 'Menyediakan direktori storan lalai atau kolam storan untuk fail cakera VM.', + }, + dependencyNotReady: + 'Semakan kebergantungan VM gagal. Selesaikan kebergantungan sebelum menggunakan mesin maya.', + helperUnavailable: + 'Permulaan keupayaan VM gagal. Semak log Core atau naik taraf semula sebelum menggunakan mesin maya.', + helperNotStart: 'Perkhidmatan VM tidak berjalan. Mulakan perkhidmatan sebelum menggunakan mesin maya.', + cpu: 'CPU', + memory: 'Memori', + disk: 'Cakera', + memoryLimitHelper: 'Memori maksimum yang boleh diperuntukkan: {0}', + memoryLimitExceeded: + 'Peruntukan memori VM akan melebihi memori hos yang boleh diperuntukkan. Kurangkan memori dan cuba lagi.', + storageLimitHelper: 'Ruang kosong semasa dalam kolam storan sasaran: {0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: 'Saiz Cakera', + diskPath: 'Laluan Cakera', + diskPathHelper: + 'Pilihan. Jika kosong, sistem akan mencipta .qcow2 dalam direktori kolam storan yang dipilih. Nilai tersuai mestilah laluan mutlak.', + imagePath: 'Laluan Imej', + imagePathHelper: 'Pilihan. Jika ditetapkan, gunakan laluan mutlak imej qcow2 sedia ada.', + isoPath: 'Laluan ISO', + isoPathHelper: 'Pilihan. Jika ditetapkan, gunakan laluan mutlak ISO sedia ada.', + storagePool: 'Kolam Storan', + storagePoolHelper: + 'Menggunakan kolam storan default secara lalai untuk mencipta dan mengurus volum cakera VM.', + network: 'Rangkaian', + networkHelper: + 'Menggunakan rangkaian NAT default secara lalai. Anda juga boleh memilih atau memasukkan nama rangkaian libvirt sedia ada.', + bridgeName: 'Nama Bridge', + natNetworkHelper: + 'NAT mencipta rangkaian maya libvirt untuk akses keluar VM. Nama bridge dalaman dijana secara automatik dan bridge hos tidak diperlukan.', + bridgeNetworkHelper: + 'Bridge menyambungkan VM ke Linux bridge hos sedia ada supaya VM boleh menyertai rangkaian Layer 2 yang sama dengan hos.', + bridgeNameHelper: + 'Pilih Linux bridge sedia ada pada hos, seperti br0. NIC fizikal bukan bridge, dan bridge yang diurus docker/libvirt tidak boleh dipilih di sini.', + natCIDRHelper: 'CIDR, get laluan dan julat DHCP digunakan oleh rangkaian maya NAT baharu.', + networkCIDR: 'CIDR Rangkaian', + gateway: 'Get Laluan', + dhcpStart: 'Mula DHCP', + dhcpEnd: 'Tamat DHCP', + bootOrder: 'Susunan But', + bootOrderHelper: + 'HD but daripada cakera dahulu dan kemudian mencuba ISO jika cakera tidak boleh dibut. CD-ROM but daripada ISO dahulu.', + startAfterCreate: 'Mula selepas dicipta', + autoStart: 'Mula automatik', + resourceAutoStart: 'Mula automatik', + capacity: 'Kapasiti', + available: 'Tersedia', + vmCount: 'VM Terpaut', + usedIPs: 'IP Digunakan', + syncFromServer: 'Segerak dari Pelayan', + notFound: 'Tidak ditemui', + resourceStatus: { + running: 'Berjalan', + inactive: 'Tidak aktif', + building: 'Membina', + degraded: 'Menurun', + inaccessible: 'Tidak boleh diakses', + unknown: 'Tidak diketahui', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: 'Nama baru', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: 'Konsol', + consoleNotReady: 'Konsol VNC tidak tersedia. Mulakan VM dan semak konfigurasi grafik VNC.', + consoleConnecting: 'Menyambung ke konsol VNC...', + consoleDisconnected: 'Konsol VNC telah terputus.', + consoleConnectFailed: 'Gagal menyambung ke konsol VNC. Semak perkhidmatan proksi konsol.', + consoleCredentialsRequired: 'Konsol VNC memerlukan kelayakan. Semak konfigurasi proksi backend.', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: 'Antara Muka Rangkaian', + device: 'Peranti', + path: 'Laluan', + start: 'Mula', + shutdown: 'Matikan', + reboot: 'But Semula', + destroy: 'Henti Paksa', + suspend: 'Gantung', + resume: 'Sambung', + operateConfirm: 'Sahkan {0} mesin maya [{1}]?', + forceDeleteConfirm: + 'Mesin maya sedang berjalan. Ia akan dihentikan secara paksa sebelum dipadam. Teruskan?', + createResource: 'Cipta {0}', + isoSource: 'Sumber ISO', + isoUploadDirHelper: 'Fail akan dimuat naik ke: {0}', + isoSize: 'Saiz ISO', + dir: 'Direktori Setempat', + }, cluster: { cluster: 'Aplikasi Tinggi Ketersediaan', name: 'Nama Kluster', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index e01f0a115..6e33db234 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -115,6 +115,8 @@ const message = { total: 'Total de {0}', name: 'Nome', type: 'Tipo', + card: 'Cartao', + table: 'Tabela', status: 'Status', records: 'Registros', group: 'Grupo', @@ -2212,6 +2214,7 @@ const message = { sync: 'Sincronização de Recursos', exchange: 'Sincronização de Recursos', xapp: 'App', + vm: 'Gerenciamento de Máquinas Virtuais', }, websiteLog: 'Logs do website', runLog: 'Logs de execução', @@ -5803,6 +5806,138 @@ const message = { selectTargetFirst: 'Selecione pelo menos um nó de destino.', submitSuccess: 'Tarefa de sincronização enviada com sucesso.', }, + vm: { + vm: 'Máquina virtual', + title: 'Gerenciamento de Máquinas Virtuais', + dependencies: 'Dependências', + dependencyPurpose: 'Finalidade', + dependencyPurposeMap: { + kvm: 'Fornece suporte de virtualização por hardware para execução acelerada de VMs.', + libvirt: + 'Fornece serviços de gerenciamento de VMs para controle de ciclo de vida e agendamento de recursos.', + storage: + 'Fornece o diretório de armazenamento padrão ou pool de armazenamento para arquivos de disco da VM.', + }, + dependencyNotReady: + 'As verificações de dependência da VM falharam. Corrija as dependências antes de usar máquinas virtuais.', + helperUnavailable: + 'A inicialização da capacidade de VM falhou. Verifique os logs do Core ou atualize novamente antes de usar VMs.', + helperNotStart: 'O serviço de VM não está em execução. Inicie o serviço antes de usar máquinas virtuais.', + cpu: 'CPU', + memory: 'Memória', + disk: 'Disco', + memoryLimitHelper: 'Memória máxima alocável: {0}', + memoryLimitExceeded: + 'A alocação de memória da VM excederá a memória alocável do host. Reduza a memória e tente novamente.', + storageLimitHelper: 'Espaço livre atual no pool de armazenamento de destino: {0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: 'Tamanho do Disco', + diskPath: 'Caminho do Disco', + diskPathHelper: + 'Opcional. Se vazio, o sistema cria .qcow2 no diretório do pool de armazenamento selecionado. Valores personalizados devem ser caminhos absolutos.', + imagePath: 'Caminho da Imagem', + imagePathHelper: 'Opcional. Se definido, use o caminho absoluto de uma imagem qcow2 existente.', + isoPath: 'Caminho do ISO', + isoPathHelper: 'Opcional. Se definido, use o caminho absoluto de um ISO existente.', + storagePool: 'Pool de Armazenamento', + storagePoolHelper: + 'Usa o pool de armazenamento default por padrão para criar e gerenciar volumes de disco da VM.', + network: 'Rede', + networkHelper: + 'Usa a rede NAT default por padrão. Você também pode selecionar ou inserir um nome de rede libvirt existente.', + bridgeName: 'Nome da Bridge', + natNetworkHelper: + 'NAT cria uma rede virtual libvirt para acesso externo da VM. O nome da bridge interna é gerado automaticamente e nenhuma bridge do host é necessária.', + bridgeNetworkHelper: + 'Bridge conecta VMs a uma Linux bridge existente no host para que elas entrem na mesma rede de camada 2 do host.', + bridgeNameHelper: + 'Selecione uma Linux bridge existente no host, como br0. NICs físicas não são bridges, e bridges gerenciadas por docker/libvirt não podem ser selecionadas aqui.', + natCIDRHelper: 'CIDR, gateway e intervalo DHCP são usados pela nova rede virtual NAT.', + networkCIDR: 'CIDR da Rede', + gateway: 'Gateway', + dhcpStart: 'Início DHCP', + dhcpEnd: 'Fim DHCP', + bootOrder: 'Ordem de Boot', + bootOrderHelper: + 'HD inicializa pelo disco primeiro e tenta o ISO se o disco não for inicializável. CD-ROM inicializa pelo ISO primeiro.', + startAfterCreate: 'Iniciar após criação', + autoStart: 'Inicialização automática', + resourceAutoStart: 'Inicialização automática', + capacity: 'Capacidade', + available: 'Disponível', + vmCount: 'VMs Vinculadas', + usedIPs: 'IPs Usados', + syncFromServer: 'Sincronizar do Servidor', + notFound: 'Não encontrado', + resourceStatus: { + running: 'Em execução', + inactive: 'Inativo', + building: 'Construindo', + degraded: 'Degradado', + inaccessible: 'Inacessível', + unknown: 'Desconhecido', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: 'Novo nome', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: 'Console', + consoleNotReady: 'O console VNC está indisponível. Inicie a VM e verifique sua configuração gráfica VNC.', + consoleConnecting: 'Conectando ao console VNC...', + consoleDisconnected: 'O console VNC foi desconectado.', + consoleConnectFailed: 'Falha ao conectar ao console VNC. Verifique o serviço de proxy do console.', + consoleCredentialsRequired: 'O console VNC requer credenciais. Verifique a configuração do proxy backend.', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: 'Interface de Rede', + device: 'Dispositivo', + path: 'Caminho', + start: 'Iniciar', + shutdown: 'Desligar', + reboot: 'Reiniciar', + destroy: 'Forçar Parada', + suspend: 'Suspender', + resume: 'Retomar', + operateConfirm: 'Confirmar {0} máquina virtual [{1}]?', + forceDeleteConfirm: + 'A máquina virtual está em execução. Ela será forçada a parar antes da exclusão. Continuar?', + createResource: 'Criar {0}', + isoSource: 'Origem do ISO', + isoUploadDirHelper: 'O arquivo será enviado para: {0}', + isoSize: 'Tamanho do ISO', + dir: 'Diretório Local', + }, cluster: { cluster: 'Alta Disponibilidade de Aplicações', name: 'Nome do Cluster', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 2719ad8de..919d1fdb6 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -115,6 +115,8 @@ const message = { total: 'Всего {0}', name: 'Имя', type: 'Тип', + card: 'Карточка', + table: 'Таблица', status: 'Статус', records: 'Записи', group: 'Группа', @@ -2085,6 +2087,7 @@ const message = { sync: 'Синхронизация ресурсов', exchange: 'Синхронизация ресурсов', xapp: 'App', + vm: 'Управление виртуальными машинами', }, websiteLog: 'Логи веб-сайта', runLog: 'Логи выполнения', @@ -5654,6 +5657,139 @@ const message = { selectTargetFirst: 'Выберите хотя бы один целевой узел.', submitSuccess: 'Задача синхронизации успешно отправлена.', }, + vm: { + vm: 'Виртуальная машина', + title: 'Управление виртуальными машинами', + dependencies: 'Зависимости', + dependencyPurpose: 'Назначение', + dependencyPurposeMap: { + kvm: 'Предоставляет аппаратную виртуализацию для ускоренного запуска виртуальных машин.', + libvirt: 'Предоставляет службу управления ВМ, отвечает за жизненный цикл и распределение ресурсов.', + storage: 'Предоставляет каталог хранения по умолчанию или пул хранения для файлов дисков ВМ.', + }, + dependencyNotReady: + 'Проверка зависимостей ВМ не пройдена. Исправьте зависимости перед использованием виртуальных машин.', + helperUnavailable: + 'Не удалось инициализировать возможности ВМ. Проверьте журналы Core или повторите обновление перед использованием виртуальных машин.', + helperNotStart: + 'Служба виртуальных машин не запущена. Запустите службу перед использованием виртуальных машин.', + cpu: 'CPU', + memory: 'Память', + disk: 'Диск', + memoryLimitHelper: 'Максимально доступная память: {0}', + memoryLimitExceeded: + 'Выделение памяти ВМ превысит доступную память хоста. Уменьшите память и повторите попытку.', + storageLimitHelper: 'Текущее свободное место в целевом пуле хранения: {0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: 'Размер диска', + diskPath: 'Путь к диску', + diskPathHelper: + 'Можно оставить пустым. Система автоматически создаст ИмяВМ.qcow2 в каталоге выбранного пула хранения. Если указано, путь должен быть абсолютным.', + imagePath: 'Путь к образу', + imagePathHelper: + 'Можно оставить пустым. Если указано, используйте абсолютный путь к существующему образу qcow2.', + isoPath: 'Путь к ISO', + isoPathHelper: 'Можно оставить пустым. Если указано, используйте абсолютный путь к существующему ISO.', + storagePool: 'Пул хранения', + storagePoolHelper: + 'По умолчанию используется пул хранения default для создания и управления дисковыми томами ВМ.', + network: 'Сеть', + networkHelper: + 'По умолчанию используется сеть NAT default. Также можно выбрать или ввести имя существующей сети libvirt.', + bridgeName: 'Имя моста', + natNetworkHelper: + 'NAT создаёт виртуальную сеть libvirt для исходящего доступа ВМ. Имя внутреннего моста генерируется автоматически, выбирать мост хоста не нужно.', + bridgeNetworkHelper: + 'Bridge подключает ВМ к существующему Linux bridge на хосте, чтобы ВМ вошла в ту же сеть L2, что и хост.', + bridgeNameHelper: + 'Выберите существующий Linux bridge на хосте, например br0. Физические NIC не являются bridge, а bridge под управлением docker/libvirt здесь выбрать нельзя.', + natCIDRHelper: 'CIDR, шлюз и диапазон DHCP будут использоваться новой виртуальной сетью NAT.', + networkCIDR: 'CIDR сети', + gateway: 'Шлюз', + dhcpStart: 'Начальный адрес DHCP', + dhcpEnd: 'Конечный адрес DHCP', + bootOrder: 'Порядок загрузки', + bootOrderHelper: + 'HD сначала загружает с диска, а затем пробует ISO, если диск не загрузочный. CD-ROM сначала загружает с ISO.', + startAfterCreate: 'Запустить после создания', + autoStart: 'Автозапуск', + resourceAutoStart: 'Автозапуск', + capacity: 'Ёмкость', + available: 'Доступно', + vmCount: 'Связанные ВМ', + usedIPs: 'Использованные IP', + syncFromServer: 'Синхронизировать с сервера', + notFound: 'Не существует', + resourceStatus: { + running: 'Работает', + inactive: 'Не запущено', + building: 'Создаётся', + degraded: 'Деградировано', + inaccessible: 'Недоступно', + unknown: 'Неизвестно', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: 'Новое имя', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: 'Консоль', + consoleNotReady: 'Консоль VNC недоступна. Запустите ВМ и проверьте конфигурацию графики VNC.', + consoleConnecting: 'Подключение к консоли VNC...', + consoleDisconnected: 'Консоль VNC отключена.', + consoleConnectFailed: 'Не удалось подключиться к консоли VNC. Проверьте службу прокси консоли.', + consoleCredentialsRequired: + 'Для консоли VNC требуются учётные данные. Проверьте конфигурацию backend-прокси.', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: 'Сетевой интерфейс', + device: 'Устройство', + path: 'Путь', + start: 'Запустить', + shutdown: 'Выключить', + reboot: 'Перезагрузить', + destroy: 'Принудительно остановить', + suspend: 'Приостановить', + resume: 'Возобновить', + operateConfirm: 'Подтвердить {0} виртуальной машины [{1}]?', + forceDeleteConfirm: + 'Виртуальная машина работает. Перед удалением она будет принудительно остановлена. Продолжить?', + createResource: 'Создать {0}', + isoSource: 'Источник ISO', + isoUploadDirHelper: 'Файл будет загружен в: {0}', + isoSize: 'Размер ISO', + dir: 'Локальный каталог', + }, cluster: { cluster: 'Высокая доступность приложений', name: 'Имя кластера', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index c09a4f789..dbd6f9392 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -115,6 +115,8 @@ const message = { total: 'Toplam {0}', name: 'Ad', type: 'Tür', + card: 'Kart', + table: 'Tablo', status: 'Durum', statusSuccess: 'Başarılı', statusFailed: 'Başarısız', @@ -2094,6 +2096,7 @@ const message = { sync: 'Kaynak Senkronizasyonu', exchange: 'Kaynak Senkronizasyonu', xapp: 'App', + vm: 'Sanal Makine Yönetimi', }, websiteLog: 'Website logları', runLog: 'Çalıştırma logları', @@ -5642,6 +5645,136 @@ const message = { selectTargetFirst: 'Lütfen en az bir hedef düğüm seçin.', submitSuccess: 'Senkronizasyon görevi başarıyla gönderildi.', }, + vm: { + vm: 'Sanal Makine', + title: 'Sanal Makine Yönetimi', + dependencies: 'Bağımlılıklar', + dependencyPurpose: 'Amaç', + dependencyPurposeMap: { + kvm: 'Hızlandırılmış VM çalıştırma için donanım sanallaştırma desteği sağlar.', + libvirt: 'Yaşam döngüsü denetimi ve kaynak zamanlaması için VM yönetim hizmetleri sağlar.', + storage: 'VM disk dosyaları için varsayılan depolama dizinini veya depolama havuzunu sağlar.', + }, + dependencyNotReady: + 'VM bağımlılık denetimleri başarısız oldu. Sanal makineleri kullanmadan önce bağımlılıkları düzeltin.', + helperUnavailable: + 'VM yeteneği başlatılamadı. Sanal makineleri kullanmadan önce Core günlüklerini kontrol edin veya yeniden yükseltin.', + helperNotStart: 'VM hizmeti çalışmıyor. Sanal makineleri kullanmadan önce hizmeti başlatın.', + cpu: 'CPU', + memory: 'Bellek', + disk: 'Disk', + memoryLimitHelper: 'Ayrılabilir maksimum bellek: {0}', + memoryLimitExceeded: + 'VM bellek ayırması, ana makinede ayrılabilir belleği aşacak. Belleği azaltıp tekrar deneyin.', + storageLimitHelper: 'Hedef depolama havuzundaki mevcut boş alan: {0}', + iso: 'ISO', + createSource: 'Create Source', + blankDisk: 'Blank Disk', + template: 'Template', + templateName: 'Template Name', + templateHelper: 'Create a new VM by fully cloning the selected template disk.', + convertToTemplate: 'Convert to Template', + sourceVM: 'Source VM', + templateSize: 'Template Size', + deleteTemplate: 'Delete Template', + deleteTemplateFile: 'Delete template disk file', + diskSize: 'Disk Boyutu', + diskPath: 'Disk Yolu', + diskPathHelper: + 'İsteğe bağlıdır. Boş bırakılırsa sistem seçilen depolama havuzu dizininde .qcow2 oluşturur. Özel değerler mutlak yol olmalıdır.', + imagePath: 'İmaj Yolu', + imagePathHelper: 'İsteğe bağlıdır. Ayarlanırsa mevcut bir qcow2 imajının mutlak yolunu kullanın.', + isoPath: 'ISO Yolu', + isoPathHelper: 'İsteğe bağlıdır. Ayarlanırsa mevcut bir ISO dosyasının mutlak yolunu kullanın.', + storagePool: 'Depolama Havuzu', + storagePoolHelper: + 'VM disk birimlerini oluşturmak ve yönetmek için varsayılan olarak default depolama havuzunu kullanır.', + network: 'Ağ', + networkHelper: + 'Varsayılan olarak default NAT ağı kullanılır. Mevcut bir libvirt ağ adını seçebilir veya girebilirsiniz.', + bridgeName: 'Bridge Adı', + natNetworkHelper: + 'NAT, VM çıkış erişimi için libvirt sanal ağı oluşturur. Dahili bridge adı otomatik üretilir ve ana makine bridge seçimi gerekmez.', + bridgeNetworkHelper: + 'Bridge, VMleri ana makinedeki mevcut Linux bridgee bağlayarak ana makineyle aynı Layer 2 ağına katılmalarını sağlar.', + bridgeNameHelper: + 'Ana makinede var olan br0 gibi bir Linux bridge seçin. Fiziksel NIC bridge değildir ve docker/libvirt tarafından yönetilen bridge burada seçilemez.', + natCIDRHelper: 'CIDR, ağ geçidi ve DHCP aralığı yeni NAT sanal ağı tarafından kullanılır.', + networkCIDR: 'Ağ CIDR', + gateway: 'Ağ Geçidi', + dhcpStart: 'DHCP Başlangıcı', + dhcpEnd: 'DHCP Bitişi', + bootOrder: 'Önyükleme Sırası', + bootOrderHelper: + 'HD önce diskten önyükler, disk önyüklenebilir değilse ISOyu dener. CD-ROM önce ISOdan önyükler.', + startAfterCreate: 'Oluşturduktan sonra başlat', + autoStart: 'Otomatik başlat', + resourceAutoStart: 'Otomatik başlat', + capacity: 'Kapasite', + available: 'Kullanılabilir', + vmCount: 'Bağlı VMler', + usedIPs: 'Kullanılan IPler', + syncFromServer: 'Sunucudan senkronize et', + notFound: 'Bulunamadı', + resourceStatus: { + running: 'Çalışıyor', + inactive: 'Etkin değil', + building: 'Oluşturuluyor', + degraded: 'Bozulmuş', + inaccessible: 'Erişilemez', + unknown: 'Bilinmiyor', + }, + monitor: 'Monitor', + monitorNotReady: 'The virtual machine is not running. Monitoring data is unavailable.', + monitorLoadFailed: 'Failed to get VM monitoring data.', + monitorLoadFailedWithReason: 'Failed to get VM monitoring data: {0}', + rename: 'Rename', + newName: 'Yeni ad', + renameHelper: 'Enter a new VM name. The VM must be shut down before renaming.', + renameConfirm: 'Rename virtual machine [{0}] to [{1}]?', + renameRunningHelper: 'Shut down the virtual machine before renaming it.', + cpuUsage: 'CPU Usage', + memoryUsage: 'Memory Usage', + diskIO: 'Disk IO', + networkIO: 'Network IO', + diskReadWrite: 'Disk Read / Write', + networkRxTx: 'Network RX / TX', + rx: 'RX', + tx: 'TX', + console: 'Konsol', + consoleNotReady: 'VNC konsolu kullanılamıyor. VMyi başlatın ve VNC grafik yapılandırmasını kontrol edin.', + consoleConnecting: 'VNC konsoluna bağlanılıyor...', + consoleDisconnected: 'VNC konsolu bağlantısı kesildi.', + consoleConnectFailed: 'VNC konsoluna bağlanılamadı. Konsol proxy hizmetini kontrol edin.', + consoleCredentialsRequired: + 'VNC konsolu kimlik bilgileri gerektiriyor. Backend proxy yapılandırmasını kontrol edin.', + snapshot: 'Snapshot', + createSnapshot: 'Create Snapshot', + recoverSnapshot: 'Recover Snapshot', + snapshotName: 'Snapshot Name', + snapshotPath: 'Snapshot File', + snapshotSize: 'Snapshot Size', + currentSnapshot: 'Current', + snapshotEmpty: 'No snapshots', + snapshotRecoverConfirm: + 'Recover virtual machine [{1}] to snapshot [{0}]? The VM disk will be restored to the snapshot state.', + interface: 'Ağ Arayüzü', + device: 'Aygıt', + path: 'Yol', + start: 'Başlat', + shutdown: 'Kapat', + reboot: 'Yeniden Başlat', + destroy: 'Zorla Durdur', + suspend: 'Askıya Al', + resume: 'Sürdür', + operateConfirm: '[{1}] sanal makinesi için {0} işlemini onaylıyor musunuz?', + forceDeleteConfirm: 'Sanal makine çalışıyor. Silmeden önce zorla durdurulacak. Devam edilsin mi?', + createResource: '{0} oluştur', + isoSource: 'ISO Kaynağı', + isoUploadDirHelper: 'Dosya şuraya yüklenecek: {0}', + isoSize: 'ISO Boyutu', + dir: 'Yerel Dizin', + }, cluster: { cluster: 'Высокая доступность приложений', name: 'Имя кластера', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 7765102fa..86d65e3e7 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -114,6 +114,8 @@ const message = { total: '共 {0} 條', name: '名稱', type: '類型', + card: '卡片', + table: '表格', status: '狀態', records: '任務輸出', group: '分組', @@ -1939,6 +1941,7 @@ const message = { sync: '資源同步', exchange: '資源同步', xapp: 'APP', + vm: '虛擬機管理', }, websiteLog: '網站日誌', runLog: '執行日誌', @@ -5182,6 +5185,126 @@ const message = { selectTargetFirst: '請先選擇至少一個目標節點。', submitSuccess: '同步任務已提交。', }, + vm: { + vm: '虛擬機', + title: '虛擬機管理', + dependencies: '依賴', + dependencyPurpose: '作用', + dependencyPurposeMap: { + kvm: '提供硬體虛擬化能力,是虛擬機加速執行的基礎。', + libvirt: '提供虛擬機管理服務,負責虛擬機生命週期和資源調度。', + storage: '提供預設儲存目錄或儲存池,用於保存虛擬機磁碟檔案。', + }, + dependencyNotReady: '虛擬機依賴檢測未通過,請處理依賴後再使用虛擬機功能。', + helperUnavailable: '虛擬機能力初始化失敗,請檢查 Core 日誌或重新升級後再使用虛擬機功能。', + helperNotStart: '虛擬機執行環境未就緒,請處理主機虛擬化服務後再使用虛擬機功能。', + cpu: 'CPU', + memory: '記憶體', + disk: '磁碟', + memoryLimitHelper: '最大可分配記憶體:{0}', + memoryLimitExceeded: 'VM 記憶體分配將超過主機可分配記憶體,請降低記憶體後重試。', + storageLimitHelper: '目標儲存池目前可用空間:{0}', + iso: 'ISO', + createSource: '建立來源', + blankDisk: '空白磁碟', + template: '範本', + templateName: '範本名稱', + templateHelper: '將所選範本磁碟完整複製為新的虛擬機磁碟。', + convertToTemplate: '轉為範本', + sourceVM: '來源虛擬機', + templateSize: '範本大小', + deleteTemplate: '刪除範本', + deleteTemplateFile: '刪除範本磁碟檔案', + diskSize: '磁碟容量', + diskPath: '磁碟路徑', + diskPathHelper: '可留空,系統將在所選儲存池目錄下自動建立 虛擬機名稱.qcow2;如填寫,必須是絕對路徑。', + imagePath: '映像路徑', + imagePathHelper: '可留空;如填寫,需為已存在的 qcow2 映像絕對路徑。', + isoPath: 'ISO 路徑', + isoPathHelper: '可留空;如填寫,需為已存在的 ISO 絕對路徑。', + storagePool: '儲存池', + storagePoolHelper: '預設使用 default 儲存池,用於建立和管理虛擬機磁碟卷。', + network: '網路', + networkHelper: '預設使用 default NAT 網路;也可以選擇或輸入已存在的 libvirt 網路名稱。', + bridgeName: '橋接名稱', + natNetworkHelper: + 'NAT 會建立 libvirt 虛擬網路供虛擬機出網使用,內部橋接名稱會自動產生,不需要選擇主機橋接網卡。', + bridgeNetworkHelper: 'Bridge 會將虛擬機接入主機既有的 Linux bridge,使虛擬機加入主機所在的二層網路。', + bridgeNameHelper: + '請選擇主機上已存在的 Linux bridge,例如 br0。實體網卡不是 bridge,docker/libvirt 管理的 bridge 不能在此選擇。', + natCIDRHelper: 'CIDR、閘道和 DHCP 位址範圍將用於新建的 NAT 虛擬網路。', + networkCIDR: '網路 CIDR', + gateway: '閘道', + dhcpStart: 'DHCP 起始位址', + dhcpEnd: 'DHCP 結束位址', + bootOrder: '啟動項', + bootOrderHelper: 'HD 表示優先從磁碟啟動,磁碟不可啟動時再嘗試 ISO;CD-ROM 表示優先從 ISO 啟動。', + startAfterCreate: '建立後啟動', + autoStart: '開機自啟', + resourceAutoStart: '開機自啟', + capacity: '容量', + available: '可用', + vmCount: '關聯虛擬機', + usedIPs: '已用 IP', + syncFromServer: '從伺服器同步', + notFound: '不存在', + resourceStatus: { + running: '執行中', + inactive: '未啟動', + building: '建置中', + degraded: '降級', + inaccessible: '無法存取', + unknown: '未知', + }, + monitor: '監控', + monitorNotReady: '虛擬機未執行,無法取得監控資料。', + monitorLoadFailed: '取得虛擬機監控資料失敗。', + monitorLoadFailedWithReason: '取得虛擬機監控資料失敗:{0}', + rename: '重新命名', + newName: '新名稱', + renameHelper: '請輸入新的虛擬機名稱,重新命名需要虛擬機處於關機狀態。', + renameConfirm: '確認將虛擬機 [{0}] 重新命名為 [{1}]?', + renameRunningHelper: '請先關閉虛擬機後再重新命名。', + cpuUsage: 'CPU 使用率', + memoryUsage: '記憶體使用率', + diskIO: '磁碟 IO', + networkIO: '網路 IO', + diskReadWrite: '磁碟讀 / 寫', + networkRxTx: '網路收 / 發', + rx: '接收', + tx: '傳送', + console: '控制台', + consoleNotReady: 'VNC 控制台不可用,請啟動虛擬機並檢查 VNC 圖形設定。', + consoleConnecting: '正在連線 VNC 控制台...', + consoleDisconnected: 'VNC 控制台已中斷連線。', + consoleConnectFailed: 'VNC 控制台連線失敗,請檢查控制台代理服務。', + consoleCredentialsRequired: 'VNC 控制台需要認證資訊,請檢查後端代理設定。', + snapshot: '快照', + createSnapshot: '建立快照', + recoverSnapshot: '恢復快照', + snapshotName: '快照名稱', + snapshotPath: '快照檔案', + snapshotSize: '快照大小', + currentSnapshot: '目前', + snapshotEmpty: '暫無快照', + snapshotRecoverConfirm: '確認將虛擬機 [{1}] 恢復到快照 [{0}]?恢復後虛擬機磁碟會回到該快照狀態。', + interface: '網路介面', + device: '裝置', + path: '路徑', + start: '啟動', + shutdown: '關機', + reboot: '重新啟動', + destroy: '強制停止', + suspend: '暫停', + resume: '恢復', + operateConfirm: '確認 {0} 虛擬機 [{1}]?', + forceDeleteConfirm: '虛擬機正在執行,刪除前將強制停止,是否繼續?', + createResource: '建立 {0}', + isoSource: 'ISO 來源', + isoUploadDirHelper: '檔案將上傳到:{0}', + isoSize: 'ISO 大小', + dir: '本機目錄', + }, cluster: { cluster: '應用高可用', name: '叢集名稱', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index d9d153e5c..03a0428f9 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -56,6 +56,7 @@ const message = { power: '授权', search: '搜索', refresh: '刷新', + reconnect: '重新连接', get: '获取', upgrade: '升级', update: '更新', @@ -110,6 +111,8 @@ const message = { all: '所有', name: '名称', type: '类型', + card: '卡片', + table: '表格', status: '状态', group: '分组', default: '默认', @@ -254,6 +257,8 @@ const message = { length128Err: '长度不能超过128位', maxLength: '长度不能超过 {0} 位', alias: '支持英文、数字、-和_,长度1-128,并且不能以-_开头和结尾', + vmName: '支持英文、数字、.-和_,长度1-64,且必须以英文或数字开头', + vmNetwork: '支持英文、数字、.:-和_,长度1-64,且必须以英文或数字开头', }, res: { paramError: '请求失败,请稍后重试!', @@ -1936,6 +1941,7 @@ const message = { sync: '资源同步', exchange: '资源同步', xapp: 'APP', + vm: '虚拟机管理', }, websiteLog: '网站日志', runLog: '运行日志', @@ -5171,6 +5177,126 @@ const message = { selectTargetFirst: '请先选择至少一个目标节点。', submitSuccess: '同步任务已提交。', }, + vm: { + vm: '虚拟机', + title: '虚拟机管理', + dependencies: '依赖', + dependencyPurpose: '作用', + dependencyPurposeMap: { + kvm: '提供硬件虚拟化能力,是虚拟机加速运行的基础。', + libvirt: '提供虚拟机管理服务,负责虚拟机生命周期和资源调度。', + storage: '提供默认存储目录或存储池,用于保存虚拟机磁盘文件。', + }, + dependencyNotReady: '虚拟机依赖检测未通过,请处理依赖后再使用虚拟机功能。', + helperUnavailable: '虚拟机能力初始化失败,请检查 Core 日志或重新升级后再使用虚拟机功能。', + helperNotStart: '虚拟机运行环境未就绪,请处理宿主机虚拟化服务后再使用虚拟机功能。', + cpu: 'CPU', + memory: '内存', + disk: '磁盘', + memoryLimitHelper: '最大可分配内存:{0}', + memoryLimitExceeded: 'VM 内存分配将超过宿主机可分配内存,请降低内存后重试。', + storageLimitHelper: '目标存储池当前可用空间:{0}', + iso: 'ISO', + createSource: '创建来源', + blankDisk: '空白磁盘', + template: '模板', + templateName: '模板名称', + templateHelper: '将所选模板磁盘完整克隆为新的虚拟机磁盘。', + convertToTemplate: '转为模板', + sourceVM: '来源虚拟机', + templateSize: '模板大小', + deleteTemplate: '删除模板', + deleteTemplateFile: '删除模板磁盘文件', + diskSize: '磁盘容量', + diskPath: '磁盘路径', + diskPathHelper: '可留空,系统将在所选存储池目录下自动创建 虚拟机名称.qcow2;如填写,必须是绝对路径。', + imagePath: '镜像路径', + imagePathHelper: '可留空;如填写,需为已存在的 qcow2 镜像绝对路径。', + isoPath: 'ISO 路径', + isoPathHelper: '可留空;如填写,需为已存在的 ISO 绝对路径。', + storagePool: '存储池', + storagePoolHelper: '默认使用 default 存储池,用于创建和管理虚拟机磁盘卷。', + network: '网络', + networkHelper: '默认使用 default NAT 网络;也可以选择或输入已存在的 libvirt 网络名称。', + bridgeName: '桥接名称', + natNetworkHelper: + 'NAT 会创建 libvirt 虚拟网络供虚拟机出网使用,内部桥接名称自动生成,不需要选择宿主机桥接网卡。', + bridgeNetworkHelper: 'Bridge 会把虚拟机接入宿主机已有的 Linux bridge,使虚拟机加入宿主机所在的二层网络。', + bridgeNameHelper: + '请选择宿主机上已存在的 Linux bridge,例如 br0。物理网卡不是 bridge,docker/libvirt 管理的 bridge 不能在这里选择。', + natCIDRHelper: 'CIDR、网关和 DHCP 地址段将用于新建的 NAT 虚拟网络。', + networkCIDR: '网络 CIDR', + gateway: '网关', + dhcpStart: 'DHCP 起始地址', + dhcpEnd: 'DHCP 结束地址', + bootOrder: '启动项', + bootOrderHelper: 'HD 表示优先从磁盘启动,磁盘不可启动时再尝试 ISO;CD-ROM 表示优先从 ISO 启动。', + startAfterCreate: '创建后启动', + autoStart: '开机自启', + resourceAutoStart: '开机自启', + capacity: '容量', + available: '可用', + vmCount: '关联虚拟机', + usedIPs: '已用 IP', + syncFromServer: '从服务器同步', + notFound: '不存在', + resourceStatus: { + running: '运行中', + inactive: '未启动', + building: '构建中', + degraded: '降级', + inaccessible: '不可访问', + unknown: '未知', + }, + monitor: '监控', + monitorNotReady: '虚拟机未运行,无法获取监控数据。', + monitorLoadFailed: '获取虚拟机监控数据失败。', + monitorLoadFailedWithReason: '获取虚拟机监控数据失败:{0}', + rename: '重命名', + newName: '新名称', + renameHelper: '请输入新的虚拟机名称,重命名需要虚拟机处于关机状态。', + renameConfirm: '确认将虚拟机 [{0}] 重命名为 [{1}]?', + renameRunningHelper: '请先关闭虚拟机后再重命名。', + cpuUsage: 'CPU 使用率', + memoryUsage: '内存使用率', + diskIO: '磁盘 IO', + networkIO: '网络 IO', + diskReadWrite: '磁盘读 / 写', + networkRxTx: '网络收 / 发', + rx: '接收', + tx: '发送', + console: '控制台', + consoleNotReady: 'VNC 控制台不可用,请启动虚拟机并检查 VNC 图形配置。', + consoleConnecting: '正在连接 VNC 控制台...', + consoleDisconnected: 'VNC 控制台已断开连接。', + consoleConnectFailed: 'VNC 控制台连接失败,请检查控制台代理服务。', + consoleCredentialsRequired: 'VNC 控制台需要认证信息,请检查后端代理配置。', + snapshot: '快照', + createSnapshot: '创建快照', + recoverSnapshot: '恢复快照', + snapshotName: '快照名称', + snapshotPath: '快照文件', + snapshotSize: '快照大小', + currentSnapshot: '当前', + snapshotEmpty: '暂无快照', + snapshotRecoverConfirm: '确认将虚拟机 [{1}] 恢复到快照 [{0}]?恢复后虚拟机磁盘会回到该快照状态。', + interface: '网络接口', + device: '设备', + path: '路径', + start: '启动', + shutdown: '关机', + reboot: '重启', + destroy: '强制停止', + suspend: '暂停', + resume: '恢复', + operateConfirm: '确认 {0} 虚拟机 [{1}]?', + forceDeleteConfirm: '虚拟机正在运行,删除前将强制停止,是否继续?', + createResource: '创建 {0}', + isoSource: 'ISO 来源', + isoUploadDirHelper: '文件将上传到:{0}', + isoSize: 'ISO 大小', + dir: '本地目录', + }, cluster: { cluster: '应用高可用', name: '集群名称', diff --git a/frontend/src/styles/common.scss b/frontend/src/styles/common.scss index 87d953b27..e4c3e60c4 100644 --- a/frontend/src/styles/common.scss +++ b/frontend/src/styles/common.scss @@ -279,6 +279,10 @@ html { width: 300px !important; } +.p-w-80 { + width: 80px !important; +} + .p-w-100 { width: 100px !important; } diff --git a/frontend/src/utils/size.ts b/frontend/src/utils/size.ts index 2be0ff017..df4a07bab 100644 --- a/frontend/src/utils/size.ts +++ b/frontend/src/utils/size.ts @@ -2,6 +2,24 @@ function formattedNumber(num: string) { return num.endsWith('.00') ? Number(num.slice(0, -3)) : Number(num); } +export type BinarySizeUnit = 'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB'; + +const binarySizeUnitPower: Record = { + B: 0, + KiB: 1, + MiB: 2, + GiB: 3, + TiB: 4, +}; + +export function convertBinarySize(size: number, from: BinarySizeUnit, to: BinarySizeUnit, precision = 2): number { + if (!size || from === to) { + return size; + } + const bytes = size * Math.pow(1024, binarySizeUnitPower[from]); + return formattedNumber((bytes / Math.pow(1024, binarySizeUnitPower[to])).toFixed(precision)); +} + export function computeSize(size: number): string { const num = 1024.0; if (size < num) return size + ' B';