Compare commits

..
Author SHA1 Message Date
ssongliu 726fd94e07 fix(dashboard): improve vLLM monitoring display and preserve samples (#14326) 2026-11-06 23:44:30 +08:00
王贺andssongliu 95f12c16c3 feat(ai): add GPU environment diagnostics and device summaries (#14322)
* feat(ai): add GPU environment diagnostics and device summaries

* chore(ai): remove GPU diagnostics comments and test files

* feat: separate host and container GPU environment status

* fix(ai): correct GPU environment diagnostics and timeout handling

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-11-06 23:16:10 +08:00
Yuki Guo 7a40ef9725 refactor : modify the text for the quick-access menu button (#14321) 2026-11-06 22:33:01 +08:00
ssongliu ea31289da3 feat(dashboard): add vLLM token throughput monitoring (#14325) 2026-11-06 21:47:02 +08:00
92 changed files with 2347 additions and 740 deletions
+4 -14
View File
@@ -4,8 +4,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/service" "github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -21,20 +19,12 @@ func (b *BaseApi) LoadModelGPUDevices(c *gin.Context) {
// @Security Timestamp // @Security Timestamp
// @Router /ai/gpu/load [get] // @Router /ai/gpu/load [get]
func (b *BaseApi) LoadGpuInfo(c *gin.Context) { func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
ok, client := accelerator.New() info, err := service.LoadGPUInfo(c.Request.Context(), c.Query("refresh") == "true")
if ok { if err != nil {
snapshot, err := client.Collect(c.Request.Context()) helper.BadRequest(c, err)
if err != nil {
helper.BadRequest(c, err)
return
}
if warning := snapshot.Warning(); warning != nil {
global.LOG.Warnf("load realtime accelerator data partially failed, err: %v", warning)
}
helper.SuccessWithData(c, &snapshot.Info)
return return
} }
helper.SuccessWithData(c, &accelerator.Info{}) helper.SuccessWithData(c, info)
} }
// @Tags AI // @Tags AI
+38
View File
@@ -0,0 +1,38 @@
package dto
import (
"time"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
)
type GPURealtimeInfo struct {
accelerator.Info
Environment GPUEnvironment `json:"environment"`
Summaries []GPUSummary `json:"summaries"`
}
type GPUEnvironment struct {
CheckedAt time.Time `json:"checkedAt"`
HardwareStatus string `json:"hardwareStatus"`
Checks []GPUEnvironmentCheck `json:"checks"`
}
type GPUEnvironmentCheck struct {
Vendor string `json:"vendor"`
Item string `json:"item"`
Status string `json:"status"`
Code string `json:"code,omitempty"`
Version string `json:"version,omitempty"`
Reason string `json:"reason,omitempty"`
}
type GPUSummary struct {
Vendor string `json:"vendor"`
DriverVersion string `json:"driverVersion"`
CudaVersion string `json:"cudaVersion"`
CardCount *int `json:"cardCount"`
MemoryTotal *float64 `json:"memoryTotal"`
SharedMemoryTotal *float64 `json:"sharedMemoryTotal"`
MemoryPartial bool `json:"memoryPartial"`
}
+13 -8
View File
@@ -192,6 +192,18 @@ func (u *DockerService) LoadDockerStatus() *dto.DockerStatus {
return &data return &data
} }
func readDockerDaemonConfig() (map[string]interface{}, error) {
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return nil, err
}
daemonMap := make(map[string]interface{})
if err := json.Unmarshal(file, &daemonMap); err != nil {
return nil, err
}
return daemonMap, nil
}
func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) { func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) {
ctx := context.Background() ctx := context.Background()
var data dto.DaemonJsonConf var data dto.DaemonJsonConf
@@ -210,18 +222,11 @@ func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) {
if strings.Contains(stdout2, "Swarm: active") { if strings.Contains(stdout2, "Swarm: active") {
data.IsSwarm = true data.IsSwarm = true
} }
if _, err := os.Stat(constant.DaemonJsonPath); err != nil { daemonMap, err := readDockerDaemonConfig()
return &data, nil
}
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil { if err != nil {
return &data, nil return &data, nil
} }
var conf daemonJsonItem var conf daemonJsonItem
daemonMap := make(map[string]interface{})
if err := json.Unmarshal(file, &daemonMap); err != nil {
return &data, nil
}
arr, err := json.Marshal(daemonMap) arr, err := json.Marshal(daemonMap)
if err != nil { if err != nil {
return &data, err return &data, err
+440
View File
@@ -0,0 +1,440 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/npu"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types/system"
)
const gpuEnvironmentCacheKey = "gpu:environment"
var gpuEnvironmentLock = make(chan struct{}, 1)
var gpuVendors = []string{"nvidia", "intel", "ascend", "amd"}
type gpuEnvironmentProbe struct {
CheckedAt time.Time `json:"checkedAt"`
Hardware map[string]int `json:"hardware"`
HardwareError string `json:"hardwareError,omitempty"`
Commands map[string]bool `json:"commands"`
NVIDIAChecked bool `json:"nvidiaChecked"`
Checks []dto.GPUEnvironmentCheck `json:"checks"`
}
func LoadGPUInfo(ctx context.Context, refresh bool) (*dto.GPURealtimeInfo, error) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
if err := ctx.Err(); err != nil {
return nil, err
}
snapshot := &accelerator.Snapshot{}
var collectErr error
if available, client := accelerator.New(); available {
collected, err := client.Collect(ctx)
collectErr = err
if collected != nil {
snapshot = collected
}
}
if err := ctx.Err(); err != nil {
return nil, err
}
if collectErr != nil {
snapshot.Info.Warnings = append(snapshot.Info.Warnings, collectErr.Error())
}
if snapshot.Info.CollectedAt.IsZero() {
snapshot.Info.CollectedAt = time.Now()
}
if global.LOG != nil {
for _, warning := range snapshot.Info.Warnings {
global.LOG.Warnf("load realtime accelerator data: %s", warning)
}
}
summaries := summarizeGPUs(snapshot)
nvidiaPresent := slices.ContainsFunc(summaries, func(item dto.GPUSummary) bool { return item.Vendor == "nvidia" })
probe := loadGPUEnvironmentProbe(ctx, refresh, nvidiaPresent)
if err := ctx.Err(); err != nil {
return nil, err
}
environment := assembleGPUEnvironment(probe, summaries, collectErr, snapshot.Info)
for _, check := range environment.Checks {
if check.Item == "driver" && check.Vendor != "" && !slices.ContainsFunc(summaries, func(item dto.GPUSummary) bool { return item.Vendor == check.Vendor }) {
summaries = append(summaries, dto.GPUSummary{Vendor: check.Vendor, MemoryPartial: true})
}
}
return &dto.GPURealtimeInfo{Info: snapshot.Info, Environment: environment, Summaries: summaries}, nil
}
func loadGPUEnvironmentProbe(ctx context.Context, refresh, nvidiaPresent bool) gpuEnvironmentProbe {
select {
case gpuEnvironmentLock <- struct{}{}:
defer func() { <-gpuEnvironmentLock }()
case <-ctx.Done():
return gpuEnvironmentProbe{CheckedAt: time.Now(), HardwareError: ctx.Err().Error()}
}
if !refresh && global.CACHE != nil {
var cached gpuEnvironmentProbe
if value := global.CACHE.Get(gpuEnvironmentCacheKey); value != "" && json.Unmarshal([]byte(value), &cached) == nil && (!nvidiaPresent || cached.NVIDIAChecked) {
return cached
}
}
probe := gpuEnvironmentProbe{CheckedAt: time.Now(), Commands: map[string]bool{}}
var err error
probe.Hardware, err = detectGPUHardware("/sys/bus/pci/devices")
if err != nil {
probe.HardwareError = err.Error()
}
for vendor := range gpu.ManagementCommands() {
probe.Commands[vendor] = true
}
probe.Commands["intel"], _ = xpu.New()
probe.Commands["ascend"], _ = npu.New()
if nvidiaPresent || probe.Hardware["nvidia"] > 0 {
probe.NVIDIAChecked = true
probe.Checks = checkNVIDIAContainerEnvironment(ctx)
}
if ctx.Err() == nil && global.CACHE != nil {
if data, err := json.Marshal(probe); err == nil {
global.CACHE.SetWithTTL(gpuEnvironmentCacheKey, string(data), time.Minute)
}
}
return probe
}
func detectGPUHardware(directory string) (map[string]int, error) {
devices := map[string]int{}
entries, err := os.ReadDir(directory)
if err != nil {
return devices, err
}
var readErrors []string
for _, entry := range entries {
path := filepath.Join(directory, entry.Name())
vendorData, err := os.ReadFile(filepath.Join(path, "vendor"))
if err != nil {
readErrors = append(readErrors, err.Error())
continue
}
vendor := map[string]string{"0x10de": "nvidia", "0x8086": "intel", "0x19e5": "ascend", "0x1002": "amd"}[strings.ToLower(strings.TrimSpace(string(vendorData)))]
if vendor == "" {
continue
}
classData, err := os.ReadFile(filepath.Join(path, "class"))
if err != nil {
readErrors = append(readErrors, err.Error())
continue
}
class, err := strconv.ParseUint(strings.TrimSpace(string(classData)), 0, 32)
if err != nil {
readErrors = append(readErrors, fmt.Sprintf("read PCI class for %s: %v", entry.Name(), err))
continue
}
baseClass := class >> 16
if (vendor == "ascend" && baseClass == 0x12) || (vendor != "ascend" && (baseClass == 0x03 || (vendor == "nvidia" && baseClass == 0x12))) {
devices[vendor]++
}
}
if len(readErrors) > 0 {
return devices, fmt.Errorf("PCI inventory incomplete: %s", strings.Join(readErrors, "; "))
}
return devices, nil
}
func assembleGPUEnvironment(probe gpuEnvironmentProbe, summaries []dto.GPUSummary, collectErr error, info accelerator.Info) dto.GPUEnvironment {
result := dto.GPUEnvironment{CheckedAt: probe.CheckedAt, HardwareStatus: "not_detected", Checks: []dto.GPUEnvironmentCheck{}}
if len(probe.Hardware) > 0 || len(summaries) > 0 {
result.HardwareStatus = "detected"
} else if probe.HardwareError != "" {
result.HardwareStatus = "unknown"
}
for _, vendor := range gpuVendors {
index := slices.IndexFunc(summaries, func(item dto.GPUSummary) bool { return item.Vendor == vendor })
if index < 0 && probe.Hardware[vendor] == 0 && !(probe.HardwareError != "" && probe.Commands[vendor] && collectErr != nil) {
continue
}
check := dto.GPUEnvironmentCheck{Vendor: vendor, Item: "driver", Status: "unknown"}
switch {
case index >= 0:
check.Status = "passed"
check.Version = summaries[index].DriverVersion
if vendor == "intel" {
var readErrors []string
for _, device := range info.XPUs {
if device.ReadError != "" {
readErrors = append(readErrors, device.ReadError)
}
}
if len(readErrors) > 0 {
check.Status = "unknown"
if len(readErrors) == len(info.XPUs) {
check.Status = "failed"
}
check.Reason = strings.Join(readErrors, "; ")
}
}
case !probe.Commands[vendor]:
check.Status = "missing"
check.Reason = "The device management utility was not found; check the driver and management tool installation."
case collectErr != nil:
check.Status = "failed"
check.Reason = collectErr.Error()
default:
check.Reason = "The management utility returned no readable devices."
if len(info.Warnings) > 0 {
check.Status = "failed"
check.Reason = strings.Join(info.Warnings, "; ")
}
}
result.Checks = append(result.Checks, check)
}
result.Checks = append(result.Checks, probe.Checks...)
if probe.HardwareError != "" {
result.Checks = append(result.Checks, dto.GPUEnvironmentCheck{Item: "driver", Status: "unknown", Reason: probe.HardwareError})
}
return result
}
func findGPUContainerCommand(name string) string {
for _, path := range []string{name, filepath.Join("/usr/bin", name), filepath.Join("/usr/sbin", name), filepath.Join("/usr/local/bin", name), filepath.Join("/usr/local/sbin", name), filepath.Join("/usr/local/nvidia/toolkit", name)} {
if cmd.Which(path) {
return path
}
if filepath.IsAbs(path) {
if info, err := os.Stat(path); err == nil && !info.IsDir() {
return path
}
}
}
return ""
}
func checkNVIDIAContainerEnvironment(ctx context.Context) []dto.GPUEnvironmentCheck {
toolkitCtx, cancelToolkit := context.WithTimeout(ctx, 5*time.Second)
toolkit := checkNVIDIAToolkit(toolkitCtx, findGPUContainerCommand, func(ctx context.Context, path string) (string, error) {
return cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(3*time.Second)).RunWithStdout(path, "--version")
})
cancelToolkit()
dockerCtx, cancelDocker := context.WithTimeout(ctx, 5*time.Second)
defer cancelDocker()
dockerCheck := dto.GPUEnvironmentCheck{Vendor: "nvidia", Item: "docker", Status: "failed"}
cli, err := docker.NewDockerClient()
if err == nil {
defer cli.Close()
var info system.Info
info, err = cli.Info(dockerCtx)
if err == nil {
dockerCheck = checkNVIDIADockerInfo(info, false)
if cli.DaemonHost() == "unix:///var/run/docker.sock" {
dockerCheck = checkNVIDIADockerConfig(info, readDockerDaemonConfig, findGPUContainerCommand)
} else {
dockerCheck.Status = "unknown"
dockerCheck.Reason = "The selected Docker daemon's NVIDIA runtime cannot be verified using local executables or the default daemon.json."
}
}
}
if err != nil {
dockerCheck.Reason = err.Error()
}
return []dto.GPUEnvironmentCheck{toolkit, dockerCheck}
}
func checkNVIDIAToolkit(ctx context.Context, findCommand func(string) string, runVersion func(context.Context, string) (string, error)) dto.GPUEnvironmentCheck {
toolkit := dto.GPUEnvironmentCheck{Vendor: "nvidia", Item: "toolkit", Status: "missing"}
cli := findCommand("nvidia-container-cli")
if cli == "" {
toolkit.Reason = "The required nvidia-container-cli component was not found."
return toolkit
}
runtime := findCommand("nvidia-container-runtime")
if runtime == "" {
runtime = findCommand("nvidia-container-runtime-hook")
}
if runtime == "" {
toolkit.Reason = "The NVIDIA container runtime and runtime hook were not found."
return toolkit
}
data, err := runVersion(ctx, cli)
if err != nil || strings.TrimSpace(data) == "" {
toolkit.Status = "failed"
toolkit.Reason = "nvidia-container-cli returned an empty version."
if err != nil {
toolkit.Reason = err.Error()
}
return toolkit
}
toolkit.Status = "passed"
toolkit.Version = strings.SplitN(strings.TrimSpace(data), "\n", 2)[0]
if data, err := runVersion(ctx, runtime); err != nil || strings.TrimSpace(data) == "" {
toolkit.Status = "failed"
toolkit.Reason = "The NVIDIA container runtime returned an empty version."
if err != nil {
toolkit.Reason = err.Error()
}
return toolkit
}
return toolkit
}
func checkNVIDIADockerInfo(info system.Info, hookInstalled bool) dto.GPUEnvironmentCheck {
result := dto.GPUEnvironmentCheck{Vendor: "nvidia", Item: "docker", Status: "unconfigured", Version: info.ServerVersion}
if _, ok := info.Runtimes["nvidia"]; ok {
result.Status = "passed"
return result
}
if hookInstalled {
result.Status = "unknown"
result.Reason = "NVIDIA runtime hook is installed, but Docker does not expose its GPU device-driver registration."
}
return result
}
func checkNVIDIADockerConfig(info system.Info, readConfig func() (map[string]interface{}, error), findCommand func(string) string) dto.GPUEnvironmentCheck {
result := checkNVIDIADockerInfo(info, false)
if result.Status == "passed" {
runtimePath := info.Runtimes["nvidia"].Path
if runtimePath == "" {
result.Status = "unknown"
result.Reason = "Docker registered an NVIDIA runtime without an executable path; its availability cannot be verified."
return result
}
resolved := runtimePath
if !filepath.IsAbs(runtimePath) {
resolved = findCommand(runtimePath)
}
if resolved == "" || !cmd.Which(resolved) {
result.Status = "failed"
result.Code = "dockerConfigInvalid"
result.Reason = fmt.Sprintf("The registered NVIDIA runtime %q cannot be found or executed.", runtimePath)
}
return result
}
if hook := findCommand("nvidia-container-runtime-hook"); hook != "" && cmd.Which(hook) {
result = checkNVIDIADockerInfo(info, true)
}
config, err := readConfig()
if err != nil {
var syntaxError *json.SyntaxError
var typeError *json.UnmarshalTypeError
switch {
case os.IsNotExist(err):
if result.Status == "unknown" {
return result
}
result.Code = "dockerConfigMissing"
result.Reason = fmt.Sprintf("Docker configuration %s is missing; configure runtimes.nvidia.path and restart Docker.", constant.DaemonJsonPath)
case errors.As(err, &syntaxError), errors.As(err, &typeError):
result.Status = "failed"
result.Code = "dockerConfigInvalid"
result.Reason = fmt.Sprintf("Cannot parse Docker configuration %s: %v", constant.DaemonJsonPath, err)
default:
result.Status = "failed"
result.Code = "dockerConfigUnreadable"
result.Reason = fmt.Sprintf("Cannot read Docker configuration %s: %v", constant.DaemonJsonPath, err)
}
return result
}
if config == nil {
result.Status = "failed"
result.Code = "dockerConfigInvalid"
result.Reason = "Docker configuration must be a JSON object."
return result
}
runtimes, ok := config["runtimes"].(map[string]interface{})
if config["runtimes"] == nil || (ok && runtimes["nvidia"] == nil) {
if result.Status == "unknown" {
return result
}
result.Code = "dockerConfigMissing"
result.Reason = fmt.Sprintf("Docker configuration %s has no runtimes.nvidia entry.", constant.DaemonJsonPath)
return result
}
nvidia, ok := runtimes["nvidia"].(map[string]interface{})
runtimePath, pathOK := nvidia["path"].(string)
if !ok || !pathOK || strings.TrimSpace(runtimePath) == "" || (!filepath.IsAbs(runtimePath) && filepath.Base(runtimePath) != runtimePath) {
result.Status = "failed"
result.Code = "dockerConfigInvalid"
result.Reason = "runtimes.nvidia.path must contain a runtime executable name or absolute path."
return result
}
resolved := findCommand(runtimePath)
if resolved == "" || !cmd.Which(resolved) {
result.Status = "failed"
result.Code = "dockerConfigInvalid"
result.Reason = fmt.Sprintf("The configured NVIDIA runtime %q cannot be found or executed.", runtimePath)
return result
}
result.Status = "unconfigured"
result.Code = "dockerConfigPending"
result.Reason = "The default daemon.json contains runtimes.nvidia.path, but the running Docker daemon has not registered the NVIDIA runtime; confirm the daemon uses this configuration file, then restart Docker and check again."
return result
}
func summarizeGPUs(snapshot *accelerator.Snapshot) []dto.GPUSummary {
result := []dto.GPUSummary{}
for _, vendor := range gpuVendors {
summary := dto.GPUSummary{Vendor: vendor}
cards, chips := map[string]bool{}, map[string]bool{}
var versions []string
for _, device := range snapshot.Devices {
deviceVendor := device.Vendor
if device.XPU != nil {
deviceVendor = "intel"
}
if deviceVendor != vendor || device.ParentID != "" || chips[device.ID] {
continue
}
chips[device.ID] = true
cardID := device.ID
if device.NPU != nil {
cardID = strconv.Itoa(device.NPUIndex)
}
cards[cardID] = true
if device.DriverVersion != "" && !slices.Contains(versions, device.DriverVersion) {
versions = append(versions, device.DriverVersion)
}
if !device.Metrics.MemoryTotal.Available() {
summary.MemoryPartial = true
continue
}
bytes := device.Metrics.MemoryTotal.ValueOrZero() * 1024 * 1024
if device.GPU != nil && device.GPU.SharedMemory {
if summary.SharedMemoryTotal == nil || bytes > *summary.SharedMemoryTotal {
summary.SharedMemoryTotal = &bytes
}
} else {
if summary.MemoryTotal == nil {
summary.MemoryTotal = new(float64)
}
*summary.MemoryTotal += bytes
}
}
if len(cards) > 0 {
count := len(cards)
summary.CardCount = &count
summary.DriverVersion = strings.Join(versions, " / ")
if vendor == "nvidia" {
summary.CudaVersion = snapshot.Info.CudaVersion
}
result = append(result, summary)
}
}
return result
}
@@ -39,6 +39,7 @@ func normalizeGPU(item *gpu.Device) Device {
Vendor: item.Type, Vendor: item.Type,
Index: int(item.Index), Index: int(item.Index),
Name: item.ProductName, Name: item.ProductName,
DriverVersion: item.DriverVersion,
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName), Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
BusID: item.BusID, BusID: item.BusID,
Metrics: metrics, Metrics: metrics,
@@ -121,6 +122,7 @@ func normalizeXPU(item *xpu.Device) Device {
Vendor: item.Basic.VendorName, Vendor: item.Basic.VendorName,
Index: item.Basic.DeviceID, Index: item.Basic.DeviceID,
Name: item.Basic.DeviceName, Name: item.Basic.DeviceName,
DriverVersion: item.Basic.DriverVersion,
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName), Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
BusID: item.Basic.PciBdfAddress, BusID: item.Basic.PciBdfAddress,
Metrics: metrics, Metrics: metrics,
+3 -1
View File
@@ -56,7 +56,9 @@ func (p npuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
NPUs: info.Devices, NPUs: info.Devices,
} }
for index := range result.NPUs { for index := range result.NPUs {
result.Devices = append(result.Devices, normalizeNPU(&result.NPUs[index])) device := normalizeNPU(&result.NPUs[index])
device.DriverVersion = info.DriverVersion
result.Devices = append(result.Devices, device)
} }
return result, nil return result, nil
} }
@@ -102,6 +102,7 @@ type Device struct {
NPUIndex int NPUIndex int
ChipIndex int ChipIndex int
Name string Name string
DriverVersion string
Label string Label string
BusID string BusID string
Metrics Metrics Metrics Metrics
+15 -3
View File
@@ -23,12 +23,24 @@ type providerResult struct {
err error err error
} }
func New() (bool, Client) { func ManagementCommands() map[string]string {
client := Client{} commands := make(map[string]string)
if cmd.Which(nvidiaSMICommand) { if cmd.Which(nvidiaSMICommand) {
client.providers = append(client.providers, nvidiaSMI{}) commands["nvidia"] = nvidiaSMICommand
} }
if command, ok := findAMDSMI(); ok { if command, ok := findAMDSMI(); ok {
commands["amd"] = command
}
return commands
}
func New() (bool, Client) {
client := Client{}
commands := ManagementCommands()
if commands["nvidia"] != "" {
client.providers = append(client.providers, nvidiaSMI{})
}
if command := commands["amd"]; command != "" {
client.providers = append(client.providers, amdSMI{command: command}) client.providers = append(client.providers, amdSMI{command: command})
} }
return len(client.providers) > 0, client return len(client.providers) > 0, client
+1
View File
@@ -63,6 +63,7 @@ type Info struct {
} }
type Device struct { type Device struct {
ReadError string `json:"-"`
Tiles []TileStats `json:"tiles"` Tiles []TileStats `json:"tiles"`
ProcessStatus string `json:"processStatus"` ProcessStatus string `json:"processStatus"`
+4 -2
View File
@@ -122,8 +122,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
wgCmd.Wait() wgCmd.Wait()
if xpuErr != nil { if xpuErr != nil {
xpu.ReadError = fmt.Sprintf("xpu-smi device %d: %v", device.DeviceID, xpuErr)
mu.Lock() mu.Lock()
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d: %v", device.DeviceID, xpuErr)) res.Warnings = append(res.Warnings, xpu.ReadError)
res.Devices = append(res.Devices, xpu) res.Devices = append(res.Devices, xpu)
mu.Unlock() mu.Unlock()
return return
@@ -131,8 +132,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
var info discoveryDevice var info discoveryDevice
if err := json.Unmarshal([]byte(xpuData), &info); err != nil { if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
xpu.ReadError = fmt.Sprintf("xpu-smi device %d output: %v", device.DeviceID, err)
mu.Lock() mu.Lock()
res.Warnings = append(res.Warnings, fmt.Sprintf("xpu-smi device %d output: %v", device.DeviceID, err)) res.Warnings = append(res.Warnings, xpu.ReadError)
res.Devices = append(res.Devices, xpu) res.Devices = append(res.Devices, xpu)
mu.Unlock() mu.Unlock()
return return
+24
View File
@@ -37,6 +37,28 @@ export namespace AI {
info?: string; info?: string;
} }
export interface GPUEnvironmentCheck {
vendor: string;
item: 'driver' | 'toolkit' | 'docker';
status: 'passed' | 'missing' | 'unconfigured' | 'failed' | 'unknown';
code?: string;
version?: string;
reason?: string;
}
export interface GPUEnvironment {
checkedAt: string;
hardwareStatus: 'detected' | 'not_detected' | 'unknown';
checks: GPUEnvironmentCheck[];
}
export interface GPUSummary {
vendor: string;
driverVersion: string;
cudaVersion: string;
cardCount: number | null;
memoryTotal: number | null;
sharedMemoryTotal: number | null;
memoryPartial: boolean;
}
export interface Info { export interface Info {
collectedAt?: string; collectedAt?: string;
warnings?: string[]; warnings?: string[];
@@ -47,6 +69,8 @@ export namespace AI {
npu: NPU[]; npu: NPU[];
xpuDriverVersion: string; xpuDriverVersion: string;
xpu: XpuInfo['xpu']; xpu: XpuInfo['xpu'];
environment?: GPUEnvironment;
summaries?: GPUSummary[];
} }
export interface GPU { export interface GPU {
sharedMemory?: boolean; sharedMemory?: boolean;
+5 -2
View File
@@ -47,8 +47,11 @@ export const loadModelGPUDevices = () => {
return http.get<{ devices: AI.ModelGPUDevice[]; warnings: string[] }>('/ai/model/gpu/devices'); return http.get<{ devices: AI.ModelGPUDevice[]; warnings: string[] }>('/ai/model/gpu/devices');
}; };
export const loadGPUInfo = () => { export const loadGPUInfo = (refresh = false, currentNode?: string) => {
return http.get<AI.Info>(`/ai/gpu/load`); return http.get<AI.Info>(`/ai/gpu/load`, refresh ? { refresh: true } : {}, {
timeout: TimeoutEnum.T_40S,
headers: currentNode ? { CurrentNode: currentNode } : undefined,
});
}; };
export const getGPUOptions = (currentNode?: string) => { export const getGPUOptions = (currentNode?: string) => {
return http.get<AI.MonitorGPUOptions>( return http.get<AI.MonitorGPUOptions>(
@@ -1,57 +0,0 @@
<template>
<div>
<el-card class="cached-tabs">
<el-radio-group :model-value="activeTab" @change="select">
<el-radio-button v-for="item in items" :key="item.value" :value="item.value" size="large">
{{ item.label }}
</el-radio-button>
</el-radio-group>
</el-card>
<slot :active="activeTab" />
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { useCachedTab } from '@/composables/useCachedTab';
const props = withDefaults(
defineProps<{
items: { value: string; label: string }[];
defaultValue: string;
cacheKey?: string;
requestedValue?: string;
}>(),
{ cacheKey: 'default' },
);
const emit = defineEmits<{ activate: [value: string] }>();
const pagePath = useRoute().path;
const { activeTab, select } = useCachedTab({
key: () => JSON.stringify([pagePath, props.cacheKey]),
values: () => props.items.map((item) => item.value),
defaultValue: () => props.defaultValue,
requestedValue: () => props.requestedValue,
onActivate: (value) => emit('activate', value),
});
</script>
<style scoped lang="scss">
.cached-tabs {
--el-card-padding: 0;
:deep(.el-radio-button__inner) {
min-width: 100px;
height: 100%;
background-color: var(--panel-button-active) !important;
box-shadow: none !important;
outline: none !important;
border: 2px solid transparent !important;
}
:deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
color: $primary-color;
border-color: $primary-color !important;
border-radius: 4px;
}
}
</style>
@@ -5,8 +5,10 @@
<el-radio-group v-model="activeName" @change="handleChange"> <el-radio-group v-model="activeName" @change="handleChange">
<el-radio-button <el-radio-button
class="router_card_button" class="router_card_button"
:class="{ 'opacity-50': button.disabled }"
:label="button.label" :label="button.label"
:value="button.label" :value="button.label"
:disabled="button.disabled"
v-for="(button, index) in buttonArray" v-for="(button, index) in buttonArray"
size="large" size="large"
:key="index" :key="index"
@@ -50,7 +52,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { openPage } from '@/utils/router'; import { routerToNameWithQuery, routerToPathWithQuery } from '@/utils/router';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { hasPermissionMetaAccess, hasRouteAccess } from '@/utils/rbac'; import { hasPermissionMetaAccess, hasRouteAccess } from '@/utils/rbac';
@@ -94,10 +96,13 @@ const expiresAlertKey = computed(() => (isEnterprise.value ? 'xpack.expiresEnter
const handleChange = async (label: string) => { const handleChange = async (label: string) => {
const btn = buttonArray.value.find((btn) => btn.label === label); const btn = buttonArray.value.find((btn) => btn.label === label);
if (!btn) return; if (!btn || btn.disabled) {
syncActiveName();
return;
}
try { try {
if (btn.path) await openPage({ path: btn.path }); if (btn.path) await routerToPathWithQuery(btn.path, { uncached: 'true' });
else if (btn.name) await openPage({ name: btn.name }); else if (btn.name) await routerToNameWithQuery(btn.name, { uncached: 'true' });
} finally { } finally {
syncActiveName(); syncActiveName();
} }
+3 -40
View File
@@ -5,36 +5,21 @@
clearable clearable
v-model="searchInfo" v-model="searchInfo"
@clear="search()" @clear="search()"
@keydown.enter.stop.prevent="search()" suffix-icon="Search"
@blur="searchIfChanged()" @change="search()"
:disabled="props.disabled" :disabled="props.disabled"
:placeholder="props.placeholder || $t('commons.button.search')" :placeholder="props.placeholder || $t('commons.button.search')"
> ></el-input>
<template #suffix>
<button
type="button"
class="search-submit"
:disabled="props.disabled"
:aria-label="$t('commons.button.search')"
@mousedown.prevent
@click="search()"
>
<el-icon><Search /></el-icon>
</button>
</template>
</el-input>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onActivated, ref, watch } from 'vue'; import { onActivated, ref, watch } from 'vue';
import { Search } from '@element-plus/icons-vue';
defineOptions({ name: 'TableSearch' }); defineOptions({ name: 'TableSearch' });
const emit = defineEmits(['search', 'update:searchName']); const emit = defineEmits(['search', 'update:searchName']);
const searchInfo = ref(); const searchInfo = ref();
let lastSearch: string | number | undefined;
const props = defineProps({ const props = defineProps({
placeholder: String, placeholder: String,
disabled: { disabled: {
@@ -52,7 +37,6 @@ watch(
(newVal) => { (newVal) => {
if (searchInfo.value !== newVal) { if (searchInfo.value !== newVal) {
searchInfo.value = newVal; searchInfo.value = newVal;
lastSearch = newVal;
} }
}, },
{ immediate: true }, { immediate: true },
@@ -63,28 +47,7 @@ onActivated(() => {
}); });
const search = () => { const search = () => {
if (props.disabled) return;
lastSearch = searchInfo.value;
emit('update:searchName', searchInfo.value); emit('update:searchName', searchInfo.value);
emit('search'); emit('search');
}; };
const searchIfChanged = () => {
if (searchInfo.value !== lastSearch) search();
};
</script> </script>
<style scoped>
.search-submit {
display: inline-flex;
align-items: center;
padding: 0;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
}
.search-submit:disabled {
cursor: not-allowed;
}
</style>
@@ -175,8 +175,6 @@ import { useCardColumns } from './useCardColumns';
import { useContextMenu } from './useContextMenu'; import { useContextMenu } from './useContextMenu';
import { useResponsivePagination } from './useResponsivePagination'; import { useResponsivePagination } from './useResponsivePagination';
import { useTableSelection } from './useTableSelection'; import { useTableSelection } from './useTableSelection';
import { useRoute } from 'vue-router';
import { usePaginationCache } from './usePaginationCache';
const slots = useSlots(); const slots = useSlots();
const attrs = useAttrs(); const attrs = useAttrs();
const { isMobile, openMenuTabs, currentNode } = useGlobalStore(); const { isMobile, openMenuTabs, currentNode } = useGlobalStore();
@@ -189,16 +187,11 @@ type ViewMode = 'table' | 'card';
const props = defineProps({ const props = defineProps({
header: String, header: String,
data: { type: Array as PropType<any[]>, default: () => [] }, data: { type: Array as PropType<any[]>, default: () => [] },
loading: { type: Boolean, default: false },
selectionContext: { type: [String, Number, Array, Object, Function], default: undefined }, selectionContext: { type: [String, Number, Array, Object, Function], default: undefined },
paginationConfig: { paginationConfig: {
type: Object, type: Object,
required: false, required: false,
}, },
cachePagination: { type: Boolean, default: undefined },
refreshOnRestore: { type: Boolean, default: true },
cacheId: String,
cacheScope: [String, Number],
pageSizes: { pageSizes: {
type: Array as PropType<number[]>, type: Array as PropType<number[]>,
default: () => [5, 10, 20, 50, 100, 200, 500], default: () => [5, 10, 20, 50, 100, 200, 500],
@@ -224,15 +217,6 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['search', 'update:selects', 'update:paginationConfig', 'update:viewMode']); const emit = defineEmits(['search', 'update:selects', 'update:paginationConfig', 'update:viewMode']);
usePaginationCache({
pagePath: useRoute().path,
pagination: () => props.paginationConfig as { currentPage: number; pageSize: number; cacheSizeKey?: string },
enabled: () => props.cachePagination,
id: () => props.cacheId,
scope: () => props.cacheScope,
search: () => emit('search'),
refreshOnRestore: () => props.refreshOnRestore,
});
const tableRef = ref(); const tableRef = ref();
const complexTableRef = ref<HTMLElement>(); const complexTableRef = ref<HTMLElement>();
const tableHeight = ref<number | string>(''); const tableHeight = ref<number | string>('');
@@ -359,10 +343,10 @@ const handleRightClick = (row, column, event) => {
}; };
watch( watch(
() => [props.data, props.loading], () => props.data,
() => { () => {
const pagination = props.paginationConfig; const pagination = props.paginationConfig;
if (props.loading || !pagination || !pagination.pageSize) return; if (!pagination || !pagination.pageSize) return;
const lastPage = Math.max(1, Math.ceil(pagination.total / pagination.pageSize)); const lastPage = Math.max(1, Math.ceil(pagination.total / pagination.pageSize));
if (pagination.currentPage > lastPage) { if (pagination.currentPage > lastPage) {
pagination.currentPage = lastPage; pagination.currentPage = lastPage;
@@ -377,7 +361,7 @@ function currentChange() {
} }
function sizeChange() { function sizeChange() {
if (props.cachePagination === false && props.paginationConfig.cacheSizeKey) { if (props.paginationConfig.cacheSizeKey) {
writePanelCache( writePanelCache(
'pagination', 'pagination',
panelCacheKey(`size:${props.paginationConfig.cacheSizeKey}`), panelCacheKey(`size:${props.paginationConfig.cacheSizeKey}`),
@@ -1,124 +0,0 @@
import { nextTick, onActivated, onDeactivated, onScopeDispose, watch } from 'vue';
import GlobalStore from '@/store/modules/global';
import { panelCacheKey, readPanelCache, writePanelCache } from '@/utils/panel-cache';
interface Pagination {
currentPage: number;
pageSize: number;
cacheSizeKey?: string;
}
interface Options {
pagePath: string;
pagination: () => Pagination | undefined;
enabled: () => boolean | undefined;
id: () => string | undefined;
scope: () => string | number | undefined;
search: () => void;
refreshOnRestore?: () => boolean;
}
/** Restore during child setup, before the owning page's onMounted query. */
export const usePaginationCache = (options: Options) => {
const store = GlobalStore();
const defaults = new WeakMap<Pagination, { currentPage: number; pageSize: number }>();
let active = true;
let restoring = false;
let activeKey: string | undefined;
let queued = false;
let disposed = false;
let pending: { key: string; currentPage: number; pageSize: number } | undefined;
let writeQueued = false;
const flush = () => {
if (!pending) return;
const { key: target, currentPage, pageSize } = pending;
pending = undefined;
writePanelCache('pagination', target, { currentPage, pageSize });
};
const requestSearch = () => {
if (queued || !key()) return;
queued = true;
void nextTick(() => {
queued = false;
if (active && !disposed && key() && options.refreshOnRestore?.() !== false) options.search();
});
};
const key = () => {
const pagination = options.pagination();
if (options.enabled() === false || !pagination || !store.cacheUserID) return undefined;
return panelCacheKey(
JSON.stringify([
'table-pagination',
options.pagePath,
options.id() ?? pagination.cacheSizeKey ?? 'default',
options.scope() ?? '',
]),
);
};
const positiveInteger = (value: unknown): value is number => Number.isInteger(value) && Number(value) > 0;
const restore = () => {
flush();
activeKey = key();
const pagination = options.pagination();
if (!activeKey || !pagination) return false;
if (!defaults.has(pagination)) {
defaults.set(pagination, { currentPage: pagination.currentPage, pageSize: pagination.pageSize });
}
const initial = defaults.get(pagination)!;
const saved = readPanelCache<Partial<Pagination>>('pagination', activeKey);
const page = positiveInteger(saved?.currentPage) ? saved.currentPage : initial.currentPage;
const size = positiveInteger(saved?.pageSize) ? saved.pageSize : initial.pageSize;
const changed = pagination.currentPage !== page || pagination.pageSize !== size;
restoring = true;
try {
pagination.pageSize = size;
pagination.currentPage = page;
} finally {
restoring = false;
}
return changed;
};
// The initial restore deliberately does not emit: the parent owns its first query.
restore();
watch(
() => [key(), options.pagination(), store.panelCache.pagination, store.panelCache.duration],
() => {
if (!active) return;
const previousKey = activeKey;
const changed = restore();
if (activeKey && (changed || previousKey !== activeKey)) requestSearch();
},
{ flush: 'sync' },
);
watch(
() => [options.pagination()?.currentPage, options.pagination()?.pageSize],
([currentPage, pageSize]) => {
// Never write old-page state under a newly selected node/account.
if (!active || restoring || !activeKey || activeKey !== key()) return;
if (!positiveInteger(currentPage) || !positiveInteger(pageSize)) return;
pending = { key: activeKey, currentPage, pageSize };
if (!writeQueued) {
writeQueued = true;
queueMicrotask(() => {
writeQueued = false;
flush();
});
}
},
{ flush: 'sync' },
);
onDeactivated(() => {
flush();
active = false;
});
onActivated(() => {
if (active) return;
active = true;
restore();
requestSearch();
});
onScopeDispose(() => {
flush();
disposed = true;
active = false;
});
};
-89
View File
@@ -1,89 +0,0 @@
import { nextTick, onActivated, onDeactivated, onMounted, onScopeDispose, readonly, ref, watch } from 'vue';
import GlobalStore from '@/store/modules/global';
import { panelCacheKey, readPanelCache, writePanelCache } from '@/utils/panel-cache';
interface CachedTabOptions {
key: () => string;
values: () => string[];
defaultValue: () => string;
requestedValue?: () => string | undefined;
onActivate: (value: string) => unknown;
}
export const useCachedTab = (options: CachedTabOptions) => {
const store = GlobalStore();
const activeTab = ref('');
let active = true;
let mounted = false;
let version = 0;
let restoredKey: string | undefined;
const key = () => panelCacheKey(`inline-tabs:${options.key()}`);
const valid = (value: unknown): value is string => typeof value === 'string' && options.values().includes(value);
const restore = (useRequested = true) => {
const requested = useRequested ? options.requestedValue?.() : undefined;
const saved = readPanelCache<string>('tabs', key());
restoredKey = key();
activeTab.value = valid(requested)
? requested
: valid(saved)
? saved
: valid(options.defaultValue())
? options.defaultValue()
: options.values()[0] || '';
};
const activate = async () => {
const current = ++version;
await nextTick();
if (current !== version || !active || !mounted || !valid(activeTab.value)) return;
options.onActivate(activeTab.value);
};
const select = (value: string) => {
if (!active || !valid(value) || value === activeTab.value) return;
activeTab.value = value;
writePanelCache('tabs', key(), value);
void activate();
};
watch(
[
key,
() => store.panelCache.tabs,
() => store.panelCache.duration,
() => JSON.stringify(options.values()),
options.defaultValue,
() => options.requestedValue?.(),
],
(values, previous) => {
if (!active) return;
// Reflecting the selected tab into the URL is not another selection.
if (
previous.length &&
values.slice(0, 5).every((value, index) => value === previous[index]) &&
values[5] === activeTab.value
)
return;
// A URL left over from the previous node/account must not override its cache.
restore(!previous.length || values[0] === previous[0]);
if (mounted) void activate();
},
{ immediate: true, flush: 'sync' },
);
onMounted(() => {
mounted = true;
void activate();
});
onDeactivated(() => {
active = false;
version++;
});
onActivated(() => {
if (active) return;
active = true;
restore(restoredKey === key());
void activate();
});
onScopeDispose(() => {
active = false;
version++;
});
return { activeTab: readonly(activeTab), select };
};
+67 -2
View File
@@ -1743,8 +1743,8 @@ const message = {
running: 'Running requests', running: 'Running requests',
waiting: 'Waiting requests', waiting: 'Waiting requests',
cacheUsage: 'KV cache usage', cacheUsage: 'KV cache usage',
promptThroughput: 'Input token throughput', promptThroughput: 'Input throughput',
generationThroughput: 'Output token throughput', generationThroughput: 'Output throughput',
requestThroughput: 'Completed requests', requestThroughput: 'Completed requests',
timeToFirstToken: 'Mean time to first token', timeToFirstToken: 'Mean time to first token',
timePerOutputToken: 'Mean inter-token latency', timePerOutputToken: 'Mean inter-token latency',
@@ -1764,6 +1764,69 @@ const message = {
timePerOutputTokenChart: 'Inter-token latency', timePerOutputTokenChart: 'Inter-token latency',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'Host GPU',
containerGPU: 'Container GPU support',
monitorAvailable: 'Monitoring available',
monitorPartial: 'Partial monitoring',
notReady: 'Not ready',
ready: 'Ready',
checkedAt: 'Checked at',
checkDetailsHelp: 'Review the check details, verify the environment, and check again.',
driverActionTitle: 'Check the host GPU driver first',
driverActionHelp:
'Install or repair the affected device driver before continuing with the container environment checks.',
driverGuide: 'View driver installation guide',
toolkitActionTitle: 'Container GPU support is not ready',
toolkitActionHelp: 'Install or repair NVIDIA Container Toolkit, then check the Docker configuration.',
toolkitGuide: 'View Toolkit installation guide',
dockerActionTitle: 'Docker NVIDIA GPU support is not ready',
dockerActionHelp: 'Check the NVIDIA runtime configuration in Docker, then check again.',
monitorUnaffected: 'Host GPU monitoring is unaffected by this issue.',
title: 'Environment checks',
passed: 'Environment checks passed',
issues: 'Detected {count} environment issues. Follow the guidance to check them.',
recheck: 'Check again',
installGuide: 'View installation guide',
goContainerSettings: 'Open Container Settings',
details: 'Check details',
vendor: 'Vendor',
gpuCount: 'GPU count',
cardCount: 'Card count',
notDetected: 'No supported GPU/NPU/XPU devices were detected on the current node.',
unknown: 'Could not confirm device status. Review the check details and try again.',
loadFailed: 'Could not read device information. Please try again.',
notApplicable: 'Not applicable',
cudaHelper:
'This is the CUDA version supported by the driver, not the CUDA Toolkit version installed on the host.',
partialMemory: 'Some memory data is unavailable',
sharedMemory: 'Shared system memory: {memory}',
driver: 'Driver and device management tools',
nvidiaDriver: 'NVIDIA Driver',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Docker NVIDIA GPU configuration',
statusPassed: 'Normal',
statusMissing: 'Not detected',
statusUnconfigured: 'Configuration required',
statusFailed: 'Check failed',
statusUnknown: 'Unknown',
driverMissing:
'The {vendor} driver was not detected. Install or check the driver (detection tool: {tool}).',
driverFailed: 'Could not read device information using {tool}. Check the driver status.',
driverUnknown: 'Could not confirm the device driver status. Check {tool}.',
toolkitMissing:
'NVIDIA Container Toolkit was not detected. Install and configure it before using NVIDIA GPUs in containers.',
toolkitFailed: 'Could not check NVIDIA Container Toolkit. Review the failure details.',
dockerConfigMissing:
'No runtimes.nvidia entry was detected in the default Docker configuration file. Complete the configuration using the documentation.',
dockerConfigInvalid:
'The NVIDIA runtime configuration in Docker is invalid. Check runtimes.nvidia.path in daemon.json.',
dockerConfigPending:
'The default configuration file contains NVIDIA runtime configuration, but the current Docker daemon has not loaded it. Confirm the configuration file, restart Docker, and check again.',
dockerConfigUnreadable: 'Could not read the Docker configuration file. Review the check details.',
dockerUnconfigured: 'Docker NVIDIA GPU configuration is incomplete.',
dockerFailed: 'Could not check the Docker NVIDIA GPU configuration. Review the failure details.',
},
systemMemory: 'Shared system memory', systemMemory: 'Shared system memory',
systemMemoryUsed: 'System memory used', systemMemoryUsed: 'System memory used',
systemMemoryTotal: 'Total system memory', systemMemoryTotal: 'Total system memory',
@@ -1823,6 +1886,8 @@ const message = {
pcieWidth: 'PCIe width (current / maximum)', pcieWidth: 'PCIe width (current / maximum)',
gpu: 'GPU Management', gpu: 'GPU Management',
monitoringTitle: 'GPU Monitoring', monitoringTitle: 'GPU Monitoring',
currentHelper:
'View real-time status and resource usage of GPU/NPU/XPU devices on the current node. The overview summarizes all devices; detailed metrics are shown per device.',
gpuHelper: 'No supported GPU/XPU/NPU management command was detected. Please check and try again!', gpuHelper: 'No supported GPU/XPU/NPU management command was detected. Please check and try again!',
process: 'Process Information', process: 'Process Information',
processCount: 'Process Count', processCount: 'Process Count',
+72 -2
View File
@@ -1754,8 +1754,8 @@ const message = {
running: 'Solicitudes en ejecución', running: 'Solicitudes en ejecución',
waiting: 'Solicitudes en espera', waiting: 'Solicitudes en espera',
cacheUsage: 'Uso de caché KV', cacheUsage: 'Uso de caché KV',
promptThroughput: 'Rendimiento de tokens de entrada', promptThroughput: 'Rendimiento de entrada',
generationThroughput: 'Rendimiento de tokens de salida', generationThroughput: 'Rendimiento de salida',
requestThroughput: 'Solicitudes completadas', requestThroughput: 'Solicitudes completadas',
timeToFirstToken: 'Latencia media del primer token', timeToFirstToken: 'Latencia media del primer token',
timePerOutputToken: 'Latencia media entre tokens', timePerOutputToken: 'Latencia media entre tokens',
@@ -1776,6 +1776,74 @@ const message = {
timePerOutputTokenChart: 'Latencia entre tokens', timePerOutputTokenChart: 'Latencia entre tokens',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'GPU del host',
containerGPU: 'Compatibilidad GPU en contenedores',
monitorAvailable: 'Supervisión disponible',
monitorPartial: 'Supervisión parcial',
notReady: 'No está listo',
ready: 'Listo',
checkedAt: 'Última comprobación',
checkDetailsHelp: 'Revisa los detalles, verifica el entorno y vuelve a comprobarlo.',
driverActionTitle: 'Comprueba primero el controlador GPU del host',
driverActionHelp:
'Instala o repara el controlador del dispositivo afectado antes de continuar con las comprobaciones del entorno de contenedores.',
driverGuide: 'Ver guía de instalación del controlador',
toolkitActionTitle: 'La compatibilidad GPU en contenedores no está lista',
toolkitActionHelp:
'Instala o repara NVIDIA Container Toolkit y después comprueba la configuración de Docker.',
toolkitGuide: 'Ver guía de instalación de Toolkit',
dockerActionTitle: 'La compatibilidad NVIDIA GPU de Docker no está lista',
dockerActionHelp:
'Comprueba la configuración del runtime NVIDIA en Docker y vuelve a realizar la comprobación.',
monitorUnaffected: 'Este problema no afecta a la supervisión de la GPU del host.',
title: 'Comprobaciones del entorno',
passed: 'Comprobaciones del entorno superadas',
issues: 'Problemas del entorno detectados: {count}. Siga las indicaciones para revisarlos.',
recheck: 'Comprobar de nuevo',
installGuide: 'Ver guía de instalación',
goContainerSettings: 'Abrir configuración de contenedores',
details: 'Detalles de la comprobación',
vendor: 'Fabricante',
gpuCount: 'Cantidad de GPU',
cardCount: 'Número de tarjetas',
notDetected: 'No se detectaron dispositivos GPU/NPU/XPU compatibles en el nodo actual.',
unknown: 'No se pudo confirmar el estado del dispositivo. Revise los detalles y vuelva a comprobarlo.',
loadFailed: 'No se pudo leer la información del dispositivo. Inténtelo de nuevo.',
notApplicable: 'No aplicable',
cudaHelper:
'Esta es la versión de CUDA compatible con el controlador, no la versión de CUDA Toolkit instalada en el host.',
partialMemory: 'Algunos datos de memoria no están disponibles',
sharedMemory: 'Memoria del sistema compartida: {memory}',
driver: 'Controlador y herramientas de gestión de dispositivos',
nvidiaDriver: 'Controlador NVIDIA',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Configuración de GPU NVIDIA en Docker',
statusPassed: 'Normal',
statusMissing: 'No detectado',
statusUnconfigured: 'Configuración pendiente',
statusFailed: 'Error de comprobación',
statusUnknown: 'Desconocido',
driverMissing:
'No se detectó el controlador de {vendor}. Instale o compruebe el controlador (herramienta de detección: {tool}).',
driverFailed:
'No se pudo leer la información del dispositivo mediante {tool}. Compruebe el estado del controlador.',
driverUnknown: 'No se pudo confirmar el estado del controlador del dispositivo. Compruebe {tool}.',
toolkitMissing:
'No se detectó NVIDIA Container Toolkit. Instálelo y configúrelo antes de usar GPU NVIDIA en contenedores.',
toolkitFailed: 'No se pudo comprobar NVIDIA Container Toolkit. Revise los detalles del error.',
dockerConfigMissing:
'No se detectó la entrada runtimes.nvidia en el archivo de configuración predeterminado de Docker. Complete la configuración siguiendo la documentación.',
dockerConfigInvalid:
'La configuración del entorno de ejecución de NVIDIA en Docker no es válida. Compruebe runtimes.nvidia.path en daemon.json.',
dockerConfigPending:
'El archivo de configuración predeterminado contiene la configuración del entorno de ejecución de NVIDIA, pero el servicio Docker actual aún no la ha cargado. Confirme el archivo de configuración, reinicie Docker y vuelva a comprobarlo.',
dockerConfigUnreadable:
'No se pudo leer el archivo de configuración de Docker. Revise los detalles de la comprobación.',
dockerUnconfigured: 'La configuración de GPU NVIDIA en Docker está incompleta.',
dockerFailed:
'No se pudo comprobar la configuración de GPU NVIDIA en Docker. Revise los detalles del error.',
},
systemMemory: 'Memoria compartida del sistema', systemMemory: 'Memoria compartida del sistema',
systemMemoryUsed: 'Memoria del sistema utilizada', systemMemoryUsed: 'Memoria del sistema utilizada',
systemMemoryTotal: 'Memoria total del sistema', systemMemoryTotal: 'Memoria total del sistema',
@@ -1836,6 +1904,8 @@ const message = {
pcieWidth: 'Ancho PCIe (actual / máximo)', pcieWidth: 'Ancho PCIe (actual / máximo)',
gpu: 'Gestión de GPU', gpu: 'Gestión de GPU',
monitoringTitle: 'Monitoreo de GPU', monitoringTitle: 'Monitoreo de GPU',
currentHelper:
'Consulte el estado en tiempo real y el uso de recursos de los dispositivos GPU/NPU/XPU del nodo actual. El resumen superior incluye todos los dispositivos; las métricas detalladas se muestran por dispositivo.',
gpuHelper: gpuHelper:
'No se detectó ningún comando de administración de GPU/XPU/NPU compatible. ¡Compruebe e inténtelo de nuevo!', 'No se detectó ningún comando de administración de GPU/XPU/NPU compatible. ¡Compruebe e inténtelo de nuevo!',
process: 'Información del Proceso', process: 'Información del Proceso',
+65 -2
View File
@@ -1719,8 +1719,8 @@ const message = {
running: 'درخواست‌های در حال اجرا', running: 'درخواست‌های در حال اجرا',
waiting: 'درخواست‌های در انتظار', waiting: 'درخواست‌های در انتظار',
cacheUsage: 'مصرف حافظه نهان KV', cacheUsage: 'مصرف حافظه نهان KV',
promptThroughput: 'گذردهی توکن ورودی', promptThroughput: 'گذردهی ورودی',
generationThroughput: 'گذردهی توکن خروجی', generationThroughput: 'گذردهی خروجی',
requestThroughput: 'نرخ درخواست‌های تکمیل‌شده', requestThroughput: 'نرخ درخواست‌های تکمیل‌شده',
timeToFirstToken: 'میانگین تأخیر توکن اول', timeToFirstToken: 'میانگین تأخیر توکن اول',
timePerOutputToken: 'میانگین تأخیر بین توکن‌ها', timePerOutputToken: 'میانگین تأخیر بین توکن‌ها',
@@ -1740,6 +1740,67 @@ const message = {
timePerOutputTokenChart: 'تأخیر بین توکن‌ها', timePerOutputTokenChart: 'تأخیر بین توکن‌ها',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'GPU میزبان',
containerGPU: 'پشتیبانی GPU در کانتینر',
monitorAvailable: 'پایش در دسترس است',
monitorPartial: 'پایش جزئی',
notReady: 'آماده نیست',
ready: 'آماده',
checkedAt: 'زمان بررسی',
checkDetailsHelp: 'جزئیات بررسی را ببینید، وضعیت محیط را تأیید کنید و دوباره بررسی کنید.',
driverActionTitle: 'ابتدا درایور GPU میزبان را بررسی کنید',
driverActionHelp: 'پیش از ادامه بررسی محیط کانتینر، درایور دستگاه دارای مشکل را نصب یا تعمیر کنید.',
driverGuide: 'مشاهده راهنمای نصب درایور',
toolkitActionTitle: 'پشتیبانی GPU در کانتینر آماده نیست',
toolkitActionHelp:
'ابتدا NVIDIA Container Toolkit را نصب یا تعمیر کنید، سپس پیکربندی Docker را بررسی کنید.',
toolkitGuide: 'مشاهده راهنمای نصب Toolkit',
dockerActionTitle: 'پشتیبانی NVIDIA GPU در Docker آماده نیست',
dockerActionHelp: 'پیکربندی محیط اجرای NVIDIA در Docker را بررسی کنید و سپس دوباره بررسی کنید.',
monitorUnaffected: 'این مشکل بر پایش GPU میزبان تأثیری ندارد.',
title: 'بررسی محیط',
passed: 'بررسی‌های محیط با موفقیت انجام شد',
issues: '{count} مشکل در محیط شناسایی شد. برای بررسی آن‌ها راهنما را دنبال کنید.',
recheck: 'بررسی دوباره',
installGuide: 'مشاهده راهنمای نصب',
goContainerSettings: 'باز کردن تنظیمات کانتینر',
details: 'جزئیات بررسی',
vendor: 'سازنده',
gpuCount: 'تعداد GPU',
cardCount: 'تعداد کارت‌ها',
notDetected: 'هیچ دستگاه GPU/NPU/XPU پشتیبانی‌شده‌ای در گره فعلی شناسایی نشد.',
unknown: 'وضعیت دستگاه قابل تأیید نیست. جزئیات بررسی را ببینید و دوباره تلاش کنید.',
loadFailed: 'خواندن اطلاعات دستگاه ممکن نیست. دوباره تلاش کنید.',
notApplicable: 'قابل اعمال نیست',
cudaHelper: 'این نسخه CUDA پشتیبانی‌شده توسط درایور است، نه نسخه CUDA Toolkit نصب‌شده روی میزبان.',
partialMemory: 'بخشی از داده‌های حافظه در دسترس نیست',
sharedMemory: 'حافظه مشترک سیستم: {memory}',
driver: 'درایور و ابزارهای مدیریت دستگاه',
nvidiaDriver: 'درایور NVIDIA',
toolkit: 'NVIDIA Container Toolkit',
docker: 'پیکربندی GPU انویدیا در Docker',
statusPassed: 'عادی',
statusMissing: 'شناسایی نشد',
statusUnconfigured: 'نیازمند پیکربندی',
statusFailed: 'بررسی ناموفق',
statusUnknown: 'نامشخص',
driverMissing: 'درایور {vendor} شناسایی نشد. درایور را نصب یا بررسی کنید (ابزار شناسایی: {tool}).',
driverFailed: 'خواندن اطلاعات دستگاه با {tool} ممکن نیست. وضعیت درایور را بررسی کنید.',
driverUnknown: 'وضعیت درایور دستگاه قابل تأیید نیست. {tool} را بررسی کنید.',
toolkitMissing:
'NVIDIA Container Toolkit شناسایی نشد. پیش از استفاده از GPU انویدیا در کانتینرها، نصب و پیکربندی آن را کامل کنید.',
toolkitFailed: 'بررسی NVIDIA Container Toolkit ممکن نیست. جزئیات خطا را ببینید.',
dockerConfigMissing:
'گزینه runtimes.nvidia در فایل پیکربندی پیش‌فرض Docker شناسایی نشد. پیکربندی را طبق مستندات کامل کنید.',
dockerConfigInvalid:
'پیکربندی محیط اجرای NVIDIA در Docker نامعتبر است. مقدار runtimes.nvidia.path را در daemon.json بررسی کنید.',
dockerConfigPending:
'فایل پیکربندی پیش‌فرض شامل پیکربندی محیط اجرای NVIDIA است، اما سرویس فعلی Docker هنوز آن را بارگذاری نکرده است. فایل پیکربندی را تأیید کنید، Docker را دوباره راه‌اندازی کنید و سپس دوباره بررسی کنید.',
dockerConfigUnreadable: 'خواندن فایل پیکربندی Docker ممکن نیست. جزئیات بررسی را ببینید.',
dockerUnconfigured: 'پیکربندی GPU انویدیا در Docker کامل نشده است.',
dockerFailed: 'بررسی پیکربندی GPU انویدیا در Docker ممکن نیست. جزئیات خطا را ببینید.',
},
systemMemory: 'حافظهٔ مشترک سیستم', systemMemory: 'حافظهٔ مشترک سیستم',
systemMemoryUsed: 'حافظهٔ مصرف‌شدهٔ سیستم', systemMemoryUsed: 'حافظهٔ مصرف‌شدهٔ سیستم',
systemMemoryTotal: 'کل حافظهٔ سیستم', systemMemoryTotal: 'کل حافظهٔ سیستم',
@@ -1798,6 +1859,8 @@ const message = {
pcieWidth: 'عرض PCIe (فعلی / حداکثر)', pcieWidth: 'عرض PCIe (فعلی / حداکثر)',
gpu: 'مدیریت GPU', gpu: 'مدیریت GPU',
monitoringTitle: 'نظارت بر GPU', monitoringTitle: 'نظارت بر GPU',
currentHelper:
'وضعیت لحظه‌ای و میزان استفاده از منابع دستگاه‌های GPU/NPU/XPU در گره فعلی را مشاهده کنید. نمای کلی در بالا، خلاصه همه دستگاه‌ها را نشان می‌دهد؛ شاخص‌های دقیق برای هر دستگاه جداگانه نمایش داده می‌شوند.',
gpuHelper: 'هیچ دستور مدیریت GPU/XPU/NPU پشتیبانی‌شده‌ای شناسایی نشد. لطفاً بررسی و دوباره تلاش کنید!', gpuHelper: 'هیچ دستور مدیریت GPU/XPU/NPU پشتیبانی‌شده‌ای شناسایی نشد. لطفاً بررسی و دوباره تلاش کنید!',
process: 'اطلاعات فرآیند', process: 'اطلاعات فرآیند',
processCount: 'تعداد فرآیندها', processCount: 'تعداد فرآیندها',
+68 -2
View File
@@ -1726,8 +1726,8 @@ const message = {
running: '実行中のリクエスト', running: '実行中のリクエスト',
waiting: '待機中のリクエスト', waiting: '待機中のリクエスト',
cacheUsage: 'KVキャッシュ使用率', cacheUsage: 'KVキャッシュ使用率',
promptThroughput: '入力トークンスループット', promptThroughput: '入力スループット',
generationThroughput: '出力トークンスループット', generationThroughput: '出力スループット',
requestThroughput: '完了リクエスト数/秒', requestThroughput: '完了リクエスト数/秒',
timeToFirstToken: '平均初回トークン遅延', timeToFirstToken: '平均初回トークン遅延',
timePerOutputToken: '平均トークン間遅延', timePerOutputToken: '平均トークン間遅延',
@@ -1747,6 +1747,70 @@ const message = {
timePerOutputTokenChart: 'トークン間遅延', timePerOutputTokenChart: 'トークン間遅延',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'ホスト GPU',
containerGPU: 'コンテナーの GPU 対応',
monitorAvailable: '監視可能',
monitorPartial: '一部監視可能',
notReady: '未準備',
ready: '準備完了',
checkedAt: '確認日時',
checkDetailsHelp: '確認の詳細を参照し、環境の状態を確認してから再チェックしてください。',
driverActionTitle: 'まずホスト GPU ドライバーを確認してください',
driverActionHelp:
'問題のあるデバイスのドライバーをインストールまたは修復してから、コンテナー環境の確認を続けてください。',
driverGuide: 'ドライバーのインストール手順',
toolkitActionTitle: 'コンテナーの GPU 環境が未準備です',
toolkitActionHelp:
'NVIDIA Container Toolkit をインストールまたは修復してから、Docker の設定を確認してください。',
toolkitGuide: 'Toolkit のインストール手順',
dockerActionTitle: 'Docker の NVIDIA GPU 環境が未準備です',
dockerActionHelp: 'Docker の NVIDIA ランタイム設定を確認してから、再チェックしてください。',
monitorUnaffected: 'この問題はホスト GPU の監視に影響しません。',
title: '環境チェック',
passed: '環境チェックに合格しました',
issues: '環境の問題が {count} 件見つかりました。案内に従って確認してください。',
recheck: '再チェック',
installGuide: 'インストールガイドを見る',
goContainerSettings: 'コンテナー設定を開く',
details: 'チェックの詳細',
vendor: 'メーカー',
gpuCount: 'GPU 数',
cardCount: 'カード数',
notDetected: '現在のノードで対応する GPU/NPU/XPU デバイスが検出されませんでした。',
unknown: 'デバイスの状態を確認できません。チェックの詳細を確認してから再実行してください。',
loadFailed: 'デバイス情報を読み取れません。再試行してください。',
notApplicable: '対象外',
cudaHelper:
'これはドライバーが対応する CUDA バージョンであり、ホストにインストールされた CUDA Toolkit のバージョンではありません。',
partialMemory: '一部のメモリデータを取得できません',
sharedMemory: '共有システムメモリ:{memory}',
driver: 'ドライバーとデバイス管理ツール',
nvidiaDriver: 'NVIDIA ドライバー',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Docker の NVIDIA GPU 設定',
statusPassed: '正常',
statusMissing: '未検出',
statusUnconfigured: '設定が必要',
statusFailed: 'チェック失敗',
statusUnknown: '不明',
driverMissing:
'{vendor} ドライバーが検出されませんでした。ドライバーをインストールするか、状態を確認してください(検出ツール:{tool})。',
driverFailed: '{tool} でデバイス情報を読み取れません。ドライバーの状態を確認してください。',
driverUnknown: 'デバイスのドライバーの状態を確認できません。{tool} を確認してください。',
toolkitMissing:
'NVIDIA Container Toolkit が検出されませんでした。コンテナーで NVIDIA GPU を使用する前にインストールと設定を完了してください。',
toolkitFailed: 'NVIDIA Container Toolkit をチェックできません。失敗の詳細を確認してください。',
dockerConfigMissing:
'Docker の既定の設定ファイルに runtimes.nvidia が検出されませんでした。ドキュメントに従って設定を完了してください。',
dockerConfigInvalid:
'Docker の NVIDIA ランタイム設定が正しくありません。daemon.json の runtimes.nvidia.path を確認してください。',
dockerConfigPending:
'既定の設定ファイルに NVIDIA ランタイム設定がありますが、現在の Docker にはまだ読み込まれていません。設定ファイルを確認し、Docker を再起動してから再チェックしてください。',
dockerConfigUnreadable: 'Docker の設定ファイルを読み取れません。チェックの詳細を確認してください。',
dockerUnconfigured: 'Docker の NVIDIA GPU 設定が完了していません。',
dockerFailed: 'Docker の NVIDIA GPU 設定をチェックできません。失敗の詳細を確認してください。',
},
systemMemory: '共有システムメモリ', systemMemory: '共有システムメモリ',
systemMemoryUsed: 'システムメモリ使用量', systemMemoryUsed: 'システムメモリ使用量',
systemMemoryTotal: 'システムメモリ合計', systemMemoryTotal: 'システムメモリ合計',
@@ -1804,6 +1868,8 @@ const message = {
pcieWidth: 'PCIe 幅(現在 / 最大)', pcieWidth: 'PCIe 幅(現在 / 最大)',
gpu: 'GPU 管理', gpu: 'GPU 管理',
monitoringTitle: 'GPU 監視', monitoringTitle: 'GPU 監視',
currentHelper:
'現在のノードにある GPU/NPU/XPU デバイスのリアルタイムの稼働状態とリソース使用状況を確認できます。上部には全デバイスの概要を表示し、詳細な指標はデバイスごとに表示します。',
gpuHelper: '対応する GPU/XPU/NPU 管理コマンドが検出されませんでした。確認して再試行してください!', gpuHelper: '対応する GPU/XPU/NPU 管理コマンドが検出されませんでした。確認して再試行してください!',
process: 'プロセス情報', process: 'プロセス情報',
type: 'タイプ', type: 'タイプ',
+65 -2
View File
@@ -1707,8 +1707,8 @@ const message = {
running: '실행 중인 요청', running: '실행 중인 요청',
waiting: '대기 중인 요청', waiting: '대기 중인 요청',
cacheUsage: 'KV 캐시 사용률', cacheUsage: 'KV 캐시 사용률',
promptThroughput: '입력 토큰 처리량', promptThroughput: '입력 처리량',
generationThroughput: '출력 토큰 처리량', generationThroughput: '출력 처리량',
requestThroughput: '완료 요청 속도', requestThroughput: '완료 요청 속도',
timeToFirstToken: '평균 첫 토큰 지연', timeToFirstToken: '평균 첫 토큰 지연',
timePerOutputToken: '평균 토큰 간 지연', timePerOutputToken: '평균 토큰 간 지연',
@@ -1728,6 +1728,67 @@ const message = {
timePerOutputTokenChart: '토큰 간 지연', timePerOutputTokenChart: '토큰 간 지연',
}, },
gpu: { gpu: {
environment: {
hostGPU: '호스트 GPU',
containerGPU: '컨테이너 GPU 지원',
monitorAvailable: '모니터링 가능',
monitorPartial: '일부 모니터링 가능',
notReady: '준비되지 않음',
ready: '준비됨',
checkedAt: '확인 시간',
checkDetailsHelp: '확인 세부 정보를 보고 환경 상태를 확인한 후 다시 검사하세요.',
driverActionTitle: '먼저 호스트 GPU 드라이버를 확인하세요',
driverActionHelp: '문제가 있는 장치의 드라이버를 설치하거나 복구한 후 컨테이너 환경 검사를 계속하세요.',
driverGuide: '드라이버 설치 안내 보기',
toolkitActionTitle: '컨테이너 GPU 지원이 준비되지 않았습니다',
toolkitActionHelp: 'NVIDIA Container Toolkit을 설치하거나 복구한 후 Docker 구성을 확인하세요.',
toolkitGuide: 'Toolkit 설치 안내 보기',
dockerActionTitle: 'Docker의 NVIDIA GPU 지원이 준비되지 않았습니다',
dockerActionHelp: 'Docker의 NVIDIA 런타임 구성을 확인한 후 다시 검사하세요.',
monitorUnaffected: '이 문제는 호스트 GPU 모니터링에 영향을 주지 않습니다.',
title: '환경 점검',
passed: '환경 점검을 통과했습니다',
issues: '환경 문제 {count}개가 발견되었습니다. 안내에 따라 확인하세요.',
recheck: '다시 점검',
installGuide: '설치 가이드 보기',
goContainerSettings: '컨테이너 설정 열기',
details: '점검 세부 정보',
vendor: '제조사',
gpuCount: 'GPU 수',
cardCount: '카드 수',
notDetected: '현재 노드에서 지원되는 GPU/NPU/XPU 장치가 감지되지 않았습니다.',
unknown: '장치 상태를 확인할 수 없습니다. 점검 세부 정보를 확인한 후 다시 시도하세요.',
loadFailed: '장치 정보를 읽을 수 없습니다. 다시 시도하세요.',
notApplicable: '해당 없음',
cudaHelper: '이 버전은 드라이버가 지원하는 CUDA 버전이며 호스트에 설치된 CUDA Toolkit 버전이 아닙니다.',
partialMemory: '일부 메모리 데이터를 가져오지 못했습니다',
sharedMemory: '공유 시스템 메모리: {memory}',
driver: '드라이버 및 장치 관리 도구',
nvidiaDriver: 'NVIDIA 드라이버',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Docker NVIDIA GPU 구성',
statusPassed: '정상',
statusMissing: '감지되지 않음',
statusUnconfigured: '구성 필요',
statusFailed: '점검 실패',
statusUnknown: '알 수 없음',
driverMissing:
'{vendor} 드라이버가 감지되지 않았습니다. 드라이버를 설치하거나 확인하세요(감지 도구: {tool}).',
driverFailed: '{tool}를 통해 장치 정보를 읽을 수 없습니다. 드라이버 상태를 확인하세요.',
driverUnknown: '장치 드라이버 상태를 확인할 수 없습니다. {tool}를 확인하세요.',
toolkitMissing:
'NVIDIA Container Toolkit이 감지되지 않았습니다. 컨테이너에서 NVIDIA GPU를 사용하기 전에 설치와 구성을 완료하세요.',
toolkitFailed: 'NVIDIA Container Toolkit을 점검할 수 없습니다. 실패 세부 정보를 확인하세요.',
dockerConfigMissing:
'Docker 기본 구성 파일에서 runtimes.nvidia 항목이 감지되지 않았습니다. 문서에 따라 구성을 완료하세요.',
dockerConfigInvalid:
'Docker의 NVIDIA 런타임 구성이 올바르지 않습니다. daemon.json의 runtimes.nvidia.path를 확인하세요.',
dockerConfigPending:
'기본 구성 파일에 NVIDIA 런타임 구성이 있지만 현재 Docker에서 아직 로드하지 않았습니다. 구성 파일을 확인하고 Docker를 다시 시작한 후 다시 점검하세요.',
dockerConfigUnreadable: 'Docker 구성 파일을 읽을 수 없습니다. 점검 세부 정보를 확인하세요.',
dockerUnconfigured: 'Docker NVIDIA GPU 구성이 완료되지 않았습니다.',
dockerFailed: 'Docker NVIDIA GPU 구성을 점검할 수 없습니다. 실패 세부 정보를 확인하세요.',
},
systemMemory: '공유 시스템 메모리', systemMemory: '공유 시스템 메모리',
systemMemoryUsed: '사용 중인 시스템 메모리', systemMemoryUsed: '사용 중인 시스템 메모리',
systemMemoryTotal: '전체 시스템 메모리', systemMemoryTotal: '전체 시스템 메모리',
@@ -1785,6 +1846,8 @@ const message = {
pcieWidth: 'PCIe 너비 (현재 / 최대)', pcieWidth: 'PCIe 너비 (현재 / 최대)',
gpu: 'GPU 관리', gpu: 'GPU 관리',
monitoringTitle: 'GPU 모니터링', monitoringTitle: 'GPU 모니터링',
currentHelper:
'현재 노드의 GPU/NPU/XPU 장치에 대한 실시간 상태와 리소스 사용량을 확인합니다. 상단에는 모든 장치의 요약이 표시되며, 상세 지표는 장치별로 표시됩니다.',
gpuHelper: '지원되는 GPU/XPU/NPU 관리 명령을 감지하지 못했습니다. 확인하고 다시 시도하세요!', gpuHelper: '지원되는 GPU/XPU/NPU 관리 명령을 감지하지 못했습니다. 확인하고 다시 시도하세요!',
process: '프로세스 정보', process: '프로세스 정보',
type: '유형', type: '유형',
+64 -2
View File
@@ -1696,8 +1696,8 @@ const message = {
running: 'ຄຳຮ້ອງຂໍທີ່ກຳລັງເຮັດວຽກ', running: 'ຄຳຮ້ອງຂໍທີ່ກຳລັງເຮັດວຽກ',
waiting: 'ຄຳຮ້ອງຂໍທີ່ກຳລັງລໍຖ້າ', waiting: 'ຄຳຮ້ອງຂໍທີ່ກຳລັງລໍຖ້າ',
cacheUsage: 'ການໃຊ້ງານແຄດ KV', cacheUsage: 'ການໃຊ້ງານແຄດ KV',
promptThroughput: 'ອັດຕາ Token ຂາເຂົ້າ', promptThroughput: 'ອັດຕາຂາເຂົ້າ',
generationThroughput: 'ອັດຕາ Token ຂາອອກ', generationThroughput: 'ອັດຕາຂາອອກ',
requestThroughput: 'ອັດຕາຄຳຮ້ອງຂໍສຳເລັດ', requestThroughput: 'ອັດຕາຄຳຮ້ອງຂໍສຳເລັດ',
timeToFirstToken: 'ເວລາລໍຖ້າສະເລ່ຍຂອງ Token ທຳອິດ', timeToFirstToken: 'ເວລາລໍຖ້າສະເລ່ຍຂອງ Token ທຳອິດ',
timePerOutputToken: 'ເວລາລໍຖ້າສະເລ່ຍລະຫວ່າງ Token', timePerOutputToken: 'ເວລາລໍຖ້າສະເລ່ຍລະຫວ່າງ Token',
@@ -1717,6 +1717,66 @@ const message = {
timePerOutputTokenChart: 'ເວລາລໍຖ້າລະຫວ່າງ Token', timePerOutputTokenChart: 'ເວລາລໍຖ້າລະຫວ່າງ Token',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'GPU ຂອງໂຮສ',
containerGPU: 'ການຮອງຮັບ GPU ໃນຄອນເທນເນີ',
monitorAvailable: 'ສາມາດຕິດຕາມໄດ້',
monitorPartial: 'ຕິດຕາມໄດ້ບາງສ່ວນ',
notReady: 'ຍັງບໍ່ພ້ອມ',
ready: 'ພ້ອມແລ້ວ',
checkedAt: 'ເວລາກວດສອບ',
checkDetailsHelp: 'ເບິ່ງລາຍລະອຽດການກວດສອບ, ຢືນຢັນສະຖານະສະພາບແວດລ້ອມ ແລ້ວກວດສອບອີກຄັ້ງ.',
driverActionTitle: 'ກວດສອບໄດຣເວີ GPU ຂອງໂຮສກ່ອນ',
driverActionHelp: 'ຕິດຕັ້ງ ຫຼື ສ້ອມແປງໄດຣເວີຂອງອຸປະກອນທີ່ມີບັນຫາ ກ່ອນກວດສອບສະພາບແວດລ້ອມຄອນເທນເນີຕໍ່.',
driverGuide: 'ເບິ່ງຄູ່ມືຕິດຕັ້ງໄດຣເວີ',
toolkitActionTitle: 'ການຮອງຮັບ GPU ໃນຄອນເທນເນີຍັງບໍ່ພ້ອມ',
toolkitActionHelp: 'ຕິດຕັ້ງ ຫຼື ສ້ອມແປງ NVIDIA Container Toolkit ກ່ອນ ແລ້ວກວດສອບການຕັ້ງຄ່າ Docker.',
toolkitGuide: 'ເບິ່ງຄູ່ມືຕິດຕັ້ງ Toolkit',
dockerActionTitle: 'ການຮອງຮັບ NVIDIA GPU ຂອງ Docker ຍັງບໍ່ພ້ອມ',
dockerActionHelp: 'ກວດສອບການຕັ້ງຄ່າ NVIDIA runtime ໃນ Docker ແລ້ວກວດສອບອີກຄັ້ງ.',
monitorUnaffected: 'ບັນຫານີ້ບໍ່ກະທົບຕໍ່ການຕິດຕາມ GPU ຂອງໂຮສ.',
title: 'ກວດສອບສະພາບແວດລ້ອມ',
passed: 'ຜ່ານການກວດສອບສະພາບແວດລ້ອມແລ້ວ',
issues: 'ພົບບັນຫາສະພາບແວດລ້ອມ {count} ລາຍການ. ກະລຸນາກວດສອບຕາມຄຳແນະນຳ.',
recheck: 'ກວດສອບອີກຄັ້ງ',
installGuide: 'ເບິ່ງຄູ່ມືການຕິດຕັ້ງ',
goContainerSettings: 'ເປີດການຕັ້ງຄ່າຄອນເທນເນີ',
details: 'ລາຍລະອຽດການກວດສອບ',
vendor: 'ຜູ້ຜະລິດ',
gpuCount: 'ຈຳນວນ GPU',
cardCount: 'ຈຳນວນບັດ',
notDetected: 'ບໍ່ພົບອຸປະກອນ GPU/NPU/XPU ທີ່ຮອງຮັບໃນໂນດປັດຈຸບັນ.',
unknown: 'ບໍ່ສາມາດຢືນຢັນສະຖານະອຸປະກອນໄດ້. ກະລຸນາເບິ່ງລາຍລະອຽດແລ້ວກວດສອບອີກຄັ້ງ.',
loadFailed: 'ບໍ່ສາມາດອ່ານຂໍ້ມູນອຸປະກອນໄດ້. ກະລຸນາລອງໃໝ່.',
notApplicable: 'ບໍ່ກ່ຽວຂ້ອງ',
cudaHelper: 'ນີ້ແມ່ນເວີຊັນ CUDA ທີ່ໄດຣເວີຮອງຮັບ ບໍ່ແມ່ນເວີຊັນ CUDA Toolkit ທີ່ຕິດຕັ້ງໃນໂຮສ.',
partialMemory: 'ບໍ່ສາມາດດຶງຂໍ້ມູນຄວາມຈຳບາງສ່ວນໄດ້',
sharedMemory: 'ຄວາມຈຳລະບົບຮ່ວມກັນ: {memory}',
driver: 'ໄດຣເວີ ແລະ ເຄື່ອງມືຈັດການອຸປະກອນ',
nvidiaDriver: 'ໄດຣເວີ NVIDIA',
toolkit: 'NVIDIA Container Toolkit',
docker: 'ການຕັ້ງຄ່າ NVIDIA GPU ໃນ Docker',
statusPassed: 'ປົກກະຕິ',
statusMissing: 'ບໍ່ພົບ',
statusUnconfigured: 'ຕ້ອງຕັ້ງຄ່າ',
statusFailed: 'ການກວດສອບລົ້ມເຫຼວ',
statusUnknown: 'ບໍ່ຮູ້',
driverMissing: 'ບໍ່ພົບໄດຣເວີ {vendor}. ກະລຸນາຕິດຕັ້ງ ຫຼື ກວດສອບໄດຣເວີ (ເຄື່ອງມືກວດສອບ: {tool}).',
driverFailed: 'ບໍ່ສາມາດອ່ານຂໍ້ມູນອຸປະກອນຜ່ານ {tool} ໄດ້. ກະລຸນາກວດສອບສະຖານະໄດຣເວີ.',
driverUnknown: 'ບໍ່ສາມາດຢືນຢັນສະຖານະໄດຣເວີອຸປະກອນໄດ້. ກະລຸນາກວດສອບ {tool}.',
toolkitMissing:
'ບໍ່ພົບ NVIDIA Container Toolkit. ກະລຸນາຕິດຕັ້ງ ແລະ ຕັ້ງຄ່າກ່ອນໃຊ້ NVIDIA GPU ໃນຄອນເທນເນີ.',
toolkitFailed: 'ບໍ່ສາມາດກວດສອບ NVIDIA Container Toolkit ໄດ້. ກະລຸນາເບິ່ງລາຍລະອຽດຂໍ້ຜິດພາດ.',
dockerConfigMissing:
'ບໍ່ພົບ runtimes.nvidia ໃນໄຟລ໌ການຕັ້ງຄ່າເລີ່ມຕົ້ນຂອງ Docker. ກະລຸນາຕັ້ງຄ່າໃຫ້ສຳເລັດຕາມເອກະສານ.',
dockerConfigInvalid:
'ການຕັ້ງຄ່າ NVIDIA runtime ໃນ Docker ບໍ່ຖືກຕ້ອງ. ກະລຸນາກວດສອບ runtimes.nvidia.path ໃນ daemon.json.',
dockerConfigPending:
'ໄຟລ໌ການຕັ້ງຄ່າເລີ່ມຕົ້ນມີການຕັ້ງຄ່າ NVIDIA runtime ແລ້ວ ແຕ່ບໍລິການ Docker ປັດຈຸບັນຍັງບໍ່ໄດ້ໂຫຼດ. ກະລຸນາຢືນຢັນໄຟລ໌ການຕັ້ງຄ່າ, ເລີ່ມ Docker ໃໝ່ ແລ້ວກວດສອບອີກຄັ້ງ.',
dockerConfigUnreadable: 'ບໍ່ສາມາດອ່ານໄຟລ໌ການຕັ້ງຄ່າ Docker ໄດ້. ກະລຸນາເບິ່ງລາຍລະອຽດການກວດສອບ.',
dockerUnconfigured: 'ການຕັ້ງຄ່າ NVIDIA GPU ໃນ Docker ຍັງບໍ່ສຳເລັດ.',
dockerFailed: 'ບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າ NVIDIA GPU ໃນ Docker ໄດ້. ກະລຸນາເບິ່ງລາຍລະອຽດຂໍ້ຜິດພາດ.',
},
systemMemory: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ຮ່ວມກັນ', systemMemory: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ຮ່ວມກັນ',
systemMemoryUsed: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ແລ້ວ', systemMemoryUsed: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ແລ້ວ',
systemMemoryTotal: 'ໜ່ວຍຄວາມຈຳລະບົບທັງໝົດ', systemMemoryTotal: 'ໜ່ວຍຄວາມຈຳລະບົບທັງໝົດ',
@@ -1774,6 +1834,8 @@ const message = {
pcieWidth: 'ຄວາມກວ້າງ PCIe (ປັດຈຸບັນ / ສູງສຸດ)', pcieWidth: 'ຄວາມກວ້າງ PCIe (ປັດຈຸບັນ / ສູງສຸດ)',
gpu: 'ການຈັດການ GPU', gpu: 'ການຈັດການ GPU',
monitoringTitle: 'ຕິດຕາມ GPU', monitoringTitle: 'ຕິດຕາມ GPU',
currentHelper:
'ເບິ່ງສະຖານະການເຮັດວຽກ ແລະ ການໃຊ້ຊັບພະຍາກອນແບບເວລາຈິງຂອງອຸປະກອນ GPU/NPU/XPU ໃນໂນດປັດຈຸບັນ. ພາບລວມດ້ານເທິງສະຫຼຸບຂໍ້ມູນຂອງທຸກອຸປະກອນ ແລະ ສະແດງຕົວຊີ້ວັດລະອຽດແຍກຕາມອຸປະກອນ.',
gpuHelper: 'ກວດບໍ່ພົບຄຳສັ່ງຈັດການ GPU/XPU/NPU ທີ່ຮອງຮັບ. ກະລຸນາກວດສອບ ແລະ ລອງໃໝ່!', gpuHelper: 'ກວດບໍ່ພົບຄຳສັ່ງຈັດການ GPU/XPU/NPU ທີ່ຮອງຮັບ. ກະລຸນາກວດສອບ ແລະ ລອງໃໝ່!',
process: 'ຂໍ້ມູນໂປຣເຊສ', process: 'ຂໍ້ມູນໂປຣເຊສ',
processCount: 'ຈຳນວນໂປຣເຊສ', processCount: 'ຈຳນວນໂປຣເຊສ',
+66 -2
View File
@@ -1757,8 +1757,8 @@ const message = {
running: 'Permintaan berjalan', running: 'Permintaan berjalan',
waiting: 'Permintaan menunggu', waiting: 'Permintaan menunggu',
cacheUsage: 'Penggunaan cache KV', cacheUsage: 'Penggunaan cache KV',
promptThroughput: 'Daya pemprosesan token input', promptThroughput: 'Daya pemprosesan input',
generationThroughput: 'Daya pemprosesan token output', generationThroughput: 'Daya pemprosesan output',
requestThroughput: 'Kadar permintaan selesai', requestThroughput: 'Kadar permintaan selesai',
timeToFirstToken: 'Purata kependaman token pertama', timeToFirstToken: 'Purata kependaman token pertama',
timePerOutputToken: 'Purata kependaman antara token', timePerOutputToken: 'Purata kependaman antara token',
@@ -1778,6 +1778,68 @@ const message = {
timePerOutputTokenChart: 'Kependaman antara token', timePerOutputTokenChart: 'Kependaman antara token',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'GPU hos',
containerGPU: 'Sokongan GPU kontena',
monitorAvailable: 'Pemantauan tersedia',
monitorPartial: 'Pemantauan separa',
notReady: 'Belum sedia',
ready: 'Sedia',
checkedAt: 'Masa pemeriksaan',
checkDetailsHelp: 'Semak butiran pemeriksaan, sahkan keadaan persekitaran dan periksa semula.',
driverActionTitle: 'Periksa pemacu GPU hos terlebih dahulu',
driverActionHelp:
'Pasang atau baiki pemacu peranti yang bermasalah sebelum meneruskan pemeriksaan persekitaran kontena.',
driverGuide: 'Lihat panduan pemasangan pemacu',
toolkitActionTitle: 'Sokongan GPU kontena belum sedia',
toolkitActionHelp: 'Pasang atau baiki NVIDIA Container Toolkit, kemudian periksa konfigurasi Docker.',
toolkitGuide: 'Lihat panduan pemasangan Toolkit',
dockerActionTitle: 'Sokongan NVIDIA GPU Docker belum sedia',
dockerActionHelp: 'Periksa konfigurasi runtime NVIDIA dalam Docker, kemudian periksa semula.',
monitorUnaffected: 'Isu ini tidak menjejaskan pemantauan GPU hos.',
title: 'Semakan persekitaran',
passed: 'Semakan persekitaran berjaya',
issues: 'Masalah persekitaran dikesan: {count}. Ikuti panduan untuk menyemaknya.',
recheck: 'Semak semula',
installGuide: 'Lihat panduan pemasangan',
goContainerSettings: 'Buka tetapan bekas',
details: 'Butiran semakan',
vendor: 'Pengeluar',
gpuCount: 'Bilangan GPU',
cardCount: 'Bilangan kad',
notDetected: 'Tiada peranti GPU/NPU/XPU yang disokong dikesan pada nod semasa.',
unknown: 'Status peranti tidak dapat dipastikan. Lihat butiran semakan dan cuba lagi.',
loadFailed: 'Maklumat peranti tidak dapat dibaca. Sila cuba lagi.',
notApplicable: 'Tidak berkenaan',
cudaHelper:
'Ini ialah versi CUDA yang disokong oleh pemacu, bukan versi CUDA Toolkit yang dipasang pada hos.',
partialMemory: 'Sebahagian data memori tidak tersedia',
sharedMemory: 'Memori sistem dikongsi: {memory}',
driver: 'Pemacu dan alat pengurusan peranti',
nvidiaDriver: 'Pemacu NVIDIA',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Konfigurasi NVIDIA GPU Docker',
statusPassed: 'Normal',
statusMissing: 'Tidak dikesan',
statusUnconfigured: 'Konfigurasi diperlukan',
statusFailed: 'Semakan gagal',
statusUnknown: 'Tidak diketahui',
driverMissing: 'Pemacu {vendor} tidak dikesan. Pasang atau semak pemacu (alat pengesanan: {tool}).',
driverFailed: 'Maklumat peranti tidak dapat dibaca menggunakan {tool}. Semak status pemacu.',
driverUnknown: 'Status pemacu peranti tidak dapat dipastikan. Semak {tool}.',
toolkitMissing:
'NVIDIA Container Toolkit tidak dikesan. Pasang dan konfigurasikannya sebelum menggunakan NVIDIA GPU dalam bekas.',
toolkitFailed: 'NVIDIA Container Toolkit tidak dapat disemak. Lihat butiran kegagalan.',
dockerConfigMissing:
'Entri runtimes.nvidia tidak dikesan dalam fail konfigurasi lalai Docker. Lengkapkan konfigurasi mengikut dokumentasi.',
dockerConfigInvalid:
'Konfigurasi masa jalan NVIDIA dalam Docker tidak sah. Semak runtimes.nvidia.path dalam daemon.json.',
dockerConfigPending:
'Fail konfigurasi lalai mengandungi konfigurasi masa jalan NVIDIA, tetapi perkhidmatan Docker semasa belum memuatkannya. Sahkan fail konfigurasi, mulakan semula Docker dan semak sekali lagi.',
dockerConfigUnreadable: 'Fail konfigurasi Docker tidak dapat dibaca. Lihat butiran semakan.',
dockerUnconfigured: 'Konfigurasi NVIDIA GPU Docker belum lengkap.',
dockerFailed: 'Konfigurasi NVIDIA GPU Docker tidak dapat disemak. Lihat butiran kegagalan.',
},
systemMemory: 'Memori sistem dikongsi', systemMemory: 'Memori sistem dikongsi',
systemMemoryUsed: 'Memori sistem digunakan', systemMemoryUsed: 'Memori sistem digunakan',
systemMemoryTotal: 'Jumlah memori sistem', systemMemoryTotal: 'Jumlah memori sistem',
@@ -1838,6 +1900,8 @@ const message = {
pcieWidth: 'Lebar PCIe (semasa / maksimum)', pcieWidth: 'Lebar PCIe (semasa / maksimum)',
gpu: 'Pengurusan GPU', gpu: 'Pengurusan GPU',
monitoringTitle: 'Pemantauan GPU', monitoringTitle: 'Pemantauan GPU',
currentHelper:
'Lihat status masa nyata dan penggunaan sumber peranti GPU/NPU/XPU pada nod semasa. Ringkasan di bahagian atas merangkumi semua peranti; metrik terperinci dipaparkan mengikut peranti.',
gpuHelper: 'Tiada arahan pengurusan GPU/XPU/NPU yang disokong dikesan. Sila periksa dan cuba lagi!', gpuHelper: 'Tiada arahan pengurusan GPU/XPU/NPU yang disokong dikesan. Sila periksa dan cuba lagi!',
process: 'Maklumat Proses', process: 'Maklumat Proses',
type: 'Jenis', type: 'Jenis',
+73 -2
View File
@@ -1754,8 +1754,8 @@ const message = {
running: 'Solicitações em execução', running: 'Solicitações em execução',
waiting: 'Solicitações em espera', waiting: 'Solicitações em espera',
cacheUsage: 'Uso do cache KV', cacheUsage: 'Uso do cache KV',
promptThroughput: 'Taxa de tokens de entrada', promptThroughput: 'Taxa de entrada',
generationThroughput: 'Taxa de tokens de saída', generationThroughput: 'Taxa de saída',
requestThroughput: 'Solicitações concluídas', requestThroughput: 'Solicitações concluídas',
timeToFirstToken: 'Latência média do primeiro token', timeToFirstToken: 'Latência média do primeiro token',
timePerOutputToken: 'Latência média entre tokens', timePerOutputToken: 'Latência média entre tokens',
@@ -1776,6 +1776,75 @@ const message = {
timePerOutputTokenChart: 'Latência entre tokens', timePerOutputTokenChart: 'Latência entre tokens',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'GPU do host',
containerGPU: 'Suporte a GPU em contêineres',
monitorAvailable: 'Monitoramento disponível',
monitorPartial: 'Monitoramento parcial',
notReady: 'Não está pronto',
ready: 'Pronto',
checkedAt: 'Verificado em',
checkDetailsHelp: 'Confira os detalhes, verifique o ambiente e execute a verificação novamente.',
driverActionTitle: 'Verifique primeiro o driver da GPU do host',
driverActionHelp:
'Instale ou repare o driver do dispositivo afetado antes de continuar a verificação do ambiente de contêineres.',
driverGuide: 'Ver guia de instalação do driver',
toolkitActionTitle: 'O suporte a GPU em contêineres não está pronto',
toolkitActionHelp:
'Instale ou repare o NVIDIA Container Toolkit e depois verifique a configuração do Docker.',
toolkitGuide: 'Ver guia de instalação do Toolkit',
dockerActionTitle: 'O suporte a NVIDIA GPU no Docker não está pronto',
dockerActionHelp:
'Verifique a configuração do runtime NVIDIA no Docker e execute a verificação novamente.',
monitorUnaffected: 'Este problema não afeta o monitoramento da GPU do host.',
title: 'Verificações do ambiente',
passed: 'Verificações do ambiente concluídas com sucesso',
issues: 'Problemas de ambiente detectados: {count}. Siga as orientações para verificá-los.',
recheck: 'Verificar novamente',
installGuide: 'Ver guia de instalação',
goContainerSettings: 'Abrir configurações de contêineres',
details: 'Detalhes da verificação',
vendor: 'Fabricante',
gpuCount: 'Quantidade de GPUs',
cardCount: 'Quantidade de placas',
notDetected: 'Nenhum dispositivo GPU/NPU/XPU compatível foi detectado no nó atual.',
unknown:
'Não foi possível confirmar o estado do dispositivo. Consulte os detalhes e verifique novamente.',
loadFailed: 'Não foi possível ler as informações do dispositivo. Tente novamente.',
notApplicable: 'Não se aplica',
cudaHelper:
'Esta é a versão do CUDA compatível com o driver, não a versão do CUDA Toolkit instalada no host.',
partialMemory: 'Alguns dados de memória estão indisponíveis',
sharedMemory: 'Memória compartilhada do sistema: {memory}',
driver: 'Driver e ferramentas de gerenciamento de dispositivos',
nvidiaDriver: 'Driver NVIDIA',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Configuração de GPU NVIDIA no Docker',
statusPassed: 'Normal',
statusMissing: 'Não detectado',
statusUnconfigured: 'Configuração pendente',
statusFailed: 'Falha na verificação',
statusUnknown: 'Desconhecido',
driverMissing:
'O driver {vendor} não foi detectado. Instale ou verifique o driver (ferramenta de detecção: {tool}).',
driverFailed:
'Não foi possível ler as informações do dispositivo usando {tool}. Verifique o estado do driver.',
driverUnknown: 'Não foi possível confirmar o estado do driver do dispositivo. Verifique {tool}.',
toolkitMissing:
'NVIDIA Container Toolkit não foi detectado. Instale e configure a ferramenta antes de usar GPUs NVIDIA em contêineres.',
toolkitFailed: 'Não foi possível verificar o NVIDIA Container Toolkit. Consulte os detalhes da falha.',
dockerConfigMissing:
'Nenhuma entrada runtimes.nvidia foi detectada no arquivo de configuração padrão do Docker. Conclua a configuração seguindo a documentação.',
dockerConfigInvalid:
'A configuração do ambiente de execução NVIDIA no Docker é inválida. Verifique runtimes.nvidia.path no daemon.json.',
dockerConfigPending:
'O arquivo de configuração padrão contém a configuração do ambiente de execução NVIDIA, mas o serviço Docker atual ainda não a carregou. Confirme o arquivo de configuração, reinicie o Docker e verifique novamente.',
dockerConfigUnreadable:
'Não foi possível ler o arquivo de configuração do Docker. Consulte os detalhes da verificação.',
dockerUnconfigured: 'A configuração de GPU NVIDIA no Docker está incompleta.',
dockerFailed:
'Não foi possível verificar a configuração de GPU NVIDIA no Docker. Consulte os detalhes da falha.',
},
systemMemory: 'Memória compartilhada do sistema', systemMemory: 'Memória compartilhada do sistema',
systemMemoryUsed: 'Memória do sistema em uso', systemMemoryUsed: 'Memória do sistema em uso',
systemMemoryTotal: 'Memória total do sistema', systemMemoryTotal: 'Memória total do sistema',
@@ -1836,6 +1905,8 @@ const message = {
pcieWidth: 'Largura PCIe (atual / máxima)', pcieWidth: 'Largura PCIe (atual / máxima)',
gpu: 'Gerenciamento de GPU', gpu: 'Gerenciamento de GPU',
monitoringTitle: 'Monitoramento de GPU', monitoringTitle: 'Monitoramento de GPU',
currentHelper:
'Veja o estado em tempo real e o uso de recursos dos dispositivos GPU/NPU/XPU no nó atual. O resumo superior inclui todos os dispositivos; as métricas detalhadas são exibidas por dispositivo.',
gpuHelper: gpuHelper:
'Nenhum comando de gerenciamento de GPU/XPU/NPU compatível foi detectado. Verifique e tente novamente!', 'Nenhum comando de gerenciamento de GPU/XPU/NPU compatível foi detectado. Verifique e tente novamente!',
process: 'Informações do Processo', process: 'Informações do Processo',
+71 -2
View File
@@ -1747,8 +1747,8 @@ const message = {
running: 'Выполняемые запросы', running: 'Выполняемые запросы',
waiting: 'Ожидающие запросы', waiting: 'Ожидающие запросы',
cacheUsage: 'Использование KV-кэша', cacheUsage: 'Использование KV-кэша',
promptThroughput: 'Скорость входных токенов', promptThroughput: 'Скорость ввода',
generationThroughput: 'Скорость выходных токенов', generationThroughput: 'Скорость вывода',
requestThroughput: 'Завершённые запросы', requestThroughput: 'Завершённые запросы',
timeToFirstToken: 'Среднее время до первого токена', timeToFirstToken: 'Среднее время до первого токена',
timePerOutputToken: 'Средняя задержка между токенами', timePerOutputToken: 'Средняя задержка между токенами',
@@ -1768,6 +1768,73 @@ const message = {
timePerOutputTokenChart: 'Задержка между токенами', timePerOutputTokenChart: 'Задержка между токенами',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'GPU хоста',
containerGPU: 'Поддержка GPU в контейнерах',
monitorAvailable: 'Мониторинг доступен',
monitorPartial: 'Частичный мониторинг',
notReady: 'Не готово',
ready: 'Готово',
checkedAt: 'Время проверки',
checkDetailsHelp: 'Изучите результаты проверки, проверьте состояние среды и повторите проверку.',
driverActionTitle: 'Сначала проверьте драйвер GPU хоста',
driverActionHelp:
'Установите или восстановите драйвер проблемного устройства, затем продолжите проверку среды контейнеров.',
driverGuide: 'Инструкция по установке драйвера',
toolkitActionTitle: 'Поддержка GPU в контейнерах не готова',
toolkitActionHelp:
'Установите или восстановите NVIDIA Container Toolkit, затем проверьте настройки Docker.',
toolkitGuide: 'Инструкция по установке Toolkit',
dockerActionTitle: 'Поддержка NVIDIA GPU в Docker не готова',
dockerActionHelp: 'Проверьте настройки среды выполнения NVIDIA в Docker и повторите проверку.',
monitorUnaffected: 'Эта проблема не влияет на мониторинг GPU хоста.',
title: 'Проверка окружения',
passed: 'Проверки окружения пройдены',
issues: 'Обнаружено проблем окружения: {count}. Проверьте их согласно подсказкам.',
recheck: 'Проверить снова',
installGuide: 'Руководство по установке',
goContainerSettings: 'Открыть настройки контейнеров',
details: 'Подробности проверки',
vendor: 'Производитель',
gpuCount: 'Количество GPU',
cardCount: 'Количество карт',
notDetected: 'На текущем узле не обнаружены поддерживаемые устройства GPU/NPU/XPU.',
unknown:
'Не удалось определить состояние устройства. Просмотрите подробности проверки и повторите попытку.',
loadFailed: 'Не удалось прочитать сведения об устройстве. Повторите попытку.',
notApplicable: 'Не применимо',
cudaHelper:
'Это версия CUDA, поддерживаемая драйвером, а не версия CUDA Toolkit, установленная на хосте.',
partialMemory: 'Часть данных о памяти недоступна',
sharedMemory: 'Общая системная память: {memory}',
driver: 'Драйвер и средства управления устройствами',
nvidiaDriver: 'Драйвер NVIDIA',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Настройка NVIDIA GPU в Docker',
statusPassed: 'Норма',
statusMissing: 'Не обнаружено',
statusUnconfigured: 'Требуется настройка',
statusFailed: 'Ошибка проверки',
statusUnknown: 'Неизвестно',
driverMissing:
'Драйвер {vendor} не обнаружен. Установите или проверьте драйвер (средство обнаружения: {tool}).',
driverFailed:
'Не удалось прочитать сведения об устройстве с помощью {tool}. Проверьте состояние драйвера.',
driverUnknown: 'Не удалось определить состояние драйвера устройства. Проверьте {tool}.',
toolkitMissing:
'NVIDIA Container Toolkit не обнаружен. Установите и настройте его перед использованием NVIDIA GPU в контейнерах.',
toolkitFailed: 'Не удалось проверить NVIDIA Container Toolkit. Просмотрите сведения об ошибке.',
dockerConfigMissing:
'Запись runtimes.nvidia не обнаружена в файле конфигурации Docker по умолчанию. Завершите настройку согласно документации.',
dockerConfigInvalid:
'Конфигурация среды выполнения NVIDIA в Docker некорректна. Проверьте runtimes.nvidia.path в daemon.json.',
dockerConfigPending:
'Файл конфигурации по умолчанию содержит настройки среды выполнения NVIDIA, но текущая служба Docker ещё не загрузила их. Проверьте файл конфигурации, перезапустите Docker и повторите проверку.',
dockerConfigUnreadable:
'Не удалось прочитать файл конфигурации Docker. Просмотрите подробности проверки.',
dockerUnconfigured: 'Настройка NVIDIA GPU в Docker не завершена.',
dockerFailed: 'Не удалось проверить настройку NVIDIA GPU в Docker. Просмотрите сведения об ошибке.',
},
systemMemory: 'Общая системная память', systemMemory: 'Общая системная память',
systemMemoryUsed: 'Использовано системной памяти', systemMemoryUsed: 'Использовано системной памяти',
systemMemoryTotal: 'Всего системной памяти', systemMemoryTotal: 'Всего системной памяти',
@@ -1828,6 +1895,8 @@ const message = {
pcieWidth: 'Ширина PCIe (текущая / максимальная)', pcieWidth: 'Ширина PCIe (текущая / максимальная)',
gpu: 'Управление GPU', gpu: 'Управление GPU',
monitoringTitle: 'Мониторинг GPU', monitoringTitle: 'Мониторинг GPU',
currentHelper:
'Просматривайте состояние и использование ресурсов устройств GPU/NPU/XPU текущего узла в реальном времени. Вверху показана сводка по всем устройствам, а подробные показатели отображаются для каждого устройства отдельно.',
gpuHelper: 'Поддерживаемая команда управления GPU/XPU/NPU не обнаружена. Проверьте и повторите попытку!', gpuHelper: 'Поддерживаемая команда управления GPU/XPU/NPU не обнаружена. Проверьте и повторите попытку!',
process: 'Информация о Процессе', process: 'Информация о Процессе',
type: 'Тип', type: 'Тип',
+69 -2
View File
@@ -1750,8 +1750,8 @@ const message = {
running: 'Çalışan istekler', running: 'Çalışan istekler',
waiting: 'Bekleyen istekler', waiting: 'Bekleyen istekler',
cacheUsage: 'KV önbellek kullanımı', cacheUsage: 'KV önbellek kullanımı',
promptThroughput: 'Girdi token hızı', promptThroughput: 'Girdi hızı',
generationThroughput: 'Çıktı token hızı', generationThroughput: 'Çıktı hızı',
requestThroughput: 'Tamamlanan istek hızı', requestThroughput: 'Tamamlanan istek hızı',
timeToFirstToken: 'Ortalama ilk token gecikmesi', timeToFirstToken: 'Ortalama ilk token gecikmesi',
timePerOutputToken: 'Ortalama token arası gecikme', timePerOutputToken: 'Ortalama token arası gecikme',
@@ -1772,6 +1772,71 @@ const message = {
timePerOutputTokenChart: 'Token arası gecikme', timePerOutputTokenChart: 'Token arası gecikme',
}, },
gpu: { gpu: {
environment: {
hostGPU: 'Ana makine GPU’su',
containerGPU: 'Konteyner GPU desteği',
monitorAvailable: 'İzleme kullanılabilir',
monitorPartial: 'Kısmi izleme',
notReady: 'Hazır değil',
ready: 'Hazır',
checkedAt: 'Kontrol zamanı',
checkDetailsHelp: 'Kontrol ayrıntılarını inceleyin, ortam durumunu doğrulayın ve yeniden kontrol edin.',
driverActionTitle: 'Önce ana makine GPU sürücüsünü kontrol edin',
driverActionHelp:
'Konteyner ortamını kontrol etmeye devam etmeden önce sorunlu aygıtın sürücüsünü kurun veya onarın.',
driverGuide: 'Sürücü kurulum kılavuzunu görüntüle',
toolkitActionTitle: 'Konteyner GPU desteği hazır değil',
toolkitActionHelp:
'Önce NVIDIA Container Toolkit’i kurun veya onarın, ardından Docker yapılandırmasını kontrol edin.',
toolkitGuide: 'Toolkit kurulum kılavuzunu görüntüle',
dockerActionTitle: 'Docker NVIDIA GPU desteği hazır değil',
dockerActionHelp:
'Docker içindeki NVIDIA çalışma zamanı yapılandırmasını kontrol edip yeniden deneyin.',
monitorUnaffected: 'Bu sorun ana makine GPU izlemesini etkilemez.',
title: 'Ortam denetimleri',
passed: 'Ortam denetimleri başarılı',
issues: '{count} ortam sorunu algılandı. Denetlemek için yönergeleri izleyin.',
recheck: 'Yeniden denetle',
installGuide: 'Kurulum kılavuzunu görüntüle',
goContainerSettings: 'Kapsayıcı ayarlarını aç',
details: 'Denetim ayrıntıları',
vendor: 'Üretici',
gpuCount: 'GPU sayısı',
cardCount: 'Kart sayısı',
notDetected: 'Geçerli düğümde desteklenen GPU/NPU/XPU aygıtı algılanmadı.',
unknown: 'Aygıt durumu doğrulanamadı. Denetim ayrıntılarını inceleyip yeniden deneyin.',
loadFailed: 'Aygıt bilgileri okunamadı. Lütfen yeniden deneyin.',
notApplicable: 'Uygulanamaz',
cudaHelper:
'Bu, sürücünün desteklediği CUDA sürümüdür; ana makinede kurulu CUDA Toolkit sürümü değildir.',
partialMemory: 'Bazı bellek verileri alınamadı',
sharedMemory: 'Paylaşılan sistem belleği: {memory}',
driver: 'Sürücü ve aygıt yönetim araçları',
nvidiaDriver: 'NVIDIA sürücüsü',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Docker NVIDIA GPU yapılandırması',
statusPassed: 'Normal',
statusMissing: 'Algılanmadı',
statusUnconfigured: 'Yapılandırma gerekli',
statusFailed: 'Denetim başarısız',
statusUnknown: 'Bilinmiyor',
driverMissing:
'{vendor} sürücüsü algılanmadı. Sürücüyü kurun veya denetleyin (algılama aracı: {tool}).',
driverFailed: 'Aygıt bilgileri {tool} ile okunamadı. Sürücü durumunu denetleyin.',
driverUnknown: 'Aygıt sürücüsünün durumu doğrulanamadı. {tool} aracını denetleyin.',
toolkitMissing:
'NVIDIA Container Toolkit algılanmadı. Kapsayıcılarda NVIDIA GPU kullanmadan önce kurulum ve yapılandırmayı tamamlayın.',
toolkitFailed: 'NVIDIA Container Toolkit denetlenemedi. Hata ayrıntılarını inceleyin.',
dockerConfigMissing:
'Varsayılan Docker yapılandırma dosyasında runtimes.nvidia girdisi algılanmadı. Yapılandırmayı belgelere göre tamamlayın.',
dockerConfigInvalid:
'Docker NVIDIA çalışma zamanı yapılandırması geçersiz. daemon.json dosyasındaki runtimes.nvidia.path değerini denetleyin.',
dockerConfigPending:
'Varsayılan yapılandırma dosyasında NVIDIA çalışma zamanı yapılandırması var, ancak mevcut Docker hizmeti bunu henüz yüklemedi. Yapılandırma dosyasını doğrulayın, Docker hizmetini yeniden başlatıp tekrar denetleyin.',
dockerConfigUnreadable: 'Docker yapılandırma dosyası okunamadı. Denetim ayrıntılarını inceleyin.',
dockerUnconfigured: 'Docker NVIDIA GPU yapılandırması tamamlanmadı.',
dockerFailed: 'Docker NVIDIA GPU yapılandırması denetlenemedi. Hata ayrıntılarını inceleyin.',
},
systemMemory: 'Paylaşılan sistem belleği', systemMemory: 'Paylaşılan sistem belleği',
systemMemoryUsed: 'Kullanılan sistem belleği', systemMemoryUsed: 'Kullanılan sistem belleği',
systemMemoryTotal: 'Toplam sistem belleği', systemMemoryTotal: 'Toplam sistem belleği',
@@ -1831,6 +1896,8 @@ const message = {
pcieWidth: 'PCIe genişliği (geçerli / en yüksek)', pcieWidth: 'PCIe genişliği (geçerli / en yüksek)',
gpu: 'GPU Yönetimi', gpu: 'GPU Yönetimi',
monitoringTitle: 'GPU İzleme', monitoringTitle: 'GPU İzleme',
currentHelper:
'Geçerli düğümdeki GPU/NPU/XPU aygıtlarının anlık durumunu ve kaynak kullanımını görüntüleyin. Üstteki özet tüm aygıtları kapsar; ayrıntılı ölçümler aygıt bazında gösterilir.',
gpuHelper: 'Desteklenen bir GPU/XPU/NPU yönetim komutu algılanmadı. Lütfen kontrol edip tekrar deneyin!', gpuHelper: 'Desteklenen bir GPU/XPU/NPU yönetim komutu algılanmadı. Lütfen kontrol edip tekrar deneyin!',
process: 'İşlem Bilgisi', process: 'İşlem Bilgisi',
type: 'Tür', type: 'Tür',
+62 -2
View File
@@ -1636,8 +1636,8 @@ const message = {
running: '執行中請求', running: '執行中請求',
waiting: '等待中請求', waiting: '等待中請求',
cacheUsage: 'KV Cache 使用率', cacheUsage: 'KV Cache 使用率',
promptThroughput: '輸入 Token 吞吐', promptThroughput: '輸入吞吐',
generationThroughput: '輸出 Token 吞吐', generationThroughput: '輸出吞吐',
requestThroughput: '完成請求速率', requestThroughput: '完成請求速率',
timeToFirstToken: '平均首 Token 延遲', timeToFirstToken: '平均首 Token 延遲',
timePerOutputToken: '平均 Token 間延遲', timePerOutputToken: '平均 Token 間延遲',
@@ -1656,6 +1656,64 @@ const message = {
timePerOutputTokenChart: 'Token 間延遲', timePerOutputTokenChart: 'Token 間延遲',
}, },
gpu: { gpu: {
environment: {
hostGPU: '主機 GPU',
containerGPU: '容器 GPU 支援',
monitorAvailable: '可監控',
monitorPartial: '部分可監控',
notReady: '尚未就緒',
ready: '已就緒',
checkedAt: '檢測時間',
checkDetailsHelp: '請查看檢測詳情,確認環境狀態後重新檢測。',
driverActionTitle: '優先檢查主機 GPU 驅動程式',
driverActionHelp: '請先安裝或修復異常裝置的驅動程式,驅動程式就緒後再繼續檢查容器環境。',
driverGuide: '查看驅動程式安裝指引',
toolkitActionTitle: '容器 GPU 支援尚未就緒',
toolkitActionHelp: '請先安裝或修復 NVIDIA Container Toolkit,再檢查 Docker 設定。',
toolkitGuide: '查看 Toolkit 安裝指引',
dockerActionTitle: 'Docker 的 NVIDIA GPU 支援尚未就緒',
dockerActionHelp: '請檢查 Docker 的 NVIDIA 執行環境設定,完成後重新檢測。',
monitorUnaffected: '主機 GPU 監控不受此問題影響。',
title: '環境檢查',
passed: '環境檢查通過',
issues: '偵測到 {count} 項環境問題,請依照提示檢查。',
recheck: '重新偵測',
installGuide: '查看安裝文件',
goContainerSettings: '前往容器設定',
details: '偵測詳情',
vendor: '廠商',
gpuCount: 'GPU 數量',
cardCount: '卡數量',
notDetected: '目前節點未偵測到支援的 GPU/NPU/XPU 裝置。',
unknown: '無法確認裝置狀態,請查看偵測詳情後重新偵測。',
loadFailed: '無法讀取裝置資訊,請重試。',
notApplicable: '不適用',
cudaHelper: '此版本表示驅動程式支援的 CUDA 版本,不代表主機已安裝的 CUDA Toolkit。',
partialMemory: '部分顯示記憶體資料未取得',
sharedMemory: '共用系統記憶體:{memory}',
driver: '驅動程式與裝置管理工具',
nvidiaDriver: 'NVIDIA 驅動',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Docker NVIDIA GPU 設定',
statusPassed: '正常',
statusMissing: '未偵測到',
statusUnconfigured: '待設定',
statusFailed: '偵測失敗',
statusUnknown: '未知',
driverMissing: '未偵測到 {vendor} 驅動程式,請安裝或檢查驅動程式(偵測工具:{tool})。',
driverFailed: '無法透過 {tool} 讀取裝置資訊,請檢查驅動程式狀態。',
driverUnknown: '無法確認裝置驅動程式狀態,請檢查 {tool}。',
toolkitMissing: '未偵測到 NVIDIA Container Toolkit,容器使用 NVIDIA GPU 前需完成安裝與設定。',
toolkitFailed: '無法偵測 NVIDIA Container Toolkit,請查看失敗原因。',
dockerConfigMissing: 'Docker 預設設定檔中未偵測到 runtimes.nvidia,請依照文件完成設定。',
dockerConfigInvalid:
'Docker 的 NVIDIA 執行階段設定有誤,請檢查 daemon.json 中的 runtimes.nvidia.path。',
dockerConfigPending:
'預設設定檔中已存在 NVIDIA 執行階段設定,但目前 Docker 尚未載入,請確認設定檔並重新啟動 Docker 後重新偵測。',
dockerConfigUnreadable: '無法讀取 Docker 設定檔,請查看偵測詳情。',
dockerUnconfigured: '尚未完成 Docker 的 NVIDIA GPU 設定。',
dockerFailed: '無法偵測 Docker 的 NVIDIA GPU 設定,請查看失敗原因。',
},
systemMemory: '系統共享記憶體', systemMemory: '系統共享記憶體',
systemMemoryUsed: '系統已用記憶體', systemMemoryUsed: '系統已用記憶體',
systemMemoryTotal: '系統總記憶體', systemMemoryTotal: '系統總記憶體',
@@ -1713,6 +1771,8 @@ const message = {
pcieWidth: 'PCIe 寬度(目前 / 最大)', pcieWidth: 'PCIe 寬度(目前 / 最大)',
gpu: 'GPU 管理', gpu: 'GPU 管理',
monitoringTitle: 'GPU 監控', monitoringTitle: 'GPU 監控',
currentHelper:
'查看目前節點 GPU/NPU/XPU 裝置的即時運作狀態與資源使用情況。頂部彙總所有裝置,詳細指標依裝置顯示。',
gpuHelper: '目前系統未偵測到支援的 GPU/XPU/NPU 管理指令,請檢查後重試', gpuHelper: '目前系統未偵測到支援的 GPU/XPU/NPU 管理指令,請檢查後重試',
process: '行程資訊', process: '行程資訊',
type: '類型', type: '類型',
+61 -2
View File
@@ -1649,8 +1649,8 @@ const message = {
running: '运行中请求', running: '运行中请求',
waiting: '等待中请求', waiting: '等待中请求',
cacheUsage: 'KV Cache 使用率', cacheUsage: 'KV Cache 使用率',
promptThroughput: '输入 Token 吞吐', promptThroughput: '输入吞吐',
generationThroughput: '输出 Token 吞吐', generationThroughput: '输出吞吐',
requestThroughput: '完成请求速率', requestThroughput: '完成请求速率',
timeToFirstToken: '平均首 Token 延迟', timeToFirstToken: '平均首 Token 延迟',
timePerOutputToken: '平均 Token 间延迟', timePerOutputToken: '平均 Token 间延迟',
@@ -1669,6 +1669,63 @@ const message = {
timePerOutputTokenChart: 'Token 间延迟', timePerOutputTokenChart: 'Token 间延迟',
}, },
gpu: { gpu: {
environment: {
hostGPU: '宿主机 GPU',
containerGPU: '容器 GPU 支持',
monitorAvailable: '可监控',
monitorPartial: '部分可监控',
notReady: '未就绪',
ready: '已就绪',
checkedAt: '检测时间',
checkDetailsHelp: '请查看检测详情,确认环境状态后重新检测。',
driverActionTitle: '优先检查宿主机 GPU 驱动',
driverActionHelp: '请先安装或修复异常设备的驱动,驱动就绪后再继续检查容器环境。',
driverGuide: '查看驱动安装指引',
toolkitActionTitle: '容器 GPU 支持未就绪',
toolkitActionHelp: '请先安装或修复 NVIDIA Container Toolkit,再检查 Docker 配置。',
toolkitGuide: '查看 Toolkit 安装指引',
dockerActionTitle: 'Docker 的 NVIDIA GPU 支持未就绪',
dockerActionHelp: '请检查 Docker 的 NVIDIA 运行时配置,完成后重新检测。',
monitorUnaffected: '宿主机 GPU 监控不受此问题影响。',
title: '环境检查',
passed: '环境检查通过',
issues: '检测到 {count} 项环境问题,请根据提示检查。',
recheck: '重新检测',
installGuide: '查看安装文档',
goContainerSettings: '前往容器配置',
details: '检测详情',
vendor: '厂商',
gpuCount: 'GPU 数量',
cardCount: '卡数量',
notDetected: '当前节点未检测到支持的 GPU/NPU/XPU 设备。',
unknown: '无法确认设备状态,请查看检测详情后重新检测。',
loadFailed: '无法读取设备信息,请重试。',
notApplicable: '不适用',
cudaHelper: '此版本表示驱动支持的 CUDA 版本,不代表宿主机已安装的 CUDA Toolkit。',
partialMemory: '部分显存数据未获取',
sharedMemory: '共享系统内存:{memory}',
driver: '驱动与设备管理工具',
nvidiaDriver: 'NVIDIA 驱动',
toolkit: 'NVIDIA Container Toolkit',
docker: 'Docker NVIDIA GPU 配置',
statusPassed: '正常',
statusMissing: '未检测到',
statusUnconfigured: '待配置',
statusFailed: '检测失败',
statusUnknown: '未知',
driverMissing: '未检测到 {vendor} 驱动,请安装或检查驱动(检测工具:{tool})。',
driverFailed: '无法通过 {tool} 读取设备信息,请检查驱动状态。',
driverUnknown: '无法确认设备驱动状态,请检查 {tool}。',
toolkitMissing: '未检测到 NVIDIA Container Toolkit,容器使用 NVIDIA GPU 前需完成安装与配置。',
toolkitFailed: '无法检测 NVIDIA Container Toolkit,请查看失败原因。',
dockerConfigMissing: 'Docker 默认配置文件中未检测到 runtimes.nvidia,请根据文档完成配置。',
dockerConfigInvalid: 'Docker 的 NVIDIA 运行时配置有误,请检查 daemon.json 中的 runtimes.nvidia.path。',
dockerConfigPending:
'默认配置文件中已存在 NVIDIA 运行时配置,但当前 Docker 尚未加载,请确认配置文件并重启 Docker 后重新检测。',
dockerConfigUnreadable: '无法读取 Docker 配置文件,请查看检测详情。',
dockerUnconfigured: '尚未完成 Docker 的 NVIDIA GPU 配置。',
dockerFailed: '无法检测 Docker 的 NVIDIA GPU 配置,请查看失败原因。',
},
systemMemory: '系统共享内存', systemMemory: '系统共享内存',
systemMemoryUsed: '系统已用内存', systemMemoryUsed: '系统已用内存',
systemMemoryTotal: '系统总内存', systemMemoryTotal: '系统总内存',
@@ -1726,6 +1783,8 @@ const message = {
pcieWidth: 'PCIe 宽度(当前 / 最大)', pcieWidth: 'PCIe 宽度(当前 / 最大)',
gpu: 'GPU 管理', gpu: 'GPU 管理',
monitoringTitle: 'GPU 监控', monitoringTitle: 'GPU 监控',
currentHelper:
'查看当前节点 GPU/NPU/XPU 设备的实时运行状态与资源使用情况。顶部汇总全部设备,详细指标按设备展示。',
gpuHelper: '当前系统未检测到支持的 GPU/XPU/NPU 管理指令,请检查后重试!', gpuHelper: '当前系统未检测到支持的 GPU/XPU/NPU 管理指令,请检查后重试!',
process: '进程信息', process: '进程信息',
processCount: '进程数', processCount: '进程数',
@@ -15,7 +15,7 @@
<el-menu <el-menu
v-else v-else
:default-active="activeMenu" :default-active="activeMenu"
:router="false" :router="true"
:collapse="isCollapse" :collapse="isCollapse"
:collapse-transition="false" :collapse-transition="false"
:unique-opened="!menuAccordion" :unique-opened="!menuAccordion"
+6 -2
View File
@@ -62,7 +62,7 @@ import TerminalDock from '@/components/terminal/dock/index.vue';
import useResize from './hooks/useResize'; import useResize from './hooks/useResize';
import { MenuStore, TabsStore } from '@/store'; import { MenuStore, TabsStore } from '@/store';
import { getSystemAvailable } from '@/api/modules/setting'; import { getSystemAvailable } from '@/api/modules/setting';
import { openMenu } from '@/utils/router'; import { useRoute, useRouter } from 'vue-router';
import { loadMasterProductProFromDB, loadProductProFromDB } from '@/utils/xpack'; import { loadMasterProductProFromDB, loadProductProFromDB } from '@/utils/xpack';
import { useTheme } from '@/global/use-theme'; import { useTheme } from '@/global/use-theme';
import TaskList from '@/components/task-list/index.vue'; import TaskList from '@/components/task-list/index.vue';
@@ -95,6 +95,8 @@ const openTask = () => {
menuStore.setCollapse(); menuStore.setCollapse();
} }
}; };
const router = useRouter();
const route = useRoute();
const menuStore = MenuStore(); const menuStore = MenuStore();
const tabsStore = TabsStore(); const tabsStore = TabsStore();
@@ -149,7 +151,9 @@ watch(
}, },
); );
const handleMenuClick = async (path) => { const handleMenuClick = async (path) => {
await openMenu(path); await router.push({ path: path });
tabsStore.addTab(route);
tabsStore.activeTabPath = route.path;
}; };
const toLogin = () => { const toLogin = () => {
+31 -5
View File
@@ -1,4 +1,4 @@
import { panelCacheKey, writePanelCache, prunePanelCache } from '@/utils/panel-cache'; import { panelCacheKey, readPanelCache, writePanelCache, removePanelCache, prunePanelCache } from '@/utils/panel-cache';
import router from '@/routers/router'; import router from '@/routers/router';
import NProgress from '@/config/nprogress'; import NProgress from '@/config/nprogress';
import { useGlobalStore } from '@/composables/useGlobalStore'; import { useGlobalStore } from '@/composables/useGlobalStore';
@@ -11,6 +11,7 @@ import { TerminalDockSessionStore, TerminalSessionStore } from '@/store';
const axiosCanceler = new AxiosCanceler(); const axiosCanceler = new AxiosCanceler();
let isRedirecting = false;
const enterpriseLicenseCheckWhiteList = ['EnterpriseLicenseRequired', 'entrance', 'login', 'Expired']; const enterpriseLicenseCheckWhiteList = ['EnterpriseLicenseRequired', 'entrance', 'login', 'Expired'];
const noLoginWhiteList = ['entrance', 'login', 'file-share', '404', 'Expired']; const noLoginWhiteList = ['entrance', 'login', 'file-share', '404', 'Expired'];
@@ -29,7 +30,7 @@ const clearLoginStatus = () => {
TerminalDockSessionStore().closeAll(); TerminalDockSessionStore().closeAll();
}; };
router.beforeEach(async (to) => { router.beforeEach(async (to, from) => {
const { entrance, isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isLogin, isOnRestart } = const { entrance, isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isLogin, isOnRestart } =
useGlobalStore(); useGlobalStore();
NProgress.start(); NProgress.start();
@@ -102,11 +103,34 @@ router.beforeEach(async (to) => {
if (to.name === 'Expired') { if (to.name === 'Expired') {
return true; return true;
} }
// Compatibility for old links: explicit navigation no longer needs to clear a cache. const activeMenuKey = panelCacheKey(String(to.meta.activeMenu || ''));
if (to.query.uncached != undefined) { if (to.query.uncached != undefined) {
const query = { ...to.query }; const query = { ...to.query };
delete query.uncached; delete query.uncached;
return { path: to.path, query, hash: to.hash }; removePanelCache('tabs', activeMenuKey);
return { path: to.path, query };
}
const cachedRoute = readPanelCache<string>('tabs', activeMenuKey);
if (
to.meta.activeMenu &&
to.meta.activeMenu != from.meta.activeMenu &&
typeof cachedRoute === 'string' &&
cachedRoute &&
cachedRoute !== to.path &&
!isRedirecting
) {
const cachedRouteInfo = router.resolve(cachedRoute);
if (
cachedRouteInfo.matched.length > 0 &&
cachedRouteInfo.meta.activeMenu === to.meta.activeMenu &&
hasRouteAccess(cachedRouteInfo)
) {
isRedirecting = true;
NProgress.done();
return cachedRoute;
}
removePanelCache('tabs', activeMenuKey);
} }
if (!hasRouteAccess(to)) { if (!hasRouteAccess(to)) {
@@ -120,10 +144,11 @@ router.beforeEach(async (to) => {
router.afterEach((to, _from, failure) => { router.afterEach((to, _from, failure) => {
prunePanelCache(); prunePanelCache();
if (failure) { if (failure) {
isRedirecting = false;
NProgress.done(); NProgress.done();
return; return;
} }
if (to.meta.activeMenu && !to.meta.ignoreTab) { if (to.meta.activeMenu && !to.meta.ignoreTab && !isRedirecting) {
let notMathParam = true; let notMathParam = true;
if (to.matched.some((record) => record.path.includes(':'))) { if (to.matched.some((record) => record.path.includes(':'))) {
notMathParam = false; notMathParam = false;
@@ -141,6 +166,7 @@ router.afterEach((to, _from, failure) => {
} }
} }
isRedirecting = false;
NProgress.done(); NProgress.done();
}); });
+1
View File
@@ -28,5 +28,6 @@ declare interface RouterButton {
path?: string; path?: string;
name?: string; name?: string;
count?: number; count?: number;
disabled?: boolean;
permission?: string | string[]; permission?: string | string[];
} }
+6 -36
View File
@@ -4,7 +4,6 @@ import { hasRouteAccess } from '@/utils/rbac';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgError } from '@/utils/message'; import { MsgError } from '@/utils/message';
import type { RouteLocationRaw } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router';
import { panelCacheKey, readPanelCache, removePanelCache } from '@/utils/panel-cache';
const pushWithAccessCheck = async (to: RouteLocationRaw) => { const pushWithAccessCheck = async (to: RouteLocationRaw) => {
const resolvedRoute = router.resolve(to); const resolvedRoute = router.resolve(to);
@@ -16,53 +15,24 @@ const pushWithAccessCheck = async (to: RouteLocationRaw) => {
tabStoreMiddleWare(); tabStoreMiddleWare();
}; };
/** Explicit navigation always opens the requested page, independent of tab caching. */
export const openPage = pushWithAccessCheck;
/** Only module-menu entry points restore the last visited route within that module. */
export const openMenu = async (to: RouteLocationRaw) => {
let entry = router.resolve(to);
const visited = new Set<string>();
// Menu roots often redirect and only their destination declares activeMenu.
while (!visited.has(entry.fullPath)) {
visited.add(entry.fullPath);
const redirect = entry.matched[entry.matched.length - 1]?.redirect;
if (!redirect) break;
entry = router.resolve(typeof redirect === 'function' ? redirect(entry, router.currentRoute.value) : redirect);
}
const menu = entry.meta.activeMenu;
if (menu) {
const key = panelCacheKey(String(menu));
const cached = readPanelCache<unknown>('tabs', key);
if (typeof cached === 'string') {
const destination = router.resolve(cached);
if (destination.matched.length && destination.meta.activeMenu === menu && hasRouteAccess(destination)) {
return openPage(cached);
}
}
if (cached !== undefined) removePanelCache('tabs', key);
}
return openPage(to);
};
export const routerToName = async (name: string) => { export const routerToName = async (name: string) => {
await openPage({ name: name }); await pushWithAccessCheck({ name: name });
}; };
export const routerToPath = async (path: string) => { export const routerToPath = async (path: string) => {
await openPage({ path: path }); await pushWithAccessCheck({ path: path });
}; };
export const routerToFileWithPath = async (pathItem: string) => { export const routerToFileWithPath = async (pathItem: string) => {
await openPage({ name: 'File', query: { path: pathItem, uncached: 'true' } }); await pushWithAccessCheck({ name: 'File', query: { path: pathItem, uncached: 'true' } });
}; };
export const routerToNameWithQuery = async (name: string, query: any) => { export const routerToNameWithQuery = async (name: string, query: any) => {
await openPage({ name: name, query: query }); await pushWithAccessCheck({ name: name, query: query });
}; };
export const routerToPathWithQuery = async (path: string, query: any) => { export const routerToPathWithQuery = async (path: string, query: any) => {
await openPage({ path: path, query: query }); await pushWithAccessCheck({ path: path, query: query });
}; };
export const jumpToPath = (router: any, path: string) => { export const jumpToPath = (router: any, path: string) => {
@@ -70,7 +40,7 @@ export const jumpToPath = (router: any, path: string) => {
}; };
export const routerToNameWithParams = async (name: string, params: any) => { export const routerToNameWithParams = async (name: string, params: any) => {
await openPage({ name: name, params: params }); await pushWithAccessCheck({ name: name, params: params });
}; };
const tabStoreMiddleWare = () => { const tabStoreMiddleWare = () => {
+3 -3
View File
@@ -139,11 +139,11 @@ export const bindSearchState = (key: () => string | undefined, fields: SearchFie
interface SearchStateOptions { interface SearchStateOptions {
scope?: () => string | number | undefined; scope?: () => string | number | undefined;
onRevisit?: () => unknown; onRevisit: () => unknown;
} }
/** Keep queries across page visits and reload data when a cached page becomes active. */ /** Keep queries across page visits and reload data when a cached page becomes active. */
export const useSearchState = (page: string, fields: SearchFields, options: SearchStateOptions = {}) => { export const useSearchState = (page: string, fields: SearchFields, options: SearchStateOptions) => {
const { cacheUserID } = storeToRefs(GlobalStore()); const { cacheUserID } = storeToRefs(GlobalStore());
const binding = bindSearchState(() => { const binding = bindSearchState(() => {
const scope = options.scope?.(); const scope = options.scope?.();
@@ -160,6 +160,6 @@ export const useSearchState = (page: string, fields: SearchFields, options: Sear
onActivated(async () => { onActivated(async () => {
if (!deactivated) return; if (!deactivated) return;
deactivated = false; deactivated = false;
if (await binding.resume()) return options.onRevisit?.(); if (await binding.resume()) return options.onRevisit();
}); });
}; };
@@ -20,7 +20,6 @@
v-if="!noApp" v-if="!noApp"
v-model:view-mode="viewMode" v-model:view-mode="viewMode"
:data="items" :data="items"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
@search="search" @search="search"
> >
+573 -63
View File
@@ -1,32 +1,216 @@
<template> <template>
<div> <div>
<RouterMenu /> <RouterMenu :history-disabled="historyDisabled" />
<div> <div>
<LayoutContent v-loading="loading" :title="$t('aiTools.gpu.monitoringTitle')" v-if="hasAccelerators"> <LayoutContent v-loading="loading" :title="$t('aiTools.gpu.monitoringTitle')">
<template #leftToolBar>
<h3 class="environment-heading">
{{ $t(showEnvironment ? 'aiTools.gpu.environment.title' : 'aiTools.gpu.monitoringTitle') }}
</h3>
</template>
<template #rightToolBar> <template #rightToolBar>
<TableSetting title="gpu-refresh" @search="refresh()" /> <TableSetting title="gpu-refresh" @search="search()" />
<TableRefresh @search="refresh()" /> <el-tooltip :content="$t('aiTools.gpu.environment.recheck')">
<TableRefresh :disabled="loading" @search="refresh()" />
</el-tooltip>
</template> </template>
<template #main> <template #main>
<div class="device-overview"> <el-alert
v-if="loadFailed"
class="common-prompt"
:title="$t('aiTools.gpu.environment.loadFailed')"
type="error"
:closable="false"
show-icon
>
<el-button type="primary" link :disabled="loading" @click="refresh()">
{{ $t('aiTools.gpu.environment.recheck') }}
</el-button>
</el-alert>
<section v-else-if="showEnvironment" class="environment-status">
<div class="environment-row">
<div class="environment-identity">
<el-icon :size="22"><Monitor /></el-icon>
<div>
<strong>{{ $t('aiTools.gpu.environment.hostGPU') }}</strong>
<div class="environment-description">
<template v-if="driverChecks.length">
<span v-for="check in driverChecks" :key="check.vendor">
{{ checkTitle(check) }} · {{ checkStatus(check) }}
{{ check.version }}
</span>
</template>
<span v-else>{{ hasAccelerators ? deviceCountText : emptyDescription }}</span>
</div>
</div>
</div>
<el-tag :type="hostEnvironmentStatus.type">
{{ $t('aiTools.gpu.environment.' + hostEnvironmentStatus.label) }}
</el-tag>
</div>
<div v-if="containerChecks.length" class="environment-row">
<div class="environment-identity">
<el-icon :size="22"><Box /></el-icon>
<div>
<strong>{{ $t('aiTools.gpu.environment.containerGPU') }}</strong>
<div class="environment-description">
<span v-for="check in containerChecks" :key="check.item">
{{ checkTitle(check) }} · {{ checkStatus(check) }}
</span>
</div>
</div>
</div>
<el-tag :type="containerEnvironmentStatus.type">
{{ $t('aiTools.gpu.environment.' + containerEnvironmentStatus.label) }}
</el-tag>
</div>
<div v-if="environmentGuidance" class="environment-guidance">
<div>
<strong>{{ $t('aiTools.gpu.environment.' + environmentGuidance.title) }}</strong>
<p>
<template v-if="environmentGuidance.item !== 'driver' && hasAccelerators">
{{ $t('aiTools.gpu.environment.monitorUnaffected') }}
</template>
{{ $t('aiTools.gpu.environment.' + environmentGuidance.description) }}
</p>
</div>
<el-button
v-if="environmentGuidance.action === 'docker'"
type="primary"
@click="routerToName('ContainerSetting')"
>
{{ $t('aiTools.gpu.environment.goContainerSettings') }}
</el-button>
<el-button
v-else-if="environmentGuidance.action === 'details'"
type="primary"
@click="environmentDetails = ['environment']"
>
{{ $t('aiTools.gpu.environment.details') }}
</el-button>
<el-button
v-else
tag="a"
type="primary"
href="https://docs.fit2cloud.com/1panel/faq/gpu-driver"
target="_blank"
rel="noopener noreferrer"
>
{{ $t('aiTools.gpu.environment.' + environmentGuidance.action) }}
</el-button>
</div>
</section>
<el-collapse
v-if="!loadFailed && (environmentChecks.length || gpuInfo.warnings?.length)"
v-model="environmentDetails"
class="environment-details"
>
<el-collapse-item :title="$t('aiTools.gpu.environment.details')" name="environment">
<div v-for="check in environmentChecks" :key="`${check.vendor}-${check.item}`" class="mb-2">
<div class="flex flex-wrap items-center gap-2">
<span>{{ checkTitle(check) }}</span>
<el-tag :type="checkTagType(check)" size="small">
{{ checkStatus(check) }}
</el-tag>
<span v-if="check.version">{{ check.version }}</span>
</div>
<p v-if="check.status !== 'passed'" class="environment-check-description">
{{ checkDescription(check) }}
</p>
<pre v-if="check.reason" class="whitespace-pre-wrap break-words mt-1">{{
check.reason
}}</pre>
</div>
<div v-for="warning in gpuInfo.warnings || []" :key="warning" class="break-words">
{{ warning }}
</div>
</el-collapse-item>
</el-collapse>
<div
v-if="showEnvironment && !loadFailed && gpuInfo.environment?.checkedAt"
class="input-help mb-4"
>
{{ $t('aiTools.gpu.environment.checkedAt') }}:
{{ new Date(gpuInfo.environment.checkedAt).toLocaleString() }}
</div>
<div v-for="summary in overviewRows" :key="summary.vendor" class="device-overview mb-2">
<div class="overview-item">
<span>{{ $t('aiTools.gpu.environment.vendor') }}</span>
<strong>{{ vendorTitle(summary.vendor) }}</strong>
</div>
<div class="overview-item"> <div class="overview-item">
<span>{{ $t('aiTools.gpu.driverVersion') }}</span> <span>{{ $t('aiTools.gpu.driverVersion') }}</span>
<strong>{{ gpuInfo.driverVersion }}</strong> <strong>{{ displayValue(summary.driverVersion) }}</strong>
</div> </div>
<div v-if="gpuInfo.cudaVersion" class="overview-item"> <div class="overview-item">
<span>{{ $t('aiTools.gpu.cudaVersion') }}</span> <span>{{ $t('aiTools.gpu.cudaVersion') }}</span>
<strong>{{ gpuInfo.cudaVersion }}</strong> <el-tooltip
v-if="summary.vendor === 'nvidia' || summary.cudaVersion"
:content="$t('aiTools.gpu.environment.cudaHelper')"
>
<strong>{{ displayValue(summary.cudaVersion) }}</strong>
</el-tooltip>
<strong v-else>{{ $t('aiTools.gpu.environment.notApplicable') }}</strong>
</div> </div>
<div v-if="gpuInfo.collectedAt" class="overview-item"> <div class="overview-item">
<span>{{ $t('aiTools.gpu.collectedAt') }}</span> <span>
<strong>{{ new Date(gpuInfo.collectedAt).toLocaleString() }}</strong> {{
$t(
summary.vendor === 'intel' || summary.vendor === 'xpu'
? 'aiTools.gpu.environment.gpuCount'
: 'aiTools.gpu.environment.cardCount',
)
}}
</span>
<strong>{{ summary.cardCountText }}</strong>
</div>
<div class="overview-item">
<span>
{{
$t(
summary.sharedMemoryOnly
? 'aiTools.gpu.systemMemory'
: 'aiTools.gpu.memoryTotal',
)
}}
</span>
<el-tooltip
:disabled="!summary.sharedMemoryOnly && !summary.memoryPartial"
:content="summaryMemoryTooltip(summary)"
>
<strong>
{{
formatSummaryMemory(
summary.sharedMemoryOnly ? summary.sharedMemoryTotal : summary.memoryTotal,
)
}}
</strong>
</el-tooltip>
<el-tooltip
v-if="!summary.sharedMemoryOnly && summary.sharedMemoryTotal !== null"
:content="$t('aiTools.gpu.systemMemoryHelper')"
>
<span class="whitespace-normal">
{{
$t('aiTools.gpu.environment.sharedMemory', {
memory: formatSummaryMemory(summary.sharedMemoryTotal),
})
}}
</span>
</el-tooltip>
</div> </div>
<div class="overview-count">{{ deviceCountText }}</div>
</div> </div>
<div v-if="hasAccelerators && gpuInfo.collectedAt" class="input-help">
<div class="gpu-group-list"> {{ $t('aiTools.gpu.collectedAt') }}:
{{ new Date(gpuInfo.collectedAt).toLocaleString() }}
</div>
<el-empty
v-if="!hasAccelerators && !loading && !loadFailed && !showEnvironment"
:description="emptyDescription"
/>
<div v-if="hasAccelerators" class="gpu-group-list">
<section v-for="group in gpuGroups" :key="group.key" class="gpu-group"> <section v-for="group in gpuGroups" :key="group.key" class="gpu-group">
<div class="group-header"> <div v-if="group.title" class="group-header">
<div class="group-identity"> <div class="group-identity">
<strong>{{ group.title }}</strong> <strong>{{ group.title }}</strong>
</div> </div>
@@ -79,7 +263,12 @@
> >
{{ $t('commons.button.view') }} {{ $t('commons.button.view') }}
</el-button> </el-button>
<el-button type="primary" link @click="openHistory(item)"> <el-button
type="primary"
link
:disabled="historyDisabled"
@click="openHistory(item)"
>
{{ $t('aiTools.gpu.history') }} {{ $t('aiTools.gpu.history') }}
</el-button> </el-button>
</div> </div>
@@ -168,10 +357,6 @@
</div> </div>
</section> </section>
<section v-if="xpuInfo.xpu.length" class="gpu-group"> <section v-if="xpuInfo.xpu.length" class="gpu-group">
<div class="group-header">
<div class="group-identity"><strong>XPU</strong></div>
</div>
<div class="device-card-grid"> <div class="device-card-grid">
<el-card <el-card
v-for="item in xpuInfo.xpu" v-for="item in xpuInfo.xpu"
@@ -208,7 +393,12 @@
> >
{{ $t('commons.button.view') }} {{ $t('commons.button.view') }}
</el-button> </el-button>
<el-button type="primary" link @click="openHistory(item)"> <el-button
type="primary"
link
:disabled="historyDisabled"
@click="openHistory(item)"
>
{{ $t('aiTools.gpu.history') }} {{ $t('aiTools.gpu.history') }}
</el-button> </el-button>
</div> </div>
@@ -321,42 +511,34 @@
/> />
</template> </template>
</DialogPro> </DialogPro>
<LayoutContent :title="$t('aiTools.gpu.monitoringTitle')" :divider="true" v-if="!hasAccelerators && !loading">
<template #rightToolBar>
<TableRefresh @search="refresh()" />
</template>
<template #main>
<div class="app-warn">
<div class="flx-center">
<span>{{ $t('aiTools.gpu.gpuHelper') }}</span>
</div>
<div>
<img src="@/assets/images/no_app.svg" />
</div>
</div>
</template>
</LayoutContent>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'; import { computed, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { InfoFilled } from '@element-plus/icons-vue'; import { Box, InfoFilled, Monitor } from '@element-plus/icons-vue';
import { loadGPUInfo } from '@/api/modules/ai'; import { loadGPUInfo } from '@/api/modules/ai';
import RouterMenu from '@/views/ai/gpu/index.vue'; import RouterMenu from '@/views/ai/gpu/index.vue';
import { AI } from '@/api/interface/ai'; import { AI } from '@/api/interface/ai';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { routerToName } from '@/utils/router';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgWarning } from '@/utils/message'; import { computeSizeForDocker } from '@/utils/size';
import DeviceDetails, { type DetailSection } from './components/DeviceDetails.vue'; import DeviceDetails, { type DetailSection } from './components/DeviceDetails.vue';
const router = useRouter(); const router = useRouter();
const loading = ref(); const route = useRoute();
const pagePath = route.path;
const { currentNode } = useGlobalStore();
const loading = ref(false);
const loadFailed = ref(false);
const environmentDetails = ref<string[]>([]);
const processDrawerVisible = ref(false); const processDrawerVisible = ref(false);
const processDeviceKey = ref(''); const processDeviceKey = ref('');
const detailDeviceKey = ref(''); const detailDeviceKey = ref('');
const processXPUId = ref<number | null>(null); const processXPUId = ref<number | null>(null);
const gpuInfo = ref<AI.Info>({ const emptyGPUInfo = (): AI.Info => ({
cudaVersion: '', cudaVersion: '',
driverVersion: '', driverVersion: '',
type: 'nvidia', type: 'nvidia',
@@ -365,6 +547,7 @@ const gpuInfo = ref<AI.Info>({
xpuDriverVersion: '', xpuDriverVersion: '',
xpu: [], xpu: [],
}); });
const gpuInfo = ref<AI.Info>(emptyGPUInfo());
const xpuInfo = ref<AI.XpuInfo>({ const xpuInfo = ref<AI.XpuInfo>({
driverVersion: '', driverVersion: '',
type: 'xpu', type: 'xpu',
@@ -396,7 +579,7 @@ const gpuGroups = computed<GPUGroup[]>(() => {
if (!groups.has(key)) { if (!groups.has(key)) {
groups.set(key, { groups.set(key, {
key, key,
title: item.type === 'ascend' ? `NPU ${item.npuIndex}` : item.type.toUpperCase(), title: item.type === 'ascend' ? `NPU ${item.npuIndex}` : '',
devices: [], devices: [],
}); });
} }
@@ -464,6 +647,235 @@ const hasAccelerators = computed(() => {
return gpuGroups.value.length > 0 || xpuInfo.value.xpu.length > 0; return gpuGroups.value.length > 0 || xpuInfo.value.xpu.length > 0;
}); });
const vendorTitle = (vendor: string): string => {
const titles: Record<string, string> = {
nvidia: 'NVIDIA',
intel: 'Intel',
xpu: 'Intel',
ascend: 'Huawei Ascend',
amd: 'AMD',
};
if (vendor === 'mixed') {
const vendors = new Set(gpuInfo.value.gpu.map((device) => device.type as string));
if (gpuInfo.value.npu.length) vendors.add('ascend');
if (xpuInfo.value.xpu.length) vendors.add('intel');
return Array.from(vendors, vendorTitle).join(' / ') || '—';
}
return titles[vendor] || vendor || '—';
};
const displayValue = (value: string | undefined) => {
return isAvailable(value) ? value : '—';
};
const formatSummaryMemory = (value: number | null) => {
return value === null || !Number.isFinite(value) ? '—' : computeSizeForDocker(value);
};
const overviewRows = computed(() => {
if (gpuInfo.value.summaries?.length) {
return gpuInfo.value.summaries
.filter(
(summary) =>
summary.cardCount > 0 ||
isAvailable(summary.driverVersion) ||
summary.memoryTotal !== null ||
summary.sharedMemoryTotal !== null,
)
.map((summary) => {
const devices = gpuInfo.value.gpu.filter((device) => device.type === summary.vendor);
const sharedMemoryTotal =
devices.length && !devices.some((device) => device.sharedMemory) ? null : summary.sharedMemoryTotal;
return {
...summary,
sharedMemoryTotal,
cardCountText: summary.cardCount === null ? '—' : String(summary.cardCount),
sharedMemoryOnly:
summary.memoryTotal === null &&
sharedMemoryTotal !== null &&
devices.every((device) => device.sharedMemory),
};
});
}
if (!hasAccelerators.value) return [];
const memoryDevices = [
...[...gpuInfo.value.gpu, ...gpuInfo.value.npu].map((device) => ({
id: deviceKey(device),
total: device.memTotal,
shared: device.type !== 'ascend' && Boolean(device.sharedMemory),
})),
...xpuInfo.value.xpu.map((device) => ({
id: `xpu-${device.basic.deviceID}`,
total: device.basic.memory,
shared: false,
})),
];
let memoryTotal: number | null = null;
let sharedMemoryTotal: number | null = null;
let memoryPartial = false;
const seen = new Set<string>();
for (const device of memoryDevices) {
if (seen.has(device.id)) continue;
seen.add(device.id);
const total = memoryMiB(device.total || '');
if (total === null || !Number.isFinite(total) || total < 0) {
memoryPartial = true;
continue;
}
const bytes = total * 1024 * 1024;
if (device.shared) {
sharedMemoryTotal = Math.max(sharedMemoryTotal ?? 0, bytes);
} else {
memoryTotal = (memoryTotal ?? 0) + bytes;
}
}
return [
{
vendor: gpuInfo.value.type,
driverVersion: gpuInfo.value.driverVersion,
cudaVersion: gpuInfo.value.cudaVersion,
cardCountText: deviceCountText.value,
memoryTotal,
sharedMemoryTotal,
sharedMemoryOnly: sharedMemoryTotal !== null && memoryDevices.every((device) => device.shared),
memoryPartial,
},
];
});
const environmentChecks = computed(() => gpuInfo.value.environment?.checks || []);
const historyDisabled = computed(
() =>
(loading.value && !gpuInfo.value.environment && !hasAccelerators.value) ||
environmentChecks.value.some(
(check) => check.item === 'driver' && check.status !== 'passed' && (check.vendor || !hasAccelerators.value),
),
);
const driverChecks = computed(() => environmentChecks.value.filter((check) => check.item === 'driver'));
const containerChecks = computed(() => environmentChecks.value.filter((check) => check.item !== 'driver'));
const showEnvironment = computed(
() =>
gpuInfo.value.environment &&
(gpuInfo.value.environment.hardwareStatus !== 'not_detected' || environmentChecks.value.length > 0),
);
const hostEnvironmentStatus = computed<{ type: 'success' | 'warning' | 'info'; label: string }>(() => {
const hasDriverIssue = driverChecks.value.some((check) => check.status !== 'passed');
if (hasAccelerators.value && (!hasDriverIssue || driverChecks.value.some((check) => check.status === 'passed'))) {
return hasDriverIssue
? { type: 'warning', label: 'monitorPartial' }
: { type: 'success', label: 'monitorAvailable' };
}
if (driverChecks.value.some((check) => ['missing', 'unconfigured', 'failed'].includes(check.status))) {
return { type: 'warning', label: 'notReady' };
}
return { type: 'info', label: 'statusUnknown' };
});
const containerEnvironmentStatus = computed<{ type: 'success' | 'warning' | 'info'; label: string }>(() => {
const nvidiaDriver = driverChecks.value.find((check) => check.vendor === 'nvidia');
if (nvidiaDriver && nvidiaDriver.status !== 'passed') {
return nvidiaDriver.status === 'unknown'
? { type: 'info', label: 'statusUnknown' }
: { type: 'warning', label: 'notReady' };
}
if (containerChecks.value.every((check) => check.status === 'passed')) {
return { type: 'success', label: 'ready' };
}
if (containerChecks.value.some((check) => ['missing', 'unconfigured', 'failed'].includes(check.status))) {
return { type: 'warning', label: 'notReady' };
}
return { type: 'info', label: 'statusUnknown' };
});
const environmentGuidance = computed(() => {
const issue =
driverChecks.value.find((check) => check.status !== 'passed') ||
containerChecks.value.find((check) => check.item === 'toolkit' && check.status !== 'passed') ||
containerChecks.value.find((check) => check.status !== 'passed');
if (!issue) return null;
if (issue.status === 'unknown' || (issue.item === 'driver' && !issue.vendor)) {
return { item: issue.item, title: 'unknown', description: 'checkDetailsHelp', action: 'details' };
}
if (issue.item === 'driver') {
return { item: issue.item, title: 'driverActionTitle', description: 'driverActionHelp', action: 'driverGuide' };
}
if (issue.item === 'toolkit') {
return {
item: issue.item,
title: 'toolkitActionTitle',
description: 'toolkitActionHelp',
action: 'toolkitGuide',
};
}
return { item: issue.item, title: 'dockerActionTitle', description: 'dockerActionHelp', action: 'docker' };
});
const emptyDescription = computed(() => {
if (!gpuInfo.value.environment) return i18n.global.t('aiTools.gpu.gpuHelper');
return gpuInfo.value.environment.hardwareStatus === 'not_detected'
? i18n.global.t('aiTools.gpu.environment.notDetected')
: i18n.global.t('aiTools.gpu.environment.unknown');
});
const checkStatus = (check: AI.GPUEnvironmentCheck) => {
const status = check.status[0].toUpperCase() + check.status.slice(1);
return i18n.global.t(`aiTools.gpu.environment.status${status}`);
};
const checkTagType = (check: AI.GPUEnvironmentCheck) => {
if (check.status === 'passed') return 'success';
return check.status === 'failed' ? 'danger' : 'warning';
};
const checkTitle = (check: AI.GPUEnvironmentCheck) => {
if (check.vendor === 'nvidia' && check.item === 'driver') {
return i18n.global.t('aiTools.gpu.environment.nvidiaDriver');
}
const title = i18n.global.t(`aiTools.gpu.environment.${check.item}`);
return check.vendor && check.item === 'driver' ? `${vendorTitle(check.vendor)} · ${title}` : title;
};
const checkDescription = (check: AI.GPUEnvironmentCheck) => {
const managementTools: Record<string, string> = {
nvidia: 'nvidia-smi',
intel: 'xpu-smi',
ascend: 'npu-smi',
amd: 'amd-smi',
};
const tool = managementTools[check.vendor];
let key: string;
if (check.item === 'driver') {
if (!tool) return i18n.global.t('aiTools.gpu.environment.unknown');
key =
check.status === 'missing' || check.status === 'unconfigured'
? 'driverMissing'
: check.status === 'failed'
? 'driverFailed'
: 'driverUnknown';
} else if (check.item === 'toolkit') {
key = check.status === 'missing' || check.status === 'unconfigured' ? 'toolkitMissing' : 'toolkitFailed';
} else {
const configKeys = [
'dockerConfigMissing',
'dockerConfigInvalid',
'dockerConfigPending',
'dockerConfigUnreadable',
];
const code = check.code || '';
key = configKeys.includes(code)
? code
: check.status === 'missing' || check.status === 'unconfigured'
? 'dockerUnconfigured'
: 'dockerFailed';
}
const description = i18n.global.t(`aiTools.gpu.environment.${key}`, {
tool: tool || '',
vendor: vendorTitle(check.vendor),
});
return check.item === 'driver' && key !== 'driverMissing' ? `${checkTitle(check)}: ${description}` : description;
};
const summaryMemoryTooltip = (summary: { sharedMemoryOnly: boolean; memoryPartial: boolean }) =>
[
summary.sharedMemoryOnly && i18n.global.t('aiTools.gpu.systemMemoryHelper'),
summary.memoryPartial && i18n.global.t('aiTools.gpu.environment.partialMemory'),
]
.filter(Boolean)
.join(' ');
const normalizeAcceleratorInfo = (data: AI.Info): AI.Info => { const normalizeAcceleratorInfo = (data: AI.Info): AI.Info => {
const devices = (data.gpu || []) as AcceleratorDevice[]; const devices = (data.gpu || []) as AcceleratorDevice[];
const legacyNPUs = devices const legacyNPUs = devices
@@ -495,22 +907,37 @@ const applyAcceleratorInfo = (data: AI.Info) => {
}; };
}; };
const search = async () => { let requestID = 0;
const search = async (refreshEnvironment = false) => {
if (route.path !== pagePath) return;
if (loading.value) return; if (loading.value) return;
const id = ++requestID;
const node = currentNode.value;
loading.value = true; loading.value = true;
loadFailed.value = false;
try { try {
const res = await loadGPUInfo(); const res = await loadGPUInfo(refreshEnvironment, node);
if (id !== requestID || node !== currentNode.value) return;
applyAcceleratorInfo(res.data); applyAcceleratorInfo(res.data);
if (res.data.warnings?.length) {
MsgWarning(res.data.warnings.join('; '));
}
} catch { } catch {
if (id === requestID && node === currentNode.value) loadFailed.value = true;
} finally { } finally {
loading.value = false; if (id === requestID) loading.value = false;
} }
}; };
const refresh = search; const refresh = () => search(true);
watch(currentNode, () => {
environmentDetails.value = [];
requestID++;
loading.value = false;
loadFailed.value = false;
applyAcceleratorInfo(emptyGPUInfo());
detailDeviceKey.value = '';
processDrawerVisible.value = false;
search(true);
});
const visibleSections = (sections: DetailSection[]) => { const visibleSections = (sections: DetailSection[]) => {
return sections return sections
@@ -765,6 +1192,7 @@ const stableDeviceID = (vendor: string, identifier: string | undefined, fallback
}; };
const openHistory = (item: AcceleratorDevice | AI.XpuInfo['xpu'][number]) => { const openHistory = (item: AcceleratorDevice | AI.XpuInfo['xpu'][number]) => {
if (historyDisabled.value) return;
let deviceID: string; let deviceID: string;
if ('basic' in item) { if ('basic' in item) {
deviceID = stableDeviceID( deviceID = stableDeviceID(
@@ -919,9 +1347,104 @@ const loadEcc = (val: string) => {
onMounted(() => { onMounted(() => {
search(); search();
}); });
onActivated(() => {
search();
});
const invalidateRequest = () => {
requestID++;
loading.value = false;
};
onDeactivated(invalidateRequest);
onBeforeUnmount(invalidateRequest);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.environment-heading {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.environment-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
padding: 16px 0;
border-top: 1px solid var(--el-border-color-lighter);
> .el-tag {
flex-shrink: 0;
}
}
.environment-identity {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
> .el-icon {
flex-shrink: 0;
color: var(--el-text-color-secondary);
}
strong {
color: var(--el-text-color-primary);
font-weight: 600;
}
}
.environment-description {
display: flex;
flex-wrap: wrap;
gap: 4px 16px;
margin-top: 5px;
color: var(--el-text-color-regular);
font-size: 12px;
overflow-wrap: anywhere;
}
.environment-guidance {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px;
margin: 12px 0 16px;
border-radius: 6px;
background: var(--el-color-warning-light-9);
strong {
color: var(--el-color-warning-dark-2);
}
p {
margin: 6px 0 0;
color: var(--el-text-color-regular);
font-size: 12px;
line-height: 1.7;
}
> .el-button {
flex-shrink: 0;
max-width: 100%;
height: auto;
min-height: 32px;
white-space: normal;
text-align: center;
}
}
.environment-details {
margin-bottom: 12px;
}
.environment-check-description {
margin: 6px 0;
color: var(--el-text-color-regular);
line-height: 1.7;
}
@media (max-width: 767px) {
.environment-guidance {
align-items: flex-start;
flex-direction: column;
}
}
.device-overview { .device-overview {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -954,16 +1477,6 @@ onMounted(() => {
line-height: 18px; line-height: 18px;
} }
} }
.overview-count {
margin-left: 16px;
padding-left: 16px;
border-left: 1px solid var(--el-border-color-lighter);
color: var(--el-color-primary);
font-size: 12px;
font-weight: 600;
line-height: 18px;
white-space: nowrap;
}
.gpu-group-list { .gpu-group-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1168,9 +1681,6 @@ onMounted(() => {
gap: 8px 0; gap: 8px 0;
padding: 10px 12px; padding: 10px 12px;
} }
.overview-count {
width: auto;
}
.device-metrics { .device-metrics {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
+13 -12
View File
@@ -1,7 +1,7 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<RouterMenu /> <RouterMenu />
<template v-if="supported"> <template v-if="supported && options.length">
<el-card class="history-toolbar"> <el-card class="history-toolbar">
<div class="flex flex-wrap gap-3 items-center"> <div class="flex flex-wrap gap-3 items-center">
<el-select style="max-width: 100%" class="p-w-400" v-model="selectedDevice" @change="search"> <el-select style="max-width: 100%" class="p-w-400" v-model="selectedDevice" @change="search">
@@ -26,7 +26,10 @@
:clearable="false" :clearable="false"
@change="changeTimeRange" @change="changeTimeRange"
/> />
<TableRefresh @search="refresh" /> <TableSetting title="gpu-refresh" @search="refresh" />
<el-tooltip :content="$t('aiTools.gpu.environment.recheck')">
<TableRefresh :disabled="loading" @search="refresh" />
</el-tooltip>
</div> </div>
</div> </div>
<div v-if="history?.sampleCount && history.bucketSeconds > 0" class="history-summary"> <div v-if="history?.sampleCount && history.bucketSeconds > 0" class="history-summary">
@@ -67,26 +70,23 @@
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
<LayoutContent v-else-if="!loading" :title="$t('aiTools.gpu.monitoringTitle')" :divider="true"> <LayoutContent v-else :title="$t('aiTools.gpu.monitoringTitle')">
<template #rightToolBar> <template #rightToolBar>
<TableRefresh @search="refresh" /> <TableSetting title="gpu-refresh" @search="refresh" />
<el-tooltip :content="$t('aiTools.gpu.environment.recheck')">
<TableRefresh :disabled="loading" @search="refresh" />
</el-tooltip>
</template> </template>
<template #main> <template #main>
<el-alert <el-alert
v-if="failed" v-if="failed"
class="common-prompt"
:title="$t('aiTools.gpu.historyLoadFailed')" :title="$t('aiTools.gpu.historyLoadFailed')"
type="error" type="error"
:closable="false" :closable="false"
show-icon show-icon
/> />
<div v-else class="app-warn"> <el-empty v-else-if="!loading" :description="$t('aiTools.gpu.environment.notDetected')" />
<div class="flx-center">
<span>{{ $t('aiTools.gpu.gpuHelper') }}</span>
</div>
<div>
<img src="@/assets/images/no_app.svg" />
</div>
</div>
</template> </template>
</LayoutContent> </LayoutContent>
</div> </div>
@@ -371,6 +371,7 @@ const loadOptions = async () => {
} }
}; };
const refresh = () => { const refresh = () => {
if (loading.value) return;
if (followNow) timeRangeGlobal.value = [timeRangeGlobal.value[0], new Date()]; if (followNow) timeRangeGlobal.value = [timeRangeGlobal.value[0], new Date()];
void loadOptions(); void loadOptions();
}; };
+11 -2
View File
@@ -9,8 +9,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import i18n from '@/lang'; import i18n from '@/lang';
import { computed } from 'vue';
const buttons = [ const props = defineProps({
historyDisabled: {
type: Boolean,
default: false,
},
});
const buttons = computed(() => [
{ {
label: i18n.global.t('aiTools.gpu.current'), label: i18n.global.t('aiTools.gpu.current'),
path: '/ai/gpu/current', path: '/ai/gpu/current',
@@ -18,6 +26,7 @@ const buttons = [
{ {
label: i18n.global.t('aiTools.gpu.history'), label: i18n.global.t('aiTools.gpu.history'),
path: '/ai/gpu/history', path: '/ai/gpu/history',
disabled: props.historyDisabled,
}, },
]; ]);
</script> </script>
+1 -6
View File
@@ -16,12 +16,7 @@
<TableRefresh @search="search()" /> <TableRefresh @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
-1
View File
@@ -53,7 +53,6 @@
<ComplexTable <ComplexTable
:data="connections" :data="connections"
row-key="id" row-key="id"
:cache-pagination="false"
:pagination-config="pagination" :pagination-config="pagination"
:columns="accountColumns" :columns="accountColumns"
local-key="modelServiceColumn" local-key="modelServiceColumn"
@@ -50,7 +50,6 @@
v-else-if="modelInstalled" v-else-if="modelInstalled"
:selection-context="() => [searchName]" :selection-context="() => [searchName]"
row-key="id" row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
@sort-change="search" @sort-change="search"
@@ -85,7 +85,6 @@
<template #main> <template #main>
<ComplexTable <ComplexTable
:selection-context="() => [searchName, activeTag, includeAppStore, props.filters]" :selection-context="() => [searchName, activeTag, includeAppStore, props.filters]"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:view-mode="viewMode" v-model:view-mode="viewMode"
v-model:selects="selects" v-model:selects="selects"
@@ -36,7 +36,6 @@
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:data="data" :data="data"
@sort-change="search" @sort-change="search"
@@ -28,7 +28,6 @@
<ComplexTable <ComplexTable
:selection-context="() => [searchName]" :selection-context="() => [searchName]"
row-key="id" row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:data="data" :data="data"
@@ -20,7 +20,6 @@
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:data="data" :data="data"
@@ -40,7 +40,6 @@
<ComplexTable <ComplexTable
:selection-context="() => [searchName]" :selection-context="() => [searchName]"
row-key="id" row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:data="data" :data="data"
@@ -28,7 +28,6 @@
<ComplexTable <ComplexTable
:selection-context="() => [searchName]" :selection-context="() => [searchName]"
row-key="name" row-key="name"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:data="data" :data="data"
@@ -55,7 +55,6 @@
<ComplexTable <ComplexTable
:selection-context="() => [searchName, searchGroupID]" :selection-context="() => [searchName, searchGroupID]"
row-key="id" row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:default-sort=" :default-sort="
paginationConfig.order !== 'null' paginationConfig.order !== 'null'
@@ -70,7 +70,6 @@
:selection-context="() => [searchInfo, group]" :selection-context="() => [searchInfo, group]"
row-key="id" row-key="id"
v-model:selects="selects" v-model:selects="selects"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:data="data" :data="data"
@search="search" @search="search"
@@ -106,7 +106,6 @@
<ComplexTable <ComplexTable
v-if="currentDB" v-if="currentDB"
:class="{ mask: maskShow }" :class="{ mask: maskShow }"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
@sort-change="search" @sort-change="search"
@search="search" @search="search"
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" /> <TableSearch @search="search()" v-model:searchName="searchName" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable /> <el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column :label="$t('database.version')" prop="version" /> <el-table-column :label="$t('database.version')" prop="version" />
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" /> <el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
@@ -118,7 +118,6 @@
<template #main> <template #main>
<ComplexTable <ComplexTable
v-if="currentDB" v-if="currentDB"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:class="{ mask: maskShow }" :class="{ mask: maskShow }"
@sort-change="search" @sort-change="search"
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" /> <TableSearch @search="search()" v-model:searchName="searchName" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable /> <el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" /> <el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
<el-table-column :label="$t('commons.login.username')" prop="username" /> <el-table-column :label="$t('commons.login.username')" prop="username" />
@@ -93,7 +93,6 @@
<ComplexTable <ComplexTable
v-if="currentDB" v-if="currentDB"
:class="{ mask: maskShow }" :class="{ mask: maskShow }"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
@sort-change="search" @sort-change="search"
@search="search" @search="search"
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" /> <TableSearch @search="search()" v-model:searchName="searchName" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable /> <el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" /> <el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
<el-table-column :label="$t('commons.login.username')" prop="username" /> <el-table-column :label="$t('commons.login.username')" prop="username" />
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" /> <TableSearch @search="search()" v-model:searchName="searchName" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable /> <el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" /> <el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
<el-table-column :label="$t('commons.login.password')" prop="password"> <el-table-column :label="$t('commons.login.password')" prop="password">
+242 -4
View File
@@ -99,6 +99,7 @@
> >
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button> <el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button> <el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
<el-radio-button v-if="hasVllm" value="vllm">vLLM</el-radio-button>
</el-radio-group> </el-radio-group>
<el-select <el-select
v-if="chartOption === 'network'" v-if="chartOption === 'network'"
@@ -128,6 +129,26 @@
:value="item" :value="item"
/> />
</el-select> </el-select>
<el-select
v-if="chartOption === 'vllm'"
v-model="vllmInstallID"
class="p-w-200 float-right"
:placeholder="$t('aiTools.model.deployments')"
@visible-change="(visible) => visible && loadVllmOptions()"
>
<el-option
v-for="item in vllmOptions"
:key="item.appInstallId"
:label="item.name + (item.servedModelName ? ' · ' + item.servedModelName : '')"
:value="item.appInstallId"
:disabled="item.status !== 'Running'"
>
<span>
{{ item.name }}{{ item.servedModelName ? ' · ' + item.servedModelName : '' }}
</span>
<Status class="ml-2" :status="item.status" />
</el-option>
</el-select>
</template> </template>
<template #body> <template #body>
<div class="monitor-chart-content"> <div class="monitor-chart-content">
@@ -169,6 +190,47 @@
:dataZoom="true" :dataZoom="true"
/> />
</div> </div>
<template v-if="chartOption === 'vllm'">
<div class="monitor-tags" :style="monitorTagsStyle">
<el-tag>
{{ $t('aiTools.vllmMonitor.promptThroughput') }}:
{{ formatVllmMetric(vllmCurrent?.promptThroughput) }} tokens/s
</el-tag>
<el-tag>
{{ $t('aiTools.vllmMonitor.generationThroughput') }}:
{{ formatVllmMetric(vllmCurrent?.generationThroughput) }} tokens/s
</el-tag>
<el-tag>
{{ $t('aiTools.vllmMonitor.processing') }}:
{{ formatVllmMetric(vllmCurrent?.running) }}
</el-tag>
<el-tag>
{{ $t('aiTools.vllmMonitor.queued') }}:
{{ formatVllmMetric(vllmCurrent?.waiting) }}
</el-tag>
</div>
<div class="mobile-monitor-chart">
<v-charts
height="383px"
id="vllmChart"
type="line"
:option="vllmChartOption"
:dataZoom="true"
/>
</div>
<div class="vllm-monitor-footer">
<span>{{ vllmMonitorMessage }}</span>
<el-button
v-if="selectedVllm"
link
type="primary"
@click="vllmMonitorRef?.acceptParams(selectedVllm)"
>
{{ $t('commons.button.view') }}
</el-button>
</div>
<VllmMonitor v-if="selectedVllm" ref="vllmMonitorRef" />
</template>
</div> </div>
</template> </template>
</CardWithHeader> </CardWithHeader>
@@ -443,7 +505,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onBeforeUnmount, ref, reactive, computed, nextTick } from 'vue'; import { onMounted, onBeforeUnmount, ref, reactive, computed, nextTick, watch, defineAsyncComponent } from 'vue';
import SystemStatus from '@/views/home/status/index.vue'; import SystemStatus from '@/views/home/status/index.vue';
import AppLauncher from '@/views/home/app/index.vue'; import AppLauncher from '@/views/home/app/index.vue';
import VCharts from '@/components/v-charts/index.vue'; import VCharts from '@/components/v-charts/index.vue';
@@ -478,6 +540,9 @@ import {
} from '@/utils/dashboardCache'; } from '@/utils/dashboardCache';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { useCan } from '@/composables/useMenuManagePermission'; import { useCan } from '@/composables/useMenuManagePermission';
import { readPages } from '@/utils/pagination';
import { loadExtensionComponent } from '@/extensions/optional';
import type { ReqPage, ResPage, ResultData } from '@/api/interface';
const router = useRouter(); const router = useRouter();
import { useGlobalStore } from '@/composables/useGlobalStore'; import { useGlobalStore } from '@/composables/useGlobalStore';
const { const {
@@ -489,8 +554,146 @@ const {
hasNewVersion, hasNewVersion,
isAdminOrNodeAdmin, isAdminOrNodeAdmin,
isXpackOrEE, isXpackOrEE,
isProductPro,
currentNode,
} = useGlobalStore(); } = useGlobalStore();
interface VllmDeployment {
appInstallId: number;
name: string;
servedModelName: string;
status: string;
}
interface VllmSnapshot {
appInstallID: number;
createdAt: string;
status: string;
intervalSeconds: number;
promptThroughput: number | null;
generationThroughput: number | null;
running: number | null;
waiting: number | null;
metrics?: Record<string, Record<string, number>>;
}
const vllmModules = import.meta.glob<{
pageVllm: (req: ReqPage & { info: string }) => Promise<ResultData<ResPage<VllmDeployment>>>;
loadVllmCurrent: (id: number, signal: AbortSignal) => Promise<ResultData<VllmSnapshot>>;
}>('@/xpack/api/modules/vllm.ts', { eager: true });
const vllmMetricsModules = import.meta.glob<{
calculateCurrentMetrics: (snapshot: VllmSnapshot, previous?: VllmSnapshot) => VllmSnapshot;
}>('@/xpack/views/vllm/monitor/current.ts', { eager: true });
const vllmAPI = Object.values(vllmModules)[0];
const vllmMetrics = Object.values(vllmMetricsModules)[0];
const VllmMonitor = defineAsyncComponent(() => loadExtensionComponent('/vllm/monitor/index.vue'));
const vllmMonitorRef = ref();
const canViewVllm = useCan('ai_model_view');
const vllmOptions = ref<VllmDeployment[]>([]);
const vllmInstallID = ref<number>();
const vllmCurrent = ref<VllmSnapshot>();
const vllmLoadFailed = ref(false);
const vllmPoints = ref<Array<{ time: string; prompt: number | null; generation: number | null }>>([]);
let vllmTimer: ReturnType<typeof setTimeout>;
let vllmController: AbortController;
let vllmOptionsVersion = 0;
const hasVllm = computed(() => isProductPro.value && canViewVllm.value && vllmOptions.value.length > 0);
const selectedVllm = computed(() => vllmOptions.value.find((item) => item.appInstallId === vllmInstallID.value));
const formatVllmMetric = (value?: number | null) =>
value == null || !Number.isFinite(value) ? '--' : Number(value.toFixed(2)).toString();
const vllmMonitorMessage = computed(() => {
if (!selectedVllm.value) return i18n.global.t('aiTools.model.readinessNotRunning');
if (vllmLoadFailed.value) return i18n.global.t('aiTools.vllmMonitor.loadFailed');
if (vllmCurrent.value?.status === 'unavailable') return i18n.global.t('aiTools.vllmMonitor.unavailable');
if (vllmCurrent.value?.status === 'no_data') return i18n.global.t('aiTools.vllmMonitor.noMetrics');
if (!vllmCurrent.value?.intervalSeconds) return i18n.global.t('commons.table.statusWaiting');
return `${i18n.global.t('aiTools.vllmMonitor.sampleWindow')}: ${formatVllmMetric(vllmCurrent.value.intervalSeconds)} s`;
});
const vllmChartOption = computed(() => ({
xData: loadMonitorChartTime(vllmPoints.value.map((point) => point.time)),
yData: [
{
name: i18n.global.t('aiTools.vllmMonitor.promptThroughput'),
data: loadMonitorChartData(vllmPoints.value.map((point) => point.prompt)),
},
{
name: i18n.global.t('aiTools.vllmMonitor.generationThroughput'),
data: loadMonitorChartData(vllmPoints.value.map((point) => point.generation)),
},
],
grid: monitorChartGrid,
legend: monitorChartLegend,
formatStr: 'tokens/s',
yAxis: [
{
type: 'value',
name: '( tokens/s )',
min: 0,
max: ({ max }: { max: number }) => (max > 0 ? undefined : 1),
},
],
tooltip: {
trigger: 'axis',
valueFormatter: (value: number | null) => `${formatVllmMetric(value)} tokens/s`,
},
}));
const loadVllmOptions = async () => {
if (!isProductPro.value || !canViewVllm.value || !vllmAPI || !vllmMetrics) return;
const version = ++vllmOptionsVersion;
const node = currentNode.value;
try {
const items = await readPages((page) => {
if (version !== vllmOptionsVersion || node !== currentNode.value) throw new Error('node changed');
return vllmAPI.pageVllm({ ...page, info: '' });
});
if (version !== vllmOptionsVersion || node !== currentNode.value) return;
vllmOptions.value = items;
if (!items.some((item) => item.appInstallId === vllmInstallID.value && item.status === 'Running')) {
vllmInstallID.value = items.find((item) => item.status === 'Running')?.appInstallId;
}
if (!items.length && chartOption.value === 'vllm') chartOption.value = 'network';
} catch {
if (version === vllmOptionsVersion) vllmLoadFailed.value = true;
}
};
const stopVllmMonitor = (resetData = true) => {
clearTimeout(vllmTimer);
vllmController?.abort();
if (resetData) {
vllmCurrent.value = undefined;
vllmPoints.value = [];
vllmLoadFailed.value = false;
}
};
const refreshVllmMonitor = async () => {
const controller = new AbortController();
vllmController = controller;
try {
const { data } = await vllmAPI.loadVllmCurrent(vllmInstallID.value, controller.signal);
if (controller.signal.aborted) return;
const wasUnavailable = vllmCurrent.value?.status === 'unavailable';
vllmCurrent.value = vllmMetrics.calculateCurrentMetrics(data, vllmCurrent.value);
vllmLoadFailed.value = false;
vllmPoints.value.push({
time: dateFormatForSecond(new Date(data.createdAt)),
prompt: vllmCurrent.value.promptThroughput,
generation: vllmCurrent.value.generationThroughput,
});
if (data.status === 'unavailable' && !wasUnavailable) void loadVllmOptions();
} catch {
if (controller.signal.aborted) return;
vllmCurrent.value = undefined;
vllmLoadFailed.value = true;
vllmPoints.value.push({ time: dateFormatForSecond(new Date()), prompt: null, generation: null });
} finally {
if (!controller.signal.aborted) {
vllmPoints.value = vllmPoints.value.slice(-20);
vllmTimer = setTimeout(refreshVllmMonitor, 3000);
}
}
};
const DASHBOARD_CACHE_TTL = { const DASHBOARD_CACHE_TTL = {
safeStatus: 10 * 60 * 1000, safeStatus: 10 * 60 * 1000,
netOptions: 60 * 60 * 1000, netOptions: 60 * 60 * 1000,
@@ -505,7 +708,7 @@ const monitorTagsStyle = {
const monitorChartEmptyLength = 20; const monitorChartEmptyLength = 20;
const loadMonitorEmptyData = () => Array.from({ length: monitorChartEmptyLength }, () => null); const loadMonitorEmptyData = () => Array.from({ length: monitorChartEmptyLength }, () => null);
const loadMonitorEmptyTime = () => Array.from({ length: monitorChartEmptyLength }, () => ''); const loadMonitorEmptyTime = () => Array.from({ length: monitorChartEmptyLength }, () => '');
const loadMonitorChartData = (data: Array<number>) => (data.length === 0 ? loadMonitorEmptyData() : data); const loadMonitorChartData = (data: Array<number | null>) => (data.length === 0 ? loadMonitorEmptyData() : data);
const loadMonitorChartTime = (data: Array<string>) => (data.length === 0 ? loadMonitorEmptyTime() : data); const loadMonitorChartTime = (data: Array<string>) => (data.length === 0 ? loadMonitorEmptyTime() : data);
const loadIOChartOption = () => ({ const loadIOChartOption = () => ({
xData: loadMonitorChartTime(timeIODatas.value), xData: loadMonitorChartTime(timeIODatas.value),
@@ -718,6 +921,7 @@ const updateCurrentInfo = (data: Dashboard.CurrentInfo) => {
const changeOption = async () => { const changeOption = async () => {
isInit.value = true; isInit.value = true;
if (chartOption.value === 'vllm') void loadVllmOptions();
loadData(); loadData();
}; };
@@ -851,7 +1055,7 @@ const refreshDashboard = async () => {
clearDashboardCache(); clearDashboardCache();
onLoadBaseInfo(false, ''); onLoadBaseInfo(false, '');
hasRefreshedOptionsOnHover.value = false; hasRefreshedOptionsOnHover.value = false;
await Promise.allSettled([onLoadNetworkOptions(true), onLoadIOOptions(true), loadSettingInfo()]); await Promise.allSettled([onLoadNetworkOptions(true), onLoadIOOptions(true), loadSettingInfo(), loadVllmOptions()]);
MsgSuccess(i18n.global.t('commons.msg.refreshSuccess')); MsgSuccess(i18n.global.t('commons.msg.refreshSuccess'));
}; };
@@ -1028,7 +1232,7 @@ const saveMemo = async () => {
const loadData = async () => { const loadData = async () => {
if (chartOption.value === 'io') { if (chartOption.value === 'io') {
chartsOption.value['ioChart'] = loadIOChartOption(); chartsOption.value['ioChart'] = loadIOChartOption();
} else { } else if (chartOption.value === 'network') {
chartsOption.value['networkChart'] = loadNetworkChartOption(); chartsOption.value['networkChart'] = loadNetworkChartOption();
} }
}; };
@@ -1097,12 +1301,14 @@ const loadSource = (row: any) => {
const onFocus = () => { const onFocus = () => {
isActive.value = true; isActive.value = true;
skipNextCurrentInfoDelta.value = true; skipNextCurrentInfoDelta.value = true;
void loadVllmOptions();
}; };
const onBlur = () => { const onBlur = () => {
isActive.value = false; isActive.value = false;
}; };
const refreshOptionsOnHover = async () => { const refreshOptionsOnHover = async () => {
void loadVllmOptions();
if (hasRefreshedOptionsOnHover.value) return; if (hasRefreshedOptionsOnHover.value) return;
if (!netOptionsFromCache.value && !ioOptionsFromCache.value) return; if (!netOptionsFromCache.value && !ioOptionsFromCache.value) return;
hasRefreshedOptionsOnHover.value = true; hasRefreshedOptionsOnHover.value = true;
@@ -1155,7 +1361,29 @@ onMounted(() => {
loadMemo(); loadMemo();
}); });
watch(
[currentNode, isProductPro, canViewVllm],
() => {
vllmOptionsVersion++;
stopVllmMonitor();
vllmOptions.value = [];
vllmInstallID.value = undefined;
if (chartOption.value === 'vllm') chartOption.value = 'network';
void loadVllmOptions();
},
{ immediate: true, flush: 'sync' },
);
watch([chartOption, vllmInstallID, isActive], ([, installID], [, previousInstallID]) => {
stopVllmMonitor(installID !== previousInstallID);
if (chartOption.value === 'vllm' && hasVllm.value && vllmInstallID.value && isActive.value) {
void refreshVllmMonitor();
}
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
vllmOptionsVersion++;
stopVllmMonitor();
window.removeEventListener('focus', onFocus); window.removeEventListener('focus', onFocus);
window.removeEventListener('blur', onBlur); window.removeEventListener('blur', onBlur);
isCurrentActive.value = false; isCurrentActive.value = false;
@@ -1189,6 +1417,16 @@ onBeforeUnmount(() => {
margin-top: 40px; margin-top: 40px;
} }
.vllm-monitor-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
padding: 0 20px;
color: var(--el-text-color-secondary);
font-size: 12px;
}
@media only screen and (max-width: 767px) { @media only screen and (max-width: 767px) {
.dashboard-entrance-alert { .dashboard-entrance-alert {
padding-right: 40px; padding-right: 40px;
@@ -24,7 +24,7 @@
@keyup.enter="search" @keyup.enter="search"
@clear="search" @clear="search"
/> />
<el-button :disabled="loading" @click="search">{{ $t('commons.button.confirm') }}</el-button> <el-button :disabled="loading" @click="search">{{ $t('commons.button.search') }}</el-button>
</div> </div>
<div class="directory-row directory-header"> <div class="directory-row directory-header">
<span>{{ $t('file.path') }}</span> <span>{{ $t('file.path') }}</span>
@@ -506,7 +506,6 @@
<template #main> <template #main>
<ComplexTable <ComplexTable
class="file-table" class="file-table"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:ref="(el) => setTableRef(item.id, el)" :ref="(el) => setTableRef(item.id, el)"
@@ -48,7 +48,6 @@
<ComplexTable <ComplexTable
v-model:selects="selects" v-model:selects="selects"
:data="containerPageRows" :data="containerPageRows"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:heightDiff="320" :heightDiff="320"
row-key="key" row-key="key"
@@ -170,7 +169,6 @@
v-model:selects="orphanSelects" v-model:selects="orphanSelects"
v-loading="loading" v-loading="loading"
:data="orphanPageRows" :data="orphanPageRows"
:cache-pagination="false"
:pagination-config="orphanPaginationConfig" :pagination-config="orphanPaginationConfig"
row-key="policyUUID" row-key="policyUUID"
max-height="calc(100vh - 220px)" max-height="calc(100vh - 220px)"
@@ -66,7 +66,6 @@
row.interface, row.interface,
]) ])
" "
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
@search="search" @search="search"
@@ -109,7 +109,6 @@
<ComplexTable <ComplexTable
:selection-context="() => [searchName, selectedRuleFilters]" :selection-context="() => [searchName, selectedRuleFilters]"
v-model:selects="selects" v-model:selects="selects"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:data="allRows" :data="allRows"
:heightDiff="320 + noticeHeight" :heightDiff="320 + noticeHeight"
+1 -7
View File
@@ -26,13 +26,7 @@
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="400">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="400"
>
<el-table-column min-width="80" :label="$t('logs.loginIP')" prop="address" /> <el-table-column min-width="80" :label="$t('logs.loginIP')" prop="address" />
<el-table-column min-width="60" :label="$t('ssh.belong')" prop="area" /> <el-table-column min-width="60" :label="$t('ssh.belong')" prop="area" />
<el-table-column min-width="60" :label="$t('commons.table.port')" prop="port" /> <el-table-column min-width="60" :label="$t('commons.table.port')" prop="port" />
+1 -7
View File
@@ -57,13 +57,7 @@
<TableRefresh @search="resetAndLoadLogs" /> <TableRefresh @search="resetAndLoadLogs" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :data="logs" :pagination-config="paginationConfig" @row-click="openDetail" :heightDiff="320">
:data="logs"
:cache-pagination="false"
:pagination-config="paginationConfig"
@row-click="openDetail"
:heightDiff="320"
>
<el-table-column prop="time" :label="$t('commons.table.date')" width="220" show-overflow-tooltip /> <el-table-column prop="time" :label="$t('commons.table.date')" width="220" show-overflow-tooltip />
<el-table-column :label="$t('logs.priority')" width="120"> <el-table-column :label="$t('logs.priority')" width="120">
<template #default="{ row }"> <template #default="{ row }">
+1 -7
View File
@@ -21,13 +21,7 @@
<TableSetting title="login-log-refresh" @search="search()" /> <TableSetting title="login-log-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="330">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="330"
>
<el-table-column :label="$t('logs.loginIP')" prop="ip" /> <el-table-column :label="$t('logs.loginIP')" prop="ip" />
<el-table-column v-if="isEnterprise" :label="$t('commons.login.username')" prop="user" /> <el-table-column v-if="isEnterprise" :label="$t('commons.login.username')" prop="user" />
<el-table-column :label="$t('logs.loginAddress')" prop="address" /> <el-table-column :label="$t('logs.loginAddress')" prop="address" />
+1 -7
View File
@@ -78,13 +78,7 @@
<TableSetting title="operation-log-refresh" @search="search()" /> <TableSetting title="operation-log-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="330">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="330"
>
<el-table-column :label="$t('logs.resource')" prop="group" fix> <el-table-column :label="$t('logs.resource')" prop="group" fix>
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.source && row.source.indexOf('-') === -1"> <span v-if="row.source && row.source.indexOf('-') === -1">
+1 -7
View File
@@ -16,13 +16,7 @@
<TableSetting title="task-log-refresh" @search="search()" /> <TableSetting title="task-log-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="330">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="330"
>
<el-table-column :label="$t('logs.taskName')" prop="name" min-width="180px"> <el-table-column :label="$t('logs.taskName')" prop="name" min-width="180px">
<template #default="{ row }"> <template #default="{ row }">
{{ translateTaskText(row.name) }} {{ translateTaskText(row.name) }}
@@ -75,7 +75,6 @@
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:data="data" :data="data"
:height-diff="380" :height-diff="380"
@@ -14,12 +14,7 @@
</div> </div>
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="data" @search="search()">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search()"
>
<el-table-column :label="$t('xpack.alert.alertMsg')" prop="message" show-overflow-tooltip> <el-table-column :label="$t('xpack.alert.alertMsg')" prop="message" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
{{ formatMessage(row.alertDetail) }} {{ formatMessage(row.alertDetail) }}
@@ -65,7 +65,6 @@
</el-alert> </el-alert>
<ComplexTable <ComplexTable
class="mt-3" class="mt-3"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:data="allConfigs" :data="allConfigs"
:empty-text="$t('commons.msg.noneData')" :empty-text="$t('commons.msg.noneData')"
@@ -28,7 +28,6 @@
</template> </template>
</el-alert> </el-alert>
<ComplexTable <ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
@sort-change="search" @sort-change="search"
@search="search" @search="search"
+1 -7
View File
@@ -14,13 +14,7 @@
<TableSetting title="backup-account-refresh" @search="search()" /> <TableSetting title="backup-account-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<el-table-column <el-table-column
:label="$t('license.authorizationId')" :label="$t('license.authorizationId')"
:min-width="80" :min-width="80"
@@ -21,7 +21,6 @@
<ComplexTable <ComplexTable
:selection-context="() => [searchName]" :selection-context="() => [searchName]"
row-key="id" row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:data="data" :data="data"
@@ -34,7 +34,6 @@
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:data="data" :data="data"
@@ -115,11 +114,11 @@ const paginationConfig = reactive({
order: 'ascending', order: 'ascending',
}); });
const info = ref(); const info = ref();
useSearchState('terminal/command/index', { useSearchState(
search: info, 'terminal/command/index',
page: toRef(paginationConfig, 'currentPage'), { search: info, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
pageSize: toRef(paginationConfig, 'pageSize'), { onRevisit: () => search() },
}); );
const group = ref<string>(''); const group = ref<string>('');
const dialogRef = ref(); const dialogRef = ref();
const opRef = ref(); const opRef = ref();
+5 -6
View File
@@ -23,7 +23,6 @@
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
:data="data" :data="data"
@@ -91,11 +90,11 @@ const paginationConfig = reactive({
total: 0, total: 0,
}); });
const info = ref(); const info = ref();
useSearchState('terminal/host/index', { useSearchState(
search: info, 'terminal/host/index',
page: toRef(paginationConfig, 'currentPage'), { search: info, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
pageSize: toRef(paginationConfig, 'pageSize'), { onRevisit: () => search() },
}); );
const group = ref<string>(''); const group = ref<string>('');
const opRef = ref(); const opRef = ref();
+58 -28
View File
@@ -1,20 +1,35 @@
<template> <template>
<CachedTabs <div>
:items="tabs" <el-card class="router_card">
default-value="terminal" <el-radio-group v-model="activeNames" @change="handleChange">
cache-key="terminal/main" <el-radio-button class="router_card_button" size="large" value="terminal">
:requested-value="route.path === '/terminal' && route.query.path ? 'terminal' : undefined" {{ $t('menu.terminal', 2) }}
@activate="handleChange" </el-radio-button>
> <el-radio-button v-if="isAdmin" class="router_card_button" size="large" value="host">
<template #default="{ active }"> {{ $t('terminal.host', 2) }}
<div v-show="active === 'terminal'"> </el-radio-button>
<TerminalTab ref="terminalTabRef" /> <el-radio-button class="router_card_button" size="large" value="command">
</div> {{ $t('terminal.quickCommand', 2) }}
<HostTab v-if="isAdmin && active === 'host'" ref="hostTabRef" /> </el-radio-button>
<CommandTab v-if="active === 'command'" ref="commandTabRef" /> <el-radio-button v-if="isAdmin" class="router_card_button" size="large" value="setting">
<SettingTab v-if="isAdmin && active === 'setting'" ref="settingTabRef" /> {{ $t('container.setting') }}
</template> </el-radio-button>
</CachedTabs> </el-radio-group>
</el-card>
<div v-show="activeNames === 'terminal'">
<TerminalTab ref="terminalTabRef" />
</div>
<div v-if="isAdmin && activeNames === 'host'">
<HostTab ref="hostTabRef" />
</div>
<div v-if="activeNames === 'command'">
<CommandTab ref="commandTabRef" />
</div>
<div v-if="isAdmin && activeNames === 'setting'">
<SettingTab ref="settingTabRef" />
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -22,29 +37,20 @@ import HostTab from '@/views/terminal/host/index.vue';
import CommandTab from '@/views/terminal/command/index.vue'; import CommandTab from '@/views/terminal/command/index.vue';
import TerminalTab from '@/views/terminal/terminal/index.vue'; import TerminalTab from '@/views/terminal/terminal/index.vue';
import SettingTab from '@/views/terminal/setting/index.vue'; import SettingTab from '@/views/terminal/setting/index.vue';
import { computed, onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import CachedTabs from '@/components/cached-tabs/index.vue';
import i18n from '@/lang';
import { getTerminalInfo } from '@/api/modules/setting'; import { getTerminalInfo } from '@/api/modules/setting';
import { TerminalStore } from '@/store'; import { TerminalStore } from '@/store';
import { useGlobalStore } from '@/composables/useGlobalStore'; import { useGlobalStore } from '@/composables/useGlobalStore';
const terminalStore = TerminalStore(); const terminalStore = TerminalStore();
const { isAdmin } = useGlobalStore(); const { isAdmin } = useGlobalStore();
const route = useRoute(); const activeNames = ref<string>('terminal');
const tabs = computed(() => [
{ value: 'terminal', label: i18n.global.t('menu.terminal', 2) },
...(isAdmin.value ? [{ value: 'host', label: i18n.global.t('terminal.host', 2) }] : []),
{ value: 'command', label: i18n.global.t('terminal.quickCommand', 2) },
...(isAdmin.value ? [{ value: 'setting', label: i18n.global.t('container.setting') }] : []),
]);
const hostTabRef = ref(); const hostTabRef = ref();
const commandTabRef = ref(); const commandTabRef = ref();
const terminalTabRef = ref(); const terminalTabRef = ref();
const settingTabRef = ref(); const settingTabRef = ref();
const handleChange = (tab: string) => { const handleChange = (tab: any) => {
if (tab === 'host' && isAdmin.value) { if (tab === 'host' && isAdmin.value) {
hostTabRef.value?.acceptParams(); hostTabRef.value?.acceptParams();
} }
@@ -79,5 +85,29 @@ const loadTerminalSetting = async () => {
onMounted(() => { onMounted(() => {
loadTerminalSetting(); loadTerminalSetting();
handleChange('terminal');
}); });
</script> </script>
<style lang="scss" scoped>
.router_card {
--el-card-padding: 0;
}
.router_card_button {
:deep(.el-radio-button__inner) {
min-width: 100px;
height: 100%;
background-color: var(--panel-button-active) !important;
box-shadow: none !important;
outline: none !important;
border: 2px solid transparent !important;
}
:deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
color: $primary-color;
border-color: $primary-color !important;
border-radius: 4px;
}
}
</style>
@@ -53,7 +53,6 @@
row-key="id" row-key="id"
:class="{ mask: !clamStatus.isRunning }" :class="{ mask: !clamStatus.isRunning }"
v-if="!isSettingShow" v-if="!isSettingShow"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:scrollbar-always-on="isMobile" :scrollbar-always-on="isMobile"
v-model:selects="selects" v-model:selects="selects"
-1
View File
@@ -68,7 +68,6 @@
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
v-model:selects="selects" v-model:selects="selects"
@sort-change="search" @sort-change="search"
@@ -13,13 +13,7 @@
<TableSetting title="dotnet-runtime-refresh" @search="search()" /> <TableSetting title="dotnet-runtime-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="260">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
:heightDiff="260"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
@@ -13,13 +13,7 @@
<TableSetting title="go-runtime-refresh" @search="search()" /> <TableSetting title="go-runtime-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="260">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
:heightDiff="260"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
@@ -13,13 +13,7 @@
<TableSetting title="java-runtime-refresh" @search="search()" /> <TableSetting title="java-runtime-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="260">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
:heightDiff="260"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
@@ -13,13 +13,7 @@
<TableSetting title="node-runtime-refresh" @search="search()" /> <TableSetting title="node-runtime-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="260">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
:heightDiff="260"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
@@ -21,13 +21,7 @@
<TableSetting title="php-runtime-refresh" @search="search()" /> <TableSetting title="php-runtime-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="260">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
:heightDiff="260"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
@@ -13,13 +13,7 @@
<TableSetting title="python-runtime-refresh" @search="search()" /> <TableSetting title="python-runtime-refresh" @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="260">
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
:heightDiff="260"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
@@ -11,13 +11,7 @@
<el-alert :title="$t('ssl.acmeHelper')" type="info" :closable="false" /> <el-alert :title="$t('ssl.acmeHelper')" type="info" :closable="false" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
:data="data"
:cache-pagination="false"
:pagination-config="paginationConfig"
@search="search()"
v-loading="loading"
>
<el-table-column <el-table-column
:label="$t('website.email')" :label="$t('website.email')"
fix fix
+1 -7
View File
@@ -11,13 +11,7 @@
</el-button> </el-button>
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
:data="data"
:cache-pagination="false"
:pagination-config="paginationConfig"
@search="search()"
v-loading="loading"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
show-overflow-tooltip show-overflow-tooltip
@@ -8,12 +8,7 @@
</el-button> </el-button>
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()">
:data="data"
:cache-pagination="false"
:pagination-config="paginationConfig"
@search="search()"
>
<el-table-column <el-table-column
:label="$t('commons.table.name')" :label="$t('commons.table.name')"
fix fix
+1 -7
View File
@@ -23,7 +23,7 @@
</el-button> </el-button>
</template> </template>
<template #rightToolBar> <template #rightToolBar>
<TableSearch @search="searchByDomain" v-model:searchName="req.domain" /> <TableSearch @search="search()" v-model:searchName="req.domain" />
<TableRefresh @search="search()" /> <TableRefresh @search="search()" />
<fu-table-column-select <fu-table-column-select
:columns="columns" :columns="columns"
@@ -36,7 +36,6 @@
<template #main> <template #main>
<ComplexTable <ComplexTable
:data="data" :data="data"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
@search="search()" @search="search()"
v-model:selects="selects" v-model:selects="selects"
@@ -435,11 +434,6 @@ const search = () => {
}); });
}; };
const searchByDomain = () => {
paginationConfig.currentPage = 1;
search();
};
const updateDesc = (row: Website.SSLDTO, bulr: Function) => { const updateDesc = (row: Website.SSLDTO, bulr: Function) => {
bulr(); bulr();
if (row.description && row.description.length > 128) { if (row.description && row.description.length > 128) {
@@ -15,13 +15,7 @@
<TableRefresh @search="search()" /> <TableRefresh @search="search()" />
</template> </template>
<template #main> <template #main>
<ComplexTable <ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
:data="data"
:cache-pagination="false"
:pagination-config="paginationConfig"
@search="search()"
v-loading="loading"
>
<el-table-column :label="$t('template.name')" prop="name" min-width="120px" show-overflow-tooltip /> <el-table-column :label="$t('template.name')" prop="name" min-width="120px" show-overflow-tooltip />
<el-table-column :label="$t('template.type')" prop="type" width="150px"> <el-table-column :label="$t('template.type')" prop="type" width="150px">
<template #default="{ row }"> <template #default="{ row }">
@@ -61,7 +61,6 @@
<ComplexTable <ComplexTable
:selection-context="() => [req.name, req.type, req.websiteGroupId]" :selection-context="() => [req.name, req.type, req.websiteGroupId]"
row-key="id" row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig" :pagination-config="paginationConfig"
:default-sort="tableSort.order ? tableSort : undefined" :default-sort="tableSort.order ? tableSort : undefined"
v-model:view-mode="viewMode" v-model:view-mode="viewMode"