Compare commits

..
Author SHA1 Message Date
lan-yonghui f994210117 fix: prevent tar option injection 2026-11-08 12:08:42 +08:00
ssongliu 3c1f2a64b4 fix(ai): simplify GPU status and add benchmark log translations (#14334) 2026-11-07 22:58:14 +08:00
ssongliu 94e2ee5403 fix: refine GPU monitor layout and vendor environment checks (#14333) 2026-11-07 19:21:35 +08:00
ssongliu 1927e68010 fix: align model deployment config and GPU environment status (#14332) 2026-11-07 17:14:21 +08:00
ssongliu ab506e4a3b feat(container): add NVIDIA GPU runtime configuration switch (#14331) 2026-11-07 15:39:38 +08:00
王贺 58c81b9255 fix: align GB10 dual-node vLLM deployments (#14330) 2026-11-07 15:39:06 +08:00
王贺andssongliu 6fa378c13d fix: keep table tooltips below their triggers (#14329)
* fix: keep table tooltips below their triggers

* fix: unify tooltip placement and model list previews

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-11-07 12:51:34 +08:00
王贺 451ec3398e fix(ai): clarify token throughput monitoring labels (#14327) 2026-11-07 10:37:17 +08:00
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
132 changed files with 3485 additions and 977 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/dto"
"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"
)
@@ -21,20 +19,12 @@ func (b *BaseApi) LoadModelGPUDevices(c *gin.Context) {
// @Security Timestamp
// @Router /ai/gpu/load [get]
func (b *BaseApi) LoadGpuInfo(c *gin.Context) {
ok, client := accelerator.New()
if ok {
snapshot, err := client.Collect(c.Request.Context())
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)
info, err := service.LoadGPUInfo(c.Request.Context(), c.Query("refresh") == "true")
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, &accelerator.Info{})
helper.SuccessWithData(c, info)
}
// @Tags AI
+3
View File
@@ -10,6 +10,9 @@ type DockerStatus struct {
}
type DaemonJsonConf struct {
NVIDIA bool `json:"nvidia"`
NVIDIASupported bool `json:"nvidiaSupported"`
IsSwarm bool `json:"isSwarm"`
Version string `json:"version"`
Mirrors []string `json:"registryMirrors"`
+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"`
}
+1 -1
View File
@@ -48,7 +48,7 @@ func NewIAppInstallRepo() IAppInstallRepo {
func (a *AppInstallRepo) UpdateStatus(install *model.AppInstall) error {
return getDb().Model(&model.AppInstall{}).Where("id = ? AND updated_at = ?", install.ID, install.UpdatedAt).
Updates(map[string]interface{}{"status": install.Status, "message": install.Message}).Error
Updates(map[string]interface{}{"status": install.Status, "message": install.Message, "container_name": install.ContainerName}).Error
}
func (a *AppInstallRepo) WithDetailIdsIn(detailIds []uint) DBOption {
+24
View File
@@ -117,6 +117,30 @@ func SyncLocalModelAccount(install model.AppInstall, accountID uint, baseURL str
return account, nil
}
func UpdateLocalModelAccountAddress(accountID uint, previousURL, baseURL string) (*model.AgentAccount, error) {
agentAccountMutationMu.Lock()
defer agentAccountMutationMu.Unlock()
account, err := agentAccountRepo.GetFirst(repo.WithByID(accountID))
if err != nil {
return nil, err
}
if strings.TrimRight(account.BaseURL, "/") != previousURL || account.BaseURL == baseURL {
return account, nil
}
account.BaseURL = baseURL
if account.ValidateAvailability {
account.Verified = false
account.VerifiedAt = nil
account.VerifyError = ""
}
if err := agentAccountRepo.Save(account); err != nil {
return nil, err
}
terminalai.InvalidateTerminalRuntimeCache()
terminalai.InvalidateFileAIRuntimeCache()
return account, nil
}
func SyncOllamaModelAccount(createIfMissing bool) error {
app, err := appRepo.GetFirst(appRepo.WithKey("ollama"))
if errors.Is(err, gorm.ErrRecordNotFound) {
+1 -1
View File
@@ -518,7 +518,7 @@ func (a AppService) InstallWithHooks(req request.AppInstallCreate, executeScript
appInstall.ServiceName = serviceName
if !req.SkipComposeCommonConfig {
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params, app.Key); err != nil {
return
}
}
+6 -2
View File
@@ -553,7 +553,7 @@ func (a *AppInstallService) UpdateWithBeforeRestart(req request.AppInstalledUpda
}
}
if req.Advanced && !req.SkipComposeCommonConfig {
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params, installed.App.Key); err != nil {
return err
}
}
@@ -1207,7 +1207,11 @@ func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
containersMap map[string]container.Summary
containerNames = strings.Split(appInstall.ContainerName, ",")
)
containers, err = cli.ListContainersByName(containerNames)
if appInstall.App.Key == "vllm" {
containers, err = cli.ListAllContainers()
} else {
containers, err = cli.ListContainersByName(containerNames)
}
if err != nil {
return err
}
@@ -37,7 +37,7 @@ func operationStatusTestDB(t *testing.T) (*gorm.DB, *gorm.DB, model.AppInstall)
taskDB := open("tasks.db", &model.Task{})
install := model.AppInstall{
Name: "gb10-ds-vision", AppId: 1, AppDetailId: 1,
Version: "nvidia-gb10-dspark-0.1.1", ContainerName: "vllm-dspark-1", ServiceName: "vllm-dspark",
Version: "nvidia-0.25-optimized-for-ds-v4-flash", ContainerName: "vllm-dspark-1", ServiceName: "vllm-dspark",
Status: constant.StatusStarting, Env: `{"MODEL_DIR":"/models/vision"}`,
}
if err := appDB.Omit(clause.Associations).Create(&install).Error; err != nil {
+73 -16
View File
@@ -21,6 +21,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/vllm"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
@@ -239,6 +240,16 @@ func (u *appUpgradeContext) prepare(t *task.Task) error {
if err = copyUpgradeStageFile(u.original.GetPath(), u.stageDir, ".env"); err != nil {
return err
}
if u.original.App.Key == vllmAppKeyForUpgrade && vllm.IsGB10Version(u.original.Version) {
for _, directory := range []string{u.original.GetPath(), u.stageDir} {
for _, script := range []string{"start.sh", "stop.sh"} {
scriptPath := path.Join(directory, "scripts", script)
if !fileOp.Stat(scriptPath) {
return buserr.WithName("ErrFileNotFound", scriptPath)
}
}
}
}
if u.original.App.Key == constant.AppOpenclaw {
if err = copyUpgradeStageFile(u.original.GetPath(), u.stageDir, path.Join("data", "conf", "openclaw.json")); err != nil {
return err
@@ -271,7 +282,7 @@ func (u *appUpgradeContext) prepare(t *task.Task) error {
}
image := buildVllmUpgradeImage(loadVllmImageFromEnv(stagedInstall.Env), u.original.Version, u.detail.Version)
envs[vllmImageEnvKey] = image
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(u.detail.Version)), vllmGB10VersionPrefix) {
if vllm.IsGB10Version(u.detail.Version) {
envs["DSPARK_VLLM_IMAGE"] = image
}
paramBytes, marshalErr := json.Marshal(envs)
@@ -410,14 +421,22 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
if err := resetLocalModelAccountVerification(u.original); err != nil {
return err
}
useGB10Lifecycle := u.original.App.Key == vllmAppKeyForUpgrade && vllm.IsGB10Version(u.original.Version)
u.stopAttempted = true
t.LogStart(i18n.GetMsgByKey("UpgradeStop"))
if out, err := compose.Stop(u.original.GetComposePath()); err != nil {
if out != "" {
err = fmt.Errorf("%s: %w", out, err)
var stopErr error
if useGB10Lifecycle {
stopErr = runScript(t, &u.original, "stop")
} else {
out, err := compose.Stop(u.original.GetComposePath())
stopErr = err
if err != nil && out != "" {
stopErr = fmt.Errorf("%s: %w", out, err)
}
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeStop"), err)
return err
}
if stopErr != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("UpgradeStop"), stopErr)
return stopErr
}
t.LogSuccess(i18n.GetMsgByKey("UpgradeStop"))
u.phase = appUpgradeStopped
@@ -449,11 +468,13 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
}
u.downAttempted = true
if out, downErr := compose.Down(u.original.GetComposePath()); downErr != nil {
if out != "" {
downErr = fmt.Errorf("%s: %w", out, downErr)
if !useGB10Lifecycle {
if out, downErr := compose.Down(u.original.GetComposePath()); downErr != nil {
if out != "" {
downErr = fmt.Errorf("%s: %w", out, downErr)
}
return downErr
}
return downErr
}
u.phase = appUpgradeDown
@@ -473,12 +494,19 @@ func (u *appUpgradeContext) cutover(t *task.Task) error {
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
t.LogStart(logStr)
if out, upErr := compose.UpWithoutBuild(u.original.GetComposePath()); upErr != nil {
if out != "" {
upErr = fmt.Errorf("%s: %w", out, upErr)
var startErr error
if useGB10Lifecycle {
startErr = runScript(t, &u.candidate, "start")
} else {
out, err := compose.UpWithoutBuild(u.original.GetComposePath())
startErr = err
if err != nil && out != "" {
startErr = fmt.Errorf("%s: %w", out, err)
}
t.LogFailedWithErr(logStr, upErr)
return upErr
}
if startErr != nil {
t.LogFailedWithErr(logStr, startErr)
return startErr
}
t.LogSuccess(logStr)
u.phase = appUpgradeStarted
@@ -596,6 +624,9 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
t.LogSuccess(logStr)
}
}()
if u.original.App.Key == vllmAppKeyForUpgrade && vllm.IsGB10Version(u.original.Version) {
return u.rollbackVllmGB10(t)
}
if !u.downAttempted {
if out, err := compose.Operate(u.original.GetComposePath(), "start"); err != nil {
if out != "" {
@@ -667,6 +698,31 @@ func (u *appUpgradeContext) rollback(t *task.Task) (rollbackErr error) {
return errors.Join(rollbackErr, u.finishRollback())
}
func (u *appUpgradeContext) rollbackVllmGB10(t *task.Task) error {
current := u.original
if u.phase >= appUpgradeMutated {
current = u.candidate
}
stopErr := runScript(t, &current, "stop")
if u.phase >= appUpgradeMutated {
if err := u.restoreManagedFiles(); err != nil {
return errors.Join(stopErr, err)
}
if u.backupFile != "" {
if err := handleAppRecover(&u.original, t, u.backupFile, true, "", "", false); err != nil {
return errors.Join(stopErr, err)
}
}
}
if err := runScript(t, &u.original, "start"); err != nil {
return errors.Join(stopErr, err)
}
if stopErr != nil {
return stopErr
}
return u.finishRollback()
}
func (u *appUpgradeContext) finishRollback() error {
if u.feishuOriginal != nil {
if err := waitOpenclawReady(u.original.ContainerName); err != nil {
@@ -717,7 +773,8 @@ func (u *appUpgradeContext) cleanup() {
if u.feishuLockPath != "" {
feishuSwitches.Delete(u.feishuLockPath)
}
if u.feishuOriginal != nil && u.rollbackErr != nil {
if u.rollbackErr != nil && (u.feishuOriginal != nil ||
(u.original.App.Key == vllmAppKeyForUpgrade && vllm.IsGB10Version(u.original.Version))) {
return
}
if u.snapshot != nil {
+61 -9
View File
@@ -781,7 +781,7 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
envs[constant.ContainerName] = install.ContainerName
}
config.Advanced = true
if err := addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); err != nil {
if err := addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs, install.App.Key); err != nil {
return "", err
}
paramByte, err := json.Marshal(envs)
@@ -1495,6 +1495,24 @@ func doNotNeedSync(installed model.AppInstall) bool {
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
oldStatus := appInstall.Status
oldContainerName := appInstall.ContainerName
if appInstall.App.Key == "vllm" && !strings.Contains(appInstall.ContainerName, ",") {
if _, exists := containers["/"+appInstall.ContainerName]; !exists {
matchedNames := make(map[string]struct{})
for _, con := range containers {
if con.Labels["com.docker.compose.project.working_dir"] == appInstall.GetPath() &&
con.Labels["com.docker.compose.service"] == appInstall.ServiceName &&
!strings.EqualFold(con.Labels["com.docker.compose.oneoff"], "true") && len(con.Names) > 0 {
matchedNames[strings.TrimPrefix(con.Names[0], "/")] = struct{}{}
}
}
if len(matchedNames) == 1 {
for name := range matchedNames {
appInstall.ContainerName = name
}
}
}
}
containerNames := strings.Split(appInstall.ContainerName, ",")
if len(containers) == 0 {
if appInstall.Status == constant.StatusUpErr && !force {
@@ -1538,7 +1556,7 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
appInstall.Status = constant.StatusStopped
case runningCount == total:
appInstall.Status = constant.StatusRunning
if oldStatus == constant.StatusRunning {
if oldStatus == constant.StatusRunning && oldContainerName == appInstall.ContainerName {
return
}
case restartingCount == total:
@@ -1813,7 +1831,7 @@ func updateToolApp(installed *model.AppInstall) {
}
}
func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName string, req request.AppContainerConfig, params map[string]interface{}) error {
func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName string, req request.AppContainerConfig, params map[string]interface{}, appKey string) error {
services, serviceValid := composeMap["services"].(map[string]interface{})
if !serviceValid {
return buserr.New("ErrFileParse")
@@ -1839,6 +1857,11 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
return buserr.New("ErrFileParse")
}
serviceValue := service.(map[string]interface{})
if appKey == "vllm" {
if name, _ := serviceValue["container_name"].(string); strings.TrimSpace(name) == "" {
serviceValue["container_name"] = "${CONTAINER_NAME}"
}
}
deploy := map[string]interface{}{}
if de, ok := serviceValue["deploy"]; ok {
@@ -1877,15 +1900,44 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
ports, ok := serviceValue["ports"].([]interface{})
if ok {
for i, port := range ports {
portStr, portOK := port.(string)
if !portOK {
if config, ok := port.(map[string]interface{}); ok {
config["host_ip"] = "${HOST_IP}"
if req.Advanced && req.AllowPort && req.SpecifyIP == "" {
delete(config, "host_ip")
}
continue
}
portArray := strings.Split(portStr, ":")
if len(portArray) == 2 {
portArray = append([]string{"${HOST_IP}"}, portArray...)
portStr := fmt.Sprint(port)
parts := []string{}
depth, start := 0, 0
for index, char := range portStr {
switch char {
case '[', '{':
depth++
case ']', '}':
depth--
case ':':
if depth == 0 {
parts = append(parts, portStr[start:index])
start = index + 1
}
}
}
ports[i] = strings.Join(portArray, ":")
parts = append(parts, portStr[start:])
switch len(parts) {
case 1:
parts = []string{"", "", parts[0]}
case 2:
parts = append([]string{""}, parts...)
case 3:
default:
return fmt.Errorf("invalid port mapping: %s", portStr)
}
parts[0] = "${HOST_IP}"
if req.Advanced && req.AllowPort && strings.Contains(req.SpecifyIP, ":") {
parts[0] = "[${HOST_IP}]"
}
ports[i] = strings.Join(parts, ":")
}
serviceValue["ports"] = ports
}
+220 -8
View File
@@ -8,7 +8,9 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
@@ -192,6 +194,18 @@ func (u *DockerService) LoadDockerStatus() *dto.DockerStatus {
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) {
ctx := context.Background()
var data dto.DaemonJsonConf
@@ -201,6 +215,8 @@ func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) {
if err != nil {
return &data, err
}
defer client.Close()
data.NVIDIASupported = client.DaemonHost() == "unix:///var/run/docker.sock"
itemVersion, err := client.ServerVersion(ctx)
if err == nil {
data.Version = itemVersion.Version
@@ -210,18 +226,14 @@ func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) {
if strings.Contains(stdout2, "Swarm: active") {
data.IsSwarm = true
}
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
return &data, nil
}
file, err := os.ReadFile(constant.DaemonJsonPath)
daemonMap, err := readDockerDaemonConfig()
if err != nil {
return &data, nil
}
var conf daemonJsonItem
daemonMap := make(map[string]interface{})
if err := json.Unmarshal(file, &daemonMap); err != nil {
return &data, nil
if runtimes, ok := daemonMap["runtimes"].(map[string]interface{}); ok {
_, data.NVIDIA = runtimes["nvidia"]
}
var conf daemonJsonItem
arr, err := json.Marshal(daemonMap)
if err != nil {
return &data, err
@@ -253,6 +265,9 @@ func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) {
}
func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) error {
if req.Key == "NVIDIA" {
return updateDockerNVIDIA(req.Value, withRestart)
}
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
@@ -555,3 +570,200 @@ func validateDockerConfig() error {
}
return nil
}
func updateDockerNVIDIA(value string, withRestart bool) error {
if value != "enable" && value != "disable" {
return fmt.Errorf("unsupported NVIDIA setting %q", value)
}
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
if client.DaemonHost() != "unix:///var/run/docker.sock" {
return fmt.Errorf("NVIDIA configuration is only supported for the local default Docker socket")
}
enabled := value == "enable"
original, readErr := os.ReadFile(constant.DaemonJsonPath)
existed := readErr == nil
if readErr != nil && !os.IsNotExist(readErr) {
return readErr
}
daemonMap := make(map[string]interface{})
if existed {
if err := json.Unmarshal(original, &daemonMap); err != nil {
return fmt.Errorf("failed to parse Docker configuration: %w", err)
}
if daemonMap == nil {
return fmt.Errorf("Docker configuration must be a JSON object")
}
}
runtimePath, err := applyDockerNVIDIAConfig(daemonMap, enabled)
if err != nil {
return err
}
if enabled {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
check := checkNVIDIAToolkit(ctx, func(name string) string {
if name == "nvidia-container-runtime" {
return runtimePath
}
return findGPUContainerCommand(name)
}, func(ctx context.Context, executable string) (string, error) {
return cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(3*time.Second)).RunWithStdout(executable, "--version")
})
cancel()
if check.Status != "passed" {
return fmt.Errorf("NVIDIA Container Toolkit is not ready: %s", check.Reason)
}
}
updated, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
}
if err := os.MkdirAll(path.Dir(constant.DaemonJsonPath), 0755); err != nil {
return err
}
if err := writeDockerNVIDIAConfig(updated); err != nil {
return err
}
restore := func(cause error, restart bool) error {
var restoreErr error
if existed {
restoreErr = writeDockerNVIDIAConfig(original)
} else {
restoreErr = os.Remove(constant.DaemonJsonPath)
}
if restoreErr != nil {
return fmt.Errorf("%v; failed to restore Docker configuration: %w", cause, restoreErr)
}
if restart {
if err := controller.HandleRestart("docker"); err != nil {
return fmt.Errorf("%v; configuration restored but Docker restart failed: %w", cause, err)
}
}
return cause
}
if global.CACHE != nil {
defer global.CACHE.Del(gpuEnvironmentCacheKey)
}
if err := validateDockerConfig(); err != nil {
return restore(err, false)
}
if !withRestart {
return nil
}
if err := controller.HandleRestart("docker"); err != nil {
return restore(fmt.Errorf("failed to restart Docker: %w", err), true)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
info, err := client.Info(ctx)
if err != nil {
return restore(fmt.Errorf("failed to verify Docker NVIDIA runtime: %w", err), true)
}
if enabled {
check := checkNVIDIADockerConfig(info, readDockerDaemonConfig, findGPUContainerCommand)
if check.Status != "passed" {
return restore(fmt.Errorf("Docker NVIDIA runtime did not become available: %s", check.Reason), true)
}
} else if _, registered := info.Runtimes["nvidia"]; registered {
return restore(fmt.Errorf("Docker still registers the NVIDIA runtime; check other Docker configuration sources"), true)
}
return nil
}
func applyDockerNVIDIAConfig(config map[string]interface{}, enabled bool) (string, error) {
runtimes, ok := config["runtimes"].(map[string]interface{})
if config["runtimes"] != nil && !ok {
return "", fmt.Errorf("Docker runtimes configuration must be a JSON object")
}
if !enabled {
delete(runtimes, "nvidia")
if len(runtimes) == 0 {
delete(config, "runtimes")
}
if config["default-runtime"] == "nvidia" {
delete(config, "default-runtime")
}
return "", nil
}
if runtimes == nil {
runtimes = make(map[string]interface{})
config["runtimes"] = runtimes
}
nvidia, ok := runtimes["nvidia"].(map[string]interface{})
if runtimes["nvidia"] != nil && !ok {
return "", fmt.Errorf("Docker NVIDIA runtime configuration must be a JSON object")
}
if nvidia == nil {
nvidia = map[string]interface{}{"runtimeArgs": []string{}}
runtimes["nvidia"] = nvidia
}
configured, ok := nvidia["path"].(string)
if nvidia["path"] != nil && !ok {
return "", fmt.Errorf("Docker NVIDIA runtime path must be a string")
}
runtimePath := findNVIDIAContainerCommand("nvidia-container-runtime", config)
if runtimePath == "" || !cmd.Which(runtimePath) {
if configured != "" {
return "", fmt.Errorf("configured NVIDIA runtime %q is not executable", configured)
}
return "", fmt.Errorf("nvidia-container-runtime is not installed or executable")
}
if configured == "" {
nvidia["path"] = runtimePath
}
return runtimePath, nil
}
func writeDockerNVIDIAConfig(data []byte) error {
target := constant.DaemonJsonPath
mode := os.FileMode(0640)
info, err := os.Lstat(target)
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
target, err = filepath.EvalSymlinks(target)
if err != nil {
return err
}
info, err = os.Stat(target)
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return fmt.Errorf("Docker configuration must be a regular file")
}
mode = info.Mode().Perm()
}
temp, err := os.CreateTemp(filepath.Dir(target), ".daemon-nvidia-*.json")
if err != nil {
return err
}
defer func() {
_ = temp.Close()
_ = os.Remove(temp.Name())
}()
if info != nil {
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
if err := temp.Chown(int(stat.Uid), int(stat.Gid)); err != nil {
return err
}
}
}
if err := temp.Chmod(mode); err != nil {
return err
}
if _, err := temp.Write(data); err != nil {
return err
}
if err := temp.Sync(); err != nil {
return err
}
if err := temp.Close(); err != nil {
return err
}
return os.Rename(temp.Name(), target)
}
+454
View File
@@ -0,0 +1,454 @@
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 findNVIDIAContainerCommand(name string, config map[string]interface{}) string {
if name == "nvidia-container-runtime" {
runtimes, _ := config["runtimes"].(map[string]interface{})
nvidia, _ := runtimes["nvidia"].(map[string]interface{})
if configured, ok := nvidia["path"].(string); ok && configured != "" {
name = configured
}
}
return findGPUContainerCommand(name)
}
func checkNVIDIAContainerEnvironment(ctx context.Context) []dto.GPUEnvironmentCheck {
config, configErr := readDockerDaemonConfig()
toolkitCtx, cancelToolkit := context.WithTimeout(ctx, 5*time.Second)
toolkit := checkNVIDIAToolkit(toolkitCtx, func(name string) string {
return findNVIDIAContainerCommand(name, config)
}, 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, func() (map[string]interface{}, error) { return config, configErr }, 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
}
+12 -15
View File
@@ -5,22 +5,22 @@ import (
"strings"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/vllm"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
)
const (
vllmAppKeyForUpgrade = "vllm"
vllmGB10VersionPrefix = "nvidia-gb10-dspark-"
vllmImageEnvKey = "IMAGE"
vllmImageTypeNvidia = "nvidia"
vllmImageTypeIntel = "intel"
vllmImageTypeAscend = "ascend"
vllmAppKeyForUpgrade = "vllm"
vllmImageEnvKey = "IMAGE"
vllmImageTypeNvidia = "nvidia"
vllmImageTypeIntel = "intel"
vllmImageTypeAscend = "ascend"
)
func isVllmProOnlyVersion(appKey, version string) bool {
return strings.EqualFold(strings.TrimSpace(appKey), vllmAppKeyForUpgrade) &&
strings.HasPrefix(strings.ToLower(strings.TrimSpace(version)), vllmGB10VersionPrefix)
vllm.IsGB10Version(version)
}
func canAccessVllmVersion(appKey, version string) bool {
@@ -58,9 +58,6 @@ func resolveVllmVersionFamily(version, image string) string {
func trimVllmVersionFamily(version string) string {
trimmed := strings.TrimSpace(version)
normalized := strings.ToLower(trimmed)
if strings.HasPrefix(normalized, vllmGB10VersionPrefix) {
return strings.TrimSpace(trimmed[len(vllmGB10VersionPrefix):])
}
for _, family := range []string{vllmImageTypeNvidia, vllmImageTypeIntel, vllmImageTypeAscend} {
prefix := family + "-"
if strings.HasPrefix(normalized, prefix) {
@@ -71,10 +68,10 @@ func trimVllmVersionFamily(version string) string {
}
func buildDefaultVllmImageByVersion(version string) string {
tag := trimVllmVersionFamily(version)
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(version)), vllmGB10VersionPrefix) {
return "1panel/vllm-gb10-dspark:" + tag
if image := vllm.GB10Image(version); image != "" {
return image
}
tag := trimVllmVersionFamily(version)
family := resolveVllmVersionFamily(version, "")
if family == vllmImageTypeIntel {
return "intel/llm-scaler-vllm:" + tag
@@ -89,8 +86,8 @@ func buildDefaultVllmImageByVersion(version string) string {
}
func isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage string) bool {
currentGB10 := strings.HasPrefix(strings.ToLower(strings.TrimSpace(currentVersion)), vllmGB10VersionPrefix)
targetGB10 := strings.HasPrefix(strings.ToLower(strings.TrimSpace(targetVersion)), vllmGB10VersionPrefix)
currentGB10 := vllm.IsGB10Version(currentVersion)
targetGB10 := vllm.IsGB10Version(targetVersion)
if currentGB10 != targetGB10 {
return false
}
@@ -39,6 +39,7 @@ func normalizeGPU(item *gpu.Device) Device {
Vendor: item.Type,
Index: int(item.Index),
Name: item.ProductName,
DriverVersion: item.DriverVersion,
Label: fmt.Sprintf("%d - %s", item.Index, item.ProductName),
BusID: item.BusID,
Metrics: metrics,
@@ -121,6 +122,7 @@ func normalizeXPU(item *xpu.Device) Device {
Vendor: item.Basic.VendorName,
Index: item.Basic.DeviceID,
Name: item.Basic.DeviceName,
DriverVersion: item.Basic.DriverVersion,
Label: fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName),
BusID: item.Basic.PciBdfAddress,
Metrics: metrics,
+3 -1
View File
@@ -56,7 +56,9 @@ func (p npuProvider) Collect(ctx context.Context) (*ProviderSnapshot, error) {
NPUs: info.Devices,
}
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
}
@@ -102,6 +102,7 @@ type Device struct {
NPUIndex int
ChipIndex int
Name string
DriverVersion string
Label string
BusID string
Metrics Metrics
+15 -3
View File
@@ -23,12 +23,24 @@ type providerResult struct {
err error
}
func New() (bool, Client) {
client := Client{}
func ManagementCommands() map[string]string {
commands := make(map[string]string)
if cmd.Which(nvidiaSMICommand) {
client.providers = append(client.providers, nvidiaSMI{})
commands["nvidia"] = nvidiaSMICommand
}
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})
}
return len(client.providers) > 0, client
+23
View File
@@ -0,0 +1,23 @@
package vllm
import "strings"
const (
gb10VersionPrefix = "nvidia-"
gb10VersionSuffix = "-optimized-for-ds-v4-flash"
)
func IsGB10Version(version string) bool {
normalized := strings.ToLower(strings.TrimSpace(version))
tag := strings.TrimPrefix(normalized, gb10VersionPrefix)
return tag != normalized && strings.HasSuffix(tag, gb10VersionSuffix) &&
strings.TrimSpace(strings.TrimSuffix(tag, gb10VersionSuffix)) != ""
}
func GB10Image(version string) string {
trimmed := strings.TrimSpace(version)
if !IsGB10Version(trimmed) {
return ""
}
return "1panel/vllm-gb10-dual-node:" + strings.TrimSpace(trimmed[len(gb10VersionPrefix):])
}
+1
View File
@@ -63,6 +63,7 @@ type Info struct {
}
type Device struct {
ReadError string `json:"-"`
Tiles []TileStats `json:"tiles"`
ProcessStatus string `json:"processStatus"`
+4 -2
View File
@@ -122,8 +122,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
wgCmd.Wait()
if xpuErr != nil {
xpu.ReadError = fmt.Sprintf("xpu-smi device %d: %v", device.DeviceID, xpuErr)
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)
mu.Unlock()
return
@@ -131,8 +132,9 @@ func (c Client) loadDeviceInfo(ctx context.Context, device discoveryDevice, wg *
var info discoveryDevice
if err := json.Unmarshal([]byte(xpuData), &info); err != nil {
xpu.ReadError = fmt.Sprintf("xpu-smi device %d output: %v", device.DeviceID, err)
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)
mu.Unlock()
return
+1 -1
View File
@@ -43,7 +43,7 @@ func (t TarArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile
return err
}
baseDir := filepath.Dir(sourcePaths[0])
args := []string{t.getOptionStr("compress"), dstFile, "-C", baseDir}
args := []string{t.getOptionStr("compress"), dstFile, "-C", baseDir, "--"}
for _, sourcePath := range sourcePaths {
args = append(args, filepath.Base(sourcePath))
}
+2 -2
View File
@@ -55,9 +55,9 @@ func (t TarGzArchiver) Compress(ctx context.Context, sourcePaths []string, dstFi
aheadDir = "/"
}
if len(secret) != 0 {
err = runTarGzEncryptToFile(cmd.NewCommandMgr(cmd.WithContext(ctx)), tmpFile, secret, append([]string{"-C", aheadDir}, itemDirs...)...)
err = runTarGzEncryptToFile(cmd.NewCommandMgr(cmd.WithContext(ctx)), tmpFile, secret, append([]string{"-C", aheadDir, "--"}, itemDirs...)...)
} else {
err = runTarGzToFile(cmd.NewCommandMgr(cmd.WithContext(ctx)), tmpFile, append([]string{"-C", aheadDir}, itemDirs...)...)
err = runTarGzToFile(cmd.NewCommandMgr(cmd.WithContext(ctx)), tmpFile, append([]string{"-C", aheadDir, "--"}, itemDirs...)...)
}
if err != nil {
return err
+24
View File
@@ -37,6 +37,28 @@ export namespace AI {
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 {
collectedAt?: string;
warnings?: string[];
@@ -47,6 +69,8 @@ export namespace AI {
npu: NPU[];
xpuDriverVersion: string;
xpu: XpuInfo['xpu'];
environment?: GPUEnvironment;
summaries?: GPUSummary[];
}
export interface GPU {
sharedMemory?: boolean;
+2
View File
@@ -416,6 +416,8 @@ export namespace Container {
isExist: boolean;
}
export interface DaemonJsonConf {
nvidia: boolean;
nvidiaSupported: boolean;
isSwarm: boolean;
isExist: boolean;
isActive: boolean;
+5 -2
View File
@@ -47,8 +47,11 @@ export const loadModelGPUDevices = () => {
return http.get<{ devices: AI.ModelGPUDevice[]; warnings: string[] }>('/ai/model/gpu/devices');
};
export const loadGPUInfo = () => {
return http.get<AI.Info>(`/ai/gpu/load`);
export const loadGPUInfo = (refresh = false, currentNode?: string) => {
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) => {
return http.get<AI.MonitorGPUOptions>(
@@ -0,0 +1,138 @@
<template>
<div class="ai-model-list" :class="{ 'ai-model-list--single': visibleCount === 1 }">
<span v-for="(model, index) in models.slice(0, visibleCount)" :key="index" class="model-preview">
<el-tag size="small" :type="tagType" class="model-tag">
<span :title="model">{{ model }}</span>
</el-tag>
<CopyButton v-if="copyable" :content="model" />
</span>
<el-popover
v-if="models.length > visibleCount"
trigger="click"
placement="bottom-start"
:width="360"
:popper-options="{ modifiers: [{ name: 'preventOverflow', options: { padding: 16, altAxis: true } }] }"
:popper-style="{ maxWidth: 'calc(100vw - 32px)', padding: '12px' }"
>
<template #reference>
<el-button
size="small"
class="model-more"
:aria-label="$t('aiTools.model.model') + ' +' + (models.length - visibleCount)"
@click.stop
>
+{{ models.length - visibleCount }}
</el-button>
</template>
<div class="model-popover" @click.stop>
<div class="model-popover__header">
<span>{{ $t('aiTools.model.model') }}</span>
<el-tag size="small" type="info" round>{{ models.length }}</el-tag>
</div>
<ul class="model-popover__list" tabindex="0" :aria-label="$t('aiTools.model.model')">
<li v-for="(model, index) in models" :key="index">
<span class="model-popover__name">{{ model }}</span>
<CopyButton v-if="copyable" :content="model" :aria-label="$t('commons.button.copy')" />
</li>
</ul>
<div v-if="$slots.footer" class="model-popover__footer"><slot name="footer" /></div>
</div>
</el-popover>
</div>
</template>
<script setup lang="ts">
import CopyButton from '@/components/copy-button/index.vue';
withDefaults(
defineProps<{
models: string[];
visibleCount?: number;
copyable?: boolean;
tagType?: 'primary' | 'success' | 'warning' | 'info' | 'danger';
}>(),
{ visibleCount: 3, copyable: false, tagType: 'primary' },
);
</script>
<style scoped lang="scss">
.ai-model-list {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 0;
max-width: 100%;
&--single {
flex-wrap: nowrap;
}
}
.model-preview {
display: inline-flex;
align-items: center;
min-width: 0;
max-width: 100%;
}
.model-tag {
min-width: 0;
max-width: 100%;
:deep(.el-tag__content) {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
}
.model-more {
flex-shrink: 0;
margin: 0;
padding: 0 8px;
color: var(--el-color-primary);
border-color: var(--el-color-primary-light-7);
background: var(--el-color-primary-light-9);
}
.model-popover {
&__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding-bottom: 10px;
font-weight: 500;
border-bottom: 1px solid var(--el-border-color-lighter);
}
&__list {
max-height: min(320px, 50vh);
overflow-y: auto;
overscroll-behavior: contain;
margin: 8px 0 0;
padding: 0;
list-style: none;
li {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px;
line-height: 20px;
border-radius: 4px;
&:nth-child(odd) {
background: var(--el-fill-color-light);
}
}
}
&__name {
flex: 1;
min-width: 0;
white-space: normal;
overflow-wrap: anywhere;
}
&__footer {
margin-top: 8px;
padding-top: 10px;
border-top: 1px solid var(--el-border-color-lighter);
}
:deep(.el-button) {
flex-shrink: 0;
}
}
</style>
@@ -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>
@@ -1,5 +1,5 @@
<template>
<DialogPro v-model="submitVisible" :title="header" size="mini">
<DialogPro v-model="submitVisible" :title="header" size="mini" @close="onCancel">
<div>
<span v-if="operationInfo" class="operation-info">{{ operationInfo }}</span>
<slot />
+3 -1
View File
@@ -1,5 +1,5 @@
<template>
<el-tooltip placement="top-start">
<el-tooltip v-bind="TABLE_TOOLTIP_OPTIONS">
<template #content>
<div class="info-break" :style="{ width: width + 'px' }">{{ info }}</div>
</template>
@@ -7,6 +7,8 @@
</el-tooltip>
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
defineProps({
width: {
type: String,
@@ -5,8 +5,10 @@
<el-radio-group v-model="activeName" @change="handleChange">
<el-radio-button
class="router_card_button"
:class="{ 'opacity-50': button.disabled }"
:label="button.label"
:value="button.label"
:disabled="button.disabled"
v-for="(button, index) in buttonArray"
size="large"
:key="index"
@@ -50,7 +52,7 @@
</template>
<script lang="ts" setup>
import { openPage } from '@/utils/router';
import { routerToNameWithQuery, routerToPathWithQuery } from '@/utils/router';
import { computed, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { hasPermissionMetaAccess, hasRouteAccess } from '@/utils/rbac';
@@ -94,10 +96,13 @@ const expiresAlertKey = computed(() => (isEnterprise.value ? 'xpack.expiresEnter
const handleChange = async (label: string) => {
const btn = buttonArray.value.find((btn) => btn.label === label);
if (!btn) return;
if (!btn || btn.disabled) {
syncActiveName();
return;
}
try {
if (btn.path) await openPage({ path: btn.path });
else if (btn.name) await openPage({ name: btn.name });
if (btn.path) await routerToPathWithQuery(btn.path, { uncached: 'true' });
else if (btn.name) await routerToNameWithQuery(btn.name, { uncached: 'true' });
} finally {
syncActiveName();
}
+2 -1
View File
@@ -5,7 +5,7 @@
:aria-disabled="isDisabled"
@click.capture="handlePermissionClick"
>
<el-tooltip v-if="msg && msg != ''" effect="dark" placement="bottom">
<el-tooltip v-if="msg && msg != ''" effect="dark" v-bind="TABLE_TOOLTIP_OPTIONS">
<template #content>
<div class="content">{{ msg }}</div>
</template>
@@ -40,6 +40,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { computed, ref } from 'vue';
const props = defineProps({
+3 -40
View File
@@ -5,36 +5,21 @@
clearable
v-model="searchInfo"
@clear="search()"
@keydown.enter.stop.prevent="search()"
@blur="searchIfChanged()"
suffix-icon="Search"
@change="search()"
:disabled="props.disabled"
:placeholder="props.placeholder || $t('commons.button.search')"
>
<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>
></el-input>
</div>
</div>
</template>
<script setup lang="ts">
import { onActivated, ref, watch } from 'vue';
import { Search } from '@element-plus/icons-vue';
defineOptions({ name: 'TableSearch' });
const emit = defineEmits(['search', 'update:searchName']);
const searchInfo = ref();
let lastSearch: string | number | undefined;
const props = defineProps({
placeholder: String,
disabled: {
@@ -52,7 +37,6 @@ watch(
(newVal) => {
if (searchInfo.value !== newVal) {
searchInfo.value = newVal;
lastSearch = newVal;
}
},
{ immediate: true },
@@ -63,28 +47,7 @@ onActivated(() => {
});
const search = () => {
if (props.disabled) return;
lastSearch = searchInfo.value;
emit('update:searchName', searchInfo.value);
emit('search');
};
const searchIfChanged = () => {
if (searchInfo.value !== lastSearch) search();
};
</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>
@@ -170,13 +170,11 @@
import { panelCacheKey, writePanelCache } from '@/utils/panel-cache';
import { computed, onDeactivated, onMounted, ref, useAttrs } from 'vue';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
import { flattenVNodes, TABLE_OVERFLOW_TOOLTIP_OPTIONS, type FuTableOperationButton } from '@/components/table/shared';
import { useCardColumns } from './useCardColumns';
import { useContextMenu } from './useContextMenu';
import { useResponsivePagination } from './useResponsivePagination';
import { useTableSelection } from './useTableSelection';
import { useRoute } from 'vue-router';
import { usePaginationCache } from './usePaginationCache';
const slots = useSlots();
const attrs = useAttrs();
const { isMobile, openMenuTabs, currentNode } = useGlobalStore();
@@ -189,16 +187,11 @@ type ViewMode = 'table' | 'card';
const props = defineProps({
header: String,
data: { type: Array as PropType<any[]>, default: () => [] },
loading: { type: Boolean, default: false },
selectionContext: { type: [String, Number, Array, Object, Function], default: undefined },
paginationConfig: {
type: Object,
required: false,
},
cachePagination: { type: Boolean, default: undefined },
refreshOnRestore: { type: Boolean, default: true },
cacheId: String,
cacheScope: [String, Number],
pageSizes: {
type: Array as PropType<number[]>,
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']);
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 complexTableRef = ref<HTMLElement>();
const tableHeight = ref<number | string>('');
@@ -290,7 +274,7 @@ const scheduleCardContentHeightSync = () => {
});
};
const resolvedTooltipOptions = computed(() => ({
placement: 'bottom-start',
...TABLE_OVERFLOW_TOOLTIP_OPTIONS,
...((attrs.tooltipOptions ?? attrs['tooltip-options']) as Record<string, unknown> | undefined),
}));
const getRowKey = (row: any, index: number) => {
@@ -359,10 +343,10 @@ const handleRightClick = (row, column, event) => {
};
watch(
() => [props.data, props.loading],
() => props.data,
() => {
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));
if (pagination.currentPage > lastPage) {
pagination.currentPage = lastPage;
@@ -377,7 +361,7 @@ function currentChange() {
}
function sizeChange() {
if (props.cachePagination === false && props.paginationConfig.cacheSizeKey) {
if (props.paginationConfig.cacheSizeKey) {
writePanelCache(
'pagination',
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;
});
};
+7
View File
@@ -1,4 +1,5 @@
import type { VNode } from 'vue';
import type { UseTooltipProps } from 'element-plus';
import { hasManagePermissionAccess, hasPermissionAccess, type PermissionBindingValue } from '@/utils/permission';
export { flattenVNodes } from '@/components/shared/vnode';
@@ -28,6 +29,12 @@ export interface FuTableOperationButton<Row = any> {
export const FU_TABLE_STORAGE_PREFIX = 'FU-T-';
export const TABLE_TOOLTIP_OPTIONS: Pick<UseTooltipProps, 'placement'> = {
placement: 'bottom',
};
export const TABLE_OVERFLOW_TOOLTIP_OPTIONS = TABLE_TOOLTIP_OPTIONS;
export const isElTableColumnVNode = (vnode: VNode) => {
const type = vnode.type as any;
return type === 'el-table-column' || type?.name === 'ElTableColumn' || type?.__name === 'ElTableColumn';
+2 -1
View File
@@ -1,6 +1,6 @@
<template>
<div class="tooltip-container">
<el-tooltip :disabled="showTooltip">
<el-tooltip :disabled="showTooltip" v-bind="TABLE_TOOLTIP_OPTIONS">
<template #content>
<div :style="{ width: tootipWidth, 'word-break': 'break-all' }">{{ text }}</div>
</template>
@@ -13,6 +13,7 @@
</template>
<script setup lang="ts">
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { onMounted, ref } from 'vue';
defineOptions({ name: 'Tooltip' });
-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 };
};
+80 -2
View File
@@ -1743,8 +1743,8 @@ const message = {
running: 'Running requests',
waiting: 'Waiting requests',
cacheUsage: 'KV cache usage',
promptThroughput: 'Input token throughput',
generationThroughput: 'Output token throughput',
promptThroughput: 'Input token rate',
generationThroughput: 'Output token rate',
requestThroughput: 'Completed requests',
timeToFirstToken: 'Mean time to first token',
timePerOutputToken: 'Mean inter-token latency',
@@ -1764,6 +1764,74 @@ const message = {
timePerOutputTokenChart: 'Inter-token latency',
},
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',
passedLabel: 'Checks passed',
issuesLabel: 'Environment issues · {count}',
loadFailedLabel: 'Check failed',
notDetectedLabel: 'No devices detected',
unknownLabel: 'Status unknown',
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',
systemMemoryUsed: 'System memory used',
systemMemoryTotal: 'Total system memory',
@@ -1823,6 +1891,8 @@ const message = {
pcieWidth: 'PCIe width (current / maximum)',
gpu: 'GPU Management',
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!',
process: 'Process Information',
processCount: 'Process Count',
@@ -2072,6 +2142,8 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'Auto refresh',
logWindowHelp: 'Only recent log output is shown. Download the selected log file for its full contents.',
prefixTokens: 'Prefix tokens',
seed: 'Random seed',
report: 'Report',
@@ -2285,6 +2357,12 @@ const message = {
missing: 'Missing',
},
container: {
nvidiaSupport: 'NVIDIA GPU support',
nvidiaHelper:
'Register the NVIDIA container runtime. Install NVIDIA Container Toolkit first. Assign GPUs to each container separately.',
nvidiaLocalOnly: 'Only the local default Docker socket is supported.',
nvidiaChangeHelper:
'Disabling may prevent containers using the NVIDIA runtime from starting. This switch does not block GPU access.',
imageRemoteSearch: 'Remote search',
imageRemoteSearchHelper: 'Only Docker Hub repository images can be searched',
imageRemoteSearchProxyRequired:
+86 -2
View File
@@ -1754,8 +1754,8 @@ const message = {
running: 'Solicitudes en ejecución',
waiting: 'Solicitudes en espera',
cacheUsage: 'Uso de caché KV',
promptThroughput: 'Rendimiento de tokens de entrada',
generationThroughput: 'Rendimiento de tokens de salida',
promptThroughput: 'Tasa de tokens de entrada',
generationThroughput: 'Tasa de tokens de salida',
requestThroughput: 'Solicitudes completadas',
timeToFirstToken: 'Latencia media del primer token',
timePerOutputToken: 'Latencia media entre tokens',
@@ -1776,6 +1776,79 @@ const message = {
timePerOutputTokenChart: 'Latencia entre tokens',
},
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',
passedLabel: 'Comprobación correcta',
issuesLabel: 'Problemas del entorno · {count}',
loadFailedLabel: 'Comprobación fallida',
notDetectedLabel: 'Sin dispositivos detectados',
unknownLabel: 'Estado desconocido',
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',
systemMemoryUsed: 'Memoria del sistema utilizada',
systemMemoryTotal: 'Memoria total del sistema',
@@ -1836,6 +1909,8 @@ const message = {
pcieWidth: 'Ancho PCIe (actual / máximo)',
gpu: 'Gestión 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:
'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',
@@ -2092,6 +2167,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'Actualización automática',
logWindowHelp:
'Solo se muestran los registros recientes. Descargue el archivo seleccionado para ver todo el contenido.',
prefixTokens: 'Tokens de prefijo',
seed: 'Semilla aleatoria',
report: 'Informe',
@@ -2313,6 +2391,12 @@ const message = {
missing: 'No existe',
},
container: {
nvidiaSupport: 'Compatibilidad con GPU NVIDIA',
nvidiaHelper:
'Registra el entorno de ejecución de NVIDIA. Instala primero NVIDIA Container Toolkit. Asigna las GPU a cada contenedor por separado.',
nvidiaLocalOnly: 'Solo se admite el socket predeterminado de Docker local.',
nvidiaChangeHelper:
'Al desactivar, los contenedores que usan el entorno de NVIDIA podrían no iniciarse. Este interruptor no bloquea el acceso a la GPU.',
imageRemoteSearch: 'Búsqueda remota',
imageRemoteSearchHelper: 'Solo se pueden buscar imágenes de repositorios de Docker Hub',
imageRemoteSearchProxyRequired:
+79 -2
View File
@@ -1719,8 +1719,8 @@ const message = {
running: 'درخواست‌های در حال اجرا',
waiting: 'درخواست‌های در انتظار',
cacheUsage: 'مصرف حافظه نهان KV',
promptThroughput: 'گذردهی توکن ورودی',
generationThroughput: 'گذردهی توکن خروجی',
promptThroughput: 'نرخ توکن ورودی',
generationThroughput: 'نرخ توکن خروجی',
requestThroughput: 'نرخ درخواست‌های تکمیل‌شده',
timeToFirstToken: 'میانگین تأخیر توکن اول',
timePerOutputToken: 'میانگین تأخیر بین توکن‌ها',
@@ -1740,6 +1740,72 @@ const message = {
timePerOutputTokenChart: 'تأخیر بین توکن‌ها',
},
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: 'بررسی محیط',
passedLabel: 'بررسی موفق',
issuesLabel: 'مشکلات محیط · {count}',
loadFailedLabel: 'بررسی ناموفق',
notDetectedLabel: 'دستگاهی شناسایی نشد',
unknownLabel: 'وضعیت نامشخص',
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: 'حافظهٔ مشترک سیستم',
systemMemoryUsed: 'حافظهٔ مصرف‌شدهٔ سیستم',
systemMemoryTotal: 'کل حافظهٔ سیستم',
@@ -1798,6 +1864,8 @@ const message = {
pcieWidth: 'عرض PCIe (فعلی / حداکثر)',
gpu: 'مدیریت GPU',
monitoringTitle: 'نظارت بر GPU',
currentHelper:
'وضعیت لحظه‌ای و میزان استفاده از منابع دستگاه‌های GPU/NPU/XPU در گره فعلی را مشاهده کنید. نمای کلی در بالا، خلاصه همه دستگاه‌ها را نشان می‌دهد؛ شاخص‌های دقیق برای هر دستگاه جداگانه نمایش داده می‌شوند.',
gpuHelper: 'هیچ دستور مدیریت GPU/XPU/NPU پشتیبانی‌شده‌ای شناسایی نشد. لطفاً بررسی و دوباره تلاش کنید!',
process: 'اطلاعات فرآیند',
processCount: 'تعداد فرآیندها',
@@ -2048,6 +2116,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'بازخوانی خودکار',
logWindowHelp:
'فقط بخش اخیر گزارش نمایش داده می‌شود. برای مشاهده محتوای کامل، فایل گزارش انتخاب‌شده را دانلود کنید.',
prefixTokens: 'تعداد توکن\u200cهای پیشوند',
seed: 'بذر تصادفی',
report: 'گزارش',
@@ -2261,6 +2332,12 @@ const message = {
missing: 'ناموجود',
},
container: {
nvidiaSupport: 'پشتیبانی از GPU انویدیا',
nvidiaHelper:
'محیط اجرای کانتینر NVIDIA را ثبت می‌کند. ابتدا NVIDIA Container Toolkit را نصب کنید. GPU را جداگانه به هر کانتینر اختصاص دهید.',
nvidiaLocalOnly: 'فقط سوکت پیش‌فرض Docker محلی پشتیبانی می‌شود.',
nvidiaChangeHelper:
'غیرفعال‌سازی ممکن است مانع شروع کانتینرهای دارای محیط اجرای NVIDIA شود. این کلید دسترسی به GPU را مسدود نمی‌کند.',
imageRemoteSearch: 'جستجوی راه دور',
imageRemoteSearchHelper: 'فقط جستجوی تصاویر مخزن Docker Hub پشتیبانی می\u200cشود',
imageRemoteSearchProxyRequired: 'برای جستجوی تصاویر راه دور در محیط فعلی، ابتدا یک سرور پروکسی پیکربندی کنید.',
+81 -2
View File
@@ -1726,8 +1726,8 @@ const message = {
running: '実行中のリクエスト',
waiting: '待機中のリクエスト',
cacheUsage: 'KVキャッシュ使用率',
promptThroughput: '入力トークンスループット',
generationThroughput: '出力トークンスループット',
promptThroughput: '入力トークン速度',
generationThroughput: '出力トークン速度',
requestThroughput: '完了リクエスト数/秒',
timeToFirstToken: '平均初回トークン遅延',
timePerOutputToken: '平均トークン間遅延',
@@ -1747,6 +1747,75 @@ const message = {
timePerOutputTokenChart: 'トークン間遅延',
},
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: '環境チェック',
passedLabel: 'チェック成功',
issuesLabel: '環境の問題 · {count} 件',
loadFailedLabel: 'チェック失敗',
notDetectedLabel: 'デバイス未検出',
unknownLabel: '状態不明',
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: '共有システムメモリ',
systemMemoryUsed: 'システムメモリ使用量',
systemMemoryTotal: 'システムメモリ合計',
@@ -1804,6 +1873,8 @@ const message = {
pcieWidth: 'PCIe 幅(現在 / 最大)',
gpu: 'GPU 管理',
monitoringTitle: 'GPU 監視',
currentHelper:
'現在のノードにある GPU/NPU/XPU デバイスのリアルタイムの稼働状態とリソース使用状況を確認できます。上部には全デバイスの概要を表示し、詳細な指標はデバイスごとに表示します。',
gpuHelper: '対応する GPU/XPU/NPU 管理コマンドが検出されませんでした。確認して再試行してください!',
process: 'プロセス情報',
type: 'タイプ',
@@ -2054,6 +2125,8 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: '自動更新',
logWindowHelp: '最近のログのみ表示します。全内容は選択したログファイルをダウンロードして確認できます。',
prefixTokens: 'プレフィックストークン数',
seed: '乱数シード',
report: 'レポート',
@@ -2270,6 +2343,12 @@ const message = {
missing: '存在しない',
},
container: {
nvidiaSupport: 'NVIDIA GPU サポート',
nvidiaHelper:
'NVIDIA コンテナーランタイムを登録します。先に NVIDIA Container Toolkit をインストールしてください。GPU はコンテナーごとに割り当てる必要があります。',
nvidiaLocalOnly: 'ローカルの既定の Docker ソケットのみ対応しています。',
nvidiaChangeHelper:
'無効にすると NVIDIA ランタイムを使用するコンテナーが起動できなくなる場合があります。このスイッチは GPU へのアクセスを遮断しません。',
imageRemoteSearch: 'リモート検索',
imageRemoteSearchHelper: 'Docker Hub リポジトリのイメージのみ検索できます',
imageRemoteSearchProxyRequired:
+78 -2
View File
@@ -1707,8 +1707,8 @@ const message = {
running: '실행 중인 요청',
waiting: '대기 중인 요청',
cacheUsage: 'KV 캐시 사용률',
promptThroughput: '입력 토큰 처리량',
generationThroughput: '출력 토큰 처리량',
promptThroughput: '입력 토큰 속도',
generationThroughput: '출력 토큰 속도',
requestThroughput: '완료 요청 속도',
timeToFirstToken: '평균 첫 토큰 지연',
timePerOutputToken: '평균 토큰 간 지연',
@@ -1728,6 +1728,72 @@ const message = {
timePerOutputTokenChart: '토큰 간 지연',
},
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: '환경 점검',
passedLabel: '검사 통과',
issuesLabel: '환경 문제 · {count}개',
loadFailedLabel: '검사 실패',
notDetectedLabel: '장치 감지 안 됨',
unknownLabel: '상태 알 수 없음',
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: '공유 시스템 메모리',
systemMemoryUsed: '사용 중인 시스템 메모리',
systemMemoryTotal: '전체 시스템 메모리',
@@ -1785,6 +1851,8 @@ const message = {
pcieWidth: 'PCIe 너비 (현재 / 최대)',
gpu: 'GPU 관리',
monitoringTitle: 'GPU 모니터링',
currentHelper:
'현재 노드의 GPU/NPU/XPU 장치에 대한 실시간 상태와 리소스 사용량을 확인합니다. 상단에는 모든 장치의 요약이 표시되며, 상세 지표는 장치별로 표시됩니다.',
gpuHelper: '지원되는 GPU/XPU/NPU 관리 명령을 감지하지 못했습니다. 확인하고 다시 시도하세요!',
process: '프로세스 정보',
type: '유형',
@@ -2033,6 +2101,8 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: '자동 새로고침',
logWindowHelp: '최근 로그만 표시합니다. 전체 내용을 보려면 선택한 로그 파일을 다운로드하세요.',
prefixTokens: '접두사 토큰 수',
seed: '난수 시드',
report: '보고서',
@@ -2242,6 +2312,12 @@ const message = {
missing: '없음',
},
container: {
nvidiaSupport: 'NVIDIA GPU 지원',
nvidiaHelper:
'NVIDIA 컨테이너 런타임을 등록합니다. 먼저 NVIDIA Container Toolkit을 설치하세요. GPU는 각 컨테이너에 별도로 할당해야 합니다.',
nvidiaLocalOnly: '로컬 기본 Docker 소켓만 지원합니다.',
nvidiaChangeHelper:
'비활성화하면 NVIDIA 런타임을 사용하는 컨테이너가 시작되지 않을 수 있습니다. 이 스위치는 GPU 접근을 차단하지 않습니다.',
imageRemoteSearch: '원격 검색',
imageRemoteSearchHelper: 'Docker Hub 저장소 이미지만 검색할 수 있습니다',
imageRemoteSearchProxyRequired: '현재 실행 환경에서 원격 이미지를 검색하려면 먼저 프록시 서버를 설정하세요.',
+75
View File
@@ -1717,6 +1717,71 @@ const message = {
timePerOutputTokenChart: 'ເວລາລໍຖ້າລະຫວ່າງ Token',
},
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: 'ກວດສອບສະພາບແວດລ້ອມ',
passedLabel: 'ກວດສອບຜ່ານ',
issuesLabel: 'ບັນຫາສະພາບແວດລ້ອມ · {count}',
loadFailedLabel: 'ກວດສອບລົ້ມເຫຼວ',
notDetectedLabel: 'ບໍ່ພົບອຸປະກອນ',
unknownLabel: 'ບໍ່ຮູ້ສະຖານະ',
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: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ຮ່ວມກັນ',
systemMemoryUsed: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ແລ້ວ',
systemMemoryTotal: 'ໜ່ວຍຄວາມຈຳລະບົບທັງໝົດ',
@@ -1774,6 +1839,8 @@ const message = {
pcieWidth: 'ຄວາມກວ້າງ PCIe (ປັດຈຸບັນ / ສູງສຸດ)',
gpu: 'ການຈັດການ GPU',
monitoringTitle: 'ຕິດຕາມ GPU',
currentHelper:
'ເບິ່ງສະຖານະການເຮັດວຽກ ແລະ ການໃຊ້ຊັບພະຍາກອນແບບເວລາຈິງຂອງອຸປະກອນ GPU/NPU/XPU ໃນໂນດປັດຈຸບັນ. ພາບລວມດ້ານເທິງສະຫຼຸບຂໍ້ມູນຂອງທຸກອຸປະກອນ ແລະ ສະແດງຕົວຊີ້ວັດລະອຽດແຍກຕາມອຸປະກອນ.',
gpuHelper: 'ກວດບໍ່ພົບຄຳສັ່ງຈັດການ GPU/XPU/NPU ທີ່ຮອງຮັບ. ກະລຸນາກວດສອບ ແລະ ລອງໃໝ່!',
process: 'ຂໍ້ມູນໂປຣເຊສ',
processCount: 'ຈຳນວນໂປຣເຊສ',
@@ -2020,6 +2087,8 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'ໂຫຼດຄືນອັດຕະໂນມັດ',
logWindowHelp: 'ສະແດງສະເພາະບັນທຶກຫຼ້າສຸດ. ດາວໂຫຼດໄຟລ໌ບັນທຶກທີ່ເລືອກເພື່ອເບິ່ງເນື້ອຫາທັງໝົດ.',
prefixTokens: 'ຈຳນວນ Token ຄຳນຳໜ້າ',
seed: 'ຄ່າເລີ່ມຕົ້ນການສຸ່ມ',
report: 'ລາຍງານ',
@@ -2223,6 +2292,12 @@ const message = {
missing: 'ບໍ່ມີ',
},
container: {
nvidiaSupport: 'ຮອງຮັບ NVIDIA GPU',
nvidiaHelper:
'ລົງທະບຽນ NVIDIA runtime ສຳລັບຄອນເທນເນີ. ຕິດຕັ້ງ NVIDIA Container Toolkit ກ່ອນ ແລະ ຈັດສັນ GPU ໃຫ້ແຕ່ລະຄອນເທນເນີແຍກກັນ.',
nvidiaLocalOnly: 'ຮອງຮັບສະເພາະ Docker socket ເລີ່ມຕົ້ນໃນເຄື່ອງນີ້.',
nvidiaChangeHelper:
'ການປິດອາດເຮັດໃຫ້ຄອນເທນເນີທີ່ໃຊ້ NVIDIA runtime ເລີ່ມບໍ່ໄດ້. ສະວິດນີ້ບໍ່ໄດ້ປິດກັ້ນການເຂົ້າໃຊ້ GPU.',
imageRemoteSearch: 'ຄົ້ນຫາທາງໄກ',
imageRemoteSearchHelper: 'ຮອງຮັບການຄົ້ນຫາອິມເມຈໃນ Docker Hub ເທົ່ານັ້ນ',
imageRemoteSearchProxyRequired: 'ເພື່ອຄົ້ນຫາອິມເມຈທາງໄກໃນສະພາບແວດລ້ອມປັດຈຸບັນ ກະລຸນາຕັ້ງຄ່າພຣັອກຊີເຊີບເວີກ່ອນ.',
+79 -2
View File
@@ -1757,8 +1757,8 @@ const message = {
running: 'Permintaan berjalan',
waiting: 'Permintaan menunggu',
cacheUsage: 'Penggunaan cache KV',
promptThroughput: 'Daya pemprosesan token input',
generationThroughput: 'Daya pemprosesan token output',
promptThroughput: 'Kadar token input',
generationThroughput: 'Kadar token output',
requestThroughput: 'Kadar permintaan selesai',
timeToFirstToken: 'Purata kependaman token pertama',
timePerOutputToken: 'Purata kependaman antara token',
@@ -1778,6 +1778,73 @@ const message = {
timePerOutputTokenChart: 'Kependaman antara token',
},
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',
passedLabel: 'Semakan lulus',
issuesLabel: 'Isu persekitaran · {count}',
loadFailedLabel: 'Semakan gagal',
notDetectedLabel: 'Tiada peranti dikesan',
unknownLabel: 'Status tidak diketahui',
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',
systemMemoryUsed: 'Memori sistem digunakan',
systemMemoryTotal: 'Jumlah memori sistem',
@@ -1838,6 +1905,8 @@ const message = {
pcieWidth: 'Lebar PCIe (semasa / maksimum)',
gpu: 'Pengurusan 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!',
process: 'Maklumat Proses',
type: 'Jenis',
@@ -2091,6 +2160,8 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'Muat semula automatik',
logWindowHelp: 'Hanya log terkini dipaparkan. Muat turun fail log yang dipilih untuk melihat kandungan penuh.',
prefixTokens: 'Token awalan',
seed: 'Benih rawak',
report: 'Laporan',
@@ -2311,6 +2382,12 @@ const message = {
missing: 'Tiada',
},
container: {
nvidiaSupport: 'Sokongan GPU NVIDIA',
nvidiaHelper:
'Daftarkan runtime kontena NVIDIA. Pasang NVIDIA Container Toolkit dahulu. Peruntukkan GPU kepada setiap kontena secara berasingan.',
nvidiaLocalOnly: 'Hanya soket Docker lalai setempat disokong.',
nvidiaChangeHelper:
'Melumpuhkan mungkin menghalang kontena yang menggunakan runtime NVIDIA daripada bermula. Suis ini tidak menyekat akses GPU.',
imageRemoteSearch: 'Carian jauh',
imageRemoteSearchHelper: 'Hanya imej repositori Docker Hub boleh dicari',
imageRemoteSearchProxyRequired:
+84
View File
@@ -1776,6 +1776,80 @@ const message = {
timePerOutputTokenChart: 'Latência entre tokens',
},
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',
passedLabel: 'Verificação aprovada',
issuesLabel: 'Problemas no ambiente · {count}',
loadFailedLabel: 'Falha na verificação',
notDetectedLabel: 'Nenhum dispositivo detectado',
unknownLabel: 'Estado desconhecido',
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',
systemMemoryUsed: 'Memória do sistema em uso',
systemMemoryTotal: 'Memória total do sistema',
@@ -1836,6 +1910,8 @@ const message = {
pcieWidth: 'Largura PCIe (atual / máxima)',
gpu: 'Gerenciamento 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:
'Nenhum comando de gerenciamento de GPU/XPU/NPU compatível foi detectado. Verifique e tente novamente!',
process: 'Informações do Processo',
@@ -2090,6 +2166,8 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'Atualização automática',
logWindowHelp: 'Apenas os logs recentes são exibidos. Baixe o arquivo selecionado para ver todo o conteúdo.',
prefixTokens: 'Tokens de prefixo',
seed: 'Semente aleatória',
report: 'Relatório',
@@ -2311,6 +2389,12 @@ const message = {
missing: 'Não existe',
},
container: {
nvidiaSupport: 'Suporte a GPU NVIDIA',
nvidiaHelper:
'Registra o runtime de contêiner NVIDIA. Instale primeiro o NVIDIA Container Toolkit. Atribua GPUs a cada contêiner separadamente.',
nvidiaLocalOnly: 'Apenas o socket padrão do Docker local é suportado.',
nvidiaChangeHelper:
'Desativar pode impedir a inicialização de contêineres que usam o runtime NVIDIA. Esta opção não bloqueia o acesso à GPU.',
imageRemoteSearch: 'Pesquisa remota',
imageRemoteSearchHelper: 'Apenas imagens de repositórios do Docker Hub podem ser pesquisadas',
imageRemoteSearchProxyRequired:
+83
View File
@@ -1768,6 +1768,78 @@ const message = {
timePerOutputTokenChart: 'Задержка между токенами',
},
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: 'Проверка окружения',
passedLabel: 'Проверка пройдена',
issuesLabel: 'Проблемы окружения · {count}',
loadFailedLabel: 'Ошибка проверки',
notDetectedLabel: 'Устройства не обнаружены',
unknownLabel: 'Статус неизвестен',
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: 'Общая системная память',
systemMemoryUsed: 'Использовано системной памяти',
systemMemoryTotal: 'Всего системной памяти',
@@ -1828,6 +1900,8 @@ const message = {
pcieWidth: 'Ширина PCIe (текущая / максимальная)',
gpu: 'Управление GPU',
monitoringTitle: 'Мониторинг GPU',
currentHelper:
'Просматривайте состояние и использование ресурсов устройств GPU/NPU/XPU текущего узла в реальном времени. Вверху показана сводка по всем устройствам, а подробные показатели отображаются для каждого устройства отдельно.',
gpuHelper: 'Поддерживаемая команда управления GPU/XPU/NPU не обнаружена. Проверьте и повторите попытку!',
process: 'Информация о Процессе',
type: 'Тип',
@@ -2081,6 +2155,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'Автообновление',
logWindowHelp:
'Показана только последняя часть журнала. Скачайте выбранный файл, чтобы посмотреть его полностью.',
prefixTokens: 'Токены префикса',
seed: 'Начальное значение генератора случайных чисел',
report: 'Отчёт',
@@ -2304,6 +2381,12 @@ const message = {
missing: 'Отсутствует',
},
container: {
nvidiaSupport: 'Поддержка GPU NVIDIA',
nvidiaHelper:
'Регистрирует среду выполнения контейнеров NVIDIA. Сначала установите NVIDIA Container Toolkit. Назначайте GPU каждому контейнеру отдельно.',
nvidiaLocalOnly: 'Поддерживается только стандартный локальный сокет Docker.',
nvidiaChangeHelper:
'Отключение может помешать запуску контейнеров со средой выполнения NVIDIA. Этот переключатель не блокирует доступ к GPU.',
imageRemoteSearch: 'Удалённый поиск',
imageRemoteSearchHelper: 'Поддерживается поиск образов только в репозиториях Docker Hub',
imageRemoteSearchProxyRequired: 'Для поиска удалённых образов в текущей среде сначала настройте прокси-сервер.',
+81
View File
@@ -1772,6 +1772,76 @@ const message = {
timePerOutputTokenChart: 'Token arası gecikme',
},
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',
passedLabel: 'Kontrol başarılı',
issuesLabel: 'Ortam sorunları · {count}',
loadFailedLabel: 'Kontrol başarısız',
notDetectedLabel: 'Cihaz algılanmadı',
unknownLabel: 'Durum bilinmiyor',
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',
systemMemoryUsed: 'Kullanılan sistem belleği',
systemMemoryTotal: 'Toplam sistem belleği',
@@ -1831,6 +1901,8 @@ const message = {
pcieWidth: 'PCIe genişliği (geçerli / en yüksek)',
gpu: 'GPU Yönetimi',
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!',
process: 'İşlem Bilgisi',
type: 'Tür',
@@ -2083,6 +2155,9 @@ const message = {
statusUnavailable: 'Service status unavailable. Refresh to retry.',
},
benchmarkPlan: {
logAutoRefresh: 'Otomatik yenileme',
logWindowHelp:
'Yalnızca son günlük kayıtları gösterilir. Tam içeriği görmek için seçili günlük dosyasını indirin.',
prefixTokens: 'Önek token sayısı',
seed: 'Rastgelelik tohumu',
report: 'Rapor',
@@ -2302,6 +2377,12 @@ const message = {
missing: 'Mevcut değil',
},
container: {
nvidiaSupport: 'NVIDIA GPU desteği',
nvidiaHelper:
'NVIDIA konteyner çalışma zamanını kaydeder. Önce NVIDIA Container Toolkit kurun. GPU’ları her konteynere ayrı ayrı atayın.',
nvidiaLocalOnly: 'Yalnızca yerel varsayılan Docker soketi desteklenir.',
nvidiaChangeHelper:
'Devre dışı bırakmak NVIDIA çalışma zamanını kullanan konteynerlerin başlamasını engelleyebilir. Bu anahtar GPU erişimini engellemez.',
imageRemoteSearch: 'Uzak arama',
imageRemoteSearchHelper: 'Yalnızca Docker Hub deposu imajları aranabilir',
imageRemoteSearchProxyRequired:
+73 -2
View File
@@ -1636,8 +1636,8 @@ const message = {
running: '執行中請求',
waiting: '等待中請求',
cacheUsage: 'KV Cache 使用率',
promptThroughput: '輸入 Token 吞吐',
generationThroughput: '輸出 Token 吞吐',
promptThroughput: '輸入 Token 速率',
generationThroughput: '輸出 Token 速率',
requestThroughput: '完成請求速率',
timeToFirstToken: '平均首 Token 延遲',
timePerOutputToken: '平均 Token 間延遲',
@@ -1656,6 +1656,69 @@ const message = {
timePerOutputTokenChart: 'Token 間延遲',
},
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: '環境檢查',
passedLabel: '檢查通過',
issuesLabel: '環境異常 · {count} 項',
loadFailedLabel: '檢查失敗',
notDetectedLabel: '未偵測到裝置',
unknownLabel: '狀態未知',
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: '系統共享記憶體',
systemMemoryUsed: '系統已用記憶體',
systemMemoryTotal: '系統總記憶體',
@@ -1713,6 +1776,8 @@ const message = {
pcieWidth: 'PCIe 寬度(目前 / 最大)',
gpu: 'GPU 管理',
monitoringTitle: 'GPU 監控',
currentHelper:
'查看目前節點 GPU/NPU/XPU 裝置的即時運作狀態與資源使用情況。頂部彙總所有裝置,詳細指標依裝置顯示。',
gpuHelper: '目前系統未偵測到支援的 GPU/XPU/NPU 管理指令,請檢查後重試',
process: '行程資訊',
type: '類型',
@@ -1953,6 +2018,8 @@ const message = {
statusUnavailable: '無法取得服務狀態,請重新整理後重試。',
},
benchmarkPlan: {
logAutoRefresh: '自動重新整理',
logWindowHelp: '僅顯示最近部分日誌,可下載目前日誌檔案查看完整內容。',
prefixTokens: '前綴 Token 數',
seed: '隨機種子',
report: '報告',
@@ -2148,6 +2215,10 @@ const message = {
missing: '不存在',
},
container: {
nvidiaSupport: 'NVIDIA GPU 支援',
nvidiaHelper: '註冊 NVIDIA 容器執行環境,需先安裝 NVIDIA Container Toolkit。各容器仍需個別分配 GPU。',
nvidiaLocalOnly: '僅支援本機預設 Docker Socket。',
nvidiaChangeHelper: '關閉後,使用 NVIDIA 執行環境的容器可能無法啟動;此開關不會禁止容器存取 GPU。',
imageRemoteSearch: '遠端搜尋',
imageRemoteSearchHelper: '僅支援搜尋 Docker Hub 倉庫映像',
imageRemoteSearchProxyRequired: '目前運行環境下,如需搜尋遠端映像,請先設定代理伺服器',
+72 -2
View File
@@ -1649,8 +1649,8 @@ const message = {
running: '运行中请求',
waiting: '等待中请求',
cacheUsage: 'KV Cache 使用率',
promptThroughput: '输入 Token 吞吐',
generationThroughput: '输出 Token 吞吐',
promptThroughput: '输入 Token 速率',
generationThroughput: '输出 Token 速率',
requestThroughput: '完成请求速率',
timeToFirstToken: '平均首 Token 延迟',
timePerOutputToken: '平均 Token 间延迟',
@@ -1669,6 +1669,68 @@ const message = {
timePerOutputTokenChart: 'Token 间延迟',
},
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: '环境检查',
passedLabel: '检查通过',
issuesLabel: '环境异常 · {count} 项',
loadFailedLabel: '检查失败',
notDetectedLabel: '未检测到设备',
unknownLabel: '状态未知',
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: '系统共享内存',
systemMemoryUsed: '系统已用内存',
systemMemoryTotal: '系统总内存',
@@ -1726,6 +1788,8 @@ const message = {
pcieWidth: 'PCIe 宽度(当前 / 最大)',
gpu: 'GPU 管理',
monitoringTitle: 'GPU 监控',
currentHelper:
'查看当前节点 GPU/NPU/XPU 设备的实时运行状态与资源使用情况。顶部汇总全部设备,详细指标按设备展示。',
gpuHelper: '当前系统未检测到支持的 GPU/XPU/NPU 管理指令,请检查后重试!',
process: '进程信息',
processCount: '进程数',
@@ -1962,6 +2026,8 @@ const message = {
statusUnavailable: '无法获取服务状态,请刷新重试。',
},
benchmarkPlan: {
logAutoRefresh: '自动刷新',
logWindowHelp: '仅展示最近部分日志,可下载当前日志文件查看完整内容。',
prefixTokens: '前缀 Token 数',
seed: '随机种子',
report: '报告',
@@ -2157,6 +2223,10 @@ const message = {
missing: '不存在',
},
container: {
nvidiaSupport: 'NVIDIA GPU 支持',
nvidiaHelper: '注册 NVIDIA 容器运行时,需要先安装 NVIDIA Container Toolkit。具体容器仍需单独分配 GPU。',
nvidiaLocalOnly: '仅支持本机默认 Docker Socket。',
nvidiaChangeHelper: '关闭后,使用 NVIDIA 运行时的容器可能无法启动;这不是禁止容器访问 GPU 的安全开关。',
imageRemoteSearch: '远程搜索',
imageRemoteSearchHelper: '仅支持搜索 Docker Hub 仓库镜像',
imageRemoteSearchProxyRequired: '当前运行环境下,如需搜索远程镜像,请优先配置代理服务器',
@@ -15,7 +15,7 @@
<el-menu
v-else
:default-active="activeMenu"
:router="false"
:router="true"
:collapse="isCollapse"
:collapse-transition="false"
: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 { MenuStore, TabsStore } from '@/store';
import { getSystemAvailable } from '@/api/modules/setting';
import { openMenu } from '@/utils/router';
import { useRoute, useRouter } from 'vue-router';
import { loadMasterProductProFromDB, loadProductProFromDB } from '@/utils/xpack';
import { useTheme } from '@/global/use-theme';
import TaskList from '@/components/task-list/index.vue';
@@ -95,6 +95,8 @@ const openTask = () => {
menuStore.setCollapse();
}
};
const router = useRouter();
const route = useRoute();
const menuStore = MenuStore();
const tabsStore = TabsStore();
@@ -149,7 +151,9 @@ watch(
},
);
const handleMenuClick = async (path) => {
await openMenu(path);
await router.push({ path: path });
tabsStore.addTab(route);
tabsStore.activeTabPath = route.path;
};
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 NProgress from '@/config/nprogress';
import { useGlobalStore } from '@/composables/useGlobalStore';
@@ -11,6 +11,7 @@ import { TerminalDockSessionStore, TerminalSessionStore } from '@/store';
const axiosCanceler = new AxiosCanceler();
let isRedirecting = false;
const enterpriseLicenseCheckWhiteList = ['EnterpriseLicenseRequired', 'entrance', 'login', 'Expired'];
const noLoginWhiteList = ['entrance', 'login', 'file-share', '404', 'Expired'];
@@ -29,7 +30,7 @@ const clearLoginStatus = () => {
TerminalDockSessionStore().closeAll();
};
router.beforeEach(async (to) => {
router.beforeEach(async (to, from) => {
const { entrance, isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isLogin, isOnRestart } =
useGlobalStore();
NProgress.start();
@@ -102,11 +103,34 @@ router.beforeEach(async (to) => {
if (to.name === 'Expired') {
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) {
const query = { ...to.query };
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)) {
@@ -120,10 +144,11 @@ router.beforeEach(async (to) => {
router.afterEach((to, _from, failure) => {
prunePanelCache();
if (failure) {
isRedirecting = false;
NProgress.done();
return;
}
if (to.meta.activeMenu && !to.meta.ignoreTab) {
if (to.meta.activeMenu && !to.meta.ignoreTab && !isRedirecting) {
let notMathParam = true;
if (to.matched.some((record) => record.path.includes(':'))) {
notMathParam = false;
@@ -141,6 +166,7 @@ router.afterEach((to, _from, failure) => {
}
}
isRedirecting = false;
NProgress.done();
});
+1
View File
@@ -28,5 +28,6 @@ declare interface RouterButton {
path?: string;
name?: string;
count?: number;
disabled?: boolean;
permission?: string | string[];
}
+6 -36
View File
@@ -4,7 +4,6 @@ import { hasRouteAccess } from '@/utils/rbac';
import i18n from '@/lang';
import { MsgError } from '@/utils/message';
import type { RouteLocationRaw } from 'vue-router';
import { panelCacheKey, readPanelCache, removePanelCache } from '@/utils/panel-cache';
const pushWithAccessCheck = async (to: RouteLocationRaw) => {
const resolvedRoute = router.resolve(to);
@@ -16,53 +15,24 @@ const pushWithAccessCheck = async (to: RouteLocationRaw) => {
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) => {
await openPage({ name: name });
await pushWithAccessCheck({ name: name });
};
export const routerToPath = async (path: string) => {
await openPage({ path: path });
await pushWithAccessCheck({ path: path });
};
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) => {
await openPage({ name: name, query: query });
await pushWithAccessCheck({ name: name, query: query });
};
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) => {
@@ -70,7 +40,7 @@ export const jumpToPath = (router: any, path: string) => {
};
export const routerToNameWithParams = async (name: string, params: any) => {
await openPage({ name: name, params: params });
await pushWithAccessCheck({ name: name, params: params });
};
const tabStoreMiddleWare = () => {
+3 -3
View File
@@ -139,11 +139,11 @@ export const bindSearchState = (key: () => string | undefined, fields: SearchFie
interface SearchStateOptions {
scope?: () => string | number | undefined;
onRevisit?: () => unknown;
onRevisit: () => unknown;
}
/** 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 binding = bindSearchState(() => {
const scope = options.scope?.();
@@ -160,6 +160,6 @@ export const useSearchState = (page: string, fields: SearchFields, options: Sear
onActivated(async () => {
if (!deactivated) return;
deactivated = false;
if (await binding.resume()) return options.onRevisit?.();
if (await binding.resume()) return options.onRevisit();
});
};
@@ -20,7 +20,6 @@
v-if="!noApp"
v-model:view-mode="viewMode"
:data="items"
:cache-pagination="false"
:pagination-config="paginationConfig"
@search="search"
>
File diff suppressed because it is too large Load Diff
+13 -12
View File
@@ -1,7 +1,7 @@
<template>
<div v-loading="loading">
<RouterMenu />
<template v-if="supported">
<template v-if="supported && options.length">
<el-card class="history-toolbar">
<div class="flex flex-wrap gap-3 items-center">
<el-select style="max-width: 100%" class="p-w-400" v-model="selectedDevice" @change="search">
@@ -26,7 +26,10 @@
:clearable="false"
@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 v-if="history?.sampleCount && history.bucketSeconds > 0" class="history-summary">
@@ -67,26 +70,23 @@
</el-col>
</el-row>
</template>
<LayoutContent v-else-if="!loading" :title="$t('aiTools.gpu.monitoringTitle')" :divider="true">
<LayoutContent v-else :title="$t('aiTools.gpu.monitoringTitle')">
<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 #main>
<el-alert
v-if="failed"
class="common-prompt"
:title="$t('aiTools.gpu.historyLoadFailed')"
type="error"
:closable="false"
show-icon
/>
<div v-else class="app-warn">
<div class="flx-center">
<span>{{ $t('aiTools.gpu.gpuHelper') }}</span>
</div>
<div>
<img src="@/assets/images/no_app.svg" />
</div>
</div>
<el-empty v-else-if="!loading" :description="$t('aiTools.gpu.environment.notDetected')" />
</template>
</LayoutContent>
</div>
@@ -371,6 +371,7 @@ const loadOptions = async () => {
}
};
const refresh = () => {
if (loading.value) return;
if (followNow) timeRangeGlobal.value = [timeRangeGlobal.value[0], new Date()];
void loadOptions();
};
+11 -2
View File
@@ -9,8 +9,16 @@
<script lang="ts" setup>
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'),
path: '/ai/gpu/current',
@@ -18,6 +26,7 @@ const buttons = [
{
label: i18n.global.t('aiTools.gpu.history'),
path: '/ai/gpu/history',
disabled: props.historyDisabled,
},
];
]);
</script>
+1 -6
View File
@@ -16,12 +16,7 @@
<TableRefresh @search="search()" />
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="items"
@search="search()"
>
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
<el-table-column
:label="$t('commons.table.name')"
fix
@@ -38,6 +38,7 @@
>
<el-select
v-model="form.localEngineId"
fit-input-width
filterable
:loading="loadingEngines"
@change="handleLocalEngineChange"
@@ -45,26 +46,13 @@
<el-option v-for="item in localEngines" :key="item.id" :label="item.name" :value="item.id">
<div class="local-engine-option">
<span class="local-engine-option__name" :title="item.name">{{ item.name }}</span>
<el-tag
<AiModelList
v-if="item.models.length"
size="small"
type="info"
class="local-engine-option__model"
>
<span :title="item.models[0]">{{ item.models[0] }}</span>
</el-tag>
<el-popover v-if="item.models.length > 1" trigger="click" placement="bottom" :width="320">
<template #reference>
<el-button size="small" class="local-engine-option__more" @click.stop>
+{{ item.models.length - 1 }}
</el-button>
</template>
<div class="local-engine-models">
<el-tag v-for="model in item.models.slice(1)" :key="model" size="small" type="info">
{{ model }}
</el-tag>
</div>
</el-popover>
class="local-engine-option__models"
:models="item.models"
:visible-count="1"
tag-type="info"
/>
</div>
</el-option>
</el-select>
@@ -331,6 +319,7 @@
</template>
<script setup lang="ts">
import AiModelList from '@/components/ai-model-list/index.vue';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { FormInstance } from 'element-plus';
import { AI } from '@/api/interface/ai';
@@ -992,34 +981,8 @@ defineExpose({
text-overflow: ellipsis;
}
&__model {
max-width: 55%;
min-width: 0;
:deep(.el-tag__content) {
overflow: hidden;
text-overflow: ellipsis;
}
}
&__more {
flex-shrink: 0;
}
}
.local-engine-models {
display: flex;
flex-wrap: wrap;
gap: 6px;
max-height: 240px;
overflow: auto;
.el-tag {
height: auto;
padding: 4px 6px;
white-space: normal;
overflow-wrap: anywhere;
max-width: 100%;
&__models {
max-width: 65%;
}
}
+19 -36
View File
@@ -53,7 +53,6 @@
<ComplexTable
:data="connections"
row-key="id"
:cache-pagination="false"
:pagination-config="pagination"
:columns="accountColumns"
local-key="modelServiceColumn"
@@ -70,7 +69,7 @@
: 'aiTools.model.cloudService',
)
"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-icon
class="service-source-icon"
@@ -116,36 +115,27 @@
</el-table-column>
<el-table-column prop="models" :label="$t('aiTools.model.model')" min-width="200">
<template #default="{ row }">
<div v-if="row.models?.length" class="model-line">
<span class="cell-ellipsis" :title="row.models[0].id">{{ row.models[0].id }}</span>
<el-button
v-if="!row.appInstallId && row.models.length > 1"
size="small"
class="model-more"
@click="poolRef.open(row)"
>
+{{ row.models.length - 1 }}
</el-button>
<el-tooltip
v-if="row.appInstallId && row.models.length > 1"
:content="
row.models
.slice(1)
.map((item) => item.id)
.join(', ')
"
placement="top"
>
<el-button size="small" class="model-more">+{{ row.models.length - 1 }}</el-button>
</el-tooltip>
</div>
<AiModelList
v-if="row.models?.length"
:models="row.models.map((item) => item.id)"
:visible-count="1"
>
<template v-if="!row.appInstallId" #footer>
<el-button link type="primary" @click="poolRef.open(row)">
{{ $t('aiTools.agents.modelPool') }}
</el-button>
</template>
</AiModelList>
<span v-if="!row.models?.length">—</span>
</template>
</el-table-column>
<el-table-column prop="verified" :label="$t('aiTools.model.connectionStatus')" min-width="130">
<template #default="{ row }">
<div class="verification-status">
<el-tooltip :disabled="!row.verifiedAt && !row.verifyError" placement="top">
<el-tooltip
:disabled="!row.verifiedAt && !row.verifyError"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<template #content>
<div v-if="row.verifiedAt">
{{ $t('aiTools.model.verifiedAt') }}:
@@ -165,7 +155,7 @@
<el-tooltip
v-if="row.validateAvailability"
:content="$t('aiTools.model.reverify')"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-button
v-permission
@@ -269,12 +259,13 @@
</template>
<script setup lang="ts">
import AiModelList from '@/components/ai-model-list/index.vue';
import { computed, reactive, ref, toRef, watch } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessageBox } from 'element-plus';
import { Cloudy, Connection, Monitor, RefreshRight } from '@element-plus/icons-vue';
import type { AI } from '@/api/interface/ai';
import type { FuTableColumnConfig } from '@/components/table/shared';
import { TABLE_TOOLTIP_OPTIONS, type FuTableColumnConfig } from '@/components/table/shared';
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts, verifyAgentAccount } from '@/api/modules/ai';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { getAgentProviderDisplayName } from '@/utils/agent';
@@ -588,14 +579,6 @@ watch(
white-space: nowrap;
text-overflow: ellipsis;
}
.model-line {
display: flex;
align-items: center;
}
.model-more {
flex-shrink: 0;
margin-left: 8px;
}
.model-filter {
width: 220px;
}
@@ -50,7 +50,6 @@
v-else-if="modelInstalled"
:selection-context="() => [searchName]"
row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:selects="selects"
@sort-change="search"
@@ -84,14 +83,18 @@
<el-tag v-if="row.status === 'Deleted'" type="info">
{{ $t('commons.status.notfound') }}
</el-tag>
<el-tooltip v-if="row.status === 'Unknown'" :content="row.message" placement="top">
<el-tooltip
v-if="row.status === 'Unknown'"
:content="row.message"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-tag type="warning">{{ $t('commons.status.unknown') }}</el-tag>
</el-tooltip>
<el-tooltip
v-if="row.status === 'Canceled'"
:content="row.message"
:disabled="!row.message"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-tag type="danger">{{ $t('commons.status.systemrestart') }}</el-tag>
</el-tooltip>
@@ -151,6 +154,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import AddDialog from '@/views/ai/ollama/add/index.vue';
import TaskLog from '@/components/log/task/index.vue';
@@ -216,6 +216,7 @@
<el-divider class="!my-3" />
<el-table
:tooltip-options="TABLE_OVERFLOW_TOOLTIP_OPTIONS"
v-if="composeContainers.length > 0"
:data="tableData"
size="small"
@@ -522,6 +523,7 @@
</template>
<script lang="ts" setup>
import { TABLE_OVERFLOW_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import { computed, h, ref } from 'vue';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
@@ -85,7 +85,6 @@
<template #main>
<ComplexTable
:selection-context="() => [searchName, activeTag, includeAppStore, props.filters]"
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:view-mode="viewMode"
v-model:selects="selects"
@@ -308,7 +307,7 @@
:key="item"
:hide-after="20"
:content="item"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-button
v-if="item.indexOf('->') !== -1"
@@ -448,7 +447,12 @@
<el-checkbox border v-model="showIPv6Ports">IPv6</el-checkbox>
</div>
<div v-if="filteredPorts.length" class="container-port-dialog">
<el-tooltip v-for="item in filteredPorts" :key="item" :content="item" placement="top">
<el-tooltip
v-for="item in filteredPorts"
:key="item"
:content="item"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-button
:icon="item.indexOf('->') !== -1 ? 'Position' : undefined"
plain
@@ -485,6 +489,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import PruneDialog from '@/views/container/container/prune/index.vue';
import RenameDialog from '@/views/container/container/rename/index.vue';
@@ -120,7 +120,7 @@
</el-tab-pane>
<el-tab-pane :label="$t('container.volume')" name="storage">
<el-table :data="inspectData?.Mounts" border>
<el-table :tooltip-options="TABLE_OVERFLOW_TOOLTIP_OPTIONS" :data="inspectData?.Mounts" border>
<el-table-column :label="$t('commons.table.type')" width="100">
<template #default="{ row }">
<el-tag size="small">
@@ -169,6 +169,7 @@
</template>
<script lang="ts" setup>
import { TABLE_OVERFLOW_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { ref } from 'vue';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import { routerToFileWithPath } from '@/utils/router';
@@ -17,7 +17,7 @@
<template #default="{ row }">
<el-select v-if="row.type === 'volume'" filterable v-model="row.sourceDir">
<div v-for="(item, indexV) of volumeOptions" :key="indexV">
<el-tooltip :hide-after="20" :content="item.option" placement="top">
<el-tooltip :hide-after="20" :content="item.option" v-bind="TABLE_TOOLTIP_OPTIONS">
<el-option :value="item.option" :label="item.option.substring(0, 30)" />
</el-tooltip>
</div>
@@ -82,6 +82,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { Container } from '@/api/interface/container';
import { listVolume } from '@/api/modules/container';
import { ref, onMounted } from 'vue';
+2 -1
View File
@@ -36,7 +36,6 @@
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@sort-change="search"
@@ -51,6 +50,7 @@
</el-text>
<div class="float-right">
<el-tooltip
v-bind="TABLE_TOOLTIP_OPTIONS"
:content="row.isPinned ? $t('commons.table.unpin') : $t('commons.table.pin')"
>
<el-button
@@ -162,6 +162,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import { toRef, reactive, ref, computed, watch, onActivated, onDeactivated, onBeforeUnmount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
@@ -12,7 +12,7 @@
<span class="input-help">{{ $t('container.imageRemoteSearchHelper') }}</span>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="items">
<el-table :tooltip-options="TABLE_OVERFLOW_TOOLTIP_OPTIONS" v-loading="loading" :data="items">
<el-table-column prop="name" :label="$t('container.imageName')" min-width="180" />
<el-table-column
prop="description"
@@ -42,6 +42,7 @@
</template>
<script setup lang="ts">
import { TABLE_OVERFLOW_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { onBeforeUnmount, ref, watch } from 'vue';
import { searchRemoteImages, type RemoteImage } from '@/api/modules/container';
import { getSettingBaseInfo } from '@/api/modules/setting';
@@ -28,7 +28,6 @@
<ComplexTable
:selection-context="() => [searchName]"
row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
@@ -20,7 +20,6 @@
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
@@ -149,6 +149,18 @@
<el-switch v-permission v-model="form.iptables" @change="handleIptables"></el-switch>
<span class="input-help">{{ $t('container.iptablesHelper1') }}</span>
</el-form-item>
<el-form-item :label="$t('container.nvidiaSupport')" prop="nvidia">
<el-switch
v-permission
v-model="form.nvidia"
:disabled="!form.nvidiaSupported"
@change="handleNVIDIA"
/>
<span class="input-help">{{ $t('container.nvidiaHelper') }}</span>
<span v-if="!form.nvidiaSupported" class="input-help">
{{ $t('container.nvidiaLocalOnly') }}
</span>
</el-form-item>
<el-form-item label="Live restore" prop="liveRestore">
<el-switch
v-permission
@@ -239,6 +251,7 @@
<ConfirmDialog ref="confirmDialogRefIpv6" @confirm="onSaveIPv6" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefIptable" @confirm="onSubmitOpenIPtable" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefLog" @confirm="onSubmitSaveLog" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefNVIDIA" @confirm="onSubmitSaveNVIDIA" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefLive" @confirm="onSubmitSaveLive" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefCgroup" @confirm="onSubmitSaveCgroup" @cancel="search" />
@@ -288,6 +301,7 @@ const confirmDialogRefLog = ref();
const mirrorRef = ref();
const registriesRef = ref();
const confirmDialogRefLive = ref();
const confirmDialogRefNVIDIA = ref();
const confirmDialogRefCgroup = ref();
const confirmDialogRefIptable = ref();
const confirmDialogRefIpv6 = ref();
@@ -302,6 +316,8 @@ const form = reactive({
mirrors: '',
registries: '',
liveRestore: false,
nvidia: false,
nvidiaSupported: false,
iptables: true,
cgroupDriver: '',
@@ -407,6 +423,19 @@ const onSubmitOpenIPtable = () => {
save('IPtables', 'enable');
};
const handleNVIDIA = () => {
confirmDialogRefNVIDIA.value.acceptParams({
header: i18n.global.t('database.confChange'),
operationInfo: form.nvidia
? i18n.global.t('container.restartHelper')
: `${i18n.global.t('container.nvidiaChangeHelper')} ${i18n.global.t('container.restartHelper')}`,
submitInputInfo: i18n.global.t('database.restartNow'),
});
};
const onSubmitSaveNVIDIA = () => {
save('NVIDIA', form.nvidia ? 'enable' : 'disable');
};
const handleLive = async () => {
let params = {
header: i18n.global.t('database.confChange'),
@@ -509,6 +538,8 @@ const search = async () => {
form.version = res.data.version;
form.cgroupDriver = res.data.cgroupDriver || 'cgroupfs';
form.liveRestore = res.data.liveRestore;
form.nvidia = res.data.nvidia;
form.nvidiaSupported = res.data.nvidiaSupported;
form.iptables = res.data.iptables;
form.mirrors = res.data.registryMirrors ? res.data.registryMirrors.join('\n') : '';
form.registries = res.data.insecureRegistries ? res.data.insecureRegistries.join('\n') : '';
@@ -40,7 +40,6 @@
<ComplexTable
:selection-context="() => [searchName]"
row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
@@ -28,7 +28,6 @@
<ComplexTable
:selection-context="() => [searchName]"
row-key="name"
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
@@ -88,7 +87,7 @@
prop="mountpoint"
>
<template #default="{ row }">
<el-tooltip :content="row.mountpoint">
<el-tooltip :content="row.mountpoint" v-bind="TABLE_TOOLTIP_OPTIONS">
<el-button
v-permission:view="'host_file_view'"
type="primary"
@@ -125,6 +124,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import CreateDialog from '@/views/container/volume/create/index.vue';
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
@@ -55,7 +55,6 @@
<ComplexTable
:selection-context="() => [searchName, searchGroupID]"
row-key="id"
:cache-pagination="false"
:pagination-config="paginationConfig"
:default-sort="
paginationConfig.order !== 'null'
@@ -70,7 +70,6 @@
:selection-context="() => [searchInfo, group]"
row-key="id"
v-model:selects="selects"
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
@@ -106,7 +106,6 @@
<ComplexTable
v-if="currentDB"
:class="{ mask: maskShow }"
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" />
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<ComplexTable :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 :label="$t('database.version')" prop="version" />
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
@@ -10,7 +10,11 @@
{{ $t('database.addUserAuthorization') }}
</el-button>
</div>
<el-table :data="authorizedUsers" :empty-text="$t('commons.msg.noneData')">
<el-table
:tooltip-options="TABLE_OVERFLOW_TOOLTIP_OPTIONS"
:data="authorizedUsers"
:empty-text="$t('commons.msg.noneData')"
>
<el-table-column :label="$t('commons.table.user')" show-overflow-tooltip min-width="180">
<template #default="{ row }">{{ row.username }}@{{ row.host }}</template>
</el-table-column>
@@ -160,6 +164,7 @@
</template>
<script lang="ts" setup>
import { TABLE_OVERFLOW_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { computed, reactive, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import Tooltip from '@/components/tooltip/index.vue';
+4 -2
View File
@@ -118,7 +118,6 @@
<template #main>
<ComplexTable
v-if="currentDB"
:cache-pagination="false"
:pagination-config="paginationConfig"
:class="{ mask: maskShow }"
@sort-change="search"
@@ -131,7 +130,9 @@
<Tooltip v-if="!row.isDelete" :islink="false" :text="row.name" />
<div v-else>
<span v-if="row.name.length < 15">{{ row.name }}</span>
<el-tooltip v-else :content="row.name">{{ row.name.substring(0, 10) }}...</el-tooltip>
<el-tooltip v-else :content="row.name" v-bind="TABLE_TOOLTIP_OPTIONS">
{{ row.name.substring(0, 10) }}...
</el-tooltip>
<el-tag round type="info" class="ml-1" size="small">
{{ $t('database.isDelete') }}
</el-tag>
@@ -227,6 +228,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import BindDialog from '@/views/database/mysql/bind/index.vue';
import UserDialog from '@/views/database/mysql/user/index.vue';
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" />
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<ComplexTable :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('database.address')" prop="address" />
<el-table-column :label="$t('commons.login.username')" prop="username" />
@@ -51,7 +51,7 @@
v-for="item in userDatabases(row).slice(0, 1)"
:key="row.username + '@' + row.host + item"
:content="item"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-tag size="small">{{ item }}</el-tag>
</el-tooltip>
@@ -66,7 +66,7 @@
v-for="item in userDatabases(row)"
:key="row.username + '@' + row.host + item"
:content="item"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-tag size="small">{{ item }}</el-tag>
</el-tooltip>
@@ -203,6 +203,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { computed, reactive, ref } from 'vue';
import Tooltip from '@/components/tooltip/index.vue';
import i18n from '@/lang';
@@ -93,7 +93,6 @@
<ComplexTable
v-if="currentDB"
:class="{ mask: maskShow }"
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
@@ -105,7 +104,9 @@
<Tooltip v-if="!row.isDelete" :islink="false" :text="row.name" />
<div v-else>
<span v-if="row.name.length < 15">{{ row.name }}</span>
<el-tooltip v-else :content="row.name">{{ row.name.substring(0, 10) }}...</el-tooltip>
<el-tooltip v-else :content="row.name" v-bind="TABLE_TOOLTIP_OPTIONS">
{{ row.name.substring(0, 10) }}...
</el-tooltip>
<el-tag round type="info" class="ml-1" size="small">
{{ $t('database.isDelete') }}
</el-tag>
@@ -242,6 +243,7 @@
</template>
<script lang="ts" setup>
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import BindDialog from '@/views/database/postgresql/bind/index.vue';
import OperateDialog from '@/views/database/postgresql/create/index.vue';
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" />
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<ComplexTable :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('database.address')" prop="address" />
<el-table-column :label="$t('commons.login.username')" prop="username" />
@@ -7,7 +7,12 @@
<el-button v-permission @click="batchDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
<el-table :data="data" class="mt-5" @selection-change="handleSelectionChange">
<el-table
:tooltip-options="TABLE_OVERFLOW_TOOLTIP_OPTIONS"
:data="data"
class="mt-5"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" fix />
<el-table-column :label="$t('commons.table.name')" min-width="50" show-overflow-tooltip>
<template #default="{ row }">
@@ -75,6 +80,7 @@
</template>
<script setup lang="ts">
import { TABLE_OVERFLOW_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { Command } from '@/api/interface/command';
import { deleteCommand, getCommandPage, addCommand, editCommand } from '@/api/modules/command';
import { reactive, ref } from 'vue';
@@ -10,13 +10,7 @@
<TableSearch @search="search()" v-model:searchName="searchName" />
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
@sort-change="search"
@search="search"
:data="data"
>
<ComplexTable :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('database.address')" prop="address" />
<el-table-column :label="$t('commons.login.password')" prop="password">
+7 -1
View File
@@ -4,7 +4,12 @@
<template #header-r>
<el-popover placement="left" :width="226" trigger="click">
<el-input size="small" v-model="filter" clearable @input="loadOption()" />
<el-table :show-header="false" :data="options" max-height="150px">
<el-table
:tooltip-options="TABLE_OVERFLOW_TOOLTIP_OPTIONS"
:show-header="false"
:data="options"
max-height="150px"
>
<el-table-column prop="key" width="120" show-overflow-tooltip />
<el-table-column prop="name">
<template #default="{ row }">
@@ -143,6 +148,7 @@
</template>
<script lang="ts" setup>
import { TABLE_OVERFLOW_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { getAppIconUrl, installedOp } from '@/api/modules/app';
import { changeLauncherStatus, loadAppLauncher, loadAppLauncherOption } from '@/api/modules/dashboard';
import i18n from '@/lang';
+244 -4
View File
@@ -99,6 +99,9 @@
>
<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 v-if="hasVllm" value="vllm">
{{ $t('aiTools.vllmMonitor.tokenThroughput') }}
</el-radio-button>
</el-radio-group>
<el-select
v-if="chartOption === 'network'"
@@ -128,6 +131,26 @@
:value="item"
/>
</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 #body>
<div class="monitor-chart-content">
@@ -169,6 +192,47 @@
:dataZoom="true"
/>
</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>
</template>
</CardWithHeader>
@@ -443,7 +507,7 @@
</template>
<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 AppLauncher from '@/views/home/app/index.vue';
import VCharts from '@/components/v-charts/index.vue';
@@ -478,6 +542,9 @@ import {
} from '@/utils/dashboardCache';
import { MsgSuccess } from '@/utils/message';
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();
import { useGlobalStore } from '@/composables/useGlobalStore';
const {
@@ -489,8 +556,146 @@ const {
hasNewVersion,
isAdminOrNodeAdmin,
isXpackOrEE,
isProductPro,
currentNode,
} = 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 = {
safeStatus: 10 * 60 * 1000,
netOptions: 60 * 60 * 1000,
@@ -505,7 +710,7 @@ const monitorTagsStyle = {
const monitorChartEmptyLength = 20;
const loadMonitorEmptyData = () => Array.from({ length: monitorChartEmptyLength }, () => null);
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 loadIOChartOption = () => ({
xData: loadMonitorChartTime(timeIODatas.value),
@@ -718,6 +923,7 @@ const updateCurrentInfo = (data: Dashboard.CurrentInfo) => {
const changeOption = async () => {
isInit.value = true;
if (chartOption.value === 'vllm') void loadVllmOptions();
loadData();
};
@@ -851,7 +1057,7 @@ const refreshDashboard = async () => {
clearDashboardCache();
onLoadBaseInfo(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'));
};
@@ -1028,7 +1234,7 @@ const saveMemo = async () => {
const loadData = async () => {
if (chartOption.value === 'io') {
chartsOption.value['ioChart'] = loadIOChartOption();
} else {
} else if (chartOption.value === 'network') {
chartsOption.value['networkChart'] = loadNetworkChartOption();
}
};
@@ -1097,12 +1303,14 @@ const loadSource = (row: any) => {
const onFocus = () => {
isActive.value = true;
skipNextCurrentInfoDelta.value = true;
void loadVllmOptions();
};
const onBlur = () => {
isActive.value = false;
};
const refreshOptionsOnHover = async () => {
void loadVllmOptions();
if (hasRefreshedOptionsOnHover.value) return;
if (!netOptionsFromCache.value && !ioOptionsFromCache.value) return;
hasRefreshedOptionsOnHover.value = true;
@@ -1155,7 +1363,29 @@ onMounted(() => {
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(() => {
vllmOptionsVersion++;
stopVllmMonitor();
window.removeEventListener('focus', onFocus);
window.removeEventListener('blur', onBlur);
isCurrentActive.value = false;
@@ -1189,6 +1419,16 @@ onBeforeUnmount(() => {
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) {
.dashboard-entrance-alert {
padding-right: 40px;
@@ -24,7 +24,7 @@
@keyup.enter="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 class="directory-row directory-header">
<span>{{ $t('file.path') }}</span>
@@ -93,14 +93,18 @@
></el-table-column>
<el-table-column :label="$t('commons.button.log')" prop="log" min-width="160">
<template #default="{ row }">
<el-tooltip :content="row.log" placement="top">
<el-tooltip :content="row.log" v-bind="TABLE_TOOLTIP_OPTIONS">
{{ row.log }}
</el-tooltip>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.message')" prop="status" width="100">
<template #default="{ row }">
<el-tooltip v-if="row.status === 'FAILED'" :content="row.message" placement="top">
<el-tooltip
v-if="row.status === 'FAILED'"
:content="row.message"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-button type="danger" link>
{{ $t('commons.status.' + row.status.toLowerCase()) }}
</el-button>
@@ -132,6 +136,7 @@
</template>
<script setup lang="ts">
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { readCachedPageSize } from '@/utils/panel-cache';
import i18n from '@/lang';
import { reactive, ref } from 'vue';
@@ -4,7 +4,7 @@
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
<el-table-column :label="$t('file.path')" show-overflow-tooltip prop="path">
<template #default="{ row }">
<el-tooltip class="box-item" effect="dark" :content="row.path" placement="top">
<el-tooltip class="box-item" effect="dark" :content="row.path" v-bind="TABLE_TOOLTIP_OPTIONS">
<span class="table-link text-ellipsis" @click="toFavorite(row)" type="primary">
<svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon>
<svg-icon v-else className="table-icon" iconName="p-file-normal"></svg-icon>
@@ -20,6 +20,7 @@
</template>
<script setup lang="ts">
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { readCachedPageSize } from '@/utils/panel-cache';
import { searchFavorite, removeFavorite } from '@/api/modules/files';
import i18n from '@/lang';
@@ -506,7 +506,6 @@
<template #main>
<ComplexTable
class="file-table"
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:selects="selects"
:ref="(el) => setTableRef(item.id, el)"
@@ -528,9 +527,6 @@
show-overflow-tooltip
:sortable="'custom'"
prop="name"
:tooltip-options="{
placement: 'bottom-start',
}"
>
<template #default="{ row }">
<div class="file-row">
@@ -4,7 +4,7 @@
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
<el-table-column :label="$t('file.path')" show-overflow-tooltip prop="path" min-width="250">
<template #default="{ row }">
<el-tooltip class="box-item" effect="dark" :content="row.path" placement="top">
<el-tooltip class="box-item" effect="dark" :content="row.path" v-bind="TABLE_TOOLTIP_OPTIONS">
<span class="table-link text-ellipsis" @click="viewDetail(row)" type="primary">
<svg-icon className="table-icon" iconName="p-file-normal"></svg-icon>
{{ row.fileName }}
@@ -24,6 +24,7 @@
</template>
<script setup lang="ts">
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { readCachedPageSize } from '@/utils/panel-cache';
import { removeFileShare, searchFileShare } from '@/api/modules/files';
import { File } from '@/api/interface/file';
@@ -48,7 +48,6 @@
<ComplexTable
v-model:selects="selects"
:data="containerPageRows"
:cache-pagination="false"
:pagination-config="paginationConfig"
:heightDiff="320"
row-key="key"
@@ -170,7 +169,6 @@
v-model:selects="orphanSelects"
v-loading="loading"
:data="orphanPageRows"
:cache-pagination="false"
:pagination-config="orphanPaginationConfig"
row-key="policyUUID"
max-height="calc(100vh - 220px)"
@@ -66,7 +66,6 @@
row.interface,
])
"
:cache-pagination="false"
:pagination-config="paginationConfig"
v-model:selects="selects"
@search="search"
@@ -109,7 +109,6 @@
<ComplexTable
:selection-context="() => [searchName, selectedRuleFilters]"
v-model:selects="selects"
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="allRows"
:heightDiff="320 + noticeHeight"
@@ -137,7 +136,7 @@
<el-tooltip
v-if="row.observed.protected && !row.isWhitelist"
:content="$t('firewall.builtinRuleProtected')"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
:show-after="200"
:popper-style="{ maxWidth: '320px' }"
>
@@ -205,7 +204,7 @@
<div v-else class="firewall-used-cell">
<el-tooltip
:content="usageEntryLabel(usageEntriesByRow[row.rowKey][0])"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-tag
class="cursor-pointer firewall-used-entry"
@@ -245,7 +244,7 @@
v-for="entry in usageEntriesByRow[row.rowKey]"
:key="`${row.rowKey}:${entry.key}`"
:content="usageEntryLabel(entry)"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-tag
class="cursor-pointer firewall-used-popover-entry"
@@ -305,7 +304,7 @@
<script lang="ts" setup>
import { useSearchState } from '@/utils/search-reset';
import { Firewall } from '@/api/interface/firewall';
import type { FuTableOperationButton } from '@/components/table/shared';
import { TABLE_TOOLTIP_OPTIONS, type FuTableOperationButton } from '@/components/table/shared';
import { buildHostRuleExport } from './transfer';
import { Process } from '@/api/interface/process';
import {
+8 -8
View File
@@ -26,13 +26,7 @@
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="400"
>
<ComplexTable :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="60" :label="$t('ssh.belong')" prop="area" />
<el-table-column min-width="60" :label="$t('commons.table.port')" prop="port" />
@@ -48,7 +42,12 @@
<el-tag type="success">{{ $t('commons.status.success') }}</el-tag>
</div>
<div v-else>
<el-tooltip class="box-item" effect="dark" :content="row.message" placement="top-start">
<el-tooltip
class="box-item"
effect="dark"
:content="row.message"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-tag type="danger">{{ $t('commons.status.failed') }}</el-tag>
</el-tooltip>
</div>
@@ -99,6 +98,7 @@
</template>
<script setup lang="ts">
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { dateFormatServerTimezone } from '@/utils/date';
+1 -7
View File
@@ -57,13 +57,7 @@
<TableRefresh @search="resetAndLoadLogs" />
</template>
<template #main>
<ComplexTable
:data="logs"
:cache-pagination="false"
:pagination-config="paginationConfig"
@row-click="openDetail"
:heightDiff="320"
>
<ComplexTable :data="logs" :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 :label="$t('logs.priority')" width="120">
<template #default="{ row }">
+1 -7
View File
@@ -21,13 +21,7 @@
<TableSetting title="login-log-refresh" @search="search()" />
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="330"
>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="330">
<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 :label="$t('logs.loginAddress')" prop="address" />
+3 -8
View File
@@ -78,13 +78,7 @@
<TableSetting title="operation-log-refresh" @search="search()" />
</template>
<template #main>
<ComplexTable
:cache-pagination="false"
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="330"
>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="330">
<el-table-column :label="$t('logs.resource')" prop="group" fix>
<template #default="{ row }">
<span v-if="row.source && row.source.indexOf('-') === -1">
@@ -104,7 +98,7 @@
<el-tooltip
v-if="row.authMethod === 'api_key' && row.apiKeyID"
:content="row.apiKeyID"
placement="top"
v-bind="TABLE_TOOLTIP_OPTIONS"
>
<el-text type="info" size="small">
API Key · {{ row.apiKeyName || row.apiKeyID }}
@@ -145,6 +139,7 @@
</template>
<script setup lang="ts">
import { TABLE_TOOLTIP_OPTIONS } from '@/components/table/shared';
import { useSearchState } from '@/utils/search-reset';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import LogRouter from '@/views/log/router/index.vue';

Some files were not shown because too many files have changed in this diff Show More