Compare commits

..
Author SHA1 Message Date
dependabot[bot] b89ad36b74 chore(deps): bump github.com/qiniu/go-sdk/v7 in /agent
Bumps [github.com/qiniu/go-sdk/v7](https://github.com/qiniu/go-sdk) from 7.27.0 to 7.29.0.
- [Release notes](https://github.com/qiniu/go-sdk/releases)
- [Changelog](https://github.com/qiniu/go-sdk/blob/master/CHANGELOG.md)
- [Commits](https://github.com/qiniu/go-sdk/compare/v7.27.0...v7.29.0)

---
updated-dependencies:
- dependency-name: github.com/qiniu/go-sdk/v7
  dependency-version: 7.29.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-14 16:44:46 +00:00
213 changed files with 1149 additions and 5912 deletions
-16
View File
@@ -1,16 +0,0 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) AIBenchmarkSources(c *gin.Context) {
sources, err := service.AIBenchmarkSources(c.Query("kind"), c.Query("selected"))
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, sources)
}
+5 -8
View File
@@ -293,7 +293,7 @@ func (b *BaseApi) PageAlertConfig(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /alert/config/update [post]
// @x-panel-log {"bodyKeys":["displayName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置 [displayName]","formatEN":"update alert config [displayName]"}
// @x-panel-log {"bodyKeys":["id","displayName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置 [id][displayName]","formatEN":"update alert config [id][displayName]"}
func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
var req dto.AlertConfigUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -321,15 +321,13 @@ func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /alert/config/status [post]
// @x-panel-log {"bodyKeys":["status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置状态 [name][status]","formatEN":"update alert config status [name][status]"}
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新告警配置状态 [id][status]","formatEN":"update alert config status [id][status]"}
func (b *BaseApi) UpdateAlertConfigStatus(c *gin.Context) {
var req dto.AlertConfigStatusUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
name, err := alertService.UpdateAlertConfigStatus(req, loadAuditUser(c))
helper.SetOperationLogValues(c, map[string]string{"name": name})
if err != nil {
if err := alertService.UpdateAlertConfigStatus(req, loadAuditUser(c)); err != nil {
helper.InternalServer(c, err)
return
}
@@ -355,14 +353,13 @@ func loadAuditUser(c *gin.Context) string {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /alert/config/del [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除告警配置 [name]","formatEN":"delete alert config [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除告警配置 [id]","formatEN":"delete alert config [id]"}
func (b *BaseApi) DeleteAlertConfig(c *gin.Context) {
var req dto.DeleteRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
name, err := alertService.DeleteAlertConfig(req.ID)
helper.SetOperationLogValues(c, map[string]string{"name": name})
err := alertService.DeleteAlertConfig(req.ID)
if err != nil {
helper.InternalServer(c, err)
return
-21
View File
@@ -3,7 +3,6 @@ package helper
import (
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
@@ -185,23 +184,3 @@ func GetParamInt32(paramName string, c *gin.Context) (int32, error) {
intNum, _ := strconv.Atoi(idParam)
return int32(intNum), nil
}
// SetOperationLogValues supplies server-resolved values to Core's operation log.
// Call before writing the response. Only pass public audit fields, never credentials.
func SetOperationLogValues(c *gin.Context, values map[string]string) {
const header = "X-Op-Resolved"
resolved := make(map[string]string)
if data, err := base64.RawURLEncoding.DecodeString(c.Writer.Header().Get(header)); err == nil {
_ = json.Unmarshal(data, &resolved)
}
if resolved == nil {
resolved = make(map[string]string)
}
for key, value := range values {
resolved[key] = value
}
data, err := json.Marshal(resolved)
if err == nil {
c.Header(header, base64.RawURLEncoding.EncodeToString(data))
}
}
-84
View File
@@ -1,84 +0,0 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/gin-gonic/gin"
)
// @Tags Dashboard
// @Summary List quick directories
// @Success 200 {array} model.QuickDirectory
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/quick/directory [get]
func (b *BaseApi) ListQuickDirectories(c *gin.Context) {
items, err := dashboardService.ListQuickDirectories()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, items)
}
// @Tags Dashboard
// @Summary Create quick directory
// @Accept json
// @Param request body dto.QuickDirectoryCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/quick/directory [post]
func (b *BaseApi) CreateQuickDirectory(c *gin.Context) {
var req dto.QuickDirectoryCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
item, err := dashboardService.CreateQuickDirectory(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, item)
}
// @Tags Dashboard
// @Summary Update quick directory
// @Accept json
// @Param request body dto.QuickDirectoryUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/quick/directory/update [post]
func (b *BaseApi) UpdateQuickDirectory(c *gin.Context) {
var req dto.QuickDirectoryUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
item, err := dashboardService.UpdateQuickDirectory(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, item)
}
// @Tags Dashboard
// @Summary Delete quick directory
// @Accept json
// @Param request body dto.QuickDirectoryDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/quick/directory/del [post]
func (b *BaseApi) DeleteQuickDirectory(c *gin.Context) {
var req dto.QuickDirectoryDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := dashboardService.DeleteQuickDirectory(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+1 -3
View File
@@ -9,8 +9,7 @@ type AgentCreateReq struct {
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
BridgePort int `json:"bridgePort"`
AllowedOrigins []string `json:"allowedOrigins"`
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent deepseek-harness octop"`
AccessHost string `json:"accessHost"`
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
Model string `json:"model"`
AccountID uint `json:"accountId"`
Token string `json:"token"`
@@ -111,7 +110,6 @@ type AgentBatchOperateResult struct {
}
type AgentItem struct {
AccessHost string `json:"accessHost"`
ID uint `json:"id"`
Name string `json:"name"`
Remark string `json:"remark"`
-13
View File
@@ -1,13 +0,0 @@
package dto
type AIBenchmarkSource struct {
ID string `json:"id"`
Kind string `json:"kind"`
Name string `json:"name"`
Status string `json:"status"`
BaseURL string `json:"baseUrl"`
Endpoint string `json:"endpoint"`
Models []string `json:"models"`
Tokenizer string `json:"tokenizer"`
Message string `json:"message"`
}
+1 -21
View File
@@ -31,27 +31,7 @@ type DashboardBase struct {
}
type ChangeQuicks struct {
Quicks []QuickVisibility `json:"quicks" validate:"required,dive"`
}
type QuickVisibility struct {
ID uint `json:"id" validate:"required"`
IsShow bool `json:"isShow"`
}
type QuickDirectoryCreate struct {
Path string `json:"path" validate:"required"`
Alias string `json:"alias"`
}
type QuickDirectoryDelete struct {
ID uint `json:"id" validate:"required"`
Legacy bool `json:"legacy"`
}
type QuickDirectoryUpdate struct {
QuickDirectoryDelete
QuickDirectoryCreate
Quicks []QuickJump `json:"quicks"`
}
type QuickJump struct {
-1
View File
@@ -26,7 +26,6 @@ type FirewallSubsystemStatus struct {
}
type FirewallLifecycleOperation struct {
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
WithDockerRestart bool `json:"withDockerRestart"`
}
-6
View File
@@ -1,7 +1,6 @@
package dto
import (
"encoding/json"
"time"
"github.com/1Panel-dev/1Panel/agent/app/model"
@@ -145,11 +144,6 @@ type MonitorVLLMCurrent struct {
AppInstallID uint `json:"appInstallID" validate:"required"`
}
type MonitorVLLMCurrentData struct {
model.MonitorVLLM
Metrics map[string]json.RawMessage `json:"metrics,omitempty"`
}
type MonitorVLLMClean struct {
AppInstallID uint `json:"appInstallID" validate:"required"`
}
-7
View File
@@ -5,13 +5,6 @@ type AppLauncher struct {
Key string `json:"key"`
}
type QuickDirectory struct {
BaseModel
Path string `json:"path"`
Alias string `json:"alias"`
Legacy bool `json:"legacy" gorm:"-"`
}
type QuickJump struct {
BaseModel
Name string `json:"name"`
-2
View File
@@ -86,9 +86,7 @@ type MonitorVLLM struct {
RawMetrics string `json:"-"`
HistogramDeltas string `json:"-"`
IntervalSeconds float64 `json:"intervalSeconds" gorm:"-"`
SampleIntervalSeconds int `json:"-"`
RequestDeltas string `json:"-"`
CompletedRequests *float64 `json:"completedRequests" gorm:"-"`
QueueTime *float64 `json:"queueTime"`
AverageGenerationTokens *float64 `json:"averageGenerationTokens"`
PrefixCacheHitRate *float64 `json:"prefixCacheHitRate"`
+3 -2
View File
@@ -66,7 +66,7 @@ var catalog = map[string]Meta{
{APIType: "openai-responses", BaseURL: "https://api.deepseek.com"},
anthropicAPIConfig("https://api.deepseek.com/anthropic", AuthModeXAPIKey),
},
Models: []Model{{ID: "deepseek-flash", Name: "deepseek-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}},
Models: []Model{{ID: "deepseek-v4-flash", Name: "deepseek-v4-flash"}, {ID: "deepseek-v4-pro", Name: "deepseek-v4-pro"}},
},
"bailian-coding-plan": {
Key: "bailian-coding-plan", DisplayNameKey: "AIProviderBailianCodingPlan", Sort: 30, DefaultAPIType: "openai-completions", EnvKey: "QWEN_API_KEY",
@@ -82,6 +82,7 @@ var catalog = map[string]Meta{
{ID: "kimi-k2.5", Name: "Kimi K2.5"},
{ID: "qwen3.5-plus", Name: "Qwen3.5-Plus"},
{ID: "glm-5", Name: "GLM-5"},
{ID: "MiniMax-M2.5", Name: "MiniMax M2.5"},
{ID: "qwen3.6-plus", Name: "Qwen3.6-Plus"},
{ID: "qwen3.7-plus", Name: "Qwen3.7-Plus"},
},
@@ -113,7 +114,7 @@ var catalog = map[string]Meta{
{APIType: "openai-completions", BaseURL: "https://api.minimaxi.com/v1"},
{APIType: "minimax-images", BaseURL: "https://api.minimaxi.com"},
},
Models: []Model{{ID: "MiniMax-M3.1-Flash", Name: "MiniMax M3.1 Flash"}, {ID: "MiniMax-M3", Name: "MiniMax M3"}},
Models: []Model{{ID: "MiniMax-M3", Name: "MiniMax M3"}, {ID: "MiniMax-M2.7", Name: "MiniMax M2.7"}, {ID: "MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"}},
},
"xiaomi": {
Key: "xiaomi", DisplayName: "Xiaomi", Sort: 46, DefaultAPIType: "openai-completions", EnvKey: "XIAOMI_API_KEY",
-100
View File
@@ -1,100 +0,0 @@
package provider
import (
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
)
func TestMiniMaxCatalog(t *testing.T) {
meta, ok := Get("minimax")
if !ok || !reflect.DeepEqual(meta.Models, []Model{{ID: "MiniMax-M3.1-Flash", Name: "MiniMax M3.1 Flash"}, {ID: "MiniMax-M3", Name: "MiniMax M3"}}) {
t.Fatalf("unexpected MiniMax models: %+v", meta.Models)
}
if meta.DefaultAPIType != "anthropic-messages" {
t.Fatalf("changed default protocol: %s", meta.DefaultAPIType)
}
for _, provider := range All() {
for _, model := range provider.Models {
if strings.Contains(strings.ToLower(model.ID), "minimax") && model.ID != "MiniMax-M3.1-Flash" && model.ID != "MiniMax-M3" {
t.Errorf("legacy suggestion in %s: %s", provider.Key, model.ID)
}
}
}
}
func TestMiniMaxVerificationRequests(t *testing.T) {
for _, region := range []string{"https://api.minimaxi.com", "https://api.minimax.io"} {
for _, protocol := range []string{"anthropic-messages", "openai-completions"} {
for _, auth := range []string{AuthModeXAPIKey, AuthModeBearer} {
for _, model := range []string{"MiniMax-M3.1-Flash", "MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.7-highspeed", "custom-model"} {
t.Run(region+"/"+protocol+"/"+auth+"/"+model, func(t *testing.T) {
base, endpoint := region+"/v1", "/chat/completions"
if protocol == "anthropic-messages" {
base, endpoint = region+"/anthropic", "/v1/messages"
}
built := BuildVerifyRequest("minimax", protocol, auth, base, "test-key", model)
if built.URL != base+endpoint {
t.Fatalf("URL = %s", built.URL)
}
var captured map[string]interface{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != strings.TrimPrefix(base, region)+endpoint {
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
}
if protocol == "anthropic-messages" && auth == AuthModeXAPIKey {
if r.Header.Get("x-api-key") != "test-key" || r.Header.Get("Authorization") != "" {
t.Error("incorrect API key authentication")
}
} else if r.Header.Get("Authorization") != "Bearer test-key" {
t.Error("incorrect bearer authentication")
}
if err := json.NewDecoder(r.Body).Decode(&captured); err != nil {
t.Error(err)
}
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
if err := VerifyAccount("minimax", protocol, auth, server.URL+strings.TrimPrefix(base, region), "test-key", model); err != nil {
t.Fatal(err)
}
if captured["model"] != model || captured["stream"] != false || captured["max_tokens"] != float64(defaultVerifyMaxTokens) {
t.Fatalf("unexpected serialized body: %#v", captured)
}
if model == "MiniMax-M3.1-Flash" {
if protocol == "anthropic-messages" {
if !reflect.DeepEqual(captured["thinking"], map[string]interface{}{"type": "adaptive"}) || !reflect.DeepEqual(captured["output_config"], map[string]interface{}{"effort": "max"}) || captured["reasoning_effort"] != nil {
t.Fatalf("invalid Anthropic Flash reasoning: %#v", captured)
}
} else if captured["reasoning_effort"] != "max" || captured["thinking"] != nil || captured["output_config"] != nil {
t.Fatalf("invalid OpenAI Flash reasoning: %#v", captured)
}
} else if captured["reasoning_effort"] != nil || captured["thinking"] != nil || captured["output_config"] != nil {
t.Fatalf("changed legacy/custom reasoning: %#v", captured)
}
})
}
}
}
}
}
func TestMiniMaxExplicitGatewayAndCustomModels(t *testing.T) {
for _, provider := range []string{"bailian-coding-plan", "custom", "openrouter"} {
for _, protocol := range []string{"openai-completions", "anthropic-messages"} {
for _, model := range []string{"MiniMax-M2.5", "MiniMax-M3.1-Flash", "my-custom-model"} {
request := BuildVerifyRequest(provider, protocol, AuthModeBearer, "https://example.test/v1", "test-key", model)
var body map[string]interface{}
if err := json.Unmarshal(request.Body, &body); err != nil {
t.Fatal(err)
}
if body["model"] != model || body["reasoning_effort"] != nil || body["output_config"] != nil || body["thinking"] != nil {
t.Fatalf("changed explicit %s request: %#v", provider, body)
}
}
}
}
}
+4 -13
View File
@@ -118,15 +118,10 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri
headers["x-api-key"] = apiKey
}
headers["anthropic-version"] = "2023-06-01"
body := map[string]interface{}{
request.Body = mustJSON(map[string]interface{}{
"model": model, "max_tokens": defaultVerifyMaxTokens, "stream": false,
"messages": []map[string]interface{}{{"role": "user", "content": []map[string]string{{"type": "text", "text": "test"}}}},
}
if provider == "minimax" && model == "MiniMax-M3.1-Flash" {
body["thinking"] = map[string]string{"type": "adaptive"}
body["output_config"] = map[string]string{"effort": "max"}
}
request.Body = mustJSON(body)
})
case "openai-responses":
request.URL = baseURL + "/responses"
headers["Authorization"] = "Bearer " + apiKey
@@ -136,13 +131,9 @@ func BuildVerifyRequest(provider, apiType, authMode, baseURL, apiKey, model stri
if (provider != "ollama" && provider != "llmman") || strings.TrimSpace(apiKey) != "" {
headers["Authorization"] = "Bearer " + apiKey
}
body := map[string]interface{}{
request.Body = mustJSON(map[string]interface{}{
"model": model, "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": defaultVerifyMaxTokens, "stream": false,
}
if provider == "minimax" && model == "MiniMax-M3.1-Flash" {
body["reasoning_effort"] = "max"
}
request.Body = mustJSON(body)
})
}
return request
}
+3 -69
View File
@@ -3,7 +3,6 @@ package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
)
type LauncherRepo struct{}
@@ -18,11 +17,6 @@ type ILauncherRepo interface {
GetQuickJump(opts ...DBOption) (model.QuickJump, error)
ListQuickJump(withAll bool) []model.QuickJump
UpdateQuicks(quicks []model.QuickJump) error
ListQuickDirectories() ([]model.QuickDirectory, error)
CountQuickDirectories() (int64, error)
CreateQuickDirectory(item *model.QuickDirectory) error
UpdateQuickDirectory(item *model.QuickDirectory) error
DeleteQuickDirectory(id uint, legacy bool) error
}
func NewILauncherRepo() ILauncherRepo {
@@ -101,73 +95,13 @@ func (u *LauncherRepo) UpdateQuicks(quicks []model.QuickJump) error {
for _, item := range quicks {
if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
"is_show": item.IsShow,
"detail": item.Detail,
"alias": item.Alias,
}).Error; err != nil {
tx.Rollback()
return err
}
}
return tx.Commit().Error
}
// Legacy entries remain in the overview configuration until explicitly deleted.
func legacyQuickDirectories(db *gorm.DB) *gorm.DB {
return db.Model(&model.QuickJump{}).Where("name = ? AND is_show = ? AND detail <> ?", "File", true, "")
}
func (u *LauncherRepo) ListQuickDirectories() ([]model.QuickDirectory, error) {
items := make([]model.QuickDirectory, 0)
if err := global.DB.Order("id ASC").Find(&items).Error; err != nil {
return nil, err
}
var quicks []model.QuickJump
if err := legacyQuickDirectories(global.DB).Order("id ASC").Find(&quicks).Error; err != nil {
return nil, err
}
for _, quick := range quicks {
items = append(items, model.QuickDirectory{BaseModel: quick.BaseModel, Path: quick.Detail, Alias: quick.Alias, Legacy: true})
}
return items, nil
}
func (u *LauncherRepo) CountQuickDirectories() (int64, error) {
var count, legacyCount int64
if err := global.DB.Model(&model.QuickDirectory{}).Count(&count).Error; err != nil {
return 0, err
}
if err := legacyQuickDirectories(global.DB).Count(&legacyCount).Error; err != nil {
return 0, err
}
return count + legacyCount, nil
}
func (u *LauncherRepo) CreateQuickDirectory(item *model.QuickDirectory) error {
return global.DB.Create(item).Error
}
func (u *LauncherRepo) UpdateQuickDirectory(item *model.QuickDirectory) error {
var result *gorm.DB
if item.Legacy {
result = legacyQuickDirectories(global.DB).Where("id = ?", item.ID).Updates(map[string]interface{}{"detail": item.Path, "alias": item.Alias})
} else {
result = global.DB.Model(&model.QuickDirectory{}).Where("id = ?", item.ID).Updates(map[string]interface{}{"path": item.Path, "alias": item.Alias})
}
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
func (u *LauncherRepo) DeleteQuickDirectory(id uint, legacy bool) error {
var result *gorm.DB
if legacy {
result = legacyQuickDirectories(global.DB).Where("id = ?", id).Updates(map[string]interface{}{"detail": nil, "alias": nil})
} else {
result = global.DB.Delete(&model.QuickDirectory{}, id)
}
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
tx.Commit()
return nil
}
+2 -10
View File
@@ -16,8 +16,7 @@ type VLLMMonitorRepo struct{}
type VLLMHistoryPoint struct {
model.MonitorVLLM
Bucket int64
CompletedRequests *float64
Bucket int64
}
func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error {
@@ -78,19 +77,12 @@ func (r *VLLMMonitorRepo) History(ctx context.Context, id uint, start, end time.
}
db = db.Group("bucket").Order("bucket ASC")
} else {
columns = append([]string{"id", "created_at", "app_install_id", "status", "sample_interval_seconds"}, metrics...)
columns = append([]string{"id", "created_at", "app_install_id", "status"}, metrics...)
if group == "" || group == "requests" {
columns = append(columns, "histogram_deltas", "request_deltas")
}
db = db.Order("created_at ASC, id ASC")
}
if group == "" || group == "overview" {
count := "CASE WHEN json_valid(request_deltas) THEN CASE WHEN json_extract(request_deltas, '$.seconds') > 0 THEN ROUND(request_throughput * json_extract(request_deltas, '$.seconds')) END END"
if seconds > 0 {
count = "SUM(" + count + ")"
}
columns = append(columns, count+" AS completed_requests")
}
var points []VLLMHistoryPoint
err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error
return points, err
+1 -12
View File
@@ -142,9 +142,6 @@ const (
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
agentType := req.AgentType
if err := validateNativeAgentCreate(req); err != nil {
return nil, err
}
if err := checkPortExist(req.WebUIPort); err != nil {
return nil, err
}
@@ -240,8 +237,6 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
}
} else if agentType == constant.AppCopaw {
dashboardAuth = normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
} else if agentType == constant.AppDeepSeekHarness || agentType == constant.AppOctop {
dashboardAuth = agentDashboardAuth{Username: req.DashboardUsername, Password: req.DashboardPassword}
}
params := map[string]interface{}{
@@ -250,12 +245,6 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
constant.HostIP: "",
}
setAgentWebUIParams(params, agentType, detail.Version, req.WebUIPort)
if agentType == constant.AppDeepSeekHarness {
params["HTTPS_ACCESS_HOST"] = req.AccessHost
}
if agentType == constant.AppOctop {
params["OCTOP_LOG_LEVEL"] = "info"
}
if agentType == constant.AppOpenclaw {
if allowedOrigin := firstAllowedOrigin(allowedOrigins); allowedOrigin != "" {
params["ALLOWED_ORIGIN"] = allowedOrigin
@@ -749,7 +738,7 @@ func requiresAgentAccount(agentType string) bool {
}
func setAgentWebUIParams(params map[string]interface{}, agentType, appVersion string, webUIPort int) {
if agentType == constant.AppDeepSeekHarness || agentType == constant.AppOpenclaw && isOpenclawHTTPSWindowVersion(appVersion) {
if agentType == constant.AppOpenclaw && isOpenclawHTTPSWindowVersion(appVersion) {
params["PANEL_APP_PORT_HTTPS"] = webUIPort
return
}
-115
View File
@@ -1,115 +0,0 @@
package service
import (
"encoding/json"
"path/filepath"
"reflect"
"testing"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
)
func TestMiniMaxOpenclawModelMetadata(t *testing.T) {
for _, provider := range []string{"minimax", "custom", "bailian-coding-plan"} {
for _, protocol := range []string{"anthropic-messages", "openai-completions"} {
for _, id := range []string{"MiniMax-M3.1-Flash", "MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.5", "saved-custom"} {
t.Run(provider+"/"+protocol+"/"+id, func(t *testing.T) {
account := &model.AgentAccount{Provider: provider, APIType: protocol, BaseURL: "https://example.test/v1", APIKey: "test-key"}
_, entry, _, _, err := buildOpenclawAccountModelConfig(account, dto.AgentAccountModel{ID: id, Name: "Saved name"})
if err != nil {
t.Fatal(err)
}
payload, err := json.Marshal(entry)
if err != nil {
t.Fatal(err)
}
var actual modelEntry
if err := json.Unmarshal(payload, &actual); err != nil {
t.Fatal(err)
}
if actual.ID != id || actual.Name != "Saved name" {
t.Fatalf("changed saved identity: %s", payload)
}
if provider == "minimax" && id == "MiniMax-M3.1-Flash" {
if actual.ContextWindow != 1000000 || actual.MaxTokens != 524288 || !reflect.DeepEqual(actual.Input, []string{"text", "image"}) {
t.Fatalf("incorrect direct Flash metadata: %s", payload)
}
} else if actual.ContextWindow != 0 || actual.MaxTokens != 0 {
t.Fatalf("changed existing/gateway limits: %s", payload)
}
})
}
}
}
}
func TestMiniMaxPreservesSavedOpenclawMetadata(t *testing.T) {
current := &modelsConfig{Mode: "merge", Providers: map[string]modelProvider{
"minimax": {Models: []modelEntry{{ID: "MiniMax-M3.1-Flash", Input: []string{"text"}, ContextWindow: 32000, MaxTokens: 4096}}},
}}
conf := map[string]interface{}{}
if err := applyOpenclawModelsConfig(conf, current); err != nil {
t.Fatal(err)
}
next := &modelsConfig{Mode: "merge", Providers: map[string]modelProvider{
"minimax": {Models: []modelEntry{{ID: "MiniMax-M3.1-Flash", Input: []string{"text", "image"}, ContextWindow: 1000000, MaxTokens: 524288}}},
}}
preserveOpenclawModelMetadata(conf, next)
if !reflect.DeepEqual(next.Providers["minimax"].Models, current.Providers["minimax"].Models) {
t.Fatalf("saved metadata changed: %+v", next)
}
}
func TestMiniMaxDefaultAccountRequest(t *testing.T) {
for _, protocol := range []string{"anthropic-messages", "openai-completions"} {
account := &model.AgentAccount{Provider: "minimax", APIType: protocol}
models, err := buildInitialAgentAccountModels(account, nil)
if err != nil {
t.Fatal(err)
}
selected, err := resolveAgentAccountVerifyModel("minimax", "", models)
if err != nil || selected != "MiniMax-M3.1-Flash" || len(models) != 2 || models[1].ID != "MiniMax-M3" {
t.Fatalf("unexpected default: %s, %+v, %v", selected, models, err)
}
request := providercatalog.BuildVerifyRequest("minimax", protocol, "", "https://example.test", "test-key", selected)
var body map[string]interface{}
if err := json.Unmarshal(request.Body, &body); err != nil || body["model"] != selected {
t.Fatalf("default request: %s, %v", request.Body, err)
}
}
}
func TestMiniMaxHermesConfiguration(t *testing.T) {
for _, protocol := range []string{"anthropic-messages", "openai-completions"} {
for _, id := range []string{"MiniMax-M3.1-Flash", "MiniMax-M3", "MiniMax-M2.7", "my-custom-model"} {
dir := t.TempDir()
account := &model.AgentAccount{Provider: "minimax", APIType: protocol, BaseURL: "https://api.minimaxi.com/anthropic", APIKey: "test-key"}
if err := writeHermesConfig(dir, account, id, ""); err != nil {
t.Fatal(err)
}
conf, err := readHermesConfig(filepath.Join(dir, "config.yaml"))
if err != nil {
t.Fatal(err)
}
if conf.Model.Default != id || conf.Model.Provider != "minimax-cn" || conf.Model.BaseURL != account.BaseURL {
t.Fatalf("changed Hermes selection: %+v", conf.Model)
}
}
}
}
func TestMiniMaxExplicitAccountModels(t *testing.T) {
for _, provider := range []string{"minimax", "bailian-coding-plan", "custom"} {
account := &model.AgentAccount{Provider: provider, APIType: "openai-completions"}
requested := []dto.AgentAccountModel{{ID: "MiniMax-M2.5", Name: "Saved legacy"}, {ID: "my-custom-model", Name: "Saved custom"}}
actual, err := buildInitialAgentAccountModels(account, requested)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(actual, requested) {
t.Fatalf("changed explicit %s models: %+v", provider, actual)
}
}
}
-69
View File
@@ -1,69 +0,0 @@
package service
import (
"net"
"regexp"
"strings"
"unicode"
"unicode/utf8"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
)
var deepSeekUsernamePattern = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
var deepSeekHostLabelPattern = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$`)
func isDeepSeekAccessHost(value string) bool {
if ip := net.ParseIP(value); ip != nil {
return ip.To4() != nil && !strings.Contains(value, ":")
}
if value == "" || len(value) > 253 || strings.Trim(value, "0123456789.") == "" {
return false
}
for _, label := range strings.Split(value, ".") {
if !deepSeekHostLabelPattern.MatchString(label) {
return false
}
}
return true
}
func validateNativeAgentCreate(req dto.AgentCreateReq) error {
if req.AgentType != constant.AppDeepSeekHarness && req.AgentType != constant.AppOctop {
return nil
}
if strings.TrimSpace(req.DashboardUsername) == "" || strings.ContainsAny(req.DashboardUsername, "\r\n\x00") {
return buserr.WithDetail("ErrInvalidParams", "dashboardUsername", nil)
}
if strings.ContainsAny(req.DashboardPassword, "\r\n\x00") || req.DashboardPassword != strings.TrimSpace(req.DashboardPassword) {
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
}
if req.AgentType == constant.AppDeepSeekHarness {
if !isDeepSeekAccessHost(req.AccessHost) {
return buserr.WithDetail("ErrInvalidParams", "accessHost", nil)
}
if !deepSeekUsernamePattern.MatchString(req.DashboardUsername) {
return buserr.WithDetail("ErrInvalidParams", "dashboardUsername", nil)
}
if utf8.RuneCountInString(req.DashboardPassword) < 12 {
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
}
return nil
}
hasLetter, hasDigit := false, false
for _, char := range req.DashboardPassword {
hasLetter = hasLetter || unicode.IsLetter(char)
hasDigit = hasDigit || unicode.IsDigit(char)
}
if utf8.RuneCountInString(req.DashboardPassword) < 8 || !hasLetter || !hasDigit {
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
}
switch strings.ToLower(req.DashboardPassword) {
case "password", "password1", "password12", "password123", "12345678", "123456789", "qwerty123",
"admin123", "welcome1", "letmein1", "changeme1", "octop123", "abc12345", "iloveyou1":
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
}
return nil
}
+2 -21
View File
@@ -415,15 +415,6 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
} else {
item.WebUIPort = appInstall.HttpPort
}
if agentType == constant.AppDeepSeekHarness {
item.WebUIPort = appInstall.HttpsPort
if values, err := readAgentEnvMap(appInstall.GetEnvPath()); err == nil {
accessHost := values["HTTPS_ACCESS_HOST"]
if isDeepSeekAccessHost(accessHost) {
item.AccessHost = accessHost
}
}
}
item.Path = appInstall.GetPath()
item.Status = appInstall.Status
item.Message = appInstall.Message
@@ -451,8 +442,7 @@ func localizedAgentProviderName(provider string) string {
}
func isAgentAppKey(appKey string) bool {
return appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppHermesAgent ||
appKey == constant.AppDeepSeekHarness || appKey == constant.AppOctop
return appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppHermesAgent
}
func isOpenclawLegacyHTTPVersion(version string) bool {
@@ -1263,12 +1253,7 @@ func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.Agen
if err != nil {
return "", modelEntry{}, "", modelProvider{}, err
}
entry := buildOpenclawModelEntry(providerPatch.ModelID, model)
if account.Provider == "minimax" && providerPatch.ModelID == "MiniMax-M3.1-Flash" {
entry.ContextWindow = 1000000
entry.MaxTokens = 524288
}
return providerPatch.PrimaryModel, entry, providerPatch.ProviderKey, modelProvider{
return providerPatch.PrimaryModel, buildOpenclawModelEntry(providerPatch.ModelID, model), providerPatch.ProviderKey, modelProvider{
ApiKey: providerPatch.APIKey,
BaseUrl: providerPatch.BaseURL,
Api: providerPatch.APIType,
@@ -1656,10 +1641,6 @@ func agentDashboardAuthEnvKeys(agentType string) (string, string, bool) {
return hermesDashboardUsernameEnvKey, hermesDashboardPasswordEnvKey, true
case constant.AppCopaw:
return qwenPawAuthUsernameEnvKey, qwenPawAuthPasswordEnvKey, true
case constant.AppDeepSeekHarness:
return "DSH_AUTH_USERNAME", "DSH_AUTH_PASSWORD", true
case constant.AppOctop:
return "OCTOP_ADMIN_USERNAME", "OCTOP_DEFAULT_PASSWORD", true
default:
return "", "", false
}
-209
View File
@@ -1,209 +0,0 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/mattn/go-shellwords"
)
func AIBenchmarkSources(kind, selected string) ([]dto.AIBenchmarkSource, error) {
if kind != "vllm" && kind != "ollama" {
return nil, errors.New("unsupported benchmark source")
}
selectedID := strings.SplitN(selected, ":", 2)[0]
type instance struct {
id uint
name, container string
port int
}
instances := []instance{}
apps, err := appRepo.GetBy(appRepo.WithKey(kind))
if err != nil {
return nil, err
}
for _, app := range apps {
installs, err := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(app.ID))
if err != nil {
return nil, err
}
for _, install := range installs {
if selected != "" && strconv.FormatUint(uint64(install.ID), 10) != selectedID {
continue
}
instances = append(instances, instance{id: install.ID, name: install.Name, container: install.ContainerName, port: install.HttpPort})
}
}
results := []dto.AIBenchmarkSource{}
if len(instances) == 0 {
return results, nil
}
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
defer client.Close()
if !strings.HasPrefix(client.DaemonHost(), "unix://") {
return nil, errors.New("benchmark shortcuts require a Docker daemon on the main node; enter the remote service address manually")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
for _, instance := range instances {
base := dto.AIBenchmarkSource{Kind: kind, Name: instance.name, Endpoint: "/v1/chat/completions", Models: []string{}, Status: "unknown"}
inspected, err := client.ContainerInspect(ctx, instance.container)
if err != nil {
base.ID = strconv.FormatUint(uint64(instance.id), 10)
base.Message = "container inspection unavailable"
results = append(results, base)
continue
}
if inspected.State != nil {
base.Status = inspected.State.Status
}
args := []string{}
if inspected.Config != nil {
args = append(args, inspected.Config.Entrypoint...)
args = append(args, inspected.Config.Cmd...)
}
if len(args) >= 3 && (args[0] == "sh" || args[0] == "bash" || strings.HasSuffix(args[0], "/sh") || strings.HasSuffix(args[0], "/bash")) && args[1] == "-c" {
parser := shellwords.NewParser()
parser.ParseEnv = false
parser.ParseBacktick = false
if parsed, err := parser.Parse(args[2]); err == nil {
args = parsed
}
}
flags := map[string]string{}
for i, arg := range args {
if key, value, ok := strings.Cut(arg, "="); ok && strings.HasPrefix(key, "--") {
flags[key] = value
} else if strings.HasPrefix(arg, "--") && i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") {
flags[arg] = args[i+1]
}
}
if name := flags["--served-model-name"]; name != "" {
base.Models = []string{name}
}
tokenizer := flags["--tokenizer"]
if tokenizer == "" {
tokenizer = flags["--model"]
for i, arg := range args {
if tokenizer == "" && arg == "serve" && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
tokenizer = args[i+1]
}
}
}
if kind != "ollama" && tokenizer != "" {
if !filepath.IsAbs(tokenizer) {
base.Tokenizer = tokenizer
} else {
longest := 0
for _, mount := range inspected.Mounts {
relative, err := filepath.Rel(mount.Destination, tokenizer)
if err == nil && relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) && len(mount.Destination) > longest {
candidate := filepath.Join(mount.Source, relative)
if info, err := os.Stat(candidate); err == nil && info.IsDir() {
base.Tokenizer = candidate
longest = len(mount.Destination)
}
}
}
}
}
ports := map[int]string{}
if instance.port > 0 {
ports[instance.port] = "127.0.0.1"
}
if inspected.NetworkSettings != nil {
for _, bindings := range inspected.NetworkSettings.Ports {
for _, binding := range bindings {
port, _ := strconv.Atoi(binding.HostPort)
if port > 0 {
host := binding.HostIP
if host == "" || host == "0.0.0.0" || host == "::" {
host = "127.0.0.1"
}
ports[port] = host
}
}
}
}
if len(ports) == 0 && inspected.HostConfig != nil && inspected.HostConfig.NetworkMode == "host" {
port, _ := strconv.Atoi(flags["--port"])
if port > 0 {
ports[port] = "127.0.0.1"
}
}
ordered := []int{}
for port := range ports {
ordered = append(ordered, port)
}
sort.Ints(ordered)
if len(ordered) == 0 {
ordered = append(ordered, 0)
}
for _, port := range ordered {
source := base
source.ID = fmt.Sprintf("%d:%d", instance.id, port)
if selected != "" && source.ID != selected {
continue
}
if port > 0 {
source.BaseURL = "http://" + net.JoinHostPort(ports[port], strconv.Itoa(port))
}
if selected == source.ID && source.BaseURL != "" && source.Status == "running" {
endpoint := "/v1/models"
if kind == "ollama" {
endpoint = "/api/tags"
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, source.BaseURL+endpoint, nil)
if err != nil {
return nil, err
}
response, err := (&http.Client{Timeout: 3 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}).Do(request)
if err != nil {
source.Message = "model list unavailable; enter the served model name"
} else {
raw, readErr := io.ReadAll(io.LimitReader(response.Body, 2*1024*1024))
response.Body.Close()
if response.StatusCode == http.StatusOK && readErr == nil {
var data struct {
Data []struct {
ID string `json:"id"`
} `json:"data"`
Models []struct {
Name string `json:"name"`
} `json:"models"`
}
if err := json.Unmarshal(raw, &data); err == nil {
source.Models = []string{}
for _, model := range data.Data {
source.Models = append(source.Models, model.ID)
}
for _, model := range data.Models {
source.Models = append(source.Models, model.Name)
}
}
} else {
source.Message = fmt.Sprintf("model list HTTP %d; provide model name and credentials if required", response.StatusCode)
}
}
}
results = append(results, source)
}
}
return results, nil
}
+14 -26
View File
@@ -74,8 +74,8 @@ type IAlertService interface {
GetAlertConfig(req dto.AlertConfigQuery) ([]model.AlertConfig, error)
PageAlertConfig(req dto.AlertConfigPageReq) (int64, []model.AlertConfig, error)
UpdateAlertConfig(req dto.AlertConfigUpdate, operator string) error
UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) (string, error)
DeleteAlertConfig(id uint) (string, error)
UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error
DeleteAlertConfig(id uint) error
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
TestCustomAlertConfig(req dto.AlertConfigTest, lang string) (dto.AlertConfigTestResult, error)
}
@@ -623,26 +623,25 @@ func (a AlertService) updateCustomAlertConfig(req dto.AlertConfigUpdate, operato
})
}
func (a AlertService) UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) (string, error) {
func (a AlertService) UpdateAlertConfigStatus(req dto.AlertConfigStatusUpdate, operator string) error {
if err := validateAlertConfigStatus(req.Status); err != nil {
return "", err
return err
}
config, err := alertRepo.GetConfigById(req.ID)
if err != nil {
return "", err
return err
}
name := alertConfigLogName(config.Config, config.Title)
if req.Status == constant.AlertEnable {
if err := a.validateCommunityAlertConfigType(config.Type); err != nil {
return name, err
return err
}
if config.Type == constant.Custom {
if _, err := alertwebhook.Resolve(config); err != nil {
return name, err
return err
}
}
}
return name, alertRepo.UpdateAlertConfig(map[string]interface{}{
return alertRepo.UpdateAlertConfig(map[string]interface{}{
"status": req.Status,
"update_user": operator,
}, repo.WithByID(req.ID))
@@ -814,20 +813,19 @@ func alertConfigSMSPhone(configData string) string {
return strings.TrimSpace(cfg.Phone)
}
func (a AlertService) DeleteAlertConfig(id uint) (string, error) {
config, err := alertRepo.GetConfigById(id)
func (a AlertService) DeleteAlertConfig(id uint) error {
_, err := alertRepo.GetConfigById(id)
if err != nil {
return "", err
return err
}
name := alertConfigLogName(config.Config, config.Title)
usedAlerts, err := alertRepo.List(alertRepo.WithByAlertMethodContainsConfigID(id))
if err != nil {
return name, err
return err
}
if len(usedAlerts) > 0 {
return name, buserr.New("ErrAlertConfigInUse")
return buserr.New("ErrAlertConfigInUse")
}
return name, alertRepo.DeleteAlertConfig(repo.WithByID(id))
return alertRepo.DeleteAlertConfig(repo.WithByID(id))
}
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
@@ -1045,13 +1043,3 @@ func (a AlertService) validateCronJobAlertChannels(alertType, advanced, method s
_, err = a.validateAlertMethodReferences(method)
return err
}
func alertConfigLogName(config, title string) string {
var value struct {
DisplayName string `json:"displayName"`
}
if json.Unmarshal([]byte(config), &value) == nil && strings.TrimSpace(value.DisplayName) != "" {
return strings.TrimSpace(value.DisplayName)
}
return title
}
+16 -37
View File
@@ -258,35 +258,26 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
fileOp := files.NewFileOp()
versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version)
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
return appDetailDTO, err
}
if err == nil && app.Resource == constant.AppResourceRemote {
detail.Update = false
appDetailDTO.Update = false
if !fileOp.Stat(versionPath) || detail.Update {
if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) {
return appDetailDTO, err
}
}
switch app.Type {
case constant.RuntimePHP:
paramMap := make(map[string]interface{})
if app.Resource == constant.AppResourceRemote {
if err = json.Unmarshal([]byte(detail.Params), &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap
} else {
paramsPath := filepath.Join(versionPath, "data.yml")
if !fileOp.Stat(paramsPath) {
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
}
param, err := fileOp.GetContent(paramsPath)
if err != nil {
return appDetailDTO, err
}
if err = yaml.Unmarshal(param, &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap["additionalProperties"]
paramsPath := filepath.Join(versionPath, "data.yml")
if !fileOp.Stat(paramsPath) {
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil)
}
param, err := fileOp.GetContent(paramsPath)
if err != nil {
return appDetailDTO, err
}
paramMap := make(map[string]interface{})
if err = yaml.Unmarshal(param, &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap["additionalProperties"]
composePath := filepath.Join(versionPath, "docker-compose.yml")
if !fileOp.Stat(composePath) {
return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil)
@@ -317,12 +308,6 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
if appDetailDTO.DockerCompose == "" {
filename := filepath.Base(appDetailDTO.DownloadUrl)
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml")
if app.Resource == constant.AppResourceRemote {
dockerComposeUrl, err = currentAppDownloadURL(dockerComposeUrl)
if err != nil {
return appDetailDTO, err
}
}
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if statusCode == http.StatusNotFound {
return appDetailDTO, buserr.New("ErrAppVersionUnavailable")
@@ -449,12 +434,6 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
} else {
if appDetail.DockerCompose == "" {
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.AppRepoURL(), global.CONF.Base.Mode, app.Key, appDetail.Version)
if app.Resource == constant.AppResourceRemote {
dockerComposeUrl, err = currentAppDownloadURL(dockerComposeUrl)
if err != nil {
return
}
}
var statusCode int
statusCode, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if statusCode == http.StatusNotFound {
-6
View File
@@ -707,12 +707,6 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
if req.UpdateVersion != "" && req.UpdateVersion == detail.Version && detail.DockerCompose == "" && !app.IsLocalApp() {
filename := filepath.Base(detail.DownloadUrl)
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
if app.Resource == constant.AppResourceRemote {
dockerComposeUrl, err = currentAppDownloadURL(dockerComposeUrl)
if err != nil {
return versions, err
}
}
statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if statusCode == http.StatusNotFound {
return versions, buserr.New("ErrAppVersionUnavailable")
+1 -5
View File
@@ -191,11 +191,7 @@ func (c *appSyncContext) processOneApp(item appWorkItem) appWorkResult {
if _, ok := InitTypes[app.Type]; ok {
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
dockerComposeUrl, err := appDownloadURL(dockerComposeUrl, strconv.Itoa(c.list.LastModified))
var composeRes []byte
if err == nil {
_, composeRes, err = req_helper.HandleRequestWithClient(&c.httpClient, dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
}
_, composeRes, err := req_helper.HandleRequestWithClient(&c.httpClient, dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if err == nil {
detail.DockerCompose = string(composeRes)
} else {
+13 -44
View File
@@ -8,7 +8,6 @@ import (
"maps"
"math"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
@@ -892,52 +891,16 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
}
}
func appDownloadURL(rawURL, version string) (string, error) {
parsedURL, err := url.Parse(rawURL)
if err != nil {
return "", err
}
query := parsedURL.Query()
query.Set("v", version)
parsedURL.RawQuery = query.Encode()
return parsedURL.String(), nil
}
func currentAppDownloadURL(rawURL string) (string, error) {
storeVersion, err := settingRepo.GetValueByKey("AppStoreLastModified")
if err != nil {
return "", err
}
return appDownloadURL(rawURL, storeVersion)
}
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, logger *logrus.Logger) (err error) {
if app.IsLocalApp() || app.IsCustomApp() {
return nil
}
defer func() {
if err != nil && appInstall != nil {
appInstall.Status = constant.StatusDownloadErr
appInstall.Message = err.Error()
}
}()
appResourceDir := path.Join(global.Dir.AppResourceDir, app.Resource)
appDownloadDir := app.GetAppResourcePath()
appVersionDir := path.Join(appDownloadDir, appDetail.Version)
fileOp := files.NewFileOp()
storeVersion, err := settingRepo.GetValueByKey("AppStoreLastModified")
if err != nil {
return err
}
cacheVersionPath := path.Join(appVersionDir, ".1panel-appstore-version")
if !appDetail.Update && fileOp.Stat(appVersionDir) {
if cachedVersion, readErr := os.ReadFile(cacheVersionPath); readErr == nil && string(cachedVersion) == storeVersion {
return nil
}
}
downloadURL, err := appDownloadURL(appDetail.DownloadUrl, storeVersion)
if err != nil {
return err
return
}
if !fileOp.Stat(appDownloadDir) {
_ = fileOp.CreateDir(appDownloadDir, constant.DirPerm)
@@ -946,14 +909,23 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
_ = fileOp.CreateDir(appVersionDir, constant.DirPerm)
}
if logger == nil {
global.LOG.Infof("download app [%s] from %s", app.Name, downloadURL)
global.LOG.Infof("download app [%s] from %s", app.Name, appDetail.DownloadUrl)
} else {
logger.Printf("download app [%s] from %s", app.Name, downloadURL)
logger.Printf("download app [%s] from %s", app.Name, appDetail.DownloadUrl)
}
filePath := path.Join(appVersionDir, app.Key+"-"+appDetail.Version+".tar.gz")
if err = files.DownloadFileWithProxy(downloadURL, filePath); err != nil {
defer func() {
if err != nil {
if appInstall != nil {
appInstall.Status = constant.StatusDownloadErr
appInstall.Message = err.Error()
}
}
}()
if err = files.DownloadFileWithProxy(appDetail.DownloadUrl, filePath); err != nil {
if logger == nil {
global.LOG.Errorf("download app [%s] error %v", app.Name, err)
} else {
@@ -970,9 +942,6 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
return
}
_ = fileOp.DeleteFile(filePath)
if err = os.WriteFile(cacheVersionPath, []byte(storeVersion), constant.FilePerm); err != nil {
return err
}
appDetail.Update = false
_ = appDetailRepo.Update(context.Background(), appDetail)
return
+6 -16
View File
@@ -43,10 +43,6 @@ type IDashboardService interface {
LoadQuickOptions() []dto.QuickJump
ChangeQuick(req dto.ChangeQuicks) error
ListQuickDirectories() ([]model.QuickDirectory, error)
CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error)
UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error)
DeleteQuickDirectory(req dto.QuickDirectoryDelete) error
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
ChangeShow(req dto.SettingUpdate) error
@@ -401,15 +397,16 @@ func (u *DashboardService) LoadQuickOptions() []dto.QuickJump {
}
func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
showCount := 0
quicks := make([]model.QuickJump, 0, len(req.Quicks))
var quicks []model.QuickJump
for _, item := range req.Quicks {
var quick model.QuickJump
if item.IsShow {
showCount++
}
quicks = append(quicks, model.QuickJump{
BaseModel: model.BaseModel{ID: item.ID},
IsShow: item.IsShow,
})
if err := copier.Copy(&quick, &item); err != nil {
return err
}
quicks = append(quicks, quick)
}
if showCount == 0 {
return buserr.New("ErrMinQuickJump")
@@ -709,13 +706,6 @@ func loadQuickJump(base *dto.DashboardBase) {
quicks := launcherRepo.ListQuickJump(false)
for i := 0; i < len(quicks); i++ {
switch quicks[i].Name {
case "File":
count, err := launcherRepo.CountQuickDirectories()
if err != nil {
global.LOG.Errorf("load quick directory count: %v", err)
}
quicks[i].Detail = fmt.Sprintf("%d", count)
quicks[i].Alias = ""
case "Agent":
quicks[i].Detail = fmt.Sprintf("%d", base.AgentNumber)
case "Website":
+55 -47
View File
@@ -131,9 +131,6 @@ func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystem
func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOperation) (dto.FirewallLifecycleOperationResponse, error) {
response := dto.FirewallLifecycleOperationResponse{}
if request.BackupFile != "" && request.Operation != string(lifecycle.OperationStart) {
return response, fmt.Errorf("backup restoration is only supported when starting firewalld or ufw")
}
if request.Operation == "disableBanPing" || request.Operation == "enableBanPing" {
return response, s.OperateFirewall(request)
}
@@ -158,19 +155,6 @@ func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOp
if err != nil {
return response, err
}
var restoreRequest dto.FirewallRuleCreate
if request.BackupFile != "" {
if client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW {
return response, fmt.Errorf("backup restoration is only supported when starting firewalld or ufw")
}
restoreRequest, err = prepareFirewallRuleCreate(dto.FirewallRuleCreate{BackupFile: request.BackupFile, Initialize: true})
if err != nil {
return response, err
}
if err := validateFirewallCreateBatch(restoreRequest, filter.Provider(client.Name())); err != nil {
return response, err
}
}
if (client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW) ||
(request.Operation != string(lifecycle.OperationStart) && request.Operation != string(lifecycle.OperationStop) &&
request.Operation != string(lifecycle.OperationRestart)) {
@@ -189,26 +173,7 @@ func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOp
return response, err
}
taskItem.AddSubTaskWithOps(name, func(t *task.Task) error {
if request.BackupFile != "" {
if err := s.checkSelectedProvider(t.TaskCtx, filter.Provider(client.Name())); err != nil {
return err
}
}
prepareStart := func(lifecycle.Client) error {
if request.BackupFile != "" {
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallCreateRulesStep"), func() error {
return s.runFirewallCreateRulesTask(t, restoreRequest, filter.Provider(client.Name()))
})
}
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
err := s.SyncPortWhitelist(t.TaskCtx)
if err == nil && client.Name() == lifecycle.ProviderFirewalld {
err = lifecycleproviders.RemoveFirewalldSSHService()
}
return err
})
}
return s.runFirewallLifecycleTask(t, client, request, prepareStart)
return s.runFirewallLifecycleTask(t, client, request, true)
}, nil, 0, 0)
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
closeUnstartedFirewallTask(taskItem)
@@ -589,9 +554,29 @@ func (s *FirewallService) LoadFirewallNativeDetail(ctx context.Context, request
}
func (s *FirewallService) Create(ctx context.Context, request dto.FirewallRuleCreate) (dto.FirewallRuleCreateResponse, error) {
request, err := prepareFirewallRuleCreate(request)
if err != nil {
return dto.FirewallRuleCreateResponse{}, err
if request.BackupFile != "" {
if len(request.Items) != 0 {
return dto.FirewallRuleCreateResponse{}, filter.ErrInvalidRule
}
items, err := readFirewallBackup(request.BackupFile)
if err != nil {
return dto.FirewallRuleCreateResponse{}, err
}
for _, item := range items {
request.Items = append(request.Items, dto.FirewallRuleCreateItem{Rule: item.FirewallRule, Raw: item.Raw, ParseStatus: item.ParseStatus, SourceKind: constant.FirewallRuleSourceImported})
}
}
for index, item := range request.Items {
if item.SourceKind != constant.FirewallRuleSourceImported || item.Rule.Scope.Provider != filter.ProviderUFW || item.ParseStatus != filter.ParseStatusPartial || item.Raw == "" {
continue
}
observed := filterufw.ParseRules(item.Rule.Scope, item.Raw)
if len(observed) == 1 && observed[0].ParseStatus == filter.ParseStatusSupported {
observed[0].Rule.Description = item.Rule.Description
request.Items[index].Rule = observed[0].Rule
request.Items[index].ParseStatus, request.Items[index].Raw = filter.ParseStatusSupported, ""
}
}
provider, err := s.selectedProvider(ctx)
@@ -624,11 +609,30 @@ func (s *FirewallService) Create(ctx context.Context, request dto.FirewallRuleCr
if err != nil {
return err
}
return s.runFirewallLifecycleTask(t, client, dto.FirewallLifecycleOperation{Operation: "start"}, nil)
return s.runFirewallLifecycleTask(t, client, dto.FirewallLifecycleOperation{Operation: "start"}, false)
}, nil)
}
ruleState := make(map[string]filter.RuleSet)
taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("FirewallCreateRulesStep"), func(t *task.Task) error {
return s.runFirewallCreateRulesTask(t, request, provider)
firewallRuleMutationMu.Lock()
if err := s.checkSelectedProvider(t.TaskCtx, provider); err != nil {
firewallRuleMutationMu.Unlock()
return err
}
_, err := s.createRules(t.TaskCtx, request, t, ruleState)
firewallRuleMutationMu.Unlock()
if !request.Initialize || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return err
}
if err != nil {
ruleState = nil
}
whitelistErr := s.syncPortWhitelist(t.TaskCtx, ruleState)
if whitelistErr == nil && provider == filter.ProviderFirewalld {
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
}
t.LogWithStatus(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), whitelistErr)
return errors.Join(err, whitelistErr)
}, nil, 0, 0)
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
taskItem.LogFailedWithErr(taskItem.Name, err)
@@ -773,11 +777,7 @@ func (s *FirewallService) restoreFirewallAfterStart(client lifecycle.Client) err
return nil
}
}
whitelistErr := s.SyncPortWhitelist(ctx)
if whitelistErr == nil && provider == filter.ProviderFirewalld {
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
}
recordFailure("restore whitelist allowances", whitelistErr)
recordFailure("restore whitelist allowances", s.SyncPortWhitelist(ctx))
return errors.Join(recoveryErrors...)
}
@@ -805,9 +805,17 @@ func (s *FirewallService) restoreFirewalldRuntimeDependents(ctx context.Context,
return restoreErr
}
func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycle.Client, request dto.FirewallLifecycleOperation, prepareStart func(lifecycle.Client) error) error {
func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycle.Client, request dto.FirewallLifecycleOperation, syncWhitelist bool) error {
ctx := t.TaskCtx
provider := filter.Provider(client.Name())
var prepareStart func(lifecycle.Client) error
if syncWhitelist {
prepareStart = func(lifecycle.Client) error {
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
return s.SyncPortWhitelist(ctx)
})
}
}
operationErr := operateFirewallLifecycle(firewallLifecycleClient{client}, request.Operation, request.WithDockerRestart, prepareStart, t)
if request.Operation == string(lifecycle.OperationStop) {
return operationErr
+3 -117
View File
@@ -554,58 +554,6 @@ func (s *FirewallService) checkSelectedProvider(ctx context.Context, requested f
return nil
}
func prepareFirewallRuleCreate(request dto.FirewallRuleCreate) (dto.FirewallRuleCreate, error) {
if request.BackupFile != "" {
if len(request.Items) != 0 {
return request, filter.ErrInvalidRule
}
items, err := readFirewallBackup(request.BackupFile)
if err != nil {
return request, err
}
for _, item := range items {
request.Items = append(request.Items, dto.FirewallRuleCreateItem{Rule: item.FirewallRule, Raw: item.Raw, ParseStatus: item.ParseStatus, SourceKind: constant.FirewallRuleSourceImported})
}
}
for index, item := range request.Items {
if item.SourceKind != constant.FirewallRuleSourceImported || item.Rule.Scope.Provider != filter.ProviderUFW || item.ParseStatus != filter.ParseStatusPartial || item.Raw == "" {
continue
}
observed := filterufw.ParseRules(item.Rule.Scope, item.Raw)
if len(observed) == 1 && observed[0].ParseStatus == filter.ParseStatusSupported {
observed[0].Rule.Description = item.Rule.Description
request.Items[index].Rule = observed[0].Rule
request.Items[index].ParseStatus, request.Items[index].Raw = filter.ParseStatusSupported, ""
}
}
return request, nil
}
func (s *FirewallService) runFirewallCreateRulesTask(t *task.Task, request dto.FirewallRuleCreate, provider filter.Provider) error {
ruleState := make(map[string]filter.RuleSet)
firewallRuleMutationMu.Lock()
if err := s.checkSelectedProvider(t.TaskCtx, provider); err != nil {
firewallRuleMutationMu.Unlock()
return err
}
_, err := s.createRules(t.TaskCtx, request, t, ruleState)
firewallRuleMutationMu.Unlock()
if !request.Initialize || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return err
}
if err != nil {
ruleState = nil
}
whitelistErr := s.syncPortWhitelist(t.TaskCtx, ruleState)
if whitelistErr == nil && provider == filter.ProviderFirewalld {
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
}
t.LogWithStatus(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), whitelistErr)
return errors.Join(err, whitelistErr)
}
func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallRuleCreate, t *task.Task, ruleState map[string]filter.RuleSet) (result dto.FirewallRuleCreateResponse, taskErr error) {
var firstFailure error
defer func() {
@@ -1786,6 +1734,9 @@ func operateFirewallLifecycle(client lifecycle.Client, operation string, withDoc
var recoveryErrors []error
if prepareStart != nil {
err := prepareStart(client)
if err == nil && client.Name() == constant.FirewallProviderFirewalld {
err = lifecycleproviders.RemoveFirewalldSSHService()
}
if err != nil {
recoveryErrors = append(recoveryErrors, fmt.Errorf("prepare firewall after %s: %w", operation, err))
}
@@ -1807,82 +1758,17 @@ func operateFirewallLifecycle(client lifecycle.Client, operation string, withDoc
}
func (c firewallLifecycleClient) Start() error {
firewallWhitelistMu.Lock()
defer firewallWhitelistMu.Unlock()
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
if err := c.prepareAccessWhitelistBeforeStart(); err != nil {
global.LOG.Warnf("prepare %s access whitelist before start failed: %v", c.Name(), err)
}
return c.Client.Start()
}
func (c firewallLifecycleClient) Restart() error {
firewallWhitelistMu.Lock()
defer firewallWhitelistMu.Unlock()
firewallRuleMutationMu.Lock()
defer firewallRuleMutationMu.Unlock()
if err := c.prepareAccessWhitelistBeforeStart(); err != nil {
global.LOG.Warnf("prepare %s access whitelist before restart failed: %v", c.Name(), err)
}
return c.Client.Restart()
}
func (c firewallLifecycleClient) prepareAccessWhitelistBeforeStart() error {
provider := filter.Provider(c.Name())
if provider != filter.ProviderFirewalld && provider != filter.ProviderUFW {
return nil
}
ports, err := LoadRequiredFirewallPortWhiteList()
if err != nil {
return err
}
var adapter filter.Adapter
var active bool
if provider == filter.ProviderFirewalld {
active, err = c.Status()
if err != nil {
return err
}
adapter = filterfirewalld.NewAdapter()
} else {
adapter = filterufw.NewAdapter()
}
var commands []filter.NativeCommand
for _, port := range firewall.ExpandPortWhitelist(ports) {
rule := firewall.RuleForSystemPort(provider, port)
rule.Scope = rule.Scope.Normalize()
rule.UUID = uuid.NewString()
if provider == filter.ProviderUFW {
position := int64(1)
rule.OrderIndex = &position
}
plan, err := adapter.BuildCommands(filter.RuleSet{Scope: rule.Scope}, []filter.RuleChange{{Operation: filter.ChangeCreate, After: &rule}})
if err != nil {
return err
}
for _, item := range plan.Rules {
for _, command := range item.Commands {
if provider == filter.ProviderFirewalld {
if active {
command.Args = append(command.Args, "--permanent")
} else {
command.Executable = "firewall-offline-cmd"
}
}
commands = append(commands, command)
}
}
}
manager := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second), cmd.WithEnv("LC_ALL=C", "LANGUAGE=en_US:en"))
for _, command := range commands {
if err := manager.RunWithOptionalSudo(command.Executable, command.Args...); err != nil {
return fmt.Errorf("prepare %s access whitelist before start: %w", provider, err)
}
}
return nil
}
func runFirewallLifecycleAction(t *task.Task, name string, action func() error) error {
t.Log(i18n.GetWithName("TaskStart", name))
started := time.Now()
+1 -1
View File
@@ -55,7 +55,7 @@ type IMonitorService interface {
LoadIOOptions() []string
LoadNetworkOptions() []string
LoadVLLMMonitorData(ctx context.Context, req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error)
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error)
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error)
CleanVLLMMonitor(req dto.MonitorVLLMClean) error
LoadGPUOptions() dto.MonitorGPUOptions
LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error)
+28 -34
View File
@@ -60,9 +60,6 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
if err != nil {
return data, err
}
for i := range points {
points[i].MonitorVLLM.CompletedRequests = points[i].CompletedRequests
}
if data.BucketSeconds > 0 {
aggregates := map[int64]*vllm.HistoryAccumulator{}
err = vllmMonitorRepo.WalkDeltas(ctx, req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation, req.Group, func(bucket int64, histograms, requests string) error {
@@ -94,6 +91,15 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
data.Points = append(data.Points, point)
}
} else {
setting, err := settingRepo.GetValueByKey("VLLMMonitorInterval")
if err != nil {
return data, err
}
intervalSeconds, err := strconv.Atoi(setting)
if err != nil || intervalSeconds <= 0 {
return data, fmt.Errorf("invalid vLLM monitoring interval: %s", setting)
}
interval := time.Duration(intervalSeconds) * time.Second
for i, point := range points {
if err := ctx.Err(); err != nil {
return data, err
@@ -103,20 +109,8 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
return data, err
}
aggregate.Apply(&point.MonitorVLLM, "max")
if i > 0 {
previous := points[i-1]
interval := time.Duration(max(previous.SampleIntervalSeconds, point.SampleIntervalSeconds)) * time.Second
if interval == 0 {
if i > 1 {
interval = previous.CreatedAt.Sub(points[i-2].CreatedAt)
}
if i+1 < len(points) {
interval = max(interval, points[i+1].CreatedAt.Sub(point.CreatedAt))
}
}
if interval > 0 && point.CreatedAt.Sub(previous.CreatedAt) > 2*interval {
data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: previous.CreatedAt.Add(interval), AppInstallID: req.AppInstallID})
}
if i > 0 && point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: points[i-1].CreatedAt.Add(interval), AppInstallID: req.AppInstallID})
}
data.Points = append(data.Points, point.MonitorVLLM)
}
@@ -124,30 +118,31 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
return data, nil
}
func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error) {
func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) {
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
if err != nil {
return dto.MonitorVLLMCurrentData{}, err
return model.MonitorVLLM{}, err
}
if install.App.Key != "vllm" {
return dto.MonitorVLLMCurrentData{}, fmt.Errorf("not a vLLM installation")
return model.MonitorVLLM{}, fmt.Errorf("not a vLLM installation")
}
point, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{})
data := dto.MonitorVLLMCurrentData{MonitorVLLM: point}
previous, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{})
if err != nil {
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
return data, nil
return previous, nil
}
if err := json.Unmarshal([]byte(point.RawMetrics), &data.Metrics); err != nil {
return data, err
timer := time.NewTimer(time.Second)
defer timer.Stop()
select {
case <-ctx.Done():
return model.MonitorVLLM{}, ctx.Err()
case <-timer.C:
}
for name := range data.Metrics {
if name == "request_prompt_tokens_sum" || name == "request_prompt_tokens_count" ||
(!strings.HasSuffix(name, "_total") && !strings.HasSuffix(name, "_sum") && !strings.HasSuffix(name, "_count")) {
delete(data.Metrics, name)
}
point, err := collectVLLMMetrics(ctx, install, previous)
if err != nil {
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
}
return data, nil
return point, nil
}
func (m *MonitorService) CleanVLLMMonitor(req dto.MonitorVLLMClean) error {
@@ -169,7 +164,7 @@ func StartVLLMMonitor(interval string) error {
return fmt.Errorf("invalid vLLM monitoring interval: %s", interval)
}
service := &MonitorService{}
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(func() { service.saveVLLMData(seconds) }))
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveVLLMData))
id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() })
if err != nil {
return err
@@ -181,7 +176,7 @@ func StartVLLMMonitor(interval string) error {
return nil
}
func (m *MonitorService) saveVLLMData(intervalSeconds int) {
func (m *MonitorService) saveVLLMData() {
if !vllmMonitorMutex.TryLock() {
return
}
@@ -233,7 +228,6 @@ func (m *MonitorService) saveVLLMData(intervalSeconds int) {
return err
}
point, err := collectVLLMMetrics(context.Background(), install, previous)
point.SampleIntervalSeconds = intervalSeconds
if err != nil {
global.LOG.Debugf("Collect vLLM metrics for %s failed: %v", install.Name, err)
}
-49
View File
@@ -1,49 +0,0 @@
package service
import (
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
)
func (u *DashboardService) ListQuickDirectories() ([]model.QuickDirectory, error) {
return launcherRepo.ListQuickDirectories()
}
func quickDirectoryValues(req dto.QuickDirectoryCreate) (model.QuickDirectory, error) {
item := model.QuickDirectory{Path: req.Path, Alias: strings.TrimSpace(req.Alias)}
if strings.TrimSpace(item.Path) == "" {
return item, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
}
return item, nil
}
func (u *DashboardService) CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error) {
item, err := quickDirectoryValues(req)
if err != nil {
return nil, err
}
if err := launcherRepo.CreateQuickDirectory(&item); err != nil {
return nil, err
}
return &item, nil
}
func (u *DashboardService) UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error) {
item, err := quickDirectoryValues(req.QuickDirectoryCreate)
if err != nil {
return nil, err
}
item.ID = req.ID
item.Legacy = req.Legacy
if err := launcherRepo.UpdateQuickDirectory(&item); err != nil {
return nil, err
}
return &item, nil
}
func (u *DashboardService) DeleteQuickDirectory(req dto.QuickDirectoryDelete) error {
return launcherRepo.DeleteQuickDirectory(req.ID, req.Legacy)
}
+3 -12
View File
@@ -212,17 +212,10 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
websiteSSL.Shell = req.Shell
}
if req.PushDir {
dirs := certificateDirectories(req.Dir)
if len(dirs) == 0 {
if !files.NewFileOp().Stat(req.Dir) {
return nil, buserr.New("ErrLinkPathNotFound")
}
for _, dir := range dirs {
info, err := os.Stat(dir)
if err != nil || !info.IsDir() {
return nil, buserr.New("ErrLinkPathNotFound")
}
}
websiteSSL.Dir = strings.Join(dirs, "\n")
websiteSSL.Dir = req.Dir
}
if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n")
@@ -355,9 +348,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
if websiteSSL.ExecShell {
workDir := global.Dir.DataDir
if websiteSSL.PushDir {
if dirs := certificateDirectories(websiteSSL.Dir); len(dirs) > 0 {
workDir = dirs[0]
}
workDir = websiteSSL.Dir
}
logger.Println(i18n.GetMsgByKey("ExecShellStart"))
if err = runShellScriptFile(workDir, websiteSSL.Shell, logger); err != nil {
+8 -21
View File
@@ -209,17 +209,11 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
websiteSSL.Shell = create.Shell
}
if create.PushDir {
dirs := certificateDirectories(create.Dir)
if len(dirs) == 0 {
fileOP := files.NewFileOp()
if !fileOP.Stat(create.Dir) {
return res, buserr.New("ErrLinkPathNotFound")
}
for _, dir := range dirs {
info, err := os.Stat(dir)
if err != nil || !info.IsDir() {
return res, buserr.New("ErrLinkPathNotFound")
}
}
websiteSSL.Dir = strings.Join(dirs, "\n")
websiteSSL.Dir = create.Dir
}
setSSLPushConfig(&websiteSSL, create.PushNode, create.Nodes)
@@ -579,9 +573,7 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
if websiteSSL.ExecShell {
workDir := global.Dir.DataDir
if websiteSSL.PushDir {
if dirs := certificateDirectories(websiteSSL.Dir); len(dirs) > 0 {
workDir = dirs[0]
}
workDir = websiteSSL.Dir
}
printSSLLog(logger, "ExecShellStart", nil)
if err = runShellScriptFile(workDir, websiteSSL.Shell, logger); err != nil {
@@ -801,16 +793,11 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error {
}
if update.PushDir {
dirs := certificateDirectories(update.Dir)
if len(dirs) == 0 {
return buserr.New("ErrLinkPathNotFound")
fileOP := files.NewFileOp()
if !fileOP.Stat(update.Dir) {
_ = fileOP.CreateDir(update.Dir, constant.DirPerm)
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, constant.DirPerm); err != nil {
return err
}
}
updateParams["dir"] = strings.Join(dirs, "\n")
updateParams["dir"] = update.Dir
}
var domains []string
if update.OtherDomains != "" {
+18 -33
View File
@@ -1529,41 +1529,26 @@ func checkWebsitePort(defaultHTTPsPort, port int, websiteType string) error {
return nil
}
// certificateDirectories accepts both legacy single paths and newline-separated paths.
func certificateDirectories(value string) []string {
var dirs []string
seen := make(map[string]bool)
for _, dir := range strings.Split(value, "\n") {
dir = strings.TrimSpace(dir)
if dir == "" {
continue
}
dir = filepath.Clean(dir)
if !seen[dir] {
seen[dir] = true
dirs = append(dirs, dir)
}
}
return dirs
}
func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
if !websiteSSL.PushDir {
return
}
fileOp := files.NewFileOp()
for _, dir := range certificateDirectories(websiteSSL.Dir) {
status := i18n.GetMsgByKey("Success")
for _, file := range []struct{ name, content string }{
{"privkey.pem", websiteSSL.PrivateKey},
{"fullchain.pem", websiteSSL.Pem},
} {
if err := fileOp.SaveFile(path.Join(dir, file.name), file.content, constant.FilePerm); err != nil {
status = i18n.GetMsgByKey("Failed")
logger.Printf("Push dir %s failed: %s", dir, err)
}
if websiteSSL.PushDir {
fileOp := files.NewFileOp()
var (
pushErr error
MsgMap = map[string]interface{}{"path": websiteSSL.Dir, "status": i18n.GetMsgByKey("Success")}
)
if pushErr = fileOp.SaveFile(path.Join(websiteSSL.Dir, "privkey.pem"), websiteSSL.PrivateKey, constant.FilePerm); pushErr != nil {
MsgMap["status"] = i18n.GetMsgByKey("Failed")
logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap))
logger.Println("Push dir failed:" + pushErr.Error())
}
if pushErr = fileOp.SaveFile(path.Join(websiteSSL.Dir, "fullchain.pem"), websiteSSL.Pem, constant.FilePerm); pushErr != nil {
MsgMap["status"] = i18n.GetMsgByKey("Failed")
logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap))
logger.Println("Push dir failed:" + pushErr.Error())
}
if pushErr == nil {
logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap))
}
logger.Println(i18n.GetMsgWithMap("PushDirLog", map[string]interface{}{"path": dir, "status": status}))
}
}
+14 -10
View File
@@ -108,29 +108,33 @@
"formatEN": "create alert [title]"
},
"/alert/config/del": {
"bodyKeys": [],
"bodyKeys": [
"id"
],
"paramKeys": [],
"formatZH": "删除告警配置 [name]",
"formatEN": "delete alert config [name]",
"beforeFunctions": []
"beforeFunctions": [],
"formatZH": "删除告警配置 [id]",
"formatEN": "delete alert config [id]"
},
"/alert/config/status": {
"bodyKeys": [
"id",
"status"
],
"paramKeys": [],
"formatZH": "更新告警配置状态 [name][status]",
"formatEN": "update alert config status [name][status]",
"beforeFunctions": []
"beforeFunctions": [],
"formatZH": "更新告警配置状态 [id][status]",
"formatEN": "update alert config status [id][status]"
},
"/alert/config/update": {
"bodyKeys": [
"id",
"displayName"
],
"paramKeys": [],
"formatZH": "更新告警配置 [displayName]",
"formatEN": "update alert config [displayName]",
"beforeFunctions": []
"beforeFunctions": [],
"formatZH": "更新告警配置 [id][displayName]",
"formatEN": "update alert config [id][displayName]"
},
"/alert/del": {
"bodyKeys": [
-2
View File
@@ -10,8 +10,6 @@ const (
AppOpenclaw = "openclaw"
AppCopaw = "copaw"
AppHermesAgent = "hermes-agent"
AppDeepSeekHarness = "deepseek-harness"
AppOctop = "octop"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppPostgresql = "postgresql"
+3 -3
View File
@@ -1,6 +1,6 @@
module github.com/1Panel-dev/1Panel/agent
go 1.27.3
go 1.26.6
replace github.com/go-acme/lego/v5 => github.com/1Panel-dev/lego/v5 v5.3.1
@@ -38,7 +38,7 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.11
github.com/qiniu/go-sdk/v7 v7.27.0
github.com/qiniu/go-sdk/v7 v7.29.0
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.26.9
github.com/sirupsen/logrus v1.10.2
@@ -66,6 +66,7 @@ require (
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
@@ -216,7 +217,6 @@ require (
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/arch v0.26.0 // indirect
golang.org/x/image v0.45.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
google.golang.org/grpc v1.83.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
+6 -50
View File
@@ -228,18 +228,12 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v29.7.2+incompatible h1:dlkwallR8XqfeVnA2ELEhdwvb4lsSwuB4IgsG8Q9cLY=
github.com/docker/cli v29.7.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.8.1+incompatible h1:qYL1bCp6cRw2SB1xmLlIOPyV171dilw9W2Jew38vy9c=
github.com/docker/cli v29.8.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.9.0+incompatible h1:Cw9x50ioSGgGwXJ4eO+hIBry/w3LLbyF8GPAEKz1fnE=
github.com/docker/cli v29.9.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M=
github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-connections v0.8.2 h1:9d9DEokx5W7qlr0shkFXIxEA7+TTD+llwzcykSlxMNc=
github.com/docker/go-connections v0.8.2/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -254,8 +248,6 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.11.1 h1:2zpWRSQNVKN4eKsKO9eM1ILDgWfYMY9GwqRmK6XeQ/0=
github.com/ebitengine/purego v0.11.1/go.mod h1:DCHPP08djqhNSoTfImcnHYQRZmd0qhakvrozqaEYhGQ=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
@@ -573,8 +565,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg=
github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-shellwords v1.0.16 h1:RRxAaRzU1YbzOSCj9NJqg2/VIbSWv0dnPoD3EwE8kxI=
github.com/mattn/go-shellwords v1.0.16/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
@@ -711,8 +701,6 @@ github.com/pkg/sftp v1.13.11/go.mod h1:uNkH9roSXglNJqM+glJJi+TQXQUm0fXFWqCFmT8hs
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20260805114148-88456608a4f6 h1:jL3a8soXdzuTCcRnKhOmtcsVOObdDTFf4O2B403HPRU=
github.com/power-devops/perfstat v0.0.0-20260805114148-88456608a4f6/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -735,8 +723,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/qiniu/go-sdk/v7 v7.27.0 h1:n+2U0S5fhbmG/lN/agO8KcYJaQDqROUVShtnp56Mkw8=
github.com/qiniu/go-sdk/v7 v7.27.0/go.mod h1:pTwVR1B+8SXcPLhDzBUasiKFTD9F7jRglRDR553BW3k=
github.com/qiniu/go-sdk/v7 v7.29.0 h1:W6bnl2IEtpkKH/MJtYC3HKGwcEViu7hqZ2OWc0Dg78Q=
github.com/qiniu/go-sdk/v7 v7.29.0/go.mod h1:pTwVR1B+8SXcPLhDzBUasiKFTD9F7jRglRDR553BW3k=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
@@ -762,10 +750,6 @@ github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9t
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil/v4 v4.26.7 h1:IXzpHz/dkMRYAhKkOXr1HB6SuzWU3eoyyeWe7g3bNZc=
github.com/shirou/gopsutil/v4 v4.26.7/go.mod h1:5O9FjBiXoTDFatIWjZZosqj4pV0DRtLx598xGbBehzM=
github.com/shirou/gopsutil/v4 v4.26.8 h1:YQMTF/1J50B5+Y0vlo1eDRf5DoR7Gk69hY+8wjYkQeo=
github.com/shirou/gopsutil/v4 v4.26.8/go.mod h1:5O9FjBiXoTDFatIWjZZosqj4pV0DRtLx598xGbBehzM=
github.com/shirou/gopsutil/v4 v4.26.9 h1:CaBo/hBFqvlJoLvAQEKdHdf5GD97/MN2ACfyv63+2Ig=
github.com/shirou/gopsutil/v4 v4.26.9/go.mod h1:nKH+8wX2zxr/mDbsR+AAy6Qd2z4ZOQ7wfQaEp+yrYmg=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
@@ -892,14 +876,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.mongodb.org/mongo-driver v1.17.10 h1:kdAgQvu8TROXZpSkJQd5wzfaNCCrMbpZyKFtQ6qkPCE=
go.mongodb.org/mongo-driver v1.17.10/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.mongodb.org/mongo-driver/v2 v2.9.0 h1:e2mQdOmbkiYz+dj3faM7lVDwl7WdnRD+g5VicafMhL0=
go.mongodb.org/mongo-driver/v2 v2.9.0/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA=
go.mongodb.org/mongo-driver/v2 v2.9.1 h1:jewiFs2m1/VOQp8qhFshX6hWZ+EAXDhZHXExAUMcOgQ=
go.mongodb.org/mongo-driver/v2 v2.9.1/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA=
go.mongodb.org/mongo-driver/v2 v2.9.2 h1:OCAdjI37pC/SVxucz/9CM8zD4jCdegtsY5QcKzYFCgE=
go.mongodb.org/mongo-driver/v2 v2.9.2/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -974,10 +952,6 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -996,8 +970,6 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1023,9 +995,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1080,8 +1051,6 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1108,8 +1077,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1171,6 +1138,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1187,10 +1155,6 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
@@ -1206,10 +1170,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1226,10 +1188,6 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1293,10 +1251,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-3
View File
@@ -114,9 +114,6 @@ func agentDBMigrations() []*gormigrate.Migration {
migrations.AddVLLMMonitor,
migrations.AddMonitorSettings,
migrations.AddVLLMRequestMetrics,
migrations.AddVLLMSampleInterval,
migrations.AddNativeAgentInstalls,
migrations.AddQuickDirectory,
}
}
@@ -1,47 +0,0 @@
package migrations
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
var AddNativeAgentInstalls = &gormigrate.Migration{
ID: "20261020-add-deepseek-harness-octop-agent-installs",
Migrate: func(tx *gorm.DB) error {
var installs []struct {
model.AppInstall
AgentType string
}
if err := tx.Table("app_installs").
Select("app_installs.*, apps.key AS agent_type").
Joins("JOIN apps ON apps.id = app_installs.app_id").
Where("apps.key IN ?", []string{constant.AppDeepSeekHarness, constant.AppOctop}).
Where("NOT EXISTS (SELECT 1 FROM agents WHERE agents.app_install_id = app_installs.id)").
Order("app_installs.id").
Scan(&installs).Error; err != nil {
return err
}
for _, install := range installs {
var count int64
if err := tx.Model(&model.Agent{}).Where("LOWER(name) = LOWER(?)", install.Name).Count(&count).Error; err != nil {
return err
}
if count > 0 {
global.LOG.Warnf("skip agent registration for app install %d: name already exists", install.ID)
continue
}
agent := model.Agent{
BaseModel: model.BaseModel{CreatedAt: install.CreatedAt, UpdatedAt: install.UpdatedAt},
Name: install.Name, AgentType: install.AgentType, AppInstallID: install.ID,
Status: install.Status, Message: install.Message,
}
if err := tx.Create(&agent).Error; err != nil {
return err
}
}
return nil
},
}
-14
View File
@@ -1881,17 +1881,3 @@ var AddVLLMRequestMetrics = &gormigrate.Migration{
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
},
}
var AddVLLMSampleInterval = &gormigrate.Migration{
ID: "20261016-vllm-sample-interval",
Migrate: func(tx *gorm.DB) error {
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
},
}
var AddQuickDirectory = &gormigrate.Migration{
ID: "20261016-add-quick-directory",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.QuickDirectory{})
},
}
-1
View File
@@ -13,7 +13,6 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
aiToolsRouter.GET("/benchmark/sources", baseApi.AIBenchmarkSources)
aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel)
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
-4
View File
@@ -14,10 +14,6 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
cmdRouter.GET("/quick/option", baseApi.LoadQuickOption)
cmdRouter.POST("/quick/change", baseApi.UpdateQuickJump)
cmdRouter.GET("/quick/directory", baseApi.ListQuickDirectories)
cmdRouter.POST("/quick/directory", baseApi.CreateQuickDirectory)
cmdRouter.POST("/quick/directory/update", baseApi.UpdateQuickDirectory)
cmdRouter.POST("/quick/directory/del", baseApi.DeleteQuickDirectory)
cmdRouter.GET("/app/launcher", baseApi.LoadAppLauncher)
cmdRouter.POST("/app/launcher/show", baseApi.UpdateAppLauncher)
cmdRouter.POST("/app/launcher/option", baseApi.LoadAppLauncherOption)
+1 -11
View File
@@ -314,17 +314,7 @@ func (s *Session) detach(a *attachment, clean, revalidate bool, cursor uint64) {
if !s.Persistent || revalidate {
timeout = revalidateGrace
}
// Stop cannot cancel a fired timer; a racing Attach clears s.grace.
var grace *time.Timer
grace = time.AfterFunc(timeout, func() {
s.mu.Lock()
expired := s.grace == grace
s.mu.Unlock()
if expired {
s.Close()
}
})
s.grace = grace
s.grace = time.AfterFunc(timeout, s.Close)
}
s.mu.Unlock()
if clean {
-1
View File
@@ -266,7 +266,6 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
Language: settingInfo.Language,
MenuTabs: settingInfo.MenuTabs,
MenuAccordion: settingInfo.MenuAccordion,
PanelCache: settingInfo.PanelCache,
PanelName: settingInfo.PanelName,
Theme: settingInfo.Theme,
NeedCaptcha: needCaptcha,
-13
View File
@@ -4,16 +4,6 @@ import (
"time"
)
const DefaultPanelCache = `{"tabs":true,"pagination":true,"refresh":true,"search":false,"duration":"24h"}`
type PanelCache struct {
Tabs *bool `json:"tabs"`
Pagination *bool `json:"pagination"`
Refresh *bool `json:"refresh"`
Search *bool `json:"search"`
Duration string `json:"duration"`
}
type SettingInfo struct {
SystemVersion string `json:"systemVersion"`
DeveloperMode string `json:"developerMode"`
@@ -28,7 +18,6 @@ type SettingInfo struct {
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
MenuAccordion string `json:"menuAccordion"`
PanelCache string `json:"panelCache"`
Language string `json:"language"`
DocSource string `json:"docSource"`
@@ -73,7 +62,6 @@ type SettingBaseInfo struct {
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
MenuAccordion string `json:"menuAccordion"`
PanelCache string `json:"panelCache"`
Language string `json:"language"`
HideMenu string `json:"hideMenu"`
DocSource string `json:"docSource"`
@@ -300,7 +288,6 @@ type LoginSetting struct {
Language string `json:"language"`
MenuTabs string `json:"menuTabs"`
MenuAccordion string `json:"menuAccordion"`
PanelCache string `json:"panelCache"`
PanelName string `json:"panelName"`
Theme string `json:"theme"`
NeedCaptcha bool `json:"needCaptcha"`
-27
View File
@@ -120,9 +120,6 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
for _, set := range setting {
settingMap[set.Key] = set.Value
}
if settingMap["PanelCache"] == "" {
settingMap["PanelCache"] = dto.DefaultPanelCache
}
repairAndSortHideMenu(settingMap)
var info dto.SettingInfo
arr, err := json.Marshal(settingMap)
@@ -158,9 +155,6 @@ func (u *SettingService) GetSettingBaseInfo() (*dto.SettingBaseInfo, error) {
for _, set := range setting {
settingMap[set.Key] = set.Value
}
if settingMap["PanelCache"] == "" {
settingMap["PanelCache"] = dto.DefaultPanelCache
}
repairAndSortHideMenu(settingMap)
var info dto.SettingBaseInfo
arr, err := json.Marshal(settingMap)
@@ -230,27 +224,6 @@ func (u *SettingService) Update(c *gin.Context, key, value string) error {
if auth.IsAPICredentialSetting(key) {
return buserr.New("ErrInvalidParams")
}
if key == "PanelCache" {
var config dto.PanelCache
decoder := json.NewDecoder(strings.NewReader(value))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&config); err != nil {
return buserr.New("ErrInvalidParams")
}
if err := decoder.Decode(new(any)); err != io.EOF {
return buserr.New("ErrInvalidParams")
}
if config.Tabs == nil || config.Pagination == nil || config.Refresh == nil || config.Search == nil {
return buserr.New("ErrInvalidParams")
}
switch config.Duration {
case "24h", "week", "month", "forever":
default:
return buserr.New("ErrInvalidParams")
}
data, _ := json.Marshal(config)
return settingRepo.UpdateOrCreate(key, string(data))
}
oldVal, err := settingRepo.Get(repo.WithByKey(key))
if err != nil {
return err
+12 -9
View File
@@ -3906,11 +3906,13 @@ const docTemplate = `{
"Alert"
],
"x-panel-log": {
"bodyKeys": [],
"paramKeys": [],
"formatZH": "删除告警配置 [name]",
"formatEN": "delete alert config [name]",
"BeforeFunctions": []
"BeforeFunctions": [],
"bodyKeys": [
"id"
],
"formatEN": "delete alert config [id]",
"formatZH": "删除告警配置 [id]",
"paramKeys": []
}
}
},
@@ -4039,13 +4041,14 @@ const docTemplate = `{
"Alert"
],
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"id",
"displayName"
],
"paramKeys": [],
"formatZH": "更新告警配置 [displayName]",
"formatEN": "update alert config [displayName]",
"BeforeFunctions": []
"formatEN": "update alert config [id][displayName]",
"formatZH": "更新告警配置 [id][displayName]",
"paramKeys": []
}
}
},
+12 -9
View File
@@ -3902,11 +3902,13 @@
"Alert"
],
"x-panel-log": {
"bodyKeys": [],
"paramKeys": [],
"formatZH": "删除告警配置 [name]",
"formatEN": "delete alert config [name]",
"BeforeFunctions": []
"BeforeFunctions": [],
"bodyKeys": [
"id"
],
"formatEN": "delete alert config [id]",
"formatZH": "删除告警配置 [id]",
"paramKeys": []
}
}
},
@@ -4035,13 +4037,14 @@
"Alert"
],
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"id",
"displayName"
],
"paramKeys": [],
"formatZH": "更新告警配置 [displayName]",
"formatEN": "update alert config [displayName]",
"BeforeFunctions": []
"formatEN": "update alert config [id][displayName]",
"formatZH": "更新告警配置 [id][displayName]",
"paramKeys": []
}
}
},
+14 -10
View File
@@ -108,29 +108,33 @@
"formatEN": "create alert [title]"
},
"/alert/config/del": {
"bodyKeys": [],
"bodyKeys": [
"id"
],
"paramKeys": [],
"formatZH": "删除告警配置 [name]",
"formatEN": "delete alert config [name]",
"beforeFunctions": []
"beforeFunctions": [],
"formatZH": "删除告警配置 [id]",
"formatEN": "delete alert config [id]"
},
"/alert/config/status": {
"bodyKeys": [
"id",
"status"
],
"paramKeys": [],
"formatZH": "更新告警配置状态 [name][status]",
"formatEN": "update alert config status [name][status]",
"beforeFunctions": []
"beforeFunctions": [],
"formatZH": "更新告警配置状态 [id][status]",
"formatEN": "update alert config status [id][status]"
},
"/alert/config/update": {
"bodyKeys": [
"id",
"displayName"
],
"paramKeys": [],
"formatZH": "更新告警配置 [displayName]",
"formatEN": "update alert config [displayName]",
"beforeFunctions": []
"beforeFunctions": [],
"formatZH": "更新告警配置 [id][displayName]",
"formatEN": "update alert config [id][displayName]"
},
"/alert/del": {
"bodyKeys": [
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/1Panel-dev/1Panel/core
go 1.27.3
go 1.26.6
require (
github.com/beevik/etree v1.7.0
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "An unrevoked API key with this name already exists"
ErrAPIKeyNotFound: "The API key does not exist or does not belong to the current user"
ErrAPIKeyAppBindingDisabled: "APP QR binding is not allowed for this API key"
ErrAPIKeyTerminalUpgradeRequired: "Unable to verify API key terminal authorization support. Check the node connection and upgrade its Agent before retrying."
LocalAppSyncDispatch: "Dispatch local app sync"
LocalAppSyncDispatchNode: "Dispatch local app sync to {{.node}} (node task: {{.taskID}})"
ErrLocalAppSyncDisabled: "Local app sync across nodes is disabled"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "Ya existe una clave API no revocada con este nombre"
ErrAPIKeyNotFound: "La clave API no existe o no pertenece al usuario actual"
ErrAPIKeyAppBindingDisabled: "Esta clave API no permite vincular la APP mediante código QR"
ErrAPIKeyTerminalUpgradeRequired: "No se pudo verificar la autorización de terminal con clave API. Compruebe la conexión del nodo y actualice su Agent antes de reintentar."
LocalAppSyncDispatch: "Enviar sincronización de apps locales"
LocalAppSyncDispatchNode: "Enviar sincronización de apps locales a {{.node}} (tarea del nodo: {{.taskID}})"
ErrLocalAppSyncDisabled: "La sincronización de apps locales entre nodos está desactivada"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "یک کلید API لغونشده با این نام از
ErrAPIKeyNotFound: "کلید API وجود ندارد یا متعلق به کاربر فعلی نیست"
ErrAPIKeyAppBindingDisabled: "اتصال برنامه با کد QR برای این کلید API مجاز نیست"
ErrAPIKeyTerminalUpgradeRequired: "امکان بررسی پشتیبانی از مجوزدهی ترمینال با کلید API وجود ندارد. اتصال گره را بررسی و Agent آن را ارتقا دهید، سپس دوباره تلاش کنید."
LocalAppSyncDispatch: "ارسال درخواست همگام‌سازی برنامه‌های محلی"
LocalAppSyncDispatchNode: "ارسال درخواست همگام‌سازی به {{.node}} (وظیفه گره: {{.taskID}})"
ErrLocalAppSyncDisabled: "همگام‌سازی برنامه‌های محلی بین گره‌ها غیرفعال است"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "同じ名前の未失効 API キーが既に存在しま
ErrAPIKeyNotFound: "API キーが存在しないか、現在のユーザーに属していません"
ErrAPIKeyAppBindingDisabled: "この API キーでは APP の QR コード連携が許可されていません"
ErrAPIKeyTerminalUpgradeRequired: "API キーによるターミナル認証への対応を確認できません。ノードの接続を確認し、Agent を更新して再試行してください。"
LocalAppSyncDispatch: "ローカルアプリ同期要求の送信"
LocalAppSyncDispatchNode: "{{.node}} にローカルアプリ同期要求を送信(ノードタスク: {{.taskID}})"
ErrLocalAppSyncDisabled: "ノード間のローカルアプリ同期は無効です"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "같은 이름의 폐기되지 않은 API Key가 이미 있
ErrAPIKeyNotFound: "API Key가 없거나 현재 사용자 소유가 아닙니다"
ErrAPIKeyAppBindingDisabled: "이 API Key는 APP QR 코드 연결을 허용하지 않습니다"
ErrAPIKeyTerminalUpgradeRequired: "API Key 터미널 인증 지원 여부를 확인할 수 없습니다. 노드 연결을 확인하고 Agent를 업그레이드한 후 다시 시도하세요."
LocalAppSyncDispatch: "로컬 앱 동기화 요청 전송"
LocalAppSyncDispatchNode: "{{.node}}에 로컬 앱 동기화 요청 전송 (노드 작업: {{.taskID}})"
ErrLocalAppSyncDisabled: "노드 간 로컬 앱 동기화가 비활성화되어 있습니다"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "ມີ API Key ທີ່ຍັງບໍ່ຖືກຖອ
ErrAPIKeyNotFound: "ບໍ່ພົບ API Key ຫຼືບໍ່ແມ່ນຂອງຜູ້ໃຊ້ປັດຈຸບັນ"
ErrAPIKeyAppBindingDisabled: "API Key ນີ້ບໍ່ອະນຸຍາດໃຫ້ຜູກ APP ຜ່ານລະຫັດ QR"
ErrAPIKeyTerminalUpgradeRequired: "ບໍ່ສາມາດຢືນຢັນການຮອງຮັບການອະນຸຍາດເທີມິນອນດ້ວຍ API Key. ກວດສອບການເຊື່ອມຕໍ່ໂນດ ແລະອັບເກຣດ Agent ກ່ອນລອງອີກ."
LocalAppSyncDispatch: "ສົ່ງຄຳຂໍຊິງຄ໌ແອັບໃນເຄື່ອງ"
LocalAppSyncDispatchNode: "ສົ່ງຄຳຂໍຊິງຄ໌ໄປຫາ {{.node}} (ວຽກຂອງໂນດ: {{.taskID}})"
ErrLocalAppSyncDisabled: "ການຊິງຄ໌ແອັບລະຫວ່າງໂນດຍັງບໍ່ໄດ້ເປີດ"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "Kunci API yang belum dibatalkan dengan nama ini sudah wuju
ErrAPIKeyNotFound: "Kunci API tidak wujud atau bukan milik pengguna semasa"
ErrAPIKeyAppBindingDisabled: "Pemautan APP melalui kod QR tidak dibenarkan untuk kunci API ini"
ErrAPIKeyTerminalUpgradeRequired: "Sokongan pengesahan terminal melalui kunci API tidak dapat disahkan. Semak sambungan nod dan naik taraf Agent sebelum mencuba lagi."
LocalAppSyncDispatch: "Hantar penyegerakan aplikasi tempatan"
LocalAppSyncDispatchNode: "Hantar penyegerakan aplikasi tempatan ke {{.node}} (tugas nod: {{.taskID}})"
ErrLocalAppSyncDisabled: "Penyegerakan aplikasi tempatan antara nod dinyahdayakan"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "Já existe uma chave API não revogada com esse nome"
ErrAPIKeyNotFound: "A chave API não existe ou não pertence ao usuário atual"
ErrAPIKeyAppBindingDisabled: "Esta chave API não permite vincular o APP por código QR"
ErrAPIKeyTerminalUpgradeRequired: "Não foi possível verificar o suporte à autorização de terminal por chave API. Verifique a conexão do nó e atualize o Agent antes de tentar novamente."
LocalAppSyncDispatch: "Enviar sincronização de apps locais"
LocalAppSyncDispatchNode: "Enviar sincronização de apps locais para {{.node}} (tarefa do nó: {{.taskID}})"
ErrLocalAppSyncDisabled: "A sincronização de apps locais entre nós está desativada"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "Неотозванный ключ API с таким име
ErrAPIKeyNotFound: "Ключ API не существует или не принадлежит текущему пользователю"
ErrAPIKeyAppBindingDisabled: "Для этого ключа API привязка приложения через QR-код запрещена"
ErrAPIKeyTerminalUpgradeRequired: "Не удалось проверить поддержку авторизации терминала по ключу API. Проверьте подключение узла, обновите его Agent и повторите попытку."
LocalAppSyncDispatch: "Отправка запросов синхронизации локальных приложений"
LocalAppSyncDispatchNode: "Отправка запроса синхронизации узлу {{.node}} (задача узла: {{.taskID}})"
ErrLocalAppSyncDisabled: "Синхронизация локальных приложений между узлами отключена"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "Bu adda iptal edilmemiş bir API anahtarı zaten var"
ErrAPIKeyNotFound: "API anahtarı mevcut değil veya geçerli kullanıcıya ait değil"
ErrAPIKeyAppBindingDisabled: "Bu API anahtarı için QR koduyla APP bağlantısına izin verilmiyor"
ErrAPIKeyTerminalUpgradeRequired: "API anahtarıyla terminal yetkilendirme desteği doğrulanamadı. Düğüm bağlantısını kontrol edip Agent sürümünü yükselttikten sonra tekrar deneyin."
LocalAppSyncDispatch: "Yerel uygulama eşitleme isteği gönder"
LocalAppSyncDispatchNode: "{{.node}} düğümüne yerel uygulama eşitleme isteği gönder (düğüm görevi: {{.taskID}})"
ErrLocalAppSyncDisabled: "Düğümler arası yerel uygulama eşitleme devre dışı"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "已存在同名且未撤銷的 API Key"
ErrAPIKeyNotFound: "API Key 不存在或不屬於目前使用者"
ErrAPIKeyAppBindingDisabled: "此 API Key 未允許 APP 掃碼綁定"
ErrAPIKeyTerminalUpgradeRequired: "無法確認節點支援 API Key 終端授權,請檢查節點連線並升級 Agent 後重試。"
LocalAppSyncDispatch: "下發本機應用同步"
LocalAppSyncDispatchNode: "向節點 {{.node}} 下發本機應用同步(節點任務:{{.taskID}})"
ErrLocalAppSyncDisabled: "尚未啟用本機應用跨節點同步"
-3
View File
@@ -499,6 +499,3 @@ ErrAPIKeyNameExists: "已存在同名的未撤销 API Key"
ErrAPIKeyNotFound: "API Key 不存在或不属于当前用户"
ErrAPIKeyAppBindingDisabled: "此 API Key 未允许 APP 扫码绑定"
ErrAPIKeyTerminalUpgradeRequired: "无法确认节点支持 API Key 终端授权,请检查节点连接并升级 Agent 后重试。"
LocalAppSyncDispatch: "下发本地应用同步"
LocalAppSyncDispatchNode: "向节点 {{.node}} 下发本地应用同步(节点任务:{{.taskID}})"
ErrLocalAppSyncDisabled: "尚未开启本地应用跨节点同步"
-1
View File
@@ -26,7 +26,6 @@ func Init() {
}
var baseSettingKeys = map[string]struct{}{
"PanelCache": {},
"PanelName": {},
"Theme": {},
"MenuTabs": {},
+4 -2
View File
@@ -133,9 +133,11 @@ func OperationLog() gin.HandlerFunc {
record.AuthMethod = "session"
}
// Handlers may supply audit values even without database resolution metadata.
mergeResolvedData(writer.resolvedHeader, formatMap)
if len(operationDic.BeforeFunctions) != 0 {
if needAgentResolve {
mergeResolvedData(writer.resolvedHeader, formatMap)
}
if allowCoreFallback && !hasAllResolvedData(formatMap, operationDic.BeforeFunctions) {
dbItem, err := newDB(record.Path)
if err == nil {
-73
View File
@@ -1,73 +0,0 @@
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { build, type Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
export function benchmarkReportPlugin(root: string): Plugin | undefined {
const entry = resolve(root, 'src/enterprise/views/ai-benchmark/report/main.ts');
if (!existsSync(entry)) return;
const id = 'virtual:benchmark-report';
const resolvedID = `\0${id}`;
const dependencies = new Set<string>();
return {
name: 'benchmark-report',
resolveId(source) {
if (source === id) return resolvedID;
},
async load(source) {
if (source !== resolvedID) return;
const result = await build({
root,
configFile: false,
publicDir: false,
logLevel: 'warn',
resolve: { preserveSymlinks: true },
plugins: [
vue(),
{
name: 'benchmark-report-dependencies',
buildEnd() {
for (const file of this.getModuleIds()) {
if (!file.includes('node_modules') && !file.startsWith('\0')) {
dependencies.add(file.split('?')[0]);
}
}
},
},
],
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
},
build: {
write: false,
minify: 'oxc',
cssCodeSplit: false,
lib: { entry, name: 'BenchmarkReport', formats: ['iife'] },
},
});
const outputs = (Array.isArray(result) ? result : [result]).flatMap((bundle) =>
'output' in bundle ? bundle.output : [],
);
const script = outputs
.filter((output) => output.type === 'chunk')
.map((output) => output.code)
.join('\n');
const styles = outputs
.filter((output) => output.type === 'asset' && output.fileName.endsWith('.css'))
.map((output) => (output.type === 'asset' ? String(output.source) : ''))
.join('\n');
if (!script || !styles) throw new Error('Benchmark report bundle is incomplete');
for (const file of dependencies) this.addWatchFile(file);
return `export const script = ${JSON.stringify(script)}; export const styles = ${JSON.stringify(styles)};`;
},
handleHotUpdate(context) {
if (!dependencies.has(context.file)) return;
const module = context.server.moduleGraph.getModuleById(resolvedID);
if (module) context.server.moduleGraph.invalidateModule(module);
context.server.ws.send({ type: 'full-reload' });
},
};
}
+1 -3
View File
@@ -1,7 +1,7 @@
import { ReqPage } from '.';
export namespace AI {
export type AgentType = 'openclaw' | 'copaw' | 'hermes-agent' | 'deepseek-harness' | 'octop';
export type AgentType = 'openclaw' | 'copaw' | 'hermes-agent';
export interface OllamaModelInfo {
id: number;
@@ -423,7 +423,6 @@ export namespace AI {
}
export interface AgentCreateReq {
accessHost?: string;
name: string;
remark: string;
appVersion: string;
@@ -451,7 +450,6 @@ export namespace AI {
}
export interface AgentItem {
accessHost: string;
id: number;
name: string;
remark: string;
-3
View File
@@ -1,9 +1,6 @@
import { ReqPage, CommonModel } from '.';
export namespace App {
export interface LocalAppSyncConfig {
enabled: boolean;
}
export interface App extends CommonModel {
name: string;
icon?: string;
-1
View File
@@ -77,7 +77,6 @@ export namespace Login {
language: string;
menuTabs: string;
menuAccordion: string;
panelCache: string;
panelName: string;
theme: string;
isOffline: boolean;
-6
View File
@@ -8,12 +8,6 @@ export namespace Dashboard {
diskSize: number;
}
export interface QuickDirectory {
id: number;
legacy?: boolean;
path: string;
alias: string;
}
export interface QuickJump {
id: number;
name: string;
-2
View File
@@ -41,7 +41,6 @@ export namespace Setting {
theme: string;
menuTabs: string;
menuAccordion: string;
panelCache: string;
language: string;
docSource: string;
@@ -87,7 +86,6 @@ export namespace Setting {
theme: string;
menuTabs: string;
menuAccordion: string;
panelCache: string;
language: string;
hideMenu: string;
docSource: string;
-12
View File
@@ -144,15 +144,3 @@ export function getAppIconUrl(appKey: string, node?: string): string {
export const installAppToNodes = (param: App.InstallAppToNodes) => {
return http.post(`/core/xpack/sync/app/install`, param);
};
export const getLocalAppSyncConfig = () => {
return http.get<App.LocalAppSyncConfig>('/core/xpack/settings/app/local-sync');
};
export const updateLocalAppSyncConfig = (config: App.LocalAppSyncConfig) => {
return http.post('/core/xpack/settings/app/local-sync', config);
};
export const dispatchLocalAppSync = (req: App.AppStoreSync) => {
return http.post('/core/xpack/settings/app/local-sync/dispatch', req);
};
+2 -4
View File
@@ -1,5 +1,4 @@
import http from '@/api';
import type { AxiosRequestConfig } from 'axios';
import { ResPage, SearchWithPage } from '../interface';
import { Container } from '../interface/container';
import { TimeoutEnum } from '@/enums/http-enum';
@@ -24,11 +23,10 @@ export const loadContainerUsers = (name: string) => {
export const listContainerFiles = (params: Container.ContainerFileReq) => {
return http.post<Array<Container.ContainerFileInfo>>(`/containers/files/search`, params, TimeoutEnum.T_40S);
};
export const uploadContainerFile = (params: FormData, config?: AxiosRequestConfig & { skipErrorMessage?: boolean }) => {
export const uploadContainerFile = (params: FormData) => {
return http.upload(`/containers/files/upload`, params, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: TimeoutEnum.T_5M,
...config,
headers: { 'Content-Type': 'multipart/form-data', ...config?.headers },
});
};
export const getContainerFileContent = (params: Container.ContainerFileReq) => {
+1 -9
View File
@@ -8,7 +8,7 @@ export const loadOsInfo = () => {
export const loadQuickOption = () => {
return http.get<Array<Dashboard.QuickJump>>(`/dashboard/quick/option`);
};
export const changeQuick = (quicks: Array<Pick<Dashboard.QuickJump, 'id' | 'isShow'>>) => {
export const changeQuick = (quicks: Array<Dashboard.QuickJump>) => {
return http.post(`/dashboard/quick/change`, { quicks: quicks });
};
export const loadAppLauncher = () => {
@@ -48,11 +48,3 @@ export const loadTopMem = () => {
export const systemRestart = (operation: string) => {
return http.post(`/dashboard/system/restart/${operation}`);
};
export const loadQuickDirectories = () => http.get<Dashboard.QuickDirectory[]>('/dashboard/quick/directory');
export const createQuickDirectory = (data: Omit<Dashboard.QuickDirectory, 'id'>) =>
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory', data);
export const updateQuickDirectory = (data: Dashboard.QuickDirectory) =>
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory/update', data);
export const deleteQuickDirectory = (id: number, legacy = false) =>
http.post('/dashboard/quick/directory/del', { id, legacy });
+6 -21
View File
@@ -10,13 +10,8 @@ export type FileUploadRequestConfig = AxiosRequestConfig & {
skipErrorMessage?: boolean;
};
export const getFilesList = (params: File.ReqFile, currentNode?: string) => {
return http.post<File.File>(
'files/search',
params,
TimeoutEnum.T_5M,
currentNode ? { CurrentNode: currentNode } : undefined,
);
export const getFilesList = (params: File.ReqFile) => {
return http.post<File.File>('files/search', params, TimeoutEnum.T_5M);
};
export const fileAiSearch = (params: File.FileAISearchReq) => {
@@ -222,13 +217,8 @@ export const checkFileShare = (params: File.FileShareCheck, headers?: AxiosReque
return http.get('files/share/check', params, { headers });
};
export const computeDirSize = (params: File.DirSizeReq, currentNode?: string) => {
return http.post<File.DirSizeRes>(
'files/size',
params,
TimeoutEnum.T_5M,
currentNode ? { CurrentNode: currentNode } : undefined,
);
export const computeDirSize = (params: File.DirSizeReq) => {
return http.post<File.DirSizeRes>('files/size', params, TimeoutEnum.T_5M);
};
export const computeDepthDirSize = (params: File.DirSizeReq) => {
@@ -251,13 +241,8 @@ export const clearRecycle = () => {
return http.post<any>('files/recycle/clear');
};
export const searchFavorite = (params: ReqPage, currentNode?: string) => {
return http.post<ResPage<File.Favorite>>(
'files/favorite/search',
params,
undefined,
currentNode ? { CurrentNode: currentNode } : undefined,
);
export const searchFavorite = (params: ReqPage) => {
return http.post<ResPage<File.Favorite>>('files/favorite/search', params);
};
export const addFavorite = (path: string) => {
+2 -2
View File
@@ -21,10 +21,10 @@ export const loadForwardBaseInfo = () =>
export const searchForwardRule = (request: Firewall.ForwardRuleSearch) =>
http.post<ResPage<Firewall.RuleInfo>>('/hosts/firewall/forward/search', request, TimeoutEnum.T_40S);
export const operateFire = (operation: string, withDockerRestart: boolean, backupFile?: string) =>
export const operateFire = (operation: string, withDockerRestart: boolean) =>
http.post<Firewall.FilterChainOperationResult>(
'/hosts/firewall/operate',
{ operation, withDockerRestart, backupFile },
{ operation, withDockerRestart },
TimeoutEnum.T_10M,
);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

@@ -8,7 +8,10 @@
<el-table-column :label="$t('commons.table.name')" prop="name">
<template #default="{ row }">
<div v-if="!row.edit">
<span>{{ row.name }}</span>
<span v-if="row.name === 'Default'">
{{ $t('commons.table.default') }}
</span>
<span v-if="row.name !== 'Default'">{{ row.name }}</span>
<el-tag v-if="row.isDefault" type="success" class="ml-2" size="small">
({{ $t('commons.table.default') }})
</el-tag>
+16 -45
View File
@@ -79,16 +79,6 @@
@row-click="handleRowClick"
class="cursor-pointer dialog-file-list"
>
<el-table-column v-if="isMultiple && form.dir" width="48">
<template #default="{ row }">
<el-checkbox
v-if="row.isDir && !row.isCreate"
:model-value="selectedPathSet.has(row.path)"
@click.stop
@change="toggleDirectory(row.path)"
/>
</template>
</el-table-column>
<el-table-column prop="name" show-overflow-tooltip fix>
<template #default="{ row }">
<svg-icon
@@ -141,13 +131,6 @@
</el-table>
</div>
<div class="file-list-bottom">
<el-checkbox
v-if="isMultiple && form.dir"
:model-value="selectedPathSet.has(req.path)"
@change="toggleDirectory(req.path)"
>
{{ req.path }}
</el-checkbox>
<div v-if="!isMultiple && selectRow?.path">
{{ $t('file.currentSelect') }}
<el-tooltip :content="selectRow.path" placement="top">
@@ -180,11 +163,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button
type="primary"
@click="selectFile"
:disabled="disBtn || (isMultiple && selectRows.length === 0)"
>
<el-button type="primary" @click="selectFile" :disabled="disBtn">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -195,20 +174,18 @@
<script lang="ts" setup>
import { File } from '@/api/interface/file';
import { computeDirSize, createFile, getFileContent, getFilesList, searchFavorite } from '@/api/modules/files';
import { computed, nextTick, reactive, ref } from 'vue';
import { nextTick, onUpdated, reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess, MsgWarning } from '@/utils/message';
import { useSearchableForSelect } from '@/views/host/file-management/hooks/searchable';
import { computeSize } from '@/utils/size';
import { debounce } from '@/utils/misc';
const props = defineProps<{ currentNode?: string }>();
const data = ref([]);
const loading = ref(false);
const paths = ref<string[]>([]);
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300, showHidden: true });
const selectRow = ref({ path: '', name: '' });
const selectRows = ref<Array<{ path: string; name: string }>>([]);
const selectedPathSet = computed(() => new Set(selectRows.value.map((item) => item.path)));
const isMultiple = ref(false);
const rowRefs = ref();
const open = ref(false);
@@ -219,7 +196,7 @@ const favorites = ref<File.Favorite[]>([]);
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchableForSelect(paths);
const oldUrl = ref<string>('');
const em = defineEmits(['choose', 'close']);
const em = defineEmits(['choose']);
const form = reactive({
path: '/',
@@ -234,7 +211,6 @@ interface DialogProps {
isAll?: boolean;
disabled?: boolean;
multiple?: boolean;
selectedPaths?: string[];
}
const acceptParams = (props: DialogProps): void => {
form.path = props.path || '/';
@@ -242,8 +218,7 @@ const acceptParams = (props: DialogProps): void => {
form.isAll = props.isAll || false;
form.disabled = props.disabled || false;
isMultiple.value = props.multiple || false;
disBtn.value = false;
selectRows.value = [...new Set(props.selectedPaths || [])].map((path) => ({ path, name: path }));
selectRows.value = [];
selectRow.value = { path: '', name: '' };
openPage();
req.path = form.path;
@@ -270,7 +245,6 @@ const handleClose = () => {
open.value = false;
selectRow.value = { path: '', name: '' };
selectRows.value = [];
em('close');
};
const openPage = () => {
@@ -307,7 +281,7 @@ const openDir = async (row: File.File, column: any, event: any) => {
}
// Handle file click
const fullPath = (req.path === '/' ? req.path : req.path + '/') + row.name;
if (isMultiple.value && (form.isAll || !form.dir)) {
if (isMultiple.value) {
// Multiple mode: toggle selection
const index = selectRows.value.findIndex((r) => r.path === fullPath);
if (index > -1) {
@@ -368,7 +342,7 @@ const getFileSize = async (path: string) => {
};
updateByPath(path, { btnLoading: true });
try {
const res = await getFileContent(params, props.currentNode);
const res = await getFileContent(params);
updateByPath(path, { dirSize: res.data.size });
} finally {
updateByPath(path, { btnLoading: false });
@@ -381,7 +355,7 @@ const getDirSize = async (path: string) => {
};
updateByPath(path, { btnLoading: true });
try {
const res = await computeDirSize(req, props.currentNode);
const res = await computeDirSize(req);
updateByPath(path, { dirSize: res.data.size });
} finally {
updateByPath(path, { btnLoading: false });
@@ -411,7 +385,7 @@ const getPaths = (reqPath: string) => {
const search = async (req: File.ReqFile) => {
req.dir = form.dir;
loading.value = true;
await getFilesList(req, props.currentNode)
await getFilesList(req)
.then((res) => {
if (!res.data.path) {
req.path = oldUrl.value;
@@ -498,26 +472,23 @@ const createFolder = async (row: any) => {
let addItem = {};
Object.assign(addItem, addForm);
loading.value = true;
createFile(addItem as File.FileCreate, props.currentNode)
.then(async () => {
createFile(addItem as File.FileCreate)
.then(() => {
row.isCreate = false;
disBtn.value = false;
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
await search(req);
})
.finally(() => {
loading.value = false;
});
};
const toggleDirectory = (path: string) => {
const index = selectRows.value.findIndex((item) => item.path === path);
if (index >= 0) {
selectRows.value.splice(index, 1);
} else {
selectRows.value.push({ path, name: path });
onUpdated(() => {
if (form.path != '') {
req.path = form.path;
}
};
search(req);
});
const removeSelected = (index: number) => {
selectRows.value.splice(index, 1);
@@ -525,7 +496,7 @@ const removeSelected = (index: number) => {
const loadFavorites = async () => {
try {
const res = await searchFavorite({ page: 1, pageSize: 100 }, props.currentNode);
const res = await searchFavorite({ page: 1, pageSize: 100 });
favorites.value = res.data.items || [];
} catch {}
};
+4 -1
View File
@@ -8,7 +8,10 @@
<el-table-column :label="$t('commons.table.name')" prop="name">
<template #default="{ row }">
<div v-if="!row.edit">
<span>{{ row.name }}</span>
<span v-if="row.name === 'Default'">
{{ $t('commons.table.default') }}
</span>
<span v-if="row.name !== 'Default'">{{ row.name }}</span>
<el-tag v-if="row.isDefault" type="success" class="ml-2" size="small">
({{ $t('commons.table.default') }})
</el-tag>
+45 -43
View File
@@ -20,55 +20,57 @@
</template>
<script setup lang="ts">
import { onActivated, onDeactivated, onMounted, onUnmounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { panelCacheKey, readPanelCache, writePanelCache } from '@/utils/panel-cache';
import { onMounted, onUnmounted, ref } from 'vue';
defineOptions({ name: 'TableSetting' });
const refreshRateUnit = ref(0);
const refreshRateUnit = ref<number>(0);
const emit = defineEmits(['search']);
const props = defineProps({ title: String, rate: Number });
const route = useRoute();
// Capture the owning page; useRoute continues to change while a page is deactivated.
const pagePath = route.path;
const cacheKey = () => panelCacheKey(`refresh:${pagePath}:${props.title || ''}`);
let timer: ReturnType<typeof setInterval> | undefined;
let active = false;
const stopTimer = () => {
clearInterval(timer);
timer = undefined;
};
const startTimer = () => {
stopTimer();
if (active && refreshRateUnit.value > 0) {
timer = setInterval(() => emit('search'), 1000 * refreshRateUnit.value);
const props = defineProps({
title: String,
rate: Number,
});
let timer: NodeJS.Timer | null = null;
const changeRefresh = (command: number) => {
refreshRateUnit.value = command || 0;
if (refreshRateUnit.value !== 0) {
if (timer) {
clearInterval(Number(timer));
timer = null;
}
timer = setInterval(() => {
emit('search');
}, 1000 * refreshRateUnit.value);
} else {
if (timer) {
clearInterval(Number(timer));
timer = null;
}
}
localStorage.setItem(props.title, refreshRateUnit.value + '');
};
const restore = () => {
const rate = readPanelCache<number>('refresh', cacheKey());
refreshRateUnit.value = Number.isFinite(rate) && rate >= 0 ? rate : props.rate || 0;
startTimer();
};
const changeRefresh = (rate: number) => {
refreshRateUnit.value = rate;
writePanelCache('refresh', cacheKey(), rate);
startTimer();
};
const activate = () => {
if (active) return;
active = true;
restore();
};
watch(cacheKey, () => {
if (active) restore();
onUnmounted(() => {
clearInterval(Number(timer));
timer = null;
if (props.title) {
localStorage.setItem(props.title, refreshRateUnit.value + '');
}
});
onMounted(activate);
onActivated(activate);
onDeactivated(() => {
active = false;
stopTimer();
onMounted(() => {
if (props.title && localStorage.getItem(props.title) != null) {
let rate = Number(localStorage.getItem(props.title));
refreshRateUnit.value = rate ? Number(rate) : 0;
changeRefresh(refreshRateUnit.value);
return;
}
if (props.rate) {
refreshRateUnit.value = props.rate;
changeRefresh(refreshRateUnit.value);
}
});
onUnmounted(stopTimer);
</script>
<style lang="scss" scoped>
@@ -167,7 +167,6 @@
</div>
</template>
<script setup lang="ts">
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';
@@ -342,33 +341,13 @@ const handleRightClick = (row, column, event) => {
openContextMenu(row, event);
};
watch(
() => props.data,
() => {
const pagination = props.paginationConfig;
if (!pagination || !pagination.pageSize) return;
const lastPage = Math.max(1, Math.ceil(pagination.total / pagination.pageSize));
if (pagination.currentPage > lastPage) {
pagination.currentPage = lastPage;
emit('search');
}
},
{ flush: 'post' },
);
function currentChange() {
emit('search');
}
function sizeChange() {
if (props.paginationConfig.cacheSizeKey) {
writePanelCache(
'pagination',
panelCacheKey(`size:${props.paginationConfig.cacheSizeKey}`),
props.paginationConfig.pageSize,
);
}
props.paginationConfig.currentPage = 1;
localStorage.setItem(props.paginationConfig.cacheSizeKey, props.paginationConfig.pageSize);
emit('search');
}
+1 -2
View File
@@ -52,7 +52,6 @@
</template>
<script setup lang="ts">
import { readCachedPageSize } from '@/utils/panel-cache';
import TaskLog from '@/components/log/task/index.vue';
import NodeSelect from '@/components/node-select/index.vue';
@@ -74,7 +73,7 @@ const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'task-list-page-size',
currentPage: 1,
pageSize: readCachedPageSize('task-list-page-size') || 20,
pageSize: Number(localStorage.getItem('task-list-page-size')) || 20,
total: 0,
small: true,
});
@@ -154,7 +154,7 @@ const connectionTree = computed<ConnectionTreeItem[]>(() => {
groups.push(
...hostTree.value.map((group): ConnectionTreeItem => ({
id: `host-group-${group.id}`,
label: group.label,
label: group.label === 'Default' ? i18n.global.t('commons.table.default') : group.label,
kind: 'group',
children: (group.children || []).map((host) => ({
id: `host-${host.id}`,
@@ -18,7 +18,6 @@
size="w-70"
:show-close="false"
:modal="false"
modal-penetrable
@opened="claim"
>
<template #header>
@@ -38,7 +38,14 @@
</el-form-item>
<el-form-item v-if="!form.isLocal" :label="$t('commons.table.group')" prop="groupID">
<el-select filterable v-model="form.groupID" clearable style="width: 100%">
<el-option v-for="item in groupList" :key="item.id" :label="item.name" :value="item.id" />
<div v-for="item in groupList" :key="item.id">
<el-option
v-if="item.name === 'Default'"
:label="$t('commons.table.default')"
:value="item.id"
/>
<el-option v-else :label="item.name" :value="item.id" />
</div>
</el-select>
</el-form-item>
<el-form-item v-if="!form.isLocal" :label="$t('commons.table.title')" prop="name">
@@ -7,7 +7,6 @@
:ref="(el: any) => store.setInstance(item.key, el)"
@session="(id: string) => store.setSessionId(item.key, id)"
@expired="store.onExpired(item.key)"
@reconnect="store.reconnect(item.key)"
/>
</Teleport>
</template>
+3 -14
View File
@@ -31,7 +31,7 @@ const { currentNode } = useGlobalStore();
// session: agent side session id known (fresh or reattached)
// expired: the agent no longer has the session; a reconnect must open a new one
const emit = defineEmits(['session', 'expired', 'reconnect']);
const emit = defineEmits(['session', 'expired']);
// Close codes of the agent's session protocol (agent/utils/terminal/session.go).
const CLOSE_SESSION_NOT_FOUND = 4404;
@@ -56,7 +56,6 @@ let closing = false;
let reconnecting = false;
let reconnectNoticeShown = false;
let revalidating = false;
let expired = false;
let reconnectStartedAt = 0;
let reconnectDelay = 1000;
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
@@ -280,7 +279,6 @@ function resizeTerminal() {
if (terminalElement.value.clientWidth <= 0 || terminalElement.value.clientHeight <= 0) {
return;
}
if (terminalElement.value.closest('.terminal-host')) return;
fitAddon.fit();
if (isWsOpen()) {
@@ -313,10 +311,9 @@ const initWebSocket = async (endpoint_: string, args: string = '') => {
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
const host = href.split('//')[1].split('/')[0];
const endpoint = endpoint_.replace(/^\/+/, '');
const params = new URLSearchParams(args);
const node = params.has('id') ? 'local' : currentNode.value;
let node = args.indexOf('id=') !== -1 ? 'local' : currentNode.value;
let conn = `${protocol}://${host}/${endpoint}?cols=${term.value.cols}&rows=${term.value.rows}&${args}&operateNode=${node}`;
if (params.has('operateNode')) {
if (args.indexOf('operateNode=') !== -1) {
conn = `${protocol}://${host}/${endpoint}?cols=${term.value.cols}&rows=${term.value.rows}&${args}`;
}
if (sessionId.value) {
@@ -487,7 +484,6 @@ const closeRealTerminal = (ev: CloseEvent) => {
'31',
ev.code === 1000 ? 'The connection has been disconnected.' : i18n.global.t('terminal.sessionExpired'),
);
expired = true;
emit('expired');
return;
case CLOSE_ATTACHED_ELSEWHERE:
@@ -606,13 +602,6 @@ function sendMsg(data: string, line: string = '') {
function onTermData(data: string) {
if (!data) return;
if (expired) {
if (isEnterInputData(data)) {
expired = false;
emit('reconnect');
}
return;
}
if (aiNotice.value.loading) return;
sendMsg(data, isEnterInputData(data) ? getCurrentTerminalLine() : '');
}
@@ -4,8 +4,11 @@
<script lang="ts" setup>
import { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue';
import echarts from '@/utils/echarts';
import { useGlobalStore } from '@/composables/useGlobalStore';
const { themeConfig } = useGlobalStore();
const isDarkTheme = ref(false);
const PieChartRef = ref<HTMLElement>();
let themeObserver: MutationObserver | undefined;
let mediaQuery: MediaQueryList;
let resizeObserver: ResizeObserver | undefined;
let resizeFrame: number | undefined;
@@ -44,17 +47,16 @@ function resizeChart() {
echarts.getInstanceByDom(PieChartRef.value)?.resize();
}
function getThemeColors() {
// Match the applied page theme, including while settings are still loading.
const isDarkTheme = document.documentElement.classList.contains('dark');
const style = getComputedStyle(document.documentElement);
return {
primaryLight2: style.getPropertyValue('--panel-color-primary-light-3').trim() || '#4c8ef1',
primaryLight1: style.getPropertyValue('--panel-color-primary').trim() || '#005eeb',
pieBgColor: isDarkTheme ? '#434552' : '#ffffff',
textColor: isDarkTheme ? '#ffffff' : '#0f0f0f',
subtextColor: isDarkTheme ? '#BBBFC4' : '#646A73',
shadowColor: isDarkTheme ? '#16191D' : 'rgba(0, 94, 235, 0.1)',
backgroundStyleColor: isDarkTheme ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 94, 235, 0.05)',
primaryLight2: getComputedStyle(document.documentElement)
.getPropertyValue('--panel-color-primary-light-3')
.trim(),
primaryLight1: getComputedStyle(document.documentElement).getPropertyValue('--panel-color-primary').trim(),
pieBgColor: isDarkTheme.value ? '#434552' : '#ffffff',
textColor: isDarkTheme.value ? '#ffffff' : '#0f0f0f',
subtextColor: isDarkTheme.value ? '#BBBFC4' : '#646A73',
shadowColor: isDarkTheme.value ? '#16191D' : 'rgba(0, 94, 235, 0.1)',
backgroundStyleColor: isDarkTheme.value ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 94, 235, 0.05)',
};
}
@@ -62,6 +64,11 @@ function initChart() {
if (!PieChartRef.value) {
return;
}
if (themeConfig.value.theme === 'auto') {
isDarkTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
} else {
isDarkTheme.value = themeConfig.value.theme === 'dark';
}
let myChart = echarts?.getInstanceByDom(PieChartRef.value);
if (myChart === null || myChart === undefined) {
myChart = echarts.init(PieChartRef.value);
@@ -187,11 +194,8 @@ function handleThemeChange() {
onMounted(() => {
nextTick(() => {
themeObserver = new MutationObserver(handleThemeChange);
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'style'],
});
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', handleThemeChange);
initChart();
window.addEventListener('resize', changeChartSize);
if (PieChartRef.value) {
@@ -210,7 +214,7 @@ onBeforeUnmount(() => {
echarts.getInstanceByDom(PieChartRef.value)?.dispose();
}
window.removeEventListener('resize', changeChartSize);
themeObserver?.disconnect();
mediaQuery?.removeEventListener('change', handleThemeChange);
resizeObserver?.disconnect();
});
</script>
@@ -0,0 +1,24 @@
import { storeToRefs } from 'pinia';
import { onActivated, onDeactivated } from 'vue';
import GlobalStore from '@/store/modules/global';
import { bindSearchReset, type SearchFields } from '@/utils/search-reset';
interface SearchResetOptions {
scope?: () => string | number | undefined;
onRevisit: () => unknown;
}
export const useSearchReset = (fields: SearchFields, options: SearchResetOptions) => {
const { currentNode } = storeToRefs(GlobalStore());
const reset = bindSearchReset(options.scope ? [currentNode, options.scope] : [currentNode], fields);
let deactivated = false;
onDeactivated(() => {
deactivated = true;
});
onActivated(() => {
if (!deactivated) return;
deactivated = false;
reset();
return options.onRevisit();
});
};
-5
View File
@@ -5,8 +5,3 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module 'virtual:benchmark-report' {
export const script: string;
export const styles: string;
}
+10 -193
View File
@@ -494,7 +494,6 @@ const message = {
recommend: 'Recommended',
dir: 'Directory',
alias: 'Alias',
quickDirDelete: 'Delete ({0})?',
quickDir: 'Quick Access',
minQuickJump: 'Add at least one quick access entry',
maxQuickJump: 'You can add up to four quick access entries',
@@ -774,15 +773,6 @@ const message = {
},
aiTools: {
agents: {
deepSeekAccessHostHelper:
"Enter the IPv4 address or hostname used in your browser, without a scheme, port or path. HTTPS uses the application's local certificate.",
deepSeekAuthHelper:
'Username: letters, numbers, dot, underscore or hyphen. Password: at least 12 characters, without leading/trailing spaces or line breaks.',
octopAuthHelper:
'Password: at least 8 characters with letters and digits; avoid common passwords. If rejected during initialization, check the application logs for setup instructions.',
initialCredentials: "Initial credentials; use the application's current password if changed.",
nativeAgentAccessUnavailable:
'The existing installation has no compatible HTTPS access address or port. Check its application settings.',
agent: 'Agent',
account: 'Model Account',
batchOperation: 'Batch Operation',
@@ -1493,13 +1483,16 @@ const message = {
statusWaiting: 'Waiting',
base: 'Basic Information',
backend: 'Backend',
baseUrl: 'Base URL',
endpoint: 'Endpoint',
tokenizer: 'Tokenizer',
tokenizerPlaceholder: 'Enter an absolute directory on the main node or a Hugging Face repository ID',
tokenizerPlaceholder:
'Select or enter a local tokenizer directory, e.g. /opt/1panel/tokenizers/DeepSeek-V3',
runConfig: 'Run Config',
rawResult: 'Raw Result',
resultMetrics: 'Result Metrics',
contextTokens: 'Context Length',
contextTokensHelper: 'Input token limit plus output token limit for this benchmark.',
outputThroughput: 'Output Throughput',
outputThroughputHelper: 'Generated output tokens per second. Higher means faster generation.',
totalThroughput: 'Total Throughput',
@@ -1507,30 +1500,23 @@ const message = {
firstTokenLatency: 'First Token Latency',
firstTokenLatencyHelper:
'Time from sending the request to receiving the first token. Lower means faster response.',
tokenValueHelper: 'Enter a positive integer or k value, e.g. 512, 1k, 32k',
numPrompts: 'Prompts',
concurrency: 'Concurrency',
requestRate: 'Request Rate',
requestRateUnlimited: 'Unlimited (max throughput)',
requestRateCustom: 'Custom QPS',
requestRateCustomPlaceholder: 'Requests per second, e.g. 2.5',
successfulRequests: 'Successful Requests',
requestThroughput: 'Request Throughput',
mean: 'Mean',
median: 'Median',
timeout: 'Timeout (seconds)',
image: 'vLLM Image',
ignoreEos: 'Ignore end-of-sequence token (EOS)',
ignoreEosHelp:
'Prevents early stopping at EOS to help test the configured output length. Other stop conditions still apply; server support is required.',
ignoreEos: 'Ignore EOS',
extraHeaders: 'Extra Headers',
},
vllmMonitor: {
concurrency: 'Current concurrency',
sampleMoment: 'At sampling time',
sampleWindow: 'Sample interval',
requestUnit: 'requests',
processing: 'Processing',
queued: 'Queued',
requestTiming: 'Request timing',
completedRequests: 'Requests completed in interval',
tokensPerSecond: 'tokens/s',
requestsPerSecond: 'Requests/s',
finishReasonLabels: {
@@ -1733,155 +1719,6 @@ const message = {
modelType: 'Model type',
},
},
benchmarkPlan: {
prepareTimeoutHelp: 'Preparation time limit, excluded from the overall timeout.',
pointTimeoutHelp:
'Separate timer per point, including startup, warmups and all requests; capped by the remaining overall time.',
totalTimeoutHelp: 'Starts after preparation, covers all points and intervals, and stops the test on timeout.',
concurrencyLevels: 'Concurrency levels',
concurrencyHelp:
'Levels run sequentially. Each value limits the number of requests in progress at the same time.',
requestCountMode: 'Request count',
fixedRequestMode: 'Fixed requests per level',
scaledRequestMode: 'Calculate from concurrency',
requestsPerPoint: 'Requests per level',
requestCountFormula:
'Per level: fixed value, or max(concurrency × multiplier, minimum requests). Scenario total: sum across levels × trials, excluding warmups.',
controlLog: 'Preparation and control log',
executionLog: 'Execution log',
pointLog: 'Point #{0} · {1} · Execution log',
archivedLog: '{0} (archive {1})',
tokenLengths: 'Input / output tokens',
kind: 'Result category',
kindServe: 'serve (regular tests)',
kindPD: 'pd (Prefill/Decode probes)',
saveDetailed: 'Save request details',
saveDetailedHelp:
'Save per-request lengths, latencies, generated text and errors. Summary metrics remain available when disabled, reducing result file size.',
parameters: 'Parameters',
temperatureHelp:
'Controls output randomness. Zero usually gives more stable output; higher values increase randomness.',
pointTimeout: 'Per-point timeout (seconds)',
temperature: 'Sampling temperature',
requestUrl: 'API URL',
invalidRequestUrl:
'Enter a full HTTP(S) URL ending in /chat/completions or /completions, without credentials, query parameters, or fragments.',
requestRateHelp:
'Applies only to this scenario. Unlimited rate still respects concurrency limits; fractional QPS is supported.',
tokenizerModeHelp:
'Available modes depend on the vLLM version in the benchmark image. Tokenizer loading is checked during task preparation.',
invalidBaseUrl: 'Enter a valid HTTP(S) URL without credentials, query parameters or fragments.',
invalidEndpoint:
'Enter an endpoint path matching the selected protocol and ending in {0}, without query parameters or fragments.',
invalidTokenizer:
'Use an absolute directory or repository ID; relative paths, commas and line breaks are not supported.',
invalidRequestRate: 'Select unlimited or enter a finite number greater than 0; decimals are supported.',
invalidCaCert: 'Enter an absolute CA certificate file path on the primary node, without commas or line breaks.',
bodyOverride: 'Extra body cannot override {0}.',
invalidBody: 'Enter a valid JSON object.',
invalidConcurrencies: 'Provide 1–100 concurrency values, each an integer between 1 and 100000.',
emptyRequestCount: 'When calculating from concurrency, multiplier and minimum requests cannot both be 0.',
requestCountLimit: 'Calculated requests per point must not exceed 1000000.',
contextExceeded: 'Input tokens, output tokens and template reserve exceed the context limit.',
planLimit: 'Provide 1–100 scenarios with no more than 2000 expanded points.',
caCertHelp:
'Select a CA certificate file on the primary server to verify HTTPS services. Leave empty to use system certificates.',
extraBody: 'Extra request body',
entryKey: 'Parameter name',
entryValue: 'Parameter value',
tokenizerMode: 'Tokenizer mode',
requireIdle: 'Check service idle before starting',
requireIdleHelp:
'Checks /metrics once before warmup and load testing. Stops if the service is busy or its idle state cannot be verified. This does not prevent other traffic during the test.',
trustRemoteCode: 'Trust remote code',
trustRemoteCodeHelp:
'Allows custom tokenizer code from the model repository to run inside the benchmark container. Enable only when required and the source is trusted.',
tokenizerModes: { auto: 'Auto (recommended)', slow: 'Slow tokenizer', mistral: 'Mistral tokenizer' },
quickDescription: 'Verify connectivity and measurement with a single request.',
regularDescription: 'Short, medium and long workloads for routine performance testing.',
fullDescription:
'Multiple scenarios, concurrency levels and repeated runs, including long inputs and shared prefixes.',
customDescription: 'Start with one scenario, set its parameters and add more as needed.',
scenarios: 'Scenarios',
builtIn: 'Built-in',
builtInHelp: 'Built-in schemes cannot be edited or deleted. Adjust a copy when creating a test.',
modified: 'Modified',
imageExists: 'Available locally',
imageNeedsPull: 'Pull required',
remark: 'Remarks',
source: 'Local service',
mode: 'Test plan',
quick: 'Quick check',
regular: 'Regular',
full: 'Full',
custom: 'Custom',
addScenario: 'Add scenario',
advanced: 'Advanced settings',
points: 'Points',
warmups: 'Warmup requests',
fixedRequests: 'Fixed requests',
multiplier: 'Requests per concurrency',
minRequests: 'Minimum requests',
trials: 'Trials',
prefixRatio: 'Shared prefix ratio',
totalTimeout: 'Round timeout (s)',
prepareTimeout: 'Preparation timeout (s)',
interval: 'Point interval (s)',
context: 'Context limit (0: unknown)',
reserve: 'Template token reserve',
storage: 'Storage limit (MiB)',
stopOnError: 'Stop subsequent points on error',
caCert: 'CA certificate path',
validTrials: 'Valid / total trials',
retry: 'Retry selected points',
environment: 'Environment',
plan: 'Frozen configuration',
clientHelp:
'The client container runs on the main node and sends requests to an existing service. It does not start inference or require a GPU.',
retryHelp: 'Only selected abnormal points are retried, with the same parameters and new seeds.',
secretHelp: 'API keys and headers are excluded from copies. Enter them again if needed.',
resultHelp:
'Only successful runs without request anomalies enter the median and min/max summaries. P99 is the median of per-run P99 values. Cache and external traffic are uncontrolled.',
},
benchmarkOverview: {
noResults: 'No valid test results. Check the failure reason and logs.',
noGroupResults: 'No valid results for this combination. Select another scenario or concurrency.',
partial: 'Partial results: the test did not fully succeed. Metrics cover valid points only.',
highlights: 'Key metrics',
aggregate: 'Median across valid runs in this combination. Click a metric for details.',
trend: 'Concurrency trend',
std: 'Std. deviation',
min: 'Minimum',
max: 'Maximum',
tpot: 'Time per output token',
tpotHelp: 'Average generation time per output token after the first token.',
itl: 'Inter-token latency',
itlHelp: 'Time between consecutive tokens in a streaming response.',
e2el: 'End-to-end latency',
e2elHelp: 'Time from sending the request to receiving the complete response.',
requestHelp: 'Number of requests completed per second.',
usageHelp:
'Medians of valid runs in this combination, not request totals for the entire test. See the Points tab for abnormal runs.',
duration: 'Run duration',
throughput: 'Throughput',
latency: 'Latency',
usage: 'Requests and usage (valid runs)',
},
benchmarkTokenizer: {
title: 'Tokenizer management',
local: 'Register local directory',
upload: 'Upload tokenizer package',
uploadHelp:
'Upload a ZIP / tar.gz containing tokenizer files without model weights. Maximum: 128 MiB compressed, 256 MiB extracted.',
source: 'Import method',
deleteLocal: 'Remove the registration for tokenizer “{0}”? The original directory and files will be kept.',
deleteUpload: 'Delete tokenizer “{0}” and its uploaded files? Historical test reports will be kept.',
select: 'Select a tokenizer',
selectHelp:
'For offline testing, select a managed tokenizer. Custom input still accepts a local directory or repository name.',
},
container: {
createByCommand: 'Create by command',
importContainerBackupTip: 'Import container backup file, only .tar.gz is supported',
@@ -3125,20 +2962,6 @@ const message = {
remote: 'Remote SSH',
},
setting: {
panelCache: {
title: 'Cache settings',
tabs: 'Tab cache',
pagination: 'Pagination cache',
refresh: 'Refresh interval cache',
search: 'Search input cache',
duration: 'Cache duration',
'24h': '24h',
week: 'One week',
month: 'One month',
forever: 'Forever',
helper: 'Expires after the last state change. Expired values return to page defaults.',
},
panel: 'Panel',
user: 'Panel user',
userChange: 'Change panel user',
@@ -3861,7 +3684,6 @@ const message = {
link: 'Link',
showCurrentArch: 'Architecture',
syncLocalApp: 'Sync Local App',
localAppSyncToNodes: 'Include child nodes when syncing local apps',
memoryRequiredHelper: 'Current application memory requirement {0}',
gpuConfig: 'Enable GPU Support',
gpuConfigHelper:
@@ -4422,7 +4244,7 @@ const message = {
pushDir: 'Push the certificate to the local directory',
dir: 'Directory',
pushDirHelper:
'Select multiple directories or enter one directory per line. Each directory will receive "fullchain.pem" and "privkey.pem".',
'Certificate file "fullchain.pem" and key file "privkey.pem" will be generated in this directory.',
organizationDetail: 'Organization details',
fromWebsite: 'From website',
dnsMauanlHelper:
@@ -4477,14 +4299,9 @@ const message = {
familyIncomplete: '{0} chains are incomplete',
resetWithBackup: 'Back up rules and reset',
resetOnly: 'Reset only',
resetWithBackupHelper:
'Save the current module rules as JSON for restoration during initialization or startup.',
resetWithBackupHelper: 'Save the current module rules as JSON for restoration during initialization.',
resetOnlyHelper: 'Clear current rules without creating a new backup. Existing backups are kept.',
backupDirectoryHelper: 'Valid rule backups in {0} are listed below. The newest file is selected by default.',
startOnly: 'Start only',
startAndRestore: 'Start and restore from backup',
startWithBackupHelper:
'Start the firewall normally, or choose an existing JSON backup to start and restore rules.',
restoreAllRules: 'Initialize and restore rules',
importRuleBackup: 'Import from backup',
initializeOnly: 'Initialize only',
+9 -194
View File
@@ -493,7 +493,6 @@ const message = {
home: {
recommend: 'Recomendado',
dir: 'Directorio',
quickDirDelete: '¿Confirmar la eliminación de ({0})?',
quickDir: 'Acceso rápido',
minQuickJump: 'Agrega al menos una entrada de acceso rápido',
maxQuickJump: 'Puedes agregar hasta cuatro entradas de acceso rápido',
@@ -777,15 +776,6 @@ const message = {
},
aiTools: {
agents: {
deepSeekAccessHostHelper:
'Introduzca la IPv4 o el nombre de host usado en el navegador, sin protocolo, puerto ni ruta. HTTPS usa el certificado local de la aplicación.',
deepSeekAuthHelper:
'Usuario: letras, números, punto, guion bajo o guion. Contraseña: al menos 12 caracteres, sin espacios al inicio o al final ni saltos de línea.',
octopAuthHelper:
'Contraseña: al menos 8 caracteres con letras y números; evite contraseñas comunes. Si se rechaza al iniciar, consulte las instrucciones en los registros de la aplicación.',
initialCredentials: 'Credenciales iniciales; si las cambió en la aplicación, use la contraseña actual.',
nativeAgentAccessUnavailable:
'La instalación existente no tiene una dirección o puerto HTTPS compatible. Revise la configuración de la aplicación.',
agent: 'Agente',
account: 'Cuenta de modelo',
batchOperation: 'Operación por lotes',
@@ -1501,14 +1491,16 @@ const message = {
statusWaiting: 'En espera',
base: 'Información básica',
backend: 'Backend',
baseUrl: 'URL base',
endpoint: 'Endpoint',
tokenizer: 'Tokenizador',
tokenizerPlaceholder:
'Introduzca un directorio absoluto del nodo principal o un ID de repositorio de Hugging Face',
'Seleccione o introduzca un directorio local de tokenizador, p. ej. /opt/1panel/tokenizers/DeepSeek-V3',
runConfig: 'Configuración de ejecución',
rawResult: 'Resultado bruto',
resultMetrics: 'Métricas de resultado',
contextTokens: 'Longitud de contexto',
contextTokensHelper: 'Suma del límite de tokens de entrada y salida de este benchmark.',
outputThroughput: 'Rendimiento de salida',
outputThroughputHelper: 'Tokens de salida generados por segundo. Un valor mayor indica más velocidad.',
totalThroughput: 'Rendimiento total',
@@ -1517,30 +1509,23 @@ const message = {
firstTokenLatency: 'Latencia del primer token',
firstTokenLatencyHelper:
'Tiempo desde el envío de la solicitud hasta recibir el primer token. Un valor menor indica respuesta más rápida.',
tokenValueHelper: 'Introduzca un entero positivo o valor con k, p. ej. 512, 1k, 32k',
numPrompts: 'Prompts',
concurrency: 'Concurrencia',
mean: 'Media',
median: 'Mediana',
requestRate: 'Tasa de solicitudes',
requestRateUnlimited: 'Ilimitado (máximo rendimiento)',
requestRateCustom: 'QPS personalizado',
requestRateCustomPlaceholder: 'Solicitudes por segundo, p. ej. 2.5',
successfulRequests: 'Solicitudes correctas',
requestThroughput: 'Rendimiento de solicitudes',
timeout: 'Tiempo de espera (segundos)',
image: 'Imagen vLLM',
ignoreEos: 'Ignorar el token de fin de secuencia (EOS)',
ignoreEosHelp:
'Evita detenerse antes por EOS para probar la longitud de salida configurada. Las demás condiciones de parada siguen vigentes; requiere soporte del servidor.',
ignoreEos: 'Ignorar EOS',
extraHeaders: 'Encabezados adicionales',
},
vllmMonitor: {
concurrency: 'Concurrencia actual',
sampleMoment: 'Al tomar la muestra',
sampleWindow: 'Intervalo de muestreo',
requestUnit: 'solicitudes',
processing: 'En proceso',
queued: 'En cola',
requestTiming: 'Tiempo de las solicitudes',
completedRequests: 'Solicitudes completadas en el intervalo',
tokensPerSecond: 'tokens/s',
requestsPerSecond: 'Solicitudes/s',
finishReasonLabels: {
@@ -1747,156 +1732,6 @@ const message = {
modelType: 'Tipo de modelo',
},
},
benchmarkPlan: {
prepareTimeoutHelp: 'Límite de preparación, excluido del tiempo límite total.',
pointTimeoutHelp:
'Tiempo independiente por punto, con inicio, calentamiento y solicitudes; limitado por el tiempo total restante.',
totalTimeoutHelp: 'Empieza tras la preparación, incluye puntos e intervalos y detiene la prueba al agotarse.',
concurrencyLevels: 'Niveles de concurrencia',
concurrencyHelp: 'Los niveles se ejecutan en secuencia. Cada valor limita las solicitudes simultáneas.',
requestCountMode: 'Cantidad de solicitudes',
fixedRequestMode: 'Solicitudes fijas por nivel',
scaledRequestMode: 'Calcular según la concurrencia',
requestsPerPoint: 'Solicitudes por nivel',
requestCountFormula:
'Por nivel: valor fijo o max(concurrencia × multiplicador, mínimo de solicitudes). Total del escenario: suma de los niveles × repeticiones, sin calentamiento.',
controlLog: 'Registro de preparación y control',
executionLog: 'Registro de ejecución',
pointLog: 'Punto n.º {0} · {1} · Registro de ejecución',
archivedLog: '{0} (archivo histórico {1})',
tokenLengths: 'Tokens de entrada / salida',
kind: 'Categoría de resultados',
kindServe: 'serve (pruebas normales)',
kindPD: 'pd (mediciones de Prefill/Decode)',
saveDetailed: 'Guardar detalles de solicitudes',
saveDetailedHelp:
'Guarda longitudes, latencias, texto generado y errores de cada solicitud. Al desactivarlo se conservan las métricas resumidas y se reduce el tamaño de los archivos.',
parameters: 'Parámetros',
temperatureHelp:
'Controla la aleatoriedad de la respuesta. Cero suele producir resultados más estables; los valores altos aumentan la aleatoriedad.',
pointTimeout: 'Tiempo límite por punto (segundos)',
temperature: 'Temperatura de muestreo',
requestUrl: 'URL de API',
invalidRequestUrl:
'Introduzca una URL HTTP(S) completa terminada en /chat/completions o /completions, sin credenciales, parámetros de consulta ni fragmentos.',
requestRateHelp:
'Se aplica solo a este escenario. La tasa ilimitada respeta los límites de concurrencia; se admite QPS fraccionario.',
tokenizerModeHelp:
'Los modos disponibles dependen de la versión de vLLM de la imagen de prueba. La carga del tokenizador se comprueba al preparar la tarea.',
invalidBaseUrl: 'Introduzca una URL HTTP(S) válida sin credenciales, consultas ni fragmentos.',
invalidEndpoint:
'Introduzca una ruta compatible con el protocolo seleccionado que termine en {0}, sin parámetros de consulta ni fragmentos.',
invalidTokenizer:
'Use un directorio absoluto o un ID de repositorio; no se admiten rutas relativas, comas ni saltos de línea.',
invalidRequestRate: 'Seleccione sin límite o introduzca un número finito mayor que 0; se admiten decimales.',
invalidCaCert:
'Introduzca la ruta absoluta del certificado CA en el nodo principal, sin comas ni saltos de línea.',
bodyOverride: 'El cuerpo adicional no puede sobrescribir {0}.',
invalidBody: 'Introduzca un objeto JSON válido.',
invalidConcurrencies: 'Indique entre 1 y 100 valores de concurrencia, cada uno un entero entre 1 y 100000.',
emptyRequestCount:
'Al calcular según la concurrencia, el multiplicador y el mínimo de solicitudes no pueden ser ambos 0.',
requestCountLimit: 'Las solicitudes calculadas por punto no pueden superar 1000000.',
contextExceeded: 'Los tokens de entrada, salida y reserva de plantilla superan el límite de contexto.',
planLimit: 'Indique entre 1 y 100 escenarios con un máximo de 2000 puntos.',
caCertHelp:
'Seleccione un certificado CA del servidor principal para verificar servicios HTTPS. Déjelo vacío para usar los certificados del sistema.',
extraBody: 'Cuerpo adicional de la solicitud',
entryKey: 'Nombre del parámetro',
entryValue: 'Valor del parámetro',
tokenizerMode: 'Modo del tokenizador',
requireIdle: 'Comprobar que el servicio esté inactivo antes de comenzar',
requireIdleHelp:
'Consulta /metrics una vez antes del calentamiento y la prueba de carga. Se detiene si el servicio está ocupado o no se puede verificar su estado. No impide otro tráfico durante la prueba.',
trustRemoteCode: 'Confiar en código remoto',
trustRemoteCodeHelp:
'Permite ejecutar código de tokenización del repositorio del modelo dentro del contenedor de pruebas. Actívelo solo si es necesario y confía en la fuente.',
tokenizerModes: { auto: 'Automático (recomendado)', slow: 'Tokenizador lento', mistral: 'Tokenizador Mistral' },
quickDescription: 'Verifica la conectividad y la medición con una sola solicitud.',
regularDescription: 'Cargas cortas, medias y largas para pruebas habituales de rendimiento.',
fullDescription:
'Varios escenarios, niveles de concurrencia y repeticiones, con entradas largas y prefijos compartidos.',
customDescription: 'Empiece con un escenario, configure sus parámetros y añada más según necesite.',
scenarios: 'Escenarios',
builtIn: 'Integrado',
builtInHelp: 'Los esquemas integrados no se pueden editar ni eliminar. Ajuste una copia al crear una prueba.',
modified: 'Modificado',
imageExists: 'Disponible localmente',
imageNeedsPull: 'Requiere descarga',
remark: 'Observaciones',
source: 'Servicio local',
mode: 'Plan de prueba',
quick: 'Comprobación rápida',
regular: 'Normal',
full: 'Completa',
custom: 'Personalizada',
addScenario: 'Añadir escenario',
advanced: 'Configuración avanzada',
points: 'Puntos',
warmups: 'Solicitudes de calentamiento',
fixedRequests: 'Solicitudes fijas',
multiplier: 'Solicitudes por concurrencia',
minRequests: 'Solicitudes mínimas',
trials: 'Repeticiones',
prefixRatio: 'Proporción de prefijo compartido',
totalTimeout: 'Tiempo límite de ronda (s)',
prepareTimeout: 'Tiempo límite de preparación (s)',
interval: 'Intervalo entre puntos (s)',
context: 'Límite de contexto (0: desconocido)',
reserve: 'Tokens reservados para plantilla',
storage: 'Límite de almacenamiento (MiB)',
stopOnError: 'Detener los siguientes puntos si hay errores',
caCert: 'Ruta del certificado CA',
validTrials: 'Válidas / total',
retry: 'Repetir puntos seleccionados',
environment: 'Entorno',
plan: 'Configuración fijada',
clientHelp:
'El contenedor cliente se ejecuta en el nodo principal y envía solicitudes a un servicio existente. No inicia inferencia ni requiere GPU.',
retryHelp: 'Solo se repiten los puntos anómalos seleccionados, con los mismos parámetros y nuevas semillas.',
secretHelp: 'Las copias no incluyen claves API ni cabeceras. Vuelva a introducirlas si es necesario.',
resultHelp:
'La mediana y los mínimos/máximos solo incluyen ejecuciones correctas sin anomalías. P99 es la mediana de los P99 de cada ejecución. La caché y el tráfico externo no están controlados.',
},
benchmarkOverview: {
noResults: 'No hay resultados válidos. Consulte la causa del fallo y los registros.',
noGroupResults: 'Esta combinación no tiene resultados válidos. Seleccione otro escenario o concurrencia.',
partial: 'Resultados parciales: la prueba no se completó con éxito. Solo se incluyen puntos válidos.',
highlights: 'Métricas principales',
aggregate: 'Mediana de las ejecuciones válidas de esta combinación. Pulse una métrica para ver los detalles.',
trend: 'Tendencia por concurrencia',
std: 'Desviación estándar',
min: 'Mínimo',
max: 'Máximo',
tpot: 'Tiempo por token de salida',
tpotHelp: 'Tiempo medio de generación por token de salida después del primero.',
itl: 'Latencia entre tokens',
itlHelp: 'Tiempo entre tokens consecutivos de una respuesta en streaming.',
e2el: 'Latencia de extremo a extremo',
e2elHelp: 'Tiempo desde el envío de la solicitud hasta la recepción de la respuesta completa.',
requestHelp: 'Número de solicitudes completadas por segundo.',
usageHelp:
'Medianas de las ejecuciones válidas de esta combinación, no totales de solicitudes de toda la prueba. Consulte las ejecuciones anómalas en la pestaña de puntos.',
duration: 'Duración de la ejecución',
throughput: 'Rendimiento',
latency: 'Latencia',
usage: 'Solicitudes y uso (ejecuciones válidas)',
},
benchmarkTokenizer: {
title: 'Gestión de tokenizadores',
local: 'Registrar directorio local',
upload: 'Subir paquete de tokenizador',
uploadHelp: 'Suba un ZIP / tar.gz sin pesos del modelo. Máximo: 128 MiB comprimido y 256 MiB extraído.',
source: 'Método de importación',
deleteLocal:
'¿Quitar el registro del tokenizador «{0}»? Se conservarán el directorio y los archivos originales.',
deleteUpload: '¿Eliminar el tokenizador «{0}» y sus archivos subidos? Se conservarán los informes anteriores.',
select: 'Seleccionar un tokenizador',
selectHelp:
'Para pruebas sin conexión, seleccione un tokenizador gestionado. La entrada personalizada admite directorios locales o nombres de repositorios.',
},
container: {
createByCommand: 'Crear por comando',
importContainerBackupTip: 'Importar archivo de copia de seguridad de contenedor, solo se admite .tar.gz',
@@ -3172,20 +3007,6 @@ const message = {
remote: 'SSH remoto',
},
setting: {
panelCache: {
title: 'Configuración de caché',
tabs: 'Caché de pestañas',
pagination: 'Caché de paginación',
refresh: 'Caché del intervalo de actualización',
search: 'Caché del campo de búsqueda',
duration: 'Duración de la caché',
'24h': '24 horas',
week: 'Una semana',
month: 'Un mes',
forever: 'Permanente',
helper: 'El plazo se calcula desde el último cambio de estado. Al caducar, se restablecen los valores predeterminados.',
},
panel: 'Panel',
user: 'Usuario del panel',
userChange: 'Cambiar usuario del panel',
@@ -3930,7 +3751,6 @@ const message = {
link: 'Enlace',
showCurrentArch: 'Arquitectura',
syncLocalApp: 'Sincronizar app local',
localAppSyncToNodes: 'Incluir nodos secundarios al sincronizar apps locales',
memoryRequiredHelper: 'La app requiere {0} de memoria',
gpuConfig: 'Habilitar soporte GPU',
gpuConfigHelper: 'Asegúrese de tener GPU NVIDIA, drivers y NVIDIA Docker Container Toolkit instalados',
@@ -4473,8 +4293,7 @@ const message = {
domainHelper: 'Un dominio por línea, admite * y dirección IP',
pushDir: 'Guardar certificado en el directorio local',
dir: 'Directorio',
pushDirHelper:
'Seleccione varios directorios o introduzca un directorio por línea. En cada directorio se generarán los archivos "fullchain.pem" y "privkey.pem".',
pushDirHelper: 'Se generarán los archivos "fullchain.pem" y "privkey.pem" en este directorio.',
organizationDetail: 'Detalles de la organización',
fromWebsite: 'Desde el sitio web',
dnsMauanlHelper: 'En modo manual, haz clic en solicitar tras la creación para obtener el valor DNS',
@@ -4528,14 +4347,10 @@ const message = {
resetWithBackup: 'Guardar reglas y restablecer',
resetOnly: 'Solo restablecer',
resetWithBackupHelper:
'Guarde las reglas del módulo actual en JSON para restaurarlas al inicializar o iniciar el cortafuegos.',
'Guarda las reglas del módulo actual en JSON para restaurarlas durante la inicialización.',
resetOnlyHelper: 'Borrar las reglas actuales sin crear una copia nueva. Se conservan las copias existentes.',
backupDirectoryHelper:
'Se muestran las copias válidas de reglas en {0}. Se selecciona la más reciente por defecto.',
startOnly: 'Solo iniciar',
startAndRestore: 'Iniciar y restaurar desde una copia de seguridad',
startWithBackupHelper:
'Inicie el cortafuegos normalmente o elija una copia JSON existente para iniciar y restaurar reglas.',
restoreAllRules: 'Inicializar y restaurar reglas',
importRuleBackup: 'Importar desde copia de seguridad',
initializeOnly: 'Solo inicializar',
+10 -191
View File
@@ -489,7 +489,6 @@ const message = {
recommend: 'پیشنهادی',
dir: 'دایرکتوری',
alias: 'نام مستعار',
quickDirDelete: 'حذف ({0}) را تأیید می‌کنید؟',
quickDir: 'دسترسی سریع',
minQuickJump: 'حداقل یک ورودی دسترسی سریع اضافه کنید',
maxQuickJump: 'می‌توانید حداکثر چهار ورودی دسترسی سریع اضافه کنید',
@@ -769,14 +768,6 @@ const message = {
},
aiTools: {
agents: {
deepSeekAccessHostHelper:
'نشانی IPv4 یا نام میزبان مورد استفاده در مرورگر را بدون پروتکل، درگاه یا مسیر وارد کنید. HTTPS از گواهی محلی برنامه استفاده می‌کند.',
deepSeekAuthHelper:
'نام کاربری: حروف لاتین، اعداد، نقطه، زیرخط یا خط تیره. گذرواژه: حداقل ۱۲ نویسه، بدون فاصله در ابتدا و انتها یا شکست خط.',
octopAuthHelper:
'گذرواژه: حداقل ۸ نویسه شامل حروف و اعداد؛ از گذرواژه‌های رایج پرهیز کنید. اگر رد شد، راهنمای تنظیمات را در گزارش‌های برنامه ببینید.',
initialCredentials: 'اطلاعات ورود اولیه؛ اگر در برنامه تغییر کرده، از گذرواژه فعلی استفاده کنید.',
nativeAgentAccessUnavailable: 'نصب موجود نشانی یا درگاه HTTPS سازگار ندارد. تنظیمات برنامه را بررسی کنید.',
agent: 'عامل',
account: 'حساب مدل',
batchOperation: 'عملیات دسته‌ای',
@@ -1477,13 +1468,16 @@ const message = {
statusWaiting: 'در انتظار',
base: 'اطلاعات پایه',
backend: 'بک‌اند',
baseUrl: 'URL پایه',
endpoint: 'نقطه پایانی',
tokenizer: 'توکن‌ساز',
tokenizerPlaceholder: 'مسیر مطلق پوشه در گره اصلی یا شناسه مخزن Hugging Face را وارد کنید',
tokenizerPlaceholder:
'یک دایرکتوری توکن‌ساز محلی را انتخاب یا وارد کنید، مثلاً /opt/1panel/tokenizers/DeepSeek-V3',
runConfig: 'پیکربندی اجرا',
rawResult: 'نتیجه خام',
resultMetrics: 'معیارهای نتیجه',
contextTokens: 'طول زمینه',
contextTokensHelper: 'محدودیت توکن ورودی به اضافه محدودیت توکن خروجی برای این معیار سنجش.',
outputThroughput: 'توان خروجی',
outputThroughputHelper: 'توکن‌های خروجی تولید شده در ثانیه. بیشتر به معنای تولید سریع‌تر است.',
totalThroughput: 'توان کل',
@@ -1491,30 +1485,23 @@ const message = {
'توکن‌های ورودی و خروجی پردازش شده در ثانیه. برای اندازه‌گیری ظرفیت کلی استفاده می‌شود.',
firstTokenLatency: 'تأخیر اولین توکن',
firstTokenLatencyHelper: 'زمان از ارسال درخواست تا دریافت اولین توکن. کمتر به معنای پاسخ سریع‌تر است.',
tokenValueHelper: 'یک عدد صحیح مثبت یا مقدار k وارد کنید، مثلاً 512، 1k، 32k',
numPrompts: 'راهنماها',
concurrency: 'هم‌روندی',
requestRate: 'نرخ درخواست',
requestRateUnlimited: 'نامحدود (حداکثر توان)',
requestRateCustom: 'QPS سفارشی',
requestRateCustomPlaceholder: 'درخواست در ثانیه، مثلاً 2.5',
successfulRequests: 'درخواست‌های موفق',
requestThroughput: 'توان درخواست',
mean: 'میانگین',
median: 'میانه',
timeout: 'مهلت (ثانیه)',
image: 'تصویر vLLM',
ignoreEos: 'نادیده گرفتن توکن پایان دنباله (EOS)',
ignoreEosHelp:
'از توقف زودهنگام به دلیل EOS جلوگیری می‌کند تا طول خروجی تنظیم‌شده آزموده شود. سایر شرایط توقف همچنان اعمال می‌شوند و پشتیبانی سرور لازم است.',
ignoreEos: 'نادیده گرفتن EOS',
extraHeaders: 'هدرهای اضافی',
},
vllmMonitor: {
concurrency: 'درخواست‌های هم‌زمان فعلی',
sampleMoment: 'در لحظه نمونه‌برداری',
sampleWindow: 'بازه نمونه‌برداری',
requestUnit: 'درخواست',
processing: 'در حال پردازش',
queued: 'در صف',
requestTiming: 'زمان پردازش درخواست',
completedRequests: 'تعداد درخواست‌های تکمیل‌شده در بازه',
tokensPerSecond: 'tokens/ثانیه',
requestsPerSecond: 'درخواست/ثانیه',
finishReasonLabels: {
@@ -1714,153 +1701,6 @@ const message = {
modelType: 'نوع مدل',
},
},
benchmarkPlan: {
prepareTimeoutHelp: 'مهلت آماده‌سازی در مهلت کلی آزمون محاسبه نمی‌شود.',
pointTimeoutHelp:
'زمان‌سنج مستقل هر نقطه، شامل راه‌اندازی، گرم‌کردن و همه درخواست‌ها؛ محدود به زمان کلی باقی‌مانده.',
totalTimeoutHelp:
'پس از آماده‌سازی شروع می‌شود و همه نقاط و فاصله‌ها را شامل می‌شود. با پایان مهلت، آزمون متوقف می‌شود.',
concurrencyLevels: 'سطوح هم‌زمانی',
concurrencyHelp:
'سطوح به‌ترتیب آزمایش می‌شوند. هر مقدار سقف تعداد درخواست‌های در حال اجرا به‌صورت هم‌زمان است.',
requestCountMode: 'تنظیم تعداد درخواست‌ها',
fixedRequestMode: 'تعداد ثابت برای هر سطح',
scaledRequestMode: 'محاسبه بر اساس هم‌زمانی',
requestsPerPoint: 'درخواست در هر سطح',
requestCountFormula:
'هر سطح: مقدار ثابت یا max(هم‌زمانی × ضریب، حداقل درخواست‌ها). مجموع سناریو: مجموع سطوح × تعداد تکرار، بدون گرم‌کردن.',
controlLog: 'گزارش آماده‌سازی و کنترل',
executionLog: 'گزارش اجرا',
pointLog: 'نقطهٔ {0} · {1} · گزارش اجرا',
archivedLog: '{0} (بایگانی {1})',
tokenLengths: 'توکن‌های ورودی / خروجی',
kind: 'دسته‌بندی نتایج',
kindServe: 'serve (آزمون‌های معمول)',
kindPD: 'pd (سنجش Prefill/Decode)',
saveDetailed: 'ذخیره جزئیات درخواست‌ها',
saveDetailedHelp:
'طول، تأخیر، متن تولیدشده و خطاهای هر درخواست را ذخیره می‌کند. با غیرفعال کردن، شاخص‌های خلاصه حفظ و حجم فایل نتیجه کمتر می‌شود.',
parameters: 'پارامترها',
temperatureHelp:
'میزان تصادفی بودن خروجی را کنترل می‌کند. صفر معمولاً خروجی پایدارتری می‌دهد؛ مقادیر بالاتر تصادفی بودن را افزایش می‌دهند.',
pointTimeout: 'مهلت هر نقطه (ثانیه)',
temperature: 'دمای نمونه‌برداری',
requestUrl: 'نشانی API',
invalidRequestUrl:
'نشانی کامل HTTP(S) را با پایان /chat/completions یا /completions وارد کنید؛ بدون اطلاعات ورود، پارامترهای پرس‌وجو یا بخش پس از #.',
requestRateHelp:
'فقط برای این سناریو اعمال می‌شود. نرخ نامحدود همچنان از سقف هم‌زمانی پیروی می‌کند؛ QPS اعشاری پشتیبانی می‌شود.',
tokenizerModeHelp:
'حالت‌های موجود به نسخه vLLM در تصویر آزمون بستگی دارند. بارگذاری توکن‌ساز هنگام آماده‌سازی کار بررسی می‌شود.',
invalidBaseUrl: 'نشانی معتبر HTTP(S) بدون اطلاعات ورود، پارامتر پرس‌وجو یا قطعه وارد کنید.',
invalidEndpoint:
'مسیر سازگار با پروتکل انتخاب‌شده را وارد کنید که به {0} ختم شود و پارامتر پرس‌وجو یا بخش نشانی نداشته باشد.',
invalidTokenizer: 'مسیر مطلق پوشه یا شناسه مخزن را وارد کنید؛ مسیر نسبی، ویرگول و خط جدید پشتیبانی نمی‌شوند.',
invalidRequestRate: 'نامحدود را انتخاب کنید یا عدد متناهی بزرگ‌تر از 0 وارد کنید؛ اعشار مجاز است.',
invalidCaCert: 'مسیر مطلق فایل گواهی CA در گره اصلی را بدون ویرگول یا خط جدید وارد کنید.',
bodyOverride: 'بدنه اضافی نمی‌تواند {0} را بازنویسی کند.',
invalidBody: 'یک شیء JSON معتبر وارد کنید.',
invalidConcurrencies: 'بین 1 تا 100 مقدار هم‌زمانی وارد کنید؛ هر مقدار عدد صحیح بین 1 و 100000 باشد.',
emptyRequestCount: 'هنگام محاسبه بر اساس هم‌زمانی، ضریب و حداقل درخواست‌ها نمی‌توانند هر دو 0 باشند.',
requestCountLimit: 'تعداد محاسبه‌شده درخواست‌ها در هر نقطه نباید بیشتر از 1000000 باشد.',
contextExceeded: 'مجموع توکن‌های ورودی، خروجی و ذخیره قالب از حد زمینه بیشتر است.',
planLimit: 'بین 1 تا 100 سناریو با حداکثر 2000 نقطه وارد کنید.',
caCertHelp:
'فایل گواهی CA را از سرور اصلی برای تأیید سرویس HTTPS انتخاب کنید. برای استفاده از گواهی‌های سیستم خالی بگذارید.',
extraBody: 'بدنه اضافی درخواست',
entryKey: 'نام پارامتر',
entryValue: 'مقدار پارامتر',
tokenizerMode: 'حالت توکن‌ساز',
requireIdle: 'بررسی بیکار بودن سرویس پیش از شروع',
requireIdleHelp:
'پیش از گرم‌کردن و آزمون بار، /metrics یک بار بررسی می‌شود. اگر سرویس مشغول باشد یا بیکار بودن آن قابل تأیید نباشد، آزمون متوقف می‌شود. این بررسی مانع ترافیک دیگر در طول آزمون نمی‌شود.',
trustRemoteCode: 'اعتماد به کد راه‌دور',
trustRemoteCodeHelp:
'اجرای کد سفارشی توکن‌ساز از مخزن مدل در کانتینر آزمون را مجاز می‌کند. فقط در صورت نیاز و اعتماد به منبع فعال کنید.',
tokenizerModes: { auto: 'خودکار (پیشنهادی)', slow: 'توکن‌ساز کند', mistral: 'توکن‌ساز Mistral' },
quickDescription: 'اتصال و فرایند اندازه‌گیری را با یک درخواست بررسی می‌کند.',
regularDescription: 'بارهای کوتاه، متوسط و بلند برای آزمون روزمره عملکرد.',
fullDescription: 'چندین سناریو، سطح هم‌زمانی و اجرای تکراری، شامل ورودی‌های بلند و پیشوندهای مشترک.',
customDescription:
'با یک سناریو شروع کنید، پارامترهای آن را تنظیم کنید و در صورت نیاز سناریوهای بیشتری اضافه کنید.',
scenarios: 'سناریوها',
builtIn: 'داخلی',
builtInHelp: 'طرح‌های داخلی قابل ویرایش یا حذف نیستند. هنگام ایجاد آزمون یک نسخه را تنظیم کنید.',
modified: 'تغییریافته',
imageExists: 'موجود در میزبان',
imageNeedsPull: 'نیازمند دریافت',
remark: 'یادداشت',
source: 'سرویس محلی',
mode: 'برنامه آزمون',
quick: 'بررسی سریع',
regular: 'معمولی',
full: 'کامل',
custom: 'سفارشی',
addScenario: 'افزودن سناریو',
advanced: 'تنظیمات پیشرفته',
points: 'نقاط اندازه‌گیری',
warmups: 'درخواست‌های گرم‌سازی',
fixedRequests: 'درخواست ثابت',
multiplier: 'ضریب درخواست بر هم‌زمانی',
minRequests: 'حداقل درخواست',
trials: 'تکرارها',
prefixRatio: 'نسبت پیشوند مشترک',
totalTimeout: 'مهلت دور (ثانیه)',
prepareTimeout: 'مهلت آماده‌سازی (ثانیه)',
interval: 'فاصله نقاط (ثانیه)',
context: 'حد زمینه (۰: نامشخص)',
reserve: 'توکن ذخیره قالب',
storage: 'حد ذخیره‌سازی (MiB)',
stopOnError: 'توقف نقاط بعدی هنگام خطا',
caCert: 'مسیر گواهی CA',
validTrials: 'معتبر / کل تکرارها',
retry: 'تکرار نقاط انتخاب‌شده',
environment: 'محیط اجرا',
plan: 'پیکربندی ثابت',
clientHelp:
'کانتینر کلاینت در گره اصلی به سرویس موجود درخواست می‌فرستد. سرویس استنتاج راه‌اندازی نمی‌کند و به GPU نیاز ندارد.',
retryHelp: 'فقط نقاط غیرعادی انتخاب‌شده با همان پارامترها و بذرهای جدید تکرار می‌شوند.',
secretHelp: 'کپی‌ها شامل کلید API و سرآیندها نیستند. در صورت نیاز دوباره وارد کنید.',
resultHelp:
'میانه و کمینه/بیشینه فقط از اجراهای موفق بدون ناهنجاری درخواست محاسبه می‌شوند. P99 میانه P99 هر اجرا است. کش و ترافیک بیرونی کنترل نشده‌اند.',
},
benchmarkOverview: {
noResults: 'نتیجه معتبر وجود ندارد. علت شکست و گزارش‌ها را بررسی کنید.',
noGroupResults: 'برای این ترکیب نتیجه معتبر وجود ندارد. سناریو یا هم‌زمانی دیگری انتخاب کنید.',
partial: 'نتایج جزئی: آزمون کاملاً موفق نبوده است. معیارها فقط نقاط معتبر را شامل می‌شوند.',
highlights: 'معیارهای اصلی',
aggregate: 'میانه اجراهای معتبر این ترکیب. برای جزئیات روی معیار کلیک کنید.',
trend: 'روند هم‌زمانی',
std: 'انحراف معیار',
min: 'کمینه',
max: 'بیشینه',
tpot: 'زمان هر توکن خروجی',
tpotHelp: 'میانگین زمان تولید هر توکن خروجی پس از توکن نخست.',
itl: 'تأخیر بین توکن‌ها',
itlHelp: 'زمان بین توکن‌های متوالی در پاسخ جریانی.',
e2el: 'تأخیر سرتاسری',
e2elHelp: 'زمان ارسال درخواست تا دریافت کامل پاسخ.',
requestHelp: 'تعداد درخواست‌های تکمیل‌شده در ثانیه.',
usageHelp:
'میانه اجراهای معتبر این ترکیب است، نه مجموع درخواست‌های کل آزمون. اجراهای غیرعادی را در زبانه نقاط بررسی کنید.',
duration: 'مدت اجرا',
throughput: 'توان عملیاتی',
latency: 'تأخیر',
usage: 'درخواست‌ها و مصرف (اجراهای معتبر)',
},
benchmarkTokenizer: {
title: 'مدیریت توکن‌ساز',
local: 'ثبت پوشه محلی',
upload: 'بارگذاری بسته توکن‌ساز',
uploadHelp: 'فایل ZIP / tar.gz بدون وزن‌های مدل بارگذاری کنید. حداکثر ۱۲۸ MiB فشرده و ۲۵۶ MiB پس از استخراج.',
source: 'روش ورود',
deleteLocal: 'ثبت توکن‌ساز «{0}» حذف شود؟ پوشه و فایل‌های اصلی حفظ می‌شوند.',
deleteUpload: 'توکن‌ساز «{0}» و فایل‌های بارگذاری‌شده حذف شوند؟ گزارش‌های پیشین حفظ می‌شوند.',
select: 'انتخاب توکنایزر',
selectHelp: 'برای آزمون آفلاین از فهرست مدیریت انتخاب کنید. ورودی سفارشی پوشه محلی یا نام مخزن را می‌پذیرد.',
},
container: {
createByCommand: 'ایجاد با دستور',
importContainerBackupTip: 'واردات فایل پشتیبان کانتینر، فقط .tar.gz پشتیبانی می‌شود',
@@ -3092,20 +2932,6 @@ const message = {
remote: 'SSH از راه دور',
},
setting: {
panelCache: {
title: 'تنظیمات حافظهٔ نهان',
tabs: 'حافظهٔ نهان زبانه‌ها',
pagination: 'حافظهٔ نهان صفحه‌بندی',
refresh: 'حافظهٔ نهان فاصلهٔ بازخوانی',
search: 'حافظهٔ نهان ورودی جستجو',
duration: 'مدت نگهداری حافظهٔ نهان',
'24h': '۲۴ ساعت',
week: 'یک هفته',
month: 'یک ماه',
forever: 'دائمی',
helper: 'مدت اعتبار از آخرین تغییر وضعیت محاسبه می‌شود. پس از انقضا، مقادیر پیش‌فرض بازیابی می‌شوند.',
},
panel: 'پنل',
user: 'کاربر پنل',
userChange: 'تغییر کاربر پنل',
@@ -3824,7 +3650,6 @@ const message = {
link: 'لینک',
showCurrentArch: 'معماری',
syncLocalApp: 'همگام‌سازی برنامه محلی',
localAppSyncToNodes: 'همگام‌سازی گره‌های فرزند هنگام همگام‌سازی برنامه‌های محلی',
memoryRequiredHelper: 'نیاز حافظه برنامه فعلی {0}',
gpuConfig: 'فعال‌سازی پشتیبانی GPU',
gpuConfigHelper:
@@ -4381,8 +4206,7 @@ const message = {
domainHelper: 'هر خط یک نام دامنه، پشتیبانی از * و آدرس IP',
pushDir: 'ارسال گواهی به دایرکتوری محلی',
dir: 'دایرکتوری',
pushDirHelper:
'چند دایرکتوری انتخاب کنید یا در هر خط یک دایرکتوری وارد کنید. فایل‌های "fullchain.pem" و "privkey.pem" در هر دایرکتوری ایجاد می‌شوند.',
pushDirHelper: 'فایل گواهی "fullchain.pem" و فایل کلید "privkey.pem" در این دایرکتوری تولید می‌شوند.',
organizationDetail: 'جزئیات سازمان',
fromWebsite: 'از وب‌سایت',
dnsMauanlHelper:
@@ -4435,15 +4259,10 @@ const message = {
familyIncomplete: 'زنجیره‌های {0} ناقص هستند',
resetWithBackup: 'پشتیبان‌گیری قوانین و بازنشانی',
resetOnly: 'فقط بازنشانی',
resetWithBackupHelper:
'قوانین ماژول فعلی را به‌صورت JSON ذخیره کنید تا هنگام مقداردهی اولیه یا راه‌اندازی بازیابی شوند.',
resetWithBackupHelper: 'قوانین ماژول فعلی را به‌صورت JSON ذخیره کنید تا هنگام مقداردهی اولیه بازیابی شوند.',
resetOnlyHelper: 'قوانین فعلی بدون ایجاد نسخه پشتیبان جدید پاک می‌شوند. نسخه‌های پشتیبان قبلی حفظ می‌شوند.',
backupDirectoryHelper:
'نسخه‌های پشتیبان معتبر قوانین در {0} نمایش داده می‌شوند. جدیدترین فایل به‌طور پیش‌فرض انتخاب می‌شود.',
startOnly: 'فقط راه\u200cاندازی',
startAndRestore: 'راه\u200cاندازی و بازیابی از نسخه پشتیبان',
startWithBackupHelper:
'فایروال را به‌طور معمول راه‌اندازی کنید یا یک نسخه پشتیبان JSON موجود را برای راه‌اندازی و بازیابی قوانین انتخاب کنید.',
restoreAllRules: 'مقداردهی اولیه و بازیابی قوانین',
importRuleBackup: 'ورود از نسخه پشتیبان',
initializeOnly: 'فقط راه‌اندازی',
+10 -190
View File
@@ -492,7 +492,6 @@ const message = {
recommend: 'おすすめ',
dir: 'ディレクトリ',
alias: 'エイリアス',
quickDirDelete: '({0})を削除しますか?',
quickDir: 'クイックアクセス',
minQuickJump: 'クイックアクセス項目を1つ以上追加してください',
maxQuickJump: '最大4件のクイックアクセス項目を追加できます',
@@ -773,15 +772,6 @@ const message = {
},
aiTools: {
agents: {
deepSeekAccessHostHelper:
'ブラウザーで使用する IPv4 アドレスまたはホスト名を、スキーム・ポート・パスなしで入力してください。HTTPS はアプリのローカル証明書を使用します。',
deepSeekAuthHelper:
'ユーザー名には英数字、ドット、アンダースコア、ハイフンを使用できます。パスワードは12文字以上で、前後の空白や改行を含めないでください。',
octopAuthHelper:
'パスワードは英字と数字を含む8文字以上とし、よく使われるパスワードを避けてください。初期化時に拒否された場合は、アプリのログで設定手順を確認してください。',
initialCredentials: '初期ログイン情報です。アプリ内で変更した場合は現在のパスワードを使用してください。',
nativeAgentAccessUnavailable:
'既存のインストールに対応する HTTPS アクセス先またはポートがありません。アプリの設定を確認してください。',
agent: 'エージェント',
account: 'モデルアカウント',
batchOperation: '一括操作',
@@ -1482,43 +1472,39 @@ const message = {
statusWaiting: '待機中',
base: '基本情報',
backend: 'バックエンド',
baseUrl: 'Base URL',
endpoint: 'エンドポイント',
tokenizer: 'トークナイザー',
tokenizerPlaceholder: 'メインノード上の絶対ディレクトリパス、または Hugging Face リポジトリ ID を入力',
tokenizerPlaceholder:
'ローカルトークナイザーディレクトリを選択または入力してください。例: /opt/1panel/tokenizers/DeepSeek-V3',
runConfig: '実行設定',
rawResult: '生データ',
resultMetrics: '結果指標',
contextTokens: 'コンテキスト長',
contextTokensHelper: 'このベンチマークの入力 Token 上限と出力 Token 上限の合計です。',
outputThroughput: '出力スループット',
outputThroughputHelper: '1 秒あたりに生成される出力 Token 数です。高いほど生成が速くなります。',
totalThroughput: '合計スループット',
totalThroughputHelper: '1 秒あたりに処理される入力と出力 Token の合計で、全体性能の測定に使います。',
firstTokenLatency: '初回 Token レイテンシ',
firstTokenLatencyHelper: 'リクエスト送信から最初の Token 受信までの時間です。低いほど応答が速くなります。',
tokenValueHelper: '正の整数または k 単位を入力してください。例: 512、1k、32k',
numPrompts: 'プロンプト数',
concurrency: '同時実行数',
mean: '平均値',
median: '中央値',
requestRate: 'リクエストレート',
requestRateUnlimited: '無制限(最大スループット)',
requestRateCustom: 'カスタム QPS',
requestRateCustomPlaceholder: '1 秒あたりのリクエスト数。例: 2.5',
successfulRequests: '成功リクエスト',
requestThroughput: 'リクエストスループット',
timeout: 'タイムアウト(秒)',
image: 'vLLM イメージ',
ignoreEos: '終了トークン(EOS)を無視',
ignoreEosHelp:
'EOS による早期終了を防ぎ、指定した出力長でのテストを助けます。他の停止条件は有効で、サーバー側の対応が必要です。',
ignoreEos: 'EOS を無視',
extraHeaders: '追加ヘッダー',
},
vllmMonitor: {
concurrency: '現在の同時リクエスト',
sampleMoment: 'サンプリング時点',
sampleWindow: '集計区間',
requestUnit: '件',
processing: '処理中',
queued: '待機中',
requestTiming: 'リクエスト処理時間',
completedRequests: '区間内の完了リクエスト数',
tokensPerSecond: 'tokens/秒',
requestsPerSecond: 'リクエスト/秒',
finishReasonLabels: {
@@ -1717,153 +1703,6 @@ const message = {
modelType: 'モデルタイプ',
},
},
benchmarkPlan: {
prepareTimeoutHelp: '準備時間の上限です。テスト全体のタイムアウトには含みません。',
pointTimeoutHelp:
'各測定点で個別に計時し、起動、ウォームアップ、全リクエストを含みます。全体の残り時間にも制限されます。',
totalTimeoutHelp: '準備完了後に開始し、全測定点と待機時間を含みます。時間切れでテストを停止します。',
concurrencyLevels: '同時実行数の段階',
concurrencyHelp: '各段階を順番にテストします。各値は同時に処理するリクエスト数の上限です。',
requestCountMode: 'リクエスト数の設定',
fixedRequestMode: '各段階のリクエスト数を固定',
scaledRequestMode: '同時実行数から計算',
requestsPerPoint: '各段階のリクエスト数',
requestCountFormula:
'各段階:固定値、または max(同時実行数 × 倍率, 最小リクエスト数)。 シナリオ合計:各段階の合計 × 繰り返し回数(ウォームアップを除く)。',
controlLog: '準備・制御ログ',
executionLog: '実行ログ',
pointLog: '測定点 #{0} · {1} · 実行ログ',
archivedLog: '{0}(履歴 {1})',
tokenLengths: '入力 / 出力トークン',
kind: '結果分類',
kindServe: 'serve(通常のテスト)',
kindPD: 'pd(Prefill/Decode 測定)',
saveDetailed: 'リクエスト詳細を保存',
saveDetailedHelp:
'各リクエストの長さ、遅延、生成テキスト、エラーを保存します。無効にしても集計指標は残り、結果ファイルを小さくできます。',
parameters: 'パラメーター',
temperatureHelp:
'生成内容のランダム性を制御します。0 は通常より安定した出力になり、値が高いほどランダム性が増します。',
pointTimeout: '測定点ごとのタイムアウト(秒)',
temperature: 'サンプリング温度',
requestUrl: 'API URL',
invalidRequestUrl:
'/chat/completions または /completions で終わる完全な HTTP(S) URL を入力してください。認証情報、クエリ、フラグメントは使用できません。',
requestRateHelp:
'このシナリオにのみ適用します。無制限でも同時実行数の上限に従います。小数の QPS も使用できます。',
tokenizerModeHelp:
'利用可能なモードはベンチマークイメージの vLLM バージョンによって異なります。タスクの準備時にトークナイザーを読み込めるか確認します。',
invalidBaseUrl: '認証情報、クエリ、フラグメントを含まない有効な HTTP(S) URL を入力してください。',
invalidEndpoint:
'選択したプロトコルに対応し、{0} で終わるパスを入力してください。クエリパラメーターやフラグメントは含めないでください。',
invalidTokenizer:
'絶対ディレクトリパスまたはリポジトリ ID を指定してください。相対パス、カンマ、改行は使用できません。',
invalidRequestRate: '無制限を選択するか、0 より大きい有限の数値を入力してください。小数も使用できます。',
invalidCaCert: 'プライマリノード上の CA 証明書の絶対パスを入力してください。カンマや改行は使用できません。',
bodyOverride: '追加本文で {0} を上書きできません。',
invalidBody: '有効な JSON オブジェクトを入力してください。',
invalidConcurrencies: '同時実行数は 1~100 項目、各値は 1~100000 の整数にしてください。',
emptyRequestCount: '同時実行数から計算する場合、倍率と最小リクエスト数の両方を 0 にできません。',
requestCountLimit: '測定点ごとの計算済みリクエスト数は 1000000 以下にしてください。',
contextExceeded: '入力・出力トークンとテンプレート予約分の合計がコンテキスト上限を超えています。',
planLimit: 'シナリオは 1~100 件、展開後の測定点は 2000 件以下にしてください。',
caCertHelp:
'HTTPS サービスの検証に使う CA 証明書をプライマリサーバーから選択します。空欄の場合はシステム証明書を使用します。',
extraBody: '追加のリクエスト本文',
entryKey: 'パラメーター名',
entryValue: 'パラメーター値',
tokenizerMode: 'トークナイザーモード',
requireIdle: '開始前にサービスのアイドル状態を確認',
requireIdleHelp:
'ウォームアップと負荷テストの前に /metrics を一度確認します。サービスが処理中、またはアイドル状態を確認できない場合は停止します。テスト中の他のトラフィックは防げません。',
trustRemoteCode: 'リモートコードを信頼',
trustRemoteCodeHelp:
'モデルリポジトリのカスタムトークナイザーコードをベンチマークコンテナー内で実行します。必要な場合に、信頼できる提供元に対してのみ有効にしてください。',
tokenizerModes: { auto: '自動(推奨)', slow: '低速トークナイザー', mistral: 'Mistral 専用' },
quickDescription: '単一リクエストで接続と測定処理を確認します。',
regularDescription: '短・中・長の負荷で日常的な性能テストを行います。',
fullDescription: '長い入力と共有プレフィックスを含む、複数シナリオ・同時実行数での反復測定です。',
customDescription: '1つのシナリオから始め、パラメーターを設定し、必要に応じて追加します。',
scenarios: 'シナリオ',
builtIn: '組み込み',
builtInHelp: '組み込みプランは編集・削除できません。テスト作成時にコピーを調整できます。',
modified: '変更済み',
imageExists: 'ローカルに存在',
imageNeedsPull: '取得が必要',
remark: '備考',
source: 'ローカルサービス',
mode: 'テスト計画',
quick: '簡易確認',
regular: '通常',
full: '完全',
custom: 'カスタム',
addScenario: 'シナリオを追加',
advanced: '詳細設定',
points: '測定点',
warmups: 'ウォームアップ要求数',
fixedRequests: '固定要求数',
multiplier: '同時実行数に対する要求数倍率',
minRequests: '最小要求数',
trials: '繰り返し回数',
prefixRatio: '共通プレフィックス比率',
totalTimeout: '全体のタイムアウト(秒)',
prepareTimeout: '準備のタイムアウト(秒)',
interval: '測定間隔(秒)',
context: 'コンテキスト上限(0:不明)',
reserve: 'テンプレート用予約トークン',
storage: '保存容量上限(MiB)',
stopOnError: 'エラー時に後続の測定を停止',
caCert: 'CA 証明書のパス',
validTrials: '有効 / 総試行数',
retry: '選択した測定点を再試行',
environment: '実行環境',
plan: '固定された設定',
clientHelp:
'クライアントコンテナはメインノードで既存サービスに要求を送信します。推論サービスの起動や GPU の割り当ては不要です。',
retryHelp: '選択した異常な測定点のみ、同じパラメータと新しいシードで再試行します。',
secretHelp: 'コピーに API キーとヘッダーは含まれません。必要に応じて再入力してください。',
resultHelp:
'要求異常のない成功した試行のみを集計し、中央値と最小・最大値を表示します。P99 は試行ごとの P99 の中央値です。キャッシュと外部トラフィックは制御されません。',
},
benchmarkOverview: {
noResults: '有効なテスト結果がありません。失敗の原因とログを確認してください。',
noGroupResults: 'この組み合わせに有効な結果はありません。シナリオまたは同時実行数を変更してください。',
partial: '部分的な結果:テストは完全には成功していません。有効な測定点のみを表示します。',
highlights: '主要指標',
aggregate: 'この組み合わせの有効な実行の中央値です。指標をクリックすると詳細を表示します。',
trend: '同時実行数による推移',
std: '標準偏差',
min: '最小値',
max: '最大値',
tpot: '出力トークンあたりの時間',
tpotHelp: '最初のトークン以降の、出力トークンあたりの平均生成時間。',
itl: 'トークン間レイテンシ',
itlHelp: 'ストリーミング応答の連続するトークン間の時間。',
e2el: 'エンドツーエンドのレイテンシ',
e2elHelp: 'リクエスト送信から応答の受信完了までの時間。',
requestHelp: '1 秒あたりに完了したリクエスト数。',
usageHelp:
'この組み合わせの有効な実行の中央値であり、テスト全体のリクエスト総数ではありません。異常な実行は測定点タブで確認してください。',
duration: '実行時間',
throughput: 'スループット',
latency: 'レイテンシ',
usage: 'リクエストと使用量(有効な実行)',
},
benchmarkTokenizer: {
title: 'トークナイザー管理',
local: 'ローカルディレクトリを登録',
upload: 'トークナイザーをアップロード',
uploadHelp:
'モデルの重みを含まない ZIP / tar.gz をアップロードしてください。圧縮時 128 MiB、展開後 256 MiB まで。',
source: 'インポート方法',
deleteLocal: '「{0}」の登録を解除しますか?元のディレクトリとファイルは保持されます。',
deleteUpload: '「{0}」とアップロード済みファイルを削除しますか?過去のテストレポートは保持されます。',
select: 'トークナイザーを選択',
selectHelp:
'オフラインでは管理リストから選択してください。カスタム入力ではローカルパスやリポジトリ名も使えます。',
},
container: {
createByCommand: 'コマンドで作成',
importContainerBackupTip: 'コンテナバックアップファイルをインポートします。.tar.gz のみ対応です',
@@ -3098,20 +2937,6 @@ const message = {
remote: 'リモートSSH',
},
setting: {
panelCache: {
title: 'キャッシュ設定',
tabs: 'タブのキャッシュ',
pagination: 'ページネーションのキャッシュ',
refresh: '更新間隔のキャッシュ',
search: '検索入力のキャッシュ',
duration: 'キャッシュ期間',
'24h': '24時間',
week: '1週間',
month: '1か月',
forever: '無期限',
helper: '有効期間は状態を最後に変更した時点から計算されます。期限が切れると既定値に戻ります。',
},
panel: 'パネル',
user: 'パネルユーザー',
userChange: 'パネルユーザーを変更します',
@@ -3845,7 +3670,6 @@ const message = {
link: 'リンク',
showCurrentArch: '現在のサーバーアーキテクチャのアプリケーション',
syncLocalApp: 'ローカルアプリの同期',
localAppSyncToNodes: 'ローカルアプリ同期時に子ノードも同期',
memoryRequiredHelper: '現在のアプリケーションは {0} メモリが必要です',
gpuConfig: 'GPUサポートを有効化',
gpuConfigHelper:
@@ -4405,8 +4229,7 @@ const message = {
domainHelper: '1行ごとに1つのドメイン名、 *およびIPアドレスをサポートします',
pushDir: '証明書をローカルディレクトリにプッシュします',
dir: 'ディレクトリ',
pushDirHelper:
'複数のディレクトリを選択するか、1行に1つずつディレクトリを入力してください。各ディレクトリに「fullchain.pem」と「privkey.pem」が生成されます。',
pushDirHelper: '証明書ファイル「FullChain.PEM」とキーファイル「Privkey.Pem」がこのディレクトリで生成されます。',
organizationDetail: '組織の詳細',
fromWebsite: 'ウェブサイトから',
dnsMauanlHelper: '手動解像度モードでは、作成後に適用ボタンをクリックしてDNS解像度値を取得する必要があります',
@@ -4459,12 +4282,9 @@ const message = {
familyIncomplete: '{0} のチェーンが不完全です',
resetWithBackup: 'ルールをバックアップしてリセット',
resetOnly: 'リセットのみ',
resetWithBackupHelper: '現在のモジュールのルールを JSON に保存し、初期化時または起動時に復元できます。',
resetWithBackupHelper: '現在のモジュールのルールを JSON に保存し、初期化時に復元できます。',
resetOnlyHelper: '新しいバックアップを作成せずに現在のルールを消去します。既存のバックアップは保持されます。',
backupDirectoryHelper: '{0} 内の有効なルールバックアップを表示します。既定では最新のファイルが選択されます。',
startOnly: '起動のみ',
startAndRestore: '起動してバックアップから復元',
startWithBackupHelper: '通常起動するか、既存の JSON バックアップを選んで起動とルールの復元を行います。',
restoreAllRules: '初期化してルールを復元',
importRuleBackup: 'バックアップからインポート',
initializeOnly: '初期化のみ',
+10 -187
View File
@@ -487,7 +487,6 @@ const message = {
recommend: '추천',
dir: '디렉토리',
alias: '별칭',
quickDirDelete: '({0})을(를) 삭제하시겠습니까?',
quickDir: '빠른 액세스',
minQuickJump: '최소 하나의 빠른 액세스 항목을 추가하세요',
maxQuickJump: '빠른 액세스를 최대 4개까지 추가할 수 있습니다',
@@ -764,15 +763,6 @@ const message = {
},
aiTools: {
agents: {
deepSeekAccessHostHelper:
'브라우저에서 사용하는 IPv4 주소 또는 호스트 이름을 입력하세요. 프로토콜, 포트, 경로는 제외하세요. HTTPS는 앱의 로컬 인증서를 사용합니다.',
deepSeekAuthHelper:
'사용자 이름은 영문, 숫자, 점, 밑줄, 하이픈만 지원합니다. 비밀번호는 12자 이상이며 앞뒤 공백이나 줄바꿈을 포함할 수 없습니다.',
octopAuthHelper:
'비밀번호는 문자와 숫자를 포함한 8자 이상이어야 하며 흔한 비밀번호는 피하세요. 초기화 시 거부되면 앱 로그의 설정 안내를 확인하세요.',
initialCredentials: '초기 로그인 정보입니다. 앱에서 변경했다면 현재 비밀번호를 사용하세요.',
nativeAgentAccessUnavailable:
'기존 설치에 호환되는 HTTPS 접속 주소 또는 포트가 없습니다. 앱 설정을 확인하세요.',
agent: '에이전트',
account: '모델 계정',
batchOperation: '일괄 작업',
@@ -1467,43 +1457,39 @@ const message = {
statusWaiting: '대기 중',
base: '기본 정보',
backend: '백엔드',
baseUrl: 'Base URL',
endpoint: '엔드포인트',
tokenizer: '토크나이저',
tokenizerPlaceholder: '메인 노드의 절대 디렉터리 경로 또는 Hugging Face 저장소 ID를 입력하세요',
tokenizerPlaceholder:
'로컬 토크나이저 디렉터리를 선택하거나 입력하세요. 예: /opt/1panel/tokenizers/DeepSeek-V3',
runConfig: '실행 구성',
rawResult: '원시 결과',
resultMetrics: '결과 지표',
contextTokens: '컨텍스트 길이',
contextTokensHelper: '이 벤치마크의 입력 Token 제한과 출력 Token 제한의 합계입니다.',
outputThroughput: '출력 처리량',
outputThroughputHelper: '초당 생성된 출력 Token 수입니다. 높을수록 생성 속도가 빠릅니다.',
totalThroughput: '총 처리량',
totalThroughputHelper: '초당 처리된 입력 및 출력 Token 수로 전체 처리 능력을 측정합니다.',
firstTokenLatency: '첫 Token 지연 시간',
firstTokenLatencyHelper: '요청 전송부터 첫 Token 수신까지의 시간입니다. 낮을수록 응답이 빠릅니다.',
tokenValueHelper: '양의 정수 또는 k 값을 입력하세요. 예: 512, 1k, 32k',
numPrompts: '프롬프트 수',
concurrency: '동시성',
mean: '평균값',
median: '중앙값',
requestRate: '요청 속도',
requestRateUnlimited: '무제한(최대 처리량)',
requestRateCustom: '사용자 지정 QPS',
requestRateCustomPlaceholder: '초당 요청 수, 예: 2.5',
successfulRequests: '성공한 요청',
requestThroughput: '요청 처리량',
timeout: '시간 초과(초)',
image: 'vLLM 이미지',
ignoreEos: '종료 토큰(EOS) 무시',
ignoreEosHelp:
'EOS로 인한 조기 종료를 방지하여 설정한 출력 길이로 테스트하도록 돕습니다. 다른 종료 조건은 계속 적용되며 서버 지원이 필요합니다.',
ignoreEos: 'EOS 무시',
extraHeaders: '추가 헤더',
},
vllmMonitor: {
concurrency: '현재 동시 요청',
sampleMoment: '샘플링 시점',
sampleWindow: '집계 구간',
requestUnit: '개 요청',
processing: '처리 중',
queued: '대기 중',
requestTiming: '요청 소요 시간',
completedRequests: '구간 내 완료 요청 수',
tokensPerSecond: 'tokens/초',
requestsPerSecond: '요청/초',
finishReasonLabels: {
@@ -1700,150 +1686,6 @@ const message = {
modelType: '모델 유형',
},
},
benchmarkPlan: {
prepareTimeoutHelp: '준비 시간 제한이며 전체 시간 제한에서 제외됩니다.',
pointTimeoutHelp: '측정점별로 시작, 워밍업 및 모든 요청 시간을 측정하며, 남은 전체 시간의 제한도 받습니다.',
totalTimeoutHelp: '준비 후 시작하며 모든 측정점과 대기 시간을 포함합니다. 시간 초과 시 테스트를 중단합니다.',
concurrencyLevels: '동시 실행 단계',
concurrencyHelp: '각 단계를 순서대로 테스트합니다. 각 값은 동시에 진행 중인 요청 수의 상한입니다.',
requestCountMode: '요청 수 설정',
fixedRequestMode: '단계별 고정 요청 수',
scaledRequestMode: '동시 실행 수로 계산',
requestsPerPoint: '단계별 요청 수',
requestCountFormula:
'단계별: 고정값 또는 max(동시 실행 수 × 배수, 최소 요청 수). 시나리오 총량: 모든 단계의 합계 × 반복 횟수 (워밍업 제외).',
controlLog: '준비 및 제어 로그',
executionLog: '실행 로그',
pointLog: '측정 지점 #{0} · {1} · 실행 로그',
archivedLog: '{0} (이전 로그 {1})',
tokenLengths: '입력 / 출력 토큰',
kind: '결과 분류',
kindServe: 'serve (일반 테스트)',
kindPD: 'pd (Prefill/Decode 측정)',
saveDetailed: '요청 상세 정보 저장',
saveDetailedHelp:
'각 요청의 길이, 지연 시간, 생성 텍스트 및 오류를 저장합니다. 비활성화해도 요약 지표는 유지되며 결과 파일 크기를 줄일 수 있습니다.',
parameters: '매개변수',
temperatureHelp:
'생성 내용의 무작위성을 제어합니다. 0은 일반적으로 더 안정적인 출력을 제공하며 값이 높을수록 무작위성이 커집니다.',
pointTimeout: '측정점별 제한 시간(초)',
temperature: '샘플링 온도',
requestUrl: 'API 주소',
invalidRequestUrl:
'/chat/completions 또는 /completions로 끝나는 전체 HTTP(S) 주소를 입력하세요. 인증 정보, 쿼리 매개변수, 프래그먼트는 사용할 수 없습니다.',
requestRateHelp: '현재 시나리오에만 적용됩니다. 무제한 속도도 동시 실행 제한을 따르며 소수 QPS를 지원합니다.',
tokenizerModeHelp:
'사용 가능한 모드는 벤치마크 이미지의 vLLM 버전에 따라 다릅니다. 작업 준비 중 토크나이저 로드를 확인합니다.',
invalidBaseUrl: '인증 정보, 쿼리, 프래그먼트가 없는 유효한 HTTP(S) URL을 입력하세요.',
invalidEndpoint:
'선택한 프로토콜에 맞고 {0}(으)로 끝나는 경로를 입력하세요. 쿼리 매개변수나 프래그먼트는 제외하세요.',
invalidTokenizer: '절대 디렉터리 경로나 저장소 ID를 입력하세요. 상대 경로, 쉼표, 줄바꿈은 지원하지 않습니다.',
invalidRequestRate: '무제한을 선택하거나 0보다 큰 유한한 수를 입력하세요. 소수도 지원합니다.',
invalidCaCert: '기본 노드에 있는 CA 인증서의 절대 파일 경로를 입력하세요. 쉼표나 줄바꿈은 사용할 수 없습니다.',
bodyOverride: '추가 본문에서 {0}을 덮어쓸 수 없습니다.',
invalidBody: '유효한 JSON 객체를 입력하세요.',
invalidConcurrencies: '동시 실행 값은 1~100개이며 각 값은 1~100000의 정수여야 합니다.',
emptyRequestCount: '동시 실행 수로 계산할 때 배수와 최소 요청 수를 모두 0으로 설정할 수 없습니다.',
requestCountLimit: '측정점별 계산된 요청 수는 1000000을 초과할 수 없습니다.',
contextExceeded: '입력·출력 토큰과 템플릿 예약량의 합이 컨텍스트 한도를 초과합니다.',
planLimit: '시나리오는 1~100개, 전체 측정점은 2000개 이하여야 합니다.',
caCertHelp:
'HTTPS 서비스를 검증할 CA 인증서 파일을 주 서버에서 선택하세요. 비워 두면 시스템 인증서를 사용합니다.',
extraBody: '추가 요청 본문',
entryKey: '매개변수 이름',
entryValue: '매개변수 값',
tokenizerMode: '토크나이저 모드',
requireIdle: '시작 전 서비스 유휴 상태 확인',
requireIdleHelp:
'워밍업과 부하 테스트 전에 /metrics를 한 번 확인합니다. 서비스가 사용 중이거나 유휴 상태를 확인할 수 없으면 중지합니다. 테스트 중 다른 트래픽을 차단하지는 않습니다.',
trustRemoteCode: '원격 코드 신뢰',
trustRemoteCodeHelp:
'모델 저장소의 사용자 정의 토크나이저 코드를 벤치마크 컨테이너에서 실행하도록 허용합니다. 필요하고 출처를 신뢰할 때만 켜세요.',
tokenizerModes: { auto: '자동 (권장)', slow: '느린 토크나이저', mistral: 'Mistral 전용' },
quickDescription: '단일 요청으로 연결과 측정 과정을 확인합니다.',
regularDescription: '일상적인 성능 테스트를 위한 짧은 입력, 중간 입력, 긴 입력 부하입니다.',
fullDescription: '긴 입력과 공유 접두사를 포함하여 여러 시나리오와 동시 요청 수로 반복 측정합니다.',
customDescription: '시나리오 하나로 시작하여 매개변수를 설정하고 필요에 따라 추가합니다.',
scenarios: '시나리오',
builtIn: '기본 제공',
builtInHelp: '기본 제공 계획은 수정하거나 삭제할 수 없습니다. 테스트 생성 시 복사본을 조정하세요.',
modified: '수정됨',
imageExists: '로컬에 있음',
imageNeedsPull: '다운로드 필요',
remark: '비고',
source: '로컬 서비스',
mode: '테스트 계획',
quick: '빠른 확인',
regular: '일반',
full: '전체',
custom: '사용자 지정',
addScenario: '시나리오 추가',
advanced: '고급 설정',
points: '측정점',
warmups: '준비 요청 수',
fixedRequests: '고정 요청 수',
multiplier: '동시 실행 수당 요청 배수',
minRequests: '최소 요청 수',
trials: '반복 횟수',
prefixRatio: '공유 접두사 비율',
totalTimeout: '전체 제한 시간 (초)',
prepareTimeout: '준비 제한 시간 (초)',
interval: '측정 간격 (초)',
context: '컨텍스트 한도 (0: 알 수 없음)',
reserve: '템플릿 예약 토큰',
storage: '저장 한도 (MiB)',
stopOnError: '오류 발생 시 후속 측정 중지',
caCert: 'CA 인증서 경로',
validTrials: '유효 / 전체 횟수',
retry: '선택한 측정점 재시도',
environment: '실행 환경',
plan: '고정 설정',
clientHelp:
'클라이언트 컨테이너는 주 노드에서 기존 서비스에 요청을 보냅니다. 추론 서비스를 시작하거나 GPU를 할당하지 않습니다.',
retryHelp: '선택한 비정상 측정점만 같은 매개변수와 새 시드로 재시도합니다.',
secretHelp: '복사본에는 API 키와 헤더가 없습니다. 필요한 경우 다시 입력하세요.',
resultHelp:
'요청 이상이 없는 성공한 실행만 중앙값과 최솟값/최댓값에 포함합니다. P99는 각 실행 P99의 중앙값입니다. 캐시와 외부 트래픽은 통제되지 않습니다.',
},
benchmarkOverview: {
noResults: '유효한 테스트 결과가 없습니다. 실패 원인과 로그를 확인하세요.',
noGroupResults: '이 조합에 유효한 결과가 없습니다. 시나리오 또는 동시 실행 수를 변경하세요.',
partial: '부분 결과: 테스트가 완전히 성공하지 않았습니다. 유효한 측정점만 반영합니다.',
highlights: '주요 지표',
aggregate: '현재 조합의 유효한 실행 중앙값입니다. 지표를 클릭하면 상세 통계를 볼 수 있습니다.',
trend: '동시 실행 수별 추세',
std: '표준 편차',
min: '최솟값',
max: '최댓값',
tpot: '출력 토큰당 시간',
tpotHelp: '첫 토큰 이후 출력 토큰 하나를 생성하는 평균 시간입니다.',
itl: '토큰 간 지연',
itlHelp: '스트리밍 응답에서 연속 토큰 사이의 시간입니다.',
e2el: '종단 간 지연',
e2elHelp: '요청을 전송한 시점부터 전체 응답 수신 완료까지의 시간입니다.',
requestHelp: '초당 완료된 요청 수입니다.',
usageHelp:
'현재 조합의 유효한 실행 중앙값이며 전체 테스트의 요청 합계가 아닙니다. 비정상 실행은 측정점 탭에서 확인하세요.',
duration: '실행 시간',
throughput: '처리량',
latency: '지연',
usage: '요청 및 사용량(유효한 실행)',
},
benchmarkTokenizer: {
title: '토크나이저 관리',
local: '로컬 디렉터리 등록',
upload: '토크나이저 패키지 업로드',
uploadHelp:
'모델 가중치가 없는 ZIP / tar.gz 파일을 업로드하세요. 압축 파일은 128 MiB, 압축 해제 후에는 256 MiB까지 가능합니다.',
source: '가져오기 방식',
deleteLocal: '토크나이저 “{0}”의 등록을 삭제할까요? 원본 디렉터리와 파일은 유지됩니다.',
deleteUpload: '토크나이저 “{0}” 및 업로드한 파일을 삭제할까요? 기존 테스트 보고서는 유지됩니다.',
select: '토크나이저 선택',
selectHelp:
'오프라인 환경에서는 관리 목록에서 선택하세요. 사용자 지정 입력은 로컬 디렉터리나 저장소 이름도 지원합니다.',
},
container: {
createByCommand: '명령으로 생성',
importContainerBackupTip: '컨테이너 백업 파일 가져오기, .tar.gz 형식만 지원합니다',
@@ -3056,20 +2898,6 @@ const message = {
remote: '원격 SSH',
},
setting: {
panelCache: {
title: '캐시 설정',
tabs: '탭 캐시',
pagination: '페이지 이동 캐시',
refresh: '새로 고침 간격 캐시',
search: '검색 입력 캐시',
duration: '캐시 유지 기간',
'24h': '24시간',
week: '1주',
month: '1개월',
forever: '무기한',
helper: '유효 기간은 상태를 마지막으로 변경한 시점부터 계산됩니다. 만료되면 기본값으로 돌아갑니다.',
},
panel: '패널',
user: '패널 사용자',
userChange: '패널 사용자 변경',
@@ -3777,7 +3605,6 @@ const message = {
link: '링크',
showCurrentArch: '현재 서버 아키텍처 애플리케이션',
syncLocalApp: '로컬 애플리케이션 동기화',
localAppSyncToNodes: '로컬 앱 동기화 시 하위 노드도 동기화',
memoryRequiredHelper: '현재 애플리케이션은 {0} 메모리가 필요합니다',
gpuConfig: 'GPU 지원 활성화',
gpuConfigHelper:
@@ -4324,8 +4151,7 @@ const message = {
domainHelper: '줄당 하나의 도메인 이름, * 및 IP 주소 지원',
pushDir: '로컬 디렉토리로 인증서 푸시',
dir: '디렉토리',
pushDirHelper:
'여러 디렉터리를 선택하거나 한 줄에 하나씩 입력하세요. 각 디렉터리에 "fullchain.pem"과 "privkey.pem" 파일이 생성됩니다.',
pushDirHelper: '인증서 파일 "fullchain.pem" 및 키 파일 "privkey.pem"이 이 디렉토리에 생성됩니다.',
organizationDetail: '조직 세부 정보',
fromWebsite: '웹사이트에서 가져오기',
dnsMauanlHelper: '수동 해석 모드에서는 생성 후 신청 버튼을 클릭하여 DNS 해석 값을 얻어야 합니다.',
@@ -4378,12 +4204,9 @@ const message = {
familyIncomplete: '{0} 체인이 불완전합니다',
resetWithBackup: '규칙 백업 후 초기화',
resetOnly: '초기화만 수행',
resetWithBackupHelper: '현재 모듈의 규칙을 JSON으로 저장하여 초기화 또는 시작 시 복원할 수 있습니다.',
resetWithBackupHelper: '현재 모듈의 규칙을 JSON으로 저장하여 초기화 시 복원할 수 있습니다.',
resetOnlyHelper: '새 백업 없이 현재 규칙을 삭제합니다. 기존 백업은 유지됩니다.',
backupDirectoryHelper: '{0}의 유효한 규칙 백업을 표시합니다. 기본적으로 최신 파일이 선택됩니다.',
startOnly: '시작만',
startAndRestore: '시작 및 백업에서 복원',
startWithBackupHelper: '방화벽을 일반적으로 시작하거나 기존 JSON 백업을 선택하여 시작하고 규칙을 복원합니다.',
restoreAllRules: '초기화 및 규칙 복원',
importRuleBackup: '백업에서 가져오기',
initializeOnly: '초기화만 수행',
+10 -184
View File
@@ -487,7 +487,6 @@ const message = {
recommend: 'ແນະນຳ',
dir: 'ໄດເຣັກທໍຣີ',
alias: 'ຊື່ຫຼິ້ນ',
quickDirDelete: 'ຢືນຢັນການລຶບ ({0}) ຫຼືບໍ່?',
quickDir: 'ເຂົ້າເຖິງດ່ວນ',
minQuickJump: 'ເພີ່ມການເຂົ້າເຖິງດ່ວນຢ່າງໜ້ອຍໜຶ່ງລາຍການ',
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດສີ່ລາຍການ',
@@ -762,15 +761,6 @@ const message = {
},
aiTools: {
agents: {
deepSeekAccessHostHelper:
'ປ້ອນທີ່ຢູ່ IPv4 ຫຼືຊື່ໂຮສທີ່ໃຊ້ໃນບຣາວເຊີ ໂດຍບໍ່ມີໂປຣໂຕຄອນ, ພອດ ຫຼືເສັ້ນທາງ. HTTPS ໃຊ້ໃບຢັ້ງຢືນພາຍໃນຂອງແອັບ.',
deepSeekAuthHelper:
'ຊື່ຜູ້ໃຊ້: ຕົວອັກສອນອັງກິດ, ຕົວເລກ, ຈຸດ, ຂີດລຸ່ມ ຫຼືຂີດກາງ. ລະຫັດຜ່ານ: ຢ່າງໜ້ອຍ 12 ຕົວ ໂດຍບໍ່ມີຊ່ອງຫວ່າງຢູ່ຫົວ/ທ້າຍ ຫຼືຂຶ້ນແຖວໃໝ່.',
octopAuthHelper:
'ລະຫັດຜ່ານ: ຢ່າງໜ້ອຍ 8 ຕົວ ປະກອບດ້ວຍຕົວອັກສອນແລະຕົວເລກ; ຫຼີກລ້ຽງລະຫັດທີ່ໃຊ້ທົ່ວໄປ. ຖ້າຖືກປະຕິເສດ ໃຫ້ເບິ່ງຄຳແນະນຳໃນບັນທຶກຂອງແອັບ.',
initialCredentials: 'ຂໍ້ມູນເຂົ້າລະບົບເບື້ອງຕົ້ນ; ຖ້າປ່ຽນໃນແອັບແລ້ວ ໃຫ້ໃຊ້ລະຫັດປັດຈຸບັນ.',
nativeAgentAccessUnavailable:
'ການຕິດຕັ້ງທີ່ມີຢູ່ບໍ່ມີທີ່ຢູ່ ຫຼືພອດ HTTPS ທີ່ເຂົ້າກັນໄດ້. ກະລຸນາກວດສອບການຕັ້ງຄ່າແອັບ.',
agent: 'ຕົວແທນ (Agent)',
account: 'ບັນຊີໂມເດວ',
batchOperation: 'ດຳເນີນການແບບກຸ່ມ',
@@ -1461,43 +1451,39 @@ const message = {
statusWaiting: 'ກຳລັງລໍຖ້າ',
base: 'ຂໍ້ມູນພື້ນຖານ',
backend: 'Backend',
baseUrl: 'Base URL',
endpoint: 'Endpoint',
tokenizer: 'Tokenizer',
tokenizerPlaceholder: 'ປ້ອນເສັ້ນທາງໂຟນເດີແບບສົມບູນໃນໂນດຫຼັກ ຫຼື ID ຄັງ Hugging Face',
tokenizerPlaceholder:
'ເລືອກ ຫຼື ປ້ອນໂຟນເດີ tokenizer ພາຍໃນເຄື່ອງ, ເຊັ່ນ: /opt/1panel/tokenizers/DeepSeek-V3',
runConfig: 'ການກຳນົດຄ່າການລັນ',
rawResult: 'ຜົນລັບດິບ (Raw Result)',
resultMetrics: 'ຕົວຊີ້ວັດຜົນລັບ',
contextTokens: 'ຄວາມຍາວ Context',
contextTokensHelper: 'ຂີດຈຳກັດ input token ບວກກັບ output token ສຳລັບການທົດສອບນີ້.',
outputThroughput: 'ອັດຕາການສົ່ງອອກ',
outputThroughputHelper: 'ຈຳນວນ output tokens ທີ່ສ້າງໄດ້ຕໍ່ວິນາທີ. ຍິ່ງສູງຍິ່ງໄວ.',
totalThroughput: 'ອັດຕາການປະມວນຜົນລວມ',
totalThroughputHelper: 'ຈຳນວນ input ແລະ output tokens ທີ່ປະມວນຜົນຕໍ່ວິນາທີ. ໃຊ້ວັດແທກຄວາມສາມາດໂດຍລວມ.',
firstTokenLatency: 'ຄວາມໜ່ວງຂອງ Token ທຳອິດ',
firstTokenLatencyHelper: 'ເວລາຕັ້ງແຕ່ສົ່ງຄຳຂໍຈົນເຖິງການໄດ້ຮັບ token ທຳອິດ. ຍິ່ງຕ່ຳຍິ່ງຕອບສະໜອງໄວ.',
tokenValueHelper: 'ປ້ອນຕົວເລກຈຳນວນເຕັມ ຫຼື ຄ່າ k, ເຊັ່ນ: 512, 1k, 32k',
numPrompts: 'ຈຳນວນ Prompt',
concurrency: 'ການເຮັດວຽກພ້ອມກັນ',
requestRate: 'ອັດຕາຄຳຂໍ',
requestRateUnlimited: 'ບໍ່ຈຳກັດ (max throughput)',
requestRateCustom: 'ກຳນົດ QPS ເອງ',
requestRateCustomPlaceholder: 'ຄຳຂໍຕໍ່ວິນາທີ, ເຊັ່ນ: 2.5',
successfulRequests: 'ຄຳຂໍທີ່ສຳເລັດ',
requestThroughput: 'ອັດຕາຄຳຮ້ອງຂໍ',
mean: 'ຄ່າສະເລ່ຍ',
median: 'ຄ່າມັດທະຍະຖານ',
timeout: 'ໝົດເວລາ (ວິນາທີ)',
image: 'vLLM Image',
ignoreEos: 'ລະເວັ້ນໂທເຄັນສິ້ນສຸດ (EOS)',
ignoreEosHelp:
'ປ້ອງກັນການຢຸດກ່ອນກຳນົດຍ້ອນ EOS ເພື່ອທົດສອບຄວາມຍາວຜົນລັບທີ່ຕັ້ງໄວ້. ເງື່ອນໄຂຢຸດອື່ນຍັງມີຜົນ ແລະຕ້ອງມີການຮອງຮັບຈາກເຊີບເວີ.',
ignoreEos: 'ລະເວັ້ນ EOS',
extraHeaders: 'Headers ເພີ່ມເຕີມ',
},
vllmMonitor: {
concurrency: 'ຄຳຮ້ອງຂໍພ້ອມກັນໃນປັດຈຸບັນ',
sampleMoment: 'ໃນເວລາເກັບຕົວຢ່າງ',
sampleWindow: 'ຊ່ວງເວລາເກັບຕົວຢ່າງ',
requestUnit: 'ຄຳຮ້ອງຂໍ',
processing: 'ກຳລັງປະມວນຜົນ',
queued: 'ກຳລັງລໍຖ້າ',
requestTiming: 'ເວລາປະມວນຜົນຄຳຮ້ອງຂໍ',
completedRequests: 'ຈຳນວນຄຳຮ້ອງຂໍທີ່ສຳເລັດໃນຊ່ວງເວລາ',
tokensPerSecond: 'tokens/ວິນາທີ',
requestsPerSecond: 'ຄຳຮ້ອງຂໍ/ວິນາທີ',
finishReasonLabels: {
@@ -1691,147 +1677,6 @@ const message = {
modelType: 'ປະເພດໂມເດວ',
},
},
benchmarkPlan: {
prepareTimeoutHelp: 'ເວລາຈຳກັດການກຽມການ ບໍ່ນັບໃນເວລາທັງຮອບ.',
pointTimeoutHelp: 'ຈັບເວລາແຍກຕໍ່ຈຸດ ລວມການເລີ່ມ, ອຸ່ນເຄື່ອງ ແລະ ທຸກຄຳຮ້ອງຂໍ; ຈຳກັດດ້ວຍເວລາທັງຮອບທີ່ເຫຼືອ.',
totalTimeoutHelp: 'ເລີ່ມຫຼັງກຽມການ ລວມທຸກຈຸດແລະໄລຍະຫ່າງ. ຢຸດທົດສອບເມື່ອໝົດເວລາ.',
concurrencyLevels: 'ລະດັບການເຮັດວຽກພ້ອມກັນ',
concurrencyHelp: 'ທົດສອບແຕ່ລະລະດັບຕາມລຳດັບ. ແຕ່ລະຄ່າກຳນົດຈຳນວນຄຳຮ້ອງຂໍສູງສຸດທີ່ດຳເນີນພ້ອມກັນ.',
requestCountMode: 'ຕັ້ງຄ່າຈຳນວນຄຳຮ້ອງຂໍ',
fixedRequestMode: 'ຈຳນວນຄົງທີ່ຕໍ່ລະດັບ',
scaledRequestMode: 'ຄຳນວນຕາມຈຳນວນທີ່ເຮັດວຽກພ້ອມກັນ',
requestsPerPoint: 'ຄຳຮ້ອງຂໍຕໍ່ລະດັບ',
requestCountFormula:
'ຕໍ່ລະດັບ: ຄ່າຄົງທີ່ ຫຼື max(ຈຳນວນພ້ອມກັນ × ຕົວຄູນ, ຄຳຮ້ອງຂໍຂັ້ນຕ່ຳ). ລວມຕໍ່ສະຖານະການ: ຜົນລວມທຸກລະດັບ × ຈຳນວນຮອບຊ້ຳ, ບໍ່ລວມອຸ່ນເຄື່ອງ.',
controlLog: 'ບັນທຶກການກະກຽມ ແລະ ຄວບຄຸມ',
executionLog: 'ບັນທຶກການດຳເນີນງານ',
pointLog: 'ຈຸດທົດສອບ #{0} · {1} · ບັນທຶກການດຳເນີນງານ',
archivedLog: '{0} (ປະຫວັດ {1})',
tokenLengths: 'ໂທເຄັນຂາເຂົ້າ / ຂາອອກ',
kind: 'ໝວດໝູ່ຜົນລັບ',
kindServe: 'serve (ການທົດສອບທົ່ວໄປ)',
kindPD: 'pd (ການວັດແທກ Prefill/Decode)',
saveDetailed: 'ບັນທຶກລາຍລະອຽດຄຳຮ້ອງ',
saveDetailedHelp:
'ບັນທຶກຄວາມຍາວ, ຄວາມລ່າຊ້າ, ຂໍ້ຄວາມທີ່ສ້າງ ແລະຂໍ້ຜິດພາດຂອງແຕ່ລະຄຳຮ້ອງ. ເມື່ອປິດ ຍັງຮັກສາຄ່າສະຫຼຸບໄວ້ ແລະຫຼຸດຂະໜາດໄຟລ໌ຜົນລັບ.',
parameters: 'ພາລາມິເຕີ',
temperatureHelp: 'ຄວບຄຸມຄວາມສຸ່ມຂອງຜົນລັບ. ຄ່າ 0 ມັກໃຫ້ຜົນລັບທີ່ຄົງທີ່ກວ່າ; ຄ່າສູງຂຶ້ນຈະເພີ່ມຄວາມສຸ່ມ.',
pointTimeout: 'ເວລາຈຳກັດຕໍ່ຈຸດ (ວິນາທີ)',
temperature: 'ອຸນຫະພູມການສຸ່ມ',
requestUrl: 'ທີ່ຢູ່ API',
invalidRequestUrl:
'ປ້ອນທີ່ຢູ່ HTTP(S) ເຕັມທີ່ລົງທ້າຍດ້ວຍ /chat/completions ຫຼື /completions ໂດຍບໍ່ມີຂໍ້ມູນເຂົ້າລະບົບ, ພາລາມິເຕີສອບຖາມ ຫຼື ສ່ວນຫຼັງ #.',
requestRateHelp:
'ໃຊ້ກັບສະຖານະການນີ້ເທົ່ານັ້ນ. ອັດຕາບໍ່ຈຳກັດຍັງຢູ່ພາຍໃຕ້ຂີດຈຳກັດພ້ອມກັນ; ຮອງຮັບ QPS ທົດສະນິຍົມ.',
tokenizerModeHelp:
'ໂໝດທີ່ໃຊ້ໄດ້ຂຶ້ນກັບເວີຊັນ vLLM ໃນອິມເມຈທົດສອບ. ການໂຫຼດຕົວແຍກໂທເຄັນຈະຖືກກວດສອບໃນຂັ້ນຕອນກະກຽມວຽກ.',
invalidBaseUrl: 'ປ້ອນ URL HTTP(S) ທີ່ຖືກຕ້ອງ ໂດຍບໍ່ມີຂໍ້ມູນເຂົ້າລະບົບ, ຄຳຖາມ ຫຼືສ່ວນຍ່ອຍ.',
invalidEndpoint:
'ປ້ອນເສັ້ນທາງທີ່ກົງກັບໂປຣໂຕຄໍທີ່ເລືອກ ແລະລົງທ້າຍດ້ວຍ {0} ໂດຍບໍ່ມີພາລາມິເຕີຄຳຖາມ ຫຼືສ່ວນຍ່ອຍຂອງ URL.',
invalidTokenizer: 'ໃຊ້ເສັ້ນທາງໂຟນເດີແບບເຕັມ ຫຼື ID ຄັງເກັບ; ບໍ່ຮອງຮັບເສັ້ນທາງສຳພັນ, ຈຸດຈ້ຳ ຫຼືຂຶ້ນແຖວໃໝ່.',
invalidRequestRate: 'ເລືອກບໍ່ຈຳກັດ ຫຼືປ້ອນຈຳນວນຈຳກັດທີ່ໃຫຍ່ກວ່າ 0; ຮອງຮັບທົດສະນິຍົມ.',
invalidCaCert: 'ປ້ອນເສັ້ນທາງເຕັມຂອງໄຟລ໌ CA ໃນໂນດຫຼັກ ໂດຍບໍ່ມີຈຸດຈ້ຳ ຫຼືຂຶ້ນແຖວໃໝ່.',
bodyOverride: 'ເນື້ອຫາເພີ່ມບໍ່ສາມາດຂຽນທັບ {0}.',
invalidBody: 'ປ້ອນວັດຖຸ JSON ທີ່ຖືກຕ້ອງ.',
invalidConcurrencies: 'ປ້ອນຄ່າພ້ອມກັນ 1–100 ລາຍການ, ແຕ່ລະຄ່າເປັນຈຳນວນເຕັມ 1–100000.',
emptyRequestCount: 'ເມື່ອຄຳນວນຕາມຈຳນວນທີ່ເຮັດວຽກພ້ອມກັນ, ຕົວຄູນແລະຈຳນວນຄຳຮ້ອງຂໍຂັ້ນຕ່ຳບໍ່ສາມາດເປັນ 0 ພ້ອມກັນ.',
requestCountLimit: 'ຄຳຮ້ອງຂໍທີ່ຄຳນວນຕໍ່ຈຸດຕ້ອງບໍ່ເກີນ 1000000.',
contextExceeded: 'ຜົນລວມໂທເຄັນຂາເຂົ້າ, ຂາອອກ ແລະສຳຮອງແມ່ແບບເກີນຂີດຈຳກັດບໍລິບົດ.',
planLimit: 'ຕ້ອງມີ 1–100 ສະຖານະການ ແລະລວມບໍ່ເກີນ 2000 ຈຸດ.',
caCertHelp: 'ເລືອກໄຟລ໌ໃບຢັ້ງຢືນ CA ໃນເຊີບເວີຫຼັກເພື່ອກວດສອບ HTTPS. ປະວ່າງເພື່ອໃຊ້ໃບຢັ້ງຢືນຂອງລະບົບ.',
extraBody: 'ເນື້ອຫາຄຳຮ້ອງຂໍເພີ່ມເຕີມ',
entryKey: 'ຊື່ພາຣາມິເຕີ',
entryValue: 'ຄ່າພາຣາມິເຕີ',
tokenizerMode: 'ໂໝດຕົວແບ່ງໂທເຄັນ',
requireIdle: 'ກວດສອບວ່າບໍລິການວ່າງກ່ອນເລີ່ມ',
requireIdleHelp:
'ກວດສອບ /metrics ໜຶ່ງຄັ້ງກ່ອນອຸ່ນເຄື່ອງ ແລະ ທົດສອບໂຫຼດ. ຢຸດຖ້າບໍລິການບໍ່ວ່າງ ຫຼື ບໍ່ສາມາດຢືນຢັນໄດ້ວ່າວ່າງ. ການກວດສອບນີ້ບໍ່ປ້ອງກັນການຮ້ອງຂໍອື່ນໃນລະຫວ່າງທົດສອບ.',
trustRemoteCode: 'ເຊື່ອຖືລະຫັດທາງໄກ',
trustRemoteCodeHelp:
'ອະນຸຍາດໃຫ້ແລ່ນລະຫັດຕົວແບ່ງໂທເຄັນທີ່ກຳນົດເອງຈາກຄັງໂມເດວໃນຄອນເທນເນີທົດສອບ. ເປີດເມື່ອຈຳເປັນແລະແຫຼ່ງທີ່ມາເຊື່ອຖືໄດ້ເທົ່ານັ້ນ.',
tokenizerModes: { auto: 'ອັດຕະໂນມັດ (ແນະນຳ)', slow: 'ຕົວແບ່ງໂທເຄັນແບບຊ້າ', mistral: 'ຕົວແບ່ງໂທເຄັນ Mistral' },
quickDescription: 'ກວດສອບການເຊື່ອມຕໍ່ແລະການວັດແທກດ້ວຍຄຳຮ້ອງຂໍດຽວ.',
regularDescription: 'ພາລະງານສັ້ນ, ປານກາງ ແລະຍາວ ສຳລັບທົດສອບປະສິດທິພາບປະຈຳວັນ.',
fullDescription:
'ຫຼາຍສະຖານະການ, ຫຼາຍລະດັບຄຳຮ້ອງຂໍພ້ອມກັນ ແລະການວັດແທກຊ້ຳ ລວມທັງຂໍ້ມູນເຂົ້າຍາວແລະຄຳນຳໜ້າຮ່ວມກັນ.',
customDescription: 'ເລີ່ມຈາກໜຶ່ງສະຖານະການ, ຕັ້ງຄ່າພາລາມິເຕີ ແລະເພີ່ມຕາມຄວາມຕ້ອງການ.',
scenarios: 'ສະຖານະການ',
builtIn: 'ມາພ້ອມລະບົບ',
builtInHelp: 'ແຜນທີ່ມາພ້ອມລະບົບແກ້ໄຂຫຼືລຶບບໍ່ໄດ້. ປັບສຳເນົາໃນເວລາສ້າງການທົດສອບ.',
modified: 'ປັບແລ້ວ',
imageExists: 'ມີຢູ່ໃນເຄື່ອງແລ້ວ',
imageNeedsPull: 'ຕ້ອງດາວໂຫຼດ',
remark: 'ໝາຍເຫດ',
source: 'ບໍລິການພາຍໃນເຄື່ອງ',
mode: 'ແຜນທົດສອບ',
quick: 'ກວດສອບດ່ວນ',
regular: 'ປົກກະຕິ',
full: 'ເຕັມຮູບແບບ',
custom: 'ກຳນົດເອງ',
addScenario: 'ເພີ່ມສະຖານະການ',
advanced: 'ການຕັ້ງຄ່າຂັ້ນສູງ',
points: 'ຈຸດວັດແທກ',
warmups: 'ຄຳຮ້ອງຂໍອຸ່ນເຄື່ອງ',
fixedRequests: 'ຄຳຮ້ອງຂໍຄົງທີ່',
multiplier: 'ຕົວຄູນຄຳຮ້ອງຂໍຕໍ່ການເຮັດວຽກພ້ອມກັນ',
minRequests: 'ຄຳຮ້ອງຂໍຕ່ຳສຸດ',
trials: 'ຈຳນວນຊ້ຳ',
prefixRatio: 'ອັດຕາສ່ວນຄຳນຳໜ້າຮ່ວມ',
totalTimeout: 'ເວລາຈຳກັດຮອບ (ວິນາທີ)',
prepareTimeout: 'ເວລາຈຳກັດກະກຽມ (ວິນາທີ)',
interval: 'ໄລຍະຫ່າງຈຸດ (ວິນາທີ)',
context: 'ຂີດຈຳກັດບໍລິບົດ (0: ບໍ່ຮູ້)',
reserve: 'ໂທເຄນສຳຮອງແມ່ແບບ',
storage: 'ຂີດຈຳກັດບ່ອນເກັບ (MiB)',
stopOnError: 'ຢຸດຈຸດຕໍ່ໄປເມື່ອຜິດພາດ',
caCert: 'ເສັ້ນທາງໃບຢັ້ງຢືນ CA',
validTrials: 'ຖືກຕ້ອງ / ທັງໝົດ',
retry: 'ທົດສອບຈຸດທີ່ເລືອກຄືນ',
environment: 'ສະພາບແວດລ້ອມ',
plan: 'ການຕັ້ງຄ່າຄົງທີ່',
clientHelp:
'ຄອນເທນເນີລູກຂ່າຍເຮັດວຽກຢູ່ໂນດຫຼັກ ແລະ ສົ່ງຄຳຮ້ອງຂໍໄປບໍລິການທີ່ມີແລ້ວ. ບໍ່ເລີ່ມບໍລິການອະນຸມານ ແລະ ບໍ່ຕ້ອງການ GPU.',
retryHelp: 'ທົດສອບຄືນສະເພາະຈຸດຜິດປົກກະຕິທີ່ເລືອກ ດ້ວຍພາລາມິເຕີເດີມ ແລະ ຄ່າສຸ່ມໃໝ່.',
secretHelp: 'ສຳເນົາບໍ່ມີລະຫັດ API ແລະ ສ່ວນຫົວ. ກະລຸນາປ້ອນໃໝ່ຖ້າຈຳເປັນ.',
resultHelp:
'ຄ່າກາງ ແລະ ຄ່າຕ່ຳສຸດ/ສູງສຸດໃຊ້ສະເພາະການທົດສອບສຳເລັດທີ່ບໍ່ມີຄຳຮ້ອງຂໍຜິດປົກກະຕິ. P99 ແມ່ນຄ່າກາງຂອງ P99 ແຕ່ລະຄັ້ງ. ແຄຊ ແລະ ການຈະລາຈອນພາຍນອກບໍ່ໄດ້ຄວບຄຸມ.',
},
benchmarkOverview: {
noResults: 'ບໍ່ມີຜົນການທົດສອບທີ່ໃຊ້ໄດ້. ກວດສອບສາເຫດ ແລະ ບັນທຶກ.',
noGroupResults: 'ບໍ່ມີຜົນທີ່ໃຊ້ໄດ້ສຳລັບຊຸດນີ້. ເລືອກສະຖານະການ ຫຼື ຈຳນວນພ້ອມກັນອື່ນ.',
partial: 'ຜົນບາງສ່ວນ: ການທົດສອບບໍ່ສຳເລັດທັງໝົດ. ຕົວຊີ້ວັດລວມສະເພາະຈຸດທີ່ໃຊ້ໄດ້.',
highlights: 'ຕົວຊີ້ວັດຫຼັກ',
aggregate: 'ຄ່າມັດທະຍະຖານຂອງການແລ່ນທີ່ໃຊ້ໄດ້ໃນຊຸດນີ້. ຄລິກຕົວຊີ້ວັດເພື່ອເບິ່ງລາຍລະອຽດ.',
trend: 'ແນວໂນ້ມຕາມຈຳນວນພ້ອມກັນ',
std: 'ສ່ວນບ່ຽງເບນມາດຕະຖານ',
min: 'ຕ່ຳສຸດ',
max: 'ສູງສຸດ',
tpot: 'ເວລາຕໍ່ Token ຂາອອກ',
tpotHelp: 'ເວລາສ້າງສະເລ່ຍຕໍ່ Token ຂາອອກຫຼັງ Token ທຳອິດ.',
itl: 'ຄວາມຊັກຊ້າລະຫວ່າງ Token',
itlHelp: 'ເວລາລະຫວ່າງ Token ຕໍ່ເນື່ອງໃນຄຳຕອບແບບສະຕຣີມ.',
e2el: 'ຄວາມຊັກຊ້າຕັ້ງແຕ່ຕົ້ນຈົນຈົບ',
e2elHelp: 'ເວລາຈາກການສົ່ງຄຳຂໍຈົນໄດ້ຮັບຄຳຕອບຄົບຖ້ວນ.',
requestHelp: 'ຈຳນວນຄຳຂໍທີ່ສຳເລັດຕໍ່ວິນາທີ.',
usageHelp: 'ຄ່າມັດທະຍະຖານຂອງການແລ່ນທີ່ໃຊ້ໄດ້ໃນຊຸດນີ້ ບໍ່ແມ່ນຈຳນວນຄຳຂໍທັງໝົດ. ເບິ່ງການແລ່ນຜິດປົກກະຕິໃນແຖບຈຸດ.',
duration: 'ໄລຍະເວລາການແລ່ນ',
throughput: 'ອັດຕາປະມວນຜົນ',
latency: 'ຄວາມຊັກຊ້າ',
usage: 'ຄຳຂໍ ແລະ ການໃຊ້ງານ (ການແລ່ນທີ່ໃຊ້ໄດ້)',
},
benchmarkTokenizer: {
title: 'ຈັດການຕົວແຍກໂທເຄັນ',
local: 'ລົງທະບຽນໂຟນເດີໃນເຄື່ອງ',
upload: 'ອັບໂຫຼດຊຸດຕົວແຍກໂທເຄັນ',
uploadHelp: 'ອັບໂຫຼດ ZIP / tar.gz ທີ່ບໍ່ມີນ້ຳໜັກໂມເດລ. ສູງສຸດ 128 MiB ກ່ອນແຕກ ແລະ 256 MiB ຫຼັງແຕກ.',
source: 'ວິທີນຳເຂົ້າ',
deleteLocal: 'ລຶບການລົງທະບຽນ «{0}» ບໍ? ໂຟນເດີ ແລະ ໄຟລ໌ຕົ້ນສະບັບຈະຖືກເກັບໄວ້.',
deleteUpload: 'ລຶບ «{0}» ແລະ ໄຟລ໌ທີ່ອັບໂຫຼດບໍ? ລາຍງານທົດສອບເກົ່າຈະຖືກເກັບໄວ້.',
select: 'ເລືອກຕົວແບ່ງໂທເຄັນ',
selectHelp: 'ສຳລັບອອບລາຍ ໃຫ້ເລືອກຈາກລາຍການ. ການປ້ອນເອງຮອງຮັບໂຟນເດີ ຫຼື ຊື່ຄັງ.',
},
container: {
createByCommand: 'ສ້າງດ້ວຍຄຳສັ່ງ',
importContainerBackupTip: 'ນຳເຂົ້າໄຟລ໌ສຳຮອງຄອນເທນເນີ, ຮອງຮັບສະເພາະ .tar.gz',
@@ -3034,20 +2879,6 @@ const message = {
remote: 'SSH ທາງໄກ',
},
setting: {
panelCache: {
title: 'ການຕັ້ງຄ່າແຄດ',
tabs: 'ແຄດແຖບ',
pagination: 'ແຄດການແບ່ງໜ້າ',
refresh: 'ແຄດຊ່ວງເວລາຣີເຟຣຊ',
search: 'ແຄດຂໍ້ຄວາມຄົ້ນຫາ',
duration: 'ໄລຍະເວລາເກັບແຄດ',
'24h': '24 ຊົ່ວໂມງ',
week: 'ໜຶ່ງອາທິດ',
month: 'ໜຶ່ງເດືອນ',
forever: 'ຖາວອນ',
helper: 'ໄລຍະເວລາຈະນັບຈາກການປ່ຽນສະຖານະຄັ້ງຫຼ້າສຸດ. ເມື່ອໝົດອາຍຸ ຈະກັບຄືນເປັນຄ່າເລີ່ມຕົ້ນ.',
},
panel: 'ພາເນລ',
user: 'ຜູ້ໃຊ້ພາເນລ',
userChange: 'ປ່ຽນຜູ້ໃຊ້ພາເນລ',
@@ -3751,7 +3582,6 @@ const message = {
link: 'ລິ້ງ',
showCurrentArch: 'ສະຖາປັດຕະຍະກຳ',
syncLocalApp: 'ຊິງຄ໌ແອັບໃນເຄື່ອງ',
localAppSyncToNodes: 'ຮວມໂນດລູກເມື່ອຊິງຄ໌ແອັບໃນເຄື່ອງ',
memoryRequiredHelper: 'ຄວາມຕ້ອງການໜ່ວຍຄວາມຈຳຂອງແອັບປະຈຸບັນ {0}',
gpuConfig: 'ເປີດໃຊ້ການຮອງຮັບ GPU',
gpuConfigHelper:
@@ -4293,8 +4123,7 @@ const message = {
domainHelper: 'ໜຶ່ງໂດເມນຕໍ່ແຖວ, ຮອງຮັບ * ແລະ ທີ່ຢູ່ IP',
pushDir: 'ສົ່ງໃບຢັ້ງຢືນໄປຍັງໂຟນເດີໃນເຄື່ອງ',
dir: 'ໂຟນເດີ',
pushDirHelper:
'ເລືອກຫຼາຍໂຟນເດີ ຫຼື ປ້ອນໜຶ່ງໂຟນເດີຕໍ່ແຖວ. ໄຟລ໌ "fullchain.pem" ແລະ "privkey.pem" ຈະຖືກສ້າງໃນແຕ່ລະໂຟນເດີ.',
pushDirHelper: 'ໄຟລ໌ "fullchain.pem" ແລະ "privkey.pem" ຈະຖືກສ້າງໃນໂຟນເດີນີ້.',
organizationDetail: 'ລາຍລະອຽດອົງກອນ',
fromWebsite: 'ຈາກເວັບໄຊ',
dnsMauanlHelper: 'ໃນໂໝດຈັດການ DNS ດ້ວຍຕົນເອງ, ທ່ານຕ້ອງຄລິກປຸ່ມ "ຂໍໃບຢັ້ງຢືນ" ຫຼັງຈາກສ້າງເພື່ອຮັບຄ່າ DNS',
@@ -4344,12 +4173,9 @@ const message = {
familyIncomplete: 'ຊຸດກົດ {0} ບໍ່ຄົບຖ້ວນ',
resetWithBackup: 'ສຳຮອງກົດແລ້ວຣີເຊັດ',
resetOnly: 'ຣີເຊັດເທົ່ານັ້ນ',
resetWithBackupHelper: 'ບັນທຶກກົດຂອງໂມດູນປັດຈຸບັນເປັນ JSON ເພື່ອກູ້ຄືນເມື່ອເລີ່ມຕົ້ນ ຫຼື ເປີດໃຊ້ໄຟວໍ.',
resetWithBackupHelper: 'ບັນທຶກກົດຂອງໂມດູນປັດຈຸບັນເປັນ JSON ເພື່ອກູ້ຄືນເມື່ອເລີ່ມຕົ້ນ.',
resetOnlyHelper: 'ລຶບກົດປັດຈຸບັນໂດຍບໍ່ສ້າງໄຟລ໌ສຳຮອງໃໝ່. ໄຟລ໌ສຳຮອງເກົ່າຍັງຄົງຢູ່.',
backupDirectoryHelper: 'ສະແດງໄຟລ໌ສຳຮອງກົດທີ່ໃຊ້ໄດ້ໃນ {0}. ເລືອກໄຟລ໌ຫຼ້າສຸດເປັນຄ່າເລີ່ມຕົ້ນ.',
startOnly: 'ເລີ່ມຕົ້ນເທົ່ານັ້ນ',
startAndRestore: 'ເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນຈາກຂໍ້ມູນສຳຮອງ',
startWithBackupHelper: 'ເປີດໄຟວໍຕາມປົກກະຕິ ຫຼື ເລືອກຂໍ້ມູນສຳຮອງ JSON ທີ່ມີຢູ່ເພື່ອເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນກົດ.',
restoreAllRules: 'ເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນກົດ',
importRuleBackup: 'ນຳເຂົ້າຈາກຂໍ້ມູນສຳຮອງ',
initializeOnly: 'ເລີ່ມຕົ້ນເທົ່ານັ້ນ',
+10 -196
View File
@@ -499,7 +499,6 @@ const message = {
recommend: 'Disyorkan',
dir: 'Direktori',
alias: 'Alias',
quickDirDelete: 'Sahkan pemadaman ({0})?',
quickDir: 'Akses Pantas',
minQuickJump: 'Tambah sekurang-kurangnya satu entri akses pantas',
maxQuickJump: 'Anda boleh menambah sehingga empat entri akses pantas',
@@ -782,15 +781,6 @@ const message = {
},
aiTools: {
agents: {
deepSeekAccessHostHelper:
'Masukkan alamat IPv4 atau nama hos yang digunakan dalam pelayar, tanpa protokol, port atau laluan. HTTPS menggunakan sijil setempat aplikasi.',
deepSeekAuthHelper:
'Nama pengguna: huruf, nombor, titik, garis bawah atau sempang. Kata laluan: sekurang-kurangnya 12 aksara, tanpa ruang di awal/akhir atau baris baharu.',
octopAuthHelper:
'Kata laluan: sekurang-kurangnya 8 aksara dengan huruf dan nombor; elakkan kata laluan lazim. Jika ditolak, semak arahan persediaan dalam log aplikasi.',
initialCredentials: 'Maklumat log masuk awal; gunakan kata laluan semasa jika telah diubah dalam aplikasi.',
nativeAgentAccessUnavailable:
'Pemasangan sedia ada tiada alamat atau port HTTPS yang serasi. Semak tetapan aplikasi.',
agent: 'Agen',
account: 'Akaun model',
batchOperation: 'Operasi pukal',
@@ -1506,13 +1496,16 @@ const message = {
statusWaiting: 'Menunggu',
base: 'Maklumat Asas',
backend: 'Backend',
baseUrl: 'URL Asas',
endpoint: 'Endpoint',
tokenizer: 'Tokenizer',
tokenizerPlaceholder: 'Masukkan laluan direktori mutlak pada nod utama atau ID repositori Hugging Face',
tokenizerPlaceholder:
'Pilih atau masukkan direktori tokenizer tempatan, cth. /opt/1panel/tokenizers/DeepSeek-V3',
runConfig: 'Konfigurasi Jalan',
rawResult: 'Keputusan Mentah',
resultMetrics: 'Metrik Keputusan',
contextTokens: 'Panjang Konteks',
contextTokensHelper: 'Had token input ditambah had token output untuk penanda aras ini.',
outputThroughput: 'Throughput Output',
outputThroughputHelper: 'Token output dijana setiap saat. Lebih tinggi bermaksud penjanaan lebih pantas.',
totalThroughput: 'Jumlah Throughput',
@@ -1521,30 +1514,23 @@ const message = {
firstTokenLatency: 'Latensi Token Pertama',
firstTokenLatencyHelper:
'Masa dari menghantar permintaan hingga menerima token pertama. Lebih rendah bermaksud respons lebih pantas.',
tokenValueHelper: 'Masukkan integer positif atau nilai k, cth. 512, 1k, 32k',
numPrompts: 'Prompt',
concurrency: 'Keserentakan',
mean: 'Min',
median: 'Median',
requestRate: 'Kadar Permintaan',
requestRateUnlimited: 'Tanpa had (throughput maksimum)',
requestRateCustom: 'QPS tersuai',
requestRateCustomPlaceholder: 'Permintaan sesaat, cth. 2.5',
successfulRequests: 'Permintaan berjaya',
requestThroughput: 'Throughput Permintaan',
timeout: 'Tamat Masa (saat)',
image: 'Imej vLLM',
ignoreEos: 'Abaikan token akhir jujukan (EOS)',
ignoreEosHelp:
'Mengelakkan penghentian awal akibat EOS untuk menguji panjang output yang ditetapkan. Syarat henti lain masih terpakai; sokongan pelayan diperlukan.',
ignoreEos: 'Abaikan EOS',
extraHeaders: 'Pengepala Tambahan',
},
vllmMonitor: {
concurrency: 'Permintaan serentak semasa',
sampleMoment: 'Pada masa pensampelan',
sampleWindow: 'Selang pensampelan',
requestUnit: 'permintaan',
processing: 'Sedang diproses',
queued: 'Dalam baris gilir',
requestTiming: 'Tempoh permintaan',
completedRequests: 'Permintaan selesai dalam selang masa',
tokensPerSecond: 'tokens/s',
requestsPerSecond: 'Permintaan/s',
finishReasonLabels: {
@@ -1747,157 +1733,6 @@ const message = {
modelType: 'Jenis model',
},
},
benchmarkPlan: {
prepareTimeoutHelp: 'Had masa persediaan, tidak termasuk had masa keseluruhan.',
pointTimeoutHelp:
'Pemasa berasingan setiap titik, termasuk permulaan, pemanasan dan semua permintaan; dihadkan oleh baki masa keseluruhan.',
totalTimeoutHelp:
'Bermula selepas persediaan, meliputi semua titik dan sela. Ujian berhenti apabila tamat masa.',
concurrencyLevels: 'Tahap keserentakan',
concurrencyHelp:
'Tahap diuji mengikut urutan. Setiap nilai mengehadkan bilangan permintaan yang berjalan serentak.',
requestCountMode: 'Tetapan bilangan permintaan',
fixedRequestMode: 'Permintaan tetap setiap tahap',
scaledRequestMode: 'Kira mengikut keserentakan',
requestsPerPoint: 'Permintaan setiap tahap',
requestCountFormula:
'Setiap tahap: nilai tetap atau max(keserentakan × pengganda, permintaan minimum). Jumlah senario: jumlah semua tahap × ulangan, tidak termasuk pemanasan.',
controlLog: 'Log persediaan dan kawalan',
executionLog: 'Log pelaksanaan',
pointLog: 'Titik #{0} · {1} · Log pelaksanaan',
archivedLog: '{0} (arkib {1})',
tokenLengths: 'Token input / output',
kind: 'Kategori hasil',
kindServe: 'serve (ujian biasa)',
kindPD: 'pd (pengukuran Prefill/Decode)',
saveDetailed: 'Simpan butiran permintaan',
saveDetailedHelp:
'Simpan panjang, kependaman, teks yang dijana dan ralat bagi setiap permintaan. Metrik ringkasan dikekalkan apabila dimatikan, sekali gus mengurangkan saiz fail hasil.',
parameters: 'Parameter',
temperatureHelp:
'Mengawal kerawakan output. Sifar biasanya memberikan output yang lebih stabil; nilai lebih tinggi meningkatkan kerawakan.',
pointTimeout: 'Had masa setiap titik (saat)',
temperature: 'Suhu pensampelan',
requestUrl: 'URL API',
invalidRequestUrl:
'Masukkan URL HTTP(S) penuh yang berakhir dengan /chat/completions atau /completions, tanpa kelayakan log masuk, parameter pertanyaan atau fragmen.',
requestRateHelp:
'Terpakai hanya pada senario ini. Kadar tanpa had masih mematuhi had keserentakan; QPS perpuluhan disokong.',
tokenizerModeHelp:
'Mod yang tersedia bergantung pada versi vLLM dalam imej penanda aras. Pemuatan pentoken disemak semasa penyediaan tugas.',
invalidBaseUrl: 'Masukkan URL HTTP(S) yang sah tanpa kelayakan, pertanyaan atau fragmen.',
invalidEndpoint:
'Masukkan laluan yang sepadan dengan protokol dipilih dan berakhir dengan {0}, tanpa parameter pertanyaan atau fragmen.',
invalidTokenizer:
'Gunakan direktori mutlak atau ID repositori; laluan relatif, koma dan baris baharu tidak disokong.',
invalidRequestRate:
'Pilih tanpa had atau masukkan nombor terhingga lebih besar daripada 0; perpuluhan disokong.',
invalidCaCert: 'Masukkan laluan mutlak sijil CA pada nod utama tanpa koma atau baris baharu.',
bodyOverride: 'Badan tambahan tidak boleh menimpa {0}.',
invalidBody: 'Masukkan objek JSON yang sah.',
invalidConcurrencies: 'Berikan 1–100 nilai serentak, setiap satu integer antara 1 dan 100000.',
emptyRequestCount:
'Apabila mengira mengikut keserentakan, pengganda dan permintaan minimum tidak boleh kedua-duanya 0.',
requestCountLimit: 'Permintaan yang dikira bagi setiap titik tidak boleh melebihi 1000000.',
contextExceeded: 'Jumlah token input, output dan rizab templat melebihi had konteks.',
planLimit: 'Berikan 1–100 senario dengan tidak lebih daripada 2000 titik.',
caCertHelp:
'Pilih sijil CA pada pelayan utama untuk mengesahkan perkhidmatan HTTPS. Biarkan kosong untuk menggunakan sijil sistem.',
extraBody: 'Badan permintaan tambahan',
entryKey: 'Nama parameter',
entryValue: 'Nilai parameter',
tokenizerMode: 'Mod pentoken',
requireIdle: 'Semak sama ada perkhidmatan melahu sebelum bermula',
requireIdleHelp:
'Menyemak /metrics sekali sebelum pemanasan dan ujian beban. Berhenti jika perkhidmatan sibuk atau keadaan melahu tidak dapat disahkan. Ini tidak menghalang trafik lain semasa ujian.',
trustRemoteCode: 'Percayai kod jauh',
trustRemoteCodeHelp:
'Membenarkan kod pentoken tersuai daripada repositori model dijalankan dalam bekas penanda aras. Aktifkan hanya jika diperlukan dan sumber dipercayai.',
tokenizerModes: { auto: 'Automatik (disyorkan)', slow: 'Pentoken perlahan', mistral: 'Pentoken Mistral' },
quickDescription: 'Sahkan sambungan dan pengukuran dengan satu permintaan.',
regularDescription: 'Beban pendek, sederhana dan panjang untuk ujian prestasi rutin.',
fullDescription:
'Pelbagai senario, tahap keserentakan dan ulangan, termasuk input panjang dan awalan dikongsi.',
customDescription: 'Mulakan dengan satu senario, tetapkan parameternya dan tambah lagi mengikut keperluan.',
scenarios: 'Senario',
builtIn: 'Terbina dalam',
builtInHelp: 'Pelan terbina dalam tidak boleh disunting atau dipadam. Laraskan salinan semasa mencipta ujian.',
modified: 'Diubah suai',
imageExists: 'Tersedia secara setempat',
imageNeedsPull: 'Perlu dimuat turun',
remark: 'Catatan',
source: 'Perkhidmatan setempat',
mode: 'Pelan ujian',
quick: 'Semakan pantas',
regular: 'Biasa',
full: 'Penuh',
custom: 'Tersuai',
addScenario: 'Tambah senario',
advanced: 'Tetapan lanjutan',
points: 'Titik',
warmups: 'Permintaan pemanasan',
fixedRequests: 'Permintaan tetap',
multiplier: 'Permintaan bagi keserentakan',
minRequests: 'Permintaan minimum',
trials: 'Ulangan',
prefixRatio: 'Nisbah awalan dikongsi',
totalTimeout: 'Had masa pusingan (s)',
prepareTimeout: 'Had masa persediaan (s)',
interval: 'Sela titik (s)',
context: 'Had konteks (0: tidak diketahui)',
reserve: 'Token simpanan templat',
storage: 'Had storan (MiB)',
stopOnError: 'Hentikan titik seterusnya jika ralat',
caCert: 'Laluan sijil CA',
validTrials: 'Sah / jumlah ulangan',
retry: 'Ulang titik terpilih',
environment: 'Persekitaran',
plan: 'Konfigurasi dibekukan',
clientHelp:
'Kontena klien berjalan pada nod utama dan menghantar permintaan kepada perkhidmatan sedia ada. Ia tidak memulakan inferens atau memerlukan GPU.',
retryHelp: 'Hanya titik bermasalah yang dipilih diulang, dengan parameter sama dan benih baharu.',
secretHelp: 'Salinan tidak mengandungi kunci API atau pengepala. Masukkan semula jika perlu.',
resultHelp:
'Median dan minimum/maksimum hanya melibatkan ujian berjaya tanpa anomali permintaan. P99 ialah median P99 setiap ujian. Cache dan trafik luaran tidak dikawal.',
},
benchmarkOverview: {
noResults: 'Tiada keputusan ujian yang sah. Semak sebab kegagalan dan log.',
noGroupResults: 'Tiada keputusan sah untuk gabungan ini. Pilih senario atau keserentakan lain.',
partial: 'Keputusan separa: ujian tidak berjaya sepenuhnya. Metrik hanya merangkumi titik yang sah.',
highlights: 'Metrik utama',
aggregate: 'Median pelaksanaan sah bagi gabungan ini. Klik metrik untuk butiran.',
trend: 'Trend keserentakan',
std: 'Sisihan piawai',
min: 'Minimum',
max: 'Maksimum',
tpot: 'Masa setiap token output',
tpotHelp: 'Purata masa penjanaan setiap token output selepas token pertama.',
itl: 'Kependaman antara token',
itlHelp: 'Masa antara token berturutan dalam respons penstriman.',
e2el: 'Kependaman hujung ke hujung',
e2elHelp: 'Masa dari penghantaran permintaan hingga penerimaan respons lengkap.',
requestHelp: 'Bilangan permintaan yang selesai setiap saat.',
usageHelp:
'Median pelaksanaan sah bagi gabungan ini, bukan jumlah permintaan keseluruhan ujian. Lihat pelaksanaan tidak normal dalam tab titik.',
duration: 'Tempoh pelaksanaan',
throughput: 'Daya pemprosesan',
latency: 'Kependaman',
usage: 'Permintaan dan penggunaan (pelaksanaan sah)',
},
benchmarkTokenizer: {
title: 'Pengurusan tokenizer',
local: 'Daftar direktori setempat',
upload: 'Muat naik pakej tokenizer',
uploadHelp: 'Muat naik ZIP / tar.gz tanpa pemberat model. Had: 128 MiB dimampatkan, 256 MiB selepas diekstrak.',
source: 'Kaedah import',
deleteLocal: 'Alih keluar pendaftaran tokenizer “{0}”? Direktori dan fail asal akan dikekalkan.',
deleteUpload: 'Padam tokenizer “{0}” serta fail yang dimuat naik? Laporan ujian terdahulu akan dikekalkan.',
select: 'Pilih tokenizer',
selectHelp:
'Untuk ujian luar talian, pilih tokenizer terurus. Input tersuai menerima direktori setempat atau nama repositori.',
},
container: {
createByCommand: 'Cipta melalui perintah',
importContainerBackupTip: 'Import fail sandaran kontena, hanya format .tar.gz disokong',
@@ -3170,20 +3005,6 @@ const message = {
remote: 'SSH jauh',
},
setting: {
panelCache: {
title: 'Tetapan cache',
tabs: 'Cache tab',
pagination: 'Cache halaman',
refresh: 'Cache sela penyegaran',
search: 'Cache input carian',
duration: 'Tempoh cache',
'24h': '24 jam',
week: 'Satu minggu',
month: 'Satu bulan',
forever: 'Kekal',
helper: 'Tempoh sah dikira dari perubahan keadaan terakhir. Selepas tamat tempoh, nilai lalai dipulihkan.',
},
panel: 'Panel',
user: 'Pengguna panel',
userChange: 'Tukar pengguna panel',
@@ -3921,7 +3742,6 @@ const message = {
link: 'Pautan',
showCurrentArch: 'Aplikasi seni bina pelayan semasa',
syncLocalApp: 'Segerakkan Aplikasi Tempatan',
localAppSyncToNodes: 'Sertakan nod anak semasa menyegerakkan aplikasi tempatan',
memoryRequiredHelper: 'Aplikasi semasa memerlukan {0} memori',
gpuConfig: 'Aktifkan Sokongan GPU',
gpuConfigHelper:
@@ -4487,8 +4307,7 @@ const message = {
domainHelper: 'Satu nama domain setiap baris, menyokong * dan alamat IP',
pushDir: 'Tolakkan sijil ke direktori tempatan',
dir: 'Direktori',
pushDirHelper:
'Pilih beberapa direktori atau masukkan satu direktori pada setiap baris. Fail "fullchain.pem" dan "privkey.pem" akan dihasilkan dalam setiap direktori.',
pushDirHelper: 'Fail sijil "fullchain.pem" dan fail kunci "privkey.pem" akan dihasilkan dalam direktori ini.',
organizationDetail: 'Butiran organisasi',
fromWebsite: 'Daripada laman web',
dnsMauanlHelper:
@@ -4543,15 +4362,10 @@ const message = {
familyIncomplete: 'Rantaian {0} tidak lengkap',
resetWithBackup: 'Sandarkan peraturan dan tetapkan semula',
resetOnly: 'Tetapkan semula sahaja',
resetWithBackupHelper:
'Simpan peraturan modul semasa sebagai JSON untuk dipulihkan semasa pemulaan atau semasa tembok api dimulakan.',
resetWithBackupHelper: 'Simpan peraturan modul semasa sebagai JSON untuk dipulihkan semasa pemulaan.',
resetOnlyHelper: 'Padam peraturan semasa tanpa sandaran baharu. Sandaran sedia ada dikekalkan.',
backupDirectoryHelper:
'Sandaran peraturan yang sah dalam {0} disenaraikan di bawah. Fail terkini dipilih secara lalai.',
startOnly: 'Mula sahaja',
startAndRestore: 'Mula dan pulihkan daripada sandaran',
startWithBackupHelper:
'Mulakan tembok api seperti biasa atau pilih sandaran JSON sedia ada untuk memulakan dan memulihkan peraturan.',
restoreAllRules: 'Mulakan dan pulihkan peraturan',
importRuleBackup: 'Import daripada sandaran',
initializeOnly: 'Mulakan sahaja',

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