mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-13 16:00:38 +00:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaf70cfc41 | ||
|
|
1ecd4f7f0c | ||
|
|
a8ccdcade7 | ||
|
|
4a89c0c031 | ||
|
|
f21ebb1a36 | ||
|
|
3011383b07 | ||
|
|
fd12866897 | ||
|
|
bc6cf519ce | ||
|
|
f9c43989fe | ||
|
|
e90b201001 | ||
|
|
f7394938f7 | ||
|
|
dada7294dd | ||
|
|
afcb94f6ef | ||
|
|
5a2e430dff | ||
|
|
f2ca4f56e6 | ||
|
|
b6f609aaee | ||
|
|
c960c0137d | ||
|
|
09a901294e | ||
|
|
4d481a8f31 | ||
|
|
f1e0aa3391 | ||
|
|
f475500543 | ||
|
|
8415a1ce28 | ||
|
|
faf4933a02 | ||
|
|
c692a9f5e3 | ||
|
|
e7f0c21715 | ||
|
|
da7e8f1dbf | ||
|
|
486cd5d0ad | ||
|
|
00ce5a7b36 | ||
|
|
c17f055e06 | ||
|
|
f22df4b29c | ||
|
|
b9bab90a14 |
@@ -86,17 +86,23 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
|||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
// @Summary Install redis-cli
|
// @Summary Install redis-cli
|
||||||
// @Success 200
|
// @Accept json
|
||||||
|
// @Param request body dto.RedisCliInstall true "request"
|
||||||
|
// @Success 200 {string} string
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/redis/install/cli [post]
|
// @Router /databases/redis/install/cli [post]
|
||||||
func (b *BaseApi) InstallCli(c *gin.Context) {
|
func (b *BaseApi) InstallCli(c *gin.Context) {
|
||||||
if err := redisService.InstallCli(); err != nil {
|
var req dto.RedisCliInstall
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := redisService.InstallCli(req)
|
||||||
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
helper.Success(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ func (b *BaseApi) DeleteFirewallRules(c *gin.Context) {
|
|||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/rules/update [post]
|
// @Router /hosts/firewall/rules/update [post]
|
||||||
// @x-panel-log {"bodyKeys":["instanceKey"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙规则 [instanceKey]","formatEN":"update firewall rule [instanceKey]"}
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新防火墙规则","formatEN":"update firewall rule"}
|
||||||
func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
||||||
var request dto.FirewallRuleUpdate
|
var request dto.FirewallRuleUpdate
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
@@ -340,7 +340,7 @@ func (b *BaseApi) UpdateFirewallRule(c *gin.Context) {
|
|||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/rules/reorder [post]
|
// @Router /hosts/firewall/rules/reorder [post]
|
||||||
// @x-panel-log {"bodyKeys":["instanceKey"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整防火墙规则顺序 [instanceKey]","formatEN":"reorder firewall rule [instanceKey]"}
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整防火墙规则顺序","formatEN":"reorder firewall rule"}
|
||||||
func (b *BaseApi) ReorderFirewallRule(c *gin.Context) {
|
func (b *BaseApi) ReorderFirewallRule(c *gin.Context) {
|
||||||
var request dto.FirewallRuleReorder
|
var request dto.FirewallRuleReorder
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
@@ -556,7 +556,7 @@ func (b *BaseApi) OperateDockerPortGuard(c *gin.Context) {
|
|||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /hosts/firewall/docker/policies/delete/batch [post]
|
// @Router /hosts/firewall/docker/policies/delete/batch [post]
|
||||||
// @x-panel-log {"bodyKeys":["uuids"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 Docker 端口防护策略 [uuids]","formatEN":"delete Docker port guard policies [uuids]"}
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 Docker 端口防护策略","formatEN":"delete Docker port guard policies"}
|
||||||
func (b *BaseApi) DeleteDockerPortGuardPolicies(c *gin.Context) {
|
func (b *BaseApi) DeleteDockerPortGuardPolicies(c *gin.Context) {
|
||||||
var request dto.DockerPortGuardPolicyBatchDelete
|
var request dto.DockerPortGuardPolicyBatchDelete
|
||||||
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
if err := helper.CheckBindAndValidate(&request, c); err != nil {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func (b *BaseApi) LoadVLLMMonitor(c *gin.Context) {
|
|||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := monitorService.LoadVLLMMonitorData(req)
|
data, err := monitorService.LoadVLLMMonitorData(c.Request.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ type DBBaseInfo struct {
|
|||||||
Port int64 `json:"port"`
|
Port int64 `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RedisCliInstall struct {
|
||||||
|
TaskID string `json:"taskID" validate:"omitempty,uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
// mysql
|
// mysql
|
||||||
type MysqlDBSearch struct {
|
type MysqlDBSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
|
|||||||
@@ -50,13 +50,12 @@ type FirewallBackendOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendFamilyStatus struct {
|
type FirewallBackendFamilyStatus struct {
|
||||||
Partial bool `json:"partial"`
|
Partial bool `json:"partial"`
|
||||||
Available bool `json:"available"`
|
Available bool `json:"available"`
|
||||||
Initialized bool `json:"initialized"`
|
Initialized bool `json:"initialized"`
|
||||||
Bound bool `json:"bound"`
|
Bound bool `json:"bound"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
ForwardPolicy string `json:"forwardPolicy,omitempty"`
|
RAInterfaces []string `json:"raInterfaces,omitempty"`
|
||||||
RAInterfaces []string `json:"raInterfaces,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallBackendGroup struct {
|
type FirewallBackendGroup struct {
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ type GPUProcess struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MonitorVLLMSearch struct {
|
type MonitorVLLMSearch struct {
|
||||||
|
Group string `json:"group" validate:"omitempty,oneof=overview latency requests"`
|
||||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||||
StartTime time.Time `json:"startTime" validate:"required"`
|
StartTime time.Time `json:"startTime" validate:"required"`
|
||||||
EndTime time.Time `json:"endTime" validate:"required"`
|
EndTime time.Time `json:"endTime" validate:"required"`
|
||||||
|
|||||||
@@ -79,12 +79,20 @@ type MonitorGPU struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MonitorVLLM struct {
|
type MonitorVLLM struct {
|
||||||
ID uint `json:"-" gorm:"primarykey;autoIncrement"`
|
ID uint `json:"-" gorm:"primarykey;autoIncrement"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
AppInstallID uint `json:"appInstallID"`
|
AppInstallID uint `json:"appInstallID"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
RawMetrics string `json:"-"`
|
RawMetrics string `json:"-"`
|
||||||
HistogramDeltas string `json:"-"`
|
HistogramDeltas string `json:"-"`
|
||||||
|
IntervalSeconds float64 `json:"intervalSeconds" gorm:"-"`
|
||||||
|
RequestDeltas string `json:"-"`
|
||||||
|
QueueTime *float64 `json:"queueTime"`
|
||||||
|
AverageGenerationTokens *float64 `json:"averageGenerationTokens"`
|
||||||
|
PrefixCacheHitRate *float64 `json:"prefixCacheHitRate"`
|
||||||
|
FinishReasons map[string]float64 `json:"finishReasons" gorm:"-"`
|
||||||
|
PromptDistribution map[string]float64 `json:"promptDistribution" gorm:"-"`
|
||||||
|
GenerationDistribution map[string]float64 `json:"generationDistribution" gorm:"-"`
|
||||||
|
|
||||||
Running *float64 `json:"running"`
|
Running *float64 `json:"running"`
|
||||||
Waiting *float64 `json:"waiting"`
|
Waiting *float64 `json:"waiting"`
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -15,8 +16,7 @@ type VLLMMonitorRepo struct{}
|
|||||||
|
|
||||||
type VLLMHistoryPoint struct {
|
type VLLMHistoryPoint struct {
|
||||||
model.MonitorVLLM
|
model.MonitorVLLM
|
||||||
Bucket int64
|
Bucket int64
|
||||||
HistogramSamples string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error {
|
func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error {
|
||||||
@@ -41,16 +41,30 @@ func (r *VLLMMonitorRepo) DeleteBefore(before time.Time) error {
|
|||||||
return global.VLLMMonitorDB.Where("created_at < ?", before).Delete(&model.MonitorVLLM{}).Error
|
return global.VLLMMonitorDB.Where("created_at < ?", before).Delete(&model.MonitorVLLM{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VLLMMonitorRepo) Count(id uint, start, end time.Time) (int64, error) {
|
func (r *VLLMMonitorRepo) Count(ctx context.Context, id uint, start, end time.Time) (int64, error) {
|
||||||
var count int64
|
var count int64
|
||||||
db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
db := global.VLLMMonitorDB.WithContext(ctx).Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
||||||
err := db.Count(&count).Error
|
err := db.Count(&count).Error
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VLLMMonitorRepo) History(id uint, start, end time.Time, seconds int64, aggregation string) ([]VLLMHistoryPoint, error) {
|
func (r *VLLMMonitorRepo) History(ctx context.Context, id uint, start, end time.Time, seconds int64, aggregation, group string) ([]VLLMHistoryPoint, error) {
|
||||||
db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
db := global.VLLMMonitorDB.WithContext(ctx).Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end)
|
||||||
metrics := []string{"running", "waiting", "cache_usage", "prompt_throughput", "generation_throughput", "request_throughput", "time_to_first_token", "time_per_output_token", "request_latency", "prefill_time", "decode_time", "time_to_first_token_p50", "time_to_first_token_p90", "time_to_first_token_p95", "time_to_first_token_p99", "time_per_output_token_p50", "time_per_output_token_p90", "time_per_output_token_p95", "time_per_output_token_p99", "request_latency_p50", "request_latency_p90", "request_latency_p95", "request_latency_p99"}
|
var metrics []string
|
||||||
|
if group == "" || group == "overview" {
|
||||||
|
metrics = append(metrics, "running", "waiting", "cache_usage", "prompt_throughput", "generation_throughput", "request_throughput")
|
||||||
|
if seconds <= 0 || aggregation == "max" {
|
||||||
|
metrics = append(metrics, "prefix_cache_hit_rate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if seconds <= 0 || aggregation == "max" {
|
||||||
|
if group == "" || group == "latency" {
|
||||||
|
metrics = append(metrics, "queue_time", "time_to_first_token", "time_per_output_token", "request_latency", "prefill_time", "decode_time", "time_to_first_token_p50", "time_to_first_token_p90", "time_to_first_token_p95", "time_to_first_token_p99", "time_per_output_token_p50", "time_per_output_token_p90", "time_per_output_token_p95", "time_per_output_token_p99", "request_latency_p50", "request_latency_p90", "request_latency_p95", "request_latency_p99")
|
||||||
|
}
|
||||||
|
if group == "" || group == "requests" {
|
||||||
|
metrics = append(metrics, "average_generation_tokens")
|
||||||
|
}
|
||||||
|
}
|
||||||
var columns []string
|
var columns []string
|
||||||
if seconds > 0 {
|
if seconds > 0 {
|
||||||
operation := "AVG"
|
operation := "AVG"
|
||||||
@@ -59,20 +73,52 @@ func (r *VLLMMonitorRepo) History(id uint, start, end time.Time, seconds int64,
|
|||||||
}
|
}
|
||||||
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), seconds)}
|
columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), seconds)}
|
||||||
for _, column := range metrics {
|
for _, column := range metrics {
|
||||||
if aggregation != "max" && (strings.HasPrefix(column, "time_to_first_token_p") || strings.HasPrefix(column, "time_per_output_token_p") || strings.HasPrefix(column, "request_latency_p")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
columns = append(columns, operation+"("+column+") AS "+column)
|
columns = append(columns, operation+"("+column+") AS "+column)
|
||||||
}
|
}
|
||||||
if aggregation != "max" {
|
|
||||||
columns = append(columns, "json_group_array(json(NULLIF(histogram_deltas, ''))) AS histogram_samples")
|
|
||||||
}
|
|
||||||
db = db.Group("bucket").Order("bucket ASC")
|
db = db.Group("bucket").Order("bucket ASC")
|
||||||
} else {
|
} else {
|
||||||
columns = append([]string{"id", "created_at", "app_install_id", "status"}, 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")
|
db = db.Order("created_at ASC, id ASC")
|
||||||
}
|
}
|
||||||
var points []VLLMHistoryPoint
|
var points []VLLMHistoryPoint
|
||||||
err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error
|
err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error
|
||||||
return points, err
|
return points, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VLLMMonitorRepo) WalkDeltas(ctx context.Context, id uint, start, end time.Time, seconds int64, aggregation, group string, visit func(int64, string, string) error) error {
|
||||||
|
histograms, requests := "''", "''"
|
||||||
|
if group == "" || group == "requests" || (group == "latency" && aggregation != "max") {
|
||||||
|
histograms = "COALESCE(histogram_deltas, '')"
|
||||||
|
}
|
||||||
|
if group == "" || group == "requests" || (group == "overview" && aggregation != "max") {
|
||||||
|
requests = "COALESCE(request_deltas, '')"
|
||||||
|
}
|
||||||
|
if histograms == "''" && requests == "''" {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
bucket := fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d", start.Unix(), seconds)
|
||||||
|
rows, err := global.VLLMMonitorDB.WithContext(ctx).Model(&model.MonitorVLLM{}).
|
||||||
|
Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end).
|
||||||
|
Select(bucket + ", " + histograms + ", " + requests).Rows()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var index int64
|
||||||
|
var histograms, requests string
|
||||||
|
if err := rows.Scan(&index, &histograms, &requests); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := visit(index, histograms, requests); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows.Err()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package repo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -110,7 +111,9 @@ func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t TaskRepo) UpdateRunningTaskToFailed() error {
|
func (t TaskRepo) UpdateRunningTaskToFailed() error {
|
||||||
return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{"status": constant.StatusFailed, "error_msg": "1Panel restart causes failure"}).Error
|
return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{
|
||||||
|
"status": constant.StatusFailed, "error_msg": constant.InterruptedMsg, "end_at": time.Now(),
|
||||||
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t TaskRepo) CountExecutingTask() (int64, error) {
|
func (t TaskRepo) CountExecutingTask() (int64, error) {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||||
|
)
|
||||||
|
|
||||||
|
func prepareAIGatewayAppRecover(install *model.AppInstall, recoverFile, secret string) (string, func(), error) {
|
||||||
|
noop := func() {}
|
||||||
|
if !isAIGatewayApp(install.App.Key) {
|
||||||
|
return "", noop, nil
|
||||||
|
}
|
||||||
|
tmpDir, err := os.MkdirTemp(global.Dir.TmpDir, "ai-gateway-recover-")
|
||||||
|
if err != nil {
|
||||||
|
return "", noop, err
|
||||||
|
}
|
||||||
|
cleanup := func() { _ = os.RemoveAll(tmpDir) }
|
||||||
|
fail := func(err error) (string, func(), error) {
|
||||||
|
cleanup()
|
||||||
|
return "", noop, err
|
||||||
|
}
|
||||||
|
if err := files.NewFileOp().TarGzExtractPro(recoverFile, tmpDir, secret); err != nil {
|
||||||
|
return fail(err)
|
||||||
|
}
|
||||||
|
backupDir := filepath.Join(tmpDir, strings.ReplaceAll(filepath.Base(recoverFile), ".tar.gz", ""))
|
||||||
|
for _, name := range []string{"app.json", "app.tar.gz"} {
|
||||||
|
info, err := os.Lstat(filepath.Join(backupDir, name))
|
||||||
|
if err != nil || !info.Mode().IsRegular() {
|
||||||
|
return fail(buserr.New("AppBackupFileIncomplete"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata, err := os.ReadFile(filepath.Join(backupDir, "app.json"))
|
||||||
|
if err != nil {
|
||||||
|
return fail(err)
|
||||||
|
}
|
||||||
|
var backup model.AppInstall
|
||||||
|
if err := json.Unmarshal(metadata, &backup); err != nil {
|
||||||
|
return fail(fmt.Errorf("unmarshal app.json failed: %w", err))
|
||||||
|
}
|
||||||
|
if backup.App.Key != install.App.Key || backup.Name != install.Name {
|
||||||
|
return fail(buserr.New("AppAttributesNotMatch"))
|
||||||
|
}
|
||||||
|
// Historical backups already include Version. Unsuffixed releases are
|
||||||
|
// SQLite; no new metadata field is required. Missing versions are invalid,
|
||||||
|
// not evidence that an archive contains SQLite data.
|
||||||
|
if strings.TrimSpace(backup.Version) == "" || strings.TrimSpace(install.Version) == "" {
|
||||||
|
return fail(buserr.New("AppBackupFileIncomplete"))
|
||||||
|
}
|
||||||
|
// Deliberately do not compare version order: restoring an older version of
|
||||||
|
// the same database type is required for failed-upgrade rollbacks.
|
||||||
|
if !isAIGatewayUpgradeVersionAllowed(install.App.Key, install.Version, backup.Version) {
|
||||||
|
return fail(buserr.New("ErrAIGatewayRecoverDatabaseTypeMismatch"))
|
||||||
|
}
|
||||||
|
return backupDir, cleanup, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const aiGatewayAppKey = "ai-gateway"
|
||||||
|
|
||||||
|
func isAIGatewayApp(appKey string) bool {
|
||||||
|
return appKey == aiGatewayAppKey || appKey == "local"+aiGatewayAppKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAIGatewayPostgresVersion(version string) bool {
|
||||||
|
return strings.HasSuffix(version, "-pg") || strings.HasSuffix(version, "-postgres")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database variants are deployment choices, not interchangeable upgrades.
|
||||||
|
func isAIGatewayUpgradeVersionAllowed(appKey, currentVersion, targetVersion string) bool {
|
||||||
|
return !isAIGatewayApp(appKey) ||
|
||||||
|
isAIGatewayPostgresVersion(currentVersion) == isAIGatewayPostgresVersion(targetVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortAIGatewayVersions(versions []string) {
|
||||||
|
sort.SliceStable(versions, func(i, j int) bool {
|
||||||
|
if common.CompareVersion(versions[i], versions[j]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if common.CompareVersion(versions[j], versions[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Prefer SQLite only when the numeric versions are equal.
|
||||||
|
return !isAIGatewayPostgresVersion(versions[i]) && isAIGatewayPostgresVersion(versions[j])
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -550,6 +550,11 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
|
|
||||||
installTask, err := task.NewTaskWithOps(appInstall.Name, task.TaskInstall, task.TaskScopeApp, req.TaskID, appInstall.ID)
|
installTask, err := task.NewTaskWithOps(appInstall.Name, task.TaskInstall, task.TaskScopeApp, req.TaskID, appInstall.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
appInstall.Status = constant.StatusInstallErr
|
||||||
|
appInstall.Message = err.Error()
|
||||||
|
if saveErr := appInstallRepo.Save(context.Background(), appInstall); saveErr != nil {
|
||||||
|
err = fmt.Errorf("%w; save failed install status: %v", err, saveErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,7 +562,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
installApp := func(t *task.Task) error {
|
installApp := func(t *task.Task) (err error) {
|
||||||
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
|
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,6 +339,11 @@ func operateAppWithLifecycleScripts(install model.AppInstall, req request.AppIns
|
|||||||
|
|
||||||
operationTask, err := task.NewTaskWithOps(install.Name, taskType, task.TaskScopeApp, req.TaskID, install.ID)
|
operationTask, err := task.NewTaskWithOps(install.Name, taskType, task.TaskScopeApp, req.TaskID, install.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
install.Status = constant.StatusUpErr
|
||||||
|
install.Message = err.Error()
|
||||||
|
if saveErr := appInstallRepo.Save(context.Background(), &install); saveErr != nil {
|
||||||
|
return fmt.Errorf("%w; save failed operation status: %v", err, saveErr)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
operation := string(req.Operate)
|
operation := string(req.Operate)
|
||||||
@@ -567,11 +572,15 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, i := range allList {
|
for _, i := range allList {
|
||||||
if i.Status == constant.StatusInstalling || i.Status == constant.StatusUpgrading || i.Status == constant.StatusRebuilding || i.Status == constant.StatusUninstalling {
|
if appInstallOperationInterruptedOnRestart(i.Status) {
|
||||||
if systemInit {
|
if systemInit {
|
||||||
i.Status = constant.StatusError
|
i.Status = appInstallOperationFailureStatus(i.Status)
|
||||||
i.Message = "1Panel restart causes the task to terminate"
|
i.Message = constant.InterruptedMsg
|
||||||
_ = appInstallRepo.Save(context.Background(), &i)
|
if err := appInstallRepo.Save(context.Background(), &i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := syncAppInstallStatus(&i, false); err != nil {
|
||||||
|
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -678,6 +687,9 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
|||||||
if !canAccessVllmVersion(app.Key, detail.Version) {
|
if !canAccessVllmVersion(app.Key, detail.Version) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !isAIGatewayUpgradeVersionAllowed(app.Key, install.Version, detail.Version) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
||||||
if len(ignores) > 0 {
|
if len(ignores) > 0 {
|
||||||
continue
|
continue
|
||||||
@@ -931,10 +943,34 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
||||||
|
operation := ""
|
||||||
|
switch appInstall.Status {
|
||||||
|
case constant.StatusInstalling:
|
||||||
|
operation = task.TaskInstall
|
||||||
|
case constant.StatusUpgrading:
|
||||||
|
operation = task.TaskUpgrade
|
||||||
|
case constant.StatusRebuilding:
|
||||||
|
operation = task.TaskBuild
|
||||||
|
case constant.StatusUninstalling:
|
||||||
|
operation = task.TaskUninstall
|
||||||
|
case constant.StatusStarting, constant.StatusWaiting:
|
||||||
|
operation = task.TaskUpdate
|
||||||
|
case constant.StatusRestarting:
|
||||||
|
operation = task.TaskRestart
|
||||||
|
}
|
||||||
|
if operation != "" {
|
||||||
|
if err := reconcileAppInstallTaskFailure(appInstall, operation, global.DB, global.TaskDB); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
switch appInstall.Status {
|
switch appInstall.Status {
|
||||||
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||||
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||||
return nil
|
return nil
|
||||||
|
case constant.StatusInstallErr, constant.StatusUpgradeErr, constant.StatusUpErr:
|
||||||
|
if !force {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cli, err := docker.NewClient()
|
cli, err := docker.NewClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func appInstallOperationFailureStatus(status string) string {
|
||||||
|
switch status {
|
||||||
|
case constant.StatusInstalling:
|
||||||
|
return constant.StatusInstallErr
|
||||||
|
case constant.StatusUpgrading:
|
||||||
|
return constant.StatusUpgradeErr
|
||||||
|
case constant.StatusUninstalling:
|
||||||
|
return constant.StatusError
|
||||||
|
default:
|
||||||
|
return constant.StatusUpErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appInstallOperationPending(status string) bool {
|
||||||
|
switch status {
|
||||||
|
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||||
|
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appInstallOperationInterruptedOnRestart(status string) bool {
|
||||||
|
// ReStarting can also describe a container's Docker restart policy.
|
||||||
|
return appInstallOperationPending(status) && status != constant.StatusRestarting
|
||||||
|
}
|
||||||
|
|
||||||
|
func reconcileAppInstallTaskFailure(install *model.AppInstall, operation string, appDB, taskDB *gorm.DB) error {
|
||||||
|
if !appInstallOperationPending(install.Status) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var operationTask model.Task
|
||||||
|
err := taskDB.Where("type = ? AND resource_id = ? AND operate = ?", "App", install.ID, operation).
|
||||||
|
Order("created_at DESC").First(&operationTask).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if operationTask.Status != constant.StatusFailed && operationTask.Status != constant.StatusCanceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// A previous operation must not turn a newly queued retry into a failure.
|
||||||
|
if operationTask.CreatedAt.Before(install.UpdatedAt) &&
|
||||||
|
(operationTask.EndAt.IsZero() || operationTask.EndAt.Before(install.UpdatedAt)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
message := operationTask.ErrorMsg
|
||||||
|
if message == "" {
|
||||||
|
message = "the application operation task failed"
|
||||||
|
}
|
||||||
|
status := appInstallOperationFailureStatus(install.Status)
|
||||||
|
result := appDB.Model(&model.AppInstall{}).
|
||||||
|
Where("id = ? AND status = ? AND updated_at = ?", install.ID, install.Status, install.UpdatedAt).
|
||||||
|
Updates(map[string]interface{}{"status": status, "message": message})
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected > 0 {
|
||||||
|
install.Status = status
|
||||||
|
install.Message = message
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func operationStatusTestDB(t *testing.T) (*gorm.DB, *gorm.DB, model.AppInstall) {
|
||||||
|
t.Helper()
|
||||||
|
open := func(name string, schema interface{}) *gorm.DB {
|
||||||
|
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), name)), &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logger.Silent),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := db.AutoMigrate(schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
sqlDB, _ := db.DB()
|
||||||
|
if sqlDB != nil {
|
||||||
|
_ = sqlDB.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
appDB := open("apps.db", &model.AppInstall{})
|
||||||
|
taskDB := open("tasks.db", &model.Task{})
|
||||||
|
install := model.AppInstall{
|
||||||
|
Name: "gb10-ds-vision", AppId: 1, AppDetailId: 1,
|
||||||
|
Version: "nvidia-gb10-dspark-0.1.1", ContainerName: "vllm-dspark-1", ServiceName: "vllm-dspark",
|
||||||
|
Status: constant.StatusStarting, Env: `{"MODEL_DIR":"/models/vision"}`,
|
||||||
|
}
|
||||||
|
if err := appDB.Omit(clause.Associations).Create(&install).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return appDB, taskDB, install
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppInstallFailedTaskRecoversPendingState(t *testing.T) {
|
||||||
|
for _, state := range []string{constant.StatusStarting, constant.StatusWaiting, constant.StatusRestarting, constant.StatusInstalling} {
|
||||||
|
t.Run(state, func(t *testing.T) {
|
||||||
|
appDB, taskDB, install := operationStatusTestDB(t)
|
||||||
|
install.Status = state
|
||||||
|
if err := appDB.Omit(clause.Associations).Save(&install).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
task := model.Task{ID: "failed", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||||
|
Status: constant.StatusFailed, ErrorMsg: "model startup script failed", CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||||
|
if err := taskDB.Create(&task).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := constant.StatusUpErr
|
||||||
|
if state == constant.StatusInstalling {
|
||||||
|
want = constant.StatusInstallErr
|
||||||
|
}
|
||||||
|
var stored model.AppInstall
|
||||||
|
if err := appDB.First(&stored, install.ID).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if install.Status != want || stored.Status != want || stored.Message != task.ErrorMsg || stored.Env != install.Env {
|
||||||
|
t.Fatalf("recovered state = %s/%s, message %q, env %q", install.Status, stored.Status, stored.Message, stored.Env)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppInstallPendingStateKeepsActiveAndUnrelatedTasks(t *testing.T) {
|
||||||
|
for _, taskStatus := range []string{constant.StatusExecuting, constant.StatusSuccess, "missing", "previous-failure", "other-resource"} {
|
||||||
|
t.Run(taskStatus, func(t *testing.T) {
|
||||||
|
appDB, taskDB, install := operationStatusTestDB(t)
|
||||||
|
task := model.Task{ID: "task", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||||
|
Status: taskStatus, CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||||
|
if taskStatus == "previous-failure" {
|
||||||
|
task.Status = constant.StatusFailed
|
||||||
|
task.CreatedAt = install.UpdatedAt.Add(-2 * time.Hour)
|
||||||
|
task.EndAt = install.UpdatedAt.Add(-time.Hour)
|
||||||
|
}
|
||||||
|
if taskStatus == "other-resource" {
|
||||||
|
task.Status = constant.StatusFailed
|
||||||
|
task.ResourceID++
|
||||||
|
}
|
||||||
|
if taskStatus != "missing" {
|
||||||
|
if err := taskDB.Create(&task).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if install.Status != constant.StatusStarting {
|
||||||
|
t.Fatalf("unrelated or active task changed pending state to %s", install.Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppInstallRecoveryDoesNotOverwriteConcurrentRetry(t *testing.T) {
|
||||||
|
appDB, taskDB, install := operationStatusTestDB(t)
|
||||||
|
task := model.Task{ID: "failed", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||||
|
Status: constant.StatusCanceled, ErrorMsg: "interrupted", CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||||
|
if err := taskDB.Create(&task).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := appDB.Callback().Update().Before("gorm:update").Register("new-operation", func(db *gorm.DB) {
|
||||||
|
if err := db.Exec("UPDATE app_installs SET updated_at = ?, message = ? WHERE id = ?", install.UpdatedAt.Add(time.Hour), "new attempt", install.ID).Error; err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var stored model.AppInstall
|
||||||
|
if err := appDB.First(&stored, install.ID).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if stored.Status != constant.StatusStarting || stored.Message != "new attempt" {
|
||||||
|
t.Fatalf("recovery overwrote a newer operation: %s, %s", stored.Status, stored.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppInstallRestartRecoveryCoversLifecycleStates(t *testing.T) {
|
||||||
|
for _, state := range []string{constant.StatusStarting, constant.StatusWaiting, constant.StatusInstalling} {
|
||||||
|
if !appInstallOperationInterruptedOnRestart(state) {
|
||||||
|
t.Errorf("restart recovery omitted %s", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, state := range []string{constant.StatusRunning, constant.StatusStopped, constant.StatusRestarting} {
|
||||||
|
if appInstallOperationInterruptedOnRestart(state) {
|
||||||
|
t.Errorf("restart recovery changes a Docker runtime state: %s", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,6 +106,9 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !isAIGatewayUpgradeVersionAllowed(install.App.Key, install.Version, detail.Version) {
|
||||||
|
return buserr.New("ErrAIGatewayDatabaseTypeMismatch")
|
||||||
|
}
|
||||||
if err = checkVllmVersionAccess(install.App.Key, detail.Version); err != nil {
|
if err = checkVllmVersionAccess(install.App.Key, detail.Version); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1406,9 +1406,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func doNotNeedSync(installed model.AppInstall) bool {
|
func doNotNeedSync(installed model.AppInstall) bool {
|
||||||
return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading ||
|
return appInstallOperationPending(installed.Status) || installed.Status == constant.StatusSyncing ||
|
||||||
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr ||
|
installed.Status == constant.StatusInstallErr || installed.Status == constant.StatusUpgradeErr || installed.Status == constant.StatusUpErr
|
||||||
installed.Status == constant.StatusStarting || installed.Status == constant.StatusRestarting || installed.Status == constant.StatusWaiting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
||||||
@@ -1502,6 +1501,11 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, installed := range appInstallList {
|
for _, installed := range appInstallList {
|
||||||
|
if sync && appInstallOperationPending(installed.Status) {
|
||||||
|
if err := syncAppInstallStatus(&installed, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if updated && ignoreUpdate(installed) {
|
if updated && ignoreUpdate(installed) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1567,6 +1571,9 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
|||||||
|
|
||||||
var versions []string
|
var versions []string
|
||||||
for _, appDetail := range details {
|
for _, appDetail := range details {
|
||||||
|
if !isAIGatewayUpgradeVersionAllowed(installed.App.Key, installed.Version, appDetail.Version) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(appDetail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(appDetail.ID), appIgnoreUpgradeRepo.WithScope("version"))
|
||||||
if len(ignores) > 0 {
|
if len(ignores) > 0 {
|
||||||
continue
|
continue
|
||||||
@@ -2275,7 +2282,12 @@ func getAppVersions(key string, details []model.AppDetail) []string {
|
|||||||
}
|
}
|
||||||
versionsRaw = append(versionsRaw, detail.Version)
|
versionsRaw = append(versionsRaw, detail.Version)
|
||||||
}
|
}
|
||||||
versions = common.GetSortedVersions(versionsRaw)
|
if isAIGatewayApp(key) {
|
||||||
|
sortAIGatewayVersions(versionsRaw)
|
||||||
|
versions = versionsRaw
|
||||||
|
} else {
|
||||||
|
versions = common.GetSortedVersions(versionsRaw)
|
||||||
|
}
|
||||||
if hasLatest {
|
if hasLatest {
|
||||||
versions = append([]string{latestVersion}, versions...)
|
versions = append([]string{latestVersion}, versions...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,11 +132,9 @@ func (u *BackupService) AppRecover(req dto.CommonRecover) error {
|
|||||||
if !fileOp.Stat(req.File) {
|
if !fileOp.Stat(req.File) {
|
||||||
return buserr.WithName("ErrFileNotFound", req.File)
|
return buserr.WithName("ErrFileNotFound", req.File)
|
||||||
}
|
}
|
||||||
if _, err := compose.Down(install.GetComposePath()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := handleAppRecover(&install, nil, req.File, false, req.Secret, req.TaskID); err != nil {
|
if err := handleAppRecover(&install, nil, req.File, false, req.Secret, req.TaskID); err != nil {
|
||||||
global.LOG.Errorf("recover app %s failed, err: %v", req.DetailName, err)
|
global.LOG.Errorf("recover app %s failed, err: %v", req.DetailName, err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -200,8 +198,20 @@ func handleAppBackup(install *model.AppInstall, parentTask *task.Task, recordID
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverFile string, isRollback bool, secret, taskID string) error {
|
func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverFile string, isRollback bool, secret, taskID string) error {
|
||||||
|
// Validate AI Gateway backups before stopping the app, creating rollback
|
||||||
|
// backups, or touching its files/database. Keep the validated extraction for
|
||||||
|
// the restore so uploaded/encrypted archives are not unpacked twice.
|
||||||
|
preparedPath, cleanup, err := prepareAIGatewayAppRecover(install, recoverFile, secret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if parentTask == nil && !isRollback {
|
||||||
|
if _, err := compose.Down(install.GetComposePath()); err != nil {
|
||||||
|
cleanup()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
err error
|
|
||||||
recoverTask *task.Task
|
recoverTask *task.Task
|
||||||
isOk = false
|
isOk = false
|
||||||
rollbackFile string
|
rollbackFile string
|
||||||
@@ -210,11 +220,13 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
if parentTask == nil {
|
if parentTask == nil {
|
||||||
recoverTask, err = task.NewTaskWithOps(install.Name, task.TaskRecover, task.TaskScopeBackup, taskID, install.ID)
|
recoverTask, err = task.NewTaskWithOps(install.Name, task.TaskRecover, task.TaskScopeBackup, taskID, install.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
cleanup()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recoverApp := func(t *task.Task) error {
|
recoverApp := func(t *task.Task) error {
|
||||||
|
defer cleanup()
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if !isRollback {
|
if !isRollback {
|
||||||
rollbackFile = path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
rollbackFile = path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||||
@@ -223,10 +235,13 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
tmpPath := preparedPath
|
||||||
return err
|
if tmpPath == "" {
|
||||||
|
if err := fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmpPath = strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||||
}
|
}
|
||||||
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
|
||||||
if err := fileOp.TarGzExtractPro(tmpPath+"/app.tar.gz", tmpPath, ""); err != nil {
|
if err := fileOp.TarGzExtractPro(tmpPath+"/app.tar.gz", tmpPath, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -236,7 +251,7 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
} else {
|
} else {
|
||||||
_, _ = compose.Up(install.GetComposePath())
|
_, _ = compose.Up(install.GetComposePath())
|
||||||
}
|
}
|
||||||
_ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", ""))
|
_ = os.RemoveAll(tmpPath)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !fileOp.Stat(tmpPath+"/app.json") || !fileOp.Stat(tmpPath+"/app.tar.gz") {
|
if !fileOp.Stat(tmpPath+"/app.json") || !fileOp.Stat(tmpPath+"/app.tar.gz") {
|
||||||
@@ -397,6 +412,7 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||||||
}
|
}
|
||||||
recoverTask.AddSubTask(task.GetTaskName(install.Name, task.TaskRecover, task.TaskScopeBackup), recoverApp, rollBackApp)
|
recoverTask.AddSubTask(task.GetTaskName(install.Name, task.TaskRecover, task.TaskScopeBackup), recoverApp, rollBackApp)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer cleanup()
|
||||||
_ = recoverTask.Execute()
|
_ = recoverTask.Execute()
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -371,43 +371,39 @@ func loadComposePathAndFiles(composeName string, dockerClient *client.Client) (s
|
|||||||
}
|
}
|
||||||
config := targetContainer.Labels[composeConfigLabel]
|
config := targetContainer.Labels[composeConfigLabel]
|
||||||
workdir := targetContainer.Labels[composeWorkdirLabel]
|
workdir := targetContainer.Labels[composeWorkdirLabel]
|
||||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
files, err := resolveComposeFiles(workdir, config)
|
||||||
composePath = config
|
if err != nil {
|
||||||
} else {
|
return "", nil, err
|
||||||
composePath = workdir
|
|
||||||
}
|
}
|
||||||
|
composePath = strings.Join(files, ",")
|
||||||
}
|
}
|
||||||
composeFiles := normalizeComposeFiles(composePath)
|
composeFiles, err := normalizeComposeFiles(composePath)
|
||||||
if len(composeFiles) == 0 {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("compose file not found for %s", composeName)
|
return "", nil, err
|
||||||
}
|
}
|
||||||
return composePath, composeFiles, nil
|
return strings.Join(composeFiles, ","), composeFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeComposeFiles(composePath string) []string {
|
func normalizeComposeFiles(composePath string) ([]string, error) {
|
||||||
items := strings.Split(composePath, ",")
|
files, err := resolveComposeFiles("", composePath)
|
||||||
result := make([]string, 0)
|
if err != nil {
|
||||||
seen := make(map[string]struct{})
|
return nil, err
|
||||||
for _, item := range items {
|
|
||||||
item = strings.TrimSpace(item)
|
|
||||||
if item == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
stat, err := os.Stat(item)
|
|
||||||
if err == nil && stat.IsDir() {
|
|
||||||
item = path.Join(item, "docker-compose.yml")
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(item); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[item]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[item] = struct{}{}
|
|
||||||
result = append(result, item)
|
|
||||||
}
|
}
|
||||||
sort.Strings(result)
|
for i, file := range files {
|
||||||
return result
|
info, err := os.Stat(file)
|
||||||
|
if err == nil && info.IsDir() {
|
||||||
|
file = filepath.Join(file, "docker-compose.yml")
|
||||||
|
info, err = os.Stat(file)
|
||||||
|
files[i] = file
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return nil, fmt.Errorf("compose path %s is not a regular file", file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *composeBackupContext) close() {
|
func (c *composeBackupContext) close() {
|
||||||
@@ -621,20 +617,46 @@ func safeJoinWithinBase(baseDir, name string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stepRestoreComposeFiles(recoverCtx *composeRecoverContext) error {
|
func stepRestoreComposeFiles(recoverCtx *composeRecoverContext) error {
|
||||||
if recoverCtx.targetDir != "" {
|
composeFiles := recoverCtx.meta.Files
|
||||||
_ = os.RemoveAll(recoverCtx.targetDir)
|
if strings.TrimSpace(recoverCtx.meta.ComposePath) != "" {
|
||||||
|
filesByPath := make(map[string]composeBackupFile, len(composeFiles))
|
||||||
|
for _, item := range composeFiles {
|
||||||
|
filesByPath[filepath.Clean(item.OriginalPath)] = item
|
||||||
|
}
|
||||||
|
orderedFiles := make([]composeBackupFile, 0, len(composeFiles))
|
||||||
|
for _, file := range strings.Split(recoverCtx.meta.ComposePath, ",") {
|
||||||
|
file = strings.TrimSpace(file)
|
||||||
|
if file == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file = filepath.Clean(file)
|
||||||
|
item, ok := filesByPath[file]
|
||||||
|
if !ok {
|
||||||
|
if len(composeFiles) != 1 || file != filepath.Dir(composeFiles[0].OriginalPath) {
|
||||||
|
return fmt.Errorf("compose file %s is missing from backup metadata", file)
|
||||||
|
}
|
||||||
|
item = composeFiles[0]
|
||||||
|
}
|
||||||
|
orderedFiles = append(orderedFiles, item)
|
||||||
|
}
|
||||||
|
composeFiles = orderedFiles
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(recoverCtx.targetDir, os.ModePerm); err != nil {
|
if len(composeFiles) == 0 {
|
||||||
return err
|
return fmt.Errorf("compose files are missing from backup metadata")
|
||||||
}
|
}
|
||||||
restored := make([]string, 0, len(recoverCtx.meta.Files))
|
restored := make([]string, 0, len(composeFiles))
|
||||||
for _, item := range recoverCtx.meta.Files {
|
sourceByTarget := make(map[string]string, len(composeFiles))
|
||||||
|
for _, item := range composeFiles {
|
||||||
backupPath, err := safeJoinWithinBase(recoverCtx.tmpDir, item.BackupPath)
|
backupPath, err := safeJoinWithinBase(recoverCtx.tmpDir, item.BackupPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid compose backup path %q, err: %v", item.BackupPath, err)
|
return fmt.Errorf("invalid compose backup path %q, err: %v", item.BackupPath, err)
|
||||||
}
|
}
|
||||||
if !recoverCtx.fileOp.Stat(backupPath) {
|
info, err := os.Stat(backupPath)
|
||||||
continue
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("compose backup path %s is not a regular file", backupPath)
|
||||||
}
|
}
|
||||||
targetName := item.FileName
|
targetName := item.FileName
|
||||||
if item.RelativePath != "" {
|
if item.RelativePath != "" {
|
||||||
@@ -650,6 +672,29 @@ func stepRestoreComposeFiles(recoverCtx *composeRecoverContext) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid compose target path %q, err: %v", targetName, err)
|
return fmt.Errorf("invalid compose target path %q, err: %v", targetName, err)
|
||||||
}
|
}
|
||||||
|
sourcePath := item.OriginalPath
|
||||||
|
if sourcePath == "" {
|
||||||
|
sourcePath = backupPath
|
||||||
|
}
|
||||||
|
sourcePath = filepath.Clean(sourcePath)
|
||||||
|
if previous, ok := sourceByTarget[targetPath]; ok && previous != sourcePath {
|
||||||
|
return fmt.Errorf("compose files %q and %q resolve to the same restore path %q", previous, sourcePath, targetPath)
|
||||||
|
}
|
||||||
|
sourceByTarget[targetPath] = sourcePath
|
||||||
|
restored = append(restored, targetPath)
|
||||||
|
}
|
||||||
|
if recoverCtx.targetDir != "" {
|
||||||
|
_ = os.RemoveAll(recoverCtx.targetDir)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(recoverCtx.targetDir, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, item := range composeFiles {
|
||||||
|
backupPath, err := safeJoinWithinBase(recoverCtx.tmpDir, item.BackupPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid compose backup path %q, err: %v", item.BackupPath, err)
|
||||||
|
}
|
||||||
|
targetPath := restored[i]
|
||||||
if err := os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil {
|
if err := os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -660,7 +705,6 @@ func stepRestoreComposeFiles(recoverCtx *composeRecoverContext) error {
|
|||||||
if err := recoverCtx.fileOp.SaveFile(targetPath, string(content), fs.ModePerm); err != nil {
|
if err := recoverCtx.fileOp.SaveFile(targetPath, string(content), fs.ModePerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
restored = append(restored, targetPath)
|
|
||||||
}
|
}
|
||||||
envPath := path.Join(recoverCtx.tmpDir, "compose_files", ".env")
|
envPath := path.Join(recoverCtx.tmpDir, "compose_files", ".env")
|
||||||
if recoverCtx.fileOp.Stat(envPath) {
|
if recoverCtx.fileOp.Stat(envPath) {
|
||||||
@@ -672,14 +716,6 @@ func stepRestoreComposeFiles(recoverCtx *composeRecoverContext) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(restored) == 0 {
|
|
||||||
defaultPath := path.Join(recoverCtx.targetDir, "docker-compose.yml")
|
|
||||||
if !recoverCtx.fileOp.Stat(defaultPath) {
|
|
||||||
return fmt.Errorf("compose file not found in backup data")
|
|
||||||
}
|
|
||||||
restored = append(restored, defaultPath)
|
|
||||||
}
|
|
||||||
sort.Strings(restored)
|
|
||||||
recoverCtx.composePath = strings.Join(restored, ",")
|
recoverCtx.composePath = strings.Join(restored, ",")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,40 +326,47 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
|||||||
case "container":
|
case "container":
|
||||||
inspectInfo, err = client.ContainerInspect(context.Background(), req.ID)
|
inspectInfo, err = client.ContainerInspect(context.Background(), req.ID)
|
||||||
case "compose":
|
case "compose":
|
||||||
filePath := ""
|
config, workdir := "", ""
|
||||||
cli, err := docker.NewDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer cli.Close()
|
|
||||||
options := container.ListOptions{All: true}
|
options := container.ListOptions{All: true}
|
||||||
options.Filters = filters.NewArgs()
|
options.Filters = filters.NewArgs()
|
||||||
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.ID))
|
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.ID))
|
||||||
containers, err := cli.ContainerList(context.Background(), options)
|
containers, err := client.ContainerList(context.Background(), options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
for _, container := range containers {
|
|
||||||
config := container.Labels[composeConfigLabel]
|
|
||||||
if len(req.Detail) != 0 && strings.Contains(config, req.Detail) {
|
|
||||||
config = req.Detail
|
|
||||||
}
|
|
||||||
workdir := container.Labels[composeWorkdirLabel]
|
|
||||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
|
||||||
filePath = config
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
filePath = workdir
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(containers) == 0 {
|
if len(containers) == 0 {
|
||||||
composeItem, _ := composeRepo.GetRecord(repo.WithByName(req.ID))
|
composeItem, err := composeRepo.GetRecord(repo.WithByName(req.ID))
|
||||||
filePath = composeItem.Path
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
config = composeItem.Path
|
||||||
|
workdir = filepath.Dir(primaryComposePath(config))
|
||||||
|
} else {
|
||||||
|
config = containers[0].Labels[composeConfigLabel]
|
||||||
|
workdir = containers[0].Labels[composeWorkdirLabel]
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(filePath); err != nil {
|
composeFiles, err := resolveComposeFiles(workdir, config)
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
filePath := composeFiles[0]
|
||||||
|
if strings.TrimSpace(req.Detail) != "" {
|
||||||
|
selectedFiles, err := resolveComposeFiles(workdir, req.Detail)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(selectedFiles) != 1 || !slices.Contains(composeFiles, selectedFiles[0]) {
|
||||||
|
return "", fmt.Errorf("compose file %s does not belong to project %s", req.Detail, req.ID)
|
||||||
|
}
|
||||||
|
filePath = selectedFiles[0]
|
||||||
|
}
|
||||||
|
info, err := os.Stat(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return "", fmt.Errorf("compose path %s is not a regular file", filePath)
|
||||||
|
}
|
||||||
content, err := os.ReadFile(filePath)
|
content, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -383,11 +390,6 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||||
client, err := docker.NewDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
name := ""
|
name := ""
|
||||||
switch req.PruneType {
|
switch req.PruneType {
|
||||||
case "container":
|
case "container":
|
||||||
@@ -408,6 +410,14 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
||||||
|
if err := t.TaskCtx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
pruneFilters := filters.NewArgs()
|
pruneFilters := filters.NewArgs()
|
||||||
if req.WithTagAll {
|
if req.WithTagAll {
|
||||||
pruneFilters.Add("dangling", "false")
|
pruneFilters.Add("dangling", "false")
|
||||||
@@ -431,10 +441,7 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
|||||||
}
|
}
|
||||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||||
case "network":
|
case "network":
|
||||||
_, err := client.NetworksPrune(context.Background(), pruneFilters)
|
return cleanUnusedNetworks(t, client)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "volume":
|
case "volume":
|
||||||
versions, err := client.ServerVersion(context.Background())
|
versions, err := client.ServerVersion(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -803,33 +810,33 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
|
|||||||
func (u *ContainerService) StreamLogs(ctx *gin.Context, params dto.StreamLog) {
|
func (u *ContainerService) StreamLogs(ctx *gin.Context, params dto.StreamLog) {
|
||||||
messageChan := make(chan string, 1024)
|
messageChan := make(chan string, 1024)
|
||||||
errorChan := make(chan error, 1)
|
errorChan := make(chan error, 1)
|
||||||
doneChan := make(chan struct{})
|
logCtx, cancel := context.WithCancel(ctx.Request.Context())
|
||||||
|
defer cancel()
|
||||||
go func() {
|
doneChan := logCtx.Done()
|
||||||
<-ctx.Request.Context().Done()
|
|
||||||
close(doneChan)
|
|
||||||
}()
|
|
||||||
go collectLogs(doneChan, params, messageChan, errorChan)
|
go collectLogs(doneChan, params, messageChan, errorChan)
|
||||||
ctx.Stream(func(w io.Writer) bool {
|
ctx.Stream(func(w io.Writer) bool {
|
||||||
select {
|
return writeContainerLogMessage(w, doneChan, messageChan, errorChan)
|
||||||
case msg, ok := <-messageChan:
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err := fmt.Fprintf(w, "data: %s\n\n", msg)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case err := <-errorChan:
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(w, "event: error\ndata: %v\n\n", err.Error())
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeContainerLogMessage(w io.Writer, done <-chan struct{}, messages <-chan string, errors <-chan error) bool {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return false
|
||||||
|
case message, ok := <-messages:
|
||||||
|
if ok {
|
||||||
|
_, err := fmt.Fprintf(w, "data: %s\n\n", message)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
// The collector closes both channels when it finishes. Drain all buffered
|
||||||
|
// logs before handling its result, or a ready error channel can drop the tail.
|
||||||
|
if err := <-errors; err != nil {
|
||||||
|
_, _ = fmt.Fprintf(w, "event: error\ndata: %v\n\n", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<- string, errorChan chan<- error) {
|
func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<- string, errorChan chan<- error) {
|
||||||
defer close(messageChan)
|
defer close(messageChan)
|
||||||
defer close(errorChan)
|
defer close(errorChan)
|
||||||
|
|||||||
@@ -71,10 +71,9 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||||||
ContainerCount: 0,
|
ContainerCount: 0,
|
||||||
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
|
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
|
||||||
ConfigFile: localItem.Path,
|
ConfigFile: localItem.Path,
|
||||||
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
|
Workdir: filepath.Dir(primaryComposePath(localItem.Path)),
|
||||||
}
|
}
|
||||||
composeItemLocal.CreatedBy = "1Panel"
|
composeItemLocal.CreatedBy = "1Panel"
|
||||||
composeItemLocal.Path = localItem.Path
|
|
||||||
composeLocalMap[localItem.Name] = composeItemLocal
|
composeLocalMap[localItem.Name] = composeItemLocal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +111,6 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||||||
if ok {
|
if ok {
|
||||||
composeItem.CreatedBy = createdBy
|
composeItem.CreatedBy = createdBy
|
||||||
}
|
}
|
||||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
|
||||||
composeItem.Path = config
|
|
||||||
} else {
|
|
||||||
composeItem.Path = workdir
|
|
||||||
}
|
|
||||||
for i := 0; i < len(composeCreatedByLocal); i++ {
|
for i := 0; i < len(composeCreatedByLocal); i++ {
|
||||||
if composeCreatedByLocal[i].Name == name {
|
if composeCreatedByLocal[i].Name == name {
|
||||||
composeItem.CreatedBy = "1Panel"
|
composeItem.CreatedBy = "1Panel"
|
||||||
@@ -147,7 +141,9 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||||||
|
|
||||||
for key, value := range mergedMap {
|
for key, value := range mergedMap {
|
||||||
value.Name = key
|
value.Name = key
|
||||||
value.ComposeFileExists = composeFileExists(value.Workdir, value.ConfigFile)
|
composeFiles, err := resolveComposeFiles(value.Workdir, value.ConfigFile)
|
||||||
|
value.Path = strings.Join(composeFiles, ",")
|
||||||
|
value.ComposeFileExists = err == nil && composeFileExists(composeFiles)
|
||||||
value.IsPinned = pinnedByName[key]
|
value.IsPinned = pinnedByName[key]
|
||||||
records = append(records, value)
|
records = append(records, value)
|
||||||
}
|
}
|
||||||
@@ -192,29 +188,6 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
|||||||
return int64(total), listItem, nil
|
return int64(total), listItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func composeFileExists(workdir, configFile string) bool {
|
|
||||||
workdir = strings.TrimSpace(workdir)
|
|
||||||
configFile = strings.TrimSpace(configFile)
|
|
||||||
if configFile == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, item := range strings.Split(configFile, ",") {
|
|
||||||
file := strings.TrimSpace(item)
|
|
||||||
if file == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(file) && workdir != "" {
|
|
||||||
file = filepath.Join(workdir, file)
|
|
||||||
}
|
|
||||||
file = filepath.Clean(file)
|
|
||||||
info, err := os.Stat(file)
|
|
||||||
if err == nil && !info.IsDir() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||||
if err := validateComposeCreateName(req); err != nil {
|
if err := validateComposeCreateName(req); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -522,10 +495,6 @@ func isComposeProjectNameEmptyError(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||||
if len(req.Path) == 0 && req.Operation == "delete" {
|
|
||||||
_ = composeRepo.DeleteRecord(repo.WithByName(req.Name))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if cmd.CheckIllegal(req.Path, req.Operation) {
|
if cmd.CheckIllegal(req.Path, req.Operation) {
|
||||||
return buserr.New("ErrCmdIllegal")
|
return buserr.New("ErrCmdIllegal")
|
||||||
}
|
}
|
||||||
@@ -535,13 +504,19 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
|||||||
}
|
}
|
||||||
if req.WithFile {
|
if req.WithFile {
|
||||||
for _, item := range strings.Split(req.Path, ",") {
|
for _, item := range strings.Split(req.Path, ",") {
|
||||||
if len(item) != 0 {
|
item = strings.TrimSpace(item)
|
||||||
_ = os.RemoveAll(path.Dir(item))
|
if !filepath.IsAbs(item) || filepath.Dir(item) == "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := os.Stat(item)
|
||||||
|
if err == nil && info.Mode().IsRegular() {
|
||||||
|
if err := os.RemoveAll(filepath.Dir(item)); err != nil && !req.Force {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = composeRepo.DeleteRecord(repo.WithByName(req.Name))
|
return composeRepo.DeleteRecord(repo.WithByName(req.Name))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
if req.Operation == "up" {
|
if req.Operation == "up" {
|
||||||
if stdout, err := compose.Up(req.Path, req.Name); err != nil {
|
if stdout, err := compose.Up(req.Path, req.Name); err != nil {
|
||||||
@@ -584,13 +559,13 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
|||||||
write.Flush()
|
write.Flush()
|
||||||
|
|
||||||
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.DetailPath)
|
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.DetailPath)
|
||||||
if err := newComposeEnv(req.DetailPath, req.Env); err != nil {
|
if err := newComposeEnv(primaryComposePath(req.Path), req.Env); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := compose.UpWithTask(req.Path, t, req.ForcePull, req.Name); err != nil {
|
if err := compose.UpWithTask(req.Path, t, req.ForcePull, req.Name); err != nil {
|
||||||
global.LOG.Errorf("update failed when handle compose up, err: %s, now try to recreate the old compose file", err)
|
global.LOG.Errorf("update failed when handle compose up, err: %s, now try to recreate the old compose file", err)
|
||||||
if err := recreateCompose(string(oldFile), req.Path, req.Name); err != nil {
|
if err := recreateCompose(string(oldFile), req.Path, req.DetailPath, req.Name); err != nil {
|
||||||
return fmt.Errorf("update failed and recreate old compose file also failed, err: %v", err)
|
return fmt.Errorf("update failed and recreate old compose file also failed, err: %v", err)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("update failed when handle compose up, err: %s", err)
|
return fmt.Errorf("update failed when handle compose up, err: %s", err)
|
||||||
@@ -702,9 +677,18 @@ func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func removeContainerForCompose(composeName, composePath string) error {
|
func removeContainerForCompose(composeName, composePath string) error {
|
||||||
if _, err := os.Stat(composePath); err == nil {
|
composeFiles, err := resolveComposeFiles("", composePath)
|
||||||
if stdout, err := compose.Operate(composePath, "down", composeName); err != nil {
|
filesAvailable := err == nil
|
||||||
return errors.New(stdout)
|
for _, file := range composeFiles {
|
||||||
|
info, err := os.Stat(file)
|
||||||
|
if err != nil || !info.Mode().IsRegular() {
|
||||||
|
filesAvailable = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filesAvailable {
|
||||||
|
if stdout, err := compose.Operate(strings.Join(composeFiles, ","), "down", composeName); err != nil {
|
||||||
|
return fmt.Errorf("docker-compose down failed, std: %s, err: %w", stdout, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -722,14 +706,17 @@ func removeContainerForCompose(composeName, composePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var removeErrors []error
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
_ = client.ContainerRemove(ctx, c.ID, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
if err := client.ContainerRemove(ctx, c.ID, container.RemoveOptions{RemoveVolumes: true, Force: true}); err != nil {
|
||||||
|
removeErrors = append(removeErrors, fmt.Errorf("remove container %s failed: %w", c.ID, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return errors.Join(removeErrors...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func recreateCompose(content, path, projectName string) error {
|
func recreateCompose(content, composePath, detailPath, projectName string) error {
|
||||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
|
file, err := os.OpenFile(detailPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -738,7 +725,7 @@ func recreateCompose(content, path, projectName string) error {
|
|||||||
_, _ = write.WriteString(content)
|
_, _ = write.WriteString(content)
|
||||||
write.Flush()
|
write.Flush()
|
||||||
|
|
||||||
if stdout, err := compose.Up(path, projectName); err != nil {
|
if stdout, err := compose.Up(composePath, projectName); err != nil {
|
||||||
return errors.New(string(stdout))
|
return errors.New(string(stdout))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -746,6 +733,9 @@ func recreateCompose(content, path, projectName string) error {
|
|||||||
|
|
||||||
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
|
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
|
||||||
for i := 0; i < len(list); i++ {
|
for i := 0; i < len(list); i++ {
|
||||||
|
if list[i].Path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
tmpPath := list[i].Path
|
tmpPath := list[i].Path
|
||||||
if strings.Contains(list[i].Path, ",") {
|
if strings.Contains(list[i].Path, ",") {
|
||||||
tmpPath = strings.Split(list[i].Path, ",")[0]
|
tmpPath = strings.Split(list[i].Path, ",")[0]
|
||||||
@@ -774,3 +764,35 @@ func newComposeEnv(pathItem string, env string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveComposeFiles(workdir, configFile string) ([]string, error) {
|
||||||
|
workdir = strings.TrimSpace(workdir)
|
||||||
|
var files []string
|
||||||
|
for _, item := range strings.Split(configFile, ",") {
|
||||||
|
file := strings.TrimSpace(item)
|
||||||
|
if file == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(file) {
|
||||||
|
if !filepath.IsAbs(workdir) {
|
||||||
|
return nil, fmt.Errorf("cannot resolve compose file %s without an absolute working directory", file)
|
||||||
|
}
|
||||||
|
file = filepath.Join(workdir, file)
|
||||||
|
}
|
||||||
|
files = append(files, filepath.Clean(file))
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return nil, fmt.Errorf("compose config file is not specified")
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func composeFileExists(files []string) bool {
|
||||||
|
for _, file := range files {
|
||||||
|
info, err := os.Stat(file)
|
||||||
|
if err == nil && info.Mode().IsRegular() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,16 +2,24 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var networkCleanupSlot = make(chan struct{}, 1)
|
||||||
|
|
||||||
func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -172,3 +180,114 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanUnusedNetworks(t *task.Task, cli *client.Client) error {
|
||||||
|
ctx := t.TaskCtx
|
||||||
|
select {
|
||||||
|
case networkCleanupSlot <- struct{}{}:
|
||||||
|
defer func() { <-networkCleanupSlot }()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
networks, err := cli.NetworkList(ctx, network.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
deleted, skipped, failed := 0, 0, 0
|
||||||
|
defer func() {
|
||||||
|
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
|
||||||
|
"deleted": deleted, "skipped": skipped, "failed": failed,
|
||||||
|
}))
|
||||||
|
}()
|
||||||
|
var used map[string]bool
|
||||||
|
sort.Slice(networks, func(i, j int) bool { return networks[i].Name < networks[j].Name })
|
||||||
|
for _, n := range networks {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch n.Name {
|
||||||
|
case "none", "host", "bridge", "1panel-network":
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n.Scope != "local" || n.Ingress || n.ConfigOnly {
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
values := map[string]interface{}{"name": n.Name, "id": n.ID}
|
||||||
|
if used == nil {
|
||||||
|
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
used = make(map[string]bool)
|
||||||
|
for _, c := range containers {
|
||||||
|
if c.NetworkSettings == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for name, endpoint := range c.NetworkSettings.Networks {
|
||||||
|
used[name] = true
|
||||||
|
if endpoint != nil {
|
||||||
|
used[endpoint.NetworkID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if used[n.Name] || used[n.ID] {
|
||||||
|
skipped++
|
||||||
|
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inspected, err := cli.NetworkInspect(ctx, n.ID, network.InspectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
skipped++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
t.Logf("Failed to inspect network [%s] (%s): %v", n.Name, n.ID, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(inspected.Containers) > 0 {
|
||||||
|
skipped++
|
||||||
|
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cli.NetworkRemove(ctx, n.ID); err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case errdefs.IsNotFound(err):
|
||||||
|
skipped++
|
||||||
|
case errdefs.IsConflict(err):
|
||||||
|
skipped++
|
||||||
|
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
|
||||||
|
default:
|
||||||
|
failed++
|
||||||
|
t.Logf("Failed to remove network [%s] (%s): %v", n.Name, n.ID, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if failed > 0 {
|
||||||
|
return errors.New(i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -17,12 +23,15 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||||
"github.com/docker/docker/api/types/container"
|
dockerclient "github.com/docker/docker/client"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RedisService struct{}
|
type RedisService struct{}
|
||||||
|
|
||||||
|
// The CLI container is shared by all remote Redis databases on this node.
|
||||||
|
var redisCliInstallMutex sync.Mutex
|
||||||
|
|
||||||
type IRedisService interface {
|
type IRedisService interface {
|
||||||
UpdateConf(req dto.RedisConfUpdate) error
|
UpdateConf(req dto.RedisConfUpdate) error
|
||||||
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
||||||
@@ -33,7 +42,7 @@ type IRedisService interface {
|
|||||||
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
||||||
|
|
||||||
CheckHasCli() bool
|
CheckHasCli() bool
|
||||||
InstallCli() error
|
InstallCli(req dto.RedisCliInstall) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIRedisService() IRedisService {
|
func NewIRedisService() IRedisService {
|
||||||
@@ -61,30 +70,65 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) CheckHasCli() bool {
|
func (u *RedisService) CheckHasCli() bool {
|
||||||
client, err := docker.NewDockerClient()
|
installed, _ := u.checkCliInstalled()
|
||||||
if err != nil {
|
return installed
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
containerLists, err := client.ContainerList(context.Background(), container.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, item := range containerLists {
|
|
||||||
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) InstallCli() error {
|
func (u *RedisService) checkCliInstalled() (bool, error) {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
info, err := client.ContainerInspect(context.Background(), "1Panel-redis-cli-tools")
|
||||||
|
if dockerclient.IsErrNotFound(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return info.ContainerJSONBase != nil && info.State != nil && info.State.Running, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (string, error) {
|
||||||
|
if !redisCliInstallMutex.TryLock() {
|
||||||
|
return "", buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
|
defer redisCliInstallMutex.Unlock()
|
||||||
item := dto.ContainerOperate{
|
item := dto.ContainerOperate{
|
||||||
|
TaskID: req.TaskID,
|
||||||
Name: "1Panel-redis-cli-tools",
|
Name: "1Panel-redis-cli-tools",
|
||||||
Image: "redis:7.4.4",
|
Image: "redis:7.4.4",
|
||||||
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
||||||
}
|
}
|
||||||
return NewIContainerService().ContainerCreate(item, false)
|
running, err := taskRepo.GetFirst(
|
||||||
|
repo.WithByName(task.GetTaskName(item.Name, task.TaskCreate, task.TaskScopeContainer)),
|
||||||
|
repo.WithByType(task.TaskScopeContainer),
|
||||||
|
taskRepo.WithByStatus(constant.StatusExecuting),
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if running.ID != "" {
|
||||||
|
return running.ID, nil
|
||||||
|
}
|
||||||
|
installed, err := u.checkCliInstalled()
|
||||||
|
if err != nil || installed {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if item.TaskID == "" {
|
||||||
|
item.TaskID = uuid.NewString()
|
||||||
|
}
|
||||||
|
if _, err := taskRepo.GetFirst(taskRepo.WithByID(item.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
|
if err := NewIContainerService().ContainerCreate(item, true); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return item.TaskID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||||
|
|||||||
@@ -108,9 +108,7 @@ func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystem
|
|||||||
status.Reason = constant.FirewallBackendNotInstalled
|
status.Reason = constant.FirewallBackendNotInstalled
|
||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
status.IsExist = true
|
return status, err
|
||||||
status.Message = err.Error()
|
|
||||||
return status, nil
|
|
||||||
}
|
}
|
||||||
status.IsExist = true
|
status.IsExist = true
|
||||||
runtimeStatus, err := lifecycle.LoadStatus(client)
|
runtimeStatus, err := lifecycle.LoadStatus(client)
|
||||||
|
|||||||
@@ -582,6 +582,18 @@ func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallR
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
importPrioritySupported := true
|
||||||
|
if adapter, ok := runtime.(*filterfirewalld.Adapter); ok && slices.ContainsFunc(request.Items, func(item dto.FirewallRuleCreateItem) bool {
|
||||||
|
return item.SourceKind == constant.FirewallRuleSourceImported
|
||||||
|
}) {
|
||||||
|
importPrioritySupported, err = adapter.SupportsRichRulePriority(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
if !importPrioritySupported && t != nil {
|
||||||
|
t.Log("firewalld does not support rich rule priority; ignoring priority for imported rules, original rule ordering is not preserved")
|
||||||
|
}
|
||||||
|
}
|
||||||
preparedItems := make([]*createItem, 0, len(request.Items))
|
preparedItems := make([]*createItem, 0, len(request.Items))
|
||||||
var stop error
|
var stop error
|
||||||
for index, item := range request.Items {
|
for index, item := range request.Items {
|
||||||
@@ -593,6 +605,16 @@ func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallR
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item.Rule.OrderIndex = nil
|
item.Rule.OrderIndex = nil
|
||||||
|
if item.SourceKind == constant.FirewallRuleSourceImported && !importPrioritySupported {
|
||||||
|
item.Rule.Priority = nil
|
||||||
|
if item.Rule.Scope.Provider == filter.ProviderFirewalld && item.Raw != "" && item.ParseStatus != "" && item.ParseStatus != filter.ParseStatusSupported && item.Rule.NativeKind != filter.NativeKindZoneService {
|
||||||
|
item.Raw, err = filterfirewalld.RemoveRichRulePriority(item.Raw)
|
||||||
|
if err != nil {
|
||||||
|
record(createItem{index: index, request: item}, "skipped", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
rules, err := expandFirewallCreateRule(item, selected)
|
rules, err := expandFirewallCreateRule(item, selected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
status := "failed"
|
status := "failed"
|
||||||
@@ -693,7 +715,7 @@ func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallR
|
|||||||
record(*item, "skipped", buserr.New("ErrRecordExist"))
|
record(*item, "skipped", buserr.New("ErrRecordExist"))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if converted {
|
if converted && importPrioritySupported {
|
||||||
priority++
|
priority++
|
||||||
}
|
}
|
||||||
if existing[key] {
|
if existing[key] {
|
||||||
@@ -713,7 +735,7 @@ func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallR
|
|||||||
}
|
}
|
||||||
ufwActions[matchKey] = map[filter.Action]bool{item.request.Rule.Action: true}
|
ufwActions[matchKey] = map[filter.Action]bool{item.request.Rule.Action: true}
|
||||||
}
|
}
|
||||||
if converted {
|
if converted && importPrioritySupported {
|
||||||
if priority > 32767 {
|
if priority > 32767 {
|
||||||
return result, fmt.Errorf("%w: firewalld import order exceeds the priority range", filter.ErrExpansionLimit)
|
return result, fmt.Errorf("%w: firewalld import order exceeds the priority range", filter.ErrExpansionLimit)
|
||||||
}
|
}
|
||||||
@@ -729,18 +751,19 @@ func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallR
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item.request = prepared
|
item.request = prepared
|
||||||
actualKey, err := firewallRuleDuplicateKey(filter.ObservedRule{Rule: prepared.Rule, ParseStatus: prepared.ParseStatus}, false)
|
}
|
||||||
|
pending[key] = true
|
||||||
|
if converted {
|
||||||
|
actualKey, err := firewallRuleDuplicateKey(filter.ObservedRule{Rule: item.request.Rule, ParseStatus: item.request.ParseStatus}, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
pending[key] = true
|
|
||||||
if existing[actualKey] || pending[actualKey] {
|
if existing[actualKey] || pending[actualKey] {
|
||||||
record(*item, "skipped", buserr.New("ErrRecordExist"))
|
record(*item, "skipped", buserr.New("ErrRecordExist"))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pending[actualKey] = true
|
pending[actualKey] = true
|
||||||
}
|
}
|
||||||
pending[key] = true
|
|
||||||
mergedItems = append(mergedItems, item)
|
mergedItems = append(mergedItems, item)
|
||||||
}
|
}
|
||||||
for _, item := range mergedItems {
|
for _, item := range mergedItems {
|
||||||
@@ -1185,15 +1208,17 @@ func loadForwardingFirewallOverview(manager forwarding.Adapter, families []strin
|
|||||||
}
|
}
|
||||||
ipv6, ipv6Err := loadForwardingFamilyInfo(manager, constant.FirewallFamilyIPv6)
|
ipv6, ipv6Err := loadForwardingFamilyInfo(manager, constant.FirewallFamilyIPv6)
|
||||||
if ipv6.Available {
|
if ipv6.Available {
|
||||||
interfaces, err := forwarding.IPv6RAInterfaces(os.ReadFile)
|
interfaces, err := forwarding.IPv6RAInterfaces(os.ReadFile, forwarding.LoadDockerIPv4BridgePorts)
|
||||||
var pathError *os.PathError
|
var pathError *os.PathError
|
||||||
switch {
|
switch {
|
||||||
case errors.As(err, &pathError) && errors.Is(err, os.ErrNotExist) && pathError.Path == "/proc/net/if_inet6":
|
case errors.As(err, &pathError) && errors.Is(err, os.ErrNotExist) && pathError.Path == "/proc/net/if_inet6":
|
||||||
ipv6.Available, ipv6.Initialized, ipv6.Bound = false, false, false
|
ipv6.Available, ipv6.Initialized, ipv6.Bound = false, false, false
|
||||||
case err != nil:
|
case err != nil:
|
||||||
|
global.LOG.Warnf("IPv6 forwarding RA check failed: %v", err)
|
||||||
ipv6.Reason = "ipv6_ra_check_failed"
|
ipv6.Reason = "ipv6_ra_check_failed"
|
||||||
ipv6.Bound = false
|
ipv6.Bound = false
|
||||||
case len(interfaces) > 0:
|
case len(interfaces) > 0:
|
||||||
|
global.LOG.Warnf("IPv6 forwarding RA risk: interfaces %s may depend on RA/SLAAC with accept_ra=1", strings.Join(interfaces, ", "))
|
||||||
ipv6.Reason, ipv6.RAInterfaces = "ipv6_ra_required", interfaces
|
ipv6.Reason, ipv6.RAInterfaces = "ipv6_ra_required", interfaces
|
||||||
ipv6.Bound = false
|
ipv6.Bound = false
|
||||||
case !ipv6.Bound:
|
case !ipv6.Bound:
|
||||||
|
|||||||
@@ -9,13 +9,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
|
||||||
@@ -82,23 +79,6 @@ func (s *ForwardingService) LoadBaseInfo(ctx context.Context) (dto.FirewallSubsy
|
|||||||
baseInfo.PingStatus = firewall.LoadPingStatus()
|
baseInfo.PingStatus = firewall.LoadPingStatus()
|
||||||
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
|
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
|
||||||
baseInfo.IPv4, baseInfo.IPv6 = status.IPv4, status.IPv6
|
baseInfo.IPv4, baseInfo.IPv6 = status.IPv4, status.IPv6
|
||||||
for _, family := range []struct {
|
|
||||||
command string
|
|
||||||
status *dto.FirewallBackendFamilyStatus
|
|
||||||
}{
|
|
||||||
{"iptables", &baseInfo.IPv4},
|
|
||||||
{"ip6tables", &baseInfo.IPv6},
|
|
||||||
} {
|
|
||||||
if family.command == "ip6tables" && !baseInfo.IPv6Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
policy, err := loadForwardPolicy(ctx, family.command)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Warnf("inspect %s FORWARD policy: %v", family.command, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
family.status.ForwardPolicy = policy
|
|
||||||
}
|
|
||||||
return baseInfo, nil
|
return baseInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,8 +262,6 @@ func (s *ForwardingService) Restore(ctx context.Context) error {
|
|||||||
if err := manager.OperateFamily(family, missing[family]); err != nil {
|
if err := manager.OperateFamily(family, missing[family]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for _, family := range families {
|
|
||||||
if !missing[family] {
|
if !missing[family] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -308,26 +286,3 @@ func (s *ForwardingService) Restore(ctx context.Context) error {
|
|||||||
func NewIForwardingService() IForwardingService {
|
func NewIForwardingService() IForwardingService {
|
||||||
return newForwardingService()
|
return newForwardingService()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadForwardPolicy(ctx context.Context, command string) (string, error) {
|
|
||||||
if !cmd.Which(command) {
|
|
||||||
command += "-nft"
|
|
||||||
if !cmd.Which(command) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output, err := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second)).RunWithOptionalSudoAndStdout(command, "-t", "filter", "-w", "2", "-S", "FORWARD")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(output, "\n") {
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) == 3 && fields[0] == "-P" && fields[1] == "FORWARD" {
|
|
||||||
if fields[2] != "ACCEPT" && fields[2] != "DROP" {
|
|
||||||
return "", fmt.Errorf("unexpected FORWARD policy: %s", fields[2])
|
|
||||||
}
|
|
||||||
return fields[2], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("FORWARD default policy was not found")
|
|
||||||
}
|
|
||||||
|
|||||||
+30
-15
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/moby/go-archive"
|
"github.com/moby/go-archive"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -325,18 +326,16 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
client, err := docker.NewDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
taskItem.Log("Failed to create Docker client: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
for _, itemPath := range req.Paths {
|
for _, itemPath := range req.Paths {
|
||||||
currentPath := itemPath
|
currentPath := itemPath
|
||||||
itemName := path.Base(currentPath)
|
itemName := path.Base(currentPath)
|
||||||
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
||||||
taskItem.Logf("----------------- %s -----------------", itemName)
|
taskItem.Logf("----------------- %s -----------------", itemName)
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
file, err := os.Open(currentPath)
|
file, err := os.Open(currentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -347,14 +346,7 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
content, err := io.ReadAll(res.Body)
|
return consumeImageLoadResponse(res.Body, taskItem.Log)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.Contains(string(content), "Error") {
|
|
||||||
return errors.New(string(content))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
_ = taskItem.Execute()
|
_ = taskItem.Execute()
|
||||||
@@ -362,6 +354,29 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Docker may report load failures in a successful HTTP response's JSON stream.
|
||||||
|
func consumeImageLoadResponse(reader io.Reader, log func(string)) error {
|
||||||
|
decoder := json.NewDecoder(reader)
|
||||||
|
for {
|
||||||
|
var message jsonmessage.JSONMessage
|
||||||
|
if err := decoder.Decode(&message); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if message.Error != nil && message.Error.Message != "" {
|
||||||
|
return message.Error
|
||||||
|
}
|
||||||
|
if message.ErrorMessage != "" {
|
||||||
|
return errors.New(message.ErrorMessage)
|
||||||
|
}
|
||||||
|
if text := strings.TrimSpace(message.Stream); text != "" {
|
||||||
|
log(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ImageService) ImageSave(req dto.ImageSave) error {
|
func (u *ImageService) ImageSave(req dto.ImageSave) error {
|
||||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskExport, task.TaskScopeImage, req.TaskID, 1)
|
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskExport, task.TaskScopeImage, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ type IMonitorService interface {
|
|||||||
|
|
||||||
LoadIOOptions() []string
|
LoadIOOptions() []string
|
||||||
LoadNetworkOptions() []string
|
LoadNetworkOptions() []string
|
||||||
LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error)
|
LoadVLLMMonitorData(ctx context.Context, req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error)
|
||||||
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error)
|
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error)
|
||||||
CleanVLLMMonitor(req dto.MonitorVLLMClean) error
|
CleanVLLMMonitor(req dto.MonitorVLLMClean) error
|
||||||
LoadGPUOptions() dto.MonitorGPUOptions
|
LoadGPUOptions() dto.MonitorGPUOptions
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ var vllmMetricsClient = &http.Client{
|
|||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
|
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorService) LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error) {
|
func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error) {
|
||||||
data := dto.MonitorVLLMData{Points: []model.MonitorVLLM{}}
|
data := dto.MonitorVLLMData{Points: []model.MonitorVLLM{}}
|
||||||
if !req.EndTime.After(req.StartTime) {
|
if !req.EndTime.After(req.StartTime) {
|
||||||
return data, fmt.Errorf("invalid vLLM history request")
|
return data, fmt.Errorf("invalid vLLM history request")
|
||||||
}
|
}
|
||||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
|
install, err := appInstallRepo.GetFirstByCtx(ctx, repo.WithByID(req.AppInstallID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
@@ -49,23 +49,31 @@ func (m *MonitorService) LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.Mon
|
|||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc)
|
req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc)
|
||||||
data.SampleCount, err = vllmMonitorRepo.Count(req.AppInstallID, req.StartTime, req.EndTime)
|
data.SampleCount, err = vllmMonitorRepo.Count(ctx, req.AppInstallID, req.StartTime, req.EndTime)
|
||||||
if err != nil || data.SampleCount == 0 {
|
if err != nil || data.SampleCount == 0 {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
if data.SampleCount > 1200 {
|
if data.SampleCount > 1200 {
|
||||||
data.BucketSeconds = (req.EndTime.Unix() - req.StartTime.Unix() + 600) / 600
|
data.BucketSeconds = (req.EndTime.Unix() - req.StartTime.Unix() + 600) / 600
|
||||||
}
|
}
|
||||||
points, err := vllmMonitorRepo.History(req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation)
|
points, err := vllmMonitorRepo.History(ctx, req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation, req.Group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
if data.BucketSeconds > 0 {
|
if data.BucketSeconds > 0 {
|
||||||
if req.Aggregation != "max" {
|
aggregates := map[int64]*vllm.HistoryAccumulator{}
|
||||||
for i := range points {
|
err = vllmMonitorRepo.WalkDeltas(ctx, req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation, req.Group, func(bucket int64, histograms, requests string) error {
|
||||||
if err := vllm.AggregateHistograms(points[i].HistogramSamples, &points[i].MonitorVLLM); err != nil {
|
if aggregates[bucket] == nil {
|
||||||
return data, err
|
aggregates[bucket] = &vllm.HistoryAccumulator{}
|
||||||
}
|
}
|
||||||
|
return aggregates[bucket].Add(histograms, requests, req.Aggregation, req.Group)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
for i := range points {
|
||||||
|
if aggregate := aggregates[points[i].Bucket]; aggregate != nil {
|
||||||
|
aggregate.Apply(&points[i].MonitorVLLM, req.Aggregation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next := 0
|
next := 0
|
||||||
@@ -93,6 +101,14 @@ func (m *MonitorService) LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.Mon
|
|||||||
}
|
}
|
||||||
interval := time.Duration(intervalSeconds) * time.Second
|
interval := time.Duration(intervalSeconds) * time.Second
|
||||||
for i, point := range points {
|
for i, point := range points {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
var aggregate vllm.HistoryAccumulator
|
||||||
|
if err := aggregate.Add(point.HistogramDeltas, point.RequestDeltas, "max", req.Group); err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
aggregate.Apply(&point.MonitorVLLM, "max")
|
||||||
if i > 0 && point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
|
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, model.MonitorVLLM{CreatedAt: points[i-1].CreatedAt.Add(interval), AppInstallID: req.AppInstallID})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,6 +494,9 @@ func restartCompose(composePath string, itemHelper *snapRecoverHelper) error {
|
|||||||
func recoverDBData(src string, itemHelper *snapRecoverHelper) error {
|
func recoverDBData(src string, itemHelper *snapRecoverHelper) error {
|
||||||
itemHelper.Task.Log("---------------------- 11 / 11 ----------------------")
|
itemHelper.Task.Log("---------------------- 11 / 11 ----------------------")
|
||||||
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDBData"))
|
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDBData"))
|
||||||
|
if err := os.Chmod(src, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
dbDir := path.Join(global.Dir.DataDir, "db")
|
dbDir := path.Join(global.Dir.DataDir, "db")
|
||||||
if err := os.RemoveAll(dbDir); err != nil {
|
if err := os.RemoveAll(dbDir); err != nil {
|
||||||
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), err)
|
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), err)
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
|||||||
taskItem.AddSubTask(
|
taskItem.AddSubTask(
|
||||||
i18n.GetWithName("SnapCopy", global.Dir.BaseDir),
|
i18n.GetWithName("SnapCopy", global.Dir.BaseDir),
|
||||||
func(t *task.Task) error {
|
func(t *task.Task) error {
|
||||||
|
if err := os.Chmod(path.Join(rootDir, "1panel/db"), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return FileOp.CopyDir(path.Join(rootDir, "1panel"), global.Dir.BaseDir)
|
return FileOp.CopyDir(path.Join(rootDir, "1panel"), global.Dir.BaseDir)
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ func (u *TaskLogService) ReadByLine(req request.TaskLogReadReq) (*response.FileL
|
|||||||
if req.TaskID != "" {
|
if req.TaskID != "" {
|
||||||
opts = append(opts, taskRepo.WithByID(req.TaskID))
|
opts = append(opts, taskRepo.WithByID(req.TaskID))
|
||||||
} else {
|
} else {
|
||||||
opts = append(opts, repo.WithOrderRuleBy("created_at", "desc"), repo.WithByType(req.TaskType), taskRepo.WithOperate(req.TaskOperate), taskRepo.WithResourceID(req.ResourceID))
|
opts = append(opts, repo.WithOrderRuleBy("created_at", "desc"), repo.WithByType(req.TaskType), taskRepo.WithResourceID(req.ResourceID))
|
||||||
|
if req.TaskOperate != "" {
|
||||||
|
opts = append(opts, taskRepo.WithOperate(req.TaskOperate))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
taskModel, err := taskRepo.GetFirst(opts...)
|
taskModel, err := taskRepo.GetFirst(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -526,10 +526,8 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
|||||||
website.SiteDir = create.SiteDir
|
website.SiteDir = create.SiteDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createdFiles := &websiteCreateFiles{}
|
||||||
configNginx := func(t *task.Task) error {
|
configNginx := func(t *task.Task) error {
|
||||||
if err = configDefaultNginx(website, domains, appInstall, runtime, create.StreamConfig, initialSSL); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if create.Type == constant.Static && create.TemplateOutputID > 0 {
|
if create.Type == constant.Static && create.TemplateOutputID > 0 {
|
||||||
templateOutput, err := websiteTemplateOutputRepo.GetFirst(repo.WithByID(create.TemplateOutputID))
|
templateOutput, err := websiteTemplateOutputRepo.GetFirst(repo.WithByID(create.TemplateOutputID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -541,12 +539,15 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
|||||||
if _, err := os.Stat(templateOutput.OutputPath); err != nil {
|
if _, err := os.Stat(templateOutput.OutputPath); err != nil {
|
||||||
return buserr.New("ErrFileNotFound")
|
return buserr.New("ErrFileNotFound")
|
||||||
}
|
}
|
||||||
if err := copyDir(templateOutput.OutputPath, GetSitePath(*website, SiteIndexDir)); err != nil {
|
if err := createdFiles.copyTemplate(templateOutput.OutputPath, GetSitePath(*website, SiteIndexDir)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err = configDefaultNginx(website, domains, appInstall, runtime, create.StreamConfig, initialSSL, createdFiles); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if website.Type != constant.Stream {
|
if website.Type != constant.Stream {
|
||||||
if err = createWafConfig(website, domains); err != nil {
|
if err = createWafConfig(website, domains, createdFiles); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if create.Type == constant.Runtime {
|
if create.Type == constant.Runtime {
|
||||||
@@ -555,11 +556,21 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore {
|
if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore {
|
||||||
createOpenBasedirConfig(website)
|
if err := createdFiles.write(path.Join(GetSitePath(*website, SiteIndexDir), ".user.ini"), []byte(fmt.Sprintf("open_basedir=/www/sites/%s/index:/tmp/", website.Alias)), 0644, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply the site only after all Nginx, WAF, and PHP configuration is ready.
|
||||||
|
if err := opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
tx, ctx := helper.GetTxAndContext()
|
tx, ctx := helper.GetTxAndContext()
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
if err = websiteRepo.Create(ctx, website); err != nil {
|
if err = websiteRepo.Create(ctx, website); err != nil {
|
||||||
@@ -575,12 +586,19 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.Commit()
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteWebsite := func(t *task.Task) {
|
deleteWebsite := func(t *task.Task) {
|
||||||
_ = deleteWebsiteFolder(website)
|
if rollbackErr := createdFiles.rollback(); rollbackErr != nil {
|
||||||
|
t.Log(fmt.Sprintf("website file rollback: %v", rollbackErr))
|
||||||
|
}
|
||||||
|
if reloadErr := opNginx(nginxInstall.ContainerName, constant.NginxReload); reloadErr != nil {
|
||||||
|
t.Log(fmt.Sprintf("reload OpenResty after rollback: %v", reloadErr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createTask.AddSubTask(i18n.GetMsgByKey("ConfigOpenresty"), configNginx, deleteWebsite)
|
createTask.AddSubTask(i18n.GetMsgByKey("ConfigOpenresty"), configNginx, deleteWebsite)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
@@ -46,39 +45,27 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
|
|||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if fileOp.Stat(wafDataPath) {
|
if fileOp.Stat(wafDataPath) {
|
||||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||||
content, err := fileOp.GetContent(websitesConfigPath)
|
if err := updateWAFWebsites(websitesConfigPath, func(websitesArray []request.WafWebsite) ([]request.WafWebsite, error) {
|
||||||
if err != nil {
|
for index, wafWebsite := range websitesArray {
|
||||||
return nil, err
|
if wafWebsite.Key == website.Alias {
|
||||||
}
|
wafSite := request.WafWebsite{
|
||||||
var websitesArray []request.WafWebsite
|
Key: website.Alias,
|
||||||
if content != nil {
|
Domains: wafWebsite.Domains,
|
||||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
Host: wafWebsite.Host,
|
||||||
return nil, err
|
}
|
||||||
|
for _, domain := range domainModels {
|
||||||
|
wafSite.Domains = append(wafSite.Domains, domain.Domain)
|
||||||
|
wafSite.Host = append(wafSite.Host, domain.Domain+":"+strconv.Itoa(domain.Port))
|
||||||
|
}
|
||||||
|
if len(wafSite.Host) == 0 {
|
||||||
|
wafSite.Host = []string{}
|
||||||
|
}
|
||||||
|
websitesArray[index] = wafSite
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return websitesArray, nil
|
||||||
for index, wafWebsite := range websitesArray {
|
}); err != nil {
|
||||||
if wafWebsite.Key == website.Alias {
|
|
||||||
wafSite := request.WafWebsite{
|
|
||||||
Key: website.Alias,
|
|
||||||
Domains: wafWebsite.Domains,
|
|
||||||
Host: wafWebsite.Host,
|
|
||||||
}
|
|
||||||
for _, domain := range domainModels {
|
|
||||||
wafSite.Domains = append(wafSite.Domains, domain.Domain)
|
|
||||||
wafSite.Host = append(wafSite.Host, domain.Domain+":"+strconv.Itoa(domain.Port))
|
|
||||||
}
|
|
||||||
if len(wafSite.Host) == 0 {
|
|
||||||
wafSite.Host = []string{}
|
|
||||||
}
|
|
||||||
websitesArray[index] = wafSite
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
websitesContent, err := json.Marshal(websitesArray)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,51 +122,39 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
|||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
if fileOp.Stat(wafDataPath) {
|
if fileOp.Stat(wafDataPath) {
|
||||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||||
content, err := fileOp.GetContent(websitesConfigPath)
|
if err := updateWAFWebsites(websitesConfigPath, func(websitesArray []request.WafWebsite) ([]request.WafWebsite, error) {
|
||||||
if err != nil {
|
var newWebsitesArray []request.WafWebsite
|
||||||
return err
|
for _, wafWebsite := range websitesArray {
|
||||||
}
|
if wafWebsite.Key == website.Alias {
|
||||||
var websitesArray []request.WafWebsite
|
wafSite := wafWebsite
|
||||||
var newWebsitesArray []request.WafWebsite
|
oldDomains := wafSite.Domains
|
||||||
if content != nil {
|
var newDomains []string
|
||||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
for _, domain := range oldDomains {
|
||||||
return err
|
if domain == webSiteDomain.Domain {
|
||||||
}
|
continue
|
||||||
}
|
}
|
||||||
for _, wafWebsite := range websitesArray {
|
newDomains = append(newDomains, domain)
|
||||||
if wafWebsite.Key == website.Alias {
|
|
||||||
wafSite := wafWebsite
|
|
||||||
oldDomains := wafSite.Domains
|
|
||||||
var newDomains []string
|
|
||||||
for _, domain := range oldDomains {
|
|
||||||
if domain == webSiteDomain.Domain {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
newDomains = append(newDomains, domain)
|
wafSite.Domains = newDomains
|
||||||
}
|
oldHostArray := wafSite.Host
|
||||||
wafSite.Domains = newDomains
|
var newHostArray []string
|
||||||
oldHostArray := wafSite.Host
|
for _, host := range oldHostArray {
|
||||||
var newHostArray []string
|
if host == webSiteDomain.Domain+":"+strconv.Itoa(webSiteDomain.Port) {
|
||||||
for _, host := range oldHostArray {
|
continue
|
||||||
if host == webSiteDomain.Domain+":"+strconv.Itoa(webSiteDomain.Port) {
|
}
|
||||||
continue
|
newHostArray = append(newHostArray, host)
|
||||||
}
|
}
|
||||||
newHostArray = append(newHostArray, host)
|
wafSite.Host = newHostArray
|
||||||
|
if len(wafSite.Host) == 0 {
|
||||||
|
wafSite.Host = []string{}
|
||||||
|
}
|
||||||
|
newWebsitesArray = append(newWebsitesArray, wafSite)
|
||||||
|
} else {
|
||||||
|
newWebsitesArray = append(newWebsitesArray, wafWebsite)
|
||||||
}
|
}
|
||||||
wafSite.Host = newHostArray
|
|
||||||
if len(wafSite.Host) == 0 {
|
|
||||||
wafSite.Host = []string{}
|
|
||||||
}
|
|
||||||
newWebsitesArray = append(newWebsitesArray, wafSite)
|
|
||||||
} else {
|
|
||||||
newWebsitesArray = append(newWebsitesArray, wafWebsite)
|
|
||||||
}
|
}
|
||||||
}
|
return newWebsitesArray, nil
|
||||||
websitesContent, err := json.Marshal(newWebsitesArray)
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,38 +403,3 @@ func renderMainHTMLFromZip(zipPath string, values map[string]string) (string, er
|
|||||||
}
|
}
|
||||||
return renderContent(string(content), values), nil
|
return renderContent(string(content), values), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyDir(src, dst string) error {
|
|
||||||
return filepath.Walk(src, func(p string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
relPath, err := filepath.Rel(src, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
targetPath := filepath.Join(dst, relPath)
|
|
||||||
if info.IsDir() {
|
|
||||||
return os.MkdirAll(targetPath, constant.DirPerm)
|
|
||||||
}
|
|
||||||
return copyFile(p, targetPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
|
||||||
srcFile, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer srcFile.Close()
|
|
||||||
if err := os.MkdirAll(filepath.Dir(dst), constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstFile, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dstFile.Close()
|
|
||||||
_, err = io.Copy(dstFile, srcFile)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
+320
-134
@@ -1,11 +1,13 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -124,7 +127,257 @@ func applyLocationProxyPass(location *components.Location, proxyPass string, sni
|
|||||||
location.UpdateDirective("proxy_ssl_name", []string{sslName})
|
location.UpdateDirective("proxy_ssl_name", []string{sslName})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createIndexFile(website *model.Website, runtime *model.Runtime) error {
|
// websiteCreateFiles owns only paths actually created by this attempt.
|
||||||
|
// Never recursively remove a reused directory (or a newly populated one).
|
||||||
|
type websiteCreateFiles struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
entries []websiteCreatedPath
|
||||||
|
rolledBack bool
|
||||||
|
restores []func() error
|
||||||
|
}
|
||||||
|
type websiteCreatedPath struct {
|
||||||
|
path string
|
||||||
|
info os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *websiteCreateFiles) mkdir(name string) error {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
if f.rolledBack {
|
||||||
|
return fmt.Errorf("website creation already rolled back")
|
||||||
|
}
|
||||||
|
return f.mkdirLocked(filepath.Clean(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *websiteCreateFiles) mkdirLocked(name string) error {
|
||||||
|
info, err := os.Lstat(name)
|
||||||
|
if err == nil {
|
||||||
|
if !info.IsDir() {
|
||||||
|
return fmt.Errorf("website path is not a directory: %s", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = f.mkdirLocked(filepath.Dir(name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = os.Mkdir(name, constant.DirPerm); err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
return f.mkdirLocked(name)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.entries = append(f.entries, websiteCreatedPath{name, info})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// preserveExisting is for user content and logs; generated configuration must
|
||||||
|
// fail on collision. Exclusive creation also protects against concurrent writes.
|
||||||
|
func (f *websiteCreateFiles) write(name string, data []byte, mode os.FileMode, preserveExisting bool) error {
|
||||||
|
return f.writeReader(name, bytes.NewReader(data), mode, preserveExisting)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *websiteCreateFiles) writeReader(name string, data io.Reader, mode os.FileMode, preserveExisting bool) error {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
if f.rolledBack {
|
||||||
|
return fmt.Errorf("website creation already rolled back")
|
||||||
|
}
|
||||||
|
if err := f.mkdirLocked(filepath.Dir(name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode)
|
||||||
|
if os.IsExist(err) && preserveExisting {
|
||||||
|
info, statErr := os.Lstat(name)
|
||||||
|
if statErr != nil {
|
||||||
|
return statErr
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("website path is not a regular file: %s", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create website file %s: %w", name, err)
|
||||||
|
}
|
||||||
|
info, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.entries = append(f.entries, websiteCreatedPath{name, info})
|
||||||
|
_, err = io.Copy(file, data)
|
||||||
|
closeErr := file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *websiteCreateFiles) rollback() error {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
f.rolledBack = true
|
||||||
|
var firstErr error
|
||||||
|
for i := len(f.restores) - 1; i >= 0; i-- {
|
||||||
|
if err := f.restores[i](); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.restores = nil
|
||||||
|
for i := len(f.entries) - 1; i >= 0; i-- {
|
||||||
|
entry := f.entries[i]
|
||||||
|
info, err := os.Lstat(entry.path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !os.SameFile(entry.info, info) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// os.Remove refuses non-empty directories, preserving untracked content.
|
||||||
|
if err = os.Remove(entry.path); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *websiteCreateFiles) copyTemplate(src, dst string) error {
|
||||||
|
// Reject symlinks and create files exclusively. The caller rolls back any
|
||||||
|
// paths created before a collision or copy error.
|
||||||
|
return filepath.Walk(src, func(name string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rel, err := filepath.Rel(src, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target := filepath.Join(dst, rel)
|
||||||
|
if info.IsDir() {
|
||||||
|
return f.mkdir(target)
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("unsupported template file: %s", name)
|
||||||
|
}
|
||||||
|
file, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return f.writeReader(target, file, constant.FilePerm, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only change ownership on files created by this attempt, never recursively on
|
||||||
|
// an existing document root.
|
||||||
|
func (f *websiteCreateFiles) chownNew(root string) error {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
if f.rolledBack {
|
||||||
|
return fmt.Errorf("website creation already rolled back")
|
||||||
|
}
|
||||||
|
for _, entry := range f.entries {
|
||||||
|
rel, err := filepath.Rel(root, entry.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rel == ".." || filepath.IsAbs(rel) || (len(rel) > 3 && rel[:3] == "../") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := os.Lstat(entry.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !os.SameFile(entry.info, info) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := os.Chown(entry.path, constant.WebsiteUID, constant.WebsiteGID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize sites.json read-modify-write operations across website creations,
|
||||||
|
// deletions, domain updates, and creation rollbacks.
|
||||||
|
var wafWebsitesMu sync.Mutex
|
||||||
|
|
||||||
|
func updateWAFWebsites(name string, update func([]request.WafWebsite) ([]request.WafWebsite, error)) error {
|
||||||
|
wafWebsitesMu.Lock()
|
||||||
|
defer wafWebsitesMu.Unlock()
|
||||||
|
info, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("website config is not a regular file: %s", name)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var websites []request.WafWebsite
|
||||||
|
if len(content) > 0 {
|
||||||
|
if err := json.Unmarshal(content, &websites); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
websites, err = update(websites)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
content, err = json.Marshal(websites)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(name, content, info.Mode().Perm())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *websiteCreateFiles) addWAFWebsite(name string, website request.WafWebsite) error {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
if f.rolledBack {
|
||||||
|
return fmt.Errorf("website creation already rolled back")
|
||||||
|
}
|
||||||
|
if err := updateWAFWebsites(name, func(websites []request.WafWebsite) ([]request.WafWebsite, error) {
|
||||||
|
for _, existing := range websites {
|
||||||
|
if existing.Key == website.Key {
|
||||||
|
return nil, fmt.Errorf("WAF website already exists: %s", website.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(websites, website), nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.restores = append(f.restores, func() error {
|
||||||
|
return updateWAFWebsites(name, func(websites []request.WafWebsite) ([]request.WafWebsite, error) {
|
||||||
|
// Keep the latest records for other sites, including concurrent domain changes.
|
||||||
|
remaining := make([]request.WafWebsite, 0, len(websites))
|
||||||
|
for _, existing := range websites {
|
||||||
|
if existing.Key != website.Key {
|
||||||
|
remaining = append(remaining, existing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remaining, nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIndexFile(website *model.Website, runtime *model.Runtime, created *websiteCreateFiles) error {
|
||||||
var (
|
var (
|
||||||
indexPath string
|
indexPath string
|
||||||
indexContent string
|
indexContent string
|
||||||
@@ -145,49 +398,23 @@ func createIndexFile(website *model.Website, runtime *model.Runtime) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileOp := files.NewFileOp()
|
if err := created.write(indexPath, []byte(indexContent), constant.FilePerm, true); err != nil {
|
||||||
if !fileOp.Stat(indexFolder) {
|
return err
|
||||||
if err := fileOp.CreateDir(indexFolder, constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !fileOp.Stat(indexPath) {
|
html404, _ := websiteService.GetDefaultHtml("404")
|
||||||
if err := fileOp.CreateFile(indexPath); err != nil {
|
if err := created.write(path.Join(indexFolder, "404.html"), []byte(html404.Content), constant.FilePerm, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if website.Type == constant.Runtime && runtime.Resource == constant.ResourceAppstore {
|
if website.Type == constant.Runtime && runtime.Resource == constant.ResourceAppstore {
|
||||||
if err := chownRootDir(indexFolder); err != nil {
|
return created.chownNew(indexFolder)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := fileOp.WriteFile(indexPath, strings.NewReader(indexContent), constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
html404, _ := websiteService.GetDefaultHtml("404")
|
|
||||||
path404 := path.Join(indexFolder, "404.html")
|
|
||||||
if err := fileOp.WriteFile(path404, strings.NewReader(html404.Content), constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createProxyFile(website *model.Website) error {
|
func createProxyFile(website *model.Website, created *websiteCreateFiles) error {
|
||||||
proxyFolder := GetSitePath(*website, SiteProxyDir)
|
proxyFolder := GetSitePath(*website, SiteProxyDir)
|
||||||
filePath := path.Join(proxyFolder, "root.conf")
|
filePath := path.Join(proxyFolder, "root.conf")
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
if !fileOp.Stat(proxyFolder) {
|
|
||||||
if err := fileOp.CreateDir(proxyFolder, constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !fileOp.Stat(filePath) {
|
|
||||||
if err := fileOp.CreateFile(filePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config, err := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
config, err := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -201,57 +428,40 @@ func createProxyFile(website *model.Website) error {
|
|||||||
location.ChangePath("^~", "/")
|
location.ChangePath("^~", "/")
|
||||||
applyLocationProxyPass(location, website.Proxy, nil, "")
|
applyLocationProxyPass(location, website.Proxy, nil, "")
|
||||||
location.UpdateDirective("proxy_set_header", []string{"Host", "$host"})
|
location.UpdateDirective("proxy_set_header", []string{"Host", "$host"})
|
||||||
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
if err := created.write(filePath, []byte(nginx.DumpConfig(config, nginx.IndentedStyle)), constant.FilePerm, false); err != nil {
|
||||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWebsiteFolder(website *model.Website, runtime *model.Runtime) error {
|
func createWebsiteFolder(website *model.Website, runtime *model.Runtime, created *websiteCreateFiles) error {
|
||||||
siteFolder := GetSiteDir(website.Alias)
|
siteFolder := GetSiteDir(website.Alias)
|
||||||
fileOp := files.NewFileOp()
|
dirs := []string{siteFolder, path.Join(siteFolder, "log")}
|
||||||
if !fileOp.Stat(siteFolder) {
|
if website.Type != constant.Stream {
|
||||||
if err := fileOp.CreateDir(siteFolder, constant.DirPerm); err != nil {
|
dirs = append(dirs, path.Join(siteFolder, "index"), path.Join(siteFolder, "ssl"))
|
||||||
|
}
|
||||||
|
for _, dir := range dirs {
|
||||||
|
if err := created.mkdir(dir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fileOp.CreateDir(path.Join(siteFolder, "log"), constant.DirPerm); err != nil {
|
}
|
||||||
|
for _, name := range []string{"access.log", "error.log"} {
|
||||||
|
if err := created.write(path.Join(siteFolder, "log", name), nil, constant.FilePerm, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fileOp.CreateFile(path.Join(siteFolder, "log", "access.log")); err != nil {
|
}
|
||||||
|
if website.Type == constant.Runtime && runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal {
|
||||||
|
if err := created.write(path.Join(siteFolder, "php-pool", "php-fpm.sock"), nil, constant.FilePerm, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fileOp.CreateFile(path.Join(siteFolder, "log", "error.log")); err != nil {
|
}
|
||||||
|
if website.Type == constant.Static || (website.Type == constant.Runtime && runtime.Type == constant.RuntimePHP) {
|
||||||
|
if err := createIndexFile(website, runtime, created); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if website.Type != constant.Stream {
|
}
|
||||||
if err := fileOp.CreateDir(path.Join(siteFolder, "index"), constant.DirPerm); err != nil {
|
if website.Type == constant.Proxy {
|
||||||
return err
|
return createProxyFile(website, created)
|
||||||
}
|
|
||||||
if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if website.Type == constant.Runtime {
|
|
||||||
if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal {
|
|
||||||
phpPoolDir := path.Join(siteFolder, "php-pool")
|
|
||||||
if err := fileOp.CreateDir(phpPoolDir, constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fileOp.CreateFile(path.Join(phpPoolDir, "php-fpm.sock")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if website.Type == constant.Static || (website.Type == constant.Runtime && runtime.Type == constant.RuntimePHP) {
|
|
||||||
if err := createIndexFile(website, runtime); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if website.Type == constant.Proxy {
|
|
||||||
if err := createProxyFile(website); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -261,12 +471,12 @@ type websiteInitialSSL struct {
|
|||||||
request request.WebsiteHTTPSOp
|
request request.WebsiteHTTPSOp
|
||||||
}
|
}
|
||||||
|
|
||||||
func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime, streamConfig request.StreamConfig, initialSSL *websiteInitialSSL) error {
|
func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime, streamConfig request.StreamConfig, initialSSL *websiteInitialSSL, created *websiteCreateFiles) error {
|
||||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = createWebsiteFolder(website, runtime); err != nil {
|
if err = createWebsiteFolder(website, runtime, created); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
@@ -334,7 +544,10 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
|||||||
if initialSSL != nil {
|
if initialSSL != nil {
|
||||||
plan := buildWebsiteTLSPlan(domains, nginxInstall.HttpPort, nginxInstall.HttpsPort)
|
plan := buildWebsiteTLSPlan(domains, nginxInstall.HttpPort, nginxInstall.HttpsPort)
|
||||||
applyWebsiteSSLConfig(server, *website, plan, initialSSL.request)
|
applyWebsiteSSLConfig(server, *website, plan, initialSSL.request)
|
||||||
if err = createPemFile(*website, initialSSL.certificate); err != nil {
|
if err = created.write(path.Join(GetSitePath(*website, SiteSSLDir), "fullchain.pem"), []byte(initialSSL.certificate.Pem), constant.FilePerm, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = created.write(path.Join(GetSitePath(*website, SiteSSLDir), "privkey.pem"), []byte(initialSSL.certificate.PrivateKey), 0600, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,18 +617,14 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.FilePath = configPath
|
config.FilePath = configPath
|
||||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
if err = created.write(configPath, []byte(nginx.DumpConfig(config, nginx.IndentedStyle)), constant.FilePerm, false); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultWAFRuleNames = []string{"acl", "args", "cookie", "defaultUaBlack", "defaultUrlBlack", "fileExt", "header", "methodWhite", "cdn"}
|
||||||
|
|
||||||
func moveDefaultWafConfig(websiteDir string, defaultConfigContent []byte, defaultRuleDir string, fileOp files.FileOp) error {
|
func moveDefaultWafConfig(websiteDir string, defaultConfigContent []byte, defaultRuleDir string, fileOp files.FileOp) error {
|
||||||
if !fileOp.Stat(websiteDir) {
|
if !fileOp.Stat(websiteDir) {
|
||||||
if err := fileOp.CreateDir(websiteDir, constant.DirPerm); err != nil {
|
if err := fileOp.CreateDir(websiteDir, constant.DirPerm); err != nil {
|
||||||
@@ -431,8 +640,7 @@ func moveDefaultWafConfig(websiteDir string, defaultConfigContent []byte, defaul
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defaultRulesName := []string{"acl", "args", "cookie", "defaultUaBlack", "defaultUrlBlack", "fileExt", "header", "methodWhite", "cdn"}
|
for _, ruleName := range defaultWAFRuleNames {
|
||||||
for _, ruleName := range defaultRulesName {
|
|
||||||
srcPath := path.Join(defaultRuleDir, ruleName+".json")
|
srcPath := path.Join(defaultRuleDir, ruleName+".json")
|
||||||
if fileOp.Stat(srcPath) {
|
if fileOp.Stat(srcPath) {
|
||||||
_ = fileOp.Copy(srcPath, websiteRuleDir)
|
_ = fileOp.Copy(srcPath, websiteRuleDir)
|
||||||
@@ -470,7 +678,10 @@ func createAllWebsitesWAFConfig(websites []model.Website) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
|
wafWebsitesMu.Lock()
|
||||||
|
err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm)
|
||||||
|
wafWebsitesMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
@@ -499,7 +710,7 @@ func createOpenBasedirConfig(website *model.Website) {
|
|||||||
_ = fileOp.SaveFile(userIniPath, fmt.Sprintf("open_basedir=/www/sites/%s/index:/tmp/", website.Alias), 0644)
|
_ = fileOp.SaveFile(userIniPath, fmt.Sprintf("open_basedir=/www/sites/%s/index:/tmp/", website.Alias), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWafConfig(website *model.Website, domains []model.WebsiteDomain) error {
|
func createWafConfig(website *model.Website, domains []model.WebsiteDomain, created *websiteCreateFiles) error {
|
||||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -510,16 +721,6 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||||
content, err := fileOp.GetContent(websitesConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var websitesArray []request.WafWebsite
|
|
||||||
if len(content) != 0 {
|
|
||||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wafWebsite := request.WafWebsite{
|
wafWebsite := request.WafWebsite{
|
||||||
Key: website.Alias,
|
Key: website.Alias,
|
||||||
Domains: make([]string, 0),
|
Domains: make([]string, 0),
|
||||||
@@ -530,14 +731,6 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro
|
|||||||
wafWebsite.Domains = append(wafWebsite.Domains, domain.Domain)
|
wafWebsite.Domains = append(wafWebsite.Domains, domain.Domain)
|
||||||
wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+strconv.Itoa(domain.Port))
|
wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+strconv.Itoa(domain.Port))
|
||||||
}
|
}
|
||||||
websitesArray = append(websitesArray, wafWebsite)
|
|
||||||
websitesContent, err := json.Marshal(websitesArray)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sitesDir = path.Join(wafDataPath, "sites")
|
sitesDir = path.Join(wafDataPath, "sites")
|
||||||
@@ -551,20 +744,25 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
if err := created.write(path.Join(websiteDir, "config.json"), defaultConfigContent, constant.FilePerm, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := created.mkdir(path.Join(websiteDir, "rules")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, name := range defaultWAFRuleNames {
|
||||||
|
data, err := os.ReadFile(path.Join(defaultRuleDir, name+".json"))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = fileOp.DeleteDir(websiteDir)
|
return err
|
||||||
|
}
|
||||||
|
if err := created.write(path.Join(websiteDir, "rules", name+".json"), data, constant.FilePerm, false); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
if err := moveDefaultWafConfig(websiteDir, defaultConfigContent, defaultRuleDir, fileOp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
if err := created.addWAFWebsite(websitesConfigPath, wafWebsite); err != nil {
|
||||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,27 +825,15 @@ func delWafConfig(website model.Website, force bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||||
content, err := fileOp.GetContent(websitesConfigPath)
|
if err := updateWAFWebsites(websitesConfigPath, func(websites []request.WafWebsite) ([]request.WafWebsite, error) {
|
||||||
if err != nil {
|
remaining := make([]request.WafWebsite, 0, len(websites))
|
||||||
return err
|
for _, existing := range websites {
|
||||||
}
|
if existing.Key != website.Alias {
|
||||||
var websitesArray []request.WafWebsite
|
remaining = append(remaining, existing)
|
||||||
var newWebsiteArray []request.WafWebsite
|
}
|
||||||
if len(content) > 0 {
|
|
||||||
if err = json.Unmarshal(content, &websitesArray); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
return remaining, nil
|
||||||
for _, wafWebsite := range websitesArray {
|
}); err != nil {
|
||||||
if wafWebsite.Key != website.Alias {
|
|
||||||
newWebsiteArray = append(newWebsiteArray, wafWebsite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
websitesContent, err := json.Marshal(newWebsiteArray)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,7 +258,15 @@ func (s *SubTask) Execute() error {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
|
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
|
||||||
err = errors.New("timeout!")
|
err = errors.New("timeout!")
|
||||||
|
if s.RootTask.Task.Type == TaskScopeVm {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
case err = <-done:
|
case err = <-done:
|
||||||
|
if s.RootTask.Task.Type == TaskScopeVm && ctx.Err() != nil {
|
||||||
|
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
|
||||||
|
err = errors.New("timeout!")
|
||||||
|
break
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err))
|
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err))
|
||||||
if err.Error() == i18n.GetMsgByKey("ErrShutDown") {
|
if err.Error() == i18n.GetMsgByKey("ErrShutDown") {
|
||||||
|
|||||||
+348
-352
@@ -265,8 +265,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "应用端口修改 [key]-[name] => [port]",
|
"formatZH": "应用端口修改 [key]-[name] =\u003e [port]",
|
||||||
"formatEN": "Application port update [key]-[name] => [port]"
|
"formatEN": "Application port update [key]-[name] =\u003e [port]"
|
||||||
},
|
},
|
||||||
"/apps/installed/sort/update": {
|
"/apps/installed/sort/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -679,8 +679,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "容器重命名 [name] => [newName]",
|
"formatZH": "容器重命名 [name] =\u003e [newName]",
|
||||||
"formatEN": "rename container [name] => [newName]"
|
"formatEN": "rename container [name] =\u003e [newName]"
|
||||||
},
|
},
|
||||||
"/containers/repo": {
|
"/containers/repo": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -831,8 +831,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新 API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||||
"formatEN": "update api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||||
},
|
},
|
||||||
"/core/auth/expired/reset": {
|
"/core/auth/expired/reset": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -1716,8 +1716,8 @@
|
|||||||
"output_value": "name"
|
"output_value": "name"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "更新用户 [name] API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||||
"formatEN": "update user [name] api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
"formatEN": "update user [name] api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||||
},
|
},
|
||||||
"/core/enterprise/users/del": {
|
"/core/enterprise/users/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -1918,8 +1918,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]",
|
"formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||||
"formatEN": "update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]"
|
"formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]"
|
||||||
},
|
},
|
||||||
"/core/settings/memo": {
|
"/core/settings/memo": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -1948,8 +1948,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统端口 => [serverPort]",
|
"formatZH": "修改系统端口 =\u003e [serverPort]",
|
||||||
"formatEN": "update system port => [serverPort]"
|
"formatEN": "update system port =\u003e [serverPort]"
|
||||||
},
|
},
|
||||||
"/core/settings/proxy/update": {
|
"/core/settings/proxy/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -1974,8 +1974,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统 ssl => [ssl]",
|
"formatZH": "修改系统 ssl =\u003e [ssl]",
|
||||||
"formatEN": "update system ssl => [ssl]"
|
"formatEN": "update system ssl =\u003e [ssl]"
|
||||||
},
|
},
|
||||||
"/core/settings/terminal/update": {
|
"/core/settings/terminal/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -1991,8 +1991,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统配置 [key] => [value]",
|
"formatZH": "修改系统配置 [key] =\u003e [value]",
|
||||||
"formatEN": "update system setting [key] => [value]"
|
"formatEN": "update system setting [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/core/settings/upgrade": {
|
"/core/settings/upgrade": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -2000,8 +2000,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新系统 => [version]",
|
"formatZH": "更新系统 =\u003e [version]",
|
||||||
"formatEN": "upgrade system => [version]"
|
"formatEN": "upgrade system =\u003e [version]"
|
||||||
},
|
},
|
||||||
"/core/xapp/appQRCode": {
|
"/core/xapp/appQRCode": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -2459,282 +2459,6 @@
|
|||||||
"formatZH": "同步 SSL 证书 [primaryDomain]",
|
"formatZH": "同步 SSL 证书 [primaryDomain]",
|
||||||
"formatEN": "sync SSL certificate [primaryDomain]"
|
"formatEN": "sync SSL certificate [primaryDomain]"
|
||||||
},
|
},
|
||||||
"/xpack/vms": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机 [name]",
|
|
||||||
"formatEN": "create VM [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "virtual_machines",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机 [name]",
|
|
||||||
"formatEN": "delete VM [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/disks/resize": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"target",
|
|
||||||
"sizeGiB"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "扩容虚拟机 [name] 磁盘 [target] 至 [sizeGiB] GiB",
|
|
||||||
"formatEN": "expand VM [name] disk [target] to [sizeGiB] GiB"
|
|
||||||
},
|
|
||||||
"/xpack/vms/environment/enable": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "启用虚拟机运行环境",
|
|
||||||
"formatEN": "enable VM runtime environment"
|
|
||||||
},
|
|
||||||
"/xpack/vms/iso": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机镜像 [name] [type]",
|
|
||||||
"formatEN": "create VM ISO [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/iso/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_isos",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机镜像 [name]",
|
|
||||||
"formatEN": "delete VM ISO [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/iso/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "更新虚拟机镜像 [name] [type]",
|
|
||||||
"formatEN": "update VM ISO [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/networks": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机网络 [name] [type]",
|
|
||||||
"formatEN": "create VM network [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/networks/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_networks",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机网络 [name]",
|
|
||||||
"formatEN": "delete VM network [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/networks/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_networks",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "更新虚拟机网络 [name]",
|
|
||||||
"formatEN": "update VM network [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/operate": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"operate"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "操作虚拟机 [name] [operate]",
|
|
||||||
"formatEN": "operate VM [name] [operate]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/orphans/clean": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "清理虚拟机孤立数据",
|
|
||||||
"formatEN": "clean VM orphaned data"
|
|
||||||
},
|
|
||||||
"/xpack/vms/rename": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"newName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "重命名虚拟机 [name] 为 [newName]",
|
|
||||||
"formatEN": "rename VM [name] to [newName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/snapshots": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"snapshotName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
|
|
||||||
"formatEN": "create VM [name] snapshot [snapshotName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/snapshots/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"snapshotName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
|
|
||||||
"formatEN": "delete VM [name] snapshot [snapshotName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/snapshots/recover": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"snapshotName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
|
|
||||||
"formatEN": "recover VM [name] snapshot [snapshotName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/storages": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机存储 [name] [type]",
|
|
||||||
"formatEN": "create VM storage [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/storages/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_storages",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机存储 [name]",
|
|
||||||
"formatEN": "delete VM storage [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/storages/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_storages",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "更新虚拟机存储 [name]",
|
|
||||||
"formatEN": "update VM storage [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/sync": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "同步虚拟机",
|
|
||||||
"formatEN": "sync virtual machines"
|
|
||||||
},
|
|
||||||
"/xpack/vms/templates": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"templateName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
|
|
||||||
"formatEN": "create VM [name] template [templateName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/templates/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_templates",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机模板 [name]",
|
|
||||||
"formatEN": "delete VM template [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "更新虚拟机 [name]",
|
|
||||||
"formatEN": "update VM [name]"
|
|
||||||
},
|
|
||||||
"/cronjobs": {
|
"/cronjobs": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"type",
|
"type",
|
||||||
@@ -2888,8 +2612,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "首页应用 [key] => 显示:[value]",
|
"formatZH": "首页应用 [key] =\u003e 显示:[value]",
|
||||||
"formatEN": "app launcher [key] => show: [value]"
|
"formatEN": "app launcher [key] =\u003e show: [value]"
|
||||||
},
|
},
|
||||||
"/dashboard/quick/change": {
|
"/dashboard/quick/change": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -3017,7 +2741,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]",
|
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the mysql database [name] is modified => [description]"
|
"formatEN": "The description of the mysql database [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/databases/grants": {
|
"/databases/grants": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3098,7 +2822,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "mongodb 数据库 [name] 描述信息修改 [description]",
|
"formatZH": "mongodb 数据库 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the mongodb database [name] is modified => [description]"
|
"formatEN": "The description of the mongodb database [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/databases/mongodb/password": {
|
"/databases/mongodb/password": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3183,7 +2907,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]",
|
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the postgresql database [name] is modified => [description]"
|
"formatEN": "The description of the postgresql database [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/databases/pg/password": {
|
"/databases/pg/password": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3288,8 +3012,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] => [newHost] 描述 [description]",
|
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] =\u003e [newHost] 描述 [description]",
|
||||||
"formatEN": "update mysql database [database] user [username] access [host] => [newHost] description [description]"
|
"formatEN": "update mysql database [database] user [username] access [host] =\u003e [newHost] description [description]"
|
||||||
},
|
},
|
||||||
"/databases/variables/update": {
|
"/databases/variables/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -3325,8 +3049,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "批量修改文件权限和用户/组 [paths] => [mode]/[user]/[group]",
|
"formatZH": "批量修改文件权限和用户/组 [paths] =\u003e [mode]/[user]/[group]",
|
||||||
"formatEN": "Batch change file mode and owner [paths] => [mode]/[user]/[group]"
|
"formatEN": "Batch change file mode and owner [paths] =\u003e [mode]/[user]/[group]"
|
||||||
},
|
},
|
||||||
"/files/chunkdownload": {
|
"/files/chunkdownload": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3416,8 +3140,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改权限 [path] => [mode]",
|
"formatZH": "修改权限 [path] =\u003e [mode]",
|
||||||
"formatEN": "Change mode [path] => [mode]"
|
"formatEN": "Change mode [path] =\u003e [mode]"
|
||||||
},
|
},
|
||||||
"/files/move": {
|
"/files/move": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3426,8 +3150,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "移动文件 [oldPaths] => [newPath]",
|
"formatZH": "移动文件 [oldPaths] =\u003e [newPath]",
|
||||||
"formatEN": "Move [oldPaths] => [newPath]"
|
"formatEN": "Move [oldPaths] =\u003e [newPath]"
|
||||||
},
|
},
|
||||||
"/files/owner": {
|
"/files/owner": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3437,8 +3161,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改用户/组 [path] => [user]/[group]",
|
"formatZH": "修改用户/组 [path] =\u003e [user]/[group]",
|
||||||
"formatEN": "Change owner [path] => [user]/[group]"
|
"formatEN": "Change owner [path] =\u003e [user]/[group]"
|
||||||
},
|
},
|
||||||
"/files/preview": {
|
"/files/preview": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3472,8 +3196,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "重命名 [oldName] => [newName]",
|
"formatZH": "重命名 [oldName] =\u003e [newName]",
|
||||||
"formatEN": "Rename [oldName] => [newName]"
|
"formatEN": "Rename [oldName] =\u003e [newName]"
|
||||||
},
|
},
|
||||||
"/files/save": {
|
"/files/save": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3530,8 +3254,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "下载 url => [path]/[name]",
|
"formatZH": "下载 url =\u003e [path]/[name]",
|
||||||
"formatEN": "Download url => [path]/[name]"
|
"formatEN": "Download url =\u003e [path]/[name]"
|
||||||
},
|
},
|
||||||
"/files/wget/process/remove": {
|
"/files/wget/process/remove": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3673,13 +3397,11 @@
|
|||||||
"formatEN": "batch update Docker port guard policies"
|
"formatEN": "batch update Docker port guard policies"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/docker/policies/delete/batch": {
|
"/hosts/firewall/docker/policies/delete/batch": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"uuids"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "删除 Docker 端口防护策略 [uuids]",
|
"formatZH": "删除 Docker 端口防护策略",
|
||||||
"formatEN": "delete Docker port guard policies [uuids]"
|
"formatEN": "delete Docker port guard policies"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/family/operate": {
|
"/hosts/firewall/family/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3687,10 +3409,10 @@
|
|||||||
"family",
|
"family",
|
||||||
"operation"
|
"operation"
|
||||||
],
|
],
|
||||||
"formatEN": "[operation] [subsystem] [family] firewall chains",
|
|
||||||
"formatZH": "[operation] [subsystem] [family] 防火墙链",
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": []
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "[operation] [subsystem] [family] 防火墙链",
|
||||||
|
"formatEN": "[operation] [subsystem] [family] firewall chains"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/filter/operate": {
|
"/hosts/firewall/filter/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3739,13 +3461,11 @@
|
|||||||
"formatEN": "delete firewall rules"
|
"formatEN": "delete firewall rules"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/rules/reorder": {
|
"/hosts/firewall/rules/reorder": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "调整防火墙规则顺序 [instanceKey]",
|
"formatZH": "调整防火墙规则顺序",
|
||||||
"formatEN": "reorder firewall rule [instanceKey]"
|
"formatEN": "reorder firewall rule"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/rules/reset": {
|
"/hosts/firewall/rules/reset": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -3755,22 +3475,20 @@
|
|||||||
"formatEN": "reset firewall rules"
|
"formatEN": "reset firewall rules"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/rules/update": {
|
"/hosts/firewall/rules/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新防火墙规则 [instanceKey]",
|
"formatZH": "更新防火墙规则",
|
||||||
"formatEN": "update firewall rule [instanceKey]"
|
"formatEN": "update firewall rule"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/settings/ipv6": {
|
"/hosts/firewall/settings/ipv6": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"status"
|
"status"
|
||||||
],
|
],
|
||||||
"formatEN": "Set firewall IPv6 support to [status]",
|
|
||||||
"formatZH": "设置防火墙 IPv6 支持为 [status]",
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": []
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "设置防火墙 IPv6 支持为 [status]",
|
||||||
|
"formatEN": "Set firewall IPv6 support to [status]"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/settings/operate": {
|
"/hosts/firewall/settings/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3812,11 +3530,13 @@
|
|||||||
"formatEN": "update firewall port whitelist"
|
"formatEN": "update firewall port whitelist"
|
||||||
},
|
},
|
||||||
"/hosts/monitor/clean": {
|
"/hosts/monitor/clean": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "清空监控数据",
|
"formatZH": "清空 [type] 监控数据",
|
||||||
"formatEN": "clean monitor datas"
|
"formatEN": "clean [type] monitoring data"
|
||||||
},
|
},
|
||||||
"/hosts/monitor/setting/update": {
|
"/hosts/monitor/setting/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3888,8 +3608,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改 SSH 配置 [key] => [newValue]",
|
"formatZH": "修改 SSH 配置 [key] =\u003e [newValue]",
|
||||||
"formatEN": "update SSH setting [key] => [newValue]"
|
"formatEN": "update SSH setting [key] =\u003e [newValue]"
|
||||||
},
|
},
|
||||||
"/hosts/tool/config/set": {
|
"/hosts/tool/config/set": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4150,7 +3870,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "快照 [name] 描述信息修改 [description]",
|
"formatZH": "快照 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the snapshot [name] is modified => [description]"
|
"formatEN": "The description of the snapshot [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/settings/snapshot/import": {
|
"/settings/snapshot/import": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4242,8 +3962,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统配置 [key] => [value]",
|
"formatZH": "修改系统配置 [key] =\u003e [value]",
|
||||||
"formatEN": "update system setting [key] => [value]"
|
"formatEN": "update system setting [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/clam": {
|
"/toolbox/clam": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4361,8 +4081,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改主机参数 [key] => [value]",
|
"formatZH": "修改主机参数 [key] =\u003e [value]",
|
||||||
"formatEN": "update device conf [key] => [value]"
|
"formatEN": "update device conf [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/device/update/host": {
|
"/toolbox/device/update/host": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4371,8 +4091,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改主机 Host [key] => [value]",
|
"formatZH": "修改主机 Host [key] =\u003e [value]",
|
||||||
"formatEN": "update device host [key] => [value]"
|
"formatEN": "update device host [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/device/update/swap": {
|
"/toolbox/device/update/swap": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4400,8 +4120,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改 Fail2ban 配置 [key] => [value]",
|
"formatZH": "修改 Fail2ban 配置 [key] =\u003e [value]",
|
||||||
"formatEN": "update fail2ban conf [key] => [value]"
|
"formatEN": "update fail2ban conf [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/ftp": {
|
"/toolbox/ftp": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4660,8 +4380,8 @@
|
|||||||
"output_value": "domain"
|
"output_value": "domain"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "修改默认 server => [domain]",
|
"formatZH": "修改默认 server =\u003e [domain]",
|
||||||
"formatEN": "Change default server => [domain]"
|
"formatEN": "Change default server =\u003e [domain]"
|
||||||
},
|
},
|
||||||
"/websites/del": {
|
"/websites/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -5424,8 +5144,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新防篡改信息 [website][path] => [status]",
|
"formatZH": "更新防篡改信息 [website][path] =\u003e [status]",
|
||||||
"formatEN": "update tamper info [website][path] => [status]"
|
"formatEN": "update tamper info [website][path] =\u003e [status]"
|
||||||
},
|
},
|
||||||
"/xpack/vllm/command-template/create": {
|
"/xpack/vllm/command-template/create": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -5504,6 +5224,282 @@
|
|||||||
"formatZH": "更新 vLLM [name]",
|
"formatZH": "更新 vLLM [name]",
|
||||||
"formatEN": "update vLLM [name]"
|
"formatEN": "update vLLM [name]"
|
||||||
},
|
},
|
||||||
|
"/xpack/vms": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机 [name]",
|
||||||
|
"formatEN": "create VM [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "virtual_machines",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机 [name]",
|
||||||
|
"formatEN": "delete VM [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/disks/resize": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"target",
|
||||||
|
"sizeGiB"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "扩容虚拟机 [name] 磁盘 [target] 至 [sizeGiB] GiB",
|
||||||
|
"formatEN": "expand VM [name] disk [target] to [sizeGiB] GiB"
|
||||||
|
},
|
||||||
|
"/xpack/vms/environment/enable": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "启用虚拟机运行环境",
|
||||||
|
"formatEN": "enable VM runtime environment"
|
||||||
|
},
|
||||||
|
"/xpack/vms/iso": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机镜像 [name] [type]",
|
||||||
|
"formatEN": "create VM ISO [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/iso/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_isos",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机镜像 [name]",
|
||||||
|
"formatEN": "delete VM ISO [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/iso/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新虚拟机镜像 [name] [type]",
|
||||||
|
"formatEN": "update VM ISO [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/networks": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机网络 [name] [type]",
|
||||||
|
"formatEN": "create VM network [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/networks/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_networks",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机网络 [name]",
|
||||||
|
"formatEN": "delete VM network [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/networks/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_networks",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "更新虚拟机网络 [name]",
|
||||||
|
"formatEN": "update VM network [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/operate": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"operate"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "操作虚拟机 [name] [operate]",
|
||||||
|
"formatEN": "operate VM [name] [operate]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/orphans/clean": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清理虚拟机孤立数据",
|
||||||
|
"formatEN": "clean VM orphaned data"
|
||||||
|
},
|
||||||
|
"/xpack/vms/rename": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"newName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "重命名虚拟机 [name] 为 [newName]",
|
||||||
|
"formatEN": "rename VM [name] to [newName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/snapshots": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"snapshotName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
|
||||||
|
"formatEN": "create VM [name] snapshot [snapshotName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/snapshots/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"snapshotName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
|
||||||
|
"formatEN": "delete VM [name] snapshot [snapshotName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/snapshots/recover": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"snapshotName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
|
||||||
|
"formatEN": "recover VM [name] snapshot [snapshotName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/storages": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机存储 [name] [type]",
|
||||||
|
"formatEN": "create VM storage [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/storages/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_storages",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机存储 [name]",
|
||||||
|
"formatEN": "delete VM storage [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/storages/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_storages",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "更新虚拟机存储 [name]",
|
||||||
|
"formatEN": "update VM storage [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/sync": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "同步虚拟机",
|
||||||
|
"formatEN": "sync virtual machines"
|
||||||
|
},
|
||||||
|
"/xpack/vms/templates": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"templateName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
|
||||||
|
"formatEN": "create VM [name] template [templateName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/templates/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_templates",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机模板 [name]",
|
||||||
|
"formatEN": "delete VM template [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新虚拟机 [name]",
|
||||||
|
"formatEN": "update VM [name]"
|
||||||
|
},
|
||||||
"/xpack/waf/block/export": {
|
"/xpack/waf/block/export": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
|
|||||||
+14
-13
@@ -8,9 +8,9 @@ require (
|
|||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
github.com/compose-spec/compose-go/v2 v2.15.0
|
github.com/compose-spec/compose-go/v2 v2.15.0
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/docker/cli v29.7.2+incompatible
|
github.com/docker/cli v29.9.0+incompatible
|
||||||
github.com/docker/docker v28.5.2+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/docker/go-connections v0.8.1
|
github.com/docker/go-connections v0.8.2
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
@@ -27,7 +27,7 @@ require (
|
|||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/klauspost/compress v1.19.2
|
github.com/klauspost/compress v1.19.2
|
||||||
github.com/mattn/go-shellwords v1.0.14
|
github.com/mattn/go-shellwords v1.0.16
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||||
github.com/miekg/dns v1.1.73
|
github.com/miekg/dns v1.1.73
|
||||||
github.com/minio/minio-go/v7 v7.3.0
|
github.com/minio/minio-go/v7 v7.3.0
|
||||||
@@ -40,7 +40,7 @@ require (
|
|||||||
github.com/pkg/sftp v1.13.11
|
github.com/pkg/sftp v1.13.11
|
||||||
github.com/qiniu/go-sdk/v7 v7.27.0
|
github.com/qiniu/go-sdk/v7 v7.27.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.26.7
|
github.com/shirou/gopsutil/v4 v4.26.9
|
||||||
github.com/sirupsen/logrus v1.10.2
|
github.com/sirupsen/logrus v1.10.2
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/spf13/afero v1.15.0
|
github.com/spf13/afero v1.15.0
|
||||||
@@ -51,12 +51,12 @@ require (
|
|||||||
github.com/tklauser/go-sysconf v0.4.0
|
github.com/tklauser/go-sysconf v0.4.0
|
||||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
|
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
|
||||||
github.com/upyun/go-sdk v2.1.0+incompatible
|
github.com/upyun/go-sdk v2.1.0+incompatible
|
||||||
go.mongodb.org/mongo-driver/v2 v2.9.0
|
go.mongodb.org/mongo-driver/v2 v2.9.2
|
||||||
golang.org/x/crypto v0.56.0
|
golang.org/x/crypto v0.58.0
|
||||||
golang.org/x/net v0.58.0
|
golang.org/x/net v0.60.0
|
||||||
golang.org/x/sync v0.23.0
|
golang.org/x/sync v0.24.0
|
||||||
golang.org/x/sys v0.47.0
|
golang.org/x/sys v0.49.0
|
||||||
golang.org/x/text v0.41.0
|
golang.org/x/text v0.43.0
|
||||||
golang.org/x/time v0.16.0
|
golang.org/x/time v0.16.0
|
||||||
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478
|
google.golang.org/genproto v0.0.0-20260414002931-afd174a4e478
|
||||||
gopkg.in/ini.v1 v1.67.3
|
gopkg.in/ini.v1 v1.67.3
|
||||||
@@ -112,7 +112,7 @@ require (
|
|||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/purego v0.10.2 // indirect
|
github.com/ebitengine/purego v0.11.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||||
@@ -177,7 +177,7 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
||||||
github.com/philhofer/fwd v1.2.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20260805114148-88456608a4f6 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.59.1 // indirect
|
github.com/quic-go/quic-go v0.59.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
@@ -203,7 +203,7 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
go.mongodb.org/mongo-driver v1.17.10 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.45.0 // indirect
|
go.opentelemetry.io/otel v1.45.0 // indirect
|
||||||
@@ -216,6 +216,7 @@ require (
|
|||||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
|
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
|
||||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||||
golang.org/x/arch v0.26.0 // 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
|
golang.org/x/oauth2 v0.36.0 // indirect
|
||||||
google.golang.org/grpc v1.83.2 // indirect
|
google.golang.org/grpc v1.83.2 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
|||||||
@@ -230,12 +230,18 @@ github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz
|
|||||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
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 h1:dlkwallR8XqfeVnA2ELEhdwvb4lsSwuB4IgsG8Q9cLY=
|
||||||
github.com/docker/cli v29.7.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
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 h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
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 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||||
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
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 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M=
|
||||||
github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
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=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||||
@@ -250,6 +256,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
|
|||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
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 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
|
||||||
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
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=
|
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
@@ -567,6 +575,8 @@ github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw
|
|||||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
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 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg=
|
||||||
github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
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=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
@@ -703,6 +713,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
|
|||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
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 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-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=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
@@ -752,6 +764,10 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCw
|
|||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
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 h1:IXzpHz/dkMRYAhKkOXr1HB6SuzWU3eoyyeWe7g3bNZc=
|
||||||
github.com/shirou/gopsutil/v4 v4.26.7/go.mod h1:5O9FjBiXoTDFatIWjZZosqj4pV0DRtLx598xGbBehzM=
|
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=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
@@ -878,8 +894,14 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
|
|||||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
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 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
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 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.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=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -954,6 +976,10 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
|
|||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
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 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
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=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -970,6 +996,8 @@ 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-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-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.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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -997,6 +1025,7 @@ 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.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
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.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||||
|
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -1053,6 +1082,8 @@ 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.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
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.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=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -1079,6 +1110,8 @@ 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.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
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.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=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -1156,6 +1189,10 @@ 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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
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.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=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -1171,6 +1208,8 @@ 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.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
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.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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -1189,6 +1228,10 @@ 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.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 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
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=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -1252,6 +1295,8 @@ 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.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 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'Unable to derive a project name from Docker Compose
|
|||||||
ErrComposeNameInvalid: 'Invalid Compose project name: it must start with a lowercase letter or number, contain only lowercase letters, numbers, hyphens, and underscores, and be 1-256 characters long.'
|
ErrComposeNameInvalid: 'Invalid Compose project name: it must start with a lowercase letter or number, contain only lowercase letters, numbers, hyphens, and underscores, and be 1-256 characters long.'
|
||||||
ErrAppVersionUnavailable: 'This application version has been removed from the remote service. Please select another version and try again.'
|
ErrAppVersionUnavailable: 'This application version has been removed from the remote service. Please select another version and try again.'
|
||||||
ErrVllmGB10ProOnly: 'This vLLM version is available only in 1Panel Professional Edition.'
|
ErrVllmGB10ProOnly: 'This vLLM version is available only in 1Panel Professional Edition.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway can only upgrade within the same database type (SQLite or PostgreSQL).'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'The AI Gateway backup and target application must use the same database type (SQLite or PostgreSQL).'
|
||||||
ErrAppWarn: 'App status abnormal; check logs'
|
ErrAppWarn: 'App status abnormal; check logs'
|
||||||
ErrAppParamKey: 'Invalid app parameter: {{ .name }}'
|
ErrAppParamKey: 'Invalid app parameter: {{ .name }}'
|
||||||
ErrAppUpgrade: 'App upgrade failed: {{ .name }} {{ .err }}'
|
ErrAppUpgrade: 'App upgrade failed: {{ .name }} {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'Build Cache'
|
|||||||
Volume: 'Volume'
|
Volume: 'Volume'
|
||||||
Network: "Network"
|
Network: "Network"
|
||||||
PruneStart: 'Cleaning in progress'
|
PruneStart: 'Cleaning in progress'
|
||||||
|
NetworkCleanupConnected: "Skipped network [{{ .name }}] ({{ .id }}): containers connected or network in use; not deleted"
|
||||||
|
NetworkCleanupSummary: "Network cleanup finished: deleted {{ .deleted }}, skipped {{ .skipped }}, failed {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "Some networks could not be cleaned; see the task log"
|
||||||
ContainerBackupPrepare: 'Prepare container backup workspace'
|
ContainerBackupPrepare: 'Prepare container backup workspace'
|
||||||
ContainerBackupStop: 'Stop container before backup'
|
ContainerBackupStop: 'Stop container before backup'
|
||||||
ContainerBackupInspect: 'Backup container inspect and network'
|
ContainerBackupInspect: 'Backup container inspect and network'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 forwarding is disabled. Set net.ipv4.ip_f
|
|||||||
ErrFirewallRuleSavedApplyFailed: "The new rule configuration was saved, but applying it to the firewall failed. Retry by synchronizing: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "The new rule configuration was saved, but applying it to the firewall failed. Retry by synchronizing: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "A UFW rule with the same match conditions and a different action already exists. Edit the existing rule instead."
|
ErrFirewallRuleConflict: "A UFW rule with the same match conditions and a different action already exists. Edit the existing rule instead."
|
||||||
ErrForwardInterfaceNotFound: "Network interface {{ .name }} specified by the forwarding rule does not exist. Check the interface name and retry."
|
ErrForwardInterfaceNotFound: "Network interface {{ .name }} specified by the forwarding rule does not exist. Check the interface name and retry."
|
||||||
|
ErrIPv6RARisk: "IPv6 forwarding is blocked to prevent possible network loss. See the documentation and system logs for details."
|
||||||
|
ErrIPv6RACheckFailed: "IPv6 forwarding is blocked because the network configuration could not be checked. See the system logs for details."
|
||||||
|
|
||||||
ErrSSHPublicKey: "Enter a valid SSH public key"
|
ErrSSHPublicKey: "Enter a valid SSH public key"
|
||||||
ErrSSHPrivateKey: "Invalid SSH private key or incorrect passphrase; encrypted keys require the original passphrase"
|
ErrSSHPrivateKey: "Invalid SSH private key or incorrect passphrase; encrypted keys require the original passphrase"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'No se pudo derivar un nombre de proyecto desde la c
|
|||||||
ErrComposeNameInvalid: 'Nombre de proyecto Compose no válido: debe comenzar con una letra minúscula o un número, contener solo letras minúsculas, números, guiones y guiones bajos, y tener entre 1 y 256 caracteres.'
|
ErrComposeNameInvalid: 'Nombre de proyecto Compose no válido: debe comenzar con una letra minúscula o un número, contener solo letras minúsculas, números, guiones y guiones bajos, y tener entre 1 y 256 caracteres.'
|
||||||
ErrAppVersionUnavailable: 'Esta versión de la aplicación se ha eliminado del servicio remoto. Seleccione otra versión e inténtelo de nuevo.'
|
ErrAppVersionUnavailable: 'Esta versión de la aplicación se ha eliminado del servicio remoto. Seleccione otra versión e inténtelo de nuevo.'
|
||||||
ErrVllmGB10ProOnly: 'Esta versión de vLLM solo está disponible en 1Panel Professional Edition.'
|
ErrVllmGB10ProOnly: 'Esta versión de vLLM solo está disponible en 1Panel Professional Edition.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway solo puede actualizarse dentro del mismo tipo de base de datos (SQLite o PostgreSQL).'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'La copia de seguridad de AI Gateway y la aplicación de destino deben usar el mismo tipo de base de datos (SQLite o PostgreSQL).'
|
||||||
ErrAppWarn: 'Estado anómalo, revise el log'
|
ErrAppWarn: 'Estado anómalo, revise el log'
|
||||||
ErrAppParamKey: 'El campo de parámetro {{ .name }} es anómalo'
|
ErrAppParamKey: 'El campo de parámetro {{ .name }} es anómalo'
|
||||||
ErrAppUpgrade: 'La actualización de la aplicación {{ .name }} falló {{ .err }}'
|
ErrAppUpgrade: 'La actualización de la aplicación {{ .name }} falló {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'Caché de compilación'
|
|||||||
Volume: 'Volumen de almacenamiento'
|
Volume: 'Volumen de almacenamiento'
|
||||||
Network: 'Red'
|
Network: 'Red'
|
||||||
PruneStart: 'Limpiando en progreso espere...'
|
PruneStart: 'Limpiando en progreso espere...'
|
||||||
|
NetworkCleanupConnected: "Se omitió la red [{{ .name }}] ({{ .id }}): hay contenedores conectados o la red está en uso; no se eliminó"
|
||||||
|
NetworkCleanupSummary: "Limpieza de redes finalizada: eliminadas {{ .deleted }}, omitidas {{ .skipped }}, fallidas {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "No se pudieron limpiar algunas redes; consulte el registro de la tarea"
|
||||||
ContainerBackupPrepare: 'Preparar espacio de respaldo del contenedor'
|
ContainerBackupPrepare: 'Preparar espacio de respaldo del contenedor'
|
||||||
ContainerBackupStop: 'Detener contenedor antes del respaldo'
|
ContainerBackupStop: 'Detener contenedor antes del respaldo'
|
||||||
ContainerBackupInspect: 'Respaldar inspect y red del contenedor'
|
ContainerBackupInspect: 'Respaldar inspect y red del contenedor'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "El reenvío IPv4 está desactivado. Configure
|
|||||||
ErrFirewallRuleSavedApplyFailed: "Se guardó la configuración de la nueva regla, pero no se pudo aplicar al cortafuegos. Vuelva a intentarlo mediante la sincronización: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "Se guardó la configuración de la nueva regla, pero no se pudo aplicar al cortafuegos. Vuelva a intentarlo mediante la sincronización: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "Ya existe una regla UFW con las mismas condiciones y una acción diferente. Edite la regla existente."
|
ErrFirewallRuleConflict: "Ya existe una regla UFW con las mismas condiciones y una acción diferente. Edite la regla existente."
|
||||||
ErrForwardInterfaceNotFound: "La interfaz de red {{ .name }} indicada en la regla de reenvío no existe. Compruebe el nombre e inténtelo de nuevo."
|
ErrForwardInterfaceNotFound: "La interfaz de red {{ .name }} indicada en la regla de reenvío no existe. Compruebe el nombre e inténtelo de nuevo."
|
||||||
|
ErrIPv6RARisk: "El reenvío IPv6 está bloqueado para evitar una posible pérdida de conectividad. Consulte la documentación y los registros del sistema."
|
||||||
|
ErrIPv6RACheckFailed: "El reenvío IPv6 está bloqueado porque no se pudo comprobar la configuración de red. Consulte los registros del sistema."
|
||||||
|
|
||||||
ErrSSHPublicKey: "Introduce una clave pública SSH válida"
|
ErrSSHPublicKey: "Introduce una clave pública SSH válida"
|
||||||
ErrSSHPrivateKey: "Clave privada SSH no válida o contraseña incorrecta; las claves cifradas requieren la contraseña original"
|
ErrSSHPrivateKey: "Clave privada SSH no válida o contraseña incorrecta; las claves cifradas requieren la contraseña original"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'نام پروژه از پیکربندی Docker Comp
|
|||||||
ErrComposeNameInvalid: 'نام پروژه Compose نامعتبر است: باید با حرف کوچک یا عدد شروع شود، فقط شامل حروف کوچک، اعداد، خط تیره و زیرخط باشد و ۱ تا ۲۵۶ نویسه داشته باشد.'
|
ErrComposeNameInvalid: 'نام پروژه Compose نامعتبر است: باید با حرف کوچک یا عدد شروع شود، فقط شامل حروف کوچک، اعداد، خط تیره و زیرخط باشد و ۱ تا ۲۵۶ نویسه داشته باشد.'
|
||||||
ErrAppVersionUnavailable: 'این نسخه برنامه از سرویس راه دور حذف شده است. لطفاً نسخه دیگری را انتخاب کرده و دوباره تلاش کنید.'
|
ErrAppVersionUnavailable: 'این نسخه برنامه از سرویس راه دور حذف شده است. لطفاً نسخه دیگری را انتخاب کرده و دوباره تلاش کنید.'
|
||||||
ErrVllmGB10ProOnly: 'این نسخه vLLM فقط در نسخه حرفهای 1Panel در دسترس است.'
|
ErrVllmGB10ProOnly: 'این نسخه vLLM فقط در نسخه حرفهای 1Panel در دسترس است.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway فقط در همان نوع پایگاه داده (SQLite یا PostgreSQL) قابل ارتقا است.'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'نسخه پشتیبان AI Gateway و برنامه مقصد باید از یک نوع پایگاه داده (SQLite یا PostgreSQL) استفاده کنند.'
|
||||||
ErrAppWarn: 'وضعیت برنامه غیرعادی است؛ لاگها را بررسی کنید'
|
ErrAppWarn: 'وضعیت برنامه غیرعادی است؛ لاگها را بررسی کنید'
|
||||||
ErrAppParamKey: 'پارامتر برنامه نامعتبر است: {{ .name }}'
|
ErrAppParamKey: 'پارامتر برنامه نامعتبر است: {{ .name }}'
|
||||||
ErrAppUpgrade: 'ارتقاء برنامه ناموفق بود: {{ .name }} {{ .err }}'
|
ErrAppUpgrade: 'ارتقاء برنامه ناموفق بود: {{ .name }} {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'کش ساخت'
|
|||||||
Volume: 'حجم'
|
Volume: 'حجم'
|
||||||
Network: "شبکه"
|
Network: "شبکه"
|
||||||
PruneStart: 'پاکسازی در حال انجام است'
|
PruneStart: 'پاکسازی در حال انجام است'
|
||||||
|
NetworkCleanupConnected: "از شبکه [{{ .name }}] ({{ .id }}) صرفنظر شد: کانتینر متصل دارد یا در حال استفاده است؛ حذف نشد"
|
||||||
|
NetworkCleanupSummary: "پاکسازی شبکهها پایان یافت: حذفشده {{ .deleted }}، ردشده {{ .skipped }}، ناموفق {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "پاکسازی برخی شبکهها ناموفق بود؛ گزارش وظیفه را بررسی کنید"
|
||||||
ContainerBackupPrepare: 'آمادهسازی فضای کاری پشتیبان کانتینر'
|
ContainerBackupPrepare: 'آمادهسازی فضای کاری پشتیبان کانتینر'
|
||||||
ContainerBackupStop: 'توقف کانتینر قبل از پشتیبانگیری'
|
ContainerBackupStop: 'توقف کانتینر قبل از پشتیبانگیری'
|
||||||
ContainerBackupInspect: 'بررسی و شبکه کانتینر پشتیبان'
|
ContainerBackupInspect: 'بررسی و شبکه کانتینر پشتیبان'
|
||||||
@@ -719,6 +724,8 @@ ErrDockerIPv4ForwardingDisabled: "ارسال IPv4 غیرفعال است. پیش
|
|||||||
ErrFirewallRuleSavedApplyFailed: "پیکربندی قانون جدید ذخیره شد، اما اعمال آن در دیوار آتش ناموفق بود. با همگامسازی دوباره تلاش کنید: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "پیکربندی قانون جدید ذخیره شد، اما اعمال آن در دیوار آتش ناموفق بود. با همگامسازی دوباره تلاش کنید: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "یک قانون UFW با شرایط تطبیق یکسان و عملکرد متفاوت از قبل وجود دارد. قانون موجود را ویرایش کنید."
|
ErrFirewallRuleConflict: "یک قانون UFW با شرایط تطبیق یکسان و عملکرد متفاوت از قبل وجود دارد. قانون موجود را ویرایش کنید."
|
||||||
ErrForwardInterfaceNotFound: "رابط شبکه {{ .name }} مشخصشده در قانون انتقال وجود ندارد. نام رابط را بررسی کرده و دوباره تلاش کنید."
|
ErrForwardInterfaceNotFound: "رابط شبکه {{ .name }} مشخصشده در قانون انتقال وجود ندارد. نام رابط را بررسی کرده و دوباره تلاش کنید."
|
||||||
|
ErrIPv6RARisk: "فعالسازی هدایت IPv6 به دلیل خطر قطع شبکه مسدود شد. مستندات و گزارشهای سیستم را بررسی کنید."
|
||||||
|
ErrIPv6RACheckFailed: "فعالسازی هدایت IPv6 به دلیل عدم امکان بررسی پیکربندی شبکه مسدود شد. جزئیات در گزارشهای سیستم موجود است."
|
||||||
|
|
||||||
ErrSSHPublicKey: "یک کلید عمومی SSH معتبر وارد کنید"
|
ErrSSHPublicKey: "یک کلید عمومی SSH معتبر وارد کنید"
|
||||||
ErrSSHPrivateKey: "کلید خصوصی SSH نامعتبر یا عبارت عبور نادرست است؛ کلیدهای رمزگذاریشده به عبارت عبور اصلی نیاز دارند"
|
ErrSSHPrivateKey: "کلید خصوصی SSH نامعتبر یا عبارت عبور نادرست است؛ کلیدهای رمزگذاریشده به عبارت عبور اصلی نیاز دارند"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'Docker Compose 設定またはファイルの親デ
|
|||||||
ErrComposeNameInvalid: 'Compose プロジェクト名の形式が正しくありません。小文字または数字で始まり、小文字、数字、ハイフン、アンダースコアのみを使用した 1~256 文字にしてください。'
|
ErrComposeNameInvalid: 'Compose プロジェクト名の形式が正しくありません。小文字または数字で始まり、小文字、数字、ハイフン、アンダースコアのみを使用した 1~256 文字にしてください。'
|
||||||
ErrAppVersionUnavailable: '現在のアプリケーションバージョンはリモートサービスから削除されています。別のバージョンを選択して再試行してください。'
|
ErrAppVersionUnavailable: '現在のアプリケーションバージョンはリモートサービスから削除されています。別のバージョンを選択して再試行してください。'
|
||||||
ErrVllmGB10ProOnly: 'この vLLM バージョンは 1Panel プロフェッショナル版でのみ利用できます。'
|
ErrVllmGB10ProOnly: 'この vLLM バージョンは 1Panel プロフェッショナル版でのみ利用できます。'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway は同じデータベース種別(SQLite または PostgreSQL)間でのみアップグレードできます。'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI Gateway のバックアップと復元先アプリのデータベース種別(SQLite または PostgreSQL)は一致している必要があります。'
|
||||||
ErrAppWarn: '異常な状態です。ログを確認してください'
|
ErrAppWarn: '異常な状態です。ログを確認してください'
|
||||||
ErrAppParamKey: 'パラメータ {{ .name }} フィールドが異常です'
|
ErrAppParamKey: 'パラメータ {{ .name }} フィールドが異常です'
|
||||||
ErrAppUpgrade: 'アプリケーション {{ .name }} のアップグレードに失敗しました {{ .err }}'
|
ErrAppUpgrade: 'アプリケーション {{ .name }} のアップグレードに失敗しました {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'ビルドキャッシュ'
|
|||||||
Volume: 'ストレージボリューム'
|
Volume: 'ストレージボリューム'
|
||||||
Network: 'ネットワーク'
|
Network: 'ネットワーク'
|
||||||
PruneStart: 'クリーンアップ中、しばらくお待ちください...'
|
PruneStart: 'クリーンアップ中、しばらくお待ちください...'
|
||||||
|
NetworkCleanupConnected: "ネットワーク [{{ .name }}] ({{ .id }}) をスキップしました:コンテナが接続されているか使用中のため、削除しませんでした"
|
||||||
|
NetworkCleanupSummary: "ネットワークのクリーンアップが完了しました:削除 {{ .deleted }} 件、スキップ {{ .skipped }} 件、失敗 {{ .failed }} 件"
|
||||||
|
NetworkCleanupPartialFailure: "一部のネットワークをクリーンアップできませんでした。タスクログを確認してください"
|
||||||
ContainerBackupPrepare: 'コンテナバックアップ作業領域を準備'
|
ContainerBackupPrepare: 'コンテナバックアップ作業領域を準備'
|
||||||
ContainerBackupStop: 'バックアップ前にコンテナを停止'
|
ContainerBackupStop: 'バックアップ前にコンテナを停止'
|
||||||
ContainerBackupInspect: 'コンテナ inspect とネットワーク情報をバックアップ'
|
ContainerBackupInspect: 'コンテナ inspect とネットワーク情報をバックアップ'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 転送が無効です。Docker のファ
|
|||||||
ErrFirewallRuleSavedApplyFailed: "新しいルール設定は保存されましたが、ファイアウォールへの適用に失敗しました。同期で再試行してください:{{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "新しいルール設定は保存されましたが、ファイアウォールへの適用に失敗しました。同期で再試行してください:{{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "UFW に同じ一致条件でアクションが異なるルールが既に存在します。既存のルールを編集してください。"
|
ErrFirewallRuleConflict: "UFW に同じ一致条件でアクションが異なるルールが既に存在します。既存のルールを編集してください。"
|
||||||
ErrForwardInterfaceNotFound: "転送ルールで指定されたネットワークインターフェース {{ .name }} は存在しません。名前を確認して再試行してください。"
|
ErrForwardInterfaceNotFound: "転送ルールで指定されたネットワークインターフェース {{ .name }} は存在しません。名前を確認して再試行してください。"
|
||||||
|
ErrIPv6RARisk: "ネットワーク切断のおそれがあるため、IPv6 転送の有効化をブロックしました。ドキュメントとシステムログをご確認ください。"
|
||||||
|
ErrIPv6RACheckFailed: "ネットワーク設定を確認できないため、IPv6 転送の有効化をブロックしました。詳細はシステムログをご確認ください。"
|
||||||
|
|
||||||
ErrSSHPublicKey: "有効な SSH 公開鍵を入力してください"
|
ErrSSHPublicKey: "有効な SSH 公開鍵を入力してください"
|
||||||
ErrSSHPrivateKey: "SSH 秘密鍵の形式が無効か、パスフレーズが間違っています。暗号化された鍵には元のパスフレーズが必要です"
|
ErrSSHPrivateKey: "SSH 秘密鍵の形式が無効か、パスフレーズが間違っています。暗号化された鍵には元のパスフレーズが必要です"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'Docker Compose 구성 또는 파일의 상위 디
|
|||||||
ErrComposeNameInvalid: 'Compose 프로젝트 이름 형식이 잘못되었습니다. 소문자 또는 숫자로 시작하고 소문자, 숫자, 하이픈, 밑줄만 사용하여 1~256자로 입력하세요.'
|
ErrComposeNameInvalid: 'Compose 프로젝트 이름 형식이 잘못되었습니다. 소문자 또는 숫자로 시작하고 소문자, 숫자, 하이픈, 밑줄만 사용하여 1~256자로 입력하세요.'
|
||||||
ErrAppVersionUnavailable: '현재 애플리케이션 버전이 원격 서비스에서 제거되었습니다. 다른 버전을 선택한 후 다시 시도해 주세요.'
|
ErrAppVersionUnavailable: '현재 애플리케이션 버전이 원격 서비스에서 제거되었습니다. 다른 버전을 선택한 후 다시 시도해 주세요.'
|
||||||
ErrVllmGB10ProOnly: '이 vLLM 버전은 1Panel 프로페셔널 에디션에서만 사용할 수 있습니다.'
|
ErrVllmGB10ProOnly: '이 vLLM 버전은 1Panel 프로페셔널 에디션에서만 사용할 수 있습니다.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway는 동일한 데이터베이스 유형(SQLite 또는 PostgreSQL) 내에서만 업그레이드할 수 있습니다.'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI Gateway 백업과 대상 애플리케이션의 데이터베이스 유형(SQLite 또는 PostgreSQL)이 같아야 합니다.'
|
||||||
ErrAppWarn: '비정상적인 상태입니다. 로그를 확인해 주세요.'
|
ErrAppWarn: '비정상적인 상태입니다. 로그를 확인해 주세요.'
|
||||||
ErrAppParamKey: '매개변수 {{ .name }} 필드가 비정상입니다.'
|
ErrAppParamKey: '매개변수 {{ .name }} 필드가 비정상입니다.'
|
||||||
ErrAppUpgrade: '애플리케이션 {{ .name }} 업그레이드에 실패했습니다 {{ .err }}'
|
ErrAppUpgrade: '애플리케이션 {{ .name }} 업그레이드에 실패했습니다 {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: '빌드 캐시'
|
|||||||
Volume: '스토리지 볼륨'
|
Volume: '스토리지 볼륨'
|
||||||
Network: '네트워크'
|
Network: '네트워크'
|
||||||
PruneStart: '정리 중입니다. 잠시만 기다려주세요...'
|
PruneStart: '정리 중입니다. 잠시만 기다려주세요...'
|
||||||
|
NetworkCleanupConnected: "네트워크 [{{ .name }}] ({{ .id }}) 건너뜀: 연결된 컨테이너가 있거나 사용 중이므로 삭제하지 않음"
|
||||||
|
NetworkCleanupSummary: "네트워크 정리 완료: 삭제 {{ .deleted }}개, 건너뜀 {{ .skipped }}개, 실패 {{ .failed }}개"
|
||||||
|
NetworkCleanupPartialFailure: "일부 네트워크를 정리하지 못했습니다. 작업 로그를 확인하세요"
|
||||||
ContainerBackupPrepare: '컨테이너 백업 작업 공간 준비'
|
ContainerBackupPrepare: '컨테이너 백업 작업 공간 준비'
|
||||||
ContainerBackupStop: '백업 전에 컨테이너 중지'
|
ContainerBackupStop: '백업 전에 컨테이너 중지'
|
||||||
ContainerBackupInspect: '컨테이너 inspect 및 네트워크 정보 백업'
|
ContainerBackupInspect: '컨테이너 inspect 및 네트워크 정보 백업'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 전달이 비활성화되어 있습니다
|
|||||||
ErrFirewallRuleSavedApplyFailed: "새 규칙 설정이 저장되었지만 방화벽에 적용하지 못했습니다. 동기화하여 다시 시도하세요: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "새 규칙 설정이 저장되었지만 방화벽에 적용하지 못했습니다. 동기화하여 다시 시도하세요: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "UFW에 일치 조건이 같지만 동작이 다른 규칙이 이미 있습니다. 기존 규칙을 편집하세요."
|
ErrFirewallRuleConflict: "UFW에 일치 조건이 같지만 동작이 다른 규칙이 이미 있습니다. 기존 규칙을 편집하세요."
|
||||||
ErrForwardInterfaceNotFound: "전달 규칙에 지정된 네트워크 인터페이스 {{ .name }}이 존재하지 않습니다. 이름을 확인한 후 다시 시도하세요."
|
ErrForwardInterfaceNotFound: "전달 규칙에 지정된 네트워크 인터페이스 {{ .name }}이 존재하지 않습니다. 이름을 확인한 후 다시 시도하세요."
|
||||||
|
ErrIPv6RARisk: "네트워크 연결이 끊길 위험이 있어 IPv6 전달 활성화를 차단했습니다. 문서와 시스템 로그를 확인하세요."
|
||||||
|
ErrIPv6RACheckFailed: "네트워크 구성을 확인할 수 없어 IPv6 전달 활성화를 차단했습니다. 자세한 내용은 시스템 로그를 확인하세요."
|
||||||
|
|
||||||
ErrSSHPublicKey: "유효한 SSH 공개 키를 입력하세요"
|
ErrSSHPublicKey: "유효한 SSH 공개 키를 입력하세요"
|
||||||
ErrSSHPrivateKey: "SSH 개인 키 형식이 잘못되었거나 암호가 틀립니다. 암호화된 키에는 원래 암호가 필요합니다"
|
ErrSSHPrivateKey: "SSH 개인 키 형식이 잘못되었거나 암호가 틀립니다. 암호화된 키에는 원래 암호가 필요합니다"
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ ErrComposeProjectNameEmpty: 'ບໍ່ສາມາດກຳນົດຊື່ໂ
|
|||||||
ErrComposeNameInvalid: 'ຊື່ໂຄງການ Compose ບໍ່ຖືກຕ້ອງ: ຕ້ອງເລີ່ມດ້ວຍຕົວພິມນ້ອຍ ຫຼື ຕົວເລກ, ໃຊ້ໄດ້ສະເພາະຕົວພິມນ້ອຍ, ຕົວເລກ, ຂີດກາງ ແລະ ຂີດລຸ່ມ, ຄວາມຍາວ 1-256 ຕົວອັກສອນ.'
|
ErrComposeNameInvalid: 'ຊື່ໂຄງການ Compose ບໍ່ຖືກຕ້ອງ: ຕ້ອງເລີ່ມດ້ວຍຕົວພິມນ້ອຍ ຫຼື ຕົວເລກ, ໃຊ້ໄດ້ສະເພາະຕົວພິມນ້ອຍ, ຕົວເລກ, ຂີດກາງ ແລະ ຂີດລຸ່ມ, ຄວາມຍາວ 1-256 ຕົວອັກສອນ.'
|
||||||
ErrAppVersionUnavailable: 'ເວີຊັນແອັບນີ້ຖືກລຶບອອກຈາກບໍລິການທາງໄກແລ້ວ ກະລຸນາເລືອກເວີຊັນອື່ນແລ້ວລອງໃໝ່'
|
ErrAppVersionUnavailable: 'ເວີຊັນແອັບນີ້ຖືກລຶບອອກຈາກບໍລິການທາງໄກແລ້ວ ກະລຸນາເລືອກເວີຊັນອື່ນແລ້ວລອງໃໝ່'
|
||||||
ErrVllmGB10ProOnly: 'ເວີຊັນ vLLM ນີ້ມີໃຫ້ໃຊ້ສະເພາະໃນ 1Panel Professional Edition ເທົ່ານັ້ນ.'
|
ErrVllmGB10ProOnly: 'ເວີຊັນ vLLM ນີ້ມີໃຫ້ໃຊ້ສະເພາະໃນ 1Panel Professional Edition ເທົ່ານັ້ນ.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway ສາມາດອັບເກຣດໄດ້ພາຍໃນປະເພດຖານຂໍ້ມູນດຽວກັນເທົ່ານັ້ນ (SQLite ຫຼື PostgreSQL).'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'ຂໍ້ມູນສຳຮອງ AI Gateway ແລະ ແອັບເປົ້າໝາຍຕ້ອງໃຊ້ປະເພດຖານຂໍ້ມູນດຽວກັນ (SQLite ຫຼື PostgreSQL).'
|
||||||
ErrAppWarn: 'ສະຖານະແອັບຜິດປົກກະຕິ; ກະລຸນາກວດສອບບັນທຶກ'
|
ErrAppWarn: 'ສະຖານະແອັບຜິດປົກກະຕິ; ກະລຸນາກວດສອບບັນທຶກ'
|
||||||
ErrAppParamKey: 'ພາຣາມິເຕີຂອງແອັບບໍ່ຖືກຕ້ອງ: {{ .name }}'
|
ErrAppParamKey: 'ພາຣາມິເຕີຂອງແອັບບໍ່ຖືກຕ້ອງ: {{ .name }}'
|
||||||
ErrAppUpgrade: 'ອັບເກຣດແອັບລົ້ມເຫຼວ: {{ .name }} {{ .err }}'
|
ErrAppUpgrade: 'ອັບເກຣດແອັບລົ້ມເຫຼວ: {{ .name }} {{ .err }}'
|
||||||
@@ -290,6 +292,9 @@ BuildCache: 'ແຄດການສ້າງ'
|
|||||||
Volume: 'ວໍລຸມ (Volume)'
|
Volume: 'ວໍລຸມ (Volume)'
|
||||||
Network: "ເຄືອຂ່າຍ"
|
Network: "ເຄືອຂ່າຍ"
|
||||||
PruneStart: 'ກຳລັງດຳເນີນການທຳຄວາມສະອາດ'
|
PruneStart: 'ກຳລັງດຳເນີນການທຳຄວາມສະອາດ'
|
||||||
|
NetworkCleanupConnected: "ຂ້າມເຄືອຂ່າຍ [{{ .name }}] ({{ .id }}): ມີຄອນເທນເນີເຊື່ອມຕໍ່ຢູ່ ຫຼື ເຄືອຂ່າຍກຳລັງຖືກໃຊ້ງານ; ບໍ່ໄດ້ລຶບ"
|
||||||
|
NetworkCleanupSummary: "ການລ້າງເຄືອຂ່າຍສຳເລັດ: ລຶບແລ້ວ {{ .deleted }}, ຂ້າມ {{ .skipped }}, ລົ້ມເຫຼວ {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "ບໍ່ສາມາດລ້າງບາງເຄືອຂ່າຍໄດ້; ກະລຸນາເບິ່ງບັນທຶກວຽກ"
|
||||||
ContainerBackupPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
|
ContainerBackupPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
|
||||||
ContainerBackupStop: 'ຢຸດຄອນເທນເນີກ່ອນການສຳຮອງຂໍ້ມູນ'
|
ContainerBackupStop: 'ຢຸດຄອນເທນເນີກ່ອນການສຳຮອງຂໍ້ມູນ'
|
||||||
ContainerBackupInspect: 'ກວດສອບຄອນເທນເນີ ແລະ ເຄືອຂ່າຍສຳລັບການສຳຮອງຂໍ້ມູນ'
|
ContainerBackupInspect: 'ກວດສອບຄອນເທນເນີ ແລະ ເຄືອຂ່າຍສຳລັບການສຳຮອງຂໍ້ມູນ'
|
||||||
@@ -709,6 +714,8 @@ ErrDockerIPv4ForwardingDisabled: "ການສົ່ງຕໍ່ IPv4 ຖືກ
|
|||||||
ErrFirewallRuleSavedApplyFailed: "ບັນທຶກການຕັ້ງຄ່າກົດໃໝ່ແລ້ວ ແຕ່ນຳໃຊ້ກັບໄຟວໍບໍ່ສຳເລັດ. ລອງອີກຄັ້ງດ້ວຍການຊິງຂໍ້ມູນ: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "ບັນທຶກການຕັ້ງຄ່າກົດໃໝ່ແລ້ວ ແຕ່ນຳໃຊ້ກັບໄຟວໍບໍ່ສຳເລັດ. ລອງອີກຄັ້ງດ້ວຍການຊິງຂໍ້ມູນ: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "ມີກົດ UFW ທີ່ມີເງື່ອນໄຂກົງກັນແຕ່ການດຳເນີນການຕ່າງກັນແລ້ວ. ກະລຸນາແກ້ໄຂກົດທີ່ມີຢູ່."
|
ErrFirewallRuleConflict: "ມີກົດ UFW ທີ່ມີເງື່ອນໄຂກົງກັນແຕ່ການດຳເນີນການຕ່າງກັນແລ້ວ. ກະລຸນາແກ້ໄຂກົດທີ່ມີຢູ່."
|
||||||
ErrForwardInterfaceNotFound: "ບໍ່ພົບອິນເຕີເຟດເຄືອຂ່າຍ {{ .name }} ທີ່ລະບຸໃນກົດສົ່ງຕໍ່. ກະລຸນາກວດສອບຊື່ ແລ້ວລອງໃໝ່."
|
ErrForwardInterfaceNotFound: "ບໍ່ພົບອິນເຕີເຟດເຄືອຂ່າຍ {{ .name }} ທີ່ລະບຸໃນກົດສົ່ງຕໍ່. ກະລຸນາກວດສອບຊື່ ແລ້ວລອງໃໝ່."
|
||||||
|
ErrIPv6RARisk: "ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເພື່ອປ້ອງກັນຄວາມສ່ຽງທີ່ເຄືອຂ່າຍຈະຂາດການເຊື່ອມຕໍ່. ເບິ່ງເອກະສານ ແລະ ບັນທຶກລະບົບ."
|
||||||
|
ErrIPv6RACheckFailed: "ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເນື່ອງຈາກບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍໄດ້. ເບິ່ງລາຍລະອຽດໃນບັນທຶກລະບົບ."
|
||||||
|
|
||||||
ErrSSHPublicKey: "ກະລຸນາປ້ອນຄີສາທາລະນະ SSH ທີ່ຖືກຕ້ອງ"
|
ErrSSHPublicKey: "ກະລຸນາປ້ອນຄີສາທາລະນະ SSH ທີ່ຖືກຕ້ອງ"
|
||||||
ErrSSHPrivateKey: "ຄີສ່ວນຕົວ SSH ບໍ່ຖືກຕ້ອງ ຫຼື ລະຫັດຜ່ານຜິດ; ຄີທີ່ເຂົ້າລະຫັດຕ້ອງໃຊ້ລະຫັດຜ່ານເດີມ"
|
ErrSSHPrivateKey: "ຄີສ່ວນຕົວ SSH ບໍ່ຖືກຕ້ອງ ຫຼື ລະຫັດຜ່ານຜິດ; ຄີທີ່ເຂົ້າລະຫັດຕ້ອງໃຊ້ລະຫັດຜ່ານເດີມ"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'Nama projek tidak dapat diperoleh daripada konfigur
|
|||||||
ErrComposeNameInvalid: 'Nama projek Compose tidak sah: mesti bermula dengan huruf kecil atau nombor, hanya mengandungi huruf kecil, nombor, tanda sempang dan garis bawah, serta sepanjang 1-256 aksara.'
|
ErrComposeNameInvalid: 'Nama projek Compose tidak sah: mesti bermula dengan huruf kecil atau nombor, hanya mengandungi huruf kecil, nombor, tanda sempang dan garis bawah, serta sepanjang 1-256 aksara.'
|
||||||
ErrAppVersionUnavailable: 'Versi aplikasi ini telah dialih keluar daripada perkhidmatan jauh. Sila pilih versi lain dan cuba lagi.'
|
ErrAppVersionUnavailable: 'Versi aplikasi ini telah dialih keluar daripada perkhidmatan jauh. Sila pilih versi lain dan cuba lagi.'
|
||||||
ErrVllmGB10ProOnly: 'Versi vLLM ini hanya tersedia dalam 1Panel Edisi Profesional.'
|
ErrVllmGB10ProOnly: 'Versi vLLM ini hanya tersedia dalam 1Panel Edisi Profesional.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway hanya boleh dinaik taraf dalam jenis pangkalan data yang sama (SQLite atau PostgreSQL).'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'Sandaran AI Gateway dan aplikasi sasaran mesti menggunakan jenis pangkalan data yang sama (SQLite atau PostgreSQL).'
|
||||||
ErrAppWarn: 'Status tidak normal, sila semak log'
|
ErrAppWarn: 'Status tidak normal, sila semak log'
|
||||||
ErrAppParamKey: 'Parameter {{ .name }} medan tidak normal'
|
ErrAppParamKey: 'Parameter {{ .name }} medan tidak normal'
|
||||||
ErrAppUpgrade: 'Peningkatan {{ .name }} aplikasi gagal {{ .err }}'
|
ErrAppUpgrade: 'Peningkatan {{ .name }} aplikasi gagal {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'Cache binaan'
|
|||||||
Volume: 'Jilid storan'
|
Volume: 'Jilid storan'
|
||||||
Network: 'Rangkaian'
|
Network: 'Rangkaian'
|
||||||
PruneStart: 'Pembersihan sedang dijalankan, sila tunggu...'
|
PruneStart: 'Pembersihan sedang dijalankan, sila tunggu...'
|
||||||
|
NetworkCleanupConnected: "Rangkaian [{{ .name }}] ({{ .id }}) dilangkau: terdapat kontena yang disambungkan atau rangkaian sedang digunakan; tidak dipadamkan"
|
||||||
|
NetworkCleanupSummary: "Pembersihan rangkaian selesai: dipadamkan {{ .deleted }}, dilangkau {{ .skipped }}, gagal {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "Sesetengah rangkaian tidak dapat dibersihkan; semak log tugas"
|
||||||
ContainerBackupPrepare: 'Sediakan ruang kerja sandaran kontena'
|
ContainerBackupPrepare: 'Sediakan ruang kerja sandaran kontena'
|
||||||
ContainerBackupStop: 'Hentikan kontena sebelum sandaran'
|
ContainerBackupStop: 'Hentikan kontena sebelum sandaran'
|
||||||
ContainerBackupInspect: 'Sandarkan inspect dan rangkaian kontena'
|
ContainerBackupInspect: 'Sandarkan inspect dan rangkaian kontena'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "Pemajuan IPv4 dilumpuhkan. Tetapkan net.ipv4.i
|
|||||||
ErrFirewallRuleSavedApplyFailed: "Konfigurasi peraturan baharu telah disimpan, tetapi gagal digunakan pada tembok api. Cuba lagi melalui penyegerakan: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "Konfigurasi peraturan baharu telah disimpan, tetapi gagal digunakan pada tembok api. Cuba lagi melalui penyegerakan: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "Peraturan UFW dengan syarat padanan yang sama tetapi tindakan berbeza sudah wujud. Sila edit peraturan sedia ada."
|
ErrFirewallRuleConflict: "Peraturan UFW dengan syarat padanan yang sama tetapi tindakan berbeza sudah wujud. Sila edit peraturan sedia ada."
|
||||||
ErrForwardInterfaceNotFound: "Antara muka rangkaian {{ .name }} yang dinyatakan dalam peraturan pemajuan tidak wujud. Semak namanya dan cuba lagi."
|
ErrForwardInterfaceNotFound: "Antara muka rangkaian {{ .name }} yang dinyatakan dalam peraturan pemajuan tidak wujud. Semak namanya dan cuba lagi."
|
||||||
|
ErrIPv6RARisk: "Pemajuan IPv6 disekat bagi mengelakkan risiko kehilangan sambungan rangkaian. Rujuk dokumentasi dan log sistem."
|
||||||
|
ErrIPv6RACheckFailed: "Pemajuan IPv6 disekat kerana konfigurasi rangkaian tidak dapat diperiksa. Lihat log sistem untuk butiran."
|
||||||
|
|
||||||
ErrSSHPublicKey: "Masukkan kunci awam SSH yang sah"
|
ErrSSHPublicKey: "Masukkan kunci awam SSH yang sah"
|
||||||
ErrSSHPrivateKey: "Kunci peribadi SSH tidak sah atau frasa laluan salah; kunci yang disulitkan memerlukan frasa laluan asal"
|
ErrSSHPrivateKey: "Kunci peribadi SSH tidak sah atau frasa laluan salah; kunci yang disulitkan memerlukan frasa laluan asal"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'Não foi possível derivar um nome de projeto da co
|
|||||||
ErrComposeNameInvalid: 'Nome de projeto Compose inválido: deve começar com uma letra minúscula ou número, conter apenas letras minúsculas, números, hífens e sublinhados e ter de 1 a 256 caracteres.'
|
ErrComposeNameInvalid: 'Nome de projeto Compose inválido: deve começar com uma letra minúscula ou número, conter apenas letras minúsculas, números, hífens e sublinhados e ter de 1 a 256 caracteres.'
|
||||||
ErrAppVersionUnavailable: 'Esta versão do aplicativo foi removida do serviço remoto. Selecione outra versão e tente novamente.'
|
ErrAppVersionUnavailable: 'Esta versão do aplicativo foi removida do serviço remoto. Selecione outra versão e tente novamente.'
|
||||||
ErrVllmGB10ProOnly: 'Esta versão do vLLM está disponível apenas no 1Panel Professional Edition.'
|
ErrVllmGB10ProOnly: 'Esta versão do vLLM está disponível apenas no 1Panel Professional Edition.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'O AI Gateway só pode ser atualizado dentro do mesmo tipo de banco de dados (SQLite ou PostgreSQL).'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'O backup do AI Gateway e o aplicativo de destino devem usar o mesmo tipo de banco de dados (SQLite ou PostgreSQL).'
|
||||||
ErrAppWarn: 'Status anormal, verifique o log'
|
ErrAppWarn: 'Status anormal, verifique o log'
|
||||||
ErrAppParamKey: 'O campo de parâmetro {{ .name }} está anormal'
|
ErrAppParamKey: 'O campo de parâmetro {{ .name }} está anormal'
|
||||||
ErrAppUpgrade: 'Falha na atualização do aplicativo {{ .name }} {{ .err }}'
|
ErrAppUpgrade: 'Falha na atualização do aplicativo {{ .name }} {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'Cache de construção'
|
|||||||
Volume: 'Volume de armazenamento'
|
Volume: 'Volume de armazenamento'
|
||||||
Network: 'Rede'
|
Network: 'Rede'
|
||||||
PruneStart: 'Limpando em andamento aguarde...'
|
PruneStart: 'Limpando em andamento aguarde...'
|
||||||
|
NetworkCleanupConnected: "Rede [{{ .name }}] ({{ .id }}) ignorada: há contêineres conectados ou a rede está em uso; não foi excluída"
|
||||||
|
NetworkCleanupSummary: "Limpeza de redes concluída: excluídas {{ .deleted }}, ignoradas {{ .skipped }}, falhas {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "Não foi possível limpar algumas redes; consulte o log da tarefa"
|
||||||
ContainerBackupPrepare: 'Preparar área de backup do contêiner'
|
ContainerBackupPrepare: 'Preparar área de backup do contêiner'
|
||||||
ContainerBackupStop: 'Parar contêiner antes do backup'
|
ContainerBackupStop: 'Parar contêiner antes do backup'
|
||||||
ContainerBackupInspect: 'Fazer backup do inspect e rede do contêiner'
|
ContainerBackupInspect: 'Fazer backup do inspect e rede do contêiner'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "O encaminhamento IPv4 está desativado. Defina
|
|||||||
ErrFirewallRuleSavedApplyFailed: "A configuração da nova regra foi salva, mas não pôde ser aplicada ao firewall. Tente novamente por meio da sincronização: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "A configuração da nova regra foi salva, mas não pôde ser aplicada ao firewall. Tente novamente por meio da sincronização: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "Já existe uma regra UFW com as mesmas condições e uma ação diferente. Edite a regra existente."
|
ErrFirewallRuleConflict: "Já existe uma regra UFW com as mesmas condições e uma ação diferente. Edite a regra existente."
|
||||||
ErrForwardInterfaceNotFound: "A interface de rede {{ .name }} especificada na regra de encaminhamento não existe. Verifique o nome e tente novamente."
|
ErrForwardInterfaceNotFound: "A interface de rede {{ .name }} especificada na regra de encaminhamento não existe. Verifique o nome e tente novamente."
|
||||||
|
ErrIPv6RARisk: "O encaminhamento IPv6 foi bloqueado para evitar uma possível perda de conexão. Consulte a documentação e os logs do sistema."
|
||||||
|
ErrIPv6RACheckFailed: "O encaminhamento IPv6 foi bloqueado porque não foi possível verificar a configuração de rede. Consulte os logs do sistema."
|
||||||
|
|
||||||
ErrSSHPublicKey: "Informe uma chave pública SSH válida"
|
ErrSSHPublicKey: "Informe uma chave pública SSH válida"
|
||||||
ErrSSHPrivateKey: "Chave privada SSH inválida ou senha incorreta; chaves criptografadas exigem a senha original"
|
ErrSSHPrivateKey: "Chave privada SSH inválida ou senha incorreta; chaves criptografadas exigem a senha original"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'Не удалось получить имя про
|
|||||||
ErrComposeNameInvalid: 'Недопустимое имя проекта Compose: оно должно начинаться со строчной буквы или цифры, содержать только строчные буквы, цифры, дефисы и подчёркивания и иметь длину от 1 до 256 символов.'
|
ErrComposeNameInvalid: 'Недопустимое имя проекта Compose: оно должно начинаться со строчной буквы или цифры, содержать только строчные буквы, цифры, дефисы и подчёркивания и иметь длину от 1 до 256 символов.'
|
||||||
ErrAppVersionUnavailable: 'Эта версия приложения удалена из удаленного сервиса. Выберите другую версию и повторите попытку.'
|
ErrAppVersionUnavailable: 'Эта версия приложения удалена из удаленного сервиса. Выберите другую версию и повторите попытку.'
|
||||||
ErrVllmGB10ProOnly: 'Эта версия vLLM доступна только в профессиональной редакции 1Panel.'
|
ErrVllmGB10ProOnly: 'Эта версия vLLM доступна только в профессиональной редакции 1Panel.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway можно обновлять только в пределах одного типа базы данных (SQLite или PostgreSQL).'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'Резервная копия AI Gateway и целевое приложение должны использовать один и тот же тип базы данных (SQLite или PostgreSQL).'
|
||||||
ErrAppWarn: 'Ненормальное состояние, проверьте журнал'
|
ErrAppWarn: 'Ненормальное состояние, проверьте журнал'
|
||||||
ErrAppParamKey: 'Поле параметра {{ .name }} ненормально'
|
ErrAppParamKey: 'Поле параметра {{ .name }} ненормально'
|
||||||
ErrAppUpgrade: 'Обновление приложения {{ .name }} не удалось {{ .err }}'
|
ErrAppUpgrade: 'Обновление приложения {{ .name }} не удалось {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'Кэш сборки'
|
|||||||
Volume: 'Том хранилища'
|
Volume: 'Том хранилища'
|
||||||
Network: 'Сеть'
|
Network: 'Сеть'
|
||||||
PruneStart: 'Очистка выполняется, пожалуйста, подождите...'
|
PruneStart: 'Очистка выполняется, пожалуйста, подождите...'
|
||||||
|
NetworkCleanupConnected: "Сеть [{{ .name }}] ({{ .id }}) пропущена: подключены контейнеры или сеть используется; не удалена"
|
||||||
|
NetworkCleanupSummary: "Очистка сетей завершена: удалено {{ .deleted }}, пропущено {{ .skipped }}, ошибок {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "Не удалось очистить некоторые сети; проверьте журнал задачи"
|
||||||
ContainerBackupPrepare: 'Подготовить рабочую область резервной копии контейнера'
|
ContainerBackupPrepare: 'Подготовить рабочую область резервной копии контейнера'
|
||||||
ContainerBackupStop: 'Остановить контейнер перед резервным копированием'
|
ContainerBackupStop: 'Остановить контейнер перед резервным копированием'
|
||||||
ContainerBackupInspect: 'Сделать резервную копию inspect и сети контейнера'
|
ContainerBackupInspect: 'Сделать резервную копию inspect и сети контейнера'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "Пересылка IPv4 отключена. П
|
|||||||
ErrFirewallRuleSavedApplyFailed: "Настройки нового правила сохранены, но применить их к межсетевому экрану не удалось. Повторите попытку с помощью синхронизации: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "Настройки нового правила сохранены, но применить их к межсетевому экрану не удалось. Повторите попытку с помощью синхронизации: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "В UFW уже есть правило с такими же условиями и другим действием. Отредактируйте существующее правило."
|
ErrFirewallRuleConflict: "В UFW уже есть правило с такими же условиями и другим действием. Отредактируйте существующее правило."
|
||||||
ErrForwardInterfaceNotFound: "Сетевой интерфейс {{ .name }}, указанный в правиле перенаправления, не существует. Проверьте имя и повторите попытку."
|
ErrForwardInterfaceNotFound: "Сетевой интерфейс {{ .name }}, указанный в правиле перенаправления, не существует. Проверьте имя и повторите попытку."
|
||||||
|
ErrIPv6RARisk: "Включение переадресации IPv6 заблокировано из-за риска потери связи. См. документацию и системные журналы."
|
||||||
|
ErrIPv6RACheckFailed: "Включение переадресации IPv6 заблокировано: не удалось проверить настройки сети. Подробности в системных журналах."
|
||||||
|
|
||||||
ErrSSHPublicKey: "Введите действительный открытый ключ SSH"
|
ErrSSHPublicKey: "Введите действительный открытый ключ SSH"
|
||||||
ErrSSHPrivateKey: "Недопустимый закрытый ключ SSH или неверная парольная фраза; для зашифрованных ключей требуется исходная парольная фраза"
|
ErrSSHPrivateKey: "Недопустимый закрытый ключ SSH или неверная парольная фраза; для зашифрованных ключей требуется исходная парольная фраза"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: 'Docker Compose yapılandırmasından veya dosyanın
|
|||||||
ErrComposeNameInvalid: 'Geçersiz Compose proje adı: küçük harf veya rakamla başlamalı, yalnızca küçük harf, rakam, kısa çizgi ve alt çizgi içermeli ve 1-256 karakter uzunluğunda olmalıdır.'
|
ErrComposeNameInvalid: 'Geçersiz Compose proje adı: küçük harf veya rakamla başlamalı, yalnızca küçük harf, rakam, kısa çizgi ve alt çizgi içermeli ve 1-256 karakter uzunluğunda olmalıdır.'
|
||||||
ErrAppVersionUnavailable: 'Bu uygulama sürümü uzak hizmetten kaldırıldı. Lütfen başka bir sürüm seçip tekrar deneyin.'
|
ErrAppVersionUnavailable: 'Bu uygulama sürümü uzak hizmetten kaldırıldı. Lütfen başka bir sürüm seçip tekrar deneyin.'
|
||||||
ErrVllmGB10ProOnly: 'Bu vLLM sürümü yalnızca 1Panel Professional Edition sürümünde kullanılabilir.'
|
ErrVllmGB10ProOnly: 'Bu vLLM sürümü yalnızca 1Panel Professional Edition sürümünde kullanılabilir.'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway yalnızca aynı veritabanı türü (SQLite veya PostgreSQL) içinde yükseltilebilir.'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI Gateway yedeği ve hedef uygulama aynı veritabanı türünü (SQLite veya PostgreSQL) kullanmalıdır.'
|
||||||
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
|
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
|
||||||
ErrAppParamKey: 'Parametre {{ .name }} alanı anormal'
|
ErrAppParamKey: 'Parametre {{ .name }} alanı anormal'
|
||||||
ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}'
|
ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: 'Derleme önbelleği'
|
|||||||
Volume: 'Depolama hacmi'
|
Volume: 'Depolama hacmi'
|
||||||
Network: 'Ağ'
|
Network: 'Ağ'
|
||||||
PruneStart: 'Temizlik devam ediyor, lütfen bekleyin...'
|
PruneStart: 'Temizlik devam ediyor, lütfen bekleyin...'
|
||||||
|
NetworkCleanupConnected: "Ağ [{{ .name }}] ({{ .id }}) atlandı: bağlı konteynerler var veya ağ kullanımda; silinmedi"
|
||||||
|
NetworkCleanupSummary: "Ağ temizliği tamamlandı: silinen {{ .deleted }}, atlanan {{ .skipped }}, başarısız {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "Bazı ağlar temizlenemedi; görev günlüğünü kontrol edin"
|
||||||
ContainerBackupPrepare: 'Konteyner yedekleme çalışma alanını hazırla'
|
ContainerBackupPrepare: 'Konteyner yedekleme çalışma alanını hazırla'
|
||||||
ContainerBackupStop: 'Yedeklemeden önce konteyneri durdur'
|
ContainerBackupStop: 'Yedeklemeden önce konteyneri durdur'
|
||||||
ContainerBackupInspect: 'Konteyner inspect ve ağ bilgisini yedekle'
|
ContainerBackupInspect: 'Konteyner inspect ve ağ bilgisini yedekle'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 yönlendirmesi devre dışı. Docker güv
|
|||||||
ErrFirewallRuleSavedApplyFailed: "Yeni kural yapılandırması kaydedildi, ancak güvenlik duvarına uygulanamadı. Eşitleme yaparak yeniden deneyin: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "Yeni kural yapılandırması kaydedildi, ancak güvenlik duvarına uygulanamadı. Eşitleme yaparak yeniden deneyin: {{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "UFW içinde aynı eşleşme koşullarına sahip, farklı bir eylem uygulayan bir kural zaten var. Mevcut kuralı düzenleyin."
|
ErrFirewallRuleConflict: "UFW içinde aynı eşleşme koşullarına sahip, farklı bir eylem uygulayan bir kural zaten var. Mevcut kuralı düzenleyin."
|
||||||
ErrForwardInterfaceNotFound: "Yönlendirme kuralında belirtilen {{ .name }} ağ arayüzü mevcut değil. Arayüz adını kontrol edip yeniden deneyin."
|
ErrForwardInterfaceNotFound: "Yönlendirme kuralında belirtilen {{ .name }} ağ arayüzü mevcut değil. Arayüz adını kontrol edip yeniden deneyin."
|
||||||
|
ErrIPv6RARisk: "Ağ bağlantısının kesilme riski nedeniyle IPv6 yönlendirmesi engellendi. Belgeleri ve sistem günlüklerini inceleyin."
|
||||||
|
ErrIPv6RACheckFailed: "Ağ yapılandırması denetlenemediği için IPv6 yönlendirmesi engellendi. Ayrıntılar için sistem günlüklerini inceleyin."
|
||||||
|
|
||||||
ErrSSHPublicKey: "Geçerli bir SSH açık anahtarı girin"
|
ErrSSHPublicKey: "Geçerli bir SSH açık anahtarı girin"
|
||||||
ErrSSHPrivateKey: "SSH özel anahtarı geçersiz veya parola yanlış; şifreli anahtarlar mevcut parolayı gerektirir"
|
ErrSSHPrivateKey: "SSH özel anahtarı geçersiz veya parola yanlış; şifreli anahtarlar mevcut parolayı gerektirir"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: '無法從 Docker Compose 設定或檔案父目錄
|
|||||||
ErrComposeNameInvalid: 'Compose 專案名稱格式錯誤:必須以小寫字母或數字開頭,僅支援小寫字母、數字、- 和 _,長度為 1-256 個字元'
|
ErrComposeNameInvalid: 'Compose 專案名稱格式錯誤:必須以小寫字母或數字開頭,僅支援小寫字母、數字、- 和 _,長度為 1-256 個字元'
|
||||||
ErrAppVersionUnavailable: '目前應用程式版本已從遠端服務下架,請選擇其他版本後重試'
|
ErrAppVersionUnavailable: '目前應用程式版本已從遠端服務下架,請選擇其他版本後重試'
|
||||||
ErrVllmGB10ProOnly: '此 vLLM 版本僅支援 1Panel 專業版'
|
ErrVllmGB10ProOnly: '此 vLLM 版本僅支援 1Panel 專業版'
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI 閘道只能在相同資料庫類型(SQLite 或 PostgreSQL)之間升級。'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI 閘道備份與目標應用程式的資料庫類型(SQLite 或 PostgreSQL)必須一致,無法跨資料庫類型還原。'
|
||||||
ErrAppWarn: '狀態異常,請檢視日誌'
|
ErrAppWarn: '狀態異常,請檢視日誌'
|
||||||
ErrAppParamKey: '參數{{ .name }} 欄位異常'
|
ErrAppParamKey: '參數{{ .name }} 欄位異常'
|
||||||
ErrAppUpgrade: '應用程式{{ .name }} 升級失敗{{ .err }}'
|
ErrAppUpgrade: '應用程式{{ .name }} 升級失敗{{ .err }}'
|
||||||
@@ -300,6 +302,9 @@ BuildCache: '構建快取'
|
|||||||
Volume: '磁碟區'
|
Volume: '磁碟區'
|
||||||
Network: '網路'
|
Network: '網路'
|
||||||
PruneStart: '清理中,請稍候...'
|
PruneStart: '清理中,請稍候...'
|
||||||
|
NetworkCleanupConnected: "跳過網路 [{{ .name }}] ({{ .id }}):仍有容器連接或正在使用,未刪除"
|
||||||
|
NetworkCleanupSummary: "網路清理完成:已刪除 {{ .deleted }},已跳過 {{ .skipped }},失敗 {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "部分網路清理失敗,請查看任務日誌"
|
||||||
ContainerBackupPrepare: '準備容器備份工作目錄'
|
ContainerBackupPrepare: '準備容器備份工作目錄'
|
||||||
ContainerBackupStop: '備份前停止容器'
|
ContainerBackupStop: '備份前停止容器'
|
||||||
ContainerBackupInspect: '備份容器 Inspect 與網路資訊'
|
ContainerBackupInspect: '備份容器 Inspect 與網路資訊'
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 轉送尚未啟用,請先設定 net.ipv
|
|||||||
ErrFirewallRuleSavedApplyFailed: "新規則設定已儲存,但套用至防火牆失敗。可透過同步重試:{{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "新規則設定已儲存,但套用至防火牆失敗。可透過同步重試:{{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "UFW 中已存在比對條件相同但動作不同的規則,請編輯既有規則。"
|
ErrFirewallRuleConflict: "UFW 中已存在比對條件相同但動作不同的規則,請編輯既有規則。"
|
||||||
ErrForwardInterfaceNotFound: "轉發規則指定的網卡 {{ .name }} 不存在,請檢查網卡名稱後重試。"
|
ErrForwardInterfaceNotFound: "轉發規則指定的網卡 {{ .name }} 不存在,請檢查網卡名稱後重試。"
|
||||||
|
ErrIPv6RARisk: "IPv6 轉送可能導致網路中斷,已阻止啟用。請參閱文件,詳情見系統日誌。"
|
||||||
|
ErrIPv6RACheckFailed: "無法檢查 IPv6 網路設定,已阻止啟用轉送。詳情見系統日誌。"
|
||||||
|
|
||||||
ErrSSHPublicKey: "請輸入有效的 SSH 公鑰"
|
ErrSSHPublicKey: "請輸入有效的 SSH 公鑰"
|
||||||
ErrSSHPrivateKey: "SSH 私鑰格式無效或密碼錯誤;加密私鑰需要填寫原密碼"
|
ErrSSHPrivateKey: "SSH 私鑰格式無效或密碼錯誤;加密私鑰需要填寫原密碼"
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ ErrComposeProjectNameEmpty: "无法从 Docker Compose 配置或文件父目录
|
|||||||
ErrComposeNameInvalid: "Compose 项目名称格式错误:必须以小写字母或数字开头,仅支持小写字母、数字、- 和 _,长度为 1-256 个字符"
|
ErrComposeNameInvalid: "Compose 项目名称格式错误:必须以小写字母或数字开头,仅支持小写字母、数字、- 和 _,长度为 1-256 个字符"
|
||||||
ErrAppVersionUnavailable: "当前应用版本已从远程服务下架,请选择其他版本后重试"
|
ErrAppVersionUnavailable: "当前应用版本已从远程服务下架,请选择其他版本后重试"
|
||||||
ErrVllmGB10ProOnly: "该 vLLM 版本仅支持 1Panel 专业版"
|
ErrVllmGB10ProOnly: "该 vLLM 版本仅支持 1Panel 专业版"
|
||||||
|
ErrAIGatewayDatabaseTypeMismatch: 'AI 网关只能在相同数据库类型(SQLite 或 PostgreSQL)之间升级。'
|
||||||
|
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI 网关备份与目标应用的数据库类型(SQLite 或 PostgreSQL)必须一致,无法跨数据库类型恢复。'
|
||||||
ErrAppWarn: "状态异常,请查看日志"
|
ErrAppWarn: "状态异常,请查看日志"
|
||||||
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
||||||
ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}"
|
ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}"
|
||||||
@@ -300,6 +302,9 @@ BuildCache: "构建缓存"
|
|||||||
Volume: "存储卷"
|
Volume: "存储卷"
|
||||||
Network: "网络"
|
Network: "网络"
|
||||||
PruneStart: "清理中,请稍候..."
|
PruneStart: "清理中,请稍候..."
|
||||||
|
NetworkCleanupConnected: "跳过网络 [{{ .name }}] ({{ .id }}):仍有容器连接或正在使用,未删除"
|
||||||
|
NetworkCleanupSummary: "网络清理完成:已删除 {{ .deleted }},已跳过 {{ .skipped }},失败 {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "部分网络清理失败,请查看任务日志"
|
||||||
ContainerBackupPrepare: "准备容器备份工作目录"
|
ContainerBackupPrepare: "准备容器备份工作目录"
|
||||||
ContainerBackupStop: "备份前停止容器"
|
ContainerBackupStop: "备份前停止容器"
|
||||||
ContainerBackupInspect: "备份容器 Inspect 与网络信息"
|
ContainerBackupInspect: "备份容器 Inspect 与网络信息"
|
||||||
@@ -718,6 +723,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 转发未开启,请先设置 net.ipv4.i
|
|||||||
ErrFirewallRuleSavedApplyFailed: "新规则配置已保存,但应用到防火墙失败。可通过同步重试:{{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "新规则配置已保存,但应用到防火墙失败。可通过同步重试:{{ .detail }}"
|
||||||
ErrFirewallRuleConflict: "UFW 中已存在匹配条件相同但动作不同的规则,请编辑已有规则。"
|
ErrFirewallRuleConflict: "UFW 中已存在匹配条件相同但动作不同的规则,请编辑已有规则。"
|
||||||
ErrForwardInterfaceNotFound: "转发规则指定的网卡 {{ .name }} 不存在,请检查网卡名称后重试。"
|
ErrForwardInterfaceNotFound: "转发规则指定的网卡 {{ .name }} 不存在,请检查网卡名称后重试。"
|
||||||
|
ErrIPv6RARisk: "IPv6 转发存在断网风险,已阻止启用。请参阅文档,详情见系统日志。"
|
||||||
|
ErrIPv6RACheckFailed: "无法检查 IPv6 网络配置,已阻止启用转发。详情见系统日志。"
|
||||||
|
|
||||||
ErrSSHPublicKey: "请输入有效的 SSH 公钥"
|
ErrSSHPublicKey: "请输入有效的 SSH 公钥"
|
||||||
ErrSSHPrivateKey: "SSH 私钥格式无效或密码错误;加密私钥需要填写原密码"
|
ErrSSHPrivateKey: "SSH 私钥格式无效或密码错误;加密私钥需要填写原密码"
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ func agentDBMigrations() []*gormigrate.Migration {
|
|||||||
migrations.AddAcceleratorMetrics,
|
migrations.AddAcceleratorMetrics,
|
||||||
migrations.AddVLLMMonitor,
|
migrations.AddVLLMMonitor,
|
||||||
migrations.AddMonitorSettings,
|
migrations.AddMonitorSettings,
|
||||||
|
migrations.AddVLLMRequestMetrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1874,3 +1874,10 @@ var AddMonitorSettings = &gormigrate.Migration{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddVLLMRequestMetrics = &gormigrate.Migration{
|
||||||
|
ID: "20261010-vllm-request-metrics",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package vllm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HistoryAccumulator struct {
|
||||||
|
histograms map[string]histogram
|
||||||
|
prefixHits float64
|
||||||
|
prefixQueries float64
|
||||||
|
finishReasons map[string]float64
|
||||||
|
finishSeconds map[string]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *HistoryAccumulator) Add(histograms, requests, aggregation, group string) error {
|
||||||
|
if histograms != "" {
|
||||||
|
var sample map[string]histogram
|
||||||
|
if aggregation == "max" || group == "requests" {
|
||||||
|
var distributions struct {
|
||||||
|
Prompt *histogram `json:"request_prompt_tokens"`
|
||||||
|
Generation *histogram `json:"request_generation_tokens"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(histograms), &distributions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sample = map[string]histogram{}
|
||||||
|
if distributions.Prompt != nil {
|
||||||
|
sample["request_prompt_tokens"] = *distributions.Prompt
|
||||||
|
}
|
||||||
|
if distributions.Generation != nil {
|
||||||
|
sample["request_generation_tokens"] = *distributions.Generation
|
||||||
|
}
|
||||||
|
} else if group == "latency" {
|
||||||
|
var latency struct {
|
||||||
|
FirstToken *histogram `json:"time_to_first_token_seconds"`
|
||||||
|
InterToken *histogram `json:"inter_token_latency_seconds"`
|
||||||
|
Request *histogram `json:"e2e_request_latency_seconds"`
|
||||||
|
Prefill *histogram `json:"request_prefill_time_seconds"`
|
||||||
|
Decode *histogram `json:"request_decode_time_seconds"`
|
||||||
|
Queue *histogram `json:"request_queue_time_seconds"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(histograms), &latency); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sample = map[string]histogram{}
|
||||||
|
for name, item := range map[string]*histogram{
|
||||||
|
"time_to_first_token_seconds": latency.FirstToken,
|
||||||
|
"inter_token_latency_seconds": latency.InterToken,
|
||||||
|
"e2e_request_latency_seconds": latency.Request,
|
||||||
|
"request_prefill_time_seconds": latency.Prefill,
|
||||||
|
"request_decode_time_seconds": latency.Decode,
|
||||||
|
"request_queue_time_seconds": latency.Queue,
|
||||||
|
} {
|
||||||
|
if item != nil {
|
||||||
|
sample[name] = *item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err := json.Unmarshal([]byte(histograms), &sample); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if a.histograms == nil {
|
||||||
|
a.histograms = map[string]histogram{}
|
||||||
|
}
|
||||||
|
for name, item := range sample {
|
||||||
|
switch name {
|
||||||
|
case "request_prefill_time_seconds", "request_decode_time_seconds", "request_queue_time_seconds":
|
||||||
|
item.Buckets = nil
|
||||||
|
}
|
||||||
|
total, exists := a.histograms[name]
|
||||||
|
if !exists {
|
||||||
|
a.histograms[name] = item
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total.Seconds += item.Seconds
|
||||||
|
total.Sum += item.Sum
|
||||||
|
total.Count += item.Count
|
||||||
|
if len(total.Buckets) != len(item.Buckets) {
|
||||||
|
total.Buckets = nil
|
||||||
|
}
|
||||||
|
for bound, count := range total.Buckets {
|
||||||
|
addition, ok := item.Buckets[bound]
|
||||||
|
if !ok {
|
||||||
|
total.Buckets = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
total.Buckets[bound] = count + addition
|
||||||
|
}
|
||||||
|
a.histograms[name] = total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if requests == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if group == "overview" {
|
||||||
|
var prefix struct {
|
||||||
|
Seconds float64 `json:"seconds"`
|
||||||
|
Hits *float64 `json:"prefixHits"`
|
||||||
|
Queries *float64 `json:"prefixQueries"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(requests), &prefix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if prefix.Seconds > 0 && prefix.Hits != nil && prefix.Queries != nil {
|
||||||
|
a.prefixHits += *prefix.Hits
|
||||||
|
a.prefixQueries += *prefix.Queries
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var sample requestMetrics
|
||||||
|
if err := json.Unmarshal([]byte(requests), &sample); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if sample.Seconds <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if group == "" && aggregation != "max" && sample.PrefixHits != nil && sample.PrefixQueries != nil {
|
||||||
|
a.prefixHits += *sample.PrefixHits
|
||||||
|
a.prefixQueries += *sample.PrefixQueries
|
||||||
|
}
|
||||||
|
for reason, count := range sample.FinishReasons {
|
||||||
|
if a.finishReasons == nil {
|
||||||
|
a.finishReasons = map[string]float64{}
|
||||||
|
}
|
||||||
|
if aggregation == "max" {
|
||||||
|
a.finishReasons[reason] = math.Max(a.finishReasons[reason], count/sample.Seconds)
|
||||||
|
} else {
|
||||||
|
if a.finishSeconds == nil {
|
||||||
|
a.finishSeconds = map[string]float64{}
|
||||||
|
}
|
||||||
|
a.finishReasons[reason] += count
|
||||||
|
a.finishSeconds[reason] += sample.Seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *HistoryAccumulator) Apply(result *model.MonitorVLLM, aggregation string) {
|
||||||
|
applyHistograms(result, a.histograms, aggregation != "max")
|
||||||
|
for reason, count := range a.finishReasons {
|
||||||
|
if result.FinishReasons == nil {
|
||||||
|
result.FinishReasons = map[string]float64{}
|
||||||
|
}
|
||||||
|
if aggregation == "max" {
|
||||||
|
result.FinishReasons[reason] = count
|
||||||
|
} else {
|
||||||
|
result.FinishReasons[reason] = count / a.finishSeconds[reason]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aggregation != "max" && a.prefixQueries > 0 {
|
||||||
|
rate := a.prefixHits / a.prefixQueries * 100
|
||||||
|
result.PrefixCacheHitRate = &rate
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
type Metrics map[string]map[string]float64
|
type Metrics map[string]map[string]float64
|
||||||
|
|
||||||
type histogram struct {
|
type histogram struct {
|
||||||
|
Seconds float64 `json:"seconds,omitempty"`
|
||||||
Sum float64 `json:"sum"`
|
Sum float64 `json:"sum"`
|
||||||
Count float64 `json:"count"`
|
Count float64 `json:"count"`
|
||||||
Buckets map[string]float64 `json:"buckets,omitempty"`
|
Buckets map[string]float64 `json:"buckets,omitempty"`
|
||||||
@@ -37,14 +38,16 @@ func Parse(reader io.Reader) (Metrics, error) {
|
|||||||
}
|
}
|
||||||
name := strings.TrimPrefix(match[1], "vllm:")
|
name := strings.TrimPrefix(match[1], "vllm:")
|
||||||
switch name {
|
switch name {
|
||||||
case "num_requests_running", "num_requests_waiting", "kv_cache_usage_perc", "gpu_cache_usage_perc", "prompt_tokens_total", "generation_tokens_total", "request_success_total":
|
case "request_prefill_time_seconds_bucket", "request_decode_time_seconds_bucket", "request_queue_time_seconds_bucket":
|
||||||
|
continue
|
||||||
|
case "num_requests_running", "num_requests_waiting", "kv_cache_usage_perc", "gpu_cache_usage_perc", "prompt_tokens_total", "generation_tokens_total", "request_success_total", "prefix_cache_hits_total", "prefix_cache_queries_total":
|
||||||
default:
|
default:
|
||||||
base := strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(name, "_sum"), "_count"), "_bucket")
|
base := strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(name, "_sum"), "_count"), "_bucket")
|
||||||
if base == name {
|
if base == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch base {
|
switch base {
|
||||||
case "time_to_first_token_seconds", "time_per_output_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds":
|
case "time_to_first_token_seconds", "time_per_output_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds", "request_queue_time_seconds", "request_prompt_tokens", "request_generation_tokens":
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -117,7 +120,7 @@ func Calculate(current, previous Metrics, elapsed float64) (model.MonitorVLLM, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
histograms := map[string]histogram{}
|
histograms := map[string]histogram{}
|
||||||
for _, name := range []string{"time_to_first_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds"} {
|
for _, name := range []string{"time_to_first_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds", "request_queue_time_seconds", "request_prompt_tokens", "request_generation_tokens"} {
|
||||||
source := name
|
source := name
|
||||||
if name == "inter_token_latency_seconds" && len(current[name+"_count"]) == 0 {
|
if name == "inter_token_latency_seconds" && len(current[name+"_count"]) == 0 {
|
||||||
source = "time_per_output_token_seconds"
|
source = "time_per_output_token_seconds"
|
||||||
@@ -127,9 +130,21 @@ func Calculate(current, previous Metrics, elapsed float64) (model.MonitorVLLM, e
|
|||||||
if sum == nil || count == nil {
|
if sum == nil || count == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
histograms[name] = histogram{Sum: *sum, Count: *count, Buckets: bucketDeltas(current[source+"_bucket"], previous[source+"_bucket"], *count)}
|
item := histogram{Sum: *sum, Count: *count}
|
||||||
|
switch name {
|
||||||
|
case "request_prefill_time_seconds", "request_decode_time_seconds", "request_queue_time_seconds":
|
||||||
|
default:
|
||||||
|
item.Buckets = bucketDeltas(current[source+"_bucket"], previous[source+"_bucket"], *count)
|
||||||
|
}
|
||||||
|
if name == "request_prompt_tokens" || name == "request_generation_tokens" {
|
||||||
|
item.Seconds = elapsed
|
||||||
|
}
|
||||||
|
histograms[name] = item
|
||||||
|
}
|
||||||
|
applyHistograms(&result, histograms, true)
|
||||||
|
if err := calculateRequestMetrics(current, previous, elapsed, &result); err != nil {
|
||||||
|
return model.MonitorVLLM{}, err
|
||||||
}
|
}
|
||||||
applyHistograms(&result, histograms)
|
|
||||||
encoded, err := json.Marshal(histograms)
|
encoded, err := json.Marshal(histograms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.MonitorVLLM{}, err
|
return model.MonitorVLLM{}, err
|
||||||
@@ -138,43 +153,24 @@ func Calculate(current, previous Metrics, elapsed float64) (model.MonitorVLLM, e
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AggregateHistograms(encoded string, result *model.MonitorVLLM) error {
|
func applyHistograms(result *model.MonitorVLLM, histograms map[string]histogram, includeMeans bool) {
|
||||||
var samples []map[string]histogram
|
for name, target := range map[string]*map[string]float64{
|
||||||
if err := json.Unmarshal([]byte(encoded), &samples); err != nil {
|
"request_prompt_tokens": &result.PromptDistribution,
|
||||||
return err
|
"request_generation_tokens": &result.GenerationDistribution,
|
||||||
}
|
} {
|
||||||
merged := map[string]histogram{}
|
item := histograms[name]
|
||||||
for _, sample := range samples {
|
if item.Seconds <= 0 || len(item.Buckets) == 0 {
|
||||||
for name, item := range sample {
|
continue
|
||||||
if item.Count == 0 {
|
}
|
||||||
continue
|
result.IntervalSeconds = math.Max(result.IntervalSeconds, item.Seconds)
|
||||||
}
|
*target = make(map[string]float64, len(item.Buckets))
|
||||||
total, exists := merged[name]
|
for bound, count := range item.Buckets {
|
||||||
if !exists {
|
(*target)[bound] = count / item.Seconds
|
||||||
merged[name] = item
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
total.Sum += item.Sum
|
|
||||||
total.Count += item.Count
|
|
||||||
if len(total.Buckets) != len(item.Buckets) {
|
|
||||||
total.Buckets = nil
|
|
||||||
}
|
|
||||||
for bound, count := range total.Buckets {
|
|
||||||
addition, ok := item.Buckets[bound]
|
|
||||||
if !ok {
|
|
||||||
total.Buckets = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
total.Buckets[bound] = count + addition
|
|
||||||
}
|
|
||||||
merged[name] = total
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
applyHistograms(result, merged)
|
if !includeMeans {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyHistograms(result *model.MonitorVLLM, histograms map[string]histogram) {
|
|
||||||
for _, item := range []struct {
|
for _, item := range []struct {
|
||||||
name string
|
name string
|
||||||
mean **float64
|
mean **float64
|
||||||
@@ -185,6 +181,8 @@ func applyHistograms(result *model.MonitorVLLM, histograms map[string]histogram)
|
|||||||
{"e2e_request_latency_seconds", &result.RequestLatency, []**float64{&result.RequestLatencyP50, &result.RequestLatencyP90, &result.RequestLatencyP95, &result.RequestLatencyP99}},
|
{"e2e_request_latency_seconds", &result.RequestLatency, []**float64{&result.RequestLatencyP50, &result.RequestLatencyP90, &result.RequestLatencyP95, &result.RequestLatencyP99}},
|
||||||
{"request_prefill_time_seconds", &result.PrefillTime, nil},
|
{"request_prefill_time_seconds", &result.PrefillTime, nil},
|
||||||
{"request_decode_time_seconds", &result.DecodeTime, nil},
|
{"request_decode_time_seconds", &result.DecodeTime, nil},
|
||||||
|
{"request_queue_time_seconds", &result.QueueTime, nil},
|
||||||
|
{"request_generation_tokens", &result.AverageGenerationTokens, nil},
|
||||||
} {
|
} {
|
||||||
histogram, ok := histograms[item.name]
|
histogram, ok := histograms[item.name]
|
||||||
if !ok || histogram.Count <= 0 {
|
if !ok || histogram.Count <= 0 {
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package vllm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||||
|
)
|
||||||
|
|
||||||
|
type requestMetrics struct {
|
||||||
|
Seconds float64 `json:"seconds"`
|
||||||
|
PrefixHits *float64 `json:"prefixHits,omitempty"`
|
||||||
|
PrefixQueries *float64 `json:"prefixQueries,omitempty"`
|
||||||
|
FinishReasons map[string]float64 `json:"finishReasons,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateRequestMetrics(current, previous Metrics, elapsed float64, result *model.MonitorVLLM) error {
|
||||||
|
sample := requestMetrics{Seconds: elapsed, FinishReasons: map[string]float64{}}
|
||||||
|
hits := counterDelta(current["prefix_cache_hits_total"], previous["prefix_cache_hits_total"])
|
||||||
|
queries := counterDelta(current["prefix_cache_queries_total"], previous["prefix_cache_queries_total"])
|
||||||
|
if hits != nil && queries != nil && *hits <= *queries {
|
||||||
|
sample.PrefixHits, sample.PrefixQueries = hits, queries
|
||||||
|
if *queries > 0 {
|
||||||
|
rate := *hits / *queries * 100
|
||||||
|
result.PrefixCacheHitRate = &rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
before := groupFinishReasons(previous["request_success_total"])
|
||||||
|
for reason, values := range groupFinishReasons(current["request_success_total"]) {
|
||||||
|
if delta := counterDelta(values, before[reason]); delta != nil {
|
||||||
|
sample.FinishReasons[reason] = *delta
|
||||||
|
if result.FinishReasons == nil {
|
||||||
|
result.FinishReasons = map[string]float64{}
|
||||||
|
}
|
||||||
|
result.FinishReasons[reason] = *delta / elapsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoded, err := json.Marshal(sample)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result.RequestDeltas = string(encoded)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupFinishReasons(samples map[string]float64) map[string]map[string]float64 {
|
||||||
|
groups := map[string]map[string]float64{}
|
||||||
|
for labels, value := range samples {
|
||||||
|
for _, label := range re.GetRegex(re.VLLMMetricLabelPattern).FindAllStringSubmatch(labels, -1) {
|
||||||
|
if label[1] != "finished_reason" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
reason, err := strconv.Unquote(label[2])
|
||||||
|
if err != nil || reason == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if groups[reason] == nil {
|
||||||
|
groups[reason] = map[string]float64{}
|
||||||
|
}
|
||||||
|
groups[reason][labels] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
@@ -251,6 +251,10 @@ func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCo
|
|||||||
cmdItem.SysProcAttr = &syscall.SysProcAttr{
|
cmdItem.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Setpgid: true,
|
Setpgid: true,
|
||||||
}
|
}
|
||||||
|
cmdItem.Cancel = func() error {
|
||||||
|
_ = syscall.Kill(-cmdItem.Process.Pid, syscall.SIGKILL)
|
||||||
|
return cmdItem.Process.Kill()
|
||||||
|
}
|
||||||
cmds = append(cmds, cmdItem)
|
cmds = append(cmds, cmdItem)
|
||||||
}
|
}
|
||||||
return cmds
|
return cmds
|
||||||
@@ -335,6 +339,12 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
|
|||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Setpgid: true,
|
Setpgid: true,
|
||||||
}
|
}
|
||||||
|
if newContext != nil {
|
||||||
|
cmd.Cancel = func() error {
|
||||||
|
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
return cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
customWriter := &CustomWriter{taskItem: c.taskItem}
|
customWriter := &CustomWriter{taskItem: c.taskItem}
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
@@ -398,6 +408,9 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
|
|||||||
}()
|
}()
|
||||||
select {
|
select {
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
|
if newContext != nil && newContext.Err() != nil {
|
||||||
|
return "", c.pipeResultErr(newContext, err)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handleErr(&stdout, &stderr, c.IgnoreExist1, err)
|
return handleErr(&stdout, &stderr, c.IgnoreExist1, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,43 +7,20 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadDBConnByPath(fullPath, dbName string) *gorm.DB {
|
func LoadDBConnByPath(fullPath, dbName string) *gorm.DB {
|
||||||
if _, err := createDirWhenNotExist(true, path.Dir(fullPath)); err != nil {
|
|
||||||
panic(fmt.Errorf("init db dir failed, err: %v", err))
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(fullPath); err != nil {
|
|
||||||
f, err := os.Create(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("init %s db file failed, err: %v", dbName, err))
|
|
||||||
}
|
|
||||||
_ = f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := GetDBWithPath(fullPath)
|
db, err := GetDBWithPath(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(fmt.Errorf("init %s db failed, err: %w", dbName, err))
|
||||||
}
|
}
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadDBConnByPathWithErr(fullPath, dbName string) (*gorm.DB, error) {
|
func LoadDBConnByPathWithErr(fullPath, dbName string) (*gorm.DB, error) {
|
||||||
if _, err := createDirWhenNotExist(true, path.Dir(fullPath)); err != nil {
|
|
||||||
return nil, fmt.Errorf("init db dir failed, err: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(fullPath); err != nil {
|
|
||||||
f, err := os.Create(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("init %s db file failed, err: %v", dbName, err)
|
|
||||||
}
|
|
||||||
_ = f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := GetDBWithPath(fullPath)
|
db, err := GetDBWithPath(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init %s db failed, err: %v", dbName, err)
|
return nil, fmt.Errorf("init %s db failed, err: %v", dbName, err)
|
||||||
@@ -60,6 +37,30 @@ func CloseDB(db *gorm.DB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDBWithPath(dbPath string) (*gorm.DB, error) {
|
func GetDBWithPath(dbPath string) (*gorm.DB, error) {
|
||||||
|
dbDir := path.Dir(dbPath)
|
||||||
|
if err := os.MkdirAll(dbDir, 0700); err != nil {
|
||||||
|
return nil, fmt.Errorf("init db dir failed, err: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(dbDir, 0700); err != nil {
|
||||||
|
return nil, fmt.Errorf("set db dir permissions failed, err: %w", err)
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(dbPath, os.O_CREATE|os.O_RDWR, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("init db file failed, err: %w", err)
|
||||||
|
}
|
||||||
|
if err := file.Chmod(0600); err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return nil, fmt.Errorf("set db file permissions failed, err: %w", err)
|
||||||
|
}
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, suffix := range []string{"-wal", "-shm", "-journal"} {
|
||||||
|
if err := os.Chmod(dbPath+suffix, 0600); err != nil && !os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("set db sidecar permissions failed, err: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
Logger: newLogger(),
|
Logger: newLogger(),
|
||||||
@@ -104,17 +105,3 @@ func newLogger() logger.Interface {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDirWhenNotExist(isDir bool, pathItem string) (string, error) {
|
|
||||||
checkPath := pathItem
|
|
||||||
if !isDir {
|
|
||||||
checkPath = path.Dir(pathItem)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(checkPath); err != nil && os.IsNotExist(err) {
|
|
||||||
if err = os.MkdirAll(checkPath, os.ModePerm); err != nil {
|
|
||||||
global.LOG.Errorf("mkdir %s failed, err: %v", checkPath, err)
|
|
||||||
return pathItem, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pathItem, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -158,6 +158,9 @@ func GetComposeImages(filePath string, projectName ...string) ([]string, error)
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
if strings.Contains(filePath, ",") {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
content, readErr := os.ReadFile(filePath)
|
content, readErr := os.ReadFile(filePath)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ func dockerPolicyRuleKey(tokens []string, backend string) string {
|
|||||||
if address, err := netip.ParseAddr(value); err == nil {
|
if address, err := netip.ParseAddr(value); err == nil {
|
||||||
value = netip.PrefixFrom(address, address.BitLen()).String()
|
value = netip.PrefixFrom(address, address.BitLen()).String()
|
||||||
} else if prefix, err := netip.ParsePrefix(value); err == nil {
|
} else if prefix, err := netip.ParsePrefix(value); err == nil {
|
||||||
|
if prefix.Bits() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
value = prefix.Masked().String()
|
value = prefix.Masked().String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +159,13 @@ func parseDockerGuardPolicies(output, family string) (PolicyInventory, error) {
|
|||||||
group.acceptedSource = append(group.acceptedSource, source)
|
group.acceptedSource = append(group.acceptedSource, source)
|
||||||
case action == "accept":
|
case action == "accept":
|
||||||
group.acceptAll = true
|
group.acceptAll = true
|
||||||
case action == "return" && source != "":
|
case action == "return":
|
||||||
|
if source == "" {
|
||||||
|
source = "0.0.0.0/0"
|
||||||
|
if family == FamilyIPv6 {
|
||||||
|
source = "::/0"
|
||||||
|
}
|
||||||
|
}
|
||||||
group.allowedSource = append(group.allowedSource, source)
|
group.allowedSource = append(group.allowedSource, source)
|
||||||
case action == "drop" && source != "":
|
case action == "drop" && source != "":
|
||||||
group.droppedSource = append(group.droppedSource, source)
|
group.droppedSource = append(group.droppedSource, source)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (a *Adapter) CheckRule(ctx context.Context, rule filter.FirewallRule) error
|
|||||||
if rule.Priority == nil || *rule.Priority == 0 {
|
if rule.Priority == nil || *rule.Priority == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
supported, err := a.supportsRichRulePriority(ctx)
|
supported, err := a.SupportsRichRulePriority(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ func (a *Adapter) CheckRule(ctx context.Context, rule filter.FirewallRule) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Adapter) supportsRichRulePriority(ctx context.Context) (bool, error) {
|
func (a *Adapter) SupportsRichRulePriority(ctx context.Context) (bool, error) {
|
||||||
a.prioritySupportMu.Lock()
|
a.prioritySupportMu.Lock()
|
||||||
defer a.prioritySupportMu.Unlock()
|
defer a.prioritySupportMu.Unlock()
|
||||||
if a.richRulePrioritySupported != nil {
|
if a.richRulePrioritySupported != nil {
|
||||||
@@ -82,6 +82,52 @@ func (a *Adapter) supportsRichRulePriority(ctx context.Context) (bool, error) {
|
|||||||
return supported, nil
|
return supported, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RemoveRichRulePriority(raw string) (string, error) {
|
||||||
|
if strings.ContainsAny(raw, "\r\n\x00") {
|
||||||
|
return "", filter.ErrInvalidRule
|
||||||
|
}
|
||||||
|
parts := make([]string, 0)
|
||||||
|
removed := false
|
||||||
|
for start := 0; start < len(raw); {
|
||||||
|
if raw[start] == ' ' || raw[start] == '\t' {
|
||||||
|
start++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
end, quote := start, byte(0)
|
||||||
|
for end < len(raw) {
|
||||||
|
char := raw[end]
|
||||||
|
if char == '\\' && quote != '\'' {
|
||||||
|
end += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if quote != 0 {
|
||||||
|
if char == quote {
|
||||||
|
quote = 0
|
||||||
|
}
|
||||||
|
} else if char == '\'' || char == '"' {
|
||||||
|
quote = char
|
||||||
|
} else if char == ' ' || char == '\t' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
if quote != 0 || end > len(raw) {
|
||||||
|
return "", filter.ErrInvalidRule
|
||||||
|
}
|
||||||
|
token := raw[start:end]
|
||||||
|
if strings.HasPrefix(token, "priority=") {
|
||||||
|
removed = true
|
||||||
|
} else {
|
||||||
|
parts = append(parts, token)
|
||||||
|
}
|
||||||
|
start = end
|
||||||
|
}
|
||||||
|
if !removed || len(parts) == 0 || parts[0] != "rule" {
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
return strings.Join(parts, " "), nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseFirewalldVersion(output string) (int, int, error) {
|
func parseFirewalldVersion(output string) (int, int, error) {
|
||||||
version := strings.TrimSpace(output)
|
version := strings.TrimSpace(output)
|
||||||
parts := strings.Split(version, ".")
|
parts := strings.Split(version, ".")
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forwarding
|
package forwarding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -8,19 +10,27 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ensureFamilyForwardingSysctls(system forwardingSystem, family string) error {
|
func ensureFamilyForwardingSysctls(system forwardingSystem, family string) error {
|
||||||
withIPv6 := family == FamilyIPv6
|
withIPv6 := family == FamilyIPv6
|
||||||
if withIPv6 {
|
if withIPv6 {
|
||||||
interfaces, err := IPv6RAInterfaces(system.ReadFile)
|
interfaces, err := IPv6RAInterfaces(system.ReadFile, LoadDockerIPv4BridgePorts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("check IPv6 Router Advertisement: %w", err)
|
global.LOG.Warnf("IPv6 forwarding blocked: failed to check Router Advertisement: %v", err)
|
||||||
|
return buserr.New("ErrIPv6RACheckFailed")
|
||||||
}
|
}
|
||||||
if len(interfaces) > 0 {
|
if len(interfaces) > 0 {
|
||||||
return fmt.Errorf("IPv6 forwarding blocked: interfaces %s may depend on RA/SLAAC with accept_ra=1; persist accept_ra=2 on interfaces that require RA before retrying", strings.Join(interfaces, ", "))
|
global.LOG.Warnf("IPv6 forwarding blocked: interfaces %s may depend on RA/SLAAC with accept_ra=1; review the network configuration and persist accept_ra=2 on interfaces that require RA before retrying", strings.Join(interfaces, ", "))
|
||||||
|
return buserr.New("ErrIPv6RARisk")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
paths := []string{"/proc/sys/net/ipv4/ip_forward"}
|
paths := []string{"/proc/sys/net/ipv4/ip_forward"}
|
||||||
@@ -43,7 +53,7 @@ func ensureFamilyForwardingSysctls(system forwardingSystem, family string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
|
func IPv6RAInterfaces(readFile func(string) ([]byte, error), loadDockerPorts func() (map[string]bool, error)) ([]string, error) {
|
||||||
addresses, err := readFile("/proc/net/if_inet6")
|
addresses, err := readFile("/proc/net/if_inet6")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -67,21 +77,16 @@ func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, exists := globalAddresses[name]; !exists {
|
globalAddresses[name] = globalAddresses[name] || fields[3] == "00"
|
||||||
globalAddresses[name] = false
|
if fields[3] == "00" && flags&permanentAddress == 0 {
|
||||||
}
|
raInterfaces[name] = true
|
||||||
if fields[3] == "00" {
|
|
||||||
globalAddresses[name] = true
|
|
||||||
if flags&permanentAddress == 0 {
|
|
||||||
raInterfaces[name] = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
routes, err := readFile("/proc/net/ipv6_route")
|
routes, err := readFile("/proc/net/ipv6_route")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
const raRouteFlags = 0x00010000 | 0x00800000
|
const raRouteFlags = 0x00010000 | 0x00080000 | 0x00800000
|
||||||
for _, line := range strings.Split(string(routes), "\n") {
|
for _, line := range strings.Split(string(routes), "\n") {
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
@@ -98,12 +103,15 @@ func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
|
|||||||
raInterfaces[fields[9]] = true
|
raInterfaces[fields[9]] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
uncertainInterfaces := make(map[string]bool)
|
||||||
for name, hasGlobalAddress := range globalAddresses {
|
for name, hasGlobalAddress := range globalAddresses {
|
||||||
if !hasGlobalAddress {
|
if !hasGlobalAddress && !raInterfaces[name] {
|
||||||
raInterfaces[name] = true
|
raInterfaces[name] = true
|
||||||
|
uncertainInterfaces[name] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var interfaces []string
|
var interfaces []string
|
||||||
|
checkDockerPorts := false
|
||||||
for name := range raInterfaces {
|
for name := range raInterfaces {
|
||||||
if name == "lo" {
|
if name == "lo" {
|
||||||
continue
|
continue
|
||||||
@@ -118,15 +126,104 @@ func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
|
|||||||
switch strings.TrimSpace(string(value)) {
|
switch strings.TrimSpace(string(value)) {
|
||||||
case "1":
|
case "1":
|
||||||
interfaces = append(interfaces, name)
|
interfaces = append(interfaces, name)
|
||||||
|
checkDockerPorts = checkDockerPorts || uncertainInterfaces[name]
|
||||||
case "0", "2":
|
case "0", "2":
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid accept_ra value for %s", name)
|
return nil, fmt.Errorf("invalid accept_ra value for %s", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if checkDockerPorts {
|
||||||
|
ports, err := loadDockerPorts()
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Warnf("IPv6 forwarding RA check: cannot exclude Docker bridge ports: %v", err)
|
||||||
|
} else {
|
||||||
|
kept := interfaces[:0]
|
||||||
|
for _, name := range interfaces {
|
||||||
|
if !uncertainInterfaces[name] || !ports[name] {
|
||||||
|
kept = append(kept, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interfaces = kept
|
||||||
|
}
|
||||||
|
}
|
||||||
sort.Strings(interfaces)
|
sort.Strings(interfaces)
|
||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoadDockerIPv4BridgePorts() (map[string]bool, error) {
|
||||||
|
cli, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
if !strings.HasPrefix(cli.DaemonHost(), "unix://") {
|
||||||
|
return nil, errors.New("cannot verify local bridge ports using a remote Docker daemon")
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
info, err := cli.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, option := range info.SecurityOptions {
|
||||||
|
if option == "name=rootless" {
|
||||||
|
return nil, errors.New("cannot verify host bridge ports using a rootless Docker daemon")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
networks, err := cli.NetworkList(ctx, network.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
output, err := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second)).RunWithOptionalSudoAndStdout("ip", "-details", "-json", "link", "show")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dockerIPv4BridgePorts(networks, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dockerIPv4BridgePorts(networks []network.Summary, linkJSON string) (map[string]bool, error) {
|
||||||
|
bridges := make(map[string]bool)
|
||||||
|
for _, item := range networks {
|
||||||
|
if item.Driver != "bridge" || item.EnableIPv6 || item.ConfigOnly {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := item.Options["com.docker.network.bridge.name"]
|
||||||
|
if name == "" {
|
||||||
|
if item.Options["com.docker.network.bridge.default_bridge"] == "true" {
|
||||||
|
name = "docker0"
|
||||||
|
} else if len(item.ID) >= 12 {
|
||||||
|
name = "br-" + item.ID[:12]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
bridges[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var links []struct {
|
||||||
|
Name string `json:"ifname"`
|
||||||
|
Master string `json:"master"`
|
||||||
|
LinkInfo struct {
|
||||||
|
Kind string `json:"info_kind"`
|
||||||
|
} `json:"linkinfo"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(linkJSON), &links); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
confirmedBridges := make(map[string]bool)
|
||||||
|
for _, link := range links {
|
||||||
|
if link.LinkInfo.Kind == "bridge" && bridges[link.Name] {
|
||||||
|
confirmedBridges[link.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ports := make(map[string]bool)
|
||||||
|
for _, link := range links {
|
||||||
|
if link.LinkInfo.Kind == "veth" && confirmedBridges[link.Master] {
|
||||||
|
ports[link.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ports, nil
|
||||||
|
}
|
||||||
|
|
||||||
func enableFamilyForwardingSysctls(content, family string) string {
|
func enableFamilyForwardingSysctls(content, family string) string {
|
||||||
lines := strings.Split(strings.TrimRight(content, "\n"), "\n")
|
lines := strings.Split(strings.TrimRight(content, "\n"), "\n")
|
||||||
wanted := map[string]string{"net.ipv4.ip_forward": "net.ipv4.ip_forward = 1"}
|
wanted := map[string]string{"net.ipv4.ip_forward": "net.ipv4.ip_forward = 1"}
|
||||||
|
|||||||
@@ -209,18 +209,36 @@ func buildBaseChainBindingsRestoreScript(output string, bind bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func baseChainBindingCommands(output string, bind bool) []string {
|
func baseChainBindingCommands(output string, bind bool) []string {
|
||||||
lines := make([]string, 0, len(BasicChains())*2)
|
chains := BasicChains()
|
||||||
for _, chain := range BasicChains() {
|
lines := make([]string, 0, len(chains)*2)
|
||||||
binding := "-A " + InputChain + " -j " + chain
|
position, insertAt := 0, 1
|
||||||
for _, current := range strings.Split(output, "\n") {
|
for _, current := range strings.Split(output, "\n") {
|
||||||
if strings.TrimSpace(current) == binding {
|
current = strings.TrimSpace(current)
|
||||||
lines = append(lines, "-D "+InputChain+" -j "+chain)
|
if !strings.HasPrefix(current, "-A "+InputChain+" ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if chain := strings.TrimPrefix(current, "-A "+InputChain+" -j "); slices.Contains(chains, chain) {
|
||||||
|
lines = append(lines, "-D "+InputChain+" -j "+chain)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
position++
|
||||||
|
if !bind {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields, err := shellwords.Parse(current)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for index := 2; index+1 < len(fields); index++ {
|
||||||
|
if fields[index] == "-j" && strings.HasPrefix(fields[index+1], "f2b-") {
|
||||||
|
insertAt = position + 1
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if bind {
|
if bind {
|
||||||
for index, chain := range BasicChains() {
|
for index, chain := range chains {
|
||||||
lines = append(lines, fmt.Sprintf("-I %s %d -j %s", InputChain, index+1, chain))
|
lines = append(lines, fmt.Sprintf("-I %s %d -j %s", InputChain, insertAt+index, chain))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines
|
return lines
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/go-acme/lego/v5/providers/dns/namecheap"
|
"github.com/go-acme/lego/v5/providers/dns/namecheap"
|
||||||
"github.com/go-acme/lego/v5/providers/dns/namedotcom"
|
"github.com/go-acme/lego/v5/providers/dns/namedotcom"
|
||||||
"github.com/go-acme/lego/v5/providers/dns/namesilo"
|
"github.com/go-acme/lego/v5/providers/dns/namesilo"
|
||||||
|
"github.com/go-acme/lego/v5/providers/dns/netcup"
|
||||||
"github.com/go-acme/lego/v5/providers/dns/ovh"
|
"github.com/go-acme/lego/v5/providers/dns/ovh"
|
||||||
"github.com/go-acme/lego/v5/providers/dns/porkbun"
|
"github.com/go-acme/lego/v5/providers/dns/porkbun"
|
||||||
"github.com/go-acme/lego/v5/providers/dns/rainyun"
|
"github.com/go-acme/lego/v5/providers/dns/rainyun"
|
||||||
@@ -58,6 +59,7 @@ const (
|
|||||||
NameCheap DnsType = "NameCheap"
|
NameCheap DnsType = "NameCheap"
|
||||||
NameCom DnsType = "NameCom"
|
NameCom DnsType = "NameCom"
|
||||||
NameSilo DnsType = "NameSilo"
|
NameSilo DnsType = "NameSilo"
|
||||||
|
Netcup DnsType = "Netcup"
|
||||||
Ovh DnsType = "Ovh"
|
Ovh DnsType = "Ovh"
|
||||||
PorkBun DnsType = "PorkBun"
|
PorkBun DnsType = "PorkBun"
|
||||||
RainYun DnsType = "RainYun"
|
RainYun DnsType = "RainYun"
|
||||||
@@ -72,26 +74,27 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DNSParam struct {
|
type DNSParam struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
AccessKey string `json:"accessKey"`
|
AccessKey string `json:"accessKey"`
|
||||||
SecretKey string `json:"secretKey"`
|
SecretKey string `json:"secretKey"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
APIkey string `json:"apiKey"`
|
APIkey string `json:"apiKey"`
|
||||||
APIUser string `json:"apiUser"`
|
APIUser string `json:"apiUser"`
|
||||||
APISecret string `json:"apiSecret"`
|
APISecret string `json:"apiSecret"`
|
||||||
APIPrefix string `json:"apiPrefix"`
|
APIPrefix string `json:"apiPrefix"`
|
||||||
SecretID string `json:"secretID"`
|
SecretID string `json:"secretID"`
|
||||||
ClientID string `json:"clientID"`
|
ClientID string `json:"clientID"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
AuthID string `json:"authID"`
|
AuthID string `json:"authID"`
|
||||||
SubAuthID string `json:"subAuthID"`
|
SubAuthID string `json:"subAuthID"`
|
||||||
AuthPassword string `json:"authPassword"`
|
AuthPassword string `json:"authPassword"`
|
||||||
Endpoint string `json:"endpoint"`
|
Endpoint string `json:"endpoint"`
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
BaseURL string `json:"baseURL"`
|
BaseURL string `json:"baseURL"`
|
||||||
|
CustomerNumber string `json:"customerNumber"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -164,6 +167,14 @@ func getDNSProviderConfig(dnsType DnsType, params string, httpClient *http.Clien
|
|||||||
config.PropagationTimeout = propagationTimeout
|
config.PropagationTimeout = propagationTimeout
|
||||||
config.PollingInterval = pollingInterval
|
config.PollingInterval = pollingInterval
|
||||||
p, err = namesilo.NewDNSProviderConfig(config)
|
p, err = namesilo.NewDNSProviderConfig(config)
|
||||||
|
case Netcup:
|
||||||
|
config := newDNSProviderConfig(netcup.NewDefaultConfig(), httpClient)
|
||||||
|
config.Customer = param.CustomerNumber
|
||||||
|
config.Key = param.APIkey
|
||||||
|
config.Password = param.Password
|
||||||
|
config.PropagationTimeout = propagationTimeout
|
||||||
|
config.PollingInterval = pollingInterval
|
||||||
|
p, err = netcup.NewDNSProviderConfig(config)
|
||||||
case Godaddy:
|
case Godaddy:
|
||||||
config := newDNSProviderConfig(godaddy.NewDefaultConfig(), httpClient)
|
config := newDNSProviderConfig(godaddy.NewDefaultConfig(), httpClient)
|
||||||
config.APIKey = param.APIkey
|
config.APIKey = param.APIkey
|
||||||
|
|||||||
@@ -495,7 +495,9 @@ func (u *UpgradeService) handleRollback(originalDir string, errStep int, svcInfo
|
|||||||
_ = settingRepo.Update("SystemStatus", "Free")
|
_ = settingRepo.Update("SystemStatus", "Free")
|
||||||
dbPath := path.Join(global.CONF.Base.InstallDir, "1panel")
|
dbPath := path.Join(global.CONF.Base.InstallDir, "1panel")
|
||||||
if _, err := os.Stat(path.Join(originalDir, "db")); err == nil {
|
if _, err := os.Stat(path.Join(originalDir, "db")); err == nil {
|
||||||
if err := files.CopyItem(true, true, path.Join(originalDir, "db"), dbPath); err != nil {
|
if err := os.Chmod(path.Join(originalDir, "db"), 0700); err != nil {
|
||||||
|
global.LOG.Errorf("set rollback db dir permissions failed, err: %v", err)
|
||||||
|
} else if err := files.CopyItem(true, true, path.Join(originalDir, "db"), dbPath); err != nil {
|
||||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ var restoreCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4"))
|
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4"))
|
||||||
if _, err := os.Stat(path.Join(tmpPath, "db")); err == nil {
|
if _, err := os.Stat(path.Join(tmpPath, "db")); err == nil {
|
||||||
|
if err := os.Chmod(path.Join(tmpPath, "db"), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
dbPath := path.Join(baseDir, "1panel")
|
dbPath := path.Join(baseDir, "1panel")
|
||||||
targetDBDir := path.Join(dbPath, "db")
|
targetDBDir := path.Join(dbPath, "db")
|
||||||
if err := os.RemoveAll(targetDBDir); err != nil {
|
if err := os.RemoveAll(targetDBDir); err != nil {
|
||||||
|
|||||||
@@ -15092,9 +15092,26 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/databases/redis/install/cli": {
|
"/databases/redis/install/cli": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"in": "body",
|
||||||
|
"name": "request",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
@@ -18801,11 +18818,9 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [],
|
"BeforeFunctions": [],
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"uuids"
|
"formatEN": "delete Docker port guard policies",
|
||||||
],
|
"formatZH": "删除 Docker 端口防护策略",
|
||||||
"formatEN": "delete Docker port guard policies [uuids]",
|
|
||||||
"formatZH": "删除 Docker 端口防护策略 [uuids]",
|
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19365,11 +19380,9 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [],
|
"BeforeFunctions": [],
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
"formatEN": "reorder firewall rule",
|
||||||
],
|
"formatZH": "调整防火墙规则顺序",
|
||||||
"formatEN": "reorder firewall rule [instanceKey]",
|
|
||||||
"formatZH": "调整防火墙规则顺序 [instanceKey]",
|
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19510,11 +19523,9 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [],
|
"BeforeFunctions": [],
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
"formatEN": "update firewall rule",
|
||||||
],
|
"formatZH": "更新防火墙规则",
|
||||||
"formatEN": "update firewall rule [instanceKey]",
|
|
||||||
"formatZH": "更新防火墙规则 [instanceKey]",
|
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36372,9 +36383,6 @@ const docTemplate = `{
|
|||||||
"bound": {
|
"bound": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"forwardPolicy": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"initialized": {
|
"initialized": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -40152,6 +40160,14 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"dto.RedisCliInstall": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"taskID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.RedisConf": {
|
"dto.RedisConf": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"containerName": {
|
"containerName": {
|
||||||
|
|||||||
@@ -15088,9 +15088,26 @@
|
|||||||
},
|
},
|
||||||
"/databases/redis/install/cli": {
|
"/databases/redis/install/cli": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"in": "body",
|
||||||
|
"name": "request",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
@@ -18797,11 +18814,9 @@
|
|||||||
],
|
],
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [],
|
"BeforeFunctions": [],
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"uuids"
|
"formatEN": "delete Docker port guard policies",
|
||||||
],
|
"formatZH": "删除 Docker 端口防护策略",
|
||||||
"formatEN": "delete Docker port guard policies [uuids]",
|
|
||||||
"formatZH": "删除 Docker 端口防护策略 [uuids]",
|
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19361,11 +19376,9 @@
|
|||||||
],
|
],
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [],
|
"BeforeFunctions": [],
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
"formatEN": "reorder firewall rule",
|
||||||
],
|
"formatZH": "调整防火墙规则顺序",
|
||||||
"formatEN": "reorder firewall rule [instanceKey]",
|
|
||||||
"formatZH": "调整防火墙规则顺序 [instanceKey]",
|
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19506,11 +19519,9 @@
|
|||||||
],
|
],
|
||||||
"x-panel-log": {
|
"x-panel-log": {
|
||||||
"BeforeFunctions": [],
|
"BeforeFunctions": [],
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
"formatEN": "update firewall rule",
|
||||||
],
|
"formatZH": "更新防火墙规则",
|
||||||
"formatEN": "update firewall rule [instanceKey]",
|
|
||||||
"formatZH": "更新防火墙规则 [instanceKey]",
|
|
||||||
"paramKeys": []
|
"paramKeys": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36368,9 +36379,6 @@
|
|||||||
"bound": {
|
"bound": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"forwardPolicy": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"initialized": {
|
"initialized": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -40148,6 +40156,14 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"dto.RedisCliInstall": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"taskID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.RedisConf": {
|
"dto.RedisConf": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"containerName": {
|
"containerName": {
|
||||||
|
|||||||
+348
-352
@@ -265,8 +265,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "应用端口修改 [key]-[name] => [port]",
|
"formatZH": "应用端口修改 [key]-[name] =\u003e [port]",
|
||||||
"formatEN": "Application port update [key]-[name] => [port]"
|
"formatEN": "Application port update [key]-[name] =\u003e [port]"
|
||||||
},
|
},
|
||||||
"/apps/installed/sort/update": {
|
"/apps/installed/sort/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -679,8 +679,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "容器重命名 [name] => [newName]",
|
"formatZH": "容器重命名 [name] =\u003e [newName]",
|
||||||
"formatEN": "rename container [name] => [newName]"
|
"formatEN": "rename container [name] =\u003e [newName]"
|
||||||
},
|
},
|
||||||
"/containers/repo": {
|
"/containers/repo": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -831,8 +831,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新 API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||||
"formatEN": "update api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||||
},
|
},
|
||||||
"/core/auth/expired/reset": {
|
"/core/auth/expired/reset": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -1716,8 +1716,8 @@
|
|||||||
"output_value": "name"
|
"output_value": "name"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "更新用户 [name] API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
|
||||||
"formatEN": "update user [name] api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
"formatEN": "update user [name] api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
|
||||||
},
|
},
|
||||||
"/core/enterprise/users/del": {
|
"/core/enterprise/users/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -1918,8 +1918,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]",
|
"formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]",
|
||||||
"formatEN": "update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]"
|
"formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]"
|
||||||
},
|
},
|
||||||
"/core/settings/memo": {
|
"/core/settings/memo": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -1948,8 +1948,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统端口 => [serverPort]",
|
"formatZH": "修改系统端口 =\u003e [serverPort]",
|
||||||
"formatEN": "update system port => [serverPort]"
|
"formatEN": "update system port =\u003e [serverPort]"
|
||||||
},
|
},
|
||||||
"/core/settings/proxy/update": {
|
"/core/settings/proxy/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -1974,8 +1974,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统 ssl => [ssl]",
|
"formatZH": "修改系统 ssl =\u003e [ssl]",
|
||||||
"formatEN": "update system ssl => [ssl]"
|
"formatEN": "update system ssl =\u003e [ssl]"
|
||||||
},
|
},
|
||||||
"/core/settings/terminal/update": {
|
"/core/settings/terminal/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -1991,8 +1991,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统配置 [key] => [value]",
|
"formatZH": "修改系统配置 [key] =\u003e [value]",
|
||||||
"formatEN": "update system setting [key] => [value]"
|
"formatEN": "update system setting [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/core/settings/upgrade": {
|
"/core/settings/upgrade": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -2000,8 +2000,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新系统 => [version]",
|
"formatZH": "更新系统 =\u003e [version]",
|
||||||
"formatEN": "upgrade system => [version]"
|
"formatEN": "upgrade system =\u003e [version]"
|
||||||
},
|
},
|
||||||
"/core/xapp/appQRCode": {
|
"/core/xapp/appQRCode": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -2459,282 +2459,6 @@
|
|||||||
"formatZH": "同步 SSL 证书 [primaryDomain]",
|
"formatZH": "同步 SSL 证书 [primaryDomain]",
|
||||||
"formatEN": "sync SSL certificate [primaryDomain]"
|
"formatEN": "sync SSL certificate [primaryDomain]"
|
||||||
},
|
},
|
||||||
"/xpack/vms": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机 [name]",
|
|
||||||
"formatEN": "create VM [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "virtual_machines",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机 [name]",
|
|
||||||
"formatEN": "delete VM [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/disks/resize": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"target",
|
|
||||||
"sizeGiB"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "扩容虚拟机 [name] 磁盘 [target] 至 [sizeGiB] GiB",
|
|
||||||
"formatEN": "expand VM [name] disk [target] to [sizeGiB] GiB"
|
|
||||||
},
|
|
||||||
"/xpack/vms/environment/enable": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "启用虚拟机运行环境",
|
|
||||||
"formatEN": "enable VM runtime environment"
|
|
||||||
},
|
|
||||||
"/xpack/vms/iso": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机镜像 [name] [type]",
|
|
||||||
"formatEN": "create VM ISO [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/iso/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_isos",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机镜像 [name]",
|
|
||||||
"formatEN": "delete VM ISO [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/iso/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "更新虚拟机镜像 [name] [type]",
|
|
||||||
"formatEN": "update VM ISO [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/networks": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机网络 [name] [type]",
|
|
||||||
"formatEN": "create VM network [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/networks/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_networks",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机网络 [name]",
|
|
||||||
"formatEN": "delete VM network [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/networks/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_networks",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "更新虚拟机网络 [name]",
|
|
||||||
"formatEN": "update VM network [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/operate": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"operate"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "操作虚拟机 [name] [operate]",
|
|
||||||
"formatEN": "operate VM [name] [operate]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/orphans/clean": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "清理虚拟机孤立数据",
|
|
||||||
"formatEN": "clean VM orphaned data"
|
|
||||||
},
|
|
||||||
"/xpack/vms/rename": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"newName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "重命名虚拟机 [name] 为 [newName]",
|
|
||||||
"formatEN": "rename VM [name] to [newName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/snapshots": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"snapshotName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
|
|
||||||
"formatEN": "create VM [name] snapshot [snapshotName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/snapshots/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"snapshotName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
|
|
||||||
"formatEN": "delete VM [name] snapshot [snapshotName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/snapshots/recover": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"snapshotName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
|
|
||||||
"formatEN": "recover VM [name] snapshot [snapshotName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/storages": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机存储 [name] [type]",
|
|
||||||
"formatEN": "create VM storage [name] [type]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/storages/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_storages",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机存储 [name]",
|
|
||||||
"formatEN": "delete VM storage [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/storages/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_storages",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "更新虚拟机存储 [name]",
|
|
||||||
"formatEN": "update VM storage [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/sync": {
|
|
||||||
"bodyKeys": [],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "同步虚拟机",
|
|
||||||
"formatEN": "sync virtual machines"
|
|
||||||
},
|
|
||||||
"/xpack/vms/templates": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name",
|
|
||||||
"templateName"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
|
|
||||||
"formatEN": "create VM [name] template [templateName]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/templates/del": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [
|
|
||||||
{
|
|
||||||
"input_column": "id",
|
|
||||||
"input_value": "id",
|
|
||||||
"isList": false,
|
|
||||||
"db": "vm_templates",
|
|
||||||
"output_column": "name",
|
|
||||||
"output_value": "name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formatZH": "删除虚拟机模板 [name]",
|
|
||||||
"formatEN": "delete VM template [name]"
|
|
||||||
},
|
|
||||||
"/xpack/vms/update": {
|
|
||||||
"bodyKeys": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
|
||||||
"beforeFunctions": [],
|
|
||||||
"formatZH": "更新虚拟机 [name]",
|
|
||||||
"formatEN": "update VM [name]"
|
|
||||||
},
|
|
||||||
"/cronjobs": {
|
"/cronjobs": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"type",
|
"type",
|
||||||
@@ -2888,8 +2612,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "首页应用 [key] => 显示:[value]",
|
"formatZH": "首页应用 [key] =\u003e 显示:[value]",
|
||||||
"formatEN": "app launcher [key] => show: [value]"
|
"formatEN": "app launcher [key] =\u003e show: [value]"
|
||||||
},
|
},
|
||||||
"/dashboard/quick/change": {
|
"/dashboard/quick/change": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -3017,7 +2741,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]",
|
"formatZH": "mysql 数据库 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the mysql database [name] is modified => [description]"
|
"formatEN": "The description of the mysql database [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/databases/grants": {
|
"/databases/grants": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3098,7 +2822,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "mongodb 数据库 [name] 描述信息修改 [description]",
|
"formatZH": "mongodb 数据库 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the mongodb database [name] is modified => [description]"
|
"formatEN": "The description of the mongodb database [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/databases/mongodb/password": {
|
"/databases/mongodb/password": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3183,7 +2907,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]",
|
"formatZH": "postgresql 数据库 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the postgresql database [name] is modified => [description]"
|
"formatEN": "The description of the postgresql database [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/databases/pg/password": {
|
"/databases/pg/password": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3288,8 +3012,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] => [newHost] 描述 [description]",
|
"formatZH": "更新 mysql 数据库 [database] 用户 [username] 访问权限 [host] =\u003e [newHost] 描述 [description]",
|
||||||
"formatEN": "update mysql database [database] user [username] access [host] => [newHost] description [description]"
|
"formatEN": "update mysql database [database] user [username] access [host] =\u003e [newHost] description [description]"
|
||||||
},
|
},
|
||||||
"/databases/variables/update": {
|
"/databases/variables/update": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -3325,8 +3049,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "批量修改文件权限和用户/组 [paths] => [mode]/[user]/[group]",
|
"formatZH": "批量修改文件权限和用户/组 [paths] =\u003e [mode]/[user]/[group]",
|
||||||
"formatEN": "Batch change file mode and owner [paths] => [mode]/[user]/[group]"
|
"formatEN": "Batch change file mode and owner [paths] =\u003e [mode]/[user]/[group]"
|
||||||
},
|
},
|
||||||
"/files/chunkdownload": {
|
"/files/chunkdownload": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3416,8 +3140,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改权限 [path] => [mode]",
|
"formatZH": "修改权限 [path] =\u003e [mode]",
|
||||||
"formatEN": "Change mode [path] => [mode]"
|
"formatEN": "Change mode [path] =\u003e [mode]"
|
||||||
},
|
},
|
||||||
"/files/move": {
|
"/files/move": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3426,8 +3150,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "移动文件 [oldPaths] => [newPath]",
|
"formatZH": "移动文件 [oldPaths] =\u003e [newPath]",
|
||||||
"formatEN": "Move [oldPaths] => [newPath]"
|
"formatEN": "Move [oldPaths] =\u003e [newPath]"
|
||||||
},
|
},
|
||||||
"/files/owner": {
|
"/files/owner": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3437,8 +3161,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改用户/组 [path] => [user]/[group]",
|
"formatZH": "修改用户/组 [path] =\u003e [user]/[group]",
|
||||||
"formatEN": "Change owner [path] => [user]/[group]"
|
"formatEN": "Change owner [path] =\u003e [user]/[group]"
|
||||||
},
|
},
|
||||||
"/files/preview": {
|
"/files/preview": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3472,8 +3196,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "重命名 [oldName] => [newName]",
|
"formatZH": "重命名 [oldName] =\u003e [newName]",
|
||||||
"formatEN": "Rename [oldName] => [newName]"
|
"formatEN": "Rename [oldName] =\u003e [newName]"
|
||||||
},
|
},
|
||||||
"/files/save": {
|
"/files/save": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3530,8 +3254,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "下载 url => [path]/[name]",
|
"formatZH": "下载 url =\u003e [path]/[name]",
|
||||||
"formatEN": "Download url => [path]/[name]"
|
"formatEN": "Download url =\u003e [path]/[name]"
|
||||||
},
|
},
|
||||||
"/files/wget/process/remove": {
|
"/files/wget/process/remove": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3673,13 +3397,11 @@
|
|||||||
"formatEN": "batch update Docker port guard policies"
|
"formatEN": "batch update Docker port guard policies"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/docker/policies/delete/batch": {
|
"/hosts/firewall/docker/policies/delete/batch": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"uuids"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "删除 Docker 端口防护策略 [uuids]",
|
"formatZH": "删除 Docker 端口防护策略",
|
||||||
"formatEN": "delete Docker port guard policies [uuids]"
|
"formatEN": "delete Docker port guard policies"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/family/operate": {
|
"/hosts/firewall/family/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3687,10 +3409,10 @@
|
|||||||
"family",
|
"family",
|
||||||
"operation"
|
"operation"
|
||||||
],
|
],
|
||||||
"formatEN": "[operation] [subsystem] [family] firewall chains",
|
|
||||||
"formatZH": "[operation] [subsystem] [family] 防火墙链",
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": []
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "[operation] [subsystem] [family] 防火墙链",
|
||||||
|
"formatEN": "[operation] [subsystem] [family] firewall chains"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/filter/operate": {
|
"/hosts/firewall/filter/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3739,13 +3461,11 @@
|
|||||||
"formatEN": "delete firewall rules"
|
"formatEN": "delete firewall rules"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/rules/reorder": {
|
"/hosts/firewall/rules/reorder": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "调整防火墙规则顺序 [instanceKey]",
|
"formatZH": "调整防火墙规则顺序",
|
||||||
"formatEN": "reorder firewall rule [instanceKey]"
|
"formatEN": "reorder firewall rule"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/rules/reset": {
|
"/hosts/firewall/rules/reset": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
@@ -3755,22 +3475,20 @@
|
|||||||
"formatEN": "reset firewall rules"
|
"formatEN": "reset firewall rules"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/rules/update": {
|
"/hosts/firewall/rules/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [],
|
||||||
"instanceKey"
|
|
||||||
],
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新防火墙规则 [instanceKey]",
|
"formatZH": "更新防火墙规则",
|
||||||
"formatEN": "update firewall rule [instanceKey]"
|
"formatEN": "update firewall rule"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/settings/ipv6": {
|
"/hosts/firewall/settings/ipv6": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"status"
|
"status"
|
||||||
],
|
],
|
||||||
"formatEN": "Set firewall IPv6 support to [status]",
|
|
||||||
"formatZH": "设置防火墙 IPv6 支持为 [status]",
|
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": []
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "设置防火墙 IPv6 支持为 [status]",
|
||||||
|
"formatEN": "Set firewall IPv6 support to [status]"
|
||||||
},
|
},
|
||||||
"/hosts/firewall/settings/operate": {
|
"/hosts/firewall/settings/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3812,11 +3530,13 @@
|
|||||||
"formatEN": "update firewall port whitelist"
|
"formatEN": "update firewall port whitelist"
|
||||||
},
|
},
|
||||||
"/hosts/monitor/clean": {
|
"/hosts/monitor/clean": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "清空监控数据",
|
"formatZH": "清空 [type] 监控数据",
|
||||||
"formatEN": "clean monitor datas"
|
"formatEN": "clean [type] monitoring data"
|
||||||
},
|
},
|
||||||
"/hosts/monitor/setting/update": {
|
"/hosts/monitor/setting/update": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -3888,8 +3608,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改 SSH 配置 [key] => [newValue]",
|
"formatZH": "修改 SSH 配置 [key] =\u003e [newValue]",
|
||||||
"formatEN": "update SSH setting [key] => [newValue]"
|
"formatEN": "update SSH setting [key] =\u003e [newValue]"
|
||||||
},
|
},
|
||||||
"/hosts/tool/config/set": {
|
"/hosts/tool/config/set": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4150,7 +3870,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "快照 [name] 描述信息修改 [description]",
|
"formatZH": "快照 [name] 描述信息修改 [description]",
|
||||||
"formatEN": "The description of the snapshot [name] is modified => [description]"
|
"formatEN": "The description of the snapshot [name] is modified =\u003e [description]"
|
||||||
},
|
},
|
||||||
"/settings/snapshot/import": {
|
"/settings/snapshot/import": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4242,8 +3962,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改系统配置 [key] => [value]",
|
"formatZH": "修改系统配置 [key] =\u003e [value]",
|
||||||
"formatEN": "update system setting [key] => [value]"
|
"formatEN": "update system setting [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/clam": {
|
"/toolbox/clam": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4361,8 +4081,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改主机参数 [key] => [value]",
|
"formatZH": "修改主机参数 [key] =\u003e [value]",
|
||||||
"formatEN": "update device conf [key] => [value]"
|
"formatEN": "update device conf [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/device/update/host": {
|
"/toolbox/device/update/host": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4371,8 +4091,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改主机 Host [key] => [value]",
|
"formatZH": "修改主机 Host [key] =\u003e [value]",
|
||||||
"formatEN": "update device host [key] => [value]"
|
"formatEN": "update device host [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/device/update/swap": {
|
"/toolbox/device/update/swap": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4400,8 +4120,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "修改 Fail2ban 配置 [key] => [value]",
|
"formatZH": "修改 Fail2ban 配置 [key] =\u003e [value]",
|
||||||
"formatEN": "update fail2ban conf [key] => [value]"
|
"formatEN": "update fail2ban conf [key] =\u003e [value]"
|
||||||
},
|
},
|
||||||
"/toolbox/ftp": {
|
"/toolbox/ftp": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -4660,8 +4380,8 @@
|
|||||||
"output_value": "domain"
|
"output_value": "domain"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatZH": "修改默认 server => [domain]",
|
"formatZH": "修改默认 server =\u003e [domain]",
|
||||||
"formatEN": "Change default server => [domain]"
|
"formatEN": "Change default server =\u003e [domain]"
|
||||||
},
|
},
|
||||||
"/websites/del": {
|
"/websites/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -5424,8 +5144,8 @@
|
|||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [],
|
||||||
"formatZH": "更新防篡改信息 [website][path] => [status]",
|
"formatZH": "更新防篡改信息 [website][path] =\u003e [status]",
|
||||||
"formatEN": "update tamper info [website][path] => [status]"
|
"formatEN": "update tamper info [website][path] =\u003e [status]"
|
||||||
},
|
},
|
||||||
"/xpack/vllm/command-template/create": {
|
"/xpack/vllm/command-template/create": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
@@ -5504,6 +5224,282 @@
|
|||||||
"formatZH": "更新 vLLM [name]",
|
"formatZH": "更新 vLLM [name]",
|
||||||
"formatEN": "update vLLM [name]"
|
"formatEN": "update vLLM [name]"
|
||||||
},
|
},
|
||||||
|
"/xpack/vms": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机 [name]",
|
||||||
|
"formatEN": "create VM [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "virtual_machines",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机 [name]",
|
||||||
|
"formatEN": "delete VM [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/disks/resize": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"target",
|
||||||
|
"sizeGiB"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "扩容虚拟机 [name] 磁盘 [target] 至 [sizeGiB] GiB",
|
||||||
|
"formatEN": "expand VM [name] disk [target] to [sizeGiB] GiB"
|
||||||
|
},
|
||||||
|
"/xpack/vms/environment/enable": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "启用虚拟机运行环境",
|
||||||
|
"formatEN": "enable VM runtime environment"
|
||||||
|
},
|
||||||
|
"/xpack/vms/iso": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机镜像 [name] [type]",
|
||||||
|
"formatEN": "create VM ISO [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/iso/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_isos",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机镜像 [name]",
|
||||||
|
"formatEN": "delete VM ISO [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/iso/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新虚拟机镜像 [name] [type]",
|
||||||
|
"formatEN": "update VM ISO [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/networks": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机网络 [name] [type]",
|
||||||
|
"formatEN": "create VM network [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/networks/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_networks",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机网络 [name]",
|
||||||
|
"formatEN": "delete VM network [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/networks/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_networks",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "更新虚拟机网络 [name]",
|
||||||
|
"formatEN": "update VM network [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/operate": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"operate"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "操作虚拟机 [name] [operate]",
|
||||||
|
"formatEN": "operate VM [name] [operate]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/orphans/clean": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清理虚拟机孤立数据",
|
||||||
|
"formatEN": "clean VM orphaned data"
|
||||||
|
},
|
||||||
|
"/xpack/vms/rename": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"newName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "重命名虚拟机 [name] 为 [newName]",
|
||||||
|
"formatEN": "rename VM [name] to [newName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/snapshots": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"snapshotName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机 [name] 快照 [snapshotName]",
|
||||||
|
"formatEN": "create VM [name] snapshot [snapshotName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/snapshots/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"snapshotName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "删除虚拟机 [name] 快照 [snapshotName]",
|
||||||
|
"formatEN": "delete VM [name] snapshot [snapshotName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/snapshots/recover": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"snapshotName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "恢复虚拟机 [name] 快照 [snapshotName]",
|
||||||
|
"formatEN": "recover VM [name] snapshot [snapshotName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/storages": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机存储 [name] [type]",
|
||||||
|
"formatEN": "create VM storage [name] [type]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/storages/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_storages",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机存储 [name]",
|
||||||
|
"formatEN": "delete VM storage [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/storages/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_storages",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "更新虚拟机存储 [name]",
|
||||||
|
"formatEN": "update VM storage [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/sync": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "同步虚拟机",
|
||||||
|
"formatEN": "sync virtual machines"
|
||||||
|
},
|
||||||
|
"/xpack/vms/templates": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"templateName"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "创建虚拟机 [name] 模板 [templateName]",
|
||||||
|
"formatEN": "create VM [name] template [templateName]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/templates/del": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [
|
||||||
|
{
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"db": "vm_templates",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "删除虚拟机模板 [name]",
|
||||||
|
"formatEN": "delete VM template [name]"
|
||||||
|
},
|
||||||
|
"/xpack/vms/update": {
|
||||||
|
"bodyKeys": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "更新虚拟机 [name]",
|
||||||
|
"formatEN": "update VM [name]"
|
||||||
|
},
|
||||||
"/xpack/waf/block/export": {
|
"/xpack/waf/block/export": {
|
||||||
"bodyKeys": [],
|
"bodyKeys": [],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
|
|||||||
+14
-14
@@ -30,7 +30,7 @@ require (
|
|||||||
github.com/pkg/sftp v1.13.11
|
github.com/pkg/sftp v1.13.11
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/goxmldsig v1.6.1
|
github.com/russellhaering/goxmldsig v1.6.1
|
||||||
github.com/shirou/gopsutil/v4 v4.26.7
|
github.com/shirou/gopsutil/v4 v4.26.9
|
||||||
github.com/sirupsen/logrus v1.10.2
|
github.com/sirupsen/logrus v1.10.2
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/soheilhy/cmux v0.1.5
|
github.com/soheilhy/cmux v0.1.5
|
||||||
@@ -39,13 +39,13 @@ require (
|
|||||||
github.com/swaggo/files/v2 v2.0.2
|
github.com/swaggo/files/v2 v2.0.2
|
||||||
github.com/swaggo/swag v1.16.6
|
github.com/swaggo/swag v1.16.6
|
||||||
github.com/xlzd/gotp v0.1.0
|
github.com/xlzd/gotp v0.1.0
|
||||||
golang.org/x/crypto v0.56.0
|
golang.org/x/crypto v0.58.0
|
||||||
golang.org/x/image v0.45.0
|
golang.org/x/image v0.45.0
|
||||||
golang.org/x/net v0.58.0
|
golang.org/x/net v0.60.0
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
golang.org/x/sys v0.47.0
|
golang.org/x/sys v0.49.0
|
||||||
golang.org/x/term v0.45.0
|
golang.org/x/term v0.47.0
|
||||||
golang.org/x/text v0.41.0
|
golang.org/x/text v0.43.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/gorm v1.31.2
|
gorm.io/gorm v1.31.2
|
||||||
@@ -61,7 +61,7 @@ require (
|
|||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/purego v0.10.2 // indirect
|
github.com/ebitengine/purego v0.11.1 // indirect
|
||||||
github.com/f-amaral/go-async v0.3.0 // indirect
|
github.com/f-amaral/go-async v0.3.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
@@ -110,7 +110,7 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||||
github.com/philhofer/fwd v1.2.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/phpdave11/gofpdf v1.4.3 // indirect
|
github.com/phpdave11/gofpdf v1.4.3 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20260805114148-88456608a4f6 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.59.1 // indirect
|
github.com/quic-go/quic-go v0.59.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
@@ -120,18 +120,18 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tinylib/msgp v1.6.4 // indirect
|
github.com/tinylib/msgp v1.6.4 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
github.com/tklauser/go-sysconf v0.4.0 // indirect
|
||||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
github.com/tklauser/numcpus v0.12.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.1 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.9.2 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
golang.org/x/arch v0.26.0 // indirect
|
golang.org/x/arch v0.26.0 // indirect
|
||||||
golang.org/x/mod v0.40.0 // indirect
|
golang.org/x/mod v0.41.0 // indirect
|
||||||
golang.org/x/sync v0.22.0 // indirect
|
golang.org/x/sync v0.24.0 // indirect
|
||||||
golang.org/x/tools v0.49.0 // indirect
|
golang.org/x/tools v0.51.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
modernc.org/libc v1.72.0 // indirect
|
modernc.org/libc v1.72.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
|||||||
+40
@@ -35,6 +35,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
|
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.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/f-amaral/go-async v0.3.0 h1:h4kLsX7aKfdWaHvV0lf+/EE3OIeCzyeDYJDb/vDZUyg=
|
github.com/f-amaral/go-async v0.3.0 h1:h4kLsX7aKfdWaHvV0lf+/EE3OIeCzyeDYJDb/vDZUyg=
|
||||||
github.com/f-amaral/go-async v0.3.0/go.mod h1:Hz5Qr6DAWpbTTUjytnrg1WIsDgS7NtOei5y8SipYS7U=
|
github.com/f-amaral/go-async v0.3.0/go.mod h1:Hz5Qr6DAWpbTTUjytnrg1WIsDgS7NtOei5y8SipYS7U=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
@@ -219,6 +221,8 @@ 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
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-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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
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/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
||||||
@@ -237,6 +241,10 @@ github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88ee
|
|||||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||||
github.com/shirou/gopsutil/v4 v4.26.7 h1:IXzpHz/dkMRYAhKkOXr1HB6SuzWU3eoyyeWe7g3bNZc=
|
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.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/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
||||||
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
@@ -279,8 +287,12 @@ github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
|||||||
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||||
|
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
|
||||||
|
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
|
||||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||||
|
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
|
||||||
|
github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
@@ -293,6 +305,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
|||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.1 h1:j2U/Qp+wvueSpqitLCSZPT/+ZpVc1xzuwdHWwl7d8ro=
|
go.mongodb.org/mongo-driver/v2 v2.5.1 h1:j2U/Qp+wvueSpqitLCSZPT/+ZpVc1xzuwdHWwl7d8ro=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.1/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.mongodb.org/mongo-driver/v2 v2.5.1/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
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.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
@@ -304,19 +318,31 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
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.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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
|
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/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
|
||||||
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||||
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||||
|
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
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.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.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -324,17 +350,31 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
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.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/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
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.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||||
|
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
|
||||||
|
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
|
||||||
|
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.3.0/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
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.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.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||||
|
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
|||||||
+15
-4
@@ -230,6 +230,10 @@ func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCo
|
|||||||
cmdItem.SysProcAttr = &syscall.SysProcAttr{
|
cmdItem.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Setpgid: true,
|
Setpgid: true,
|
||||||
}
|
}
|
||||||
|
cmdItem.Cancel = func() error {
|
||||||
|
_ = syscall.Kill(-cmdItem.Process.Pid, syscall.SIGKILL)
|
||||||
|
return cmdItem.Process.Kill()
|
||||||
|
}
|
||||||
cmds = append(cmds, cmdItem)
|
cmds = append(cmds, cmdItem)
|
||||||
}
|
}
|
||||||
return cmds
|
return cmds
|
||||||
@@ -305,6 +309,12 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
|
|||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Setpgid: true,
|
Setpgid: true,
|
||||||
}
|
}
|
||||||
|
if newContext != nil {
|
||||||
|
cmd.Cancel = func() error {
|
||||||
|
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
return cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
customWriter := &CustomWriter{taskItem: c.taskItem}
|
customWriter := &CustomWriter{taskItem: c.taskItem}
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
@@ -357,11 +367,12 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
|
|||||||
}()
|
}()
|
||||||
select {
|
select {
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
if c.preserveErrorCause && newContext != nil && newContext.Err() != nil {
|
if newContext != nil && newContext.Err() != nil {
|
||||||
if cmd.Process != nil && cmd.Process.Pid > 0 {
|
resultErr := c.pipeResultErr(newContext, err)
|
||||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
if c.preserveErrorCause {
|
||||||
|
resultErr = &commandError{message: resultErr.Error(), cause: newContext.Err()}
|
||||||
}
|
}
|
||||||
return "", newContext.Err()
|
return "", resultErr
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out, resultErr := handleErr(&stdout, &stderr, c.IgnoreExist1, err)
|
out, resultErr := handleErr(&stdout, &stderr, c.IgnoreExist1, err)
|
||||||
|
|||||||
+25
-12
@@ -13,20 +13,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func LoadDBConnByPath(fullPath, dbName string) *gorm.DB {
|
func LoadDBConnByPath(fullPath, dbName string) *gorm.DB {
|
||||||
if _, err := CreateDirWhenNotExist(true, path.Dir(fullPath)); err != nil {
|
|
||||||
panic(fmt.Errorf("init db dir failed, err: %v", err))
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(fullPath); err != nil {
|
|
||||||
f, err := os.Create(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("init %s db file failed, err: %v", dbName, err))
|
|
||||||
}
|
|
||||||
_ = f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := GetDBWithPath(fullPath)
|
db, err := GetDBWithPath(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(fmt.Errorf("init %s db failed, err: %w", dbName, err))
|
||||||
}
|
}
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
@@ -40,6 +29,30 @@ func CloseDB(db *gorm.DB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDBWithPath(dbPath string) (*gorm.DB, error) {
|
func GetDBWithPath(dbPath string) (*gorm.DB, error) {
|
||||||
|
dbDir := path.Dir(dbPath)
|
||||||
|
if err := os.MkdirAll(dbDir, 0700); err != nil {
|
||||||
|
return nil, fmt.Errorf("init db dir failed, err: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(dbDir, 0700); err != nil {
|
||||||
|
return nil, fmt.Errorf("set db dir permissions failed, err: %w", err)
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(dbPath, os.O_CREATE|os.O_RDWR, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("init db file failed, err: %w", err)
|
||||||
|
}
|
||||||
|
if err := file.Chmod(0600); err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return nil, fmt.Errorf("set db file permissions failed, err: %w", err)
|
||||||
|
}
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, suffix := range []string{"-wal", "-shm", "-journal"} {
|
||||||
|
if err := os.Chmod(dbPath+suffix, 0600); err != nil && !os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("set db sidecar permissions failed, err: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
Logger: newLogger(),
|
Logger: newLogger(),
|
||||||
|
|||||||
Generated
+331
-1372
File diff suppressed because it is too large
Load Diff
+18
-14
@@ -29,16 +29,16 @@
|
|||||||
"@novnc/novnc": "^1.7.0",
|
"@novnc/novnc": "^1.7.0",
|
||||||
"@vue-office/docx": "^1.6.2",
|
"@vue-office/docx": "^1.6.2",
|
||||||
"@vue-office/excel": "^1.7.8",
|
"@vue-office/excel": "^1.7.8",
|
||||||
"@vueuse/core": "^14.4.0",
|
"@vueuse/core": "^15.0.0",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
"anser": "^2.3.5",
|
"anser": "^2.3.5",
|
||||||
"axios": "^1.18.0",
|
"axios": "^1.20.0",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dompurify": "^3.4.13",
|
"dompurify": "^3.4.16",
|
||||||
"echarts": "^6.1.0",
|
"echarts": "^6.1.0",
|
||||||
"element-plus": "2.14.4",
|
"element-plus": "2.14.7",
|
||||||
"js-base64": "^3.9.1",
|
"js-base64": "^3.9.1",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"lodash-es": "^4.18.1",
|
"lodash-es": "^4.18.1",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"sortablejs": "^1.15.7",
|
"sortablejs": "^1.15.7",
|
||||||
"uuid": "^14.0.1",
|
"uuid": "^14.0.1",
|
||||||
"vue": "^3.5.42",
|
"vue": "^3.5.43",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
"vue-demi": "^0.14.6",
|
"vue-demi": "^0.14.6",
|
||||||
@@ -63,22 +63,22 @@
|
|||||||
"@tailwindcss/postcss": "^4.3.3",
|
"@tailwindcss/postcss": "^4.3.3",
|
||||||
"@types/node": "^26.6.3",
|
"@types/node": "^26.6.3",
|
||||||
"@types/uuid": "^11.0.0",
|
"@types/uuid": "^11.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
"@typescript-eslint/eslint-plugin": "^8.71.2",
|
||||||
"@typescript-eslint/parser": "^8.68.0",
|
"@typescript-eslint/parser": "^8.71.2",
|
||||||
"@vitejs/plugin-vue": "^6.0.7",
|
"@vitejs/plugin-vue": "^6.0.10",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.6",
|
"@vitejs/plugin-vue-jsx": "^5.1.6",
|
||||||
"@vue/compiler-sfc": "^3.5.42",
|
"@vue/compiler-sfc": "^3.5.43",
|
||||||
"autoprefixer": "^10.6.1",
|
"autoprefixer": "^10.6.1",
|
||||||
"esbuild": "^0.28.2",
|
"esbuild": "^0.28.2",
|
||||||
"eslint": "^10.8.0",
|
"eslint": "^10.8.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.6",
|
"eslint-plugin-prettier": "^5.5.6",
|
||||||
"eslint-plugin-vue": "^10.10.0",
|
"eslint-plugin-vue": "^10.10.0",
|
||||||
"lint-staged": "^17.0.7",
|
"lint-staged": "^17.6.0",
|
||||||
"postcss": "^8.5.14",
|
"postcss": "^8.5.14",
|
||||||
"postcss-html": "^2.0.0",
|
"postcss-html": "^2.0.0",
|
||||||
"prettier": "^3.9.6",
|
"prettier": "^3.9.6",
|
||||||
"rollup-plugin-visualizer": "^7.0.1",
|
"rollup-plugin-visualizer": "^7.1.1",
|
||||||
"sass": "^1.105.0",
|
"sass": "^1.105.0",
|
||||||
"standard-version": "^9.5.0",
|
"standard-version": "^9.5.0",
|
||||||
"tailwindcss": "^4.3.3",
|
"tailwindcss": "^4.3.3",
|
||||||
@@ -100,7 +100,11 @@
|
|||||||
],
|
],
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"monaco-editor": {
|
"monaco-editor": {
|
||||||
"dompurify": "3.4.13"
|
"dompurify": "3.4.16"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"allowScripts": {
|
||||||
|
"@vue-office/docx@1.6.3": true,
|
||||||
|
"@vue-office/excel@1.7.14": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class RequestHttp {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.service.interceptors.response.use(
|
this.service.interceptors.response.use(
|
||||||
(response: AxiosResponse) => {
|
async (response: AxiosResponse) => {
|
||||||
const globalStore = GlobalStore();
|
const globalStore = GlobalStore();
|
||||||
const { data } = response;
|
const { data } = response;
|
||||||
const authResult = handleAuthResponseCode(data, { showRBACMessage: true });
|
const authResult = handleAuthResponseCode(data, { showRBACMessage: true });
|
||||||
@@ -100,9 +100,9 @@ class RequestHttp {
|
|||||||
return Promise.reject(data);
|
return Promise.reject(data);
|
||||||
}
|
}
|
||||||
if (data.code == ResultEnum.NODE_UNBIND) {
|
if (data.code == ResultEnum.NODE_UNBIND) {
|
||||||
changeToLocal();
|
await changeToLocal();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
return;
|
return Promise.reject(data);
|
||||||
}
|
}
|
||||||
if (data.code == ResultEnum.ERR_GLOBAL_LOADING) {
|
if (data.code == ResultEnum.ERR_GLOBAL_LOADING) {
|
||||||
globalStore.isLoading = true;
|
globalStore.isLoading = true;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export namespace Firewall {
|
|||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
bound: boolean;
|
bound: boolean;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
forwardPolicy?: 'ACCEPT' | 'DROP';
|
|
||||||
raInterfaces?: string[];
|
raInterfaces?: string[];
|
||||||
}
|
}
|
||||||
export interface BackendGroup {
|
export interface BackendGroup {
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ export namespace Website {
|
|||||||
email: string;
|
email: string;
|
||||||
url: string;
|
url: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
keyType: string;
|
||||||
useProxy: boolean;
|
useProxy: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -144,8 +144,13 @@ export const imagePull = (params: Container.ImagePull) => {
|
|||||||
export const imagePush = (params: Container.ImagePush) => {
|
export const imagePush = (params: Container.ImagePush) => {
|
||||||
return http.post<string>(`/containers/image/push`, params);
|
return http.post<string>(`/containers/image/push`, params);
|
||||||
};
|
};
|
||||||
export const imageLoad = (params: Container.ImageLoad) => {
|
export const imageLoad = (params: Container.ImageLoad, currentNode?: string) => {
|
||||||
return http.post(`/containers/image/load`, params, TimeoutEnum.T_10M);
|
return http.post(
|
||||||
|
`/containers/image/load`,
|
||||||
|
params,
|
||||||
|
TimeoutEnum.T_10M,
|
||||||
|
currentNode ? { CurrentNode: currentNode } : undefined,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const imageSave = (params: Container.ImageSave) => {
|
export const imageSave = (params: Container.ImageSave) => {
|
||||||
return http.post(`/containers/image/save`, params, TimeoutEnum.T_10M);
|
return http.post(`/containers/image/save`, params, TimeoutEnum.T_10M);
|
||||||
|
|||||||
@@ -193,8 +193,10 @@ export const redisPersistenceConf = (type: string, database: string) => {
|
|||||||
export const checkRedisCli = () => {
|
export const checkRedisCli = () => {
|
||||||
return http.get<boolean>(`/databases/redis/check`);
|
return http.get<boolean>(`/databases/redis/check`);
|
||||||
};
|
};
|
||||||
export const installRedisCli = () => {
|
export const installRedisCli = (taskID: string, node: string) => {
|
||||||
return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M);
|
return http.post<string>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
|
||||||
|
taskID,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
export const changeRedisPassword = (database: string, password: string) => {
|
export const changeRedisPassword = (database: string, password: string) => {
|
||||||
if (password) {
|
if (password) {
|
||||||
|
|||||||
@@ -126,14 +126,14 @@ const styleVars = computed(() => ({
|
|||||||
|
|
||||||
const terminalElement = ref<HTMLDivElement | null>(null);
|
const terminalElement = ref<HTMLDivElement | null>(null);
|
||||||
let eventSource: EventSource | null = null;
|
let eventSource: EventSource | null = null;
|
||||||
|
let searchVersion = 0;
|
||||||
let term: Terminal | null = null;
|
let term: Terminal | null = null;
|
||||||
const fitAddon = new FitAddon();
|
const fitAddon = new FitAddon();
|
||||||
let onScrollDisposable: { dispose: () => void } | null = null;
|
|
||||||
const MAX_VIEW_LINES = 20000;
|
const MAX_VIEW_LINES = 20000;
|
||||||
const followBottom = ref(true);
|
let followBottom = true;
|
||||||
|
|
||||||
const logSearch = reactive({
|
const logSearch = reactive({
|
||||||
isWatch: props.defaultFollow ? true : true,
|
isWatch: true,
|
||||||
isShowTimestamp: props.defaultIsShowTimestamp,
|
isShowTimestamp: props.defaultIsShowTimestamp,
|
||||||
container: '',
|
container: '',
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
@@ -168,6 +168,7 @@ const timeOptions = ref([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const stopListening = () => {
|
const stopListening = () => {
|
||||||
|
searchVersion++;
|
||||||
if (eventSource) {
|
if (eventSource) {
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
eventSource = null;
|
eventSource = null;
|
||||||
@@ -175,26 +176,13 @@ const stopListening = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clearTerminal = () => {
|
const clearTerminal = () => {
|
||||||
term?.reset();
|
// Queue the reset after pending writes so old logs cannot appear in a new search.
|
||||||
followBottom.value = true;
|
term?.write('\x1bc');
|
||||||
|
followBottom = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeLogLine = (data: string) => {
|
const writeLogLine = (data: string) => {
|
||||||
if (!term) return;
|
term?.writeln(data);
|
||||||
term.writeln(data);
|
|
||||||
if (followBottom.value) {
|
|
||||||
term.scrollToBottom();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const bindXTermEvents = () => {
|
|
||||||
if (!term) return;
|
|
||||||
onScrollDisposable?.dispose();
|
|
||||||
onScrollDisposable = term.onScroll(() => {
|
|
||||||
if (!term) return;
|
|
||||||
const active = term.buffer.active;
|
|
||||||
followBottom.value = active.baseY + active.cursorY >= active.length - 2;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showEventSourceAuthError = (message: string) => {
|
const showEventSourceAuthError = (message: string) => {
|
||||||
@@ -235,11 +223,15 @@ const initTerminal = () => {
|
|||||||
term.open(terminalElement.value);
|
term.open(terminalElement.value);
|
||||||
term.loadAddon(fitAddon);
|
term.loadAddon(fitAddon);
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
bindXTermEvents();
|
// Terminal.dispose() also disposes these listeners.
|
||||||
};
|
term.onScroll(() => {
|
||||||
|
if (!term) return;
|
||||||
const handleClose = async () => {
|
const active = term.buffer.active;
|
||||||
stopListening();
|
followBottom = active.viewportY >= active.baseY;
|
||||||
|
});
|
||||||
|
term.onWriteParsed(() => {
|
||||||
|
if (followBottom) term?.scrollToBottom();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchLogs = async () => {
|
const searchLogs = async () => {
|
||||||
@@ -248,6 +240,7 @@ const searchLogs = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopListening();
|
stopListening();
|
||||||
|
const version = searchVersion;
|
||||||
clearTerminal();
|
clearTerminal();
|
||||||
|
|
||||||
let currentNode = globalCurrentNode.value;
|
let currentNode = globalCurrentNode.value;
|
||||||
@@ -261,15 +254,19 @@ const searchLogs = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const authError = await checkStreamAuth(url, currentNode);
|
const authError = await checkStreamAuth(url, currentNode);
|
||||||
|
if (version !== searchVersion) return;
|
||||||
if (authError) {
|
if (authError) {
|
||||||
showEventSourceAuthError(authError);
|
showEventSourceAuthError(authError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
eventSource = new EventSource(url);
|
const source = new EventSource(url);
|
||||||
eventSource.onmessage = (event: MessageEvent) => {
|
eventSource = source;
|
||||||
|
source.onmessage = (event: MessageEvent) => {
|
||||||
|
if (version !== searchVersion) return;
|
||||||
writeLogLine(event.data);
|
writeLogLine(event.data);
|
||||||
};
|
};
|
||||||
eventSource.onerror = (event: MessageEvent) => {
|
source.onerror = (event: MessageEvent) => {
|
||||||
|
if (version !== searchVersion) return;
|
||||||
stopListening();
|
stopListening();
|
||||||
if (event.data && event.data != '') {
|
if (event.data && event.data != '') {
|
||||||
MsgError(event.data);
|
MsgError(event.data);
|
||||||
@@ -353,8 +350,6 @@ onMounted(() => {
|
|||||||
logSearch.resource = props.resource;
|
logSearch.resource = props.resource;
|
||||||
|
|
||||||
logSearch.tail = 100;
|
logSearch.tail = 100;
|
||||||
logSearch.mode = 'all';
|
|
||||||
logSearch.isWatch = true;
|
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
initTerminal();
|
initTerminal();
|
||||||
@@ -369,8 +364,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
handleClose();
|
stopListening();
|
||||||
onScrollDisposable?.dispose();
|
|
||||||
if (term) {
|
if (term) {
|
||||||
term.dispose();
|
term.dispose();
|
||||||
term = null;
|
term = null;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { onActivated, ref, watch } from 'vue';
|
||||||
defineOptions({ name: 'TableSearch' });
|
defineOptions({ name: 'TableSearch' });
|
||||||
|
|
||||||
const emit = defineEmits(['search', 'update:searchName']);
|
const emit = defineEmits(['search', 'update:searchName']);
|
||||||
@@ -42,6 +42,10 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
searchInfo.value = props.searchName;
|
||||||
|
});
|
||||||
|
|
||||||
const search = () => {
|
const search = () => {
|
||||||
emit('update:searchName', searchInfo.value);
|
emit('update:searchName', searchInfo.value);
|
||||||
emit('search');
|
emit('search');
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, useAttrs } from 'vue';
|
import { computed, onDeactivated, onMounted, ref, useAttrs } from 'vue';
|
||||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||||
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
|
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
|
||||||
import { useCardColumns } from './useCardColumns';
|
import { useCardColumns } from './useCardColumns';
|
||||||
@@ -459,6 +459,8 @@ watch([currentViewMode, tableData, () => props.syncCardContentHeight], scheduleC
|
|||||||
flush: 'post',
|
flush: 'post',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDeactivated(clearSelects);
|
||||||
|
|
||||||
watch([() => props.paginationConfig?.currentPage, () => props.paginationConfig?.pageSize], clearSelects, {
|
watch([() => props.paginationConfig?.currentPage, () => props.paginationConfig?.pageSize], clearSelects, {
|
||||||
flush: 'sync',
|
flush: 'sync',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { reactive, type UnwrapNestedRefs } from 'vue';
|
import { reactive, type UnwrapNestedRefs } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
|
||||||
import { getPageState } from '@/utils/page-state-cache';
|
|
||||||
|
|
||||||
|
/** A new page visit starts from its defaults instead of restoring the previous page number. */
|
||||||
export const usePageState = <T extends object>(factory: () => T): UnwrapNestedRefs<T> => {
|
export const usePageState = <T extends object>(factory: () => T): UnwrapNestedRefs<T> => {
|
||||||
const route = useRoute();
|
return reactive(factory());
|
||||||
const { currentNode } = useGlobalStore();
|
|
||||||
const key = `${currentNode.value}:${String(route.name || route.path)}`;
|
|
||||||
return reactive(getPageState(key, factory));
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { watch, type Ref } from 'vue';
|
|
||||||
import GlobalStore from '@/store/modules/global';
|
|
||||||
|
|
||||||
type SearchValue = string | number | undefined;
|
|
||||||
type SearchFields = Record<string, Ref<SearchValue>>;
|
|
||||||
|
|
||||||
export const useSearchPersistence = (page: string, fields: SearchFields, scope?: () => SearchValue) => {
|
|
||||||
const { currentNode } = storeToRefs(GlobalStore());
|
|
||||||
const defaults = Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value]));
|
|
||||||
let initialized = false;
|
|
||||||
let restoring = false;
|
|
||||||
let activeKey: string | undefined;
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => {
|
|
||||||
const context = scope?.();
|
|
||||||
if (scope && context === undefined) return undefined;
|
|
||||||
return `1panel:search:v1:${JSON.stringify([currentNode.value, page, context ?? ''])}`;
|
|
||||||
},
|
|
||||||
(key) => {
|
|
||||||
activeKey = key;
|
|
||||||
let saved: Record<string, unknown> = {};
|
|
||||||
try {
|
|
||||||
const parsed = activeKey ? JSON.parse(localStorage.getItem(activeKey) || '{}') : {};
|
|
||||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) saved = parsed;
|
|
||||||
} catch {}
|
|
||||||
restoring = true;
|
|
||||||
for (const [name, field] of Object.entries(fields)) {
|
|
||||||
const value = saved[name];
|
|
||||||
const initial = defaults[name];
|
|
||||||
let fallback = initial;
|
|
||||||
if (initialized) {
|
|
||||||
fallback = typeof initial === 'string' ? '' : undefined;
|
|
||||||
}
|
|
||||||
const valid =
|
|
||||||
(typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))) &&
|
|
||||||
(fallback === undefined || typeof value === typeof fallback);
|
|
||||||
if (value === null) {
|
|
||||||
field.value = typeof initial === 'string' ? '' : undefined;
|
|
||||||
} else if (valid) {
|
|
||||||
field.value = value as SearchValue;
|
|
||||||
} else {
|
|
||||||
field.value = fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
restoring = false;
|
|
||||||
initialized = true;
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'sync' },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value ?? null])),
|
|
||||||
(value) => {
|
|
||||||
if (restoring || !activeKey) return;
|
|
||||||
try {
|
|
||||||
localStorage.setItem(activeKey, JSON.stringify(value));
|
|
||||||
} catch {}
|
|
||||||
},
|
|
||||||
{ flush: 'sync' },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -216,6 +216,10 @@ export const DNSTypes = [
|
|||||||
label: 'NameSilo',
|
label: 'NameSilo',
|
||||||
value: 'NameSilo',
|
value: 'NameSilo',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Netcup',
|
||||||
|
value: 'Netcup',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'NameCheap',
|
label: 'NameCheap',
|
||||||
value: 'NameCheap',
|
value: 'NameCheap',
|
||||||
|
|||||||
@@ -570,6 +570,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'Stop keeping page session',
|
cancelKeepAlive: 'Stop keeping page session',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'User Center',
|
||||||
|
exitSystem: 'Log Out',
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1065,6 +1067,13 @@ const message = {
|
|||||||
whiteListHelper: 'Restrict access to only IPs in the whitelist',
|
whiteListHelper: 'Restrict access to only IPs in the whitelist',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI Gateway supports standalone deployment',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI Gateway now supports standalone deployment. The built-in AI Gateway in 1Panel will no longer receive updates. We recommend installing the standalone version from the App Store. You can also continue using the current version.',
|
||||||
|
deploymentNoticeContinue: 'Understood, continue using',
|
||||||
|
deploymentNoticeAppStore: 'Go to App Store',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'You do not have access to the App Store. Contact your administrator to install the standalone version.',
|
||||||
title: 'AI Gateway',
|
title: 'AI Gateway',
|
||||||
apiReference: 'API Reference',
|
apiReference: 'API Reference',
|
||||||
notInstalled: 'Not installed',
|
notInstalled: 'Not installed',
|
||||||
@@ -1508,6 +1517,24 @@ const message = {
|
|||||||
extraHeaders: 'Extra Headers',
|
extraHeaders: 'Extra Headers',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/s',
|
||||||
|
requestsPerSecond: 'Requests/s',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'Normal completion',
|
||||||
|
length: 'Length limit reached',
|
||||||
|
abort: 'Aborted',
|
||||||
|
error: 'Error',
|
||||||
|
repetition: 'Repetition',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'Request analysis',
|
||||||
|
promptDistribution: 'Prompt token length distribution',
|
||||||
|
generationDistribution: 'Generation token length distribution',
|
||||||
|
finishReasons: 'Request finish reasons',
|
||||||
|
queueTime: 'Mean queue time',
|
||||||
|
averageGenerationTokens: 'Mean output tokens per request',
|
||||||
|
prefixCacheHitRate: 'Prefix cache hit rate',
|
||||||
|
distributionHelper:
|
||||||
|
'Length distributions show request rates and are unaffected by the average/peak selection.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'Clear monitoring history only for this vLLM instance? Other instances, host and GPU monitoring are unaffected.',
|
'Clear monitoring history only for this vLLM instance? Other instances, host and GPU monitoring are unaffected.',
|
||||||
overview: 'Overview',
|
overview: 'Overview',
|
||||||
@@ -1869,6 +1896,11 @@ const message = {
|
|||||||
imageName: 'Image name',
|
imageName: 'Image name',
|
||||||
pull: 'Pull',
|
pull: 'Pull',
|
||||||
path: 'Path',
|
path: 'Path',
|
||||||
|
imageUploadDrop: 'Drop an image file here, or click to upload',
|
||||||
|
imageUploadHelper:
|
||||||
|
'Any file format is allowed. Docker validates the contents. Uploaded files are retained for retrying imports or manual deletion.',
|
||||||
|
imageUploadEmpty: 'The file is empty. Select a valid image archive.',
|
||||||
|
imageImportSubmitted: 'Image import task submitted. Check the task log for the result.',
|
||||||
importImage: 'Import',
|
importImage: 'Import',
|
||||||
buildArgs: 'Build Arguments',
|
buildArgs: 'Build Arguments',
|
||||||
imageBuild: 'Build',
|
imageBuild: 'Build',
|
||||||
@@ -4166,6 +4198,9 @@ const message = {
|
|||||||
'Enabling this can avoid certificate leakage, setting a default website will invalidate this setting',
|
'Enabling this can avoid certificate leakage, setting a default website will invalidate this setting',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm:
|
||||||
|
'The following certificates will be deleted. ACME certificates will be automatically revoked. Continue?',
|
||||||
|
deleteAndRevoke: 'Delete and revoke',
|
||||||
create: 'Request',
|
create: 'Request',
|
||||||
provider: 'Type',
|
provider: 'Type',
|
||||||
manualCreate: 'Created manually',
|
manualCreate: 'Created manually',
|
||||||
@@ -4306,8 +4341,6 @@ const message = {
|
|||||||
dockerGuard: 'Container Port Guard',
|
dockerGuard: 'Container Port Guard',
|
||||||
systemFirewall: 'Host firewall',
|
systemFirewall: 'Host firewall',
|
||||||
systemFirewallHelper: 'Controls host port access and inbound firewall rules.',
|
systemFirewallHelper: 'Controls host port access and inbound firewall rules.',
|
||||||
forwardPolicyDropWarning:
|
|
||||||
'This server restricts network forwarding. Port forwarding rules may not take effect.',
|
|
||||||
forwardingHelper: 'Manages port-forwarding rules.',
|
forwardingHelper: 'Manages port-forwarding rules.',
|
||||||
dockerFirewallHelper: 'Selects how 1Panel manages container port protection.',
|
dockerFirewallHelper: 'Selects how 1Panel manages container port protection.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
|
||||||
@@ -4390,9 +4423,9 @@ const message = {
|
|||||||
'Host INPUT rules do not directly protect this Docker published port. Click to open Container Port Guard.',
|
'Host INPUT rules do not directly protect this Docker published port. Click to open Container Port Guard.',
|
||||||
notInitialized: 'Not initialized',
|
notInitialized: 'Not initialized',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'IPv6 forwarding is blocked: interfaces {0} have accept_ra=1 and may rely on RA/SLAAC. Review the network configuration, persist accept_ra=2 on interfaces that need RA, then retry.',
|
'IPv6 forwarding is blocked to prevent possible network loss. See the documentation and system logs for details.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'Cannot check IPv6 RA configuration. Enabling IPv6 forwarding is blocked. Check the system network configuration and retry.',
|
'IPv6 forwarding is blocked because the network configuration could not be checked. See the system logs for details.',
|
||||||
ipv6ForwardingOnDemand:
|
ipv6ForwardingOnDemand:
|
||||||
'IPv6 forwarding is not enabled. It will be checked and enabled when IPv6 rules are added or restored.',
|
'IPv6 forwarding is not enabled. It will be checked and enabled when IPv6 rules are added or restored.',
|
||||||
familyUnsupported: 'The system does not support {0}',
|
familyUnsupported: 'The system does not support {0}',
|
||||||
|
|||||||
@@ -570,6 +570,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'Dejar de mantener la sesión de la página',
|
cancelKeepAlive: 'Dejar de mantener la sesión de la página',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'Centro de usuario',
|
||||||
|
exitSystem: 'Cerrar sesión',
|
||||||
logout: 'Cerrar sesión',
|
logout: 'Cerrar sesión',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1069,6 +1071,13 @@ const message = {
|
|||||||
whiteListHelper: 'Restringir el acceso solo a las IP incluidas en la lista blanca',
|
whiteListHelper: 'Restringir el acceso solo a las IP incluidas en la lista blanca',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI Gateway admite la implementación independiente',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI Gateway ahora admite la implementación independiente. AI Gateway integrado en 1Panel dejará de recibir actualizaciones. Recomendamos instalar la versión independiente desde la tienda de aplicaciones. También puede seguir usando la versión actual.',
|
||||||
|
deploymentNoticeContinue: 'Entendido, seguir usando',
|
||||||
|
deploymentNoticeAppStore: 'Ir a la tienda de aplicaciones',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'No tiene acceso a la tienda de aplicaciones. Contacte con su administrador para instalar la versión independiente.',
|
||||||
title: 'Gateway de IA',
|
title: 'Gateway de IA',
|
||||||
apiReference: 'Referencia de API',
|
apiReference: 'Referencia de API',
|
||||||
notInstalled: 'No instalado',
|
notInstalled: 'No instalado',
|
||||||
@@ -1517,6 +1526,24 @@ const message = {
|
|||||||
extraHeaders: 'Encabezados adicionales',
|
extraHeaders: 'Encabezados adicionales',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/s',
|
||||||
|
requestsPerSecond: 'Solicitudes/s',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'Finalización normal',
|
||||||
|
length: 'Límite de longitud alcanzado',
|
||||||
|
abort: 'Cancelada',
|
||||||
|
error: 'Error',
|
||||||
|
repetition: 'Repetición',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'Análisis de solicitudes',
|
||||||
|
promptDistribution: 'Distribución de longitud de tokens de entrada',
|
||||||
|
generationDistribution: 'Distribución de longitud de tokens de salida',
|
||||||
|
finishReasons: 'Motivos de finalización de solicitudes',
|
||||||
|
queueTime: 'Tiempo medio en cola',
|
||||||
|
averageGenerationTokens: 'Tokens de salida medios por solicitud',
|
||||||
|
prefixCacheHitRate: 'Tasa de aciertos de caché de prefijos',
|
||||||
|
distributionHelper:
|
||||||
|
'Las distribuciones de longitud muestran tasas de solicitudes y no cambian al seleccionar media o pico.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'¿Borrar solo el historial de monitoreo de esta instancia de vLLM? No afecta a otras instancias ni al monitoreo del host o de GPU.',
|
'¿Borrar solo el historial de monitoreo de esta instancia de vLLM? No afecta a otras instancias ni al monitoreo del host o de GPU.',
|
||||||
overview: 'Resumen',
|
overview: 'Resumen',
|
||||||
@@ -1893,6 +1920,12 @@ const message = {
|
|||||||
imageName: 'Nombre de la imagen',
|
imageName: 'Nombre de la imagen',
|
||||||
pull: 'Descargar',
|
pull: 'Descargar',
|
||||||
path: 'Ruta',
|
path: 'Ruta',
|
||||||
|
imageUploadDrop: 'Arrastre un archivo de imagen aquí o haga clic para subirlo',
|
||||||
|
imageUploadHelper:
|
||||||
|
'Se permite cualquier formato de archivo. Docker valida el contenido. Los archivos subidos se conservan para reintentar la importación o eliminarlos manualmente.',
|
||||||
|
imageUploadEmpty: 'El archivo está vacío. Seleccione un archivo de imagen válido.',
|
||||||
|
imageImportSubmitted:
|
||||||
|
'Tarea de importación de imagen enviada. Consulte el registro de la tarea para ver el resultado.',
|
||||||
importImage: 'Importar',
|
importImage: 'Importar',
|
||||||
buildArgs: 'Argumentos de Construcción',
|
buildArgs: 'Argumentos de Construcción',
|
||||||
imageBuild: 'Construir',
|
imageBuild: 'Construir',
|
||||||
@@ -4216,6 +4249,9 @@ const message = {
|
|||||||
'Habilitar esto puede evitar la fuga de certificados, establecer un sitio web predeterminado invalidará esta configuración',
|
'Habilitar esto puede evitar la fuga de certificados, establecer un sitio web predeterminado invalidará esta configuración',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm:
|
||||||
|
'Se eliminarán los siguientes certificados. Los certificados ACME se revocarán automáticamente. ¿Desea continuar?',
|
||||||
|
deleteAndRevoke: 'Eliminar y revocar',
|
||||||
create: 'Solicitar',
|
create: 'Solicitar',
|
||||||
provider: 'Tipo',
|
provider: 'Tipo',
|
||||||
manualCreate: 'Creado manualmente',
|
manualCreate: 'Creado manualmente',
|
||||||
@@ -4357,8 +4393,6 @@ const message = {
|
|||||||
dockerGuard: 'Protección de puertos de contenedores',
|
dockerGuard: 'Protección de puertos de contenedores',
|
||||||
systemFirewall: 'Firewall del host',
|
systemFirewall: 'Firewall del host',
|
||||||
systemFirewallHelper: 'Controla el acceso a los puertos del host y las reglas de entrada.',
|
systemFirewallHelper: 'Controla el acceso a los puertos del host y las reglas de entrada.',
|
||||||
forwardPolicyDropWarning:
|
|
||||||
'Este servidor restringe el reenvío de red. Es posible que las reglas de reenvío de puertos no surtan efecto.',
|
|
||||||
forwardingHelper: 'Gestiona las reglas de reenvío de puertos.',
|
forwardingHelper: 'Gestiona las reglas de reenvío de puertos.',
|
||||||
dockerFirewallHelper: 'Selecciona cómo gestiona 1Panel la protección de puertos de contenedores.',
|
dockerFirewallHelper: 'Selecciona cómo gestiona 1Panel la protección de puertos de contenedores.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
|
||||||
@@ -4443,9 +4477,9 @@ const message = {
|
|||||||
'Las reglas INPUT del host no protegen directamente este puerto publicado por Docker. Haz clic para abrir la protección de puertos de contenedores.',
|
'Las reglas INPUT del host no protegen directamente este puerto publicado por Docker. Haz clic para abrir la protección de puertos de contenedores.',
|
||||||
notInitialized: 'No inicializado',
|
notInitialized: 'No inicializado',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'El reenvío IPv6 está bloqueado: las interfaces {0} tienen accept_ra=1 y pueden depender de RA/SLAAC. Revise la configuración de red, configure accept_ra=2 de forma persistente en las interfaces que necesitan RA y vuelva a intentarlo.',
|
'El reenvío IPv6 está bloqueado para evitar una posible pérdida de conectividad. Consulte la documentación y los registros del sistema.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'No se puede comprobar la configuración RA de IPv6. Se ha bloqueado la activación del reenvío IPv6. Revise la configuración de red y vuelva a intentarlo.',
|
'El reenvío IPv6 está bloqueado porque no se pudo comprobar la configuración de red. Consulte los registros del sistema.',
|
||||||
ipv6ForwardingOnDemand:
|
ipv6ForwardingOnDemand:
|
||||||
'El reenvío IPv6 no está habilitado. Se comprobará y habilitará al añadir o restaurar reglas IPv6.',
|
'El reenvío IPv6 no está habilitado. Se comprobará y habilitará al añadir o restaurar reglas IPv6.',
|
||||||
familyUnsupported: 'El sistema no admite {0}',
|
familyUnsupported: 'El sistema no admite {0}',
|
||||||
|
|||||||
@@ -565,6 +565,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'لغو حفظ نشست صفحه',
|
cancelKeepAlive: 'لغو حفظ نشست صفحه',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'مرکز کاربری',
|
||||||
|
exitSystem: 'خروج از سیستم',
|
||||||
logout: 'خروج',
|
logout: 'خروج',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1051,6 +1053,13 @@ const message = {
|
|||||||
whiteListHelper: 'دسترسی را فقط به IPهای موجود در لیست سفید محدود کنید',
|
whiteListHelper: 'دسترسی را فقط به IPهای موجود در لیست سفید محدود کنید',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'درگاه هوش مصنوعی از استقرار مستقل پشتیبانی میکند',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'درگاه هوش مصنوعی اکنون از استقرار مستقل پشتیبانی میکند. درگاه هوش مصنوعی داخلی 1Panel دیگر بهروزرسانی دریافت نخواهد کرد. توصیه میکنیم نسخه مستقل را از فروشگاه برنامه نصب کنید. همچنین میتوانید به استفاده از نسخه فعلی ادامه دهید.',
|
||||||
|
deploymentNoticeContinue: 'متوجه شدم، ادامه استفاده',
|
||||||
|
deploymentNoticeAppStore: 'رفتن به فروشگاه برنامه',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'شما به فروشگاه برنامه دسترسی ندارید. برای نصب نسخه مستقل با مدیر تماس بگیرید.',
|
||||||
title: 'دروازه AI',
|
title: 'دروازه AI',
|
||||||
apiReference: 'راهنمای API',
|
apiReference: 'راهنمای API',
|
||||||
notInstalled: 'نصب نشده',
|
notInstalled: 'نصب نشده',
|
||||||
@@ -1493,6 +1502,23 @@ const message = {
|
|||||||
extraHeaders: 'هدرهای اضافی',
|
extraHeaders: 'هدرهای اضافی',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/ثانیه',
|
||||||
|
requestsPerSecond: 'درخواست/ثانیه',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'پایان عادی',
|
||||||
|
length: 'رسیدن به حد طول',
|
||||||
|
abort: 'لغوشده',
|
||||||
|
error: 'خطا',
|
||||||
|
repetition: 'تکرار خروجی',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'تحلیل درخواستها',
|
||||||
|
promptDistribution: 'توزیع طول توکنهای ورودی',
|
||||||
|
generationDistribution: 'توزیع طول توکنهای خروجی',
|
||||||
|
finishReasons: 'دلایل پایان درخواستها',
|
||||||
|
queueTime: 'میانگین زمان انتظار در صف',
|
||||||
|
averageGenerationTokens: 'میانگین توکنهای خروجی هر درخواست',
|
||||||
|
prefixCacheHitRate: 'نرخ اصابت حافظه نهان پیشوند',
|
||||||
|
distributionHelper: 'توزیع طول، نرخ درخواستها را نشان میدهد و از انتخاب میانگین یا اوج تأثیر نمیپذیرد.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'فقط سابقه نظارت این نمونه vLLM پاک شود؟ سایر نمونه\u200cها و نظارت میزبان و GPU تغییری نمی\u200cکنند.',
|
'فقط سابقه نظارت این نمونه vLLM پاک شود؟ سایر نمونه\u200cها و نظارت میزبان و GPU تغییری نمی\u200cکنند.',
|
||||||
overview: 'نمای کلی',
|
overview: 'نمای کلی',
|
||||||
@@ -1853,6 +1879,11 @@ const message = {
|
|||||||
imageName: 'نام تصویر',
|
imageName: 'نام تصویر',
|
||||||
pull: 'دریافت',
|
pull: 'دریافت',
|
||||||
path: 'مسیر',
|
path: 'مسیر',
|
||||||
|
imageUploadDrop: 'فایل ایمیج را اینجا بکشید یا برای بارگذاری کلیک کنید',
|
||||||
|
imageUploadHelper:
|
||||||
|
'همه قالبهای فایل مجاز هستند. Docker محتوای فایل را اعتبارسنجی میکند. فایلهای بارگذاریشده برای تلاش مجدد جهت وارد کردن یا حذف دستی نگه داشته میشوند.',
|
||||||
|
imageUploadEmpty: 'فایل خالی است. یک فایل ایمیج معتبر انتخاب کنید.',
|
||||||
|
imageImportSubmitted: 'وظیفه وارد کردن ایمیج ثبت شد. برای مشاهده نتیجه، گزارش وظیفه را بررسی کنید.',
|
||||||
importImage: 'واردات',
|
importImage: 'واردات',
|
||||||
buildArgs: 'آرگومانهای ساخت',
|
buildArgs: 'آرگومانهای ساخت',
|
||||||
imageBuild: 'ساخت',
|
imageBuild: 'ساخت',
|
||||||
@@ -4132,6 +4163,8 @@ const message = {
|
|||||||
'فعالسازی این گزینه میتواند از نشت گواهی جلوگیری کند، تنظیم یک وبسایت پیشفرض این تنظیم را بیاثر میکند',
|
'فعالسازی این گزینه میتواند از نشت گواهی جلوگیری کند، تنظیم یک وبسایت پیشفرض این تنظیم را بیاثر میکند',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm: 'گواهیهای زیر حذف خواهند شد. گواهیهای ACME بهطور خودکار باطل خواهند شد. آیا ادامه میدهید؟',
|
||||||
|
deleteAndRevoke: 'حذف و ابطال',
|
||||||
create: 'درخواست',
|
create: 'درخواست',
|
||||||
provider: 'نوع',
|
provider: 'نوع',
|
||||||
manualCreate: 'ایجاد دستی',
|
manualCreate: 'ایجاد دستی',
|
||||||
@@ -4271,7 +4304,6 @@ const message = {
|
|||||||
dockerGuard: 'محافظت از پورت کانتینر',
|
dockerGuard: 'محافظت از پورت کانتینر',
|
||||||
systemFirewall: 'فایروال میزبان',
|
systemFirewall: 'فایروال میزبان',
|
||||||
systemFirewallHelper: 'دسترسی پورتهای میزبان و قوانین ورودی را کنترل میکند.',
|
systemFirewallHelper: 'دسترسی پورتهای میزبان و قوانین ورودی را کنترل میکند.',
|
||||||
forwardPolicyDropWarning: 'این سرور هدایت ترافیک شبکه را محدود کرده است. ممکن است قوانین هدایت پورت کار نکنند.',
|
|
||||||
forwardingHelper: 'قوانین انتقال پورت را مدیریت میکند.',
|
forwardingHelper: 'قوانین انتقال پورت را مدیریت میکند.',
|
||||||
dockerFirewallHelper: 'روش مدیریت محافظت از پورت کانتینر در 1Panel را انتخاب میکند.',
|
dockerFirewallHelper: 'روش مدیریت محافظت از پورت کانتینر در 1Panel را انتخاب میکند.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0، آزمایشی',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0، آزمایشی',
|
||||||
@@ -4355,10 +4387,9 @@ const message = {
|
|||||||
dockerInputNotProtected:
|
dockerInputNotProtected:
|
||||||
'قوانین INPUT میزبان مستقیماً از این پورت منتشرشده Docker محافظت نمیکنند. برای باز کردن محافظت از پورت کانتینر کلیک کنید.',
|
'قوانین INPUT میزبان مستقیماً از این پورت منتشرشده Docker محافظت نمیکنند. برای باز کردن محافظت از پورت کانتینر کلیک کنید.',
|
||||||
notInitialized: 'راهاندازی نشده',
|
notInitialized: 'راهاندازی نشده',
|
||||||
ipv6RARisk:
|
ipv6RARisk: 'فعالسازی هدایت IPv6 به دلیل خطر قطع شبکه مسدود شد. مستندات و گزارشهای سیستم را بررسی کنید.',
|
||||||
'هدایت IPv6 مسدود شد: رابطهای {0} دارای accept_ra=1 هستند و ممکن است به RA/SLAAC وابسته باشند. تنظیمات شبکه را بررسی کنید، برای رابطهای نیازمند RA مقدار accept_ra=2 را بهصورت پایدار تنظیم کنید و دوباره تلاش کنید.',
|
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'بررسی تنظیمات RA در IPv6 ممکن نیست. فعالسازی هدایت IPv6 مسدود شده است. تنظیمات شبکه سیستم را بررسی کرده و دوباره تلاش کنید.',
|
'فعالسازی هدایت IPv6 به دلیل عدم امکان بررسی پیکربندی شبکه مسدود شد. جزئیات در گزارشهای سیستم موجود است.',
|
||||||
ipv6ForwardingOnDemand: 'هدایت IPv6 فعال نیست. هنگام افزودن یا بازیابی قوانین IPv6 بررسی و فعال خواهد شد.',
|
ipv6ForwardingOnDemand: 'هدایت IPv6 فعال نیست. هنگام افزودن یا بازیابی قوانین IPv6 بررسی و فعال خواهد شد.',
|
||||||
familyUnsupported: 'سیستم از {0} پشتیبانی نمیکند',
|
familyUnsupported: 'سیستم از {0} پشتیبانی نمیکند',
|
||||||
familyChainIssue: '{0} · زنجیره {1} · {2}',
|
familyChainIssue: '{0} · زنجیره {1} · {2}',
|
||||||
|
|||||||
@@ -568,6 +568,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'ページセッションの保持を解除',
|
cancelKeepAlive: 'ページセッションの保持を解除',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'ユーザーセンター',
|
||||||
|
exitSystem: 'ログアウト',
|
||||||
logout: 'ログアウト',
|
logout: 'ログアウト',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1054,6 +1056,13 @@ const message = {
|
|||||||
whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する',
|
whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI ゲートウェイの独立したデプロイに対応',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI ゲートウェイは独立したデプロイに対応しました。1Panel 内蔵の AI ゲートウェイは今後更新されません。アプリストアから独立版をインストールすることをお勧めします。現在のバージョンを引き続き使用することもできます。',
|
||||||
|
deploymentNoticeContinue: '了解して使用を継続',
|
||||||
|
deploymentNoticeAppStore: 'アプリストアへ',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'アプリストアへのアクセス権限がありません。管理者に独立版のインストールを依頼してください。',
|
||||||
title: 'AI ゲートウェイ',
|
title: 'AI ゲートウェイ',
|
||||||
apiReference: 'API リファレンス',
|
apiReference: 'API リファレンス',
|
||||||
notInstalled: '未インストール',
|
notInstalled: '未インストール',
|
||||||
@@ -1496,6 +1505,23 @@ const message = {
|
|||||||
extraHeaders: '追加ヘッダー',
|
extraHeaders: '追加ヘッダー',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/秒',
|
||||||
|
requestsPerSecond: 'リクエスト/秒',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: '正常終了',
|
||||||
|
length: '長さ上限に到達',
|
||||||
|
abort: '中止',
|
||||||
|
error: 'エラー',
|
||||||
|
repetition: '繰り返し出力',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'リクエスト分析',
|
||||||
|
promptDistribution: '入力トークン長の分布',
|
||||||
|
generationDistribution: '出力トークン長の分布',
|
||||||
|
finishReasons: 'リクエスト終了理由',
|
||||||
|
queueTime: '平均キュー待機時間',
|
||||||
|
averageGenerationTokens: 'リクエストあたりの平均出力トークン数',
|
||||||
|
prefixCacheHitRate: 'プレフィックスキャッシュヒット率',
|
||||||
|
distributionHelper: '長さの分布はリクエストレートで集計され、平均/ピークの切り替えには影響されません。',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'この vLLM インスタンスの監視履歴のみを削除しますか?他のインスタンス、ホスト、GPU の監視には影響しません。',
|
'この vLLM インスタンスの監視履歴のみを削除しますか?他のインスタンス、ホスト、GPU の監視には影響しません。',
|
||||||
overview: '概要',
|
overview: '概要',
|
||||||
@@ -1850,6 +1876,11 @@ const message = {
|
|||||||
imageName: '画像名',
|
imageName: '画像名',
|
||||||
pull: '引く',
|
pull: '引く',
|
||||||
path: 'パス',
|
path: 'パス',
|
||||||
|
imageUploadDrop: 'イメージファイルをここにドラッグするか、クリックしてアップロードしてください',
|
||||||
|
imageUploadHelper:
|
||||||
|
'ファイル形式に制限はありません。Docker が内容を検証します。アップロードしたファイルは保持されるため、インポートを再試行したり、手動で削除したりできます。',
|
||||||
|
imageUploadEmpty: 'ファイルが空です。有効なイメージファイルを選択してください。',
|
||||||
|
imageImportSubmitted: 'イメージのインポートタスクを送信しました。結果はタスクログで確認してください。',
|
||||||
importImage: '輸入',
|
importImage: '輸入',
|
||||||
buildArgs: 'ビルド引数',
|
buildArgs: 'ビルド引数',
|
||||||
imageBuild: '建てる',
|
imageBuild: '建てる',
|
||||||
@@ -4154,6 +4185,8 @@ const message = {
|
|||||||
'有効にすると証明書の漏洩を防げますが、デフォルト Web サイトを設定するとこの設定は無効になります',
|
'有効にすると証明書の漏洩を防げますが、デフォルト Web サイトを設定するとこの設定は無効になります',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm: '以下の証明書を削除します。ACME 証明書は自動的に失効されます。続行しますか?',
|
||||||
|
deleteAndRevoke: '削除して失効',
|
||||||
create: 'リクエスト',
|
create: 'リクエスト',
|
||||||
provider: 'タイプ',
|
provider: 'タイプ',
|
||||||
manualCreate: '手動で作成されます',
|
manualCreate: '手動で作成されます',
|
||||||
@@ -4294,8 +4327,6 @@ const message = {
|
|||||||
dockerGuard: 'コンテナポート保護',
|
dockerGuard: 'コンテナポート保護',
|
||||||
systemFirewall: 'ホストファイアウォール',
|
systemFirewall: 'ホストファイアウォール',
|
||||||
systemFirewallHelper: 'ホストのポートアクセスと受信ルールを管理します。',
|
systemFirewallHelper: 'ホストのポートアクセスと受信ルールを管理します。',
|
||||||
forwardPolicyDropWarning:
|
|
||||||
'このサーバーではネットワーク転送が制限されているため、ポート転送ルールが機能しない可能性があります。',
|
|
||||||
forwardingHelper: 'ポート転送ルールを管理します。',
|
forwardingHelper: 'ポート転送ルールを管理します。',
|
||||||
dockerFirewallHelper: '1Panel のコンテナポート保護の管理方法を選択します。',
|
dockerFirewallHelper: '1Panel のコンテナポート保護の管理方法を選択します。',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0、実験的',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0、実験的',
|
||||||
@@ -4377,9 +4408,9 @@ const message = {
|
|||||||
'ホストの INPUT ルールでは、この Docker 公開ポートを直接保護できません。クリックしてコンテナポート保護を開きます。',
|
'ホストの INPUT ルールでは、この Docker 公開ポートを直接保護できません。クリックしてコンテナポート保護を開きます。',
|
||||||
notInitialized: '未初期化',
|
notInitialized: '未初期化',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'IPv6 転送がブロックされています。インターフェース {0} は accept_ra=1 で、RA/SLAAC に依存している可能性があります。ネットワーク設定を確認し、RA が必要なインターフェースで accept_ra=2 を永続的に設定してから再試行してください。',
|
'ネットワーク切断のおそれがあるため、IPv6 転送の有効化をブロックしました。ドキュメントとシステムログをご確認ください。',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'IPv6 RA 設定を確認できないため、IPv6 転送の有効化をブロックしました。システムのネットワーク設定を確認して再試行してください。',
|
'ネットワーク設定を確認できないため、IPv6 転送の有効化をブロックしました。詳細はシステムログをご確認ください。',
|
||||||
ipv6ForwardingOnDemand: 'IPv6 転送は無効です。IPv6 ルールの追加または復元時に確認して有効化します。',
|
ipv6ForwardingOnDemand: 'IPv6 転送は無効です。IPv6 ルールの追加または復元時に確認して有効化します。',
|
||||||
familyUnsupported: 'システムは {0} をサポートしていません',
|
familyUnsupported: 'システムは {0} をサポートしていません',
|
||||||
familyChainIssue: '{0} · {1} チェーン · {2}',
|
familyChainIssue: '{0} · {1} チェーン · {2}',
|
||||||
|
|||||||
@@ -562,6 +562,8 @@ const message = {
|
|||||||
cancelKeepAlive: '페이지 세션 유지 해제',
|
cancelKeepAlive: '페이지 세션 유지 해제',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: '사용자 센터',
|
||||||
|
exitSystem: '로그아웃',
|
||||||
logout: '로그아웃',
|
logout: '로그아웃',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1043,6 +1045,12 @@ const message = {
|
|||||||
whiteListHelper: '화이트리스트에 있는 IP만 접근 허용',
|
whiteListHelper: '화이트리스트에 있는 IP만 접근 허용',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI 게이트웨이 독립 배포 지원',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI 게이트웨이가 독립 배포를 지원합니다. 1Panel 내장 AI 게이트웨이는 더 이상 업데이트되지 않습니다. 앱 스토어에서 독립 버전을 설치하는 것을 권장합니다. 현재 버전을 계속 사용할 수도 있습니다.',
|
||||||
|
deploymentNoticeContinue: '확인, 계속 사용',
|
||||||
|
deploymentNoticeAppStore: '앱 스토어로 이동',
|
||||||
|
deploymentNoticeNoAppPermission: '앱 스토어 접근 권한이 없습니다. 관리자에게 독립 버전 설치를 요청하세요.',
|
||||||
title: 'AI 게이트웨이',
|
title: 'AI 게이트웨이',
|
||||||
apiReference: 'API 안내',
|
apiReference: 'API 안내',
|
||||||
notInstalled: '설치되지 않음',
|
notInstalled: '설치되지 않음',
|
||||||
@@ -1482,6 +1490,23 @@ const message = {
|
|||||||
extraHeaders: '추가 헤더',
|
extraHeaders: '추가 헤더',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/초',
|
||||||
|
requestsPerSecond: '요청/초',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: '정상 종료',
|
||||||
|
length: '길이 제한 도달',
|
||||||
|
abort: '중단',
|
||||||
|
error: '오류',
|
||||||
|
repetition: '반복 출력',
|
||||||
|
},
|
||||||
|
requestAnalysis: '요청 분석',
|
||||||
|
promptDistribution: '입력 토큰 길이 분포',
|
||||||
|
generationDistribution: '출력 토큰 길이 분포',
|
||||||
|
finishReasons: '요청 종료 사유',
|
||||||
|
queueTime: '평균 대기 시간',
|
||||||
|
averageGenerationTokens: '요청당 평균 출력 토큰 수',
|
||||||
|
prefixCacheHitRate: '접두사 캐시 적중률',
|
||||||
|
distributionHelper: '길이 분포는 요청 속도로 집계되며 평균/최댓값 선택의 영향을 받지 않습니다.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'현재 vLLM 인스턴스의 모니터링 기록만 삭제하시겠습니까? 다른 인스턴스, 호스트 및 GPU 모니터링에는 영향을 주지 않습니다.',
|
'현재 vLLM 인스턴스의 모니터링 기록만 삭제하시겠습니까? 다른 인스턴스, 호스트 및 GPU 모니터링에는 영향을 주지 않습니다.',
|
||||||
overview: '개요',
|
overview: '개요',
|
||||||
@@ -1833,6 +1858,11 @@ const message = {
|
|||||||
imageName: '이미지 이름',
|
imageName: '이미지 이름',
|
||||||
pull: '풀',
|
pull: '풀',
|
||||||
path: '경로',
|
path: '경로',
|
||||||
|
imageUploadDrop: '이미지 파일을 여기로 끌어 놓거나 클릭하여 업로드하세요',
|
||||||
|
imageUploadHelper:
|
||||||
|
'파일 형식에는 제한이 없습니다. Docker가 내용을 검증합니다. 업로드한 파일은 보관되므로 가져오기를 다시 시도하거나 수동으로 삭제할 수 있습니다.',
|
||||||
|
imageUploadEmpty: '빈 파일입니다. 유효한 이미지 파일을 선택하세요.',
|
||||||
|
imageImportSubmitted: '이미지 가져오기 작업이 제출되었습니다. 작업 로그에서 결과를 확인하세요.',
|
||||||
importImage: '가져오기',
|
importImage: '가져오기',
|
||||||
buildArgs: '빌드 인수',
|
buildArgs: '빌드 인수',
|
||||||
imageBuild: '이미지 빌드',
|
imageBuild: '이미지 빌드',
|
||||||
@@ -4078,6 +4108,8 @@ const message = {
|
|||||||
'활성화하면 인증서 누출을 방지할 수 있지만, 기본 웹사이트를 설정하면 이 설정이 무효화됩니다',
|
'활성화하면 인증서 누출을 방지할 수 있지만, 기본 웹사이트를 설정하면 이 설정이 무효화됩니다',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm: '다음 인증서를 삭제합니다. ACME 인증서는 자동으로 폐기됩니다. 계속하시겠습니까?',
|
||||||
|
deleteAndRevoke: '삭제 및 폐기',
|
||||||
create: '요청',
|
create: '요청',
|
||||||
provider: '유형',
|
provider: '유형',
|
||||||
manualCreate: '수동 생성됨',
|
manualCreate: '수동 생성됨',
|
||||||
@@ -4215,7 +4247,6 @@ const message = {
|
|||||||
dockerGuard: '컨테이너 포트 보호',
|
dockerGuard: '컨테이너 포트 보호',
|
||||||
systemFirewall: '호스트 방화벽',
|
systemFirewall: '호스트 방화벽',
|
||||||
systemFirewallHelper: '호스트 포트 접근과 인바운드 규칙을 관리합니다.',
|
systemFirewallHelper: '호스트 포트 접근과 인바운드 규칙을 관리합니다.',
|
||||||
forwardPolicyDropWarning: '현재 서버에서 네트워크 전달이 제한되어 포트 전달 규칙이 작동하지 않을 수 있습니다.',
|
|
||||||
forwardingHelper: '포트 포워딩 규칙을 관리합니다.',
|
forwardingHelper: '포트 포워딩 규칙을 관리합니다.',
|
||||||
dockerFirewallHelper: '1Panel 컨테이너 포트 보호 관리 방식을 선택합니다.',
|
dockerFirewallHelper: '1Panel 컨테이너 포트 보호 관리 방식을 선택합니다.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, 실험적',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, 실험적',
|
||||||
@@ -4296,9 +4327,9 @@ const message = {
|
|||||||
'호스트 INPUT 규칙은 이 Docker 게시 포트를 직접 보호하지 않습니다. 클릭하여 컨테이너 포트 보호를 여세요.',
|
'호스트 INPUT 규칙은 이 Docker 게시 포트를 직접 보호하지 않습니다. 클릭하여 컨테이너 포트 보호를 여세요.',
|
||||||
notInitialized: '초기화되지 않음',
|
notInitialized: '초기화되지 않음',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'IPv6 전달이 차단되었습니다. 인터페이스 {0}의 accept_ra=1 설정으로 인해 RA/SLAAC에 의존할 수 있습니다. 네트워크 설정을 확인하고 RA가 필요한 인터페이스에 accept_ra=2를 영구 설정한 후 다시 시도하세요.',
|
'네트워크 연결이 끊길 위험이 있어 IPv6 전달 활성화를 차단했습니다. 문서와 시스템 로그를 확인하세요.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'IPv6 RA 설정을 확인할 수 없어 IPv6 전달 활성화를 차단했습니다. 시스템 네트워크 설정을 확인한 후 다시 시도하세요.',
|
'네트워크 구성을 확인할 수 없어 IPv6 전달 활성화를 차단했습니다. 자세한 내용은 시스템 로그를 확인하세요.',
|
||||||
ipv6ForwardingOnDemand:
|
ipv6ForwardingOnDemand:
|
||||||
'IPv6 전달이 활성화되지 않았습니다. IPv6 규칙을 추가하거나 복원할 때 확인 후 활성화됩니다.',
|
'IPv6 전달이 활성화되지 않았습니다. IPv6 규칙을 추가하거나 복원할 때 확인 후 활성화됩니다.',
|
||||||
familyUnsupported: '시스템에서 {0}을 지원하지 않습니다',
|
familyUnsupported: '시스템에서 {0}을 지원하지 않습니다',
|
||||||
|
|||||||
@@ -563,6 +563,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'ຍົກເລີກການຮັກສາເຊັດຊັນໜ້າ',
|
cancelKeepAlive: 'ຍົກເລີກການຮັກສາເຊັດຊັນໜ້າ',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'ສູນຜູ້ໃຊ້',
|
||||||
|
exitSystem: 'ອອກຈາກລະບົບ',
|
||||||
logout: 'ອອກຈາກລະບົບ',
|
logout: 'ອອກຈາກລະບົບ',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1040,6 +1042,13 @@ const message = {
|
|||||||
whiteListHelper: 'ຈຳກັດການເຂົ້າເຖິງສະເພາະ IP ໃນລາຍຊື່ທີ່ອະນຸຍາດ',
|
whiteListHelper: 'ຈຳກັດການເຂົ້າເຖິງສະເພາະ IP ໃນລາຍຊື່ທີ່ອະນຸຍາດ',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI Gateway ຮອງຮັບການຕິດຕັ້ງແບບແຍກຕ່າງຫາກ',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI Gateway ຮອງຮັບການຕິດຕັ້ງແບບແຍກຕ່າງຫາກແລ້ວ. AI Gateway ທີ່ມາພ້ອມກັບ 1Panel ຈະບໍ່ໄດ້ຮັບການອັບເດດອີກ. ແນະນຳໃຫ້ຕິດຕັ້ງລຸ້ນແຍກຕ່າງຫາກຈາກຮ້ານແອັບ. ທ່ານຍັງສາມາດໃຊ້ລຸ້ນປັດຈຸບັນຕໍ່ໄດ້.',
|
||||||
|
deploymentNoticeContinue: 'ຮັບຊາບ, ໃຊ້ຕໍ່',
|
||||||
|
deploymentNoticeAppStore: 'ໄປທີ່ຮ້ານແອັບ',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'ທ່ານບໍ່ມີສິດເຂົ້າເຖິງຮ້ານແອັບ. ກະລຸນາຕິດຕໍ່ຜູ້ດູແລເພື່ອຕິດຕັ້ງລຸ້ນແຍກຕ່າງຫາກ.',
|
||||||
title: 'AI Gateway',
|
title: 'AI Gateway',
|
||||||
apiReference: 'ຄູ່ມື API',
|
apiReference: 'ຄູ່ມື API',
|
||||||
notInstalled: 'ຍັງບໍ່ໄດ້ຕິດຕັ້ງ',
|
notInstalled: 'ຍັງບໍ່ໄດ້ຕິດຕັ້ງ',
|
||||||
@@ -1475,6 +1484,23 @@ const message = {
|
|||||||
extraHeaders: 'Headers ເພີ່ມເຕີມ',
|
extraHeaders: 'Headers ເພີ່ມເຕີມ',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/ວິນາທີ',
|
||||||
|
requestsPerSecond: 'ຄຳຮ້ອງຂໍ/ວິນາທີ',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'ສຳເລັດປົກກະຕິ',
|
||||||
|
length: 'ເຖິງຂີດຈຳກັດຄວາມຍາວ',
|
||||||
|
abort: 'ຍົກເລີກ',
|
||||||
|
error: 'ຂໍ້ຜິດພາດ',
|
||||||
|
repetition: 'ຜົນອອກຊ້ຳ',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'ການວິເຄາະຄຳຮ້ອງຂໍ',
|
||||||
|
promptDistribution: 'ການແຈກຢາຍຄວາມຍາວໂທເຄັນຂາເຂົ້າ',
|
||||||
|
generationDistribution: 'ການແຈກຢາຍຄວາມຍາວໂທເຄັນຂາອອກ',
|
||||||
|
finishReasons: 'ເຫດຜົນທີ່ຄຳຮ້ອງຂໍສິ້ນສຸດ',
|
||||||
|
queueTime: 'ເວລາລໍຖ້າໃນຄິວສະເລ່ຍ',
|
||||||
|
averageGenerationTokens: 'ຈຳນວນໂທເຄັນຂາອອກສະເລ່ຍຕໍ່ຄຳຮ້ອງຂໍ',
|
||||||
|
prefixCacheHitRate: 'ອັດຕາການພົບໃນແຄດຄຳນຳໜ້າ',
|
||||||
|
distributionHelper: 'ການແຈກຢາຍຄວາມຍາວສະແດງອັດຕາຄຳຮ້ອງຂໍ ແລະ ບໍ່ປ່ຽນຕາມການເລືອກຄ່າສະເລ່ຍ ຫຼື ຄ່າສູງສຸດ.',
|
||||||
cleanHelper: 'ລຶບສະເພາະປະຫວັດການຕິດຕາມຂອງ vLLM ນີ້ບໍ? ບໍ່ກະທົບຕໍ່ອິນສະແຕນອື່ນ, ໂຮສ ຫຼື GPU.',
|
cleanHelper: 'ລຶບສະເພາະປະຫວັດການຕິດຕາມຂອງ vLLM ນີ້ບໍ? ບໍ່ກະທົບຕໍ່ອິນສະແຕນອື່ນ, ໂຮສ ຫຼື GPU.',
|
||||||
overview: 'ພາບລວມ',
|
overview: 'ພາບລວມ',
|
||||||
latencyAnalysis: 'ການວິເຄາະຄວາມລ່າຊ້າ',
|
latencyAnalysis: 'ການວິເຄາະຄວາມລ່າຊ້າ',
|
||||||
@@ -1822,6 +1848,11 @@ const message = {
|
|||||||
imageName: 'ຊື່ Image',
|
imageName: 'ຊື່ Image',
|
||||||
pull: 'ດຶງຂໍ້ມູນ (Pull)',
|
pull: 'ດຶງຂໍ້ມູນ (Pull)',
|
||||||
path: 'ເສັ້ນທາງ',
|
path: 'ເສັ້ນທາງ',
|
||||||
|
imageUploadDrop: 'ລາກໄຟລ໌ອິມເມຈມາວາງບ່ອນນີ້ ຫຼື ຄລິກເພື່ອອັບໂຫຼດ',
|
||||||
|
imageUploadHelper:
|
||||||
|
'ບໍ່ຈຳກັດຮູບແບບໄຟລ໌. Docker ຈະກວດສອບເນື້ອຫາ. ໄຟລ໌ທີ່ອັບໂຫຼດຈະຖືກເກັບໄວ້ ເພື່ອໃຫ້ສາມາດລອງນຳເຂົ້າອີກຄັ້ງ ຫຼື ລຶບດ້ວຍຕົນເອງ.',
|
||||||
|
imageUploadEmpty: 'ໄຟລ໌ຫວ່າງເປົ່າ. ກະລຸນາເລືອກໄຟລ໌ອິມເມຈທີ່ຖືກຕ້ອງ.',
|
||||||
|
imageImportSubmitted: 'ສົ່ງວຽກນຳເຂົ້າອິມເມຈແລ້ວ. ກະລຸນາເບິ່ງບັນທຶກວຽກເພື່ອກວດສອບຜົນ.',
|
||||||
importImage: 'ນຳເຂົ້າ',
|
importImage: 'ນຳເຂົ້າ',
|
||||||
buildArgs: 'Build Arguments',
|
buildArgs: 'Build Arguments',
|
||||||
imageBuild: 'ສ້າງ (Build)',
|
imageBuild: 'ສ້າງ (Build)',
|
||||||
@@ -4049,6 +4080,8 @@ const message = {
|
|||||||
'ການເປີດໃຊ້ສິ່ງນີ້ສາມາດຫຼີກລ່ຽງການຮົ່ວໄຫຼຂອງໃບຢັ້ງຢືນ, ແຕ່ການຕັ້ງເວັບໄຊເລີ່ມຕົ້ນຈະເຮັດໃຫ້ການຕັ້ງຄ່ານີ້ບໍ່ມີຜົນ',
|
'ການເປີດໃຊ້ສິ່ງນີ້ສາມາດຫຼີກລ່ຽງການຮົ່ວໄຫຼຂອງໃບຢັ້ງຢືນ, ແຕ່ການຕັ້ງເວັບໄຊເລີ່ມຕົ້ນຈະເຮັດໃຫ້ການຕັ້ງຄ່ານີ້ບໍ່ມີຜົນ',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm: 'ຈະລຶບໃບຢັ້ງຢືນຕໍ່ໄປນີ້. ໃບຢັ້ງຢືນ ACME ຈະຖືກຖອນໂດຍອັດຕະໂນມັດ. ຕ້ອງການສືບຕໍ່ບໍ?',
|
||||||
|
deleteAndRevoke: 'ລຶບ ແລະ ຖອນ',
|
||||||
create: 'ຮ້ອງຂໍ',
|
create: 'ຮ້ອງຂໍ',
|
||||||
provider: 'ປະເພດ',
|
provider: 'ປະເພດ',
|
||||||
manualCreate: 'ສ້າງດ້ວຍຕົນເອງ',
|
manualCreate: 'ສ້າງດ້ວຍຕົນເອງ',
|
||||||
@@ -4182,7 +4215,6 @@ const message = {
|
|||||||
dockerGuard: 'ການປ້ອງກັນພອດຄອນເທນເນີ',
|
dockerGuard: 'ການປ້ອງກັນພອດຄອນເທນເນີ',
|
||||||
systemFirewall: 'ໄຟວໍໂຮສ',
|
systemFirewall: 'ໄຟວໍໂຮສ',
|
||||||
systemFirewallHelper: 'ຄວບຄຸມການເຂົ້າເຖິງພອດໂຮສ ແລະ ກົດຂາເຂົ້າ.',
|
systemFirewallHelper: 'ຄວບຄຸມການເຂົ້າເຖິງພອດໂຮສ ແລະ ກົດຂາເຂົ້າ.',
|
||||||
forwardPolicyDropWarning: 'ເຊີບເວີນີ້ຈຳກັດການສົ່ງຕໍ່ເຄືອຂ່າຍ. ກົດການສົ່ງຕໍ່ພອດອາດບໍ່ເຮັດວຽກ.',
|
|
||||||
forwardingHelper: 'ຈັດການກົດການສົ່ງຕໍ່ພອດ.',
|
forwardingHelper: 'ຈັດການກົດການສົ່ງຕໍ່ພອດ.',
|
||||||
dockerFirewallHelper: 'ເລືອກວິທີທີ່ 1Panel ຈັດການການປ້ອງກັນພອດຄອນເທນເນີ.',
|
dockerFirewallHelper: 'ເລືອກວິທີທີ່ 1Panel ຈັດການການປ້ອງກັນພອດຄອນເທນເນີ.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, ທົດລອງ',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, ທົດລອງ',
|
||||||
@@ -4260,9 +4292,9 @@ const message = {
|
|||||||
'ກົດ INPUT ຂອງໂຮສບໍ່ໄດ້ປ້ອງກັນພອດ Docker ນີ້ໂດຍກົງ. ຄລິກເພື່ອເປີດການປ້ອງກັນພອດຄອນເທນເນີ.',
|
'ກົດ INPUT ຂອງໂຮສບໍ່ໄດ້ປ້ອງກັນພອດ Docker ນີ້ໂດຍກົງ. ຄລິກເພື່ອເປີດການປ້ອງກັນພອດຄອນເທນເນີ.',
|
||||||
notInitialized: 'ຍັງບໍ່ໄດ້ເລີ່ມຕົ້ນ',
|
notInitialized: 'ຍັງບໍ່ໄດ້ເລີ່ມຕົ້ນ',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກ: ອິນເຕີເຟດ {0} ມີ accept_ra=1 ແລະອາດອາໄສ RA/SLAAC. ກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍ, ຕັ້ງ accept_ra=2 ແບບຖາວອນໃຫ້ອິນເຕີເຟດທີ່ຕ້ອງການ RA ແລ້ວລອງໃໝ່.',
|
'ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເພື່ອປ້ອງກັນຄວາມສ່ຽງທີ່ເຄືອຂ່າຍຈະຂາດການເຊື່ອມຕໍ່. ເບິ່ງເອກະສານ ແລະ ບັນທຶກລະບົບ.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'ບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າ IPv6 RA ໄດ້. ການເປີດໃຊ້ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກ. ກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍແລ້ວລອງໃໝ່.',
|
'ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເນື່ອງຈາກບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍໄດ້. ເບິ່ງລາຍລະອຽດໃນບັນທຶກລະບົບ.',
|
||||||
ipv6ForwardingOnDemand: 'ຍັງບໍ່ໄດ້ເປີດໃຊ້ການສົ່ງຕໍ່ IPv6. ລະບົບຈະກວດສອບແລະເປີດໃຊ້ເມື່ອເພີ່ມຫຼືກູ້ຄືນກົດ IPv6.',
|
ipv6ForwardingOnDemand: 'ຍັງບໍ່ໄດ້ເປີດໃຊ້ການສົ່ງຕໍ່ IPv6. ລະບົບຈະກວດສອບແລະເປີດໃຊ້ເມື່ອເພີ່ມຫຼືກູ້ຄືນກົດ IPv6.',
|
||||||
familyUnsupported: 'ລະບົບບໍ່ຮອງຮັບ {0}',
|
familyUnsupported: 'ລະບົບບໍ່ຮອງຮັບ {0}',
|
||||||
familyChainIssue: '{0} · chain {1} · {2}',
|
familyChainIssue: '{0} · chain {1} · {2}',
|
||||||
|
|||||||
@@ -575,6 +575,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'Hentikan pengekalan sesi halaman',
|
cancelKeepAlive: 'Hentikan pengekalan sesi halaman',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'Pusat Pengguna',
|
||||||
|
exitSystem: 'Log keluar',
|
||||||
logout: 'Log keluar',
|
logout: 'Log keluar',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1077,6 +1079,13 @@ const message = {
|
|||||||
whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih',
|
whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI Gateway menyokong penggunaan kendiri',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI Gateway kini menyokong penggunaan kendiri. AI Gateway terbina dalam 1Panel tidak lagi akan menerima kemas kini. Kami mengesyorkan pemasangan versi kendiri daripada gedung aplikasi. Anda juga boleh terus menggunakan versi semasa.',
|
||||||
|
deploymentNoticeContinue: 'Faham, terus gunakan',
|
||||||
|
deploymentNoticeAppStore: 'Pergi ke gedung aplikasi',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'Anda tidak mempunyai akses ke gedung aplikasi. Hubungi pentadbir untuk memasang versi kendiri.',
|
||||||
title: 'Gateway AI',
|
title: 'Gateway AI',
|
||||||
apiReference: 'Rujukan API',
|
apiReference: 'Rujukan API',
|
||||||
notInstalled: 'Belum dipasang',
|
notInstalled: 'Belum dipasang',
|
||||||
@@ -1522,6 +1531,24 @@ const message = {
|
|||||||
extraHeaders: 'Pengepala Tambahan',
|
extraHeaders: 'Pengepala Tambahan',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/s',
|
||||||
|
requestsPerSecond: 'Permintaan/s',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'Selesai secara normal',
|
||||||
|
length: 'Had panjang dicapai',
|
||||||
|
abort: 'Dibatalkan',
|
||||||
|
error: 'Ralat',
|
||||||
|
repetition: 'Pengulangan',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'Analisis permintaan',
|
||||||
|
promptDistribution: 'Taburan panjang token input',
|
||||||
|
generationDistribution: 'Taburan panjang token output',
|
||||||
|
finishReasons: 'Sebab permintaan tamat',
|
||||||
|
queueTime: 'Purata masa dalam baris gilir',
|
||||||
|
averageGenerationTokens: 'Purata token output setiap permintaan',
|
||||||
|
prefixCacheHitRate: 'Kadar padanan cache awalan',
|
||||||
|
distributionHelper:
|
||||||
|
'Taburan panjang menunjukkan kadar permintaan dan tidak dipengaruhi oleh pilihan purata/puncak.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'Padamkan sejarah pemantauan untuk instans vLLM ini sahaja? Instans lain serta pemantauan hos dan GPU tidak terjejas.',
|
'Padamkan sejarah pemantauan untuk instans vLLM ini sahaja? Instans lain serta pemantauan hos dan GPU tidak terjejas.',
|
||||||
overview: 'Gambaran keseluruhan',
|
overview: 'Gambaran keseluruhan',
|
||||||
@@ -1882,6 +1909,11 @@ const message = {
|
|||||||
imageName: 'Nama imej',
|
imageName: 'Nama imej',
|
||||||
pull: 'Tarik',
|
pull: 'Tarik',
|
||||||
path: 'Laluan',
|
path: 'Laluan',
|
||||||
|
imageUploadDrop: 'Seret fail imej ke sini atau klik untuk memuat naik',
|
||||||
|
imageUploadHelper:
|
||||||
|
'Semua format fail dibenarkan. Docker mengesahkan kandungannya. Fail yang dimuat naik disimpan untuk mencuba semula import atau dipadam secara manual.',
|
||||||
|
imageUploadEmpty: 'Fail kosong. Pilih arkib imej yang sah.',
|
||||||
|
imageImportSubmitted: 'Tugas import imej telah dihantar. Semak log tugas untuk melihat hasilnya.',
|
||||||
importImage: 'Import',
|
importImage: 'Import',
|
||||||
buildArgs: 'Argumen Binaan',
|
buildArgs: 'Argumen Binaan',
|
||||||
imageBuild: 'Bina',
|
imageBuild: 'Bina',
|
||||||
@@ -4231,6 +4263,8 @@ const message = {
|
|||||||
'Mengaktifkan ini boleh mengelakkan kebocoran sijil, menetapkan laman web lalai akan membatalkan tetapan ini',
|
'Mengaktifkan ini boleh mengelakkan kebocoran sijil, menetapkan laman web lalai akan membatalkan tetapan ini',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm: 'Sijil berikut akan dipadamkan. Sijil ACME akan dibatalkan secara automatik. Teruskan?',
|
||||||
|
deleteAndRevoke: 'Padam dan batalkan',
|
||||||
create: 'Permintaan',
|
create: 'Permintaan',
|
||||||
provider: 'Jenis',
|
provider: 'Jenis',
|
||||||
manualCreate: 'Dicipta secara manual',
|
manualCreate: 'Dicipta secara manual',
|
||||||
@@ -4374,8 +4408,6 @@ const message = {
|
|||||||
dockerGuard: 'Perlindungan port bekas',
|
dockerGuard: 'Perlindungan port bekas',
|
||||||
systemFirewall: 'Tembok api hos',
|
systemFirewall: 'Tembok api hos',
|
||||||
systemFirewallHelper: 'Mengawal akses port hos dan peraturan masuk.',
|
systemFirewallHelper: 'Mengawal akses port hos dan peraturan masuk.',
|
||||||
forwardPolicyDropWarning:
|
|
||||||
'Pelayan ini mengehadkan pemajuan rangkaian. Peraturan pemajuan port mungkin tidak berfungsi.',
|
|
||||||
forwardingHelper: 'Mengurus peraturan pemajuan port.',
|
forwardingHelper: 'Mengurus peraturan pemajuan port.',
|
||||||
dockerFirewallHelper: 'Memilih cara 1Panel mengurus perlindungan port bekas.',
|
dockerFirewallHelper: 'Memilih cara 1Panel mengurus perlindungan port bekas.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, percubaan',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, percubaan',
|
||||||
@@ -4459,9 +4491,9 @@ const message = {
|
|||||||
'Peraturan INPUT hos tidak melindungi port terbitan Docker ini secara langsung. Klik untuk membuka perlindungan port bekas.',
|
'Peraturan INPUT hos tidak melindungi port terbitan Docker ini secara langsung. Klik untuk membuka perlindungan port bekas.',
|
||||||
notInitialized: 'Belum dimulakan',
|
notInitialized: 'Belum dimulakan',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'Pemajuan IPv6 disekat: antara muka {0} mempunyai accept_ra=1 dan mungkin bergantung pada RA/SLAAC. Semak konfigurasi rangkaian, tetapkan accept_ra=2 secara kekal pada antara muka yang memerlukan RA, kemudian cuba lagi.',
|
'Pemajuan IPv6 disekat bagi mengelakkan risiko kehilangan sambungan rangkaian. Rujuk dokumentasi dan log sistem.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'Konfigurasi RA IPv6 tidak dapat diperiksa. Pengaktifan pemajuan IPv6 disekat. Semak konfigurasi rangkaian sistem dan cuba lagi.',
|
'Pemajuan IPv6 disekat kerana konfigurasi rangkaian tidak dapat diperiksa. Lihat log sistem untuk butiran.',
|
||||||
ipv6ForwardingOnDemand:
|
ipv6ForwardingOnDemand:
|
||||||
'Pemajuan IPv6 belum diaktifkan. Ia akan diperiksa dan diaktifkan apabila peraturan IPv6 ditambah atau dipulihkan.',
|
'Pemajuan IPv6 belum diaktifkan. Ia akan diperiksa dan diaktifkan apabila peraturan IPv6 ditambah atau dipulihkan.',
|
||||||
familyUnsupported: 'Sistem tidak menyokong {0}',
|
familyUnsupported: 'Sistem tidak menyokong {0}',
|
||||||
|
|||||||
@@ -568,6 +568,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'Parar de manter a sessão da página',
|
cancelKeepAlive: 'Parar de manter a sessão da página',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'Central do usuário',
|
||||||
|
exitSystem: 'Sair',
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1072,6 +1074,13 @@ const message = {
|
|||||||
'Após ativar, você pode desativar o acesso externo à porta em Loja de Apps - Instalado - Ollama - Parâmetros para melhorar a segurança.',
|
'Após ativar, você pode desativar o acesso externo à porta em Loja de Apps - Instalado - Ollama - Parâmetros para melhorar a segurança.',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI Gateway permite implantação independente',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'O AI Gateway agora permite implantação independente. O AI Gateway integrado ao 1Panel não receberá mais atualizações. Recomendamos instalar a versão independente pela loja de aplicativos. Você também pode continuar usando a versão atual.',
|
||||||
|
deploymentNoticeContinue: 'Entendido, continuar usando',
|
||||||
|
deploymentNoticeAppStore: 'Ir à loja de aplicativos',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'Você não tem acesso à loja de aplicativos. Contate o administrador para instalar a versão independente.',
|
||||||
title: 'Gateway de IA',
|
title: 'Gateway de IA',
|
||||||
apiReference: 'Referência da API',
|
apiReference: 'Referência da API',
|
||||||
notInstalled: 'Não instalado',
|
notInstalled: 'Não instalado',
|
||||||
@@ -1518,6 +1527,24 @@ const message = {
|
|||||||
extraHeaders: 'Cabeçalhos extras',
|
extraHeaders: 'Cabeçalhos extras',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/s',
|
||||||
|
requestsPerSecond: 'Solicitações/s',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'Conclusão normal',
|
||||||
|
length: 'Limite de comprimento atingido',
|
||||||
|
abort: 'Abortada',
|
||||||
|
error: 'Erro',
|
||||||
|
repetition: 'Repetição',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'Análise de solicitações',
|
||||||
|
promptDistribution: 'Distribuição do comprimento dos tokens de entrada',
|
||||||
|
generationDistribution: 'Distribuição do comprimento dos tokens de saída',
|
||||||
|
finishReasons: 'Motivos de conclusão das solicitações',
|
||||||
|
queueTime: 'Tempo médio na fila',
|
||||||
|
averageGenerationTokens: 'Média de tokens de saída por solicitação',
|
||||||
|
prefixCacheHitRate: 'Taxa de acertos do cache de prefixos',
|
||||||
|
distributionHelper:
|
||||||
|
'As distribuições de comprimento mostram taxas de solicitações e não mudam com a seleção de média ou pico.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'Limpar apenas o histórico de monitoramento desta instância do vLLM? Outras instâncias e o monitoramento do host e da GPU não serão afetados.',
|
'Limpar apenas o histórico de monitoramento desta instância do vLLM? Outras instâncias e o monitoramento do host e da GPU não serão afetados.',
|
||||||
overview: 'Visão geral',
|
overview: 'Visão geral',
|
||||||
@@ -1884,6 +1911,11 @@ const message = {
|
|||||||
imageName: 'Nome da imagem',
|
imageName: 'Nome da imagem',
|
||||||
pull: 'Puxar',
|
pull: 'Puxar',
|
||||||
path: 'Caminho',
|
path: 'Caminho',
|
||||||
|
imageUploadDrop: 'Arraste um arquivo de imagem para cá ou clique para enviar',
|
||||||
|
imageUploadHelper:
|
||||||
|
'Qualquer formato de arquivo é permitido. O Docker valida o conteúdo. Os arquivos enviados são mantidos para novas tentativas de importação ou exclusão manual.',
|
||||||
|
imageUploadEmpty: 'O arquivo está vazio. Selecione um arquivo de imagem válido.',
|
||||||
|
imageImportSubmitted: 'Tarefa de importação de imagem enviada. Consulte o log da tarefa para ver o resultado.',
|
||||||
importImage: 'Importar',
|
importImage: 'Importar',
|
||||||
buildArgs: 'Argumentos de Build',
|
buildArgs: 'Argumentos de Build',
|
||||||
imageBuild: 'Construção de imagem',
|
imageBuild: 'Construção de imagem',
|
||||||
@@ -4250,6 +4282,9 @@ const message = {
|
|||||||
'Ativar isso pode evitar vazamento de certificados, definir um site padrão invalidará esta configuração',
|
'Ativar isso pode evitar vazamento de certificados, definir um site padrão invalidará esta configuração',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm:
|
||||||
|
'Os certificados a seguir serão excluídos. Os certificados ACME serão revogados automaticamente. Continuar?',
|
||||||
|
deleteAndRevoke: 'Excluir e revogar',
|
||||||
create: 'Solicitar',
|
create: 'Solicitar',
|
||||||
provider: 'Tipo',
|
provider: 'Tipo',
|
||||||
manualCreate: 'Criado manualmente',
|
manualCreate: 'Criado manualmente',
|
||||||
@@ -4395,8 +4430,6 @@ const message = {
|
|||||||
dockerGuard: 'Proteção de portas de contêineres',
|
dockerGuard: 'Proteção de portas de contêineres',
|
||||||
systemFirewall: 'Firewall do host',
|
systemFirewall: 'Firewall do host',
|
||||||
systemFirewallHelper: 'Controla o acesso às portas do host e as regras de entrada.',
|
systemFirewallHelper: 'Controla o acesso às portas do host e as regras de entrada.',
|
||||||
forwardPolicyDropWarning:
|
|
||||||
'Este servidor restringe o encaminhamento de rede. As regras de encaminhamento de portas podem não funcionar.',
|
|
||||||
forwardingHelper: 'Gerencia regras de encaminhamento de portas.',
|
forwardingHelper: 'Gerencia regras de encaminhamento de portas.',
|
||||||
dockerFirewallHelper: 'Seleciona como o 1Panel gerencia a proteção de portas de contêineres.',
|
dockerFirewallHelper: 'Seleciona como o 1Panel gerencia a proteção de portas de contêineres.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
|
||||||
@@ -4482,9 +4515,9 @@ const message = {
|
|||||||
'As regras INPUT do host não protegem diretamente esta porta publicada pelo Docker. Clique para abrir a proteção de portas de contêineres.',
|
'As regras INPUT do host não protegem diretamente esta porta publicada pelo Docker. Clique para abrir a proteção de portas de contêineres.',
|
||||||
notInitialized: 'Não inicializado',
|
notInitialized: 'Não inicializado',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'O encaminhamento IPv6 foi bloqueado: as interfaces {0} têm accept_ra=1 e podem depender de RA/SLAAC. Verifique a configuração de rede, configure accept_ra=2 de forma persistente nas interfaces que precisam de RA e tente novamente.',
|
'O encaminhamento IPv6 foi bloqueado para evitar uma possível perda de conexão. Consulte a documentação e os logs do sistema.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'Não foi possível verificar a configuração RA do IPv6. A ativação do encaminhamento IPv6 foi bloqueada. Verifique a configuração de rede e tente novamente.',
|
'O encaminhamento IPv6 foi bloqueado porque não foi possível verificar a configuração de rede. Consulte os logs do sistema.',
|
||||||
ipv6ForwardingOnDemand:
|
ipv6ForwardingOnDemand:
|
||||||
'O encaminhamento IPv6 não está ativado. Ele será verificado e ativado ao adicionar ou restaurar regras IPv6.',
|
'O encaminhamento IPv6 não está ativado. Ele será verificado e ativado ao adicionar ou restaurar regras IPv6.',
|
||||||
familyUnsupported: 'O sistema não oferece suporte a {0}',
|
familyUnsupported: 'O sistema não oferece suporte a {0}',
|
||||||
|
|||||||
@@ -566,6 +566,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'Не сохранять сеанс страницы',
|
cancelKeepAlive: 'Не сохранять сеанс страницы',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'Центр пользователя',
|
||||||
|
exitSystem: 'Выйти из системы',
|
||||||
logout: 'Выход',
|
logout: 'Выход',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1065,6 +1067,13 @@ const message = {
|
|||||||
whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка',
|
whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI Gateway поддерживает отдельное развёртывание',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI Gateway теперь поддерживает отдельное развёртывание. Встроенный AI Gateway в 1Panel больше не будет получать обновления. Рекомендуем установить отдельную версию из магазина приложений. Вы также можете продолжить использовать текущую версию.',
|
||||||
|
deploymentNoticeContinue: 'Понятно, продолжить использование',
|
||||||
|
deploymentNoticeAppStore: 'Перейти в магазин приложений',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'У вас нет доступа к магазину приложений. Обратитесь к администратору для установки отдельной версии.',
|
||||||
title: 'AI-шлюз',
|
title: 'AI-шлюз',
|
||||||
apiReference: 'Справочник API',
|
apiReference: 'Справочник API',
|
||||||
notInstalled: 'Не установлен',
|
notInstalled: 'Не установлен',
|
||||||
@@ -1511,6 +1520,24 @@ const message = {
|
|||||||
extraHeaders: 'Дополнительные заголовки',
|
extraHeaders: 'Дополнительные заголовки',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/с',
|
||||||
|
requestsPerSecond: 'Запросов/с',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'Нормальное завершение',
|
||||||
|
length: 'Достигнут лимит длины',
|
||||||
|
abort: 'Прервано',
|
||||||
|
error: 'Ошибка',
|
||||||
|
repetition: 'Повторение',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'Анализ запросов',
|
||||||
|
promptDistribution: 'Распределение длины входных токенов',
|
||||||
|
generationDistribution: 'Распределение длины выходных токенов',
|
||||||
|
finishReasons: 'Причины завершения запросов',
|
||||||
|
queueTime: 'Среднее время в очереди',
|
||||||
|
averageGenerationTokens: 'Среднее число выходных токенов на запрос',
|
||||||
|
prefixCacheHitRate: 'Доля попаданий в кэш префиксов',
|
||||||
|
distributionHelper:
|
||||||
|
'Распределения длины показывают частоту запросов и не зависят от выбора среднего или пикового значения.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'Удалить историю мониторинга только этого экземпляра vLLM? Другие экземпляры, мониторинг хоста и GPU не будут затронуты.',
|
'Удалить историю мониторинга только этого экземпляра vLLM? Другие экземпляры, мониторинг хоста и GPU не будут затронуты.',
|
||||||
overview: 'Обзор',
|
overview: 'Обзор',
|
||||||
@@ -1874,6 +1901,11 @@ const message = {
|
|||||||
imageName: 'Имя образа',
|
imageName: 'Имя образа',
|
||||||
pull: 'Загрузить',
|
pull: 'Загрузить',
|
||||||
path: 'Путь',
|
path: 'Путь',
|
||||||
|
imageUploadDrop: 'Перетащите файл образа сюда или нажмите для загрузки',
|
||||||
|
imageUploadHelper:
|
||||||
|
'Допускаются файлы любого формата. Docker проверяет содержимое. Загруженные файлы сохраняются для повторного импорта или удаления вручную.',
|
||||||
|
imageUploadEmpty: 'Файл пуст. Выберите корректный архив образа.',
|
||||||
|
imageImportSubmitted: 'Задача импорта образа отправлена. Результат можно посмотреть в журнале задачи.',
|
||||||
importImage: 'Импорт',
|
importImage: 'Импорт',
|
||||||
buildArgs: 'Аргументы Сборки',
|
buildArgs: 'Аргументы Сборки',
|
||||||
imageBuild: 'Сборка',
|
imageBuild: 'Сборка',
|
||||||
@@ -4217,6 +4249,9 @@ const message = {
|
|||||||
'Включение этого может предотвратить утечку сертификатов, установка веб-сайта по умолчанию сделает эту настройку недействительной',
|
'Включение этого может предотвратить утечку сертификатов, установка веб-сайта по умолчанию сделает эту настройку недействительной',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm:
|
||||||
|
'Следующие сертификаты будут удалены. Сертификаты ACME будут автоматически отозваны. Продолжить?',
|
||||||
|
deleteAndRevoke: 'Удалить и отозвать',
|
||||||
create: 'Запросить',
|
create: 'Запросить',
|
||||||
provider: 'Тип',
|
provider: 'Тип',
|
||||||
manualCreate: 'Создан вручную',
|
manualCreate: 'Создан вручную',
|
||||||
@@ -4361,8 +4396,6 @@ const message = {
|
|||||||
dockerGuard: 'Защита портов контейнеров',
|
dockerGuard: 'Защита портов контейнеров',
|
||||||
systemFirewall: 'Брандмауэр хоста',
|
systemFirewall: 'Брандмауэр хоста',
|
||||||
systemFirewallHelper: 'Управляет доступом к портам хоста и входящими правилами.',
|
systemFirewallHelper: 'Управляет доступом к портам хоста и входящими правилами.',
|
||||||
forwardPolicyDropWarning:
|
|
||||||
'На этом сервере ограничена пересылка сетевого трафика. Правила перенаправления портов могут не работать.',
|
|
||||||
forwardingHelper: 'Управляет правилами перенаправления портов.',
|
forwardingHelper: 'Управляет правилами перенаправления портов.',
|
||||||
dockerFirewallHelper: 'Выбирает способ управления защитой портов контейнеров в 1Panel.',
|
dockerFirewallHelper: 'Выбирает способ управления защитой портов контейнеров в 1Panel.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, экспериментально',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, экспериментально',
|
||||||
@@ -4447,9 +4480,9 @@ const message = {
|
|||||||
'Правила INPUT хоста не защищают этот опубликованный Docker-порт напрямую. Нажмите, чтобы открыть защиту портов контейнеров.',
|
'Правила INPUT хоста не защищают этот опубликованный Docker-порт напрямую. Нажмите, чтобы открыть защиту портов контейнеров.',
|
||||||
notInitialized: 'Не инициализировано',
|
notInitialized: 'Не инициализировано',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'Пересылка IPv6 заблокирована: интерфейсы {0} имеют accept_ra=1 и могут зависеть от RA/SLAAC. Проверьте настройки сети, сохраните accept_ra=2 для интерфейсов, которым нужен RA, и повторите попытку.',
|
'Включение переадресации IPv6 заблокировано из-за риска потери связи. См. документацию и системные журналы.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'Не удалось проверить настройки RA для IPv6. Включение пересылки IPv6 заблокировано. Проверьте настройки сети и повторите попытку.',
|
'Включение переадресации IPv6 заблокировано: не удалось проверить настройки сети. Подробности в системных журналах.',
|
||||||
ipv6ForwardingOnDemand:
|
ipv6ForwardingOnDemand:
|
||||||
'Пересылка IPv6 не включена. Проверка и включение выполняются при добавлении или восстановлении правил IPv6.',
|
'Пересылка IPv6 не включена. Проверка и включение выполняются при добавлении или восстановлении правил IPv6.',
|
||||||
familyUnsupported: 'Система не поддерживает {0}',
|
familyUnsupported: 'Система не поддерживает {0}',
|
||||||
|
|||||||
@@ -570,6 +570,8 @@ const message = {
|
|||||||
cancelKeepAlive: 'Sayfa oturumunu korumayı bırak',
|
cancelKeepAlive: 'Sayfa oturumunu korumayı bırak',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: 'Kullanıcı Merkezi',
|
||||||
|
exitSystem: 'Çıkış yap',
|
||||||
logout: 'Çıkış',
|
logout: 'Çıkış',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1076,6 +1078,13 @@ const message = {
|
|||||||
whiteListHelper: 'Erişimi yalnızca beyaz listedeki IPlerle sınırlayın',
|
whiteListHelper: 'Erişimi yalnızca beyaz listedeki IPlerle sınırlayın',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI Gateway bağımsız dağıtımı destekliyor',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI Gateway artık bağımsız dağıtımı destekliyor. 1Panel içindeki AI Gateway artık güncelleme almayacak. Uygulama mağazasından bağımsız sürümü yüklemenizi öneririz. Mevcut sürümü kullanmaya da devam edebilirsiniz.',
|
||||||
|
deploymentNoticeContinue: 'Anladım, kullanmaya devam et',
|
||||||
|
deploymentNoticeAppStore: 'Uygulama mağazasına git',
|
||||||
|
deploymentNoticeNoAppPermission:
|
||||||
|
'Uygulama mağazasına erişim izniniz yok. Bağımsız sürümü yüklemek için yöneticinizle iletişime geçin.',
|
||||||
title: 'AI Ağ Geçidi',
|
title: 'AI Ağ Geçidi',
|
||||||
apiReference: 'API Referansı',
|
apiReference: 'API Referansı',
|
||||||
notInstalled: 'Kurulu değil',
|
notInstalled: 'Kurulu değil',
|
||||||
@@ -1518,6 +1527,23 @@ const message = {
|
|||||||
extraHeaders: 'Ek Başlıklar',
|
extraHeaders: 'Ek Başlıklar',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/sn',
|
||||||
|
requestsPerSecond: 'İstek/sn',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: 'Normal tamamlanma',
|
||||||
|
length: 'Uzunluk sınırına ulaşıldı',
|
||||||
|
abort: 'İptal edildi',
|
||||||
|
error: 'Hata',
|
||||||
|
repetition: 'Tekrarlama',
|
||||||
|
},
|
||||||
|
requestAnalysis: 'İstek analizi',
|
||||||
|
promptDistribution: 'Girdi belirteci uzunluk dağılımı',
|
||||||
|
generationDistribution: 'Çıktı belirteci uzunluk dağılımı',
|
||||||
|
finishReasons: 'İstek bitiş nedenleri',
|
||||||
|
queueTime: 'Ortalama kuyruk süresi',
|
||||||
|
averageGenerationTokens: 'İstek başına ortalama çıktı belirteci',
|
||||||
|
prefixCacheHitRate: 'Önek önbelleği isabet oranı',
|
||||||
|
distributionHelper: 'Uzunluk dağılımları istek hızlarını gösterir ve ortalama/tepe seçiminden etkilenmez.',
|
||||||
cleanHelper:
|
cleanHelper:
|
||||||
'Yalnızca bu vLLM örneğinin izleme geçmişi temizlensin mi? Diğer örnekler, ana makine ve GPU izlemesi etkilenmez.',
|
'Yalnızca bu vLLM örneğinin izleme geçmişi temizlensin mi? Diğer örnekler, ana makine ve GPU izlemesi etkilenmez.',
|
||||||
overview: 'Genel bakış',
|
overview: 'Genel bakış',
|
||||||
@@ -1883,6 +1909,11 @@ const message = {
|
|||||||
imageName: 'İmaj adı',
|
imageName: 'İmaj adı',
|
||||||
pull: 'Çek',
|
pull: 'Çek',
|
||||||
path: 'Yol',
|
path: 'Yol',
|
||||||
|
imageUploadDrop: 'Bir imaj dosyasını buraya sürükleyin veya yüklemek için tıklayın',
|
||||||
|
imageUploadHelper:
|
||||||
|
'Tüm dosya biçimlerine izin verilir. Docker içeriği doğrular. Yüklenen dosyalar, içe aktarmayı yeniden denemek veya elle silmek için saklanır.',
|
||||||
|
imageUploadEmpty: 'Dosya boş. Geçerli bir imaj arşivi seçin.',
|
||||||
|
imageImportSubmitted: 'İmajı içe aktarma görevi gönderildi. Sonuç için görev günlüğünü kontrol edin.',
|
||||||
importImage: 'İçe aktar',
|
importImage: 'İçe aktar',
|
||||||
buildArgs: 'Derleme Argümanları',
|
buildArgs: 'Derleme Argümanları',
|
||||||
imageBuild: 'Oluştur',
|
imageBuild: 'Oluştur',
|
||||||
@@ -4235,6 +4266,9 @@ const message = {
|
|||||||
'Etkinleştirilmesi sertifika sızıntısını önleyebilir, varsayılan bir web sitesi ayarlamak bu ayarı geçersiz kılar',
|
'Etkinleştirilmesi sertifika sızıntısını önleyebilir, varsayılan bir web sitesi ayarlamak bu ayarı geçersiz kılar',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm:
|
||||||
|
'Aşağıdaki sertifikalar silinecek. ACME sertifikaları otomatik olarak iptal edilecek. Devam edilsin mi?',
|
||||||
|
deleteAndRevoke: 'Sil ve iptal et',
|
||||||
create: 'İstek',
|
create: 'İstek',
|
||||||
provider: 'Tür',
|
provider: 'Tür',
|
||||||
manualCreate: 'Manuel olarak oluşturuldu',
|
manualCreate: 'Manuel olarak oluşturuldu',
|
||||||
@@ -4379,7 +4413,6 @@ const message = {
|
|||||||
dockerGuard: 'Konteyner portu koruması',
|
dockerGuard: 'Konteyner portu koruması',
|
||||||
systemFirewall: 'Ana makine güvenlik duvarı',
|
systemFirewall: 'Ana makine güvenlik duvarı',
|
||||||
systemFirewallHelper: 'Ana makine port erişimini ve gelen kuralları yönetir.',
|
systemFirewallHelper: 'Ana makine port erişimini ve gelen kuralları yönetir.',
|
||||||
forwardPolicyDropWarning: 'Bu sunucu ağ yönlendirmesini kısıtlıyor. Port yönlendirme kuralları çalışmayabilir.',
|
|
||||||
forwardingHelper: 'Port yönlendirme kurallarını yönetir.',
|
forwardingHelper: 'Port yönlendirme kurallarını yönetir.',
|
||||||
dockerFirewallHelper: '1Panel konteyner portu korumasının nasıl yönetileceğini seçer.',
|
dockerFirewallHelper: '1Panel konteyner portu korumasının nasıl yönetileceğini seçer.',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0, deneysel',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0, deneysel',
|
||||||
@@ -4466,9 +4499,9 @@ const message = {
|
|||||||
'Ana makine INPUT kuralları bu Docker yayımlanmış portunu doğrudan korumaz. Konteyner portu korumasını açmak için tıklayın.',
|
'Ana makine INPUT kuralları bu Docker yayımlanmış portunu doğrudan korumaz. Konteyner portu korumasını açmak için tıklayın.',
|
||||||
notInitialized: 'Başlatılmadı',
|
notInitialized: 'Başlatılmadı',
|
||||||
ipv6RARisk:
|
ipv6RARisk:
|
||||||
'IPv6 yönlendirme engellendi: {0} arayüzlerinde accept_ra=1 ayarlı ve bu arayüzler RA/SLAAC kullanıyor olabilir. Ağ yapılandırmasını kontrol edin, RA gereken arayüzlerde accept_ra=2 ayarını kalıcı olarak uygulayın ve yeniden deneyin.',
|
'Ağ bağlantısının kesilme riski nedeniyle IPv6 yönlendirmesi engellendi. Belgeleri ve sistem günlüklerini inceleyin.',
|
||||||
ipv6RACheckFailed:
|
ipv6RACheckFailed:
|
||||||
'IPv6 RA yapılandırması kontrol edilemedi. IPv6 yönlendirmeyi etkinleştirme engellendi. Sistem ağ yapılandırmasını kontrol edip yeniden deneyin.',
|
'Ağ yapılandırması denetlenemediği için IPv6 yönlendirmesi engellendi. Ayrıntılar için sistem günlüklerini inceleyin.',
|
||||||
ipv6ForwardingOnDemand:
|
ipv6ForwardingOnDemand:
|
||||||
'IPv6 yönlendirme etkin değil. IPv6 kuralları eklenirken veya geri yüklenirken kontrol edilip etkinleştirilecektir.',
|
'IPv6 yönlendirme etkin değil. IPv6 kuralları eklenirken veya geri yüklenirken kontrol edilip etkinleştirilecektir.',
|
||||||
familyUnsupported: 'Sistem {0} desteğini sunmuyor',
|
familyUnsupported: 'Sistem {0} desteğini sunmuyor',
|
||||||
|
|||||||
@@ -548,6 +548,8 @@ const message = {
|
|||||||
cancelKeepAlive: '取消保持頁面工作階段',
|
cancelKeepAlive: '取消保持頁面工作階段',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: '使用者中心',
|
||||||
|
exitSystem: '登出系統',
|
||||||
logout: '登出',
|
logout: '登出',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1006,6 +1008,12 @@ const message = {
|
|||||||
whiteListHelper: '限制僅白名單中的 IP 可瀏覽',
|
whiteListHelper: '限制僅白名單中的 IP 可瀏覽',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI 網關已支援獨立部署',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI 網關現已支援獨立部署,1Panel 內建 AI 網關後續將不再更新。建議前往應用程式商店安裝獨立版本,您也可以繼續使用目前版本。',
|
||||||
|
deploymentNoticeContinue: '已知悉,繼續使用',
|
||||||
|
deploymentNoticeAppStore: '前往應用程式商店',
|
||||||
|
deploymentNoticeNoAppPermission: '您沒有應用程式商店存取權限,請聯絡管理員安裝獨立版本。',
|
||||||
title: 'AI 閘道',
|
title: 'AI 閘道',
|
||||||
apiReference: '介面說明',
|
apiReference: '介面說明',
|
||||||
notInstalled: '未安裝',
|
notInstalled: '未安裝',
|
||||||
@@ -1429,6 +1437,23 @@ const message = {
|
|||||||
extraHeaders: '額外請求標頭',
|
extraHeaders: '額外請求標頭',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/秒',
|
||||||
|
requestsPerSecond: '請求/秒',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: '正常結束',
|
||||||
|
length: '達到長度限制',
|
||||||
|
abort: '中止',
|
||||||
|
error: '錯誤',
|
||||||
|
repetition: '重複輸出',
|
||||||
|
},
|
||||||
|
requestAnalysis: '請求分析',
|
||||||
|
promptDistribution: '輸入 Token 長度分佈',
|
||||||
|
generationDistribution: '輸出 Token 長度分佈',
|
||||||
|
finishReasons: '請求結束原因',
|
||||||
|
queueTime: '平均排隊耗時',
|
||||||
|
averageGenerationTokens: '每請求平均輸出 Token 數',
|
||||||
|
prefixCacheHitRate: '前綴快取命中率',
|
||||||
|
distributionHelper: '長度分佈按請求速率彙總,不受平均/峰值切換影響。',
|
||||||
cleanHelper: '僅清空目前 vLLM 實例的監控歷史,不影響其他實例、主機或 GPU 監控,是否繼續?',
|
cleanHelper: '僅清空目前 vLLM 實例的監控歷史,不影響其他實例、主機或 GPU 監控,是否繼續?',
|
||||||
overview: '概覽',
|
overview: '概覽',
|
||||||
latencyAnalysis: '延遲分析',
|
latencyAnalysis: '延遲分析',
|
||||||
@@ -1773,6 +1798,10 @@ const message = {
|
|||||||
delInsecureHelper: '刪除授信需要重新啟動 docker 服務,是否刪除?',
|
delInsecureHelper: '刪除授信需要重新啟動 docker 服務,是否刪除?',
|
||||||
pull: '拉取',
|
pull: '拉取',
|
||||||
path: '路徑',
|
path: '路徑',
|
||||||
|
imageUploadDrop: '拖曳映像檔案至此處,或點擊上傳',
|
||||||
|
imageUploadHelper: '不限制檔案格式,由 Docker 驗證內容。上傳的檔案會保留,可重試匯入或手動刪除。',
|
||||||
|
imageUploadEmpty: '不能上傳空檔案,請選擇有效的映像檔案。',
|
||||||
|
imageImportSubmitted: '映像匯入任務已提交,請查看任務日誌',
|
||||||
importImage: '匯入映像',
|
importImage: '匯入映像',
|
||||||
imageBuild: '構建映像',
|
imageBuild: '構建映像',
|
||||||
buildArgs: '構建參數',
|
buildArgs: '構建參數',
|
||||||
@@ -3890,6 +3919,8 @@ const message = {
|
|||||||
sslRejectHandshakeHelper: '開啟之後可以避免憑證洩露,設置預設網站會讓此設置失效',
|
sslRejectHandshakeHelper: '開啟之後可以避免憑證洩露,設置預設網站會讓此設置失效',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm: '將對以下憑證進行 刪除 操作,ACME 類型憑證會被自動撤銷,是否繼續?',
|
||||||
|
deleteAndRevoke: '刪除並撤銷',
|
||||||
create: '申請憑證',
|
create: '申請憑證',
|
||||||
provider: '類型',
|
provider: '類型',
|
||||||
manualCreate: '手動建立',
|
manualCreate: '手動建立',
|
||||||
@@ -4020,7 +4051,6 @@ const message = {
|
|||||||
dockerGuard: '容器連接埠防護',
|
dockerGuard: '容器連接埠防護',
|
||||||
systemFirewall: '主機防火牆',
|
systemFirewall: '主機防火牆',
|
||||||
systemFirewallHelper: '用於管理主機連接埠存取和入站規則。',
|
systemFirewallHelper: '用於管理主機連接埠存取和入站規則。',
|
||||||
forwardPolicyDropWarning: '目前伺服器限制了網路轉送,轉送規則可能無法生效。',
|
|
||||||
forwardingHelper: '用於管理連接埠轉發規則。',
|
forwardingHelper: '用於管理連接埠轉發規則。',
|
||||||
dockerFirewallHelper: '用於選擇 1Panel 容器連接埠防護的管理方式。',
|
dockerFirewallHelper: '用於選擇 1Panel 容器連接埠防護的管理方式。',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0,實驗性',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0,實驗性',
|
||||||
@@ -4092,9 +4122,8 @@ const message = {
|
|||||||
dockerInputUseHostFirewall: '此連接埠需要透過主機防火牆設定存取規則,請前往主機防火牆進行設定。',
|
dockerInputUseHostFirewall: '此連接埠需要透過主機防火牆設定存取規則,請前往主機防火牆進行設定。',
|
||||||
dockerInputNotProtected: '主機 INPUT 規則無法直接保護此 Docker 發佈連接埠,點擊前往容器連接埠防護。',
|
dockerInputNotProtected: '主機 INPUT 規則無法直接保護此 Docker 發佈連接埠,點擊前往容器連接埠防護。',
|
||||||
notInitialized: '未初始化',
|
notInitialized: '未初始化',
|
||||||
ipv6RARisk:
|
ipv6RARisk: 'IPv6 轉送可能導致網路中斷,已阻止啟用。請參閱文件,詳情見系統日誌。',
|
||||||
'IPv6 轉送已阻止:網路介面 {0} 的 accept_ra=1,可能依賴 RA/SLAAC。請確認網路設定,為需要接收 RA 的介面持久化設定 accept_ra=2 後重試。',
|
ipv6RACheckFailed: '無法檢查 IPv6 網路設定,已阻止啟用轉送。詳情見系統日誌。',
|
||||||
ipv6RACheckFailed: '無法檢查 IPv6 RA 設定,已阻止啟用 IPv6 轉送。請檢查系統網路設定後重試。',
|
|
||||||
ipv6ForwardingOnDemand: 'IPv6 轉送尚未啟用,將在新增或還原 IPv6 規則時檢查並啟用。',
|
ipv6ForwardingOnDemand: 'IPv6 轉送尚未啟用,將在新增或還原 IPv6 規則時檢查並啟用。',
|
||||||
familyUnsupported: '系統不支援 {0}',
|
familyUnsupported: '系統不支援 {0}',
|
||||||
familyChainIssue: '{0} · {1} 鏈 · {2}',
|
familyChainIssue: '{0} · {1} 鏈 · {2}',
|
||||||
|
|||||||
@@ -552,6 +552,8 @@ const message = {
|
|||||||
cancelKeepAlive: '取消保持页面会话',
|
cancelKeepAlive: '取消保持页面会话',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
userCenter: '用户中心',
|
||||||
|
exitSystem: '退出系统',
|
||||||
logout: '退出登录',
|
logout: '退出登录',
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
@@ -1019,6 +1021,12 @@ const message = {
|
|||||||
whiteListHelper: '限制仅白名单中的 IP 可访问',
|
whiteListHelper: '限制仅白名单中的 IP 可访问',
|
||||||
},
|
},
|
||||||
aiProxy: {
|
aiProxy: {
|
||||||
|
deploymentNoticeTitle: 'AI 网关已支持独立部署',
|
||||||
|
deploymentNoticeDescription:
|
||||||
|
'AI 网关现已支持独立部署,1Panel 内置 AI 网关后续将不再更新。建议前往应用商店安装独立版本,您也可以继续使用当前版本。',
|
||||||
|
deploymentNoticeContinue: '已知晓,继续使用',
|
||||||
|
deploymentNoticeAppStore: '前往应用商店',
|
||||||
|
deploymentNoticeNoAppPermission: '您没有应用商店访问权限,请联系管理员安装独立版本。',
|
||||||
title: 'AI 网关',
|
title: 'AI 网关',
|
||||||
apiReference: '接口说明',
|
apiReference: '接口说明',
|
||||||
notInstalled: '未安装',
|
notInstalled: '未安装',
|
||||||
@@ -1442,6 +1450,23 @@ const message = {
|
|||||||
extraHeaders: '额外请求头',
|
extraHeaders: '额外请求头',
|
||||||
},
|
},
|
||||||
vllmMonitor: {
|
vllmMonitor: {
|
||||||
|
tokensPerSecond: 'tokens/秒',
|
||||||
|
requestsPerSecond: '请求/秒',
|
||||||
|
finishReasonLabels: {
|
||||||
|
stop: '正常结束',
|
||||||
|
length: '达到长度限制',
|
||||||
|
abort: '中止',
|
||||||
|
error: '错误',
|
||||||
|
repetition: '重复输出',
|
||||||
|
},
|
||||||
|
requestAnalysis: '请求分析',
|
||||||
|
promptDistribution: '输入 Token 长度分布',
|
||||||
|
generationDistribution: '输出 Token 长度分布',
|
||||||
|
finishReasons: '请求结束原因',
|
||||||
|
queueTime: '平均排队耗时',
|
||||||
|
averageGenerationTokens: '每请求平均输出 Token 数',
|
||||||
|
prefixCacheHitRate: '前缀缓存命中率',
|
||||||
|
distributionHelper: '长度分布按请求速率汇总,不受平均/峰值切换影响。',
|
||||||
cleanHelper: '仅清空当前 vLLM 实例的监控历史,不影响其他实例、主机或 GPU 监控,是否继续?',
|
cleanHelper: '仅清空当前 vLLM 实例的监控历史,不影响其他实例、主机或 GPU 监控,是否继续?',
|
||||||
overview: '概览',
|
overview: '概览',
|
||||||
latencyAnalysis: '延迟分析',
|
latencyAnalysis: '延迟分析',
|
||||||
@@ -1793,6 +1818,10 @@ const message = {
|
|||||||
delInsecureHelper: '删除授信需要重启 docker 服务,是否删除?',
|
delInsecureHelper: '删除授信需要重启 docker 服务,是否删除?',
|
||||||
pull: '拉取',
|
pull: '拉取',
|
||||||
path: '路径',
|
path: '路径',
|
||||||
|
imageUploadDrop: '拖拽镜像文件到此处,或点击上传',
|
||||||
|
imageUploadHelper: '不限制文件格式,由 Docker 校验内容。上传的文件会保留,可重试导入或手动删除。',
|
||||||
|
imageUploadEmpty: '不能上传空文件,请选择有效的镜像文件。',
|
||||||
|
imageImportSubmitted: '镜像导入任务已提交,请查看任务日志',
|
||||||
importImage: '导入镜像',
|
importImage: '导入镜像',
|
||||||
imageBuild: '构建镜像',
|
imageBuild: '构建镜像',
|
||||||
buildArgs: '构建参数',
|
buildArgs: '构建参数',
|
||||||
@@ -3935,6 +3964,8 @@ const message = {
|
|||||||
sslRejectHandshakeHelper: '开启之后可以避免证书泄露,设置默认网站会让此设置失效',
|
sslRejectHandshakeHelper: '开启之后可以避免证书泄露,设置默认网站会让此设置失效',
|
||||||
},
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
|
deleteConfirm: '将对以下证书进行 删除 操作,ACME类型证书会被自动吊销,是否继续?',
|
||||||
|
deleteAndRevoke: '删除并吊销',
|
||||||
create: '申请证书',
|
create: '申请证书',
|
||||||
provider: '类型',
|
provider: '类型',
|
||||||
manualCreate: '手动创建',
|
manualCreate: '手动创建',
|
||||||
@@ -4066,7 +4097,6 @@ const message = {
|
|||||||
dockerGuard: '容器端口防护',
|
dockerGuard: '容器端口防护',
|
||||||
systemFirewall: '主机防火墙',
|
systemFirewall: '主机防火墙',
|
||||||
systemFirewallHelper: '用于管理主机端口访问和入站规则。',
|
systemFirewallHelper: '用于管理主机端口访问和入站规则。',
|
||||||
forwardPolicyDropWarning: '当前服务器限制了网络转发,转发规则可能无法生效。',
|
|
||||||
forwardingHelper: '用于管理端口转发规则。',
|
forwardingHelper: '用于管理端口转发规则。',
|
||||||
dockerFirewallHelper: '用于选择 1Panel 容器端口防护的管理方式。',
|
dockerFirewallHelper: '用于选择 1Panel 容器端口防护的管理方式。',
|
||||||
dockerNftablesRequirement: 'Docker ≥ 29.0.0,实验性',
|
dockerNftablesRequirement: 'Docker ≥ 29.0.0,实验性',
|
||||||
@@ -4138,9 +4168,8 @@ const message = {
|
|||||||
dockerInputUseHostFirewall: '该端口需要通过主机防火墙设置访问规则,请前往主机防火墙进行配置。',
|
dockerInputUseHostFirewall: '该端口需要通过主机防火墙设置访问规则,请前往主机防火墙进行配置。',
|
||||||
dockerInputNotProtected: '宿主机 INPUT 规则不能直接保护此 Docker 发布端口,点击前往容器端口防护。',
|
dockerInputNotProtected: '宿主机 INPUT 规则不能直接保护此 Docker 发布端口,点击前往容器端口防护。',
|
||||||
notInitialized: '未初始化',
|
notInitialized: '未初始化',
|
||||||
ipv6RARisk:
|
ipv6RARisk: 'IPv6 转发存在断网风险,已阻止启用。请参阅文档,详情见系统日志。',
|
||||||
'IPv6 转发已阻止:网卡 {0} 的 accept_ra=1,可能依赖 RA/SLAAC。请确认网络配置,为需要接收 RA 的网卡持久化设置 accept_ra=2 后重试。',
|
ipv6RACheckFailed: '无法检查 IPv6 网络配置,已阻止启用转发。详情见系统日志。',
|
||||||
ipv6RACheckFailed: '无法检查 IPv6 RA 配置,已阻止启用 IPv6 转发。请检查系统网络配置后重试。',
|
|
||||||
ipv6ForwardingOnDemand: 'IPv6 转发尚未启用,将在添加或恢复 IPv6 规则时检查并启用。',
|
ipv6ForwardingOnDemand: 'IPv6 转发尚未启用,将在添加或恢复 IPv6 规则时检查并启用。',
|
||||||
familyUnsupported: '系统不支持 {0}',
|
familyUnsupported: '系统不支持 {0}',
|
||||||
familyChainIssue: '{0} · {1} 链 · {2}',
|
familyChainIssue: '{0} · {1} 链 · {2}',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user